#include #include using namespace llarp::config; TEST_CASE("OptionDefinition int parse test", "[config]") { llarp::OptionDefinition def("foo", "bar", Default{42}); CHECK(def.getValue() == 42); CHECK(def.getNumberFound() == 0); CHECK(def.defaultValuesAsString().size() == 1); CHECK(def.defaultValuesAsString()[0] == "42"); CHECK_NOTHROW(def.parseValue("43")); CHECK(def.getValue() == 43); CHECK(def.getNumberFound() == 1); CHECK(def.defaultValuesAsString().size() == 1); CHECK(def.defaultValuesAsString()[0] == "42"); constexpr Default sqrt_625{25}; llarp::OptionDefinition def2("a", "b", sqrt_625); CHECK(def2.getValue() == 25); CHECK(def2.defaultValuesAsString().size() == 1); CHECK(def2.defaultValuesAsString()[0] == "25"); CHECK_NOTHROW(def2.parseValue("99")); CHECK(def2.getValue() == 99); CHECK(def2.getNumberFound() == 1); } TEST_CASE("OptionDefinition string parse test", "[config]") { llarp::OptionDefinition def("foo", "bar", Default{"test"}); CHECK(def.getValue() == "test"); CHECK(not def.defaultValuesAsString().empty()); CHECK(def.defaultValuesAsString()[0] == "test"); CHECK_NOTHROW(def.parseValue("foo")); CHECK(def.getValue() == "foo"); CHECK(def.getNumberFound() == 1); } TEST_CASE("OptionDefinition multiple parses test", "[config]") { { llarp::OptionDefinition def("foo", "bar", MultiValue, Default{8}); CHECK_NOTHROW(def.parseValue("9")); CHECK(def.getValue() == 9); CHECK(def.getNumberFound() == 1); // should allow since it is multi-value CHECK_NOTHROW(def.parseValue("12")); CHECK(def.getValue() == 9); // getValue() should return first value REQUIRE(def.getNumberFound() == 2); } { llarp::OptionDefinition def("foo", "baz", Default{4}); CHECK_NOTHROW(def.parseValue("3")); CHECK(def.getValue() == 3); CHECK(def.getNumberFound() == 1); // shouldn't allow since not multi-value CHECK_THROWS(def.parseValue("2")); CHECK(def.getNumberFound() == 1); } } TEST_CASE("OptionDefinition acceptor test", "[config]") { int test = -1; llarp::OptionDefinition def("foo", "bar", Default{42}, [&](int arg) { test = arg; }); CHECK_NOTHROW(def.tryAccept()); CHECK(def.getValue() == 42); CHECK(not def.defaultValues.empty()); CHECK(def.defaultValues[0] == 42); CHECK(test == 42); def.parseValue("43"); CHECK_NOTHROW(def.tryAccept()); CHECK(test == 43); } TEST_CASE("OptionDefinition acceptor throws test", "[config]") { llarp::OptionDefinition def("foo", "bar", Default{42}, [&](int arg) { (void)arg; throw std::runtime_error("FAIL"); }); REQUIRE_THROWS_WITH(def.tryAccept(), "FAIL"); } TEST_CASE("OptionDefinition tryAccept missing option test", "[config]") { int unset = -1; llarp::OptionDefinition def("foo", "bar", Required, [&](int arg) { (void)arg; unset = 0; // should never be called }); REQUIRE_THROWS_WITH(def.tryAccept(), "cannot call tryAccept() on [foo]:bar when required but no value available"); } TEST_CASE("ConfigDefinition basic add/get test", "[config]") { llarp::ConfigDefinition config{true}; config.defineOption(std::make_unique>( "router", "threads", Default{4})); CHECK(config.getConfigValue("router", "threads") == 4); CHECK_NOTHROW(config.addConfigValue( "router", "threads", "5")); CHECK(config.getConfigValue("router", "threads") == 5); } TEST_CASE("ConfigDefinition router/client-only tests", "[config]") { llarp::ConfigDefinition r_config{true}, c_config{false}; r_config.defineOption("router", "abc", Default{1}, RelayOnly); r_config.defineOption("router", "def", Default{1}, ClientOnly); r_config.defineOption("router", "ghi", Default{1}); c_config.defineOption("router", "abc", Default{1}, RelayOnly); c_config.defineOption("router", "def", Default{1}, ClientOnly); c_config.defineOption("router", "ghi", Default{1}); CHECK_NOTHROW(r_config.getConfigValue("router", "abc")); CHECK_THROWS(r_config.getConfigValue("router", "def")); CHECK_NOTHROW(r_config.getConfigValue("router", "ghi")); CHECK_THROWS(c_config.getConfigValue("router", "abc")); CHECK_NOTHROW(c_config.getConfigValue("router", "def")); CHECK_NOTHROW(c_config.getConfigValue("router", "ghi")); } TEST_CASE("ConfigDefinition missing def test", "[config]") { llarp::ConfigDefinition config{true}; CHECK_THROWS(config.addConfigValue("foo", "bar", "5")); CHECK_THROWS(config.getConfigValue("foo", "bar") == 5); config.defineOption(std::make_unique>( "quux", "bar", Default{4})); CHECK_THROWS(config.addConfigValue("foo", "bar", "5")); } TEST_CASE("ConfigDefinition required test", "[config]") { llarp::ConfigDefinition config{true}; config.defineOption(std::make_unique>( "router", "threads", Required)); CHECK_THROWS(config.validateRequiredFields()); config.addConfigValue("router", "threads", "12"); CHECK_NOTHROW(config.validateRequiredFields()); } TEST_CASE("ConfigDefinition section test", "[config]") { llarp::ConfigDefinition config{true}; config.defineOption(std::make_unique>( "foo", "bar", Required )); config.defineOption(std::make_unique>( "goo", "bar", Required )); CHECK_THROWS(config.validateRequiredFields()); config.addConfigValue("foo", "bar", "5"); CHECK_THROWS(config.validateRequiredFields()); CHECK_NOTHROW(config.addConfigValue("goo", "bar", "6")); CHECK_NOTHROW(config.validateRequiredFields()); CHECK(config.getConfigValue("foo", "bar") == 5); CHECK(config.getConfigValue("goo", "bar") == 6); } TEST_CASE("ConfigDefinition acceptAllOptions test", "[config]") { int fooBar = -1; std::string fooBaz = ""; llarp::ConfigDefinition config{true}; config.defineOption(std::make_unique>( "foo", "bar", Default{1}, [&](int arg) { fooBar = arg; })); config.defineOption(std::make_unique>( "foo", "baz", Default{"no"}, [&](std::string arg) { fooBaz = arg; })); config.addConfigValue("foo", "baz", "yes"); REQUIRE_NOTHROW(config.validateRequiredFields()); REQUIRE_NOTHROW(config.acceptAllOptions()); CHECK(fooBar == 1); CHECK(fooBaz == "yes"); } TEST_CASE("ConfigDefinition acceptAllOptions exception propagation test", "[config]") { llarp::ConfigDefinition config{true}; config.defineOption(std::make_unique>( "foo", "bar", Default{1}, [&](int arg) { (void)arg; throw std::runtime_error("FAIL"); })); REQUIRE_THROWS_WITH(config.acceptAllOptions(), "FAIL"); } TEST_CASE("ConfigDefinition defineOptions passthrough test", "[config]") { llarp::ConfigDefinition config{true}; config.defineOption("foo", "bar", Default{1}); CHECK(config.getConfigValue("foo", "bar") == 1); } TEST_CASE("ConfigDefinition undeclared definition basic test", "[config]") { llarp::ConfigDefinition config{true}; bool invoked = false; config.addUndeclaredHandler("foo", [&](std::string_view section, std::string_view name, std::string_view value) { CHECK(section == "foo"); CHECK(name == "bar"); CHECK(value == "val"); invoked = true; }); REQUIRE_NOTHROW(config.addConfigValue("foo", "bar", "val")); CHECK(invoked); } TEST_CASE("ConfigDefinition undeclared add more than once test", "[config]") { llarp::ConfigDefinition config{true}; std::string calledBy = ""; config.addUndeclaredHandler("foo", [&](std::string_view, std::string_view, std::string_view) { calledBy = "a"; }); REQUIRE_THROWS_WITH( config.addUndeclaredHandler("foo", [&](std::string_view, std::string_view, std::string_view) { calledBy = "b"; }), "section foo already has a handler"); REQUIRE_NOTHROW(config.addConfigValue("foo", "bar", "val")); CHECK(calledBy == "a"); } TEST_CASE("ConfigDefinition undeclared add/remove test", "[config]") { llarp::ConfigDefinition config{true}; std::string calledBy = ""; // add... REQUIRE_NOTHROW(config.addUndeclaredHandler("foo", [&](std::string_view, std::string_view, std::string_view) { calledBy = "a"; })); REQUIRE_NOTHROW(config.addConfigValue("foo", "bar", "val")); CHECK(calledBy == "a"); calledBy = ""; // ...then remove... REQUIRE_NOTHROW(config.removeUndeclaredHandler("foo")); CHECK_THROWS_WITH(config.addConfigValue("foo", "bar", "val"), "unrecognized section [foo]"); // ...then add again REQUIRE_NOTHROW(config.addUndeclaredHandler("foo", [&](std::string_view, std::string_view, std::string_view) { calledBy = "b"; })); REQUIRE_NOTHROW(config.addConfigValue("foo", "bar", "val")); CHECK(calledBy == "b"); } TEST_CASE("ConfigDefinition undeclared handler exception propagation test", "[config]") { llarp::ConfigDefinition config{true}; config.addUndeclaredHandler("foo", [](std::string_view, std::string_view, std::string_view) { throw std::runtime_error("FAIL"); }); REQUIRE_THROWS_WITH(config.addConfigValue("foo", "bar", "val"), "FAIL"); } TEST_CASE("ConfigDefinition undeclared handler wrong section", "[config]") { llarp::ConfigDefinition config{true}; config.addUndeclaredHandler("foo", [](std::string_view, std::string_view, std::string_view) { throw std::runtime_error("FAIL"); }); REQUIRE_THROWS_WITH(config.addConfigValue("argle", "bar", "val"), "unrecognized section [argle]"); } TEST_CASE("ConfigDefinition undeclared handler duplicate names", "[config]") { llarp::ConfigDefinition config{true}; int count = 0; config.addUndeclaredHandler("foo", [&](std::string_view, std::string_view, std::string_view) { count++; }); REQUIRE_NOTHROW(config.addConfigValue("foo", "k", "v")); REQUIRE_NOTHROW(config.addConfigValue("foo", "k", "v")); REQUIRE_NOTHROW(config.addConfigValue("foo", "k", "v")); REQUIRE(count == 3); } TEST_CASE("ConfigDefinition AssignmentAcceptor", "[config]") { llarp::ConfigDefinition config{true}; int val = -1; config.defineOption("foo", "bar", Default{2}, AssignmentAcceptor(val)); config.addConfigValue("foo", "bar", "3"); CHECK_NOTHROW(config.acceptAllOptions()); REQUIRE(val == 3); } TEST_CASE("ConfigDefinition multiple values", "[config]") { llarp::ConfigDefinition config{true}; std::vector values; config.defineOption("foo", "bar", MultiValue, Default{2}, [&](int arg) { values.push_back(arg); }); config.addConfigValue("foo", "bar", "1"); config.addConfigValue("foo", "bar", "2"); config.addConfigValue("foo", "bar", "3"); CHECK_NOTHROW(config.acceptAllOptions()); REQUIRE(values.size() == 3); CHECK(values[0] == 1); CHECK(values[1] == 2); CHECK(values[2] == 3); } TEST_CASE("ConfigDefinition [bind]iface regression", "[config regression]") { llarp::ConfigDefinition config{true}; std::string val1; std::string undeclaredName; std::string undeclaredValue; config.defineOption( "bind", "*", Default{"1090"}, [&](std::string arg) { val1 = arg; }); config.addUndeclaredHandler("bind", [&](std::string_view, std::string_view name, std::string_view value) { undeclaredName = std::string(name); undeclaredValue = std::string(value); }); config.addConfigValue("bind", "enp35s0", "1091"); CHECK_NOTHROW(config.acceptAllOptions()); CHECK(val1 == "1090"); CHECK(undeclaredName == "enp35s0"); CHECK(undeclaredValue == "1091"); } TEST_CASE("ConfigDefinition truthy/falsy bool values", "[config]") { // truthy values for (auto val : {"true", "on", "yes", "1"}) { llarp::OptionDefinition def("foo", "bar", Default{false}); // defaults to false auto maybe = def.getValue(); REQUIRE(maybe); CHECK(*maybe == false); // val should result in true CHECK_NOTHROW(def.parseValue(val)); maybe = def.getValue(); REQUIRE(maybe); CHECK(*maybe); } // falsy values for (auto val : {"false", "off", "no", "0"}) { llarp::OptionDefinition def("foo", "bar", Default{true}); // defaults to true auto maybe = def.getValue(); REQUIRE(maybe); CHECK(maybe == true); // val should result in false CHECK_NOTHROW(def.parseValue(val)); maybe = def.getValue(); REQUIRE(maybe); CHECK(maybe == false); } // illegal values for (auto val : {"", " ", "TRUE", "argle", " false", "2"}) { llarp::OptionDefinition def("foo", "bar", Default{true}); CHECK_THROWS(def.parseValue(val)); } }