// // options_test.cpp // program_args-test // // Created by Sam Jaffe on 2/7/21. // #include "program_args/arguments.h" #include #include "xcode_gtest_helper.h" using testing::ElementsAre; using testing::Eq; using testing::IsEmpty; template static T parse(char const * const (&argv)[N]) { return T(N, argv); } struct LongOptionTest : program::Arguments { using Arguments::Arguments; std::string endpoint = option("endpoint"); int port = option("port"); }; TEST(LongOptionTest, ArgumentsAreDefaultedNaturally) { auto const options = parse({""}); EXPECT_THAT(options.endpoint, Eq("")); EXPECT_THAT(options.port, Eq(0)); } TEST(LongOptionTest, CanProvideStringArgument) { auto const options = parse({"", "--endpoint", "/null"}); EXPECT_THAT(options.endpoint, Eq("/null")); } TEST(LongOptionTest, CanProvideNumericArgument) { auto const options = parse({"", "--port", "443"}); EXPECT_THAT(options.port, Eq(443)); } TEST(LongOptionTest, WillThrowOnInvalidNumericArgument) { EXPECT_THROW(parse({"", "--port", "one"}), program::ProgramArgumentsError); } TEST(LongOptionTest, WillThrowOnRepeatedArgument) { EXPECT_THROW(parse({"", "--port", "8080", "--port", "443"}), program::ArgumentStructureError); } TEST(LongOptionTest, WillThrowOnUnknownArgument) { EXPECT_THROW(parse({"", "--path", "/null"}), program::NotAnArgumentError); } TEST(LongOptionTest, DoesNotImplicitlyShortenArgs) { EXPECT_THROW(parse({"", "-p", "443"}), program::NotAnArgumentError); } struct LongOptionWithDefaultTest : program::Arguments { using Arguments::Arguments; std::string endpoint = option("endpoint") = "/default"; int port = option("port") = 8080; }; TEST(LongOptionWithDefaultTest, ArgumentsAreDefaultedNaturally) { auto const options = parse({""}); EXPECT_THAT(options.endpoint, Eq("/default")); EXPECT_THAT(options.port, Eq(8080)); } TEST(LongOptionWithDefaultTest, CanProvideStringArgument) { auto const options = parse({"", "--endpoint", "/null"}); EXPECT_THAT(options.endpoint, Eq("/null")); } TEST(LongOptionWithDefaultTest, CanProvideNumericArgument) { auto const options = parse({"", "--port", "443"}); EXPECT_THAT(options.port, Eq(443)); } struct LongOptionWithAbbrevTest : program::Arguments { using Arguments::Arguments; std::string endpoint = option("endpoint", 'e'); int port = option("port", 'p'); }; TEST(LongOptionWithAbbrevTest, CanProvideStringArgument) { auto const options = parse({"", "--endpoint", "/null"}); EXPECT_THAT(options.endpoint, Eq("/null")); } TEST(LongOptionWithAbbrevTest, CanProvideAbbrevStringArgument) { auto const options = parse({"", "-e", "/null"}); EXPECT_THAT(options.endpoint, Eq("/null")); } TEST(LongOptionWithAbbrevTest, CanProvideNumericArgument) { auto const options = parse({"", "--port", "443"}); EXPECT_THAT(options.port, Eq(443)); } TEST(LongOptionWithAbbrevTest, CanProvideAbbrevNumericArgument) { auto const options = parse({"", "-p", "443"}); EXPECT_THAT(options.port, Eq(443)); } TEST(LongOptionWithAbbrevTest, ShortAndLongArgsGoToSamePool) { EXPECT_THROW( parse({"", "--port", "8080", "-p", "443"}), program::ArgumentStructureError); } TEST(LongOptionWithAbbrevTest, CanPutAbbrevArgAndValueInSameToken) { EXPECT_NO_THROW(parse({"", "-p443"})); auto const options = parse({"", "-p443"}); EXPECT_THAT(options.port, Eq(443)); } struct LongOptionRepeatTest : program::Arguments { using Arguments::Arguments; std::vector port = option("port"); }; TEST(LongOptionRepeatTest, DefaultIsEmpty) { auto const options = parse({""}); EXPECT_THAT(options.port, IsEmpty()); } TEST(LongOptionRepeatTest, CanProvideArgument) { auto const options = parse({"", "--port", "443"}); EXPECT_THAT(options.port, ElementsAre(443)); } TEST(LongOptionRepeatTest, RepeatingArgumentsAppends) { auto const options = parse({"", "--port", "443", "--port", "8080"}); EXPECT_THAT(options.port, ElementsAre(443, 8080)); } struct LongOptionRepeatWithDefaultTest : program::Arguments { using Arguments::Arguments; std::vector port = option("port") = 8080; std::vector default_ports = option("default_ports") = {80, 443}; }; TEST(LongOptionRepeatWithDefaultTest, DefaultIsProvided) { auto const options = parse({""}); EXPECT_THAT(options.port, ElementsAre(8080)); EXPECT_THAT(options.default_ports, ElementsAre(80, 443)); } TEST(LongOptionRepeatWithDefaultTest, ArgumentOverwritesDefault) { auto const options = parse({"", "--port", "443"}); EXPECT_THAT(options.port, ElementsAre(443)); } namespace fs = std::filesystem; struct DeferOptionTest : program::Arguments { using Arguments::Arguments; fs::path path = option("path") = "."; fs::path log = option("log") = PROGRAM_DEFER(path / "test.log"); }; TEST(DeferOptionTest, DefaultResolves) { auto const options = parse({""}); EXPECT_THAT(options.log.string(), "./test.log"); } TEST(DeferOptionTest, CapturesUpdateToDependent) { auto const options = parse({"", "--path", "/var/log"}); EXPECT_THAT(options.log.string(), "/var/log/test.log"); } TEST(DeferOptionTest, SettingActualArgWillOverwrite) { auto const options = parse({"", "--path", "/var/log", "--log", "test.log"}); EXPECT_THAT(options.log.string(), "test.log"); } struct ShortOptionTest : program::Arguments { using Arguments::Arguments; int arg = option('n'); }; TEST(ShortOptionTest, DoesNotAllowDoubleDash) { EXPECT_THROW(parse({"", "--n", "5"}), program::NotAnArgumentError); } TEST(ShortOptionTest, CanConstructWithOnlyAbbrev) { auto const options = parse({"", "-n", "5"}); EXPECT_THAT(options.arg, 5); } struct GeneratedDefaultTest : program::Arguments { using Arguments::Arguments; std::string pwd = option('C'); std::string git_dir = option("git-dir") = PROGRAM_DEFER(pwd + "/.git"); int arg = option('n') = []() { return 7; }; }; TEST(GeneratedDefaultTest, DefaultCanComeFromFunction) { auto const options = parse({""}); EXPECT_THAT(options.arg, 7); } TEST(GeneratedDefaultTest, CanSetValue) { auto const options = parse({"", "-n", "5"}); EXPECT_THAT(options.arg, 5); } TEST(GeneratedDefaultTest, CanReferenceThis) { auto const options = parse({"", "-C", "/opt/local/bin"}); EXPECT_THAT(options.pwd, "/opt/local/bin"); EXPECT_THAT(options.git_dir, "/opt/local/bin/.git"); } struct CompoundOptionsTest : program::Arguments { using Arguments::Arguments; std::pair screen_size = std::make_pair(option("width"), option("height")); }; TEST(CompoundOptionsTest, CanBindTupleElements) { auto const options = parse({"", "--width", "1920", "--height", "1080"}); EXPECT_THAT(options.screen_size.first, 1920); EXPECT_THAT(options.screen_size.second, 1080); } struct dim2 { int x, y; }; template <> struct string_utils::cast_helper { bool operator()(std::string_view str, dim2 & out) { return sscanf(str.data(), "%dx%d", &out.x, &out.y) == 2; } }; struct ParsedOptionTest : program::Arguments { using Arguments::Arguments; dim2 screen_size = option("screen"); }; TEST(ParsedOptionTest, CanInvokeSpecialParser) { auto const options = parse({"", "--screen", "1920x1080"}); EXPECT_THAT(options.screen_size.x, 1920); EXPECT_THAT(options.screen_size.y, 1080); } TEST(ParsedOptionTest, SpecialParserCanFail) { EXPECT_THROW(parse({"", "--screen", "1920"}), program::ArgumentStructureError); }