#include #include "fgc/CameraProfile.h" #include "fgc/Hash.h" #include #include #include #include #include using namespace fgc; namespace fs = std::filesystem; namespace { fs::path scratch(const std::string& tag) { fs::path d = fs::temp_directory_path() / ("fgc_prof_" + tag + "_" + std::to_string(::getpid())); fs::remove_all(d); fs::create_directories(d); return d; } std::string xmlWith(const std::string& rate) { return "" "" "" "" ""; } void write(const fs::path& p, const std::string& text) { std::ofstream f(p, std::ios::binary); f << text; } } // namespace TEST_CASE("loadCameraProfile generates the record on first load") { fs::path d = scratch("first"); write(d / "standard.xml", xmlWith("10")); ProfileLoadResult r; CameraProfile p = loadCameraProfile(d.string(), "standard", r); CHECK(p.name == "standard"); CHECK(p.xml_path == (d / "standard.xml").string()); CHECK(p.txt_path == (d / "standard.txt").string()); CHECK(fs::is_regular_file(p.txt_path)); CHECK(p.settings.entries.size() == 2); // A first load has no baseline: reporting every setting as "new" would be // thousands of lines and no information for a real export. CHECK(r.regenerated); CHECK(r.first_run); CHECK(r.changes.empty()); // The record carries the hash of the XML it came from. SettingsRecord rec = readSettingsRecord(p.txt_path); CHECK(rec.hash_label == hashLabel(fnv1a64File(p.xml_path))); fs::remove_all(d); } TEST_CASE("an unchanged XML is not regenerated and reports no changes") { fs::path d = scratch("stable"); write(d / "standard.xml", xmlWith("10")); ProfileLoadResult r; loadCameraProfile(d.string(), "standard", r); const auto stamp = fs::last_write_time(d / "standard.txt"); loadCameraProfile(d.string(), "standard", r); CHECK_FALSE(r.regenerated); CHECK(r.changes.empty()); // Untouched: a rewrite every startup would churn the file and its timestamp. CHECK(fs::last_write_time(d / "standard.txt") == stamp); fs::remove_all(d); } TEST_CASE("a changed XML regenerates the record and names what moved") { fs::path d = scratch("changed"); write(d / "standard.xml", xmlWith("10")); ProfileLoadResult r; loadCameraProfile(d.string(), "standard", r); write(d / "standard.xml", xmlWith("8")); CameraProfile p = loadCameraProfile(d.string(), "standard", r); CHECK(r.regenerated); CHECK_FALSE(r.first_run); // there WAS a baseline, so this is a real change REQUIRE(r.changes.size() == 1); CHECK(r.changes[0].find("AcquisitionFrameRate") != std::string::npos); CHECK(r.changes[0].find("10 -> 8") != std::string::npos); // And the record now matches the new XML. SettingsRecord rec = readSettingsRecord(p.txt_path); CHECK(rec.hash_label == hashLabel(fnv1a64File(p.xml_path))); fs::remove_all(d); } TEST_CASE("a missing XML throws - the record is never a fallback") { fs::path d = scratch("missing"); // A record on its own must not make the profile loadable: it is a record, not // a configuration source, and applying one would defeat the GUI validation // that the XML exists to guarantee. write(d / "standard.txt", "[Source]\nxml_hash = fnv1a64:0000000000000000\n[Settings]\nGamma = 1\n"); ProfileLoadResult r; CHECK_THROWS(loadCameraProfile(d.string(), "standard", r)); fs::remove_all(d); } TEST_CASE("loadCameraProfiles picks up every *.xml and fails loudly when there are none") { fs::path d = scratch("dir"); std::map results; CHECK_THROWS(loadCameraProfiles(d.string(), results)); // empty directory CHECK_THROWS(loadCameraProfiles((d / "nope").string(), results)); // missing directory write(d / "standard.xml", xmlWith("10")); write(d / "night.xml", xmlWith("1")); write(d / "notes.md", "not a profile"); auto profiles = loadCameraProfiles(d.string(), results); CHECK(profiles.size() == 2); CHECK(profiles.count("standard") == 1); CHECK(profiles.count("night") == 1); CHECK(profiles.count("notes") == 0); // Both were written for the first time, and neither reports a change. REQUIRE(results.size() == 2); for (const auto& [name, r] : results) { CHECK(r.first_run); CHECK(r.changes.empty()); } // One bad profile fails the whole load, so a profile nobody uses today cannot // rot unnoticed until the night someone switches to it. write(d / "broken.xml", ""); CHECK_THROWS(loadCameraProfiles(d.string(), results)); fs::remove_all(d); }