fwt_software/tests/test_iniedit.cpp

107 lines
4.5 KiB
C++

#include <doctest/doctest.h>
#include "fgc/IniEdit.h"
#include <stdexcept>
#include <string>
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<std::string> 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<std::string>{"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);
}