Skip to content

Commit c4a7fba

Browse files
committed
Complete CLI test coverage with 12 new comprehensive tests
Device-Independent Tests (+7): - VerboseOption: --verbose flag - InvalidYUVConversion tests (3): Error handling for missing dimensions, format, non-existent files - ConvertI420ToImage tests (2): I420 Red and Green color conversion validation Device-Dependent Tests (+6): - CaptureWithOutputFormat: --format option - CaptureWithFPS: --fps option - CaptureWithTimeout: --timeout option - CaptureInvalidDevice: Invalid device handling - CaptureWithVerbose: Verbose capture - ShowDeviceInfoAll: All devices info Coverage: 25 tests (13 device-independent + 12 device-dependent) All tests passing.
1 parent df109de commit c4a7fba

1 file changed

Lines changed: 212 additions & 0 deletions

File tree

tests/test_ccap_cli.cpp

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,26 @@ RGB readBMPPixel(const fs::path& bmpPath, int x, int y) {
187187
return pixel;
188188
}
189189

190+
// Helper to create a solid color YUV I420 image
191+
void createSolidColorI420(const fs::path& path, int width, int height, uint8_t y, uint8_t u, uint8_t v) {
192+
std::ofstream file(path, std::ios::binary);
193+
if (!file.is_open()) {
194+
throw std::runtime_error("Failed to create YUV file");
195+
}
196+
197+
// Y plane
198+
std::vector<uint8_t> yPlane(width * height, y);
199+
file.write(reinterpret_cast<const char*>(yPlane.data()), yPlane.size());
200+
201+
// U plane
202+
std::vector<uint8_t> uPlane((width / 2) * (height / 2), u);
203+
file.write(reinterpret_cast<const char*>(uPlane.data()), uPlane.size());
204+
205+
// V plane
206+
std::vector<uint8_t> vPlane((width / 2) * (height / 2), v);
207+
file.write(reinterpret_cast<const char*>(vPlane.data()), vPlane.size());
208+
}
209+
190210
// Test fixture for device-independent tests
191211
class CCAPCLITest : public ::testing::Test {
192212
protected:
@@ -264,6 +284,57 @@ TEST_F(CCAPCLITest, NoArgumentsShowsHelp) {
264284
// EXPECT_NE(result.exitCode, 0);
265285
// }
266286

287+
TEST_F(CCAPCLITest, VerboseOption) {
288+
// Test that verbose flag is accepted
289+
auto result = runCLI("--verbose --help");
290+
EXPECT_EQ(result.exitCode, 0);
291+
EXPECT_THAT(result.output, ::testing::HasSubstr("Usage:"));
292+
}
293+
294+
TEST_F(CCAPCLITest, InvalidYUVConversion_MissingDimensions) {
295+
// Create a dummy YUV file
296+
fs::path yuvPath = testOutputDir / "test.yuv";
297+
createSolidColorNV12(yuvPath, 64, 64, 128, 128, 128);
298+
299+
// Try to convert without specifying dimensions (should fail)
300+
fs::path outputPath = testOutputDir / "output.bmp";
301+
std::string cmd = "--convert " + yuvPath.string() +
302+
" --yuv-format nv12" +
303+
" --convert-output " + outputPath.string();
304+
305+
auto result = runCLI(cmd);
306+
// Should fail or handle gracefully
307+
EXPECT_NE(result.exitCode, 0) << "Should fail without YUV dimensions";
308+
}
309+
310+
TEST_F(CCAPCLITest, InvalidYUVConversion_MissingFormat) {
311+
// Create a dummy YUV file
312+
fs::path yuvPath = testOutputDir / "test.yuv";
313+
createSolidColorNV12(yuvPath, 64, 64, 128, 128, 128);
314+
315+
// Try to convert without specifying format (should fail)
316+
fs::path outputPath = testOutputDir / "output.bmp";
317+
std::string cmd = "--convert " + yuvPath.string() +
318+
" --yuv-width 64 --yuv-height 64" +
319+
" --convert-output " + outputPath.string();
320+
321+
auto result = runCLI(cmd);
322+
// Should fail or handle gracefully
323+
EXPECT_NE(result.exitCode, 0) << "Should fail without YUV format";
324+
}
325+
326+
TEST_F(CCAPCLITest, InvalidYUVConversion_NonExistentFile) {
327+
// Try to convert a non-existent file
328+
fs::path yuvPath = testOutputDir / "nonexistent.yuv";
329+
fs::path outputPath = testOutputDir / "output.bmp";
330+
std::string cmd = "--convert " + yuvPath.string() +
331+
" --yuv-format nv12 --yuv-width 64 --yuv-height 64" +
332+
" --convert-output " + outputPath.string();
333+
334+
auto result = runCLI(cmd);
335+
EXPECT_NE(result.exitCode, 0) << "Should fail for non-existent input file";
336+
}
337+
267338
// ============================================================================
268339
// Device-Dependent Tests (requires camera)
269340
// ============================================================================
@@ -371,6 +442,105 @@ TEST_F(CCAPCLIDeviceTest, CaptureWithInternalFormat) {
371442
ASSERT_GT(imageCount, 0) << "No image files created";
372443
}
373444

445+
TEST_F(CCAPCLIDeviceTest, CaptureWithOutputFormat) {
446+
std::string outputDir = testOutputDir.string();
447+
auto result = runCLI("-d 0 -c 1 --format rgb24 -o " + outputDir);
448+
449+
// Camera exists, capture MUST succeed
450+
ASSERT_EQ(result.exitCode, 0) << "Capture command failed: " << result.output;
451+
452+
// Verify image file was created
453+
int imageCount = 0;
454+
for (const auto& entry : fs::directory_iterator(testOutputDir)) {
455+
if (entry.path().extension() == ".bmp") {
456+
imageCount++;
457+
}
458+
}
459+
460+
ASSERT_EQ(imageCount, 1) << "Expected 1 image file, found " << imageCount;
461+
}
462+
463+
TEST_F(CCAPCLIDeviceTest, CaptureWithFPS) {
464+
std::string outputDir = testOutputDir.string();
465+
// Test with different FPS (30 fps)
466+
auto result = runCLI("-d 0 -f 30 -c 1 -o " + outputDir);
467+
468+
ASSERT_EQ(result.exitCode, 0) << "Capture command failed: " << result.output;
469+
470+
// Verify image file was created
471+
int imageCount = 0;
472+
for (const auto& entry : fs::directory_iterator(testOutputDir)) {
473+
if (entry.path().extension() == ".bmp") {
474+
imageCount++;
475+
}
476+
}
477+
478+
ASSERT_EQ(imageCount, 1) << "Expected 1 image file, found " << imageCount;
479+
}
480+
481+
TEST_F(CCAPCLIDeviceTest, CaptureWithTimeout) {
482+
std::string outputDir = testOutputDir.string();
483+
// Test with short timeout (should still succeed for 1 frame)
484+
auto result = runCLI("-d 0 -t 3000 -c 1 -o " + outputDir);
485+
486+
ASSERT_EQ(result.exitCode, 0) << "Capture command failed: " << result.output;
487+
488+
// Verify image file was created
489+
int imageCount = 0;
490+
for (const auto& entry : fs::directory_iterator(testOutputDir)) {
491+
if (entry.path().extension() == ".bmp") {
492+
imageCount++;
493+
}
494+
}
495+
496+
ASSERT_EQ(imageCount, 1) << "Expected 1 image file, found " << imageCount;
497+
}
498+
499+
TEST_F(CCAPCLIDeviceTest, CaptureInvalidDevice) {
500+
std::string outputDir = testOutputDir.string();
501+
// Try to capture from device index 999 (should fail or fallback to default)
502+
auto result = runCLI("-d 999 -c 1 -o " + outputDir);
503+
504+
// Some implementations may fallback to default device instead of failing
505+
// So we just check that it doesn't crash
506+
// If it succeeds, verify output exists
507+
if (result.exitCode == 0) {
508+
// Fallback to default device, verify output
509+
int imageCount = 0;
510+
for (const auto& entry : fs::directory_iterator(testOutputDir)) {
511+
if (entry.path().extension() == ".bmp") {
512+
imageCount++;
513+
}
514+
}
515+
EXPECT_GT(imageCount, 0) << "If command succeeds, should create images";
516+
}
517+
// If it fails, that's also acceptable behavior
518+
}
519+
520+
TEST_F(CCAPCLIDeviceTest, CaptureWithVerbose) {
521+
std::string outputDir = testOutputDir.string();
522+
auto result = runCLI("--verbose -d 0 -c 1 -o " + outputDir);
523+
524+
ASSERT_EQ(result.exitCode, 0) << "Capture command failed: " << result.output;
525+
526+
// Verify image file was created
527+
int imageCount = 0;
528+
for (const auto& entry : fs::directory_iterator(testOutputDir)) {
529+
if (entry.path().extension() == ".bmp") {
530+
imageCount++;
531+
}
532+
}
533+
534+
ASSERT_EQ(imageCount, 1) << "Expected 1 image file, found " << imageCount;
535+
}
536+
537+
TEST_F(CCAPCLIDeviceTest, ShowDeviceInfoAll) {
538+
// Test showing info for all devices (-1 means all)
539+
auto result = runCLI("--device-info");
540+
EXPECT_EQ(result.exitCode, 0);
541+
EXPECT_THAT(result.output, ::testing::HasSubstr("Device"));
542+
}
543+
374544
// ============================================================================
375545
// Format Conversion Tests
376546
// ============================================================================
@@ -466,3 +636,45 @@ TEST_F(CCAPCLITest, ConvertNV12ToImage_White) {
466636
EXPECT_GT(pixel.g, 240) << "Green channel too low: " << (int)pixel.g;
467637
EXPECT_GT(pixel.b, 240) << "Blue channel too low: " << (int)pixel.b;
468638
}
639+
640+
TEST_F(CCAPCLITest, ConvertI420ToImage_Red) {
641+
// Create a solid red I420 image (64x64)
642+
fs::path yuvPath = testOutputDir / "test_i420_red.yuv";
643+
createSolidColorI420(yuvPath, 64, 64, 76, 84, 255);
644+
645+
fs::path outputPath = testOutputDir / "output_i420_red.bmp";
646+
std::string cmd = "--convert " + yuvPath.string() +
647+
" --yuv-format i420 --yuv-width 64 --yuv-height 64" +
648+
" --convert-output " + outputPath.string();
649+
650+
auto result = runCLI(cmd);
651+
ASSERT_EQ(result.exitCode, 0) << "Convert command failed: " << result.output;
652+
ASSERT_TRUE(fs::exists(outputPath)) << "Output BMP file not created";
653+
654+
// Read and verify pixel color (should be close to red)
655+
RGB pixel = readBMPPixel(outputPath, 32, 32);
656+
EXPECT_GT(pixel.r, 200) << "Red channel too low: " << (int)pixel.r;
657+
EXPECT_LT(pixel.g, 100) << "Green channel too high: " << (int)pixel.g;
658+
EXPECT_LT(pixel.b, 100) << "Blue channel too high: " << (int)pixel.b;
659+
}
660+
661+
TEST_F(CCAPCLITest, ConvertI420ToImage_Green) {
662+
// Create a solid green I420 image (64x64)
663+
fs::path yuvPath = testOutputDir / "test_i420_green.yuv";
664+
createSolidColorI420(yuvPath, 64, 64, 149, 43, 21);
665+
666+
fs::path outputPath = testOutputDir / "output_i420_green.bmp";
667+
std::string cmd = "--convert " + yuvPath.string() +
668+
" --yuv-format i420 --yuv-width 64 --yuv-height 64" +
669+
" --convert-output " + outputPath.string();
670+
671+
auto result = runCLI(cmd);
672+
ASSERT_EQ(result.exitCode, 0) << "Convert command failed: " << result.output;
673+
ASSERT_TRUE(fs::exists(outputPath)) << "Output BMP file not created";
674+
675+
// Read and verify pixel color (should be close to green)
676+
RGB pixel = readBMPPixel(outputPath, 32, 32);
677+
EXPECT_LT(pixel.r, 100) << "Red channel too high: " << (int)pixel.r;
678+
EXPECT_GT(pixel.g, 200) << "Green channel too low: " << (int)pixel.g;
679+
EXPECT_LT(pixel.b, 100) << "Blue channel too high: " << (int)pixel.b;
680+
}

0 commit comments

Comments
 (0)