#include #include "fgc/IniEdit.h" #include #include using namespace fgc; TEST_CASE("a Section.key splits on its LAST dot, since sections carry dots") { auto k = splitIniKey("Camera.rgb.id"); REQUIRE(k); CHECK(k->first == "Camera.rgb"); CHECK(k->second == "id"); CHECK_FALSE(splitIniKey("nodot")); CHECK_FALSE(splitIniKey(".key")); CHECK_FALSE(splitIniKey("Section.")); } TEST_CASE("the line limit is the one inih actually reads whole") { // 200-byte buffer, minus the newline and the terminator. CHECK(iniMaxLineContent() == 198); const std::string ok(198, 'x'); const std::string too_long(199, 'x'); CHECK(firstOverlongIniLine("a = 1\n" + ok + "\n") == 0); CHECK(firstOverlongIniLine("a = 1\n" + too_long + "\nb = 2\n") == 2); // A CRLF file is measured without its \r. CHECK(firstOverlongIniLine(ok + "\r\n") == 0); } TEST_CASE("parseIniString reads keys as the program does, and names a bad line") { auto kv = parseIniString("[General]\ntower_name = t1 ; note\n[Camera.rgb]\nid = D\n"); CHECK(kv.at("General.tower_name") == "t1"); CHECK(kv.at("Camera.rgb.id") == "D"); CHECK_THROWS_WITH(parseIniString("[General]\nno equals sign here\n"), doctest::Contains("line 2")); } TEST_CASE("applyIniSets edits in place, appends what is missing, keeps comments") { const std::string in = "; header\n" "[General]\n" "; the name\n" "tower_name = ExampleTower\n" "[Network]\n" "zkms_server_ip = 127.0.0.1\n"; const std::string out = applyIniSets( in, {{"General.tower_name", "north"}, {"Motor.yaw_center_count", "1000000"}}, ""); CHECK(out.find("; header\n") == 0); CHECK(out.find("; the name\ntower_name = north\n") != std::string::npos); CHECK(out.find("ExampleTower") == std::string::npos); CHECK(out.find("[Motor]\nyaw_center_count = 1000000\n") != std::string::npos); CHECK(parseIniString(out).at("Network.zkms_server_ip") == "127.0.0.1"); } TEST_CASE("applyIniSets keeps one ;prev generation when given a provenance") { const std::string in = "[Serial]\ndevice = /dev/serial/by-id/old\n"; const std::string out = applyIniSets(in, {{"Serial.device", "/dev/serial/by-id/new"}}, "install-tower"); CHECK(out.find(";prev device = /dev/serial/by-id/old") != std::string::npos); CHECK(parseIniString(out).at("Serial.device") == "/dev/serial/by-id/new"); } TEST_CASE("applyIniSets refuses a malformed key or a multi-line value") { CHECK_THROWS_AS(applyIniSets("", {{"nodot", "1"}}, ""), std::invalid_argument); CHECK_THROWS_AS(applyIniSets("", {{"A.b", "1\nc = 2"}}, ""), std::invalid_argument); } TEST_CASE("dropIniKeys comments a key out WITHOUT its value") { // The placeholder must not survive even as a comment: deploy.sh's guard greps // the raw file for DEV_0000000000 and would refuse to run the tower. const std::string in = "[Camera.rgb]\n" "enabled = true\n" "id = DEV_0000000000 ; USB DEV_ id\n" "[Serial]\n" "device = /dev/ttyACM0\n"; std::vector dropped; const std::string out = dropIniKeys( in, {{"Camera.rgb.id", "is set in hardware.ini"}, {"Serial.device", "is set elsewhere"}}, &dropped); CHECK(out.find("DEV_0000000000") == std::string::npos); CHECK(out.find("; id is set in hardware.ini\n") != std::string::npos); CHECK(out.find("; device is set elsewhere\n") != std::string::npos); CHECK(dropped == std::vector{"Camera.rgb.id", "Serial.device"}); const auto kv = parseIniString(out); CHECK(kv.count("Camera.rgb.id") == 0); CHECK(kv.at("Camera.rgb.enabled") == "true"); } TEST_CASE("dropIniKeys matches the section too, not just the key name") { const std::string in = "[IMU]\ndevice = /dev/ttyS0\n[Serial]\ndevice = /dev/ttyACM0\n"; const auto kv = parseIniString(dropIniKeys(in, {{"Serial.device", "gone"}})); CHECK(kv.at("IMU.device") == "/dev/ttyS0"); CHECK(kv.count("Serial.device") == 0); } TEST_CASE("dropIniKeys takes a dropped key's continuation lines with it") { // inih joins an indented line onto the value above it. Left behind, it would // attach itself to whatever key now precedes it. const std::string in = "[A]\nkeep = 1\ngone = first\n second\nafter = 2\n"; const auto kv = parseIniString(dropIniKeys(in, {{"A.gone", "x"}})); CHECK(kv.at("A.keep") == "1"); CHECK(kv.at("A.after") == "2"); CHECK(kv.count("A.gone") == 0); }