#include #include #include #include #include #include #include #include const std::initializer_list conventions = { &argument_parser::conventions::gnu_argument_convention, &argument_parser::conventions::gnu_equal_argument_convention, }; namespace v2_test { class fake_parser : public argument_parser::v2::base_parser { public: fake_parser(std::string const &program_name, std::initializer_list const &arguments) { set_program_name(program_name); ref_parsed_args() = std::vector(arguments); prepare_help_flag(); } }; } // namespace v2_test int tests_run = 0; int tests_passed = 0; void test_result(const char *name, bool passed) { tests_run++; if (passed) { tests_passed++; std::cout << " [PASS] " << name << std::endl; } else { std::cout << " [FAIL] " << name << std::endl; } } // ============================================================ // V1 Tests (using argument_parser::fake_parser) // ============================================================ void test_v1_single_positional_store() { argument_parser::fake_parser parser("test", {"hello"}); parser.add_positional_argument("greeting", "A greeting", false); parser.handle_arguments(conventions); auto val = parser.get_optional("greeting"); test_result("v1: single positional store", val.has_value() && val.value() == "hello"); } void test_v1_multiple_positionals_ordered() { argument_parser::fake_parser parser("test", {"alpha", "beta", "gamma"}); parser.add_positional_argument("first", "First arg", false); parser.add_positional_argument("second", "Second arg", false); parser.add_positional_argument("third", "Third arg", false); parser.handle_arguments(conventions); auto first = parser.get_optional("first"); auto second = parser.get_optional("second"); auto third = parser.get_optional("third"); bool ok = first.has_value() && first.value() == "alpha" && second.has_value() && second.value() == "beta" && third.has_value() && third.value() == "gamma"; test_result("v1: multiple positionals preserve order", ok); } void test_v1_positional_with_explicit_position() { argument_parser::fake_parser parser("test", {"first_val", "second_val"}); parser.add_positional_argument("second", "Second", false, 1); parser.add_positional_argument("first", "First", false, 0); parser.handle_arguments(conventions); auto first = parser.get_optional("first"); auto second = parser.get_optional("second"); bool ok = first.has_value() && first.value() == "first_val" && second.has_value() && second.value() == "second_val"; test_result("v1: explicit position index", ok); } void test_v1_positional_typed_int() { argument_parser::fake_parser parser("test", {"42"}); parser.add_positional_argument("count", "A count", false); parser.handle_arguments(conventions); auto val = parser.get_optional("count"); test_result("v1: positional with int type", val.has_value() && val.value() == 42); } void test_v1_positional_with_action() { std::string captured; argument_parser::fake_parser parser("test", {"world"}); auto action = argument_parser::helpers::make_parametered_action([&](std::string const &v) { captured = v; }); parser.add_positional_argument("name", "A name", action, false); parser.handle_arguments(conventions); test_result("v1: positional with action", captured == "world"); } void test_v1_mixed_named_and_positional() { argument_parser::fake_parser parser("test", {"--verbose", "true", "myfile.txt"}); parser.add_argument("v", "verbose", "Verbose mode", false); parser.add_positional_argument("file", "Input file", false); parser.handle_arguments(conventions); auto verbose = parser.get_optional("verbose"); auto file = parser.get_optional("file"); bool ok = verbose.has_value() && verbose.value() == true && file.has_value() && file.value() == "myfile.txt"; test_result("v1: mixed named and positional args", ok); } void test_v1_positional_after_named() { argument_parser::fake_parser parser("test", {"-n", "5", "output.txt"}); parser.add_argument("n", "number", "A number", false); parser.add_positional_argument("output", "Output file", false); parser.handle_arguments(conventions); auto number = parser.get_optional("number"); auto output = parser.get_optional("output"); bool ok = number.has_value() && number.value() == 5 && output.has_value() && output.value() == "output.txt"; test_result("v1: positional after named args", ok); } void test_v1_positional_between_named() { argument_parser::fake_parser parser("test", {"-a", "1", "positional_val", "--beta", "2"}); parser.add_argument("a", "alpha", "Alpha", false); parser.add_argument("b", "beta", "Beta", false); parser.add_positional_argument("middle", "Middle arg", false); parser.handle_arguments(conventions); auto alpha = parser.get_optional("alpha"); auto beta = parser.get_optional("beta"); auto middle = parser.get_optional("middle"); bool ok = alpha.has_value() && alpha.value() == 1 && beta.has_value() && beta.value() == 2 && middle.has_value() && middle.value() == "positional_val"; test_result("v1: positional between named args", ok); } void test_v1_double_dash_separator() { argument_parser::fake_parser parser("test", {"--", "-not-a-flag"}); parser.add_positional_argument("item", "An item", false); parser.handle_arguments(conventions); auto val = parser.get_optional("item"); test_result("v1: -- separator treats next as positional", val.has_value() && val.value() == "-not-a-flag"); } void test_v1_double_dash_multiple() { argument_parser::fake_parser parser("test", {"--name", "hello", "--", "--weird", "-x"}); parser.add_argument("n", "name", "A name", false); parser.add_positional_argument("first", "First", false); parser.add_positional_argument("second", "Second", false); parser.handle_arguments(conventions); auto name = parser.get_optional("name"); auto first = parser.get_optional("first"); auto second = parser.get_optional("second"); bool ok = name.has_value() && name.value() == "hello" && first.has_value() && first.value() == "--weird" && second.has_value() && second.value() == "-x"; test_result("v1: -- separator with multiple positionals", ok); } void test_v1_required_positional_missing() { argument_parser::fake_parser parser("test", {}); parser.add_positional_argument("file", "A file", true); bool threw = false; try { // check_for_required_arguments calls std::exit(1) so we can't easily test it // instead, test that handle_arguments doesn't crash when positionals are provided parser.handle_arguments(conventions); } catch (...) { threw = true; } // Note: required check calls std::exit(1), so if we get here the arg wasn't required-checked // This test just verifies setup doesn't crash. The exit behavior is tested manually. test_result("v1: required positional setup (no crash)", true); } void test_v1_unexpected_positional_throws() { argument_parser::fake_parser parser("test", {"unexpected"}); // no positional args defined, but a bare token is provided bool threw = false; try { parser.handle_arguments(conventions); } catch (const std::runtime_error &) { threw = true; } test_result("v1: unexpected positional throws", threw); } void test_v1_duplicate_positional_name_throws() { argument_parser::fake_parser parser("test", {"a", "b"}); parser.add_positional_argument("file", "A file", false); bool threw = false; try { parser.add_positional_argument("file", "Duplicate", false); } catch (const std::runtime_error &) { threw = true; } test_result("v1: duplicate positional name throws", threw); } void test_v1_positional_on_complete() { std::string captured_file; argument_parser::fake_parser parser("test", {"data.csv"}); parser.add_positional_argument("file", "Input file", false); parser.on_complete([&](argument_parser::base_parser const &p) { auto val = p.get_optional("file"); if (val) captured_file = val.value(); }); parser.handle_arguments(conventions); test_result("v1: positional accessible in on_complete", captured_file == "data.csv"); } // ============================================================ // V2 Tests (using v2_test::fake_parser) // ============================================================ void test_v2_single_positional() { using namespace argument_parser::v2::flags; v2_test::fake_parser parser("test", {"hello"}); parser.add_argument({{Positional, "greeting"}, {HelpText, "A greeting"}}); parser.handle_arguments(conventions); auto val = parser.get_optional("greeting"); test_result("v2: single positional store", val.has_value() && val.value() == "hello"); } void test_v2_positional_required() { using namespace argument_parser::v2::flags; v2_test::fake_parser parser("test", {"value"}); parser.add_argument({{Positional, "arg"}, {Required, true}, {HelpText, "Required arg"}}); parser.handle_arguments(conventions); auto val = parser.get_optional("arg"); test_result("v2: required positional", val.has_value() && val.value() == "value"); } void test_v2_positional_with_position() { using namespace argument_parser::v2::flags; v2_test::fake_parser parser("test", {"first_val", "second_val"}); parser.add_argument({{Positional, "second"}, {Position, 1}, {HelpText, "Second"}}); parser.add_argument({{Positional, "first"}, {Position, 0}, {HelpText, "First"}}); parser.handle_arguments(conventions); auto first = parser.get_optional("first"); auto second = parser.get_optional("second"); bool ok = first.has_value() && first.value() == "first_val" && second.has_value() && second.value() == "second_val"; test_result("v2: positional with explicit Position", ok); } void test_v2_positional_typed_int() { using namespace argument_parser::v2::flags; v2_test::fake_parser parser("test", {"99"}); parser.add_argument({{Positional, "count"}, {HelpText, "A count"}}); parser.handle_arguments(conventions); auto val = parser.get_optional("count"); test_result("v2: positional with int type", val.has_value() && val.value() == 99); } void test_v2_mixed_named_and_positional() { using namespace argument_parser::v2::flags; v2_test::fake_parser parser("test", {"--output", "out.txt", "input.txt"}); parser.add_argument({{ShortArgument, "o"}, {LongArgument, "output"}, {HelpText, "Output file"}}); parser.add_argument({{Positional, "input"}, {HelpText, "Input file"}}); parser.handle_arguments(conventions); auto output = parser.get_optional("output"); auto input = parser.get_optional("input"); bool ok = output.has_value() && output.value() == "out.txt" && input.has_value() && input.value() == "input.txt"; test_result("v2: mixed named and positional", ok); } void test_v2_positional_with_action() { using namespace argument_parser::v2::flags; std::string captured; v2_test::fake_parser parser("test", {"world"}); parser.add_argument({{Positional, "name"}, {Action, argument_parser::helpers::make_parametered_action( [&](std::string const &v) { captured = v; })}, {HelpText, "A name"}}); parser.handle_arguments(conventions); test_result("v2: positional with action", captured == "world"); } void test_v2_double_dash_separator() { using namespace argument_parser::v2::flags; v2_test::fake_parser parser("test", {"--", "--not-a-flag"}); parser.add_argument({{Positional, "item"}, {HelpText, "An item"}}); parser.handle_arguments(conventions); auto val = parser.get_optional("item"); test_result("v2: -- separator", val.has_value() && val.value() == "--not-a-flag"); } void test_v2_positional_auto_help_text() { using namespace argument_parser::v2::flags; v2_test::fake_parser parser("test", {"42"}); // no HelpText provided — should auto-generate from traits parser.add_argument({{Positional, "count"}}); parser.handle_arguments(conventions); auto val = parser.get_optional("count"); test_result("v2: positional auto help text (no crash)", val.has_value() && val.value() == 42); } void test_v2_multiple_positionals_and_named() { using namespace argument_parser::v2::flags; v2_test::fake_parser parser("test", {"-v", "src.txt", "dst.txt"}); parser.add_argument({{ShortArgument, "v"}, {LongArgument, "verbose"}}); parser.add_argument({{Positional, "source"}, {HelpText, "Source"}}); parser.add_argument({{Positional, "destination"}, {HelpText, "Destination"}}); parser.handle_arguments(conventions); auto verbose = parser.get_optional("verbose"); auto source = parser.get_optional("source"); auto dest = parser.get_optional("destination"); bool ok = verbose.has_value() && source.has_value() && source.value() == "src.txt" && dest.has_value() && dest.value() == "dst.txt"; test_result("v2: multiple positionals with named flag", ok); } void test_v2_on_complete_with_positional() { using namespace argument_parser::v2::flags; std::string captured; v2_test::fake_parser parser("test", {"payload"}); parser.add_argument({{Positional, "data"}, {HelpText, "Data"}}); parser.on_complete([&](argument_parser::base_parser const &p) { auto val = p.get_optional("data"); if (val) captured = val.value(); }); parser.handle_arguments(conventions); test_result("v2: positional accessible in on_complete", captured == "payload"); } // ============================================================ // Main // ============================================================ int main() { std::cout << "=== V1 Positional Argument Tests ===" << std::endl; std::array, 13> v1Tests { test_v1_single_positional_store, test_v1_multiple_positionals_ordered, test_v1_positional_with_explicit_position, test_v1_positional_typed_int, test_v1_positional_with_action, test_v1_mixed_named_and_positional, test_v1_positional_after_named, test_v1_positional_between_named, test_v1_double_dash_separator, test_v1_double_dash_multiple, test_v1_unexpected_positional_throws, test_v1_duplicate_positional_name_throws, test_v1_positional_on_complete }; for (auto const& test : v1Tests) { try { test(); } catch(std::exception const& e) { std::cout << "test failed: " << e.what() << std::endl; } } std::cout << "\n=== V2 Positional Argument Tests ===" << std::endl; std::array, 10> v2Tests{ test_v2_single_positional, test_v2_positional_required, test_v2_positional_with_position, test_v2_positional_typed_int, test_v2_mixed_named_and_positional, test_v2_positional_with_action, test_v2_double_dash_separator, test_v2_positional_auto_help_text, test_v2_multiple_positionals_and_named, test_v2_on_complete_with_positional }; for (auto const& test : v2Tests) { try { test(); } catch(std::exception const& e) { std::cout << "test failed: " << e.what() << std::endl; } } std::cout << "\n=== Results: " << tests_passed << "/" << tests_run << " passed ===" << std::endl; return (tests_passed == tests_run) ? 0 : 1; }