#include "TestCommon.h" #include "ANSCustomFireNSmoke.h" // =========================================================================== // Unit Tests — no model files required // =========================================================================== class FireNSmokeUnitTest : public ::testing::Test { protected: ANSCustomFS detector; }; TEST_F(FireNSmokeUnitTest, EmptyFrameReturnsNoDetections) { cv::Mat empty; auto results = detector.RunInference(empty); EXPECT_TRUE(results.empty()); } TEST_F(FireNSmokeUnitTest, TinyFrameReturnsNoDetections) { cv::Mat tiny = TestUtils::CreateTestFrame(5, 5); auto results = detector.RunInference(tiny); EXPECT_TRUE(results.empty()); } TEST_F(FireNSmokeUnitTest, UninitializedDetectorReturnsNoDetections) { cv::Mat frame = TestUtils::CreateTestFrame(640, 480); auto results = detector.RunInference(frame); EXPECT_TRUE(results.empty()); } TEST_F(FireNSmokeUnitTest, RunInferenceWithCameraId) { cv::Mat frame = TestUtils::CreateTestFrame(640, 480); auto results = detector.RunInference(frame, "test_cam_01"); EXPECT_TRUE(results.empty()); } TEST_F(FireNSmokeUnitTest, ConfigureParametersReturnsValidConfig) { CustomParams params; bool result = detector.ConfigureParameters(params); EXPECT_TRUE(result); // Should have ExclusiveROIs ROI config ASSERT_FALSE(params.ROI_Config.empty()); EXPECT_EQ(params.ROI_Config[0].Name, "ExclusiveROIs"); EXPECT_TRUE(params.ROI_Config[0].Rectangle); EXPECT_FALSE(params.ROI_Config[0].Polygon); EXPECT_FALSE(params.ROI_Config[0].Line); EXPECT_EQ(params.ROI_Config[0].MinItems, 0); EXPECT_EQ(params.ROI_Config[0].MaxItems, 20); // Should have SmokeScore and Sensitivity parameters ASSERT_GE(params.Parameters.size(), 2u); bool hasSmokeScore = false; bool hasSensitivity = false; for (const auto& p : params.Parameters) { if (p.Name == "SmokeScore") { hasSmokeScore = true; EXPECT_EQ(p.DataType, "float"); EXPECT_EQ(p.MaxValue, 1); EXPECT_EQ(p.MinValue, 0); } if (p.Name == "Sensitivity") { hasSensitivity = true; EXPECT_EQ(p.DataType, "float"); } } EXPECT_TRUE(hasSmokeScore) << "Missing SmokeScore parameter"; EXPECT_TRUE(hasSensitivity) << "Missing Sensitivity parameter"; } TEST_F(FireNSmokeUnitTest, DestroySucceeds) { EXPECT_TRUE(detector.Destroy()); } TEST_F(FireNSmokeUnitTest, DestroyCanBeCalledMultipleTimes) { EXPECT_TRUE(detector.Destroy()); EXPECT_TRUE(detector.Destroy()); } TEST_F(FireNSmokeUnitTest, InitializeWithInvalidDirectoryFails) { std::string labelMap; bool result = detector.Initialize("C:\\NonExistent\\Path\\Model", 0.5f, labelMap); EXPECT_FALSE(result); } TEST_F(FireNSmokeUnitTest, OptimizeBeforeInitializeReturnsFalse) { EXPECT_FALSE(detector.OptimizeModel(true)); } // =========================================================================== // Integration Tests — require model files on disk // =========================================================================== class FireNSmokeIntegrationTest : public ::testing::Test { protected: ANSCustomFS detector; std::string labelMap; std::vector classes; void SetUp() override { if (!TestConfig::ModelExists(TestConfig::FIRE_SMOKE_MODEL_DIR)) { GTEST_SKIP() << "Fire/Smoke model not found at: " << TestConfig::FIRE_SMOKE_MODEL_DIR; } bool ok = detector.Initialize(TestConfig::FIRE_SMOKE_MODEL_DIR, 0.5f, labelMap); ASSERT_TRUE(ok) << "Failed to initialize Fire/Smoke detector"; classes = TestUtils::ParseLabelMap(labelMap); } void TearDown() override { detector.Destroy(); } }; TEST_F(FireNSmokeIntegrationTest, InitializeProducesLabelMap) { EXPECT_FALSE(labelMap.empty()); EXPECT_FALSE(classes.empty()); } TEST_F(FireNSmokeIntegrationTest, InferenceOnSolidFrameReturnsNoDetections) { cv::Mat frame = TestUtils::CreateTestFrame(1920, 1080); auto results = detector.RunInference(frame, "test_cam"); EXPECT_TRUE(results.empty()) << "Solid gray frame should not trigger fire/smoke"; } TEST_F(FireNSmokeIntegrationTest, InferenceOnSmallFrame) { cv::Mat frame = TestUtils::CreateTestFrame(320, 240); auto results = detector.RunInference(frame, "test_cam"); SUCCEED(); } TEST_F(FireNSmokeIntegrationTest, InferenceOnLargeFrame) { cv::Mat frame = TestUtils::CreateTestFrame(3840, 2160); auto results = detector.RunInference(frame, "test_cam"); SUCCEED(); } TEST_F(FireNSmokeIntegrationTest, DetectionResultFieldsAreValid) { if (!TestConfig::VideoExists(TestConfig::FIRE_SMOKE_VIDEO)) { GTEST_SKIP() << "Fire/Smoke test video not found"; } cv::VideoCapture cap(TestConfig::FIRE_SMOKE_VIDEO); ASSERT_TRUE(cap.isOpened()); bool detectionFound = false; for (int i = 0; i < 300 && !detectionFound; i++) { cv::Mat frame; if (!cap.read(frame)) break; auto results = detector.RunInference(frame, "test_cam"); for (const auto& obj : results) { detectionFound = true; EXPECT_GE(obj.confidence, 0.0f); EXPECT_LE(obj.confidence, 1.0f); EXPECT_GE(obj.box.width, 0); EXPECT_GE(obj.box.height, 0); EXPECT_TRUE(obj.classId == 0 || obj.classId == 2) << "Expected fire (0) or smoke (2), got classId=" << obj.classId; } } cap.release(); } TEST_F(FireNSmokeIntegrationTest, PerformanceBenchmark) { if (!TestConfig::VideoExists(TestConfig::FIRE_SMOKE_VIDEO)) { GTEST_SKIP() << "Fire/Smoke test video not found"; } auto [totalDetections, avgMs] = TestUtils::RunVideoFrames(detector, TestConfig::FIRE_SMOKE_VIDEO, 100); ASSERT_GE(totalDetections, 0) << "Video could not be opened"; std::cout << "[FireNSmoke] 100 frames: avg=" << avgMs << "ms/frame, " << "detections=" << totalDetections << std::endl; EXPECT_LT(avgMs, 200.0) << "Average inference time exceeds 200ms"; } TEST_F(FireNSmokeIntegrationTest, ThreadSafetyConcurrentInference) { cv::Mat frame1 = TestUtils::CreateTestFrame(640, 480, cv::Scalar(100, 100, 100)); cv::Mat frame2 = TestUtils::CreateTestFrame(640, 480, cv::Scalar(200, 200, 200)); std::vector results1, results2; std::thread t1([&]() { results1 = detector.RunInference(frame1, "cam_1"); }); std::thread t2([&]() { results2 = detector.RunInference(frame2, "cam_2"); }); t1.join(); t2.join(); SUCCEED(); }