169 lines
5.5 KiB
C++
169 lines
5.5 KiB
C++
#include <doctest/doctest.h>
|
|
|
|
#include "fgc/CameraMode.h"
|
|
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <string>
|
|
|
|
#include <unistd.h>
|
|
|
|
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");
|
|
}
|