#include #include "fgc/CameraMode.h" #include #include #include #include using namespace fgc; namespace fs = std::filesystem; namespace { // Each test gets its own directory so a stale file can never leak between them. fs::path scratchDir(const std::string& tag) { fs::path d = fs::temp_directory_path() / ("fgc_modes_" + tag + "_" + std::to_string(::getpid())); fs::remove_all(d); fs::create_directories(d); return d; } void write(const fs::path& p, const std::string& text) { std::ofstream f(p); f << text; } const char* kMinimal = R"( [Mode] description = A mode settle_wait_ms = 1500 frame_timeout_ms = 3000 [Camera] AcquisitionFrameRate = 10 ExposureAuto = Continuous )"; } // namespace TEST_CASE("loadCameraMode reads the header and names the mode after the file") { fs::path d = scratchDir("header"); write(d / "standard.ini", kMinimal); CameraMode m = loadCameraMode((d / "standard.ini").string()); CHECK(m.name == "standard"); CHECK(m.description == "A mode"); CHECK(m.settle_wait_ms == 1500); CHECK(m.frame_timeout_ms == 3000); REQUIRE(m.features.size() == 2); fs::remove_all(d); } TEST_CASE("loadCameraMode defaults the [Mode] header when it is absent") { fs::path d = scratchDir("defaults"); write(d / "bare.ini", "[Camera]\nGamma = 1.0\n"); CameraMode m = loadCameraMode((d / "bare.ini").string()); CHECK(m.description.empty()); CHECK(m.settle_wait_ms == 1000); CHECK(m.frame_timeout_ms == 2000); fs::remove_all(d); } TEST_CASE("loadCameraMode preserves file order and repeated keys") { // GenICam selectors are stateful, so both properties are load-bearing: a map // would sort BalanceRatio ahead of BalanceRatioSelector and collapse the two // ratios into one. fs::path d = scratchDir("order"); write(d / "wb.ini", R"( [Camera] BalanceRatioSelector = Red BalanceRatio = 2.87 BalanceRatioSelector = Blue BalanceRatio = 2.0 )"); CameraMode m = loadCameraMode((d / "wb.ini").string()); REQUIRE(m.features.size() == 4); CHECK(m.features[0] == std::make_pair(std::string("BalanceRatioSelector"), std::string("Red"))); CHECK(m.features[1] == std::make_pair(std::string("BalanceRatio"), std::string("2.87"))); CHECK(m.features[2] == std::make_pair(std::string("BalanceRatioSelector"), std::string("Blue"))); CHECK(m.features[3] == std::make_pair(std::string("BalanceRatio"), std::string("2.0"))); fs::remove_all(d); } TEST_CASE("loadCameraMode strips inline comments from feature values") { fs::path d = scratchDir("comments"); write(d / "c.ini", "[Camera]\nExposureAutoMax = 20000 ; blur budget\n"); CameraMode m = loadCameraMode((d / "c.ini").string()); REQUIRE(m.features.size() == 1); CHECK(m.features[0].second == "20000"); fs::remove_all(d); } TEST_CASE("loadCameraMode rejects files a typo would otherwise make silently wrong") { fs::path d = scratchDir("bad"); write(d / "unknown_key.ini", "[Mode]\nsettle_wait = 500\n[Camera]\nGamma = 1\n"); CHECK_THROWS(loadCameraMode((d / "unknown_key.ini").string())); write(d / "unknown_section.ini", "[Modes]\ndescription = x\n[Camera]\nGamma = 1\n"); CHECK_THROWS(loadCameraMode((d / "unknown_section.ini").string())); write(d / "bad_value.ini", "[Mode]\nsettle_wait_ms = soon\n[Camera]\nGamma = 1\n"); CHECK_THROWS(loadCameraMode((d / "bad_value.ini").string())); write(d / "negative.ini", "[Mode]\nsettle_wait_ms = -1\n[Camera]\nGamma = 1\n"); CHECK_THROWS(loadCameraMode((d / "negative.ini").string())); // A mode that sets nothing on the camera is a mistake, not an empty mode. write(d / "no_features.ini", "[Mode]\ndescription = nothing\n"); CHECK_THROWS(loadCameraMode((d / "no_features.ini").string())); CHECK_THROWS(loadCameraMode((d / "does_not_exist.ini").string())); fs::remove_all(d); } TEST_CASE("loadCameraModes picks up every *.ini in the directory") { fs::path d = scratchDir("dir"); write(d / "standard.ini", kMinimal); write(d / "night.ini", kMinimal); write(d / "notes.txt", "not a mode"); auto modes = loadCameraModes(d.string()); CHECK(modes.size() == 2); CHECK(modes.count("standard") == 1); CHECK(modes.count("night") == 1); CHECK(modes.count("notes") == 0); fs::remove_all(d); } TEST_CASE("loadCameraModes fails loudly on an empty or missing directory") { fs::path d = scratchDir("empty"); CHECK_THROWS(loadCameraModes(d.string())); CHECK_THROWS(loadCameraModes((d / "nope").string())); // One bad file fails the whole load, so a mode nobody uses today still cannot // rot unnoticed until the night someone switches to it. write(d / "good.ini", kMinimal); write(d / "bad.ini", "[Mode]\nnonsense = 1\n"); CHECK_THROWS(loadCameraModes(d.string())); fs::remove_all(d); } TEST_CASE("the shipped RGB mode files parse and define both modes") { // Guards the real config/camera_modes/rgb files, which the rig depends on. for (const char* rel : {"config/camera_modes/rgb", "../config/camera_modes/rgb"}) { if (!fs::is_directory(rel)) continue; auto modes = loadCameraModes(rel); REQUIRE(modes.count("standard") == 1); REQUIRE(modes.count("night") == 1); CHECK(modes.at("standard").settle_wait_ms == 1000); CHECK(modes.at("night").settle_wait_ms == 2500); CHECK(modes.at("standard").features.size() > 40); return; } MESSAGE("shipped mode files not reachable from the test working directory; skipped"); }