diff --git a/test/coverage/CMakeLists.txt b/test/coverage/CMakeLists.txt index 34b6fd5b..ccf74a68 100644 --- a/test/coverage/CMakeLists.txt +++ b/test/coverage/CMakeLists.txt @@ -66,6 +66,8 @@ sharg_append_gcovr_args ("--exclude-lines-by-pattern" [['^\\s*[{}]{0,2}\\s*;*\\s sharg_append_gcovr_args ("--exclude-unreachable-branches") # Will exclude branches that are only generated for exception handling. sharg_append_gcovr_args ("--exclude-throw-branches") +# Will exclude non-code lines, e.g. lines with closing braces. +sharg_append_gcovr_args ("--exclude-noncode-lines") # Run up to this many gcov instances in parallel. sharg_append_gcovr_args ("-j" "${SHARG_COVERAGE_PARALLEL_LEVEL}") diff --git a/test/include/sharg/test/test_fixture.hpp b/test/include/sharg/test/test_fixture.hpp new file mode 100644 index 00000000..c41e324a --- /dev/null +++ b/test/include/sharg/test/test_fixture.hpp @@ -0,0 +1,215 @@ +// SPDX-FileCopyrightText: 2006-2024, Knut Reinert & Freie Universität Berlin +// SPDX-FileCopyrightText: 2016-2024, Knut Reinert & MPI für molekulare Genetik +// SPDX-License-Identifier: BSD-3-Clause + +/*!\file + * \brief Provides sharg::test::test_fixture. + * \author Enrico Seiler + */ + +#pragma once + +#include + +#include + +namespace sharg::detail +{ + +struct test_accessor +{ + static void set_terminal_width(sharg::parser & parser, unsigned terminal_width) + { + auto visit_fn = [terminal_width](format_t & format) + { + if constexpr (std::same_as) + format.layout = sharg::detail::format_help::console_layout_struct{terminal_width}; + }; + + std::visit(std::move(visit_fn), parser.format); + } + + static std::vector & executable_name(sharg::parser & parser) + { + return parser.executable_name; + } + + static auto & version_check_future(sharg::parser & parser) + { + return parser.version_check_future; + } +}; + +} // namespace sharg::detail + +namespace sharg::test +{ + +class test_fixture : public ::testing::Test +{ +private: + friend class early_exit_guardian; + + static sharg::parser impl(std::vector arguments, std::vector subcommands = {}) + { + sharg::parser parser{"test_parser", + std::move(arguments), + sharg::update_notifications::off, + std::move(subcommands)}; + sharg::detail::test_accessor::set_terminal_width(parser, 80u); + return parser; + } + + static void toggle_guardian(); + +protected: + template + static sharg::parser get_parser(arg_ts &&... arguments) + { + return impl(std::vector{"./test_parser", std::forward(arguments)...}); + } + + static sharg::parser get_subcommand_parser(std::vector arguments, std::vector subcommands) + { + arguments.insert(arguments.begin(), "./test_parser"); + return impl(std::move(arguments), std::move(subcommands)); + } + + static std::string get_parse_cout_on_exit(sharg::parser & parser) + { + testing::internal::CaptureStdout(); + // EXPECT_EXIT will create a new thread via clone() and the destructor of the cloned early_exit_guardian will + // be called. So we need to toggle the guardian to prevent the check inside the cloned thread, and toggle + // it back after the EXPECT_EXIT call. + toggle_guardian(); + EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); + toggle_guardian(); + return testing::internal::GetCapturedStdout(); + } +}; + +class early_exit_guardian +{ +public: + early_exit_guardian() + { + ::testing::AddGlobalTestEnvironment(new test_environment{this}); + } + early_exit_guardian(early_exit_guardian const &) = delete; + early_exit_guardian(early_exit_guardian &&) = delete; + early_exit_guardian & operator=(early_exit_guardian const &) = delete; + early_exit_guardian & operator=(early_exit_guardian &&) = delete; + + ~early_exit_guardian() + { + restore_stderr(); + check_all_tests_ran(); + } + +private: + //!\brief test_fixture::toggle_guardian is allowed to toggle `active` for `EXPECT_EXIT` tests. + friend void test_fixture::toggle_guardian(); + //!\brief Flag to indicate that all tests are done. + bool active{false}; + //!\brief Original file descriptor of stderr. + int const stored_fd = dup(2); + + /*!\brief Pointer to the current test unit. + * GetInstance() creates the object on the first call and just return that pointer on subsequent calls (static). + * It is important that this first call takes place on the "main" thread, and not in the destructor call. + */ + testing::UnitTest const * const unit_test = testing::UnitTest::GetInstance(); + + void activate() + { + assert(!active); + active = true; + } + + void deactivate() + { + assert(active); + active = false; + } + + /*!\brief Restore the original file descriptor of stderr. + * testing::internal::CaptureStderr() manipulates the file descriptor of stderr. + * There is no API (neither exposed nor internal) to check whether stderr is captured or not, and + * testing::internal::GetCapturedStderr() is UB if stderr is not captured. + * Therefore, we restore the original file descriptor of stderr by ourself. + */ + void restore_stderr() const + { + dup2(stored_fd, 2); + } + + /*!\brief Check if all tests were run. + * Exits with EXIT_FAILURE if `sharg::test::early_exit_guard.deactivate()` was not called. + */ + void check_all_tests_ran() const + { + if (!active) + return; + + // LCOV_EXCL_START + std::cerr << "\nNot all test cases were run!\n" + << "The following test unexpectedly terminated the execution:\n" + << get_current_test_name() << '\n'; + + std::exit(EXIT_FAILURE); + // LCOV_EXCL_STOP + } + + // LCOV_EXCL_START + std::string get_current_test_name() const + { + assert(unit_test && "This should never be a nullptr?!"); + + std::string result{}; + + if (::testing::TestSuite const * const test_suite = unit_test->current_test_suite()) + { + result = test_suite->name(); + result += '.'; + } + + if (::testing::TestInfo const * const test_info = unit_test->current_test_info()) + result += test_info->name(); + + return result; + } + // LCOV_EXCL_STOP + + // See https://github.com/google/googletest/blob/main/docs/advanced.md#global-set-up-and-tear-down + class test_environment : public ::testing::Environment + { + private: + friend class early_exit_guardian; + early_exit_guardian * guardian{nullptr}; + + test_environment(early_exit_guardian * guardian) : guardian{guardian} + {} + + public: + void SetUp() override + { + assert(guardian); + guardian->activate(); + } + + void TearDown() override + { + assert(guardian); + guardian->deactivate(); + } + }; +}; + +early_exit_guardian early_exit_guard{}; + +inline void test_fixture::toggle_guardian() +{ + early_exit_guard.active = !early_exit_guard.active; +} + +} // namespace sharg::test diff --git a/test/unit/detail/format_ctd_test.cpp b/test/unit/detail/format_ctd_test.cpp index 0a78b814..e4592aa5 100644 --- a/test/unit/detail/format_ctd_test.cpp +++ b/test/unit/detail/format_ctd_test.cpp @@ -5,27 +5,22 @@ #include #include +#include -#if !SHARG_HAS_TDL -TEST(format_ctd_test, skipped) -{ - GTEST_SKIP() << "TDL is not available."; -} -#else // Reused global variables -struct format_ctd_test : public ::testing::Test +class format_ctd_test : public sharg::test::test_fixture { +protected: int option_value{5}; bool flag_value{false}; int8_t non_list_pos_opt_value{1}; std::vector list_pos_opt_value{}; std::string my_stdout{}; - static constexpr std::array argv{"./format_ctd_test", "--version-check", "false", "--export-help", "ctd"}; std::string const version_str{sharg::sharg_version_cstring}; - std::string expected = + std::string const expected = R"del()del" "\n" - R"del()del" + R"del()del" "\n" R"del( )del" "\n" - R"del( )del" + R"del( )del" "\n" R"del( )del" "\n" @@ -108,10 +103,16 @@ struct format_ctd_test : public ::testing::Test } }; +#if !SHARG_HAS_TDL +TEST_F(format_ctd_test, skipped) +{ + GTEST_SKIP() << "TDL is not available."; +} +#else TEST_F(format_ctd_test, empty_information) { // Create the dummy parser. - sharg::parser parser{"default", argv.size(), argv.data()}; + auto parser = get_parser("--export-help", "ctd"); parser.info.date = "December 01, 1994"; parser.info.version = "1.1.2-rc.1"; parser.info.man_page_title = "default_man_page_title"; @@ -123,9 +124,9 @@ TEST_F(format_ctd_test, empty_information) "\n" R"()" + + R"(" name="test_parser">)" "\n" - R"( )" + R"( )" "\n" R"( )" "\n" @@ -135,25 +136,18 @@ TEST_F(format_ctd_test, empty_information) "\n"; // Test the dummy parser with minimal information. - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - - my_stdout = testing::internal::GetCapturedStdout(); - EXPECT_EQ(my_stdout, expected_short); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected_short); } TEST_F(format_ctd_test, full_information) { // Create the dummy parser. - sharg::parser parser{"default", argv.size(), argv.data()}; + auto parser = get_parser("--export-help", "ctd"); // Fill out the dummy parser with options and flags and sections and subsections. dummy_init(parser); - // Test the dummy parser without any copyright or citations. - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - my_stdout = testing::internal::GetCapturedStdout(); - EXPECT_EQ(my_stdout, expected); + // Test the dummy parser without any copyright or citations. + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } #endif diff --git a/test/unit/detail/format_cwl_test.cpp b/test/unit/detail/format_cwl_test.cpp index 88c01c3c..ee24c14f 100644 --- a/test/unit/detail/format_cwl_test.cpp +++ b/test/unit/detail/format_cwl_test.cpp @@ -5,25 +5,27 @@ #include #include +#include + +class format_cwl_test : public sharg::test::test_fixture +{}; #if !SHARG_HAS_TDL -TEST(format_cwl_test, skipped) +TEST_F(format_cwl_test, skipped) { GTEST_SKIP() << "TDL is not available."; } #else -TEST(format_cwl_test, empty_information) +TEST_F(format_cwl_test, empty_information) { - auto argv = std::array{"./format_cwl_test", "--version-check", "false", "--export-help", "cwl"}; - // Create the dummy parser. - auto parser = sharg::parser{"default", argv.size(), argv.data()}; + auto parser = get_parser("--export-help", "cwl"); parser.info.date = "December 01, 1994"; parser.info.version = "1.1.2-rc.1"; parser.info.man_page_title = "default_man_page_title"; parser.info.short_description = "A short description here."; - std::string expected_short = "label: default\n" + std::string expected_short = "label: test_parser\n" "doc: \"\"\n" "inputs:\n" " []\n" @@ -32,20 +34,14 @@ TEST(format_cwl_test, empty_information) "cwlVersion: v1.2\n" "class: CommandLineTool\n" "baseCommand:\n" - " - format_cwl_test\n"; + " - test_parser\n"; // Test the dummy parser with minimal information. - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - - auto my_stdout = testing::internal::GetCapturedStdout(); - EXPECT_EQ(my_stdout, expected_short); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected_short); } -TEST(format_cwl_test, full_information) +TEST_F(format_cwl_test, full_information) { - auto argv = std::array{"./format_cwl_test", "--version-check", "false", "--export-help", "cwl"}; - // Create variables for the arguments int option_value{5}; bool flag_value{false}; @@ -53,7 +49,7 @@ TEST(format_cwl_test, full_information) auto list_pos_opt_value = std::vector{}; // Create the dummy parser. - auto parser = sharg::parser{"default", argv.size(), argv.data()}; + auto parser = get_parser("--export-help", "cwl"); parser.info.date = "December 01, 1994"; parser.info.version = "01.01.01"; parser.info.man_page_title = "default_ctd_page_title"; @@ -78,7 +74,7 @@ TEST(format_cwl_test, full_information) parser.info.examples.push_back("example"); parser.info.examples.push_back("example2"); - std::string expected_short = "label: default\n" + std::string expected_short = "label: test_parser\n" "doc: \"description\\n" "description2\\n" "\"\n" @@ -110,20 +106,14 @@ TEST(format_cwl_test, full_information) "cwlVersion: v1.2\n" "class: CommandLineTool\n" "baseCommand:\n" - " - format_cwl_test\n"; + " - test_parser\n"; // Test the dummy parser with minimal information. - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - - auto my_stdout = testing::internal::GetCapturedStdout(); - EXPECT_EQ(my_stdout, expected_short); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected_short); } -TEST(format_cwl_test, subparser) +TEST_F(format_cwl_test, subparser) { - auto argv = std::array{"./format_cwl_test", "index", "--export-help", "cwl"}; - // Create variables for the arguments int option_value{5}; float option_value_float{0}; @@ -134,7 +124,7 @@ TEST(format_cwl_test, subparser) auto list_pos_opt_value = std::vector{}; // Create the dummy parser. - auto parser = sharg::parser{"default", argv.size(), argv.data(), sharg::update_notifications::off, {"index"}}; + auto parser = get_subcommand_parser({"index", "--export-help", "cwl"}, {"index"}); parser.info.date = "December 01, 1994"; parser.info.version = "01.01.01"; parser.info.man_page_title = "default_ctd_page_title"; @@ -144,12 +134,10 @@ TEST(format_cwl_test, subparser) parser.info.description.push_back("description"); parser.info.description.push_back("description2"); - parser.parse(); + EXPECT_NO_THROW(parser.parse()); + auto & sub_parser = parser.get_sub_parser(); - if (sub_parser.info.app_name != std::string_view{"default-index"}) - { - return; - } + ASSERT_EQ(sub_parser.info.app_name, "test_parser-index"); sub_parser.add_option(option_value, sharg::config{'i', "int", "this is a int option."}); sub_parser.add_option(option_value, sharg::config{.short_id = 'j', @@ -210,7 +198,7 @@ TEST(format_cwl_test, subparser) sub_parser.info.examples.push_back("example2"); std::string expected_short = - "label: default-index\n" + "label: test_parser-index\n" "doc: \"\"\n" "inputs:\n" " positional_0:\n" @@ -285,13 +273,8 @@ TEST(format_cwl_test, subparser) "cwlVersion: v1.2\n" "class: CommandLineTool\n" "baseCommand:\n" - " - format_cwl_test\n" + " - test_parser\n" " - index\n"; - testing::internal::CaptureStdout(); - // sub_parser.parse(); - EXPECT_EXIT(sub_parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - - auto my_stdout = testing::internal::GetCapturedStdout(); - EXPECT_EQ(my_stdout, expected_short); + EXPECT_EQ(get_parse_cout_on_exit(sub_parser), expected_short); } #endif diff --git a/test/unit/detail/format_help_test.cpp b/test/unit/detail/format_help_test.cpp index fd822461..bade5ef9 100644 --- a/test/unit/detail/format_help_test.cpp +++ b/test/unit/detail/format_help_test.cpp @@ -7,124 +7,103 @@ #include #include +#include -// reused global variables -std::string std_cout; -std::string expected; -int option_value{5}; -bool flag_value{false}; -std::vector pos_opt_value{}; -char const * argv_without_any_options[] = {"./help_add_test"}; -char const * argv_with_h[] = {"./help_add_test", "-h"}; -char const * argv_with_hh[] = {"./help_add_test", "-hh"}; -char const * argv_with_version[] = {"./help_add_test", "--version"}; - -std::string const basic_options_str = " Common options\n" - " -h, --help\n" - " Prints the help page.\n" - " -hh, --advanced-help\n" - " Prints the help page including advanced options.\n" - " --version\n" - " Prints the version information.\n" - " --copyright\n" - " Prints the copyright/license information.\n" - " --export-help (std::string)\n" - " Export the help page information. Value must be one of " +// Reused global variables +class format_help_test : public sharg::test::test_fixture +{ +protected: + std::string std_cout{}; + std::string expected{}; + int option_value{5}; + bool flag_value{false}; + std::vector pos_opt_value{}; + + static inline std::string basic_options_str = " Common options\n" + " -h, --help\n" + " Prints the help page.\n" + " -hh, --advanced-help\n" + " Prints the help page including advanced options.\n" + " --version\n" + " Prints the version information.\n" + " --copyright\n" + " Prints the copyright/license information.\n" + " --export-help (std::string)\n" + " Export the help page information. Value must be one of " #if SHARG_HAS_TDL - "[html, man,\n ctd, cwl].\n" + "[html, man,\n ctd, cwl].\n"; #else - "[html, man].\n" + "[html, man].\n"; #endif - " --version-check (bool)\n" - " Whether to check for the newest app version. Default: true\n"; - -std::string const basic_version_str = "VERSION\n" - " Last update:\n" - " test_parser version:\n" - " Sharg version: " - + std::string{sharg::sharg_version_cstring} + "\n"; -std::string license_text() -{ - std::ifstream license_file{std::string{SHARG_TEST_LICENSE_DIR} + "/LICENSE.md"}; - EXPECT_TRUE(license_file) << "Could not open file '" SHARG_TEST_LICENSE_DIR "/LICENSE.md'"; - std::stringstream buffer; - buffer << license_file.rdbuf(); + static inline std::string basic_version_str = "VERSION\n" + " Last update:\n" + " test_parser version:\n" + " Sharg version: " + + std::string{sharg::sharg_version_cstring} + "\n"; - std::string str = buffer.str(); - size_t license_start = str.find("```\n") + 4; - size_t license_end = str.find("```", license_start); + static inline std::string license_text = []() + { + std::ifstream license_file{std::string{SHARG_TEST_LICENSE_DIR} + "/LICENSE.md"}; + EXPECT_TRUE(license_file) << "Could not open file '" SHARG_TEST_LICENSE_DIR "/LICENSE.md'"; + std::stringstream buffer; + buffer << license_file.rdbuf(); - return str.substr(license_start, license_end - license_start); -} + std::string str = buffer.str(); + size_t license_start = str.find("```\n") + 4; + size_t license_end = str.find("```", license_start); -namespace sharg::detail -{ -struct test_accessor -{ - static void set_terminal_width(sharg::parser & parser, unsigned terminal_width) - { - std::visit( - [terminal_width](auto & f) - { - if constexpr (std::is_same_v) - f.layout = sharg::detail::format_help::console_layout_struct{terminal_width}; - }, - parser.format); - } + return str.substr(license_start, license_end - license_start); + }(); }; -} // namespace sharg::detail -TEST(help_page_printing, short_help) +TEST_F(format_help_test, short_help) { - expected = "empty_options\n" - "=============\n" + expected = "test_parser\n" + "===========\n" " ./some_binary_name synopsis\n" " Try -h or --help for more information.\n"; + auto parser = get_parser(); + // Empty call with no options given. For sharg::detail::format_short_help // even if required options exist. - - auto check_expected_short_help = [&](auto && argv) + auto check_expected_short_help = [&]() { - int const argc = sizeof(argv) / sizeof(*argv); - sharg::parser parser{"empty_options", argc, argv}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); parser.info.synopsis.push_back("./some_binary_name synopsis"); int option_value{}; parser.add_option(option_value, sharg::config{.short_id = 'i', .required = true}); - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); }; - char const * argv_with_version_check[] = {"./help_add_test", "--version-check", "0"}; + { + SCOPED_TRACE("Trace: First test"); + check_expected_short_help(); + } - check_expected_short_help(argv_without_any_options); - check_expected_short_help(argv_with_version_check); + { + SCOPED_TRACE("Trace: Second test"); + parser = get_parser("--version-check", "0"); + check_expected_short_help(); + } } -TEST(help_page_printing, quote_strings) +TEST_F(format_help_test, quote_strings) { - sharg::parser quoted("test_parser", 2, argv_with_h); - sharg::detail::test_accessor::set_terminal_width(quoted, 80); + auto parser = get_parser("-h"); std::string value1{}; std::string value2{"Some string"}; std::vector container_value{"Some", "other", "string"}; - quoted.add_option(value1, sharg::config{.short_id = 'a', .long_id = "string1"}); - quoted.add_option(value2, sharg::config{.short_id = 'b', .long_id = "string2"}); - quoted.add_option(value2, sharg::config{.short_id = 'c', .long_id = "string3", .default_message = "Quoted"}); - quoted.add_option(container_value, sharg::config{.short_id = 'd', .long_id = "string4"}); - quoted.add_option(container_value, sharg::config{.short_id = 'e', .long_id = "string5", .default_message = "None"}); - quoted.add_positional_option(container_value, sharg::config{/* No default_message allowed. */}); + parser.add_option(value1, sharg::config{.short_id = 'a', .long_id = "string1"}); + parser.add_option(value2, sharg::config{.short_id = 'b', .long_id = "string2"}); + parser.add_option(value2, sharg::config{.short_id = 'c', .long_id = "string3", .default_message = "Quoted"}); + parser.add_option(container_value, sharg::config{.short_id = 'd', .long_id = "string4"}); + parser.add_option(container_value, sharg::config{.short_id = 'e', .long_id = "string5", .default_message = "None"}); + parser.add_positional_option(container_value, sharg::config{/* No default_message allowed. */}); - testing::internal::CaptureStdout(); - EXPECT_EXIT(quoted.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); expected = "test_parser\n" "===========\n\n" "POSITIONAL ARGUMENTS\n" @@ -142,28 +121,24 @@ TEST(help_page_printing, quote_strings) " -e, --string5 (List of std::string)\n" " Default: None\n\n" + basic_options_str + "\n" + basic_version_str; - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(help_page_printing, quote_paths) +TEST_F(format_help_test, quote_paths) { - sharg::parser quoted("test_parser", 2, argv_with_h); - sharg::detail::test_accessor::set_terminal_width(quoted, 80); + auto parser = get_parser("-h"); std::filesystem::path value1{}; std::filesystem::path value2{"/some/path"}; std::vector container_value{"/some", "/other", "/path"}; - quoted.add_option(value1, sharg::config{.short_id = 'a', .long_id = "path1"}); - quoted.add_option(value2, sharg::config{.short_id = 'b', .long_id = "path2"}); - quoted.add_option(value2, sharg::config{.short_id = 'c', .long_id = "path3", .default_message = "/usr/bin/"}); - quoted.add_option(container_value, sharg::config{.short_id = 'd', .long_id = "path4"}); - quoted.add_option(container_value, sharg::config{.short_id = 'e', .long_id = "path5", .default_message = "None"}); - quoted.add_positional_option(container_value, sharg::config{/* No default_message allowed. */}); + parser.add_option(value1, sharg::config{.short_id = 'a', .long_id = "path1"}); + parser.add_option(value2, sharg::config{.short_id = 'b', .long_id = "path2"}); + parser.add_option(value2, sharg::config{.short_id = 'c', .long_id = "path3", .default_message = "/usr/bin/"}); + parser.add_option(container_value, sharg::config{.short_id = 'd', .long_id = "path4"}); + parser.add_option(container_value, sharg::config{.short_id = 'e', .long_id = "path5", .default_message = "None"}); + parser.add_positional_option(container_value, sharg::config{/* No default_message allowed. */}); - testing::internal::CaptureStdout(); - EXPECT_EXIT(quoted.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); expected = "test_parser\n" "===========\n\n" "POSITIONAL ARGUMENTS\n" @@ -181,33 +156,26 @@ TEST(help_page_printing, quote_paths) " -e, --path5 (List of std::filesystem::path)\n" " Default: None\n\n" + basic_options_str + "\n" + basic_version_str; - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(help_page_printing, no_information) +TEST_F(format_help_test, no_information) { - // Empty help call with -h - sharg::parser parser1{"test_parser", 2, argv_with_h}; - sharg::detail::test_accessor::set_terminal_width(parser1, 80); - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser1.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); + auto parser = get_parser("-h"); + expected = "test_parser\n" "===========\n" "\nOPTIONS\n\n" + basic_options_str + "\n" + basic_version_str; - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(help_page_printing, with_short_copyright) +TEST_F(format_help_test, with_short_copyright) { - // Again, but with short copyright, long copyright, and citation. - sharg::parser short_copy("test_parser", 2, argv_with_h); - sharg::detail::test_accessor::set_terminal_width(short_copy, 80); - short_copy.info.short_copyright = "short"; - testing::internal::CaptureStdout(); - EXPECT_EXIT(short_copy.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); + // Short copyright, long copyright, and citation. + auto parser = get_parser("-h"); + parser.info.short_copyright = "short"; + expected = "test_parser\n" "===========\n" "\nOPTIONS\n\n" @@ -216,17 +184,14 @@ TEST(help_page_printing, with_short_copyright) " test_parser Copyright: short\n" " SeqAn Copyright: 2006-2024 Knut Reinert, FU-Berlin; released under the\n" " 3-clause BSDL.\n"; - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(help_page_printing, with_long_copyright) +TEST_F(format_help_test, with_long_copyright) { - sharg::parser long_copy("test_parser", 2, argv_with_h); - sharg::detail::test_accessor::set_terminal_width(long_copy, 80); - long_copy.info.long_copyright = "long"; - testing::internal::CaptureStdout(); - EXPECT_EXIT(long_copy.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); + auto parser = get_parser("-h"); + parser.info.long_copyright = "long"; + expected = "test_parser\n" "===========\n" "\nOPTIONS\n\n" @@ -235,17 +200,14 @@ TEST(help_page_printing, with_long_copyright) " SeqAn Copyright: 2006-2024 Knut Reinert, FU-Berlin; released under the\n" " 3-clause BSDL.\n" " For full copyright and/or warranty information see --copyright.\n"; - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(help_page_printing, with_citation) +TEST_F(format_help_test, with_citation) { - sharg::parser citation("test_parser", 2, argv_with_h); - sharg::detail::test_accessor::set_terminal_width(citation, 80); - citation.info.citation = "citation"; - testing::internal::CaptureStdout(); - EXPECT_EXIT(citation.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); + auto parser = get_parser("-h"); + parser.info.citation = "citation"; + expected = "test_parser\n" "===========\n" "\nOPTIONS\n\n" @@ -254,17 +216,14 @@ TEST(help_page_printing, with_citation) " SeqAn Copyright: 2006-2024 Knut Reinert, FU-Berlin; released under the\n" " 3-clause BSDL.\n" " In your academic works please cite: citation\n"; - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(help_page_printing, with_author) +TEST_F(format_help_test, with_author) { - sharg::parser author("test_parser", 2, argv_with_h); - sharg::detail::test_accessor::set_terminal_width(author, 80); - author.info.author = "author"; - testing::internal::CaptureStdout(); - EXPECT_EXIT(author.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); + auto parser = get_parser("-h"); + parser.info.author = "author"; + expected = "test_parser\n" "===========\n" "\nOPTIONS\n\n" @@ -273,17 +232,14 @@ TEST(help_page_printing, with_author) " Author: author\n" " SeqAn Copyright: 2006-2024 Knut Reinert, FU-Berlin; released under the\n" " 3-clause BSDL.\n"; - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(help_page_printing, with_email) +TEST_F(format_help_test, with_email) { - sharg::parser email("test_parser", 2, argv_with_h); - sharg::detail::test_accessor::set_terminal_width(email, 80); - email.info.email = "email"; - testing::internal::CaptureStdout(); - EXPECT_EXIT(email.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); + auto parser = get_parser("-h"); + parser.info.email = "email"; + expected = "test_parser\n" "===========\n" "\nOPTIONS\n\n" @@ -292,78 +248,64 @@ TEST(help_page_printing, with_email) " Contact: email\n" " SeqAn Copyright: 2006-2024 Knut Reinert, FU-Berlin; released under the\n" " 3-clause BSDL.\n"; - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(help_page_printing, empty_advanced_help) +TEST_F(format_help_test, empty_advanced_help) { - // Empty help call with -hh - sharg::parser parser2{"test_parser", 2, argv_with_hh}; - sharg::detail::test_accessor::set_terminal_width(parser2, 80); - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser2.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); + auto parser = get_parser("-hh"); + expected = "test_parser\n" "===========\n" "\nOPTIONS\n\n" + basic_options_str + "\n" + basic_version_str; - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(help_page_printing, empty_version_call) +TEST_F(format_help_test, empty_version_call) { - // Empty version call - sharg::parser parser3{"test_parser", 2, argv_with_version}; - sharg::detail::test_accessor::set_terminal_width(parser3, 80); - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser3.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); + auto parser = get_parser("--version"); + expected = "test_parser\n" "===========\n" "\n" + basic_version_str; - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(help_page_printing, version_call) +TEST_F(format_help_test, version_call) { // Version call with url and options. - sharg::parser parser4{"test_parser", 2, argv_with_version}; - sharg::detail::test_accessor::set_terminal_width(parser4, 80); - parser4.info.url = "https://seqan.de"; - parser4.add_option(option_value, sharg::config{.short_id = 'i'}); - parser4.add_flag(flag_value, sharg::config{.short_id = 'f'}); - parser4.add_positional_option(pos_opt_value, sharg::config{}); - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser4.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); + auto parser = get_parser("--version"); + parser.info.url = "https://seqan.de"; + parser.add_option(option_value, sharg::config{.short_id = 'i'}); + parser.add_flag(flag_value, sharg::config{.short_id = 'f'}); + parser.add_positional_option(pos_opt_value, sharg::config{}); + expected = "test_parser\n" "===========\n" "\n" + basic_version_str + "\n" + "URL\n" " https://seqan.de\n"; - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(help_page_printing, do_not_print_hidden_options) +TEST_F(format_help_test, do_not_print_hidden_options) { // Add an option and request help. - sharg::parser parser5{"test_parser", 2, argv_with_h}; - sharg::detail::test_accessor::set_terminal_width(parser5, 80); - parser5.add_option(option_value, sharg::config{.short_id = 'i', .hidden = true}); - parser5.add_flag(flag_value, sharg::config{.short_id = 'f', .hidden = true}); - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser5.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); + auto parser = get_parser("-h"); + parser.add_option(option_value, sharg::config{.short_id = 'i', .hidden = true}); + parser.add_flag(flag_value, sharg::config{.short_id = 'f', .hidden = true}); + expected = "test_parser\n" "===========\n" "\nOPTIONS\n\n" + basic_options_str + "\n" + basic_version_str; - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(help_page_printing, advanced_options) +TEST_F(format_help_test, advanced_options) { int32_t option_value{5}; uint8_t another_option_value{2}; @@ -399,12 +341,9 @@ TEST(help_page_printing, advanced_options) }; // without -hh, only the non/advanced information are shown - sharg::parser parser_normal_help{"test_parser", 2, argv_with_h}; - sharg::detail::test_accessor::set_terminal_width(parser_normal_help, 80); - set_up(parser_normal_help); - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser_normal_help.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); + auto parser = get_parser("-h"); + set_up(parser); + expected = "test_parser\n" "===========\n" "\nOPTIONS\n" @@ -421,15 +360,12 @@ TEST(help_page_printing, advanced_options) " some line.\n" "\n" + basic_options_str + "\n" + basic_version_str; - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); // with -hh everything is shown - sharg::parser parser_advanced_help{"test_parser", 2, argv_with_hh}; - sharg::detail::test_accessor::set_terminal_width(parser_advanced_help, 80); - set_up(parser_advanced_help); - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser_advanced_help.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); + parser = get_parser("-hh"); + set_up(parser); + expected = "test_parser\n" "===========\n" "\nOPTIONS\n" @@ -457,7 +393,7 @@ TEST(help_page_printing, advanced_options) " some line.\n" "\n" + basic_options_str + "\n" + basic_version_str; - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } enum class foo @@ -472,47 +408,44 @@ auto enumeration_names(foo) return std::unordered_map{{"one", foo::one}, {"two", foo::two}, {"three", foo::three}}; } -TEST(help_page_printing, full_information) +TEST_F(format_help_test, full_information) { int8_t required_option{}; int8_t non_list_optional{1}; foo enum_option_value{}; // Add synopsis, description, short description, positional option, option, flag, and example. - sharg::parser parser6{"test_parser", 2, argv_with_h}; - sharg::detail::test_accessor::set_terminal_width(parser6, 80); - parser6.info.synopsis.push_back("./some_binary_name synopsis"); - parser6.info.synopsis.push_back("./some_binary_name synopsis2"); - parser6.info.description.push_back("description"); - parser6.info.description.push_back("description2"); - parser6.info.short_description = "so short"; - parser6.add_option(option_value, - sharg::config{.short_id = 'i', - .long_id = "int", - .description = "this is a int option.", - .default_message = "A number"}); - parser6.add_option( + auto parser = get_parser("-h"); + parser.info.synopsis.push_back("./some_binary_name synopsis"); + parser.info.synopsis.push_back("./some_binary_name synopsis2"); + parser.info.description.push_back("description"); + parser.info.description.push_back("description2"); + parser.info.short_description = "so short"; + parser.add_option(option_value, + sharg::config{.short_id = 'i', + .long_id = "int", + .description = "this is a int option.", + .default_message = "A number"}); + parser.add_option( enum_option_value, sharg::config{.short_id = 'e', .long_id = "enum", .description = "this is an enum option.", .validator = sharg::value_list_validator{sharg::enumeration_names | std::views::values}}); - parser6.add_option(required_option, - sharg::config{.short_id = 'r', - .long_id = "required-int", - .description = "this is another int option.", - .required = true}); - parser6.add_section("Flags"); - parser6.add_subsection("SubFlags"); - parser6.add_line("here come all the flags"); - parser6.add_flag(flag_value, sharg::config{.short_id = 'f', .long_id = "flag", .description = "this is a flag."}); - parser6.add_positional_option(non_list_optional, sharg::config{.description = "this is not a list."}); - parser6.add_positional_option(pos_opt_value, sharg::config{.description = "this is a positional option."}); - parser6.info.examples.push_back("example"); - parser6.info.examples.push_back("example2"); - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser6.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); + parser.add_option(required_option, + sharg::config{.short_id = 'r', + .long_id = "required-int", + .description = "this is another int option.", + .required = true}); + parser.add_section("Flags"); + parser.add_subsection("SubFlags"); + parser.add_line("here come all the flags"); + parser.add_flag(flag_value, sharg::config{.short_id = 'f', .long_id = "flag", .description = "this is a flag."}); + parser.add_positional_option(non_list_optional, sharg::config{.description = "this is not a list."}); + parser.add_positional_option(pos_opt_value, sharg::config{.description = "this is a positional option."}); + parser.info.examples.push_back("example"); + parser.info.examples.push_back("example2"); + expected = "test_parser - so short\n" "======================\n" "\n" @@ -554,92 +487,63 @@ TEST(help_page_printing, full_information) " example2\n" "\n" + basic_version_str; - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(help_page_printing, copyright) +TEST_F(format_help_test, copyright) { - // Tests the --copyright call. - char const * argvCopyright[] = {"./copyright", "--copyright"}; - sharg::parser copyright("myApp", 2, argvCopyright); - - std::string license_string = license_text(); - // Test --copyright with empty short and long copyright info. - { - testing::internal::CaptureStdout(); - EXPECT_EXIT(copyright.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); - - expected = "================================================================================\n" - "Copyright information for myApp:\n" - "--------------------------------------------------------------------------------\n" - "myApp copyright information not available.\n" - "================================================================================\n" - "This program contains SeqAn code licensed under the following terms:\n" - "--------------------------------------------------------------------------------\n" - + license_string; - - EXPECT_EQ(std_cout, expected); - } + auto parser = get_parser("--copyright"); + + expected = "================================================================================\n" + "Copyright information for test_parser:\n" + "--------------------------------------------------------------------------------\n" + "test_parser copyright information not available.\n" + "================================================================================\n" + "This program contains SeqAn code licensed under the following terms:\n" + "--------------------------------------------------------------------------------\n" + + license_text; + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); // Test --copyright with a non-empty short copyright and an empty long copyright. - copyright.info.short_copyright = "short copyright line 1\nshort copyright line 2"; - { - testing::internal::CaptureStdout(); - EXPECT_EXIT(copyright.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); - - expected = "================================================================================\n" - "Copyright information for myApp:\n" - "--------------------------------------------------------------------------------\n" - "myApp full copyright information not available. Displaying short copyright information instead:\n" - "short copyright line 1\n" - "short copyright line 2\n" - "================================================================================\n" - "This program contains SeqAn code licensed under the following terms:\n" - "--------------------------------------------------------------------------------\n" - + license_string; - - EXPECT_EQ(std_cout, expected); - } + parser = get_parser("--copyright"); + parser.info.short_copyright = "short copyright line 1\nshort copyright line 2"; + + expected = "================================================================================\n" + "Copyright information for test_parser:\n" + "--------------------------------------------------------------------------------\n" + "test_parser full copyright information not available. Displaying short copyright information instead:\n" + "short copyright line 1\n" + "short copyright line 2\n" + "================================================================================\n" + "This program contains SeqAn code licensed under the following terms:\n" + "--------------------------------------------------------------------------------\n" + + license_text; + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); // Test --copyright with a non-empty short copyright and a non-empty long copyright. - copyright.info.long_copyright = "long copyright line 1\nlong copyright line 2"; - { - testing::internal::CaptureStdout(); - EXPECT_EXIT(copyright.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std_cout = testing::internal::GetCapturedStdout(); - - expected = "================================================================================\n" - "Copyright information for myApp:\n" - "--------------------------------------------------------------------------------\n" - "long copyright line 1\n" - "long copyright line 2\n" - "================================================================================\n" - "This program contains SeqAn code licensed under the following terms:\n" - "--------------------------------------------------------------------------------\n" - + license_string; - - EXPECT_EQ(std_cout, expected); - } + parser = get_parser("--copyright"); + parser.info.long_copyright = "long copyright line 1\nlong copyright line 2"; + + expected = "================================================================================\n" + "Copyright information for test_parser:\n" + "--------------------------------------------------------------------------------\n" + "long copyright line 1\n" + "long copyright line 2\n" + "================================================================================\n" + "This program contains SeqAn code licensed under the following terms:\n" + "--------------------------------------------------------------------------------\n" + + license_text; + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(parse_test, subcommand_parser) +TEST_F(format_help_test, subcommand_parser) { int option_value{}; - std::string option_value2{}; - char const * argv[]{"./test_parser", "-h"}; - sharg::parser top_level_parser{"test_parser", 2, argv, sharg::update_notifications::on, {"sub1", "sub2"}}; - sharg::detail::test_accessor::set_terminal_width(top_level_parser, 80); - top_level_parser.info.description.push_back("description"); - top_level_parser.add_option(option_value, - sharg::config{.short_id = 'f', .long_id = "foo", .description = "foo bar."}); - - testing::internal::CaptureStdout(); - EXPECT_EXIT(top_level_parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std::string std_cout = testing::internal::GetCapturedStdout(); + auto parser = get_subcommand_parser({"-h"}, {"sub1", "sub2"}); + parser.info.description.push_back("description"); + parser.add_option(option_value, sharg::config{.short_id = 'f', .long_id = "foo", .description = "foo bar."}); std::string expected = "test_parser\n" "===========\n" @@ -662,6 +566,5 @@ TEST(parse_test, subcommand_parser) " foo bar. Default: 0\n" "\n" + basic_options_str + "\n" + basic_version_str; - - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } diff --git a/test/unit/detail/format_html_test.cpp b/test/unit/detail/format_html_test.cpp index eadc8e02..dc03505b 100644 --- a/test/unit/detail/format_html_test.cpp +++ b/test/unit/detail/format_html_test.cpp @@ -5,27 +5,25 @@ #include #include +#include -TEST(html_format, empty_information) -{ - std::string my_stdout; - std::string expected; +class format_html_test : public sharg::test::test_fixture +{}; +TEST_F(format_html_test, empty_information) +{ // Empty html help page. - std::array const argv0{"./help_add_test", "--version-check", "false", "--export-help", "html"}; - sharg::parser parser0{"empty_options", argv0.size(), argv0.data()}; - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser0.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - my_stdout = testing::internal::GetCapturedStdout(); - expected = + auto parser = get_parser("--version-check", "false", "--export-help", "html"); + + std::string expected = std::string("\n" "\n" "\n" "\n" - "empty_options — \n" + "test_parser — \n" "\n" "\n" - "

empty_options

\n" + "

test_parser

\n" "
\n" "

Options

\n" "

Common options

\n" @@ -45,14 +43,12 @@ TEST(html_format, empty_information) #else "[html, man].\n" #endif - "
--version-check (bool)
\n" - "
Whether to check for the newest app version. Default: true
\n" "\n" "

Version

\n" "

\n" "Last update: \n" "
\n" - "empty_options version: \n" + "test_parser version: \n" "
\n" "Sharg version: " + std::string{sharg::sharg_version_cstring} @@ -60,68 +56,58 @@ TEST(html_format, empty_information) "
\n" "

\n" ""); - EXPECT_EQ(my_stdout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); - std::array const argv1{"./help_add_test", "--version-check", "false", "--export-help=html"}; - sharg::parser parser1{"empty_options", argv1.size(), argv1.data()}; - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser1.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - my_stdout = testing::internal::GetCapturedStdout(); - EXPECT_EQ(my_stdout, expected); + parser = get_parser("--version-check", "false", "--export-help=html"); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(html_format, full_information_information) +TEST_F(format_html_test, full_information_information) { - std::string my_stdout; - std::string expected; int option_value{5}; bool flag_value{false}; int8_t non_list_pos_opt_value{1}; std::vector list_pos_opt_value{}; // Full html help page. - std::array const argv0{"./help_add_test", "--version-check", "false", "--export-help", "html"}; - sharg::parser parser1{"program_full_options", argv0.size(), argv0.data()}; - parser1.info.synopsis.push_back("./some_binary_name synopsis"); - parser1.info.synopsis.push_back("./some_binary_name synopsis2"); - parser1.info.description.push_back("description"); - parser1.info.description.push_back("description2"); - parser1.info.short_description = "short description"; - parser1.info.url = "https://seqan.de"; - parser1.info.short_copyright = "short copyright"; - parser1.info.long_copyright = "long_copyright"; - parser1.info.citation = "citation"; - parser1.info.author = "author"; - parser1.info.email = "email"; - parser1.add_option(option_value, - sharg::config{.short_id = 'i', - .long_id = "int", - .description = "this is a int option.", - .default_message = "A number"}); - parser1.add_option(option_value, - sharg::config{.short_id = 'j', - .long_id = "jint", - .description = "this is a required int option.", - .required = true}); - parser1.add_flag(flag_value, sharg::config{.short_id = 'f', .long_id = "flag", .description = "this is a flag."}); - parser1.add_flag(flag_value, sharg::config{.short_id = 'k', .long_id = "kflag", .description = "this is a flag."}); - parser1.add_positional_option(non_list_pos_opt_value, sharg::config{.description = "this is a positional option."}); - parser1.add_positional_option(list_pos_opt_value, sharg::config{.description = "this is a positional option."}); - parser1.info.examples.push_back("example"); - parser1.info.examples.push_back("example2"); - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser1.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); + auto parser = get_parser("--version-check", "false", "--export-help", "html"); + parser.info.synopsis.push_back("./some_binary_name synopsis"); + parser.info.synopsis.push_back("./some_binary_name synopsis2"); + parser.info.description.push_back("description"); + parser.info.description.push_back("description2"); + parser.info.short_description = "short description"; + parser.info.url = "https://seqan.de"; + parser.info.short_copyright = "short copyright"; + parser.info.long_copyright = "long_copyright"; + parser.info.citation = "citation"; + parser.info.author = "author"; + parser.info.email = "email"; + parser.add_option(option_value, + sharg::config{.short_id = 'i', + .long_id = "int", + .description = "this is a int option.", + .default_message = "A number"}); + parser.add_option(option_value, + sharg::config{.short_id = 'j', + .long_id = "jint", + .description = "this is a required int option.", + .required = true}); + parser.add_flag(flag_value, sharg::config{.short_id = 'f', .long_id = "flag", .description = "this is a flag."}); + parser.add_flag(flag_value, sharg::config{.short_id = 'k', .long_id = "kflag", .description = "this is a flag."}); + parser.add_positional_option(non_list_pos_opt_value, sharg::config{.description = "this is a positional option."}); + parser.add_positional_option(list_pos_opt_value, sharg::config{.description = "this is a positional option."}); + parser.info.examples.push_back("example"); + parser.info.examples.push_back("example2"); - my_stdout = testing::internal::GetCapturedStdout(); - expected = std::string( + std::string expected = std::string( "\n" "\n" "\n" "\n" - "program_full_options — short description\n" + "test_parser — short description\n" "\n" "\n" - "

program_full_options

\n" + "

test_parser

\n" "
short description
\n" "

Synopsis

\n" "

\n" @@ -172,8 +158,6 @@ TEST(html_format, full_information_information) #else "[html, man].\n" #endif - "

--version-check (bool)
\n" - "
Whether to check for the newest app version. Default: true
\n" "\n" "

Examples

\n" "

\n" @@ -186,7 +170,7 @@ TEST(html_format, full_information_information) "

\n" "Last update: \n" "
\n" - "program_full_options version: \n" + "test_parser version: \n" "
\n" "Sharg version: " + std::string{sharg::sharg_version_cstring} @@ -200,7 +184,7 @@ TEST(html_format, full_information_information) "

\n" "

Legal

\n" "

\n" - "program_full_options Copyright: short copyright\n" + "test_parser Copyright: short copyright\n" "
\n" "Author: author\n" "
\n" @@ -214,21 +198,25 @@ TEST(html_format, full_information_information) "
\n" "

\n" ""); - EXPECT_EQ(my_stdout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(export_help, parse_error) +TEST_F(format_html_test, parse_error) { - std::array const argv{"./help_add_test", "--version-check", "false", "--export-help"}; - std::array const argv2{"./help_add_test", "--version-check", "false", "--export-help=atml"}; - std::array const argv3{"./help_add_test", "--version-check", "false", "--export-help", "atml"}; + std::vector argv1{"./help_add_test", "--version-check", "false", "--export-help"}; + std::vector argv2{"./help_add_test", "--version-check", "false", "--export-help=atml"}; + std::vector argv3{"./help_add_test", "--version-check", "false", "--export-help", "atml"}; + std::vector argv4{"./help_add_test", "--version-check", "false", "--export-help#html"}; // no value after --export-help - EXPECT_THROW((sharg::parser{"test_parser", argv.size(), argv.data()}), sharg::parser_error); + EXPECT_THROW((sharg::parser{"test_parser", argv1}), sharg::parser_error); // wrong value after --export-help - EXPECT_THROW((sharg::parser{"test_parser", argv2.size(), argv2.data()}), sharg::validation_error); + EXPECT_THROW((sharg::parser{"test_parser", argv2}), sharg::validation_error); // wrong value after --export-help - EXPECT_THROW((sharg::parser{"test_parser", argv3.size(), argv3.data()}), sharg::validation_error); + EXPECT_THROW((sharg::parser{"test_parser", argv3}), sharg::validation_error); + + // Currently not checking for `=` + EXPECT_NO_THROW((sharg::parser{"test_parser", argv4})); } diff --git a/test/unit/detail/format_man_test.cpp b/test/unit/detail/format_man_test.cpp index a69a17c2..007f503e 100644 --- a/test/unit/detail/format_man_test.cpp +++ b/test/unit/detail/format_man_test.cpp @@ -5,85 +5,199 @@ #include #include +#include // Reused global variables -struct format_man_test : public ::testing::Test +class format_man_test : public sharg::test::test_fixture { +protected: int option_value{5}; bool flag_value{false}; int8_t non_list_pos_opt_value{1}; std::vector list_pos_opt_value{}; std::string my_stdout{}; - static constexpr std::array argv{"./format_man_test", "--version-check", "false", "--export-help", "man"}; - std::string const version_str{sharg::sharg_version_cstring}; - std::string expected = R"(.TH DEFAULT 1 "December 01, 1994" "default 01.01.01" "default_man_page_title")" + + static inline std::string version_str{sharg::sharg_version_cstring}; + static inline std::string expected = + R"(.TH TEST_PARSER 1 "December 01, 1994" "test_parser 01.01.01" "default_man_page_title")" + "\n" + R"(.SH NAME)" + "\n" + R"(test_parser \- A short description here.)" + "\n" + R"(.SH SYNOPSIS)" + "\n" + R"(\fB./format_man_test\fP synopsis)" + "\n" + R"(.br)" + "\n" + R"(\fB./format_man_test\fP synopsis2)" + "\n" + R"(.SH DESCRIPTION)" + "\n" + R"(description)" + "\n" + R"(.sp)" + "\n" + R"(description2)" + "\n" + R"(.SH POSITIONAL ARGUMENTS)" + "\n" + R"(.TP)" + "\n" + R"(\fBARGUMENT-1\fP (\fIsigned 8 bit integer\fP))" + "\n" + R"(this is a positional option.)" + "\n" + R"(.TP)" + "\n" + R"(\fBARGUMENT-2\fP (\fIList\fP of \fIstd::string\fP))" + "\n" + R"(this is a positional option. Default: [])" + "\n" + R"(.SH OPTIONS)" + "\n" + R"(.TP)" + "\n" + R"(\fB-i\fP, \fB--int\fP (\fIsigned 32 bit integer\fP))" + "\n" + R"(this is a int option. Default: A number)" + "\n" + R"(.TP)" + "\n" + R"(\fB-j\fP, \fB--jint\fP (\fIsigned 32 bit integer\fP))" + "\n" + R"(this is a required int option.)" + "\n" + R"(.SH FLAGS)" + "\n" + R"(.SS SubFlags)" + "\n" + R"(here come all the flags)" + "\n" + R"(.TP)" + "\n" + R"(\fB-f\fP, \fB--flag\fP)" + "\n" + R"(this is a flag.)" + "\n" + R"(.TP)" + "\n" + R"(\fB-k\fP, \fB--kflag\fP)" + "\n" + R"(this is a flag.)" + "\n" + R"(.SS Common options)" + "\n" + R"(.TP)" + "\n" + R"(\fB-h\fP, \fB--help\fP)" + "\n" + R"(Prints the help page.)" + "\n" + R"(.TP)" + "\n" + R"(\fB-hh\fP, \fB--advanced-help\fP)" + "\n" + R"(Prints the help page including advanced options.)" + "\n" + R"(.TP)" + "\n" + R"(\fB--version\fP)" + "\n" + R"(Prints the version information.)" + "\n" + R"(.TP)" + "\n" + R"(\fB--copyright\fP)" + "\n" + R"(Prints the copyright/license information.)" + "\n" + R"(.TP)" + "\n" + R"(\fB--export-help\fP (std::string))" + "\n" + R"(Export the help page information. Value must be one of )" +#if SHARG_HAS_TDL + "[html, man, ctd, cwl]." +#else + "[html, man]." +#endif + "\n" + R"(.SH EXAMPLES)" + "\n" + R"(example)" + "\n" + R"(.sp)" + "\n" + R"(example2)" + "\n" + R"(.SH VERSION)" + "\n" + R"(\fBLast update: \fRDecember 01, 1994)" + "\n" + R"(.br)" + "\n" + R"(\fBtest_parser version: \fR01.01.01)" + "\n" + R"(.br)" + "\n" + R"(\fBSharg version: \fR)" + + version_str + "\n"; + + // Full info parser initialisation + void dummy_init(sharg::parser & parser) + { + parser.info.date = "December 01, 1994"; + parser.info.version = "01.01.01"; + parser.info.man_page_title = "default_man_page_title"; + parser.info.short_description = "A short description here."; + parser.info.synopsis.push_back("./format_man_test synopsis"); + parser.info.synopsis.push_back("./format_man_test synopsis2"); + parser.info.description.push_back("description"); + parser.info.description.push_back("description2"); + parser.add_option(option_value, + sharg::config{.short_id = 'i', + .long_id = "int", + .description = "this is a int option.", + .default_message = "A number"}); + parser.add_option(option_value, + sharg::config{.short_id = 'j', + .long_id = "jint", + .description = "this is a required int option.", + .required = true}); + parser.add_section("Flags"); + parser.add_subsection("SubFlags"); + parser.add_line("here come all the flags"); + parser.add_flag(flag_value, + sharg::config{.short_id = 'f', .long_id = "flag", .description = "this is a flag."}); + parser.add_flag(flag_value, + sharg::config{.short_id = 'k', .long_id = "kflag", .description = "this is a flag."}); + parser.add_positional_option(non_list_pos_opt_value, + sharg::config{.description = "this is a positional option."}); + parser.add_positional_option(list_pos_opt_value, sharg::config{.description = "this is a positional option."}); + parser.info.examples.push_back("example"); + parser.info.examples.push_back("example2"); + } +}; + +TEST_F(format_man_test, empty_information) +{ + // Create the dummy parser. + auto parser = get_parser("--version-check", "false", "--export-help", "man"); + parser.info.date = "December 01, 1994"; + parser.info.version = "01.01.01"; + parser.info.man_page_title = "default_man_page_title"; + parser.info.short_description = "A short description here."; + + std::string expected = R"(.TH TEST_PARSER 1 "December 01, 1994" "test_parser 01.01.01" "default_man_page_title")" "\n" R"(.SH NAME)" "\n" - R"(default \- A short description here.)" - "\n" - R"(.SH SYNOPSIS)" - "\n" - R"(\fB./format_man_test\fP synopsis)" - "\n" - R"(.br)" - "\n" - R"(\fB./format_man_test\fP synopsis2)" - "\n" - R"(.SH DESCRIPTION)" - "\n" - R"(description)" - "\n" - R"(.sp)" - "\n" - R"(description2)" - "\n" - R"(.SH POSITIONAL ARGUMENTS)" - "\n" - R"(.TP)" - "\n" - R"(\fBARGUMENT-1\fP (\fIsigned 8 bit integer\fP))" - "\n" - R"(this is a positional option.)" - "\n" - R"(.TP)" - "\n" - R"(\fBARGUMENT-2\fP (\fIList\fP of \fIstd::string\fP))" - "\n" - R"(this is a positional option. Default: [])" + R"(test_parser \- A short description here.)" "\n" R"(.SH OPTIONS)" "\n" - R"(.TP)" - "\n" - R"(\fB-i\fP, \fB--int\fP (\fIsigned 32 bit integer\fP))" - "\n" - R"(this is a int option. Default: A number)" - "\n" - R"(.TP)" - "\n" - R"(\fB-j\fP, \fB--jint\fP (\fIsigned 32 bit integer\fP))" - "\n" - R"(this is a required int option.)" - "\n" - R"(.SH FLAGS)" - "\n" - R"(.SS SubFlags)" - "\n" - R"(here come all the flags)" - "\n" - R"(.TP)" - "\n" - R"(\fB-f\fP, \fB--flag\fP)" - "\n" - R"(this is a flag.)" - "\n" - R"(.TP)" - "\n" - R"(\fB-k\fP, \fB--kflag\fP)" - "\n" - R"(this is a flag.)" - "\n" R"(.SS Common options)" "\n" R"(.TP)" @@ -120,20 +234,6 @@ struct format_man_test : public ::testing::Test #else "[html, man]." #endif - "\n" - R"(.TP)" - "\n" - R"(\fB--version-check\fP (bool))" - "\n" - R"(Whether to check for the newest app version. Default: true)" - "\n" - R"(.SH EXAMPLES)" - "\n" - R"(example)" - "\n" - R"(.sp)" - "\n" - R"(example2)" "\n" R"(.SH VERSION)" "\n" @@ -141,204 +241,83 @@ struct format_man_test : public ::testing::Test "\n" R"(.br)" "\n" - R"(\fBdefault version: \fR01.01.01)" + R"(\fBtest_parser version: \fR01.01.01)" "\n" R"(.br)" "\n" R"(\fBSharg version: \fR)" + version_str + "\n"; - - // Full info parser initialisation - void dummy_init(sharg::parser & parser) - { - parser.info.date = "December 01, 1994"; - parser.info.version = "01.01.01"; - parser.info.man_page_title = "default_man_page_title"; - parser.info.short_description = "A short description here."; - parser.info.synopsis.push_back("./format_man_test synopsis"); - parser.info.synopsis.push_back("./format_man_test synopsis2"); - parser.info.description.push_back("description"); - parser.info.description.push_back("description2"); - parser.add_option(option_value, - sharg::config{.short_id = 'i', - .long_id = "int", - .description = "this is a int option.", - .default_message = "A number"}); - parser.add_option(option_value, - sharg::config{.short_id = 'j', - .long_id = "jint", - .description = "this is a required int option.", - .required = true}); - parser.add_section("Flags"); - parser.add_subsection("SubFlags"); - parser.add_line("here come all the flags"); - parser.add_flag(flag_value, - sharg::config{.short_id = 'f', .long_id = "flag", .description = "this is a flag."}); - parser.add_flag(flag_value, - sharg::config{.short_id = 'k', .long_id = "kflag", .description = "this is a flag."}); - parser.add_positional_option(non_list_pos_opt_value, - sharg::config{.description = "this is a positional option."}); - parser.add_positional_option(list_pos_opt_value, sharg::config{.description = "this is a positional option."}); - parser.info.examples.push_back("example"); - parser.info.examples.push_back("example2"); - } -}; - -TEST_F(format_man_test, empty_information) -{ - // Create the dummy parser. - sharg::parser parser{"default", argv.size(), argv.data()}; - parser.info.date = "December 01, 1994"; - parser.info.version = "01.01.01"; - parser.info.man_page_title = "default_man_page_title"; - parser.info.short_description = "A short description here."; - - std::string const version_str{sharg::sharg_version_cstring}; - std::string expected_short = R"(.TH DEFAULT 1 "December 01, 1994" "default 01.01.01" "default_man_page_title")" - "\n" - R"(.SH NAME)" - "\n" - R"(default \- A short description here.)" - "\n" - R"(.SH OPTIONS)" - "\n" - R"(.SS Common options)" - "\n" - R"(.TP)" - "\n" - R"(\fB-h\fP, \fB--help\fP)" - "\n" - R"(Prints the help page.)" - "\n" - R"(.TP)" - "\n" - R"(\fB-hh\fP, \fB--advanced-help\fP)" - "\n" - R"(Prints the help page including advanced options.)" - "\n" - R"(.TP)" - "\n" - R"(\fB--version\fP)" - "\n" - R"(Prints the version information.)" - "\n" - R"(.TP)" - "\n" - R"(\fB--copyright\fP)" - "\n" - R"(Prints the copyright/license information.)" - "\n" - R"(.TP)" - "\n" - R"(\fB--export-help\fP (std::string))" - "\n" - R"(Export the help page information. Value must be one of )" -#if SHARG_HAS_TDL - "[html, man, ctd, cwl]." -#else - "[html, man]." -#endif - "\n" - R"(.TP)" - "\n" - R"(\fB--version-check\fP (bool))" - "\n" - R"(Whether to check for the newest app version. Default: true)" - "\n" - R"(.SH VERSION)" - "\n" - R"(\fBLast update: \fRDecember 01, 1994)" - "\n" - R"(.br)" - "\n" - R"(\fBdefault version: \fR01.01.01)" - "\n" - R"(.br)" - "\n" - R"(\fBSharg version: \fR)" - + version_str + "\n"; - - // Test the dummy parser with minimal information. - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - - my_stdout = testing::internal::GetCapturedStdout(); - EXPECT_EQ(my_stdout, expected_short); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } TEST_F(format_man_test, full_information) { // Create the dummy parser. - sharg::parser parser{"default", argv.size(), argv.data()}; + auto parser = get_parser("--version-check", "false", "--export-help", "man"); // Fill out the dummy parser with options and flags and sections and subsections. dummy_init(parser); - // Test the dummy parser without any copyright or citations. - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - my_stdout = testing::internal::GetCapturedStdout(); - EXPECT_EQ(my_stdout, expected); + // Test the dummy parser without any copyright or citations. + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } TEST_F(format_man_test, full_info_short_copyright) { // Create the dummy parser. - sharg::parser parser{"default", argv.size(), argv.data()}; + auto parser = get_parser("--version-check", "false", "--export-help", "man"); // Fill out the dummy parser with options and flags and sections and subsections. dummy_init(parser); + // Add a short copyright and test the dummy parser. parser.info.short_copyright = "short copyright"; - expected += R"(.SH LEGAL -\fBdefault Copyright: \fRshort copyright + + std::string expected = this->expected + R"(.SH LEGAL +\fBtest_parser Copyright: \fRshort copyright .br \fBSeqAn Copyright: \fR2006-2024 Knut Reinert, FU-Berlin; released under the 3-clause BSDL. )"; - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - - my_stdout = testing::internal::GetCapturedStdout(); - EXPECT_EQ(my_stdout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } TEST_F(format_man_test, full_info_short_and_citation) { // Create the dummy parser. - sharg::parser parser{"default", argv.size(), argv.data()}; + auto parser = get_parser("--version-check", "false", "--export-help", "man"); // Fill out the dummy parser with options and flags and sections and subsections. dummy_init(parser); + // Add a short copyright & citation and test the dummy parser. parser.info.short_copyright = "short copyright"; parser.info.citation = "citation"; - expected += R"(.SH LEGAL -\fBdefault Copyright: \fRshort copyright + + std::string expected = this->expected + R"(.SH LEGAL +\fBtest_parser Copyright: \fRshort copyright .br \fBSeqAn Copyright: \fR2006-2024 Knut Reinert, FU-Berlin; released under the 3-clause BSDL. .br \fBIn your academic works please cite: \fRcitation )"; - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - - my_stdout = testing::internal::GetCapturedStdout(); - EXPECT_EQ(my_stdout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } TEST_F(format_man_test, full_info_short_long_and_citation) { // Create the dummy parser. - sharg::parser parser{"default", argv.size(), argv.data()}; + auto parser = get_parser("--version-check", "false", "--export-help", "man"); // Fill out the dummy parser with options and flags and sections and subsections. dummy_init(parser); + // Add a short copyright & citation & long copyright and test the dummy parser. parser.info.short_copyright = "short copyright"; parser.info.citation = "citation"; parser.info.long_copyright = "looong copyright"; - expected += R"(.SH LEGAL -\fBdefault Copyright: \fRshort copyright + + std::string expected = this->expected + R"(.SH LEGAL +\fBtest_parser Copyright: \fRshort copyright .br \fBSeqAn Copyright: \fR2006-2024 Knut Reinert, FU-Berlin; released under the 3-clause BSDL. .br @@ -346,51 +325,43 @@ TEST_F(format_man_test, full_info_short_long_and_citation) .br For full copyright and/or warranty information see \fB--copyright\fR. )"; - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - - my_stdout = testing::internal::GetCapturedStdout(); - EXPECT_EQ(my_stdout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } TEST_F(format_man_test, full_info_author) { // Create the dummy parser. - sharg::parser parser{"default", argv.size(), argv.data()}; + auto parser = get_parser("--version-check", "false", "--export-help", "man"); // Fill out the dummy parser with options and flags and sections and subsections. dummy_init(parser); + // Add a short copyright and test the dummy parser. parser.info.author = "author"; - expected += R"(.SH LEGAL + + std::string expected = this->expected + R"(.SH LEGAL \fBAuthor: \fRauthor .br \fBSeqAn Copyright: \fR2006-2024 Knut Reinert, FU-Berlin; released under the 3-clause BSDL. )"; - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - - my_stdout = testing::internal::GetCapturedStdout(); - EXPECT_EQ(my_stdout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } TEST_F(format_man_test, full_info_email) { // Create the dummy parser. - sharg::parser parser{"default", argv.size(), argv.data()}; + auto parser = get_parser("--version-check", "false", "--export-help", "man"); // Fill out the dummy parser with options and flags and sections and subsections. dummy_init(parser); + // Add a short copyright and test the dummy parser. parser.info.email = "email"; - expected += R"(.SH LEGAL + + std::string expected = this->expected + R"(.SH LEGAL \fBContact: \fRemail .br \fBSeqAn Copyright: \fR2006-2024 Knut Reinert, FU-Berlin; released under the 3-clause BSDL. )"; - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - - my_stdout = testing::internal::GetCapturedStdout(); - EXPECT_EQ(my_stdout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } diff --git a/test/unit/detail/seqan3_test.cpp b/test/unit/detail/seqan3_test.cpp index 56eea4d2..765d9475 100644 --- a/test/unit/detail/seqan3_test.cpp +++ b/test/unit/detail/seqan3_test.cpp @@ -5,6 +5,7 @@ #include #include +#include #if !__has_include() # error "seqan3/version.hpp is not available" @@ -22,12 +23,10 @@ std::string const basic_options_str = " Common options\n" " --export-help (std::string)\n" " Export the help page information. Value must be one of " #if SHARG_HAS_TDL - "[html, man,\n ctd, cwl].\n" + "[html, man,\n ctd, cwl].\n"; #else - "[html, man].\n" + "[html, man].\n"; #endif - " --version-check (bool)\n" - " Whether to check for the newest app version. Default: true\n"; std::string const basic_version_str = "VERSION\n" " Last update:\n" @@ -38,34 +37,15 @@ std::string const basic_version_str = "VERSION\n" " SeqAn version: " + std::string{seqan3::seqan3_version_cstring} + "\n"; -namespace sharg::detail -{ -struct test_accessor -{ - static void set_terminal_width(sharg::parser & parser, unsigned terminal_width) - { - std::visit( - [terminal_width](auto & f) - { - if constexpr (std::is_same_v) - f.layout = sharg::detail::format_help::console_layout_struct{terminal_width}; - }, - parser.format); - } -}; -} // namespace sharg::detail +class seqan3_test : public sharg::test::test_fixture +{}; -TEST(help_page_printing, no_information) +TEST_F(seqan3_test, version_string) { - std::array const argv{"./help_add_test", "--version-check", "false", "-h"}; - sharg::parser parser1{"test_parser", argv.size(), argv.data()}; - sharg::detail::test_accessor::set_terminal_width(parser1, 80); - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser1.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std::string std_cout = testing::internal::GetCapturedStdout(); + auto parser = get_parser("-h"); std::string expected = "test_parser\n" "===========\n" "\nOPTIONS\n\n" + basic_options_str + "\n" + basic_version_str; - EXPECT_EQ(std_cout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } diff --git a/test/unit/detail/type_name_as_string_test.cpp b/test/unit/detail/type_name_as_string_test.cpp index b94a7755..a2a5399b 100644 --- a/test/unit/detail/type_name_as_string_test.cpp +++ b/test/unit/detail/type_name_as_string_test.cpp @@ -11,9 +11,11 @@ // Some test namespace to check if namespace information are preserved within the naming. namespace foo { + template struct bar {}; + } // namespace foo // Some types to test if type inspection works as expected. @@ -30,24 +32,23 @@ using reflection_types = ::testing::Types class type_inspection : public ::testing::Test { - public: // Returns the name of the type according to the list of names defined above. std::string const expected_name() const { - if constexpr (std::is_same_v) + if constexpr (std::same_as) return "char"; - else if constexpr (std::is_same_v) + else if constexpr (std::same_as) return "char16_t const"; - else if constexpr (std::is_same_v) + else if constexpr (std::same_as) return "char32_t &"; - else if constexpr (std::is_same_v) + else if constexpr (std::same_as) return "short*"; - else if constexpr (std::is_same_v) + else if constexpr (std::same_as) return "double const* const"; - else if constexpr (std::is_same_v const &>) + else if constexpr (std::same_as const &>) return "foo::bar const &"; - else if constexpr (std::is_same_v>>) + else if constexpr (std::same_as>>) #ifdef _LIBCPP_VERSION return "foo::bar>"; #else diff --git a/test/unit/detail/version_check_test.hpp b/test/unit/detail/version_check_test.hpp index a16e040e..651e057a 100644 --- a/test/unit/detail/version_check_test.hpp +++ b/test/unit/detail/version_check_test.hpp @@ -4,116 +4,107 @@ #include +#include + #include +#include #include //------------------------------------------------------------------------------ // test fixtures //------------------------------------------------------------------------------ -namespace sharg::detail -{ -struct test_accessor -{ - static auto & version_check_future(sharg::parser & parser) - { - return parser.version_check_future; - } -}; -} // namespace sharg::detail - -bool wait_for(sharg::parser & parser) -{ - auto & future = sharg::detail::test_accessor::version_check_future(parser); - - if (future.valid()) - return future.get(); - return false; -} - -struct version_check : public ::testing::Test +class version_check_test : public sharg::test::test_fixture { - char const * const OPTION_VERSION_CHECK = "--version-check"; - char const * const OPTION_OFF = "false"; - char const * const OPTION_ON = "true"; +protected: + std::string const OPTION_VERSION_CHECK = "--version-check"; + std::string const OPTION_OFF = "false"; + std::string const OPTION_ON = "true"; - std::string const app_name = std::string{"test_version_check"}; + std::string const app_name = "test_version_check"; // This tmp_filename will create the file "version_checker.tmpfile" in a unique folder. sharg::test::tmp_filename tmp_file{"version_checker.tmpfile"}; void randomise_home_folder() { - using namespace std::string_literals; auto tmp_directory = tmp_file.get_path().parent_path(); + std::string const base_path = tmp_directory.string(); - int result = setenv(sharg::detail::version_checker::home_env_name, tmp_directory.c_str(), 1); - if (result != 0) - throw std::runtime_error{"Couldn't set environment variable 'home_env_name' (="s - + sharg::detail::version_checker::home_env_name + ")"s}; + if (setenv(sharg::detail::version_checker::home_env_name, base_path.data(), 1)) + throw std::runtime_error{"Couldn't set environment variable 'home_env_name' (=" + + std::string{sharg::detail::version_checker::home_env_name} + ")"}; - auto is_prefix_path = [](std::string const & base_path, std::string const & path) - { - auto && it_pair = std::mismatch(base_path.begin(), base_path.end(), path.begin(), path.end()); - return it_pair.first + 1 == base_path.end(); - }; + std::string const path = app_tmp_path().string(); // Must be string because app_tmp_path() returns a temporary. - if (is_prefix_path(tmp_directory, app_tmp_path())) + if (!path.starts_with(base_path)) throw std::runtime_error{"Setting the environment variable 'home_env_name' didn't have the correct effect" - " ("s - + std::string{tmp_directory} + " is not a prefix of "s - + std::string{app_tmp_path()} + ")"s}; + " (" + + base_path + " is not a prefix of " + path + ")"}; } - void SetUp() override + static bool wait_for(sharg::parser & parser) { - ::testing::Test::SetUp(); + auto & future = sharg::detail::test_accessor::version_check_future(parser); + if (future.valid()) + return future.get(); + return false; + } + + void SetUp() override + { // set HOME environment to a random home folder before starting each test case randomise_home_folder(); } - std::filesystem::path app_tmp_path() + static std::filesystem::path app_tmp_path() { return sharg::detail::version_checker::get_path(); } - std::filesystem::path app_version_filename() + std::filesystem::path app_version_filename() const { return app_tmp_path() / (app_name + ".version"); } - std::filesystem::path app_timestamp_filename() + std::filesystem::path app_timestamp_filename() const { return sharg::detail::version_checker{app_name, std::string{}}.timestamp_filename; } - std::chrono::duration::rep current_unix_timestamp() + static auto current_unix_timestamp() { return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) .count(); } - std::regex timestamp_regex{"^[[:digit:]]+$"}; // only digits + static inline std::regex const timestamp_regex{"^[[:digit:]]+$"}; // only digits - std::tuple simulate_parser(int argc, char const ** argv) + template + std::tuple simulate_parser(arg_ts &&... args) { - // make sure that the environment variable is not set - std::string previous_value{}; - if (char * env = std::getenv("SHARG_NO_VERSION_CHECK")) - { - previous_value = env; - unsetenv("SHARG_NO_VERSION_CHECK"); - } - bool app_call_succeeded{false}; + bool dummy_flag{false}; - sharg::parser parser{app_name, argc, argv}; + // make sure that the environment variable is not set + std::string const cached_env_var = []() + { + std::string result{}; + if (char * env = std::getenv("SHARG_NO_VERSION_CHECK")) + { + result = env; + unsetenv("SHARG_NO_VERSION_CHECK"); + } + return result; + }(); + + std::vector arguments{app_name, std::forward(args)...}; + sharg::parser parser{app_name, std::move(arguments), sharg::update_notifications::on}; parser.info.version = "2.3.4"; // In case we don't want to specify --version-check but avoid that short help format will be set (no arguments) - bool dummy{false}; - parser.add_flag(dummy, sharg::config{.short_id = 'f'}); + parser.add_flag(dummy_flag, sharg::config{.short_id = 'f'}); testing::internal::CaptureStdout(); testing::internal::CaptureStderr(); @@ -125,13 +116,13 @@ struct version_check : public ::testing::Test // any interference with following tests app_call_succeeded = wait_for(parser); - if (!previous_value.empty()) - setenv("SHARG_NO_VERSION_CHECK", previous_value.c_str(), 1); + if (!cached_env_var.empty()) + setenv("SHARG_NO_VERSION_CHECK", cached_env_var.c_str(), 1); return {out, err, app_call_succeeded}; } - bool remove_files_from_path() + bool remove_files_from_path() const { return (!std::filesystem::exists(app_version_filename()) || std::filesystem::remove(app_version_filename())) && (!std::filesystem::exists(app_timestamp_filename()) @@ -139,7 +130,7 @@ struct version_check : public ::testing::Test } template - bool create_file(std::filesystem::path const & filename, message_type const & message) + static bool create_file(std::filesystem::path const & filename, message_type const & message) { std::ofstream out_file{filename}; @@ -152,7 +143,7 @@ struct version_check : public ::testing::Test return true; } - std::string read_file(std::filesystem::path const & filename) + static std::string read_first_line(std::filesystem::path const & filename) { std::ifstream in_file{filename}; std::string line{}; @@ -167,25 +158,22 @@ struct version_check : public ::testing::Test } }; -struct sanity_checks : public version_check -{}; - //------------------------------------------------------------------------------ // sanity checks //------------------------------------------------------------------------------ // even if the homedir might not be writable at least the tmp dir should be -TEST_F(sanity_checks, path_availability) +TEST_F(version_check_test, sanity_path_availability) { EXPECT_FALSE(app_tmp_path().empty()) << "No writable directory found. All other tests cannot be trusted!"; } -TEST_F(sanity_checks, create_and_delete_files) +TEST_F(version_check_test, sanity_create_and_delete_files) { EXPECT_TRUE(create_file(app_version_filename(), "20.5.9")); - EXPECT_TRUE(create_file(app_timestamp_filename(), current_unix_timestamp())); - EXPECT_TRUE(std::filesystem::exists(app_version_filename())); + + EXPECT_TRUE(create_file(app_timestamp_filename(), current_unix_timestamp())); EXPECT_TRUE(std::filesystem::exists(app_timestamp_filename())); EXPECT_TRUE(remove_files_from_path()); // clear files again @@ -193,10 +181,9 @@ TEST_F(sanity_checks, create_and_delete_files) EXPECT_FALSE(std::filesystem::exists(app_timestamp_filename())); } -TEST_F(sanity_checks, cookie) +TEST_F(version_check_test, sanity_cookie) { - char const * argv[3] = {app_name.c_str(), OPTION_VERSION_CHECK, OPTION_ON}; - auto [out, err, app_call_succeeded] = simulate_parser(3, argv); + auto [out, err, app_call_succeeded] = simulate_parser(OPTION_VERSION_CHECK, OPTION_ON, "-f"); if (app_call_succeeded) { @@ -208,15 +195,18 @@ TEST_F(sanity_checks, cookie) // ``` // This should not be tested via EXPECT_EQ(..) because then sharg tests fail if the server wasn't // configured correctly and we want to be independent of the server. - std::cout << "Cookie:" << std::endl; + std::string line{}; + std::cout << "Cookie:\n"; + std::ifstream app_version_file{app_version_filename()}; - std::string line; std::getline(app_version_file, line); - std::cout << line << " <-- Should be UNREGISTERED_APP!" << std::endl; + std::cout << line << " <-- Should be UNREGISTERED_APP!\n"; + + std::cout << app_version_filename().string() << '\n'; + std::getline(app_version_file, line); std::cout << line << " <-- Should be the latest Sharg version! " - << "Updates done here: https://github.com/OpenMS/usage_plots/blob/master/seqan_versions.txt" - << std::endl; + << "Updates done here: https://github.com/OpenMS/usage_plots/blob/master/seqan_versions.txt\n"; } } @@ -224,69 +214,46 @@ TEST_F(sanity_checks, cookie) // version checks //------------------------------------------------------------------------------ -TEST_F(version_check, option_on) +TEST_F(version_check_test, option_on) { - char const * argv[3] = {app_name.c_str(), OPTION_VERSION_CHECK, OPTION_ON}; - - auto [out, err, app_call_succeeded] = simulate_parser(3, argv); + auto [out, err, app_call_succeeded] = simulate_parser(OPTION_VERSION_CHECK, OPTION_ON, "-f"); EXPECT_EQ(out, ""); EXPECT_EQ(err, ""); // no timestamp is written since the decision was made explicitly if (app_call_succeeded) - { EXPECT_TRUE(std::filesystem::exists(app_version_filename())); - } else - { - std::cout << "App call did not succeed (server offline?) and could thus not be tested.\n"; - } + GTEST_SKIP() << "App call did not succeed (server offline?) and could thus not be tested."; EXPECT_TRUE(remove_files_from_path()); // clear files again } // Note that we cannot test interactiveness because google test captures std::cin and thus // sharg::detail::input_is_terminal() is always false -TEST_F(version_check, option_implicitely_on) +TEST_F(version_check_test, option_implicitely_on) { - char const * argv[2] = {app_name.c_str(), "-f"}; - - auto [out, err, app_call_succeeded] = simulate_parser(2, argv); + auto [out, err, app_call_succeeded] = simulate_parser("-f"); EXPECT_EQ(out, ""); - EXPECT_EQ(err, - "\n#######################################################################\n" - " Automatic Update Notifications\n" - "#######################################################################\n" - " This app performs automatic checks for updates. For more information\n" - " see: https://docs.seqan.de/sharg/main_user/about_update_notifications.html\n" - "#######################################################################\n\n"); + EXPECT_EQ(err, ""); // make sure that all files now exist EXPECT_TRUE(std::filesystem::exists(app_timestamp_filename())) << app_timestamp_filename(); - EXPECT_TRUE(std::regex_match(read_file(app_timestamp_filename()), timestamp_regex)); + EXPECT_TRUE(std::regex_match(read_first_line(app_timestamp_filename()), timestamp_regex)); - if (app_call_succeeded) - { - EXPECT_TRUE(std::filesystem::exists(app_version_filename())); - } - else - { - std::cout << "App call did not succeed (server offline?) and could thus not be tested.\n"; - } + EXPECT_FALSE(app_call_succeeded); EXPECT_TRUE(remove_files_from_path()); // clear files again } -TEST_F(version_check, time_out) // while implicitly on +TEST_F(version_check_test, time_out) // while implicitly on { - char const * argv[2] = {app_name.c_str(), "-f"}; - // create timestamp files ASSERT_TRUE(create_file(app_timestamp_filename(), current_unix_timestamp())); - auto [out, err, app_call_succeeded] = simulate_parser(2, argv); + auto [out, err, app_call_succeeded] = simulate_parser("-f"); (void)app_call_succeeded; EXPECT_EQ(out, ""); @@ -297,20 +264,22 @@ TEST_F(version_check, time_out) // while implicitly on EXPECT_TRUE(remove_files_from_path()); // clear files again } -TEST_F(version_check, environment_variable_set) +TEST_F(version_check_test, environment_variable_set) { + bool dummy{false}; + // store variable for resetting it - std::string previous_value{}; - if (char * env = std::getenv("SHARG_NO_VERSION_CHECK")) - previous_value = env; + std::string const cached_env_var = []() + { + if (char * env = std::getenv("SHARG_NO_VERSION_CHECK")) + return std::string{env}; + return std::string{}; + }(); setenv("SHARG_NO_VERSION_CHECK", "foo", 1); - char const * argv[2] = {app_name.c_str(), "-f"}; - - sharg::parser parser{app_name, 2, argv}; + sharg::parser parser{app_name, {app_name, "-f"}, sharg::update_notifications::on}; parser.info.version = "2.3.4"; - bool dummy{false}; parser.add_flag(dummy, sharg::config{.short_id = 'f'}); testing::internal::CaptureStdout(); @@ -330,19 +299,17 @@ TEST_F(version_check, environment_variable_set) EXPECT_FALSE(std::filesystem::exists(app_timestamp_filename())) << app_timestamp_filename(); EXPECT_FALSE(std::filesystem::exists(app_version_filename())); - if (previous_value.empty()) + if (cached_env_var.empty()) unsetenv("SHARG_NO_VERSION_CHECK"); else - setenv("SHARG_NO_VERSION_CHECK", previous_value.c_str(), 1); + setenv("SHARG_NO_VERSION_CHECK", cached_env_var.c_str(), 1); EXPECT_TRUE(remove_files_from_path()); // clear files again } -TEST_F(version_check, option_off) +TEST_F(version_check_test, option_off) { - char const * argv[3] = {app_name.c_str(), OPTION_VERSION_CHECK, OPTION_OFF}; - - auto [out, err, app_call_succeeded] = simulate_parser(3, argv); + auto [out, err, app_call_succeeded] = simulate_parser(OPTION_VERSION_CHECK, OPTION_OFF, "-f"); (void)app_call_succeeded; EXPECT_EQ(out, ""); @@ -355,29 +322,34 @@ TEST_F(version_check, option_off) EXPECT_TRUE(remove_files_from_path()); // clear files again } -TEST_F(version_check, option_off_with_help_page) +TEST_F(version_check_test, option_off_with_help_page) { // Version check option always needs to be parsed, even if special formats get selected - char const * argv[4] = {app_name.c_str(), "-h", OPTION_VERSION_CHECK, OPTION_OFF}; - - std::string previous_value{}; - if (char * env = std::getenv("SHARG_NO_VERSION_CHECK")) + std::string const cached_env_var = []() { - previous_value = env; - unsetenv("SHARG_NO_VERSION_CHECK"); - } + std::string result{}; + if (char * env = std::getenv("SHARG_NO_VERSION_CHECK")) + { + result = env; + unsetenv("SHARG_NO_VERSION_CHECK"); + } + return result; + }(); - sharg::parser parser{app_name, 4, argv}; + sharg::parser parser{app_name, {app_name, "-h", OPTION_VERSION_CHECK, OPTION_OFF}, sharg::update_notifications::on}; parser.info.version = "2.3.4"; - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); + // flag = true; + std::string const help_message = get_parse_cout_on_exit(parser); + // flag = false; + EXPECT_NE(help_message, ""); // call future.get() to artificially wait for the thread to finish and avoid // any interference with following tests EXPECT_FALSE(wait_for(parser)); - if (!previous_value.empty()) - setenv("SHARG_NO_VERSION_CHECK", previous_value.c_str(), 1); + if (!cached_env_var.empty()) + setenv("SHARG_NO_VERSION_CHECK", cached_env_var.c_str(), 1); // no timestamp is written since the decision was made explicitly EXPECT_FALSE(std::filesystem::exists(app_version_filename())) << app_version_filename(); @@ -387,66 +359,60 @@ TEST_F(version_check, option_off_with_help_page) // case: the current parser has a smaller Sharg version than is present in the version file #if !defined(NDEBUG) -TEST_F(version_check, smaller_sharg_version) +TEST_F(version_check_test, smaller_sharg_version) { - char const * argv[3] = {app_name.c_str(), OPTION_VERSION_CHECK, OPTION_ON}; - // create version file with euqal app version and a greater Sharg version than the current create_file(app_version_filename(), std::string{"2.3.4\n20.5.9"}); // create timestamp file that dates one day before current to trigger a message (one day = 86400 seconds) ASSERT_TRUE(create_file(app_timestamp_filename(), current_unix_timestamp() - 100401)); - auto [out, err, app_call_succeeded] = simulate_parser(3, argv); + auto [out, err, app_call_succeeded] = simulate_parser(OPTION_VERSION_CHECK, OPTION_ON, "-f"); (void)app_call_succeeded; EXPECT_EQ(out, ""); EXPECT_EQ(err, sharg::detail::version_checker::message_sharg_update); - EXPECT_TRUE(std::regex_match(read_file(app_timestamp_filename()), timestamp_regex)); + EXPECT_TRUE(std::regex_match(read_first_line(app_timestamp_filename()), timestamp_regex)); EXPECT_TRUE(remove_files_from_path()); // clear files again } // case: the current parser has a greater app version than is present in the version file -TEST_F(version_check, greater_app_version) +TEST_F(version_check_test, greater_app_version) { - char const * argv[3] = {app_name.c_str(), OPTION_VERSION_CHECK, OPTION_ON}; - // create version file with equal Sharg version and a smaller app version than the current ASSERT_TRUE(create_file(app_version_filename(), std::string{"1.5.9\n"} + sharg::sharg_version_cstring)); // create timestamp file that dates one day before current to trigger a message ASSERT_TRUE(create_file(app_timestamp_filename(), current_unix_timestamp() - 100401)); // one day = 86400 seconds - auto [out, err, app_call_succeeded] = simulate_parser(3, argv); + auto [out, err, app_call_succeeded] = simulate_parser(OPTION_VERSION_CHECK, OPTION_ON, "-f"); (void)app_call_succeeded; EXPECT_EQ(out, ""); EXPECT_EQ(err, sharg::detail::version_checker::message_registered_app_update); - EXPECT_TRUE(std::regex_match(read_file(app_timestamp_filename()), timestamp_regex)); + EXPECT_TRUE(std::regex_match(read_first_line(app_timestamp_filename()), timestamp_regex)); EXPECT_TRUE(remove_files_from_path()); // clear files again } -TEST_F(version_check, unregistered_app) +TEST_F(version_check_test, unregistered_app) { - char const * argv[3] = {app_name.c_str(), OPTION_VERSION_CHECK, OPTION_ON}; - // create version file with equal Sharg version and a smaller app version than the current ASSERT_TRUE(create_file(app_version_filename(), std::string{"UNREGISTERED_APP\n"} + sharg::sharg_version_cstring)); // create timestamp file that dates one day before current to trigger a message ASSERT_TRUE(create_file(app_timestamp_filename(), current_unix_timestamp() - 100401)); // one day = 86400 seconds - auto [out, err, app_call_succeeded] = simulate_parser(3, argv); + auto [out, err, app_call_succeeded] = simulate_parser(OPTION_VERSION_CHECK, OPTION_ON, "-f"); (void)app_call_succeeded; EXPECT_EQ(out, ""); EXPECT_EQ(err, sharg::detail::version_checker::message_unregistered_app); - EXPECT_TRUE(std::regex_match(read_file(app_timestamp_filename()), timestamp_regex)); + EXPECT_TRUE(std::regex_match(read_first_line(app_timestamp_filename()), timestamp_regex)); EXPECT_TRUE(remove_files_from_path()); // clear files again } @@ -454,29 +420,28 @@ TEST_F(version_check, unregistered_app) // case: the current parser has a smaller app version than is present in the version file #if defined(NDEBUG) -TEST_F(version_check, smaller_app_version) +TEST_F(version_check_test, smaller_app_version) { - char const * argv[3] = {app_name.c_str(), OPTION_VERSION_CHECK, OPTION_ON}; - // create version file with equal Sharg version and a greater app version than the current ASSERT_TRUE(create_file(app_version_filename(), std::string{"20.5.9\n"} + sharg::sharg_version_cstring)); // create timestamp file that dates one day before current to trigger a message (one day = 86400 seconds) ASSERT_TRUE(create_file(app_timestamp_filename(), current_unix_timestamp() - 100401)); - auto [out, err, app_call_succeeded] = simulate_parser(3, argv); + auto [out, err, app_call_succeeded] = simulate_parser(OPTION_VERSION_CHECK, OPTION_ON, "-f"); (void)app_call_succeeded; EXPECT_EQ(out, ""); EXPECT_EQ(err, (sharg::detail::version_checker{app_name, "2.3.4"}.message_app_update)); - EXPECT_TRUE(std::regex_match(read_file(app_timestamp_filename()), timestamp_regex)); + EXPECT_TRUE(std::regex_match(read_first_line(app_timestamp_filename()), timestamp_regex)); EXPECT_TRUE(remove_files_from_path()); // clear files again } -TEST_F(version_check, smaller_app_version_custom_url) +TEST_F(version_check_test, smaller_app_version_custom_url) { + bool dummy_flag{false}; std::string previous_value{}; if (char * env = std::getenv("SHARG_NO_VERSION_CHECK")) { @@ -484,15 +449,14 @@ TEST_F(version_check, smaller_app_version_custom_url) unsetenv("SHARG_NO_VERSION_CHECK"); } - char const * argv[3] = {app_name.c_str(), OPTION_VERSION_CHECK, OPTION_ON}; - // create version file with equal Sharg version and a greater app version than the current ASSERT_TRUE(create_file(app_version_filename(), std::string{"20.5.9\n"} + sharg::sharg_version_cstring)); // create timestamp file that dates one day before current to trigger a message (one day = 86400 seconds) ASSERT_TRUE(create_file(app_timestamp_filename(), current_unix_timestamp() - 100401)); - sharg::parser parser{app_name, 3, argv}; + sharg::parser parser{app_name, {app_name, OPTION_VERSION_CHECK, OPTION_ON, "-f"}}; + parser.add_flag(dummy_flag, sharg::config{.short_id = 'f'}); parser.info.version = "2.3.4"; parser.info.url = "https//foo.de"; @@ -509,7 +473,7 @@ TEST_F(version_check, smaller_app_version_custom_url) EXPECT_EQ(out, ""); EXPECT_EQ(err, (sharg::detail::version_checker{app_name, parser.info.version, parser.info.url}.message_app_update)); - EXPECT_TRUE(std::regex_match(read_file(app_timestamp_filename()), timestamp_regex)); + EXPECT_TRUE(std::regex_match(read_first_line(app_timestamp_filename()), timestamp_regex)); if (!previous_value.empty()) setenv("SHARG_NO_VERSION_CHECK", previous_value.c_str(), 1); @@ -518,33 +482,29 @@ TEST_F(version_check, smaller_app_version_custom_url) } #endif // defined(NDEBUG) -TEST_F(version_check, user_specified_never) +TEST_F(version_check_test, user_specified_never) { - char const * argv[2] = {app_name.c_str(), "-f"}; // no explicit version check option - // create timestamp files ASSERT_TRUE(create_file(app_timestamp_filename(), "NEVER")); - auto [out, err, app_call_succeeded] = simulate_parser(2, argv); + auto [out, err, app_call_succeeded] = simulate_parser("-f"); (void)app_call_succeeded; EXPECT_EQ(out, ""); EXPECT_EQ(err, ""); EXPECT_FALSE(std::filesystem::exists(app_version_filename())); - EXPECT_EQ(read_file(app_timestamp_filename()), "NEVER"); // should not be modified + EXPECT_EQ(read_first_line(app_timestamp_filename()), "NEVER"); // should not be modified EXPECT_TRUE(remove_files_from_path()); // clear files again } -TEST_F(version_check, user_specified_always) +TEST_F(version_check_test, user_specified_always) { - char const * argv[2] = {app_name.c_str(), "-f"}; // no explicit version check option - // create timestamp files ASSERT_TRUE(create_file(app_timestamp_filename(), "ALWAYS")); - auto [out, err, app_call_succeeded] = simulate_parser(2, argv); + auto [out, err, app_call_succeeded] = simulate_parser("-f"); EXPECT_EQ(out, ""); EXPECT_EQ(err, ""); @@ -558,21 +518,19 @@ TEST_F(version_check, user_specified_always) std::cout << "App call did not succeed (server offline?) and could thus not be tested.\n"; } - EXPECT_EQ(read_file(app_timestamp_filename()), "ALWAYS"); // should not be modified - EXPECT_TRUE(remove_files_from_path()); // clear files again + EXPECT_EQ(read_first_line(app_timestamp_filename()), "ALWAYS"); // should not be modified + EXPECT_TRUE(remove_files_from_path()); // clear files again EXPECT_TRUE(remove_files_from_path()); // clear files again } -TEST_F(version_check, wrong_version_string) +TEST_F(version_check_test, wrong_version_string) { - char const * argv[2] = {app_name.c_str(), "-f"}; // no explicit version check option - // create a corrupted version file. Nothing should be printed, it is just ignored ASSERT_TRUE(create_file(app_version_filename(), std::string{"20.wrong.9\nalso.wrong.4"})); ASSERT_TRUE(create_file(app_timestamp_filename(), "ALWAYS")); - auto [out, err, app_call_succeeded] = simulate_parser(2, argv); + auto [out, err, app_call_succeeded] = simulate_parser("-f"); (void)app_call_succeeded; EXPECT_EQ(out, ""); diff --git a/test/unit/parser/enumeration_names_test.cpp b/test/unit/parser/enumeration_names_test.cpp index f5276ed1..a3b1fcd9 100644 --- a/test/unit/parser/enumeration_names_test.cpp +++ b/test/unit/parser/enumeration_names_test.cpp @@ -7,9 +7,11 @@ #include #include +#include namespace foo { + enum class bar { one, @@ -21,19 +23,23 @@ auto enumeration_names(bar) { return std::unordered_map{{"one", bar::one}, {"two", bar::two}, {"three", bar::three}}; } + } // namespace foo namespace Other { + enum class bar { one, two }; + } // namespace Other namespace sharg::custom { + template <> struct parsing { @@ -42,106 +48,74 @@ struct parsing {"two", Other::bar::two}, {"2", Other::bar::two}}; }; -} // namespace sharg::custom -TEST(parse_type_test, parse_success_enum_option) -{ - { - foo::bar option_value{}; - - char const * argv[] = {"./parser_test", "-e", "two"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - parser.add_option(option_value, sharg::config{.short_id = 'e'}); +} // namespace sharg::custom - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE(option_value == foo::bar::two); - } +class enumeration_names_test : public sharg::test::test_fixture +{}; - { - Other::bar option_value{}; +TEST_F(enumeration_names_test, parse_success_enum_option) +{ + foo::bar value{}; + Other::bar value2{}; - char const * argv[] = {"./parser_test", "-e", "two"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - parser.add_option(option_value, sharg::config{.short_id = 'e'}); + auto parser = get_parser("-e", "two"); + parser.add_option(value, sharg::config{.short_id = 'e'}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_TRUE(value == foo::bar::two); - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE(option_value == Other::bar::two); - } + parser = get_parser("-e", "two"); + parser.add_option(value2, sharg::config{.short_id = 'e'}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_TRUE(value2 == Other::bar::two); } -TEST(parse_type_test, parse_error_enum_option) +TEST_F(enumeration_names_test, parse_error_enum_option) { foo::bar option_value{}; - char const * argv[] = {"./parser_test", "-e", "four"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; + auto parser = get_parser("-e", "four"); parser.add_option(option_value, sharg::config{.short_id = 'e'}); - EXPECT_THROW(parser.parse(), sharg::user_input_error); } // https://github.com/seqan/seqan3/issues/2464 -TEST(parse_test, issue2464) +TEST_F(enumeration_names_test, issue2464) { - using option_t = foo::bar; - // Using a non-existing value of foo::bar should throw. - { - char const * argv[] = {"./parser_test", "-e", "nine"}; - - option_t option_value{}; - - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - parser.add_option(option_value, sharg::config{.short_id = 'e'}); - EXPECT_THROW(parser.parse(), sharg::user_input_error); - } - { - char const * argv[] = {"./parser_test", "-e", "one", "-e", "nine"}; + foo::bar option_value{}; + std::vector option_values{}; - std::vector option_values{}; + // Using a non-existing value of foo::bar should throw. + auto parser = get_parser("-e", "nine"); + parser.add_option(option_value, sharg::config{.short_id = 'e'}); + EXPECT_THROW(parser.parse(), sharg::user_input_error); - sharg::parser parser{"test_parser", 5, argv, sharg::update_notifications::off}; - parser.add_option(option_values, sharg::config{.short_id = 'e'}); - EXPECT_THROW(parser.parse(), sharg::user_input_error); - } + parser = get_parser("-e", "one", "-e", "nine"); + parser.add_option(option_values, sharg::config{.short_id = 'e'}); + EXPECT_THROW(parser.parse(), sharg::user_input_error); // Invalid inputs for enums are handled before any validator is evaluated. // Thus the exception will be sharg::user_input_error and not sharg::validation_error. - { - char const * argv[] = {"./parser_test", "-e", "nine"}; - - sharg::value_list_validator enum_validator{(sharg::enumeration_names | std::views::values)}; - option_t option_value{}; + sharg::value_list_validator enum_validator{(sharg::enumeration_names | std::views::values)}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - parser.add_option(option_value, sharg::config{.short_id = 'e', .advanced = true, .validator = enum_validator}); - EXPECT_THROW(parser.parse(), sharg::user_input_error); - } - { - char const * argv[] = {"./parser_test", "-e", "one", "-e", "nine"}; - - sharg::value_list_validator enum_validator{(sharg::enumeration_names | std::views::values)}; - std::vector option_values{}; + parser = get_parser("-e", "nine"); + parser.add_option(option_value, sharg::config{.short_id = 'e', .advanced = true, .validator = enum_validator}); + EXPECT_THROW(parser.parse(), sharg::user_input_error); - sharg::parser parser{"test_parser", 5, argv, sharg::update_notifications::off}; - parser.add_option(option_values, sharg::config{.short_id = 'e', .advanced = true, .validator = enum_validator}); - EXPECT_THROW(parser.parse(), sharg::user_input_error); - } + parser = get_parser("-e", "one", "-e", "nine"); + parser.add_option(option_values, sharg::config{.short_id = 'e', .advanced = true, .validator = enum_validator}); + EXPECT_THROW(parser.parse(), sharg::user_input_error); } -TEST(parse_test, enum_error_message) +TEST_F(enumeration_names_test, enum_error_message) { - // foo::bar does not contain duplicate values - { - char const * argv[] = {"./parser_test", "-e", "nine"}; - - foo::bar option_value{}; - - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - parser.add_option(option_value, sharg::config{.short_id = 'e'}); + foo::bar value{}; + Other::bar value2{}; - std::string expected_message{"You have chosen an invalid input value: nine. " - "Please use one of: [one, two, three]"}; + auto parser = get_parser(); + auto check_error = [&parser](std::string_view const expected) + { try { parser.parse(); @@ -149,51 +123,32 @@ TEST(parse_test, enum_error_message) } catch (sharg::user_input_error const & exception) { - EXPECT_EQ(expected_message, exception.what()); + EXPECT_EQ(expected, exception.what()); } catch (...) { FAIL(); } - } - // Other::bar does contain duplicate values - { - char const * argv[] = {"./parser_test", "-e", "nine"}; - - Other::bar option_value{}; + }; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - parser.add_option(option_value, sharg::config{.short_id = 'e'}); - - std::string expected_message{"You have chosen an invalid input value: nine. " - "Please use one of: [1, one, 2, two]"}; + // foo::bar does not contain duplicate values + parser = get_parser("-e", "nine"); + parser.add_option(value, sharg::config{.short_id = 'e'}); + check_error("You have chosen an invalid input value: nine. Please use one of: [one, two, three]"); - try - { - parser.parse(); - FAIL(); - } - catch (sharg::user_input_error const & exception) - { - EXPECT_EQ(expected_message, exception.what()); - } - catch (...) - { - FAIL(); - } - } + // Other::bar does contain duplicate values + parser = get_parser("-e", "nine"); + parser.add_option(value2, sharg::config{.short_id = 'e'}); + check_error("You have chosen an invalid input value: nine. Please use one of: [1, one, 2, two]"); } // https://github.com/seqan/seqan3/pull/2381 -TEST(parse_test, container_options) +TEST_F(enumeration_names_test, container_options) { std::vector option_values{}; - char const * argv[] = {"./parser_test", "-e", "two", "-e", "one", "-e", "three"}; - sharg::parser parser{"test_parser", 7, argv, sharg::update_notifications::off}; + auto parser = get_parser("-e", "two", "-e", "one", "-e", "three"); parser.add_option(option_values, sharg::config{.short_id = 'e'}); - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE(option_values == (std::vector{foo::bar::two, foo::bar::one, foo::bar::three})); } diff --git a/test/unit/parser/format_parse_test.cpp b/test/unit/parser/format_parse_test.cpp index 4fe1a659..d0084446 100644 --- a/test/unit/parser/format_parse_test.cpp +++ b/test/unit/parser/format_parse_test.cpp @@ -7,965 +7,734 @@ #include #include +#include -namespace sharg::detail -{ -struct test_accessor -{ - static auto & executable_name(sharg::parser & parser) - { - return parser.executable_name; - } -}; -} // namespace sharg::detail +class format_parse_test : public sharg::test::test_fixture +{}; -TEST(parse_type_test, add_option_short_id) +TEST_F(format_parse_test, add_option_short_id) { std::string option_value; - char const * argv[] = {"./parser_test", "-s", "option_string"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - parser.add_section("My options"); // no-op for code coverage - parser.add_subsection("My suboptions"); // no-op for code coverage - parser.add_line("line"); // no-op for code coverage - parser.add_list_item("list", "item"); // no-op for code coverage + // argument separated by space + auto parser = get_parser("-s", "option_string1"); parser.add_option(option_value, sharg::config{.short_id = 's'}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(option_value, "option_string1"); - testing::internal::CaptureStderr(); + // argument separated by no space + parser = get_parser("-Soption_string2"); + parser.add_option(option_value, sharg::config{.short_id = 'S'}); EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, "option_string"); - - // add with no space - char const * argv2[] = {"./parser_test", "-Soption_string"}; - sharg::parser parser2{"test_parser", 2, argv2, sharg::update_notifications::off}; - parser2.add_option(option_value, sharg::config{.short_id = 'S'}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser2.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, "option_string"); - - // ad with = sign - char const * argv3[] = {"./parser_test", "-s=option_string"}; - sharg::parser parser3{"test_parser", 2, argv3, sharg::update_notifications::off}; - parser3.add_option(option_value, sharg::config{.short_id = 's'}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser3.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, "option_string"); + EXPECT_EQ(option_value, "option_string2"); + + // argument separated by = + parser = get_parser("-s=option_string3"); + parser.add_option(option_value, sharg::config{.short_id = 's'}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(option_value, "option_string3"); } -TEST(parse_type_test, add_option_long_id) +TEST_F(format_parse_test, add_option_long_id) { std::string option_value; - char const * argv[] = {"./parser_test", "--string-option", "option_string"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; + // argument separated by space + auto parser = get_parser("--string-option", "option_string1"); parser.add_option(option_value, sharg::config{.long_id = "string-option"}); - - testing::internal::CaptureStderr(); EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, "option_string"); + EXPECT_EQ(option_value, "option_string1"); - char const * argv3[] = {"./parser_test", "--string-option=option_string"}; - sharg::parser parser3{"test_parser", 2, argv3, sharg::update_notifications::off}; - parser3.add_option(option_value, sharg::config{.long_id = "string-option"}); + // argument separated by no space + parser = get_parser("--string-optionoption_string2"); + parser.add_option(option_value, sharg::config{.long_id = "string-option"}); + EXPECT_THROW(parser.parse(), sharg::parser_error); + EXPECT_EQ(option_value, "option_string1"); - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser3.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, "option_string"); + // argument separated by = + parser = get_parser("--string-option=option_string3"); + parser.add_option(option_value, sharg::config{.long_id = "string-option"}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(option_value, "option_string3"); } -TEST(parse_type_test, add_flag_short_id_single) +TEST_F(format_parse_test, add_flag_short_id_single) { bool option_value1{false}; bool option_value2{false}; - char const * argv[] = {"./parser_test", "-f"}; - sharg::parser parser{"test_parser", 2, argv, sharg::update_notifications::off}; + auto parser = get_parser("-f"); parser.add_flag(option_value1, sharg::config{.short_id = 'f'}); parser.add_flag(option_value2, sharg::config{.short_id = 'a'}); - testing::internal::CaptureStderr(); EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); EXPECT_EQ(option_value1, true); EXPECT_EQ(option_value2, false); } -TEST(parse_type_test, add_flag_short_id_multiple) +TEST_F(format_parse_test, add_flag_short_id_multiple) { bool option_value1{false}; bool option_value2{false}; bool option_value3{false}; bool option_value4{false}; - char const * argv[] = {"./parser_test", "-fbc"}; - sharg::parser parser{"test_parser", 2, argv, sharg::update_notifications::off}; + auto parser = get_parser("-fbc"); parser.add_flag(option_value1, sharg::config{.short_id = 'f'}); parser.add_flag(option_value2, sharg::config{.short_id = 'a'}); parser.add_flag(option_value3, sharg::config{.short_id = 'b'}); parser.add_flag(option_value4, sharg::config{.short_id = 'c'}); - testing::internal::CaptureStderr(); EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); EXPECT_EQ(option_value1, true); EXPECT_EQ(option_value2, false); EXPECT_EQ(option_value3, true); EXPECT_EQ(option_value4, true); } -TEST(parse_type_test, add_flag_long_id) +TEST_F(format_parse_test, add_flag_long_id) { bool option_value1{false}; bool option_value2{false}; - char const * argv[] = {"./parser_test", "--another-flag"}; - sharg::parser parser{"test_parser", 2, argv, sharg::update_notifications::off}; + auto parser = get_parser("--another-flag"); parser.add_flag(option_value1, sharg::config{.long_id = "flag"}); parser.add_flag(option_value2, sharg::config{.long_id = "another-flag"}); - testing::internal::CaptureStderr(); EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); EXPECT_EQ(option_value1, false); EXPECT_EQ(option_value2, true); } -TEST(parse_type_test, add_positional_option) +TEST_F(format_parse_test, add_positional_option) { std::string positional_value; - char const * argv[] = {"./parser_test", "positional_string"}; - sharg::parser parser{"test_parser", 2, argv, sharg::update_notifications::off}; + auto parser = get_parser("positional_string"); parser.add_positional_option(positional_value, sharg::config{}); - - testing::internal::CaptureStderr(); EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); EXPECT_EQ(positional_value, "positional_string"); } -TEST(parse_type_test, independent_add_order) +TEST_F(format_parse_test, independent_add_order) { // testing same command line input different add_* order - - std::string positional_value; + std::string positional_value{}; bool flag_value{false}; - int option_value; + int option_value{}; + auto parser = get_parser("-i", "2", "-b", "arg"); - // Order 1: option, flag, positional - char const * argv[] = {"./parser_test", "-i", "2", "-b", "arg"}; - sharg::parser parser{"test_parser", 5, argv, sharg::update_notifications::off}; - parser.add_option(option_value, sharg::config{.short_id = 'i'}); - parser.add_flag(flag_value, sharg::config{.short_id = 'b'}); - parser.add_positional_option(positional_value, sharg::config{}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(positional_value, "arg"); - EXPECT_EQ(option_value, 2); - EXPECT_EQ(flag_value, true); - - flag_value = false; // reinstate to default value - - // Order 1: flag, option, positional - sharg::parser parser2{"test_parser", 5, argv, sharg::update_notifications::off}; - parser2.add_flag(flag_value, sharg::config{.short_id = 'b'}); - parser2.add_option(option_value, sharg::config{.short_id = 'i'}); - parser2.add_positional_option(positional_value, sharg::config{}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser2.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(positional_value, "arg"); - EXPECT_EQ(option_value, 2); - EXPECT_EQ(flag_value, true); - - flag_value = false; + auto check_values_and_reset = [&]() + { + EXPECT_EQ(positional_value, "arg"); + EXPECT_EQ(option_value, 2); + EXPECT_EQ(flag_value, true); - // Order 1: option, positional, flag - sharg::parser parser3{"test_parser", 5, argv, sharg::update_notifications::off}; - parser3.add_option(option_value, sharg::config{.short_id = 'i'}); - parser3.add_positional_option(positional_value, sharg::config{}); - parser3.add_flag(flag_value, sharg::config{.short_id = 'b'}); + positional_value.clear(); + flag_value = false; + option_value = 0; + parser = get_parser("-i", "2", "-b", "arg"); + }; - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser3.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(positional_value, "arg"); - EXPECT_EQ(option_value, 2); - EXPECT_EQ(flag_value, true); - - flag_value = false; + auto add_option = [&]() + { + parser.add_option(option_value, sharg::config{.short_id = 'i'}); + }; - // Order 1: flag, positional, option - sharg::parser parser4{"test_parser", 5, argv, sharg::update_notifications::off}; - parser4.add_flag(flag_value, sharg::config{.short_id = 'b'}); - parser4.add_positional_option(positional_value, sharg::config{}); - parser4.add_option(option_value, sharg::config{.short_id = 'i'}); + auto add_flag = [&]() + { + parser.add_flag(flag_value, sharg::config{.short_id = 'b'}); + }; - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser4.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(positional_value, "arg"); - EXPECT_EQ(option_value, 2); - EXPECT_EQ(flag_value, true); + auto add_positional_option = [&]() + { + parser.add_positional_option(positional_value, sharg::config{}); + }; - flag_value = false; + // Order 1: option, flag, positional + add_option(); + add_flag(); + add_positional_option(); + EXPECT_NO_THROW(parser.parse()); + check_values_and_reset(); - // Order 1: positional, flag, option - sharg::parser parser5{"test_parser", 5, argv, sharg::update_notifications::off}; - parser5.add_positional_option(positional_value, sharg::config{}); - parser5.add_flag(flag_value, sharg::config{.short_id = 'b'}); - parser5.add_option(option_value, sharg::config{.short_id = 'i'}); + // Order 2: flag, option, positional + add_flag(); + add_option(); + add_positional_option(); + EXPECT_NO_THROW(parser.parse()); + check_values_and_reset(); - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser5.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(positional_value, "arg"); - EXPECT_EQ(option_value, 2); - EXPECT_EQ(flag_value, true); + // Order 3: option, positional, flag + add_option(); + add_positional_option(); + add_flag(); + EXPECT_NO_THROW(parser.parse()); + check_values_and_reset(); - flag_value = false; + // Order 4: flag, positional, option + add_flag(); + add_positional_option(); + add_option(); + EXPECT_NO_THROW(parser.parse()); + check_values_and_reset(); - // Order 1: positional, option, flag - sharg::parser parser6{"test_parser", 5, argv, sharg::update_notifications::off}; - parser6.add_positional_option(positional_value, sharg::config{}); - parser6.add_option(option_value, sharg::config{.short_id = 'i'}); - parser6.add_flag(flag_value, sharg::config{.short_id = 'b'}); + // Order 5: positional, flag, option + add_positional_option(); + add_flag(); + add_option(); + EXPECT_NO_THROW(parser.parse()); + check_values_and_reset(); - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser6.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(positional_value, "arg"); - EXPECT_EQ(option_value, 2); - EXPECT_EQ(flag_value, true); + // Order 6: positional, option, flag + add_positional_option(); + add_option(); + add_flag(); + EXPECT_NO_THROW(parser.parse()); + check_values_and_reset(); } -TEST(parse_type_test, independent_cmd_order) +TEST_F(format_parse_test, independent_cmd_order) { // testing different command line order - - std::string positional_value; + std::string positional_value{}; bool flag_value{false}; - int option_value; - - // Order 1: option, flag, positional (POSIX conform) - char const * argv[] = {"./parser_test", "-i", "2", "-b", "arg"}; - sharg::parser parser{"test_parser", 5, argv, sharg::update_notifications::off}; - parser.add_option(option_value, sharg::config{.short_id = 'i'}); - parser.add_flag(flag_value, sharg::config{.short_id = 'b'}); - parser.add_positional_option(positional_value, sharg::config{}); + int option_value{}; - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(positional_value, "arg"); - EXPECT_EQ(option_value, 2); - EXPECT_EQ(flag_value, true); - - flag_value = false; // reinstate to default value - - // Order 1: flag, option, positional (POSIX conform) - char const * argv2[] = {"./parser_test", "-b", "-i", "2", "arg"}; - sharg::parser parser2{"test_parser", 5, argv2, sharg::update_notifications::off}; - parser2.add_option(option_value, sharg::config{.short_id = 'i'}); - parser2.add_flag(flag_value, sharg::config{.short_id = 'b'}); - parser2.add_positional_option(positional_value, sharg::config{}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser2.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(positional_value, "arg"); - EXPECT_EQ(option_value, 2); - EXPECT_EQ(flag_value, true); - - flag_value = false; - - // Order 1: option, positional, flag - char const * argv3[] = {"./parser_test", "-i", "2", "arg", "-b"}; - sharg::parser parser3{"test_parser", 5, argv3, sharg::update_notifications::off}; - parser3.add_option(option_value, sharg::config{.short_id = 'i'}); - parser3.add_flag(flag_value, sharg::config{.short_id = 'b'}); - parser3.add_positional_option(positional_value, sharg::config{}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser3.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(positional_value, "arg"); - EXPECT_EQ(option_value, 2); - EXPECT_EQ(flag_value, true); + auto check_values_and_reset = [&]() + { + EXPECT_EQ(positional_value, "arg"); + EXPECT_EQ(option_value, 2); + EXPECT_EQ(flag_value, true); - flag_value = false; + positional_value.clear(); + flag_value = false; + option_value = 0; + }; - // Order 1: flag, positional, option - char const * argv4[] = {"./parser_test", "-b", "arg", "-i", "2"}; - sharg::parser parser4{"test_parser", 5, argv4, sharg::update_notifications::off}; - parser4.add_option(option_value, sharg::config{.short_id = 'i'}); - parser4.add_flag(flag_value, sharg::config{.short_id = 'b'}); - parser4.add_positional_option(positional_value, sharg::config{}); + auto parser = get_parser(); - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser4.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(positional_value, "arg"); - EXPECT_EQ(option_value, 2); - EXPECT_EQ(flag_value, true); + auto setup_parser = [&]() + { + parser.add_option(option_value, sharg::config{.short_id = 'i'}); + parser.add_flag(flag_value, sharg::config{.short_id = 'b'}); + parser.add_positional_option(positional_value, sharg::config{}); + }; - flag_value = false; + // Order 1: option, flag, positional (POSIX conform) + parser = get_parser("-i", "2", "-b", "arg"); + setup_parser(); + EXPECT_NO_THROW(parser.parse()); + check_values_and_reset(); - // Order 1: positional, flag, option - char const * argv5[] = {"./parser_test", "arg", "-b", "-i", "2"}; - sharg::parser parser5{"test_parser", 5, argv5, sharg::update_notifications::off}; - parser5.add_option(option_value, sharg::config{.short_id = 'i'}); - parser5.add_flag(flag_value, sharg::config{.short_id = 'b'}); - parser5.add_positional_option(positional_value, sharg::config{}); + // Order 2: flag, option, positional (POSIX conform) + parser = get_parser("-b", "-i", "2", "arg"); + setup_parser(); + EXPECT_NO_THROW(parser.parse()); + check_values_and_reset(); - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser5.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(positional_value, "arg"); - EXPECT_EQ(option_value, 2); - EXPECT_EQ(flag_value, true); + // Order 3: option, positional, flag + parser = get_parser("-i", "2", "arg", "-b"); + setup_parser(); + EXPECT_NO_THROW(parser.parse()); + check_values_and_reset(); - flag_value = false; + // Order 4: flag, positional, option + parser = get_parser("-b", "arg", "-i", "2"); + setup_parser(); + EXPECT_NO_THROW(parser.parse()); + check_values_and_reset(); - // Order 1: positional, option, flag - char const * argv6[] = {"./parser_test", "arg", "-i", "2", "-b"}; - sharg::parser parser6{"test_parser", 5, argv6, sharg::update_notifications::off}; - parser6.add_option(option_value, sharg::config{.short_id = 'i'}); - parser6.add_flag(flag_value, sharg::config{.short_id = 'b'}); - parser6.add_positional_option(positional_value, sharg::config{}); + // Order 5: positional, flag, option + parser = get_parser("arg", "-b", "-i", "2"); + setup_parser(); + EXPECT_NO_THROW(parser.parse()); + check_values_and_reset(); - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser6.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(positional_value, "arg"); - EXPECT_EQ(option_value, 2); - EXPECT_EQ(flag_value, true); + // Order 6: positional, option, flag + parser = get_parser("arg", "-i", "2", "-b"); + setup_parser(); + EXPECT_NO_THROW(parser.parse()); + check_values_and_reset(); } -TEST(parse_test, double_dash_separation_success) +TEST_F(format_parse_test, double_dash_separation_success) { - std::string option_value; - // string option with dash - char const * argv[] = {"./parser_test", "--", "-strange"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; + std::string option_value{}; + auto parser = get_parser("--", "-strange"); parser.add_positional_option(option_value, sharg::config{}); - - testing::internal::CaptureStderr(); EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); EXPECT_EQ(option_value, "-strange"); // negative integer option - int option_value_int; - char const * argv2[] = {"./parser_test", "--", "-120"}; - sharg::parser parser2{"test_parser", 3, argv2, sharg::update_notifications::off}; - parser2.add_positional_option(option_value_int, sharg::config{}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser2.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); + int option_value_int{}; + parser = get_parser("--", "-120"); + parser.add_positional_option(option_value_int, sharg::config{}); + EXPECT_NO_THROW(parser.parse()); EXPECT_EQ(option_value_int, -120); } -TEST(parse_test, special_characters_as_value_success) +TEST_F(format_parse_test, special_characters_as_value_success) { - std::string option_value; + std::string option_value{}; - // weird option value. But since r/regex option is parsed and with it's + // weird option value. But since r/regex option is parsed and with its // value should work correct - char const * argv[] = {"./parser_test", "--regex", "-i=/45*&//--"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; + auto parser = get_parser("--regex", "-i=/45*&//--"); parser.add_option(option_value, sharg::config{.long_id = "regex"}); - - testing::internal::CaptureStderr(); EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); EXPECT_EQ(option_value, "-i=/45*&//--"); } -TEST(parse_test, empty_value_error) +TEST_F(format_parse_test, empty_value_error) { - int option_value; + int option_value{}; // short option - char const * argv[] = {"./parser_test", "-i"}; - sharg::parser parser{"test_parser", 2, argv, sharg::update_notifications::off}; + auto parser = get_parser("-i"); parser.add_option(option_value, sharg::config{.short_id = 'i'}); - EXPECT_THROW(parser.parse(), sharg::parser_error); // long option - char const * argv2[] = {"./parser_test", "--long"}; - sharg::parser parser2{"test_parser", 2, argv2, sharg::update_notifications::off}; - parser2.add_option(option_value, sharg::config{.long_id = "long"}); - - EXPECT_THROW(parser2.parse(), sharg::parser_error); - - // short option - char const * argv3[] = {"./parser_test", "-i="}; - sharg::parser parser3{"test_parser", 2, argv3, sharg::update_notifications::off}; - parser3.add_option(option_value, sharg::config{.short_id = 'i'}); - - EXPECT_THROW(parser3.parse(), sharg::parser_error); + parser = get_parser("--long"); + parser.add_option(option_value, sharg::config{.long_id = "long"}); + EXPECT_THROW(parser.parse(), sharg::parser_error); // short option - char const * argv4[] = {"./parser_test", "--long="}; - sharg::parser parser4{"test_parser", 2, argv4, sharg::update_notifications::off}; - parser4.add_option(option_value, sharg::config{.long_id = "long"}); + parser = get_parser("-i="); + parser.add_option(option_value, sharg::config{.short_id = 'i'}); + EXPECT_THROW(parser.parse(), sharg::parser_error); - EXPECT_THROW(parser4.parse(), sharg::parser_error); + // long option + parser = get_parser("--long="); + parser.add_option(option_value, sharg::config{.long_id = "long"}); + EXPECT_THROW(parser.parse(), sharg::parser_error); } -TEST(parse_type_test, parse_success_bool_option) +TEST_F(format_parse_test, parse_success_bool_option) { - bool option_value; - bool positional_value; + bool option_value{false}; + bool positional_value{false}; - // numbers 0 and 1 + auto check_values_and_reset = [&]() { - char const * argv[] = {"./parser_test", "-b", "1", "0"}; - sharg::parser parser{"test_parser", 4, argv, sharg::update_notifications::off}; - parser.add_option(option_value, sharg::config{.short_id = 'b'}); - parser.add_positional_option(positional_value, sharg::config{}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser.parse()); - - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); EXPECT_EQ(option_value, true); EXPECT_EQ(positional_value, false); - } - // true and false - { - char const * argv[] = {"./parser_test", "-b", "true", "false"}; - sharg::parser parser{"test_parser", 4, argv, sharg::update_notifications::off}; - parser.add_option(option_value, sharg::config{.short_id = 'b'}); - parser.add_positional_option(positional_value, sharg::config{}); + option_value = false; + positional_value = false; + }; - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser.parse()); + // numbers 0 and 1 + auto parser = get_parser("-b", "1", "0"); + parser.add_option(option_value, sharg::config{.short_id = 'b'}); + parser.add_positional_option(positional_value, sharg::config{}); + EXPECT_NO_THROW(parser.parse()); + check_values_and_reset(); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, true); - EXPECT_EQ(positional_value, false); - } + // true and false + parser = get_parser("-b", "true", "false"); + parser.add_option(option_value, sharg::config{.short_id = 'b'}); + parser.add_positional_option(positional_value, sharg::config{}); + EXPECT_NO_THROW(parser.parse()); + check_values_and_reset(); } -TEST(parse_type_test, parse_success_int_option) +TEST_F(format_parse_test, parse_success_int_option) { - int option_value; - size_t positional_value; + int option_value{}; + size_t positional_value{}; - char const * argv[] = {"./parser_test", "-i", "-2", "278"}; - sharg::parser parser{"test_parser", 4, argv, sharg::update_notifications::off}; + auto parser = get_parser("-i", "-2", "278"); parser.add_option(option_value, sharg::config{.short_id = 'i'}); parser.add_positional_option(positional_value, sharg::config{}); - - testing::internal::CaptureStderr(); EXPECT_NO_THROW(parser.parse()); - - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); EXPECT_EQ(option_value, -2); EXPECT_EQ(positional_value, 278u); } -TEST(parse_type_test, parse_success_double_option) +TEST_F(format_parse_test, parse_success_double_option) { - double option_value; - double positional_value; + double option_value{}; + double positional_value{}; - char const * argv[] = {"./parser_test", "-d", "12.457", "0.123"}; - sharg::parser parser{"test_parser", 4, argv, sharg::update_notifications::off}; + // double expression with e + auto parser = get_parser("-d", "6.0221418e23"); parser.add_option(option_value, sharg::config{.short_id = 'd'}); - parser.add_positional_option(positional_value, sharg::config{}); - - testing::internal::CaptureStderr(); EXPECT_NO_THROW(parser.parse()); + EXPECT_FLOAT_EQ(option_value, 6.0221418e23); + EXPECT_FLOAT_EQ(positional_value, 0.0); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); + // double expression with dot + parser = get_parser("-d", "12.457", "0.123"); + parser.add_option(option_value, sharg::config{.short_id = 'd'}); + parser.add_positional_option(positional_value, sharg::config{}); + EXPECT_NO_THROW(parser.parse()); EXPECT_FLOAT_EQ(option_value, 12.457); EXPECT_FLOAT_EQ(positional_value, 0.123); - - // double expression with e - char const * argv2[] = {"./parser_test", "-d", "6.0221418e23"}; - sharg::parser parser2{"test_parser", 3, argv2, sharg::update_notifications::off}; - parser2.add_option(option_value, sharg::config{.short_id = 'd'}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser2.parse()); - - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_FLOAT_EQ(option_value, 6.0221418e23); - EXPECT_FLOAT_EQ(positional_value, 0.123); } -TEST(parse_type_test, parse_error_bool_option) +TEST_F(format_parse_test, parse_error_bool_option) { - bool option_value; + bool option_value{false}; // fail on character input - char const * argv[] = {"./parser_test", "-b", "a"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; + auto parser = get_parser("-b", "a"); parser.add_option(option_value, sharg::config{.short_id = 'b'}); - EXPECT_THROW(parser.parse(), sharg::parser_error); + EXPECT_EQ(option_value, false); - // fail on number input expect 0 and 1 - char const * argv2[] = {"./parser_test", "-b", "124"}; - sharg::parser parser2{"test_parser", 3, argv2, sharg::update_notifications::off}; - parser2.add_option(option_value, sharg::config{.short_id = 'b'}); - - EXPECT_THROW(parser2.parse(), sharg::parser_error); + // fail on number input except 0 and 1 + parser = get_parser("-b", "124"); + parser.add_option(option_value, sharg::config{.short_id = 'b'}); + EXPECT_THROW(parser.parse(), sharg::parser_error); + EXPECT_EQ(option_value, false); } -TEST(parse_type_test, parse_error_int_option) +TEST_F(format_parse_test, parse_error_int_option) { - int option_value; + int signed_int{}; + unsigned unsigned_int{}; + int8_t signed_int8{}; + uint8_t unsigned_uint8{}; - // fail on character - char const * argv[] = {"./parser_test", "-i", "abc"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - parser.add_option(option_value, sharg::config{.short_id = 'i'}); + auto parser = get_parser(); - EXPECT_THROW(parser.parse(), sharg::parser_error); + auto check_for_failure = [&](t & option_value, std::string_view value, t expected = 0) + { + parser = get_parser("-i", value.data()); + parser.add_option(option_value, sharg::config{.short_id = 'i'}); + EXPECT_THROW(parser.parse(), sharg::parser_error); + EXPECT_EQ(option_value, expected); + }; - // fail on number followed by character - char const * argv2[] = {"./parser_test", "-i", "2abc"}; - sharg::parser parser2{"test_parser", 3, argv2, sharg::update_notifications::off}; - parser2.add_option(option_value, sharg::config{.short_id = 'i'}); + // fail on character + check_for_failure(signed_int, "abc"); - EXPECT_THROW(parser2.parse(), sharg::parser_error); + // fail on number followed by character + check_for_failure(signed_int, "2abc", 2); // Todo: Expected? // fail on double - char const * argv3[] = {"./parser_test", "-i", "3.12"}; - sharg::parser parser3{"test_parser", 3, argv3, sharg::update_notifications::off}; - parser3.add_option(option_value, sharg::config{.short_id = 'i'}); - - EXPECT_THROW(parser3.parse(), sharg::parser_error); + check_for_failure(signed_int, "3.12", 3); // Todo: Expected? // fail on negative number for unsigned - unsigned option_value_u; - char const * argv4[] = {"./parser_test", "-i", "-1"}; - sharg::parser parser4{"test_parser", 3, argv4, sharg::update_notifications::off}; - parser4.add_option(option_value_u, sharg::config{.short_id = 'i'}); - - EXPECT_THROW(parser4.parse(), sharg::parser_error); + check_for_failure(unsigned_int, "-1"); // fail on overflow - int8_t option_value_int8; - char const * argv5[] = {"./parser_test", "-i", "129"}; - sharg::parser parser5{"test_parser", 3, argv5, sharg::update_notifications::off}; - parser5.add_option(option_value_int8, sharg::config{.short_id = 'i'}); - - EXPECT_THROW(parser5.parse(), sharg::parser_error); - - uint8_t option_value_uint8; - char const * argv6[] = {"./parser_test", "-i", "267"}; - sharg::parser parser6{"test_parser", 3, argv6, sharg::update_notifications::off}; - parser6.add_option(option_value_uint8, sharg::config{.short_id = 'i'}); - - EXPECT_THROW(parser6.parse(), sharg::parser_error); + check_for_failure(signed_int8, "129"); + check_for_failure(unsigned_uint8, "267"); } -TEST(parse_type_test, parse_error_double_option) +TEST_F(format_parse_test, parse_error_double_option) { - double option_value; + double option_value{}; // fail on character - char const * argv[] = {"./parser_test", "-i", "abc"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; + auto parser = get_parser("-i", "abc"); parser.add_option(option_value, sharg::config{.short_id = 'd'}); - EXPECT_THROW(parser.parse(), sharg::parser_error); + EXPECT_FLOAT_EQ(option_value, 0.0); // fail on number followed by character - char const * argv2[] = {"./parser_test", "-d", "12.457a"}; - sharg::parser parser2{"test_parser", 3, argv2, sharg::update_notifications::off}; - parser2.add_option(option_value, sharg::config{.short_id = 'd'}); - - EXPECT_THROW(parser2.parse(), sharg::parser_error); + parser = get_parser("-d", "12.457a"); + parser.add_option(option_value, sharg::config{.short_id = 'd'}); + EXPECT_THROW(parser.parse(), sharg::parser_error); + EXPECT_FLOAT_EQ(option_value, 12.457); // Todo: Expected? } -TEST(parse_test, too_many_arguments_error) +TEST_F(format_parse_test, too_many_arguments_error) { - int option_value; + int option_value{}; - char const * argv[] = {"./parser_test", "5", "15"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; + auto parser = get_parser("5", "15"); parser.add_positional_option(option_value, sharg::config{}); - EXPECT_THROW(parser.parse(), sharg::too_many_arguments); + EXPECT_EQ(option_value, 5); // Todo: Expected? // since -- indicates -i as a positional argument, this causes a too many args error - char const * argv2[] = {"./parser_test", "2", "--", "-i"}; - sharg::parser parser2{"test_parser", 4, argv2, sharg::update_notifications::off}; - parser2.add_positional_option(option_value, sharg::config{}); - parser2.add_option(option_value, sharg::config{.short_id = 'i'}); - - EXPECT_THROW(parser2.parse(), sharg::too_many_arguments); + parser = get_parser("2", "--", "-i"); + parser.add_positional_option(option_value, sharg::config{}); + parser.add_option(option_value, sharg::config{.short_id = 'i'}); + EXPECT_THROW(parser.parse(), sharg::too_many_arguments); + EXPECT_EQ(option_value, 2); // Todo: Expected? } -TEST(parse_test, too_few_arguments_error) +TEST_F(format_parse_test, too_few_arguments_error) { - int option_value; + int option_value{}; - char const * argv[] = {"./parser_test", "15"}; - sharg::parser parser{"test_parser", 2, argv, sharg::update_notifications::off}; + auto parser = get_parser("15"); parser.add_positional_option(option_value, sharg::config{}); parser.add_positional_option(option_value, sharg::config{}); - EXPECT_THROW(parser.parse(), sharg::too_few_arguments); + EXPECT_EQ(option_value, 15); // Todo: Expected? // since -- indicates -i as a positional argument, this causes a too many args error - char const * argv2[] = {"./parser_test", "-i", "2"}; - sharg::parser parser2{"test_parser", 3, argv2, sharg::update_notifications::off}; - parser2.add_positional_option(option_value, sharg::config{}); - parser2.add_option(option_value, sharg::config{.short_id = 'i'}); - - EXPECT_THROW(parser2.parse(), sharg::too_few_arguments); + parser = get_parser("-i", "2"); + parser.add_positional_option(option_value, sharg::config{}); + parser.add_option(option_value, sharg::config{.short_id = 'i'}); + EXPECT_THROW(parser.parse(), sharg::too_few_arguments); + EXPECT_EQ(option_value, 2); // Todo: Expected? } -TEST(parse_test, unknown_option_error) +TEST_F(format_parse_test, unknown_option_error) { - // unknown short option - char const * argv[] = {"./parser_test", "-i", "15"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; + auto parser = get_parser(); - EXPECT_THROW(parser.parse(), sharg::unknown_option); + auto check_for_failure = [&](auto... arguments) + { + parser = get_parser(arguments...); + EXPECT_THROW(parser.parse(), sharg::unknown_option); + }; - // unknown long option - char const * argv2[] = {"./parser_test", "--arg", "8"}; - sharg::parser parser2{"test_parser", 3, argv2, sharg::update_notifications::off}; + // unknown short option + check_for_failure("-i", "15"); - EXPECT_THROW(parser2.parse(), sharg::unknown_option); + // unknown long option + check_for_failure("--arg", "8"); // unknown short flag - char const * argv3[] = {"./parser_test", "-a"}; - sharg::parser parser3{"test_parser", 2, argv3, sharg::update_notifications::off}; - - EXPECT_THROW(parser3.parse(), sharg::unknown_option); + check_for_failure("-a"); // unknown long flag - char const * argv4[] = {"./parser_test", "--arg"}; - sharg::parser parser4{"test_parser", 2, argv4, sharg::update_notifications::off}; - - EXPECT_THROW(parser4.parse(), sharg::unknown_option); + check_for_failure("--arg"); // negative numbers are seen as options - char const * argv5[] = {"./parser_test", "-5"}; - sharg::parser parser5{"test_parser", 2, argv5, sharg::update_notifications::off}; - - EXPECT_THROW(parser5.parse(), sharg::unknown_option); - - // unknown short option in more complex command line - int option_value_i; - std::string option_value_a; - std::string positional_option; - char const * argv6[] = {"./parser_test", "-i", "129", "arg1", "-b", "bcd", "-a", "abc"}; - sharg::parser parser6{"test_parser", 8, argv6, sharg::update_notifications::off}; - parser6.add_option(option_value_i, sharg::config{.short_id = 'i'}); - parser6.add_option(option_value_a, sharg::config{.short_id = 'a'}); - parser6.add_positional_option(positional_option, sharg::config{}); - - EXPECT_THROW(parser6.parse(), sharg::unknown_option); + check_for_failure("-5"); + + // unknown short option in a more complex command line + int option_value_i{}; + std::string option_value_a{}; + std::string positional_option{}; + parser = get_parser("-i", "129", "arg1", "-b", "bcd", "-a", "abc"); + parser.add_option(option_value_i, sharg::config{.short_id = 'i'}); + parser.add_option(option_value_a, sharg::config{.short_id = 'a'}); + parser.add_positional_option(positional_option, sharg::config{}); + EXPECT_THROW(parser.parse(), sharg::unknown_option); + EXPECT_EQ(option_value_i, 129); // Todo: Expected? + EXPECT_EQ(option_value_a, "abc"); // Todo: Expected? + EXPECT_EQ(positional_option, ""); } -TEST(parse_test, option_declared_multiple_times_error) +TEST_F(format_parse_test, option_declared_multiple_times_error) { - int option_value; + int option_value{}; // short option - char const * argv[] = {"./parser_test", "-i", "15", "-i", "3"}; - sharg::parser parser{"test_parser", 5, argv, sharg::update_notifications::off}; + auto parser = get_parser("-i", "15", "-i", "3"); parser.add_option(option_value, sharg::config{.short_id = 'i'}); - EXPECT_THROW(parser.parse(), sharg::option_declared_multiple_times); + EXPECT_EQ(option_value, 15); // Todo: Expected? // since -- indicates -i as a positional argument, this causes a too many args error - char const * argv2[] = {"./parser_test", "--long", "5", "--long", "6"}; - sharg::parser parser2{"test_parser", 5, argv2, sharg::update_notifications::off}; - parser2.add_option(option_value, sharg::config{.long_id = "long"}); - - EXPECT_THROW(parser2.parse(), sharg::option_declared_multiple_times); + parser = get_parser("--long", "5", "--long", "6"); + parser.add_option(option_value, sharg::config{.long_id = "long"}); + EXPECT_THROW(parser.parse(), sharg::option_declared_multiple_times); + EXPECT_EQ(option_value, 5); // Todo: Expected? // since -- indicates -i as a positional argument, this causes a too many args error - char const * argv3[] = {"./parser_test", "-i", "5", "--long", "6"}; - sharg::parser parser3{"test_parser", 5, argv3, sharg::update_notifications::off}; - parser3.add_option(option_value, sharg::config{.short_id = 'i', .long_id = "long"}); - - EXPECT_THROW(parser3.parse(), sharg::option_declared_multiple_times); + parser = get_parser("-i", "5", "--long", "6"); + parser.add_option(option_value, sharg::config{.short_id = 'i', .long_id = "long"}); + EXPECT_THROW(parser.parse(), sharg::option_declared_multiple_times); + EXPECT_EQ(option_value, 6); // Todo: Expected? } -TEST(parse_test, required_option_missing) +TEST_F(format_parse_test, required_option_missing) { - int option_value; + int option_value{}; // option is required - char const * argv[] = {"./parser_test", "5", "-i", "15"}; - sharg::parser parser{"test_parser", 4, argv, sharg::update_notifications::off}; + auto parser = get_parser("5", "-i", "15"); parser.add_option(option_value, sharg::config{.short_id = 'i'}); parser.add_option(option_value, sharg::config{.short_id = 'a', .required = true}); parser.add_positional_option(option_value, sharg::config{}); - EXPECT_THROW(parser.parse(), sharg::required_option_missing); + EXPECT_EQ(option_value, 15); // Todo: Expected? } -TEST(parse_test, argv_const_combinations) +TEST_F(format_parse_test, argv_const_combinations) { bool flag_value{false}; + auto parser = get_parser(); + + auto check_and_reset = [&]() + { + parser.add_flag(flag_value, sharg::config{.short_id = 'f'}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(flag_value, true); + flag_value = false; + }; + char arg1[]{"./parser"}; char arg2[]{"-f"}; char * argv[] = {arg1, arg2}; - // all const* - char const * const * const argv_all_const{argv}; - sharg::parser parser{"test_parser", 2, argv_all_const, sharg::update_notifications::off}; - parser.add_flag(flag_value, sharg::config{.short_id = 'f'}); - - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE(flag_value); - // none const - flag_value = false; parser = sharg::parser{"test_parser", 2, argv, sharg::update_notifications::off}; - parser.add_flag(flag_value, sharg::config{.short_id = 'f'}); + check_and_reset(); - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE(flag_value); + // all const* + char const * const * const argv_all_const{argv}; + parser = sharg::parser{"test_parser", 2, argv_all_const, sharg::update_notifications::off}; + check_and_reset(); // const 1 - flag_value = false; char const * argv_const1[] = {"./parser_test", "-f"}; parser = sharg::parser{"test_parser", 2, argv_const1, sharg::update_notifications::off}; - parser.add_flag(flag_value, sharg::config{.short_id = 'f'}); - - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE(flag_value); + check_and_reset(); // const 2 - flag_value = false; char * const argv_const2[] = {arg1, arg2}; parser = sharg::parser{"test_parser", 2, argv_const2, sharg::update_notifications::off}; - parser.add_flag(flag_value, sharg::config{.short_id = 'f'}); - - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE(flag_value); + check_and_reset(); // const 12 - flag_value = false; char const * const argv_const12[] = {arg1, arg2}; parser = sharg::parser{"test_parser", 2, argv_const12, sharg::update_notifications::off}; - parser.add_flag(flag_value, sharg::config{.short_id = 'f'}); - - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE(flag_value); + check_and_reset(); } -TEST(parse_test, multiple_empty_options) +TEST_F(format_parse_test, multiple_empty_options) { - int option_value; - - { - char const * argv[]{"./empty_long", "-s=1"}; - sharg::parser parser{"empty_long", 2, argv, sharg::update_notifications::off}; - parser.add_option(option_value, sharg::config{.short_id = 'i'}); - parser.add_option(option_value, sharg::config{.short_id = 's'}); + int option_value{}; - EXPECT_NO_THROW(parser.parse()); - EXPECT_EQ(1, option_value); - } - - { - char const * argv[]{"./empty_long", "-s=1", "--unknown"}; - sharg::parser parser{"empty_long", 3, argv, sharg::update_notifications::off}; - parser.add_option(option_value, sharg::config{.short_id = 'i'}); - parser.add_option(option_value, sharg::config{.short_id = 's'}); - - EXPECT_THROW(parser.parse(), sharg::unknown_option); - } + auto parser = get_parser("-s=1"); + parser.add_option(option_value, sharg::config{.short_id = 'i'}); + parser.add_option(option_value, sharg::config{.short_id = 's'}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(option_value, 1); + option_value = 0; - { - char const * argv[]{"./empty_short", "--long=2"}; - sharg::parser parser{"empty_short", 2, argv, sharg::update_notifications::off}; - parser.add_option(option_value, sharg::config{.long_id = "longi"}); - parser.add_option(option_value, sharg::config{.long_id = "long"}); + parser = get_parser("-s=1", "--unknown"); + parser.add_option(option_value, sharg::config{.short_id = 'i'}); + parser.add_option(option_value, sharg::config{.short_id = 's'}); + EXPECT_THROW(parser.parse(), sharg::unknown_option); + EXPECT_EQ(option_value, 1); // Todo: Expected? - EXPECT_NO_THROW(parser.parse()); - EXPECT_EQ(2, option_value); - } + parser = get_parser("--long=2"); + parser.add_option(option_value, sharg::config{.long_id = "longi"}); + parser.add_option(option_value, sharg::config{.long_id = "long"}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(option_value, 2); } -TEST(parse_test, version_check_option_error) +TEST_F(format_parse_test, version_check_option_error) { - { // version-check must be followed by a value - char const * argv[] = {"./parser_test", "--version-check"}; - EXPECT_THROW((sharg::parser{"test_parser", 2, argv}), sharg::parser_error); - } + // version-check must be followed by a value + std::vector arguments{"./parser_test", "--version-check"}; + EXPECT_THROW((sharg::parser{"test_parser", arguments, sharg::update_notifications::off}), sharg::parser_error); - { // version-check value must be 0 or 1 - char const * argv[] = {"./parser_test", "--version-check", "foo"}; - EXPECT_THROW((sharg::parser{"test_parser", 3, argv}), sharg::parser_error); - } + // version-check value must be 0 or 1 + arguments.push_back("foo"); + EXPECT_THROW((sharg::parser{"test_parser", arguments, sharg::update_notifications::off}), sharg::parser_error); } -TEST(parse_test, subcommand_parser_success) +TEST_F(format_parse_test, subcommand_parser_success) { bool flag_value{false}; std::string option_value{}; - // parsing + auto check_and_reset_options = [&](bool flag, std::string option) { - char const * argv[]{"./top_level", "-f", "sub1", "foo"}; - sharg::parser top_level_parser{"top_level", 4, argv, sharg::update_notifications::off, {"sub1", "sub2"}}; - top_level_parser.add_flag(flag_value, sharg::config{.short_id = 'f'}); - - EXPECT_NO_THROW(top_level_parser.parse()); - EXPECT_EQ(true, flag_value); - - sharg::parser & sub_parser = top_level_parser.get_sub_parser(); - - EXPECT_EQ(sub_parser.info.app_name, "top_level-sub1"); + EXPECT_EQ(flag_value, flag); + EXPECT_EQ(option_value, option); + flag_value = false; + option_value.clear(); + }; - sub_parser.add_positional_option(option_value, sharg::config{}); - - EXPECT_NO_THROW(sub_parser.parse()); - EXPECT_EQ("foo", option_value); - } + // parsing + auto top_level_parser = get_subcommand_parser({"-f", "sub1", "foo"}, {"sub1", "sub2"}); + top_level_parser.add_flag(flag_value, sharg::config{.short_id = 'f'}); + EXPECT_NO_THROW(top_level_parser.parse()); + check_and_reset_options(true, ""); - flag_value = false; // reinstate to default value + sharg::parser & sub_parser = top_level_parser.get_sub_parser(); + EXPECT_EQ(sub_parser.info.app_name, "test_parser-sub1"); + sub_parser.add_positional_option(option_value, sharg::config{}); + EXPECT_NO_THROW(sub_parser.parse()); + check_and_reset_options(false, "foo"); // top-level help page - { - char const * argv[]{"./top_level", "-h", "-f", "sub1", "foo"}; - sharg::parser top_level_parser{"top_level", 5, argv, sharg::update_notifications::off, {"sub1", "sub2"}}; - top_level_parser.add_flag(flag_value, sharg::config{.short_id = 'f'}); - - testing::internal::CaptureStdout(); - EXPECT_EXIT(top_level_parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - EXPECT_FALSE(std::string{testing::internal::GetCapturedStdout()}.empty()); - } - - flag_value = false; + top_level_parser = get_subcommand_parser({"-h", "-f", "sub1", "foo"}, {"sub1", "sub2"}); + top_level_parser.add_flag(flag_value, sharg::config{.short_id = 'f'}); + EXPECT_FALSE(get_parse_cout_on_exit(top_level_parser).empty()); + check_and_reset_options(false, ""); // sub-parser help page - { - char const * argv[]{"./top_level", "-f", "sub1", "-h"}; - sharg::parser top_level_parser{"top_level", 4, argv, sharg::update_notifications::off, {"sub1", "sub2"}}; - top_level_parser.add_flag(flag_value, sharg::config{.short_id = 'f'}); - - EXPECT_NO_THROW(top_level_parser.parse()); - EXPECT_EQ(true, flag_value); - - sharg::parser & sub_parser = top_level_parser.get_sub_parser(); + top_level_parser = get_subcommand_parser({"-f", "sub1", "foo", "-h"}, {"sub1", "sub2"}); + top_level_parser.add_flag(flag_value, sharg::config{.short_id = 'f'}); + EXPECT_NO_THROW(top_level_parser.parse()); + check_and_reset_options(true, ""); - EXPECT_EQ(sub_parser.info.app_name, "top_level-sub1"); - - sub_parser.add_positional_option(option_value, sharg::config{}); - - testing::internal::CaptureStdout(); - EXPECT_EXIT(sub_parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - EXPECT_FALSE(std::string{testing::internal::GetCapturedStdout()}.empty()); - } + sharg::parser & sub_parser2 = top_level_parser.get_sub_parser(); + EXPECT_EQ(sub_parser2.info.app_name, "test_parser-sub1"); + sub_parser2.add_positional_option(option_value, sharg::config{}); + EXPECT_FALSE(get_parse_cout_on_exit(sub_parser2).empty()); + check_and_reset_options(false, ""); // sub command may contain dash, see https://github.com/seqan/product_backlog/issues/234 - { - char const * argv[]{"./top_level", "-dash"}; - EXPECT_NO_THROW((sharg::parser{"top_level", 2, argv, sharg::update_notifications::off, {"-dash"}})); - } + EXPECT_NO_THROW((sharg::parser{"top", {"./top", "-dash"}, sharg::update_notifications::off, {"-dash"}})); } -TEST(parse_test, subcommand_parser_error) +TEST_F(format_parse_test, subcommand_parser_error) { - // incorrect sub command regardless of following arguments - { // see issue https://github.com/seqan/seqan3/issues/2172 - std::array argv{"./top_level", "subiddysub", "-f"}; - sharg::parser top_level_parser{"top", argv.size(), argv.data(), sharg::update_notifications::off, {"sub1"}}; - - EXPECT_THROW(top_level_parser.parse(), sharg::parser_error); - } + // incorrect sub command regardless of following arguments, https://github.com/seqan/seqan3/issues/2172 + auto top_level_parser = get_subcommand_parser({"subiddysub", "-f"}, {"sub1"}); + EXPECT_THROW(top_level_parser.parse(), sharg::parser_error); // incorrect sub command with no other arguments - { - std::array argv{"./top_level", "subiddysub"}; - sharg::parser top_level_parser{"top", argv.size(), argv.data(), sharg::update_notifications::off, {"sub1"}}; - - EXPECT_THROW(top_level_parser.parse(), sharg::parser_error); - } + top_level_parser = get_subcommand_parser({"subiddysub"}, {"sub1"}); + EXPECT_THROW(top_level_parser.parse(), sharg::parser_error); - // incorrect sub command with trailing special option - { // see issue https://github.com/seqan/sharg-parser/issues/171 - std::array argv{"./top_level", "subiddysub", "-h"}; - sharg::parser top_level_parser{"top", argv.size(), argv.data(), sharg::update_notifications::off, {"sub1"}}; - - EXPECT_THROW(top_level_parser.parse(), sharg::parser_error); - } + // incorrect sub command with trailing special option, https://github.com/seqan/sharg-parser/issues/171 + top_level_parser = get_subcommand_parser({"subiddysub", "-h"}, {"sub1"}); + EXPECT_THROW(top_level_parser.parse(), sharg::parser_error); } -TEST(parse_test, issue1544) +TEST_F(format_parse_test, issue1544) { - { // wrong separation of long value: - std::string option_value; - char const * argv[] = {"./parser_test", "--foohallo"}; - sharg::parser parser{"test_parser", 2, argv, sharg::update_notifications::off}; - parser.add_option(option_value, sharg::config{.long_id = "foo"}); - - EXPECT_THROW(parser.parse(), sharg::unknown_option); - } - - { // unknown option (`--foo-bar`) that has a prefix of a known option (`--foo`) - std::string option_value; - char const * argv[] = {"./parser_test", "--foo", "hallo", "--foo-bar", "ballo"}; - sharg::parser parser{"test_parser", 5, argv, sharg::update_notifications::off}; - parser.add_option(option_value, sharg::config{.long_id = "foo"}); - - EXPECT_THROW(parser.parse(), sharg::unknown_option); - } + std::string option_value{}; + std::string option_value2{}; - { // known option (`--foo-bar`) that has a prefix of a unknown option (`--foo`) - std::string option_value; - char const * argv[] = {"./parser_test", "--foo", "hallo", "--foo-bar", "ballo"}; - sharg::parser parser{"test_parser", 5, argv, sharg::update_notifications::off}; - parser.add_option(option_value, sharg::config{.long_id = "foo-bar"}); + // wrong separation of long value: + auto parser = get_parser("--foohallo"); + parser.add_option(option_value, sharg::config{.long_id = "foo"}); + EXPECT_THROW(parser.parse(), sharg::unknown_option); + EXPECT_EQ(option_value, ""); - EXPECT_THROW(parser.parse(), sharg::unknown_option); - } + // unknown option (`--foo-bar`) that has a prefix of a known option (`--foo`) + parser = get_parser("--foo", "hallo", "--foo-bar", "ballo"); + parser.add_option(option_value, sharg::config{.long_id = "foo"}); + EXPECT_THROW(parser.parse(), sharg::unknown_option); + EXPECT_EQ(option_value, "hallo"); // Todo: Expected? - { // known option (`--foo`) is a prefix of another known option (`--foo-bar`) - std::string foo_option_value; - std::string foobar_option_value; - char const * argv[] = {"./parser_test", "--foo", "hallo", "--foo-bar", "ballo"}; - sharg::parser parser{"test_parser", 5, argv, sharg::update_notifications::off}; - parser.add_option(foo_option_value, sharg::config{.long_id = "foo"}); - parser.add_option(foobar_option_value, sharg::config{.long_id = "foo-bar"}); + // known option (`--foo-bar`) that has a prefix of a unknown option (`--foo`) + parser = get_parser("--foo", "hallo", "--foo-bar", "ballo"); + parser.add_option(option_value, sharg::config{.long_id = "foo-bar"}); + EXPECT_THROW(parser.parse(), sharg::unknown_option); + EXPECT_EQ(option_value, "ballo"); // Todo: Expected? - EXPECT_NO_THROW(parser.parse()); - EXPECT_EQ(foo_option_value, "hallo"); - EXPECT_EQ(foobar_option_value, "ballo"); - } + // known option (`--foo`) is a prefix of another known option (`--foo-bar`) + parser = get_parser("--foo", "hallo", "--foo-bar", "ballo"); + parser.add_option(option_value, sharg::config{.long_id = "foo"}); + parser.add_option(option_value2, sharg::config{.long_id = "foo-bar"}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(option_value, "hallo"); + EXPECT_EQ(option_value2, "ballo"); } -TEST(parse_test, is_option_set) +TEST_F(format_parse_test, is_option_set) { std::string option_value{}; - char const * argv[] = {"./parser_test", "-l", "hallo", "--foobar", "ballo", "--", "--loo"}; - sharg::parser parser{"test_parser", 5, argv, sharg::update_notifications::off}; + + auto parser = get_parser("-l", "hallo", "--foobar", "ballo", "--"); parser.add_option(option_value, sharg::config{.short_id = 'l', .long_id = "loo"}); parser.add_option(option_value, sharg::config{.short_id = 'f', .long_id = "foobar"}); - EXPECT_THROW(parser.is_option_set("foo"), sharg::design_error); // you cannot call option_is_set before parse() + // you cannot call option_is_set before parse() + EXPECT_THROW(parser.is_option_set("foo"), sharg::design_error); EXPECT_NO_THROW(parser.parse()); @@ -973,35 +742,35 @@ TEST(parse_test, is_option_set) EXPECT_TRUE(parser.is_option_set("foobar")); EXPECT_FALSE(parser.is_option_set('f')); - EXPECT_FALSE(parser.is_option_set("loo")); // --loo is behind the `--` which signals the end of options! + EXPECT_FALSE(parser.is_option_set("loo")); // errors: - EXPECT_THROW(parser.is_option_set("l"), sharg::design_error); // short identifiers are passed as chars not strings - EXPECT_THROW(parser.is_option_set("f"), sharg::design_error); // short identifiers are passed as chars not strings + auto expect_design_error = [&](auto && option) + { + EXPECT_THROW(parser.is_option_set(option), sharg::design_error); + }; - EXPECT_THROW(parser.is_option_set("foo"), sharg::design_error); - EXPECT_THROW(parser.is_option_set("--"), sharg::design_error); - EXPECT_THROW(parser.is_option_set(""), sharg::design_error); + expect_design_error("l"); // short identifiers are passed as chars not strings + expect_design_error("f"); // short identifiers are passed as chars not strings + + expect_design_error("foo"); + expect_design_error("--"); + expect_design_error(""); - EXPECT_THROW(parser.is_option_set('!'), sharg::design_error); - EXPECT_THROW(parser.is_option_set('-'), sharg::design_error); - EXPECT_THROW(parser.is_option_set('_'), sharg::design_error); - EXPECT_THROW(parser.is_option_set('\0'), sharg::design_error); + expect_design_error('!'); + expect_design_error('-'); + expect_design_error('_'); + expect_design_error('\0'); } // https://github.com/seqan/seqan3/issues/2835 -TEST(parse_test, error_message_parsing) +TEST_F(format_parse_test, error_message_parsing) { - char const * argv[] = {"./parser_test", "--value", "-30"}; - uint64_t option_value{}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; + auto parser = get_parser("--value", "-30"); parser.add_option(option_value, sharg::config{.long_id = "value"}); - std::string expected_message{"Value parse failed for --value: Argument -30 could not be parsed as type " - "unsigned 64 bit integer."}; - try { parser.parse(); @@ -1009,6 +778,8 @@ TEST(parse_test, error_message_parsing) } catch (sharg::user_input_error const & exception) { + std::string_view const expected_message{"Value parse failed for --value: Argument -30 could not be parsed as " + "type unsigned 64 bit integer."}; EXPECT_EQ(expected_message, exception.what()); } catch (...) @@ -1018,130 +789,95 @@ TEST(parse_test, error_message_parsing) } // https://github.com/seqan/seqan3/pull/2381 -TEST(parse_test, container_options) +TEST_F(format_parse_test, container_options) { - { - std::vector option_values{}; - - char const * argv[] = {"./parser_test", "-i", "2", "-i", "1", "-i", "3"}; - sharg::parser parser{"test_parser", 7, argv, sharg::update_notifications::off}; - parser.add_option(option_values, sharg::config{.short_id = 'i'}); - - EXPECT_NO_THROW(parser.parse()); - - EXPECT_TRUE(option_values == (std::vector{2, 1, 3})); - } - - { - std::vector option_values{}; + std::vector integer_options{}; + std::vector bool_options{}; - char const * argv[] = {"./parser_test", "-b", "true", "-b", "false", "-b", "true"}; - sharg::parser parser{"test_parser", 7, argv, sharg::update_notifications::off}; - parser.add_option(option_values, sharg::config{.short_id = 'b'}); - - EXPECT_NO_THROW(parser.parse()); + auto parser = get_parser("-i", "2", "-i", "1", "-i", "3"); + parser.add_option(integer_options, sharg::config{.short_id = 'i'}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_TRUE(integer_options == (std::vector{2, 1, 3})); - EXPECT_TRUE(option_values == (std::vector{true, false, true})); - } + parser = get_parser("-b", "true", "-b", "false", "-b", "true"); + parser.add_option(bool_options, sharg::config{.short_id = 'b'}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_TRUE(bool_options == (std::vector{true, false, true})); } // https://github.com/seqan/seqan3/issues/2393 -TEST(parse_test, container_default) +TEST_F(format_parse_test, container_default) { - // overwrite default - { - std::vector option_values{1, 2, 3}; + std::vector option_values{1, 2, 3}; + bool option{false}; - char const * argv[] = {"./parser_test", "-i", "2", "-i", "1", "-i", "3"}; - sharg::parser parser{"test_parser", 7, argv, sharg::update_notifications::off}; - parser.add_option(option_values, sharg::config{.short_id = 'i'}); - - EXPECT_NO_THROW(parser.parse()); - - EXPECT_TRUE(option_values == (std::vector{2, 1, 3})); - } - // overwrite default, parameters are not consecutive + auto reset_options = [&]() { - std::vector option_values{1, 2, 3}; - bool bool_opt{false}; + option_values = {1, 2, 3}; + option = false; + }; - char const * argv[] = {"./parser_test", "-i", "2", "-b", "true", "-i", "1", "-i", "3"}; - sharg::parser parser{"test_parser", 9, argv, sharg::update_notifications::off}; - parser.add_option(option_values, sharg::config{.short_id = 'i'}); - parser.add_option(bool_opt, sharg::config{.short_id = 'b'}); + // overwrite default + auto parser = get_parser("-i", "2", "-i", "1", "-i", "3"); + parser.add_option(option_values, sharg::config{.short_id = 'i'}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_TRUE(option_values == (std::vector{2, 1, 3})); + reset_options(); - EXPECT_NO_THROW(parser.parse()); + // overwrite default, parameters are not consecutive + parser = get_parser("-i", "2", "-b", "true", "-i", "1", "-i", "3"); + parser.add_option(option_values, sharg::config{.short_id = 'i'}); + parser.add_option(option, sharg::config{.short_id = 'b'}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_TRUE(option_values == (std::vector{2, 1, 3})); + EXPECT_TRUE(option); + reset_options(); - EXPECT_TRUE(option_values == (std::vector{2, 1, 3})); - } // use default - { - std::vector option_values{1, 2, 3}; - bool bool_opt{false}; - - char const * argv[] = {"./parser_test", "-b", "true"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - parser.add_option(option_values, sharg::config{.short_id = 'i'}); - parser.add_option(bool_opt, sharg::config{.short_id = 'b'}); - - EXPECT_NO_THROW(parser.parse()); + parser = get_parser("-b", "true"); + parser.add_option(option_values, sharg::config{.short_id = 'i'}); + parser.add_option(option, sharg::config{.short_id = 'b'}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_TRUE(option_values == (std::vector{1, 2, 3})); + EXPECT_TRUE(option); + reset_options(); - EXPECT_TRUE(option_values == (std::vector{1, 2, 3})); - } // overwrite default for positional options - { - std::vector option_values{1, 2, 3}; - - char const * argv[] = {"./parser_test", "2", "1", "3"}; - sharg::parser parser{"test_parser", 4, argv, sharg::update_notifications::off}; - parser.add_positional_option(option_values, sharg::config{}); - - EXPECT_NO_THROW(parser.parse()); - - EXPECT_TRUE(option_values == (std::vector{2, 1, 3})); - } + parser = get_parser("2", "1", "3"); + parser.add_positional_option(option_values, sharg::config{}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_TRUE(option_values == (std::vector{2, 1, 3})); } -TEST(parse_test, executable_name) +TEST_F(format_parse_test, executable_name) { - testing::internal::CaptureStdout(); - { - std::array argv{"parser_test"}; - sharg::parser parser{"test_parser", argv.size(), argv.data(), sharg::update_notifications::off}; - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - auto & executable_name = sharg::detail::test_accessor::executable_name(parser); - ASSERT_EQ(executable_name.size(), 1); - EXPECT_EQ(executable_name[0], "parser_test"); - } + bool flag{false}; + auto parser = get_parser(); + auto check = [&](std::string_view expected) { - std::array argv{"./parser_test"}; - sharg::parser parser{"test_parser", argv.size(), argv.data(), sharg::update_notifications::off}; - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); + parser = sharg::parser{"test_parser", {expected.data(), "-t"}, sharg::update_notifications::off}; + parser.add_flag(flag, sharg::config{.short_id = 't'}); + EXPECT_NO_THROW(parser.parse()); auto & executable_name = sharg::detail::test_accessor::executable_name(parser); ASSERT_EQ(executable_name.size(), 1); - EXPECT_EQ(executable_name[0], "./parser_test"); - } + EXPECT_EQ(executable_name[0], expected); + flag = false; + }; - { - std::array argv{"./bin/parser_test"}; - sharg::parser parser{"test_parser", argv.size(), argv.data(), sharg::update_notifications::off}; - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - auto & executable_name = sharg::detail::test_accessor::executable_name(parser); - ASSERT_EQ(executable_name.size(), 1); - EXPECT_EQ(executable_name[0], "./bin/parser_test"); - } + check("parser_test"); + check("./parser_test"); + check("./bin/parser_test"); - { - std::array argv{"./bin/parser_test", "build"}; - sharg::parser parser{"test_parser", argv.size(), argv.data(), sharg::update_notifications::off, {"build"}}; - parser.parse(); - auto & sub_parser = parser.get_sub_parser(); - EXPECT_EXIT(sub_parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - auto & executable_name = sharg::detail::test_accessor::executable_name(sub_parser); - ASSERT_EQ(executable_name.size(), 2); - EXPECT_EQ(executable_name[0], "./bin/parser_test"); - EXPECT_EQ(executable_name[1], "build"); - } - testing::internal::GetCapturedStdout(); + parser = sharg::parser{"test_parser", {"parser_test", "build", "-t"}, sharg::update_notifications::off, {"build"}}; + EXPECT_NO_THROW(parser.parse()); + + auto & sub_parser = parser.get_sub_parser(); + sub_parser.add_flag(flag, sharg::config{.short_id = 't'}); + EXPECT_NO_THROW(sub_parser.parse()); + + auto & executable_name = sharg::detail::test_accessor::executable_name(sub_parser); + ASSERT_EQ(executable_name.size(), 2); + EXPECT_EQ(executable_name[0], "parser_test"); + EXPECT_EQ(executable_name[1], "build"); } diff --git a/test/unit/parser/format_parse_validators_test.cpp b/test/unit/parser/format_parse_validators_test.cpp index 46b62f84..87d275a6 100644 --- a/test/unit/parser/format_parse_validators_test.cpp +++ b/test/unit/parser/format_parse_validators_test.cpp @@ -8,6 +8,7 @@ #include #include +#include #include std::string const basic_options_str = " Common options\n" @@ -33,24 +34,15 @@ std::string const basic_version_str = "VERSION\n" " Sharg version: " + std::string{sharg::sharg_version_cstring} + "\n"; -namespace sharg::detail -{ -struct test_accessor -{ - static void set_terminal_width(sharg::parser & parser, unsigned terminal_width) - { - std::visit( - [terminal_width](auto & f) - { - if constexpr (std::is_same_v) - f.layout = sharg::detail::format_help::console_layout_struct{terminal_width}; - }, - parser.format); - } -}; -} // namespace sharg::detail +class validator_test : public sharg::test::test_fixture +{}; + +// clang-format off +// First argument cannot have comma, the second one can. +#define EXPECT_SAME_TYPE(a, b, ...) EXPECT_TRUE((std::same_as)) +// clang-format on -TEST(validator_test, fullfill_concept) +TEST_F(validator_test, fullfill_concept) { EXPECT_FALSE(sharg::validator); @@ -68,373 +60,276 @@ TEST(validator_test, fullfill_concept) EXPECT_TRUE(sharg::validator); } -TEST(validator_test, input_file) +TEST_F(validator_test, input_file) { - sharg::test::tmp_filename tmp_name{"testbox.fasta"}; - sharg::test::tmp_filename tmp_name_2{"testbox_2.fasta"}; - sharg::test::tmp_filename tmp_name_hidden{".testbox.fasta"}; - sharg::test::tmp_filename tmp_name_multiple{"testbox.fasta.txt"}; - - std::vector formats{std::string{"fa"}, std::string{"sam"}, std::string{"fasta"}, std::string{"fasta.txt"}}; - - std::ofstream tmp_file(tmp_name.get_path()); - std::ofstream tmp_file_2(tmp_name_2.get_path()); - std::ofstream tmp_file_hidden(tmp_name_hidden.get_path()); - std::ofstream tmp_file_multiple(tmp_name_multiple.get_path()); - - { // single file - - { // empty list of file. - sharg::input_file_validator my_validator{}; - EXPECT_NO_THROW(my_validator(tmp_name.get_path())); - } - - { // file already exists. - std::filesystem::path does_not_exist{tmp_name.get_path()}; - does_not_exist.replace_extension(".bam"); - sharg::input_file_validator my_validator{formats}; - EXPECT_THROW(my_validator(does_not_exist), sharg::validation_error); - } - - { // file has wrong format. - sharg::input_file_validator my_validator{std::vector{std::string{"sam"}}}; - EXPECT_THROW(my_validator(tmp_name.get_path()), sharg::validation_error); - } - - { // file has no extension. - std::filesystem::path does_not_exist{tmp_name.get_path()}; - does_not_exist.replace_extension(); - sharg::input_file_validator my_validator{formats}; - EXPECT_THROW(my_validator(does_not_exist), sharg::validation_error); - } - - { // filename starts with dot. - sharg::input_file_validator my_validator{formats}; - EXPECT_NO_THROW(my_validator(tmp_name_hidden.get_path())); - } - - { // file has multiple extensions. - sharg::input_file_validator my_validator{formats}; - EXPECT_NO_THROW(my_validator(tmp_name_multiple.get_path())); - } - - std::filesystem::path file_in_path; - - // option - std::string const & path = tmp_name.get_path().string(); - char const * argv[] = {"./parser_test", "-i", path.c_str()}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option(file_in_path, - sharg::config{.short_id = 'i', .validator = sharg::input_file_validator{formats}}); - - EXPECT_NO_THROW(parser.parse()); - EXPECT_EQ(file_in_path.string(), path); + sharg::test::tmp_filename const tmp_name{"testbox.fasta"}; + sharg::test::tmp_filename const tmp_name_2{"testbox_2.fasta"}; + sharg::test::tmp_filename const tmp_name_hidden{".testbox.fasta"}; + sharg::test::tmp_filename const tmp_name_multiple{"testbox.fasta.txt"}; + + std::filesystem::path const tmp_path{tmp_name.get_path()}; + std::filesystem::path const tmp_path_2{tmp_name_2.get_path()}; + std::filesystem::path const tmp_path_hidden{tmp_name_hidden.get_path()}; + std::filesystem::path const tmp_path_multiple{tmp_name_multiple.get_path()}; + + { // Open fstream to create files. + std::ofstream tmp_file{tmp_path}; + std::ofstream tmp_file_2{tmp_path_2}; + std::ofstream tmp_file_hidden{tmp_path_hidden}; + std::ofstream tmp_file_multiple{tmp_path_multiple}; } - { // file list. - std::vector input_files; + std::vector const formats{"fa", "sam", "fasta", "fasta.txt"}; + sharg::input_file_validator my_validator{formats}; - // option - std::string const & path = tmp_name.get_path().string(); - std::string const & path_2 = tmp_name_2.get_path().string(); + // filename starts with dot. + EXPECT_NO_THROW(my_validator(tmp_path_hidden)); - char const * argv[] = {"./parser_test", path.c_str(), path_2.c_str()}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_positional_option(input_files, sharg::config{.validator = sharg::input_file_validator{formats}}); + // file has multiple extensions. + EXPECT_NO_THROW(my_validator(tmp_path_multiple)); - EXPECT_NO_THROW(parser.parse()); - EXPECT_EQ(input_files.size(), 2u); - EXPECT_EQ(input_files[0].string(), path); - EXPECT_EQ(input_files[1].string(), path_2); - } + // file does not exist. + std::filesystem::path test_path = tmp_path; + EXPECT_THROW(my_validator(test_path.replace_extension(".bam")), sharg::validation_error); - { // get help page message - std::filesystem::path path; - char const * argv[] = {"./parser_test", "-h"}; - sharg::parser parser{"test_parser", 2, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_positional_option( - path, - sharg::config{.description = "desc", .validator = sharg::input_file_validator{formats}}); - - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std::string my_stdout = testing::internal::GetCapturedStdout(); - std::string expected = - std::string{"test_parser\n" - "===========\n" - "\n" - "POSITIONAL ARGUMENTS\n" - " ARGUMENT-1 (std::filesystem::path)\n" - " desc. The input file must exist and read permissions must be\n" - " granted. Valid file extensions are: [fa, sam, fasta, fasta.txt].\n" - "\nOPTIONS\n\n"} - + basic_options_str + "\n" + basic_version_str; - EXPECT_EQ(my_stdout, expected); - } + // file has no extension. + EXPECT_THROW(my_validator(test_path.replace_extension()), sharg::validation_error); - { // get help page message (file extensions) - sharg::input_file_validator validator1{formats}; - EXPECT_EQ(validator1.get_help_page_message(), - "The input file must exist and read permissions must be granted. " - "Valid file extensions are: [fa, sam, fasta, fasta.txt]."); + // file has wrong format. + my_validator = sharg::input_file_validator{std::vector{std::string{"sam"}}}; + EXPECT_THROW(my_validator(tmp_path), sharg::validation_error); - sharg::input_file_validator validator2{std::vector{}}; - EXPECT_EQ(validator2.get_help_page_message(), - "The input file must exist and read permissions must be granted."); - } + // empty list of formats. + my_validator = sharg::input_file_validator{}; + EXPECT_NO_THROW(my_validator(tmp_path)); + + // Parser with option. + my_validator = sharg::input_file_validator{formats}; + std::filesystem::path path_in{}; + std::string const tmp_path_string = tmp_path.string(); + + auto parser = get_parser("-i", tmp_path_string); + parser.add_option(path_in, sharg::config{.short_id = 'i', .validator = my_validator}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(path_in.string(), tmp_path_string); + + // Parser with list option. + std::vector input_files{}; + std::string const tmp_path_2_string = tmp_path_2.string(); + + parser = get_parser(tmp_path_string, tmp_path_2_string); + parser.add_positional_option(input_files, sharg::config{.validator = my_validator}); + + EXPECT_NO_THROW(parser.parse()); + ASSERT_EQ(input_files.size(), 2u); + EXPECT_EQ(input_files[0], tmp_path); + EXPECT_EQ(input_files[1], tmp_path_2); + + // get help page message + parser = get_parser("-h"); + parser.add_positional_option(path_in, sharg::config{.description = "desc", .validator = my_validator}); + std::string expected = std::string{"test_parser\n" + "===========\n" + "\n" + "POSITIONAL ARGUMENTS\n" + " ARGUMENT-1 (std::filesystem::path)\n" + " desc. The input file must exist and read permissions must be\n" + " granted. Valid file extensions are: [fa, sam, fasta, fasta.txt].\n" + "\nOPTIONS\n\n"} + + basic_options_str + "\n" + basic_version_str; + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); + + // get help page message (file extensions) + my_validator = sharg::input_file_validator{formats}; + EXPECT_EQ(my_validator.get_help_page_message(), + "The input file must exist and read permissions must be granted. " + "Valid file extensions are: [fa, sam, fasta, fasta.txt]."); + + my_validator = sharg::input_file_validator{}; + EXPECT_EQ(my_validator.get_help_page_message(), "The input file must exist and read permissions must be granted."); } -TEST(validator_test, output_file) +TEST_F(validator_test, output_file) { - sharg::test::tmp_filename tmp_name{"testbox.fasta"}; - std::filesystem::path not_existing_path{tmp_name.get_path()}; - sharg::test::tmp_filename tmp_name_2{"testbox_2.fasta"}; - std::ofstream tmp_file_2(tmp_name_2.get_path()); // create file - std::filesystem::path existing_path{tmp_name_2.get_path()}; - sharg::test::tmp_filename tmp_name_3{"testbox_3.fa"}; - sharg::test::tmp_filename hidden_name{".testbox.fasta"}; - - std::vector formats{std::string{"fa"}, std::string{"sam"}, std::string{"fasta"}, std::string{"fasta.txt"}}; - - { // single file - - { // file does not exist (& no formats given) - sharg::output_file_validator my_validator{sharg::output_file_open_options::open_or_create}; - EXPECT_NO_THROW(my_validator(not_existing_path)); - sharg::output_file_validator my_validator2{sharg::output_file_open_options::create_new}; - EXPECT_NO_THROW(my_validator2(not_existing_path)); - sharg::output_file_validator my_validator3{}; // default: create_new - EXPECT_NO_THROW(my_validator3(not_existing_path)); - sharg::output_file_validator my_validator4{std::vector{}}; // empty formats -> no formats - EXPECT_NO_THROW(my_validator4(not_existing_path)); - sharg::output_file_validator my_validator5{""}; // empty formats -> no formats - EXPECT_NO_THROW(my_validator5(not_existing_path)); - } - - { // filepath is a directory. Checking writeability would delete content of directory. - sharg::output_file_validator my_validator{sharg::output_file_open_options::open_or_create}; - EXPECT_THROW(my_validator(std::filesystem::temp_directory_path()), sharg::validation_error); - } - - { // file does exist & overwriting is prohibited - sharg::output_file_validator my_validator{sharg::output_file_open_options::create_new, formats}; - EXPECT_THROW(my_validator(existing_path), sharg::validation_error); - } - - { // file does exist but allow to overwrite it - sharg::output_file_validator my_validator{sharg::output_file_open_options::open_or_create, formats}; - EXPECT_NO_THROW(my_validator(existing_path)); - } - - { // file has wrong format. - sharg::output_file_validator my_validator{sharg::output_file_open_options::create_new, - std::vector{std::string{"sam"}}}; - EXPECT_THROW(my_validator(tmp_name.get_path()), sharg::validation_error); - } - - { // file has no extension. - std::filesystem::path no_extension{tmp_name.get_path()}; - no_extension.replace_extension(); - sharg::output_file_validator my_validator{sharg::output_file_open_options::create_new, formats}; - EXPECT_THROW(my_validator(no_extension), sharg::validation_error); - } - - { // filename is shorter than extension. - std::filesystem::path filename{tmp_name.get_path()}; - std::vector long_extension{"super_duper_long_extension_longer_than_seqan_tmp_filename"}; - sharg::output_file_validator my_validator{sharg::output_file_open_options::create_new, long_extension}; - EXPECT_THROW(my_validator(filename), sharg::validation_error); - } - - { // filename starts with dot. - sharg::output_file_validator my_validator{sharg::output_file_open_options::create_new, formats}; - EXPECT_NO_THROW(my_validator(hidden_name.get_path())); - } - - { // file has multiple extensions. - std::filesystem::path multiple_extension{tmp_name.get_path()}; - multiple_extension.replace_extension("fasta.txt"); - sharg::output_file_validator my_validator{sharg::output_file_open_options::create_new, formats}; - EXPECT_NO_THROW(my_validator(multiple_extension)); - } - - std::filesystem::path file_out_path; - - // option - std::string const & path = tmp_name.get_path().string(); - char const * argv[] = {"./parser_test", "-o", path.c_str()}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option( - file_out_path, - sharg::config{.short_id = 'o', - .validator = - sharg::output_file_validator{sharg::output_file_open_options::create_new, formats}}); - - EXPECT_NO_THROW(parser.parse()); - EXPECT_EQ(file_out_path.string(), path); - } + sharg::test::tmp_filename const tmp_name{"testbox.fasta"}; + sharg::test::tmp_filename const tmp_name_2{"testbox_2.fasta"}; + sharg::test::tmp_filename const tmp_name_3{"testbox_3.fa"}; + sharg::test::tmp_filename const hidden_name{".testbox.fasta"}; - { // file list. - std::vector output_files; - - // option - std::string const & path = tmp_name.get_path().string(); - std::string const & path_3 = tmp_name_3.get_path().string(); - - char const * argv[] = {"./parser_test", path.c_str(), path_3.c_str()}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_positional_option( - output_files, - sharg::config{.validator = - sharg::output_file_validator{sharg::output_file_open_options::create_new, formats}}); - - EXPECT_NO_THROW(parser.parse()); - EXPECT_EQ(output_files.size(), 2u); - EXPECT_EQ(output_files[0].string(), path); - EXPECT_EQ(output_files[1].string(), path_3); - } + std::filesystem::path const not_existing_path{tmp_name.get_path()}; + std::filesystem::path const existing_path{tmp_name_2.get_path()}; - // get help page message (overwriting prohibited) - { - std::filesystem::path path; - char const * argv[] = {"./parser_test", "-h"}; - sharg::parser parser{"test_parser", 2, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_positional_option( - path, - sharg::config{.description = "desc", - .validator = - sharg::output_file_validator{sharg::output_file_open_options::create_new, formats}}); - - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std::string my_stdout = testing::internal::GetCapturedStdout(); - std::string expected = - std::string{"test_parser\n" - "===========\n" - "\n" - "POSITIONAL ARGUMENTS\n" - " ARGUMENT-1 (std::filesystem::path)\n" - " desc. The output file must not exist already and write permissions\n" - " must be granted. Valid file extensions are: [fa, sam, fasta,\n" - " fasta.txt].\n" - "\nOPTIONS\n\n"} - + basic_options_str + "\n" + basic_version_str; - EXPECT_EQ(my_stdout, expected); + { // Open fstream to create files. + std::ofstream tmp_file{existing_path}; } - // get help page message (overwriting allowed) - { - std::filesystem::path path; - char const * argv[] = {"./parser_test", "-h"}; - sharg::parser parser{"test_parser", 2, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_positional_option( - path, - sharg::config{.description = "desc", - .validator = - sharg::output_file_validator{sharg::output_file_open_options::open_or_create, formats}}); - - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std::string my_stdout = testing::internal::GetCapturedStdout(); - std::string expected = - std::string{"test_parser\n" - "===========\n" - "\n" - "POSITIONAL ARGUMENTS\n" - " ARGUMENT-1 (std::filesystem::path)\n" - " desc. Write permissions must be granted. Valid file extensions are:\n" - " [fa, sam, fasta, fasta.txt].\n" - "\nOPTIONS\n\n"} - + basic_options_str + "\n" + basic_version_str; - EXPECT_EQ(my_stdout, expected); - } + using options = sharg::output_file_open_options; - { // get help page message (file extensions) - sharg::output_file_validator validator1{sharg::output_file_open_options::create_new, formats}; - EXPECT_EQ(validator1.get_help_page_message(), - "The output file must not exist already and write permissions " - "must be granted. Valid file extensions are: " - "[fa, sam, fasta, fasta.txt]."); - - sharg::output_file_validator validator2{sharg::output_file_open_options::create_new, - std::vector{}}; - EXPECT_EQ(validator2.get_help_page_message(), - "The output file must not exist already and write permissions " - "must be granted."); - } + std::vector const formats{std::string{"fa"}, std::string{"sam"}, std::string{"fasta"}, std::string{"fasta.txt"}}; + sharg::output_file_validator my_validator{/* options::create_new */}; - { // parameter pack constructor - extensions only - sharg::output_file_validator validator1{"fa", "sam", "fasta", "fasta.txt"}; - sharg::output_file_validator validator2{formats}; - sharg::output_file_validator validator3{sharg::output_file_open_options::create_new, formats}; - EXPECT_EQ(validator1.get_help_page_message(), validator2.get_help_page_message()); - EXPECT_EQ(validator2.get_help_page_message(), validator3.get_help_page_message()); - } + // file does not exist (& no formats given) + EXPECT_NO_THROW(my_validator(not_existing_path)); - { // parameter pack constructor - mode + extensions - sharg::output_file_validator validator1{sharg::output_file_open_options::open_or_create, - "fa", - "sam", - "fasta", - "fasta.txt"}; - sharg::output_file_validator validator2{sharg::output_file_open_options::open_or_create, formats}; - EXPECT_EQ(validator1.get_help_page_message(), validator2.get_help_page_message()); - } + my_validator = sharg::output_file_validator{options::open_or_create}; + EXPECT_NO_THROW(my_validator(not_existing_path)); + + my_validator = sharg::output_file_validator{options::create_new}; + EXPECT_NO_THROW(my_validator(not_existing_path)); + + my_validator = sharg::output_file_validator{std::vector{}}; // Empty formats -> no formats + EXPECT_NO_THROW(my_validator(not_existing_path)); + + my_validator = sharg::output_file_validator{""}; // Empty formats + EXPECT_NO_THROW(my_validator(not_existing_path)); + + // filepath is a directory. Checking writeability would delete content of directory. + my_validator = sharg::output_file_validator{options::open_or_create}; + EXPECT_THROW(my_validator(std::filesystem::temp_directory_path()), sharg::validation_error); + + // file has wrong format. + my_validator = sharg::output_file_validator{options::create_new, std::vector{std::string{"sam"}}}; + EXPECT_THROW(my_validator(existing_path), sharg::validation_error); + + // file does exist & overwriting is prohibited + my_validator = sharg::output_file_validator{options::create_new, formats}; + EXPECT_THROW(my_validator(existing_path), sharg::validation_error); + + // file does exist but allow to overwrite it + my_validator = sharg::output_file_validator{options::open_or_create, formats}; + EXPECT_TRUE(std::filesystem::exists(existing_path)); // Todo: Expected? + EXPECT_NO_THROW(my_validator(existing_path)); + EXPECT_FALSE(std::filesystem::exists(existing_path)); // Todo: Expected? + + // file has no extension. + std::filesystem::path test_path = not_existing_path; + EXPECT_THROW(my_validator(test_path.replace_extension()), sharg::validation_error); + + // filename is shorter than extension. + std::string long_extension = not_existing_path.string() + ".longer.than.path"; + my_validator = sharg::output_file_validator{options::create_new, std::vector{long_extension}}; + EXPECT_THROW(my_validator(not_existing_path), sharg::validation_error); + + long_extension = existing_path.string() + ".longer.than.path"; + my_validator = sharg::output_file_validator{options::create_new, std::vector{long_extension}}; + EXPECT_THROW(my_validator(existing_path), sharg::validation_error); + + // filename starts with dot. + my_validator = sharg::output_file_validator{options::create_new, formats}; + EXPECT_NO_THROW(my_validator(hidden_name.get_path())); + + // file has multiple extensions. + test_path = not_existing_path; + test_path.replace_extension("fasta.txt"); + EXPECT_NO_THROW(my_validator(test_path)); + + // Parser with option. + std::filesystem::path path_out{}; + std::string const not_existing_path_string = not_existing_path.string(); + + auto parser = get_parser("-o", not_existing_path_string); + parser.add_option(path_out, sharg::config{.short_id = 'o', .validator = my_validator}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(path_out.string(), not_existing_path_string); + + // Parser with list option. + std::vector output_files{}; + std::string const not_existing_path_string_2 = tmp_name_3.get_path().string(); + + parser = get_parser(not_existing_path_string, not_existing_path_string_2); + parser.add_positional_option(output_files, sharg::config{.validator = my_validator}); + + EXPECT_NO_THROW(parser.parse()); + ASSERT_EQ(output_files.size(), 2u); + EXPECT_EQ(output_files[0], not_existing_path); + EXPECT_EQ(output_files[1], tmp_name_3.get_path()); + + // get help page message (overwriting prohibited) + parser = get_parser("-h"); + parser.add_positional_option(path_out, sharg::config{.description = "desc", .validator = my_validator}); + std::string expected = std::string{"test_parser\n" + "===========\n" + "\n" + "POSITIONAL ARGUMENTS\n" + " ARGUMENT-1 (std::filesystem::path)\n" + " desc. The output file must not exist already and write permissions\n" + " must be granted. Valid file extensions are: [fa, sam, fasta,\n" + " fasta.txt].\n" + "\nOPTIONS\n\n"} + + basic_options_str + "\n" + basic_version_str; + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); + + // get help page message (overwriting allowed) + my_validator = sharg::output_file_validator{options::open_or_create, formats}; + parser = get_parser("-h"); + parser.add_positional_option(path_out, sharg::config{.description = "desc", .validator = my_validator}); + expected = std::string{"test_parser\n" + "===========\n" + "\n" + "POSITIONAL ARGUMENTS\n" + " ARGUMENT-1 (std::filesystem::path)\n" + " desc. Write permissions must be granted. Valid file extensions are:\n" + " [fa, sam, fasta, fasta.txt].\n" + "\nOPTIONS\n\n"} + + basic_options_str + "\n" + basic_version_str; + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); + + // get help page message (file extensions) + my_validator = sharg::output_file_validator{options::create_new, formats}; + EXPECT_EQ(my_validator.get_help_page_message(), + "The output file must not exist already and write permissions " + "must be granted. Valid file extensions are: " + "[fa, sam, fasta, fasta.txt]."); + + my_validator = sharg::output_file_validator{options::create_new, std::vector{}}; + EXPECT_EQ(my_validator.get_help_page_message(), + "The output file must not exist already and write permissions " + "must be granted."); + + // parameter pack constructor - extensions only + my_validator = sharg::output_file_validator{options::create_new, formats}; + sharg::output_file_validator validator1{"fa", "sam", "fasta", "fasta.txt"}; + sharg::output_file_validator validator2{formats}; + EXPECT_EQ(validator1.get_help_page_message(), validator2.get_help_page_message()); + EXPECT_EQ(validator2.get_help_page_message(), my_validator.get_help_page_message()); + + // parameter pack constructor - mode + extensions + validator1 = sharg::output_file_validator{options::open_or_create, "fa", "sam", "fasta", "fasta.txt"}; + validator2 = sharg::output_file_validator{options::open_or_create, formats}; + EXPECT_EQ(validator1.get_help_page_message(), validator2.get_help_page_message()); } -TEST(validator_test, input_directory) +TEST_F(validator_test, input_directory) { - sharg::test::tmp_filename tmp_name{"testbox.fasta"}; + sharg::test::tmp_filename const tmp_name{"testbox.fasta"}; + std::filesystem::path const tmp_path{tmp_name.get_path()}; - {// directory + { // Open fstream to create files. + std::ofstream tmp_file{tmp_path}; + } - {// has filename - std::ofstream tmp_dir(tmp_name.get_path()); sharg::input_directory_validator my_validator{}; - EXPECT_THROW(my_validator(tmp_name.get_path()), sharg::validation_error); -} -{ // read directory - std::filesystem::path p = tmp_name.get_path(); - p.remove_filename(); - std::ofstream tmp_dir(p); - sharg::input_directory_validator my_validator{}; - my_validator(p); - EXPECT_NO_THROW(my_validator(p)); + // Input is a file. + EXPECT_THROW(my_validator(tmp_path), sharg::validation_error); - std::filesystem::path dir_in_path; + // Input is a directory. + std::filesystem::path test_path = tmp_path; + test_path.remove_filename(); + EXPECT_NO_THROW(my_validator(test_path)); - // option - std::string const & path = p.string(); - char const * argv[] = {"./parser_test", "-i", path.c_str()}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option(dir_in_path, sharg::config{.short_id = 'i', .validator = sharg::input_directory_validator{}}); + // Parser with option. + std::filesystem::path path_in{}; + std::string const test_path_string = test_path.string(); + auto parser = get_parser("-i", test_path_string); + parser.add_option(path_in, sharg::config{.short_id = 'i', .validator = my_validator}); EXPECT_NO_THROW(parser.parse()); - EXPECT_EQ(path, dir_in_path.string()); -} -} + EXPECT_EQ(path_in.string(), test_path_string); -{ // get help page message - std::filesystem::path path; - char const * argv[] = {"./parser_test", "-h"}; - sharg::parser parser{"test_parser", 2, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_positional_option(path, - sharg::config{.description = "desc", .validator = sharg::input_directory_validator{}}); - - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std::string my_stdout = testing::internal::GetCapturedStdout(); + parser = get_parser("-h"); + parser.add_positional_option(path_in, sharg::config{.description = "desc", .validator = my_validator}); std::string expected = std::string{"test_parser\n" "===========\n" "\n" @@ -443,122 +338,119 @@ TEST(validator_test, input_directory) " desc. An existing, readable path for the input directory.\n" "\nOPTIONS\n\n"} + basic_options_str + "\n" + basic_version_str; - - EXPECT_EQ(my_stdout, expected); -} + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(validator_test, output_directory) +TEST_F(validator_test, output_directory) { - sharg::test::tmp_filename tmp_name{"testbox.fasta"}; - - { // read directory - std::filesystem::path p = tmp_name.get_path(); - p.remove_filename(); - sharg::output_directory_validator my_validator{}; - my_validator(p); - EXPECT_NO_THROW(); - - std::filesystem::path dir_out_path; - - // option - std::string const & path = p.string(); - char const * argv[] = {"./parser_test", "-o", path.c_str()}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option(dir_out_path, - sharg::config{.short_id = 'o', .validator = sharg::output_directory_validator{}}); - - EXPECT_NO_THROW(parser.parse()); - EXPECT_EQ(path, dir_out_path.string()); + sharg::test::tmp_filename const tmp_name{"testbox.fasta"}; + std::filesystem::path const tmp_path{tmp_name.get_path()}; + + { // Open fstream to create files. + std::ofstream tmp_file{tmp_path}; } - { // Parent path exists and is writable. - sharg::test::tmp_filename tmp_child_name{"dir/child_dir"}; - std::filesystem::path tmp_child_dir{tmp_child_name.get_path()}; - std::filesystem::path tmp_parent_path{tmp_child_dir.parent_path()}; + sharg::output_directory_validator my_validator{}; - std::filesystem::create_directory(tmp_parent_path); + // Output is a file. + EXPECT_THROW(my_validator(tmp_path), sharg::validation_error); - EXPECT_TRUE(std::filesystem::exists(tmp_parent_path)); - EXPECT_NO_THROW(sharg::output_directory_validator{}(tmp_child_dir)); - } + // Output is a directory. + std::filesystem::path test_path = tmp_path; + test_path.remove_filename(); + EXPECT_NO_THROW(my_validator(test_path)); - { - // get help page message - std::filesystem::path path; - char const * argv[] = {"./parser_test", "-h"}; - sharg::parser parser{"test_parser", 2, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_positional_option( - path, - sharg::config{.description = "desc", .validator = sharg::output_directory_validator{}}); - - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std::string my_stdout = testing::internal::GetCapturedStdout(); - std::string expected = std::string{"test_parser\n" - "===========\n" - "\n" - "POSITIONAL ARGUMENTS\n" - " ARGUMENT-1 (std::filesystem::path)\n" - " desc. A valid path for the output directory.\n" - "\nOPTIONS\n\n"} - + basic_options_str + "\n" + basic_version_str; - - EXPECT_EQ(my_stdout, expected); - } + // Parser with option. + std::filesystem::path path_out{}; + std::string const test_path_string = test_path.string(); + + auto parser = get_parser("-o", test_path_string); + parser.add_option(path_out, sharg::config{.short_id = 'o', .validator = my_validator}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(path_out.string(), test_path_string); + + // Parent path exists and is writable. + sharg::test::tmp_filename tmp_child_name{"dir/child_dir"}; + std::filesystem::path tmp_child_dir{tmp_child_name.get_path()}; + std::filesystem::path tmp_parent_path{tmp_child_dir.parent_path()}; + + EXPECT_FALSE(std::filesystem::exists(tmp_parent_path)); + EXPECT_THROW(my_validator(tmp_child_dir), sharg::validation_error); + + std::filesystem::create_directory(tmp_parent_path); + EXPECT_TRUE(std::filesystem::exists(tmp_parent_path)); + EXPECT_NO_THROW(my_validator(tmp_child_dir)); + + // get help page message + parser = get_parser("-h"); + parser.add_positional_option(path_out, sharg::config{.description = "desc", .validator = my_validator}); + std::string expected = std::string{"test_parser\n" + "===========\n" + "\n" + "POSITIONAL ARGUMENTS\n" + " ARGUMENT-1 (std::filesystem::path)\n" + " desc. A valid path for the output directory.\n" + "\nOPTIONS\n\n"} + + basic_options_str + "\n" + basic_version_str; + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(validator_test, inputfile_not_readable) +TEST_F(validator_test, inputfile_not_readable) { - sharg::test::tmp_filename tmp_name{"my_file.test"}; - std::filesystem::path tmp_file{tmp_name.get_path()}; - std::ofstream str{tmp_name.get_path()}; + sharg::test::tmp_filename const tmp_name{"my_file.test"}; + std::filesystem::path const tmp_path{tmp_name.get_path()}; + + { // Open fstream to create files. + std::ofstream tmp_file{tmp_path}; + } + + sharg::input_file_validator my_validator{}; - EXPECT_NO_THROW(sharg::input_file_validator{}(tmp_file)); + EXPECT_NO_THROW(my_validator(tmp_path)); - std::filesystem::permissions(tmp_file, + std::filesystem::permissions(tmp_path, std::filesystem::perms::owner_read | std::filesystem::perms::group_read | std::filesystem::perms::others_read, std::filesystem::perm_options::remove); - if (!sharg::test::read_access(tmp_file)) // Do not execute with root permissions. + if (!sharg::test::read_access(tmp_path)) // Do not execute with root permissions. { - EXPECT_THROW(sharg::input_file_validator{}(tmp_file), sharg::validation_error); + EXPECT_THROW(my_validator(tmp_path), sharg::validation_error); } - std::filesystem::permissions(tmp_file, + std::filesystem::permissions(tmp_path, std::filesystem::perms::owner_read | std::filesystem::perms::group_read | std::filesystem::perms::others_read, std::filesystem::perm_options::add); } -TEST(validator_test, inputfile_not_regular) +TEST_F(validator_test, inputfile_not_regular) { - sharg::test::tmp_filename tmp{"my_file.test"}; - std::filesystem::path filename = tmp.get_path(); + sharg::test::tmp_filename const tmp{"my_file.test"}; + std::filesystem::path const filename = tmp.get_path(); mkfifo(filename.c_str(), 0644); EXPECT_THROW(sharg::input_file_validator{}(filename), sharg::validation_error); } -TEST(validator_test, inputdir_not_existing) +TEST_F(validator_test, inputdir_not_existing) { - sharg::test::tmp_filename tmp_name{"dir"}; - std::filesystem::path not_existing_dir{tmp_name.get_path()}; + sharg::test::tmp_filename const tmp_name{"dir"}; + std::filesystem::path const not_existing_dir{tmp_name.get_path()}; EXPECT_THROW(sharg::input_directory_validator{}(not_existing_dir), sharg::validation_error); } -TEST(validator_test, inputdir_not_readable) +TEST_F(validator_test, inputdir_not_readable) { - sharg::test::tmp_filename tmp_name{"dir"}; - std::filesystem::path tmp_dir{tmp_name.get_path()}; + sharg::test::tmp_filename const tmp_name{"dir"}; + std::filesystem::path const tmp_dir{tmp_name.get_path()}; std::filesystem::create_directory(tmp_dir); - EXPECT_NO_THROW(sharg::input_directory_validator{}(tmp_dir)); + sharg::input_directory_validator my_validator{}; + + EXPECT_NO_THROW(my_validator(tmp_dir)); std::filesystem::permissions(tmp_dir, std::filesystem::perms::owner_read | std::filesystem::perms::group_read @@ -567,7 +459,7 @@ TEST(validator_test, inputdir_not_readable) if (!sharg::test::read_access(tmp_dir)) // Do not execute with root permissions. { - EXPECT_THROW(sharg::input_directory_validator{}(tmp_dir), sharg::validation_error); + EXPECT_THROW(my_validator(tmp_dir), sharg::validation_error); } std::filesystem::permissions(tmp_dir, @@ -576,12 +468,14 @@ TEST(validator_test, inputdir_not_readable) std::filesystem::perm_options::add); } -TEST(validator_test, outputfile_not_writable) +TEST_F(validator_test, outputfile_not_writable) { - sharg::test::tmp_filename tmp_name{"my_file.test"}; - std::filesystem::path tmp_file{tmp_name.get_path()}; + sharg::test::tmp_filename const tmp_name{"my_file.test"}; + std::filesystem::path const tmp_file{tmp_name.get_path()}; - EXPECT_NO_THROW(sharg::output_file_validator{sharg::output_file_open_options::create_new}(tmp_file)); + sharg::output_file_validator my_validator{sharg::output_file_open_options::create_new}; + + EXPECT_NO_THROW(my_validator(tmp_file)); // Parent path is not writable. std::filesystem::permissions(tmp_file.parent_path(), @@ -591,8 +485,7 @@ TEST(validator_test, outputfile_not_writable) if (!sharg::test::write_access(tmp_file)) // Do not execute with root permissions. { - EXPECT_THROW(sharg::output_file_validator{sharg::output_file_open_options::create_new}(tmp_file), - sharg::validation_error); + EXPECT_THROW(my_validator(tmp_file), sharg::validation_error); } // make sure we can remove the directory. @@ -602,175 +495,127 @@ TEST(validator_test, outputfile_not_writable) std::filesystem::perm_options::add); } -TEST(validator_test, outputdir_not_writable) +TEST_F(validator_test, output_parent_dir_not_writable) { - { // parent dir is not writable. - sharg::test::tmp_filename tmp_name{"dir"}; - std::filesystem::path tmp_dir{tmp_name.get_path()}; - - EXPECT_NO_THROW(sharg::output_file_validator{sharg::output_file_open_options::create_new}(tmp_dir)); - EXPECT_FALSE(std::filesystem::exists(tmp_dir)); - - // parent dir does not exist - sharg::test::tmp_filename tmp_child_name{"dir/child_dir"}; - std::filesystem::path tmp_child_dir{tmp_child_name.get_path()}; - std::filesystem::path tmp_parent_dir{tmp_child_dir.parent_path()}; - - EXPECT_THROW(sharg::output_directory_validator{}(tmp_child_dir), sharg::validation_error); - - // Directory exists but is not writable. - std::filesystem::create_directory(tmp_dir); - std::filesystem::permissions(tmp_dir, - std::filesystem::perms::owner_write | std::filesystem::perms::group_write - | std::filesystem::perms::others_write, - std::filesystem::perm_options::remove); - - EXPECT_TRUE(std::filesystem::exists(tmp_dir)); - if (!sharg::test::write_access(tmp_dir)) // Do not execute with root permissions. - { - EXPECT_THROW(sharg::output_directory_validator{}(tmp_dir), sharg::validation_error); - } - - // Parent path is not writable. - std::filesystem::permissions(tmp_dir.parent_path(), - std::filesystem::perms::owner_write | std::filesystem::perms::group_write - | std::filesystem::perms::others_write, - std::filesystem::perm_options::remove); - - if (!sharg::test::write_access(tmp_dir)) // Do not execute with root permissions. - { - EXPECT_THROW(sharg::output_file_validator{sharg::output_file_open_options::create_new}(tmp_dir), - sharg::validation_error); - } - - // make sure we can remove the directories. - std::filesystem::permissions(tmp_dir, - std::filesystem::perms::owner_write | std::filesystem::perms::group_write - | std::filesystem::perms::others_write, - std::filesystem::perm_options::add); - std::filesystem::permissions(tmp_dir.parent_path(), - std::filesystem::perms::owner_write | std::filesystem::perms::group_write - | std::filesystem::perms::others_write, - std::filesystem::perm_options::add); + sharg::test::tmp_filename const tmp_name{"dir"}; + std::filesystem::path const tmp_dir{tmp_name.get_path()}; + + sharg::output_file_validator my_validator{}; + + // Directory exists but is not writable. + std::filesystem::create_directory(tmp_dir); + std::filesystem::permissions(tmp_dir, + std::filesystem::perms::owner_write | std::filesystem::perms::group_write + | std::filesystem::perms::others_write, + std::filesystem::perm_options::remove); + + EXPECT_TRUE(std::filesystem::exists(tmp_dir)); + if (!sharg::test::write_access(tmp_dir)) // Do not execute with root permissions. + { + EXPECT_THROW(my_validator(tmp_dir), sharg::validation_error); } - { // this dir is not writable - sharg::test::tmp_filename tmp_name{"dir"}; - std::filesystem::path tmp_dir{tmp_name.get_path()}; - - std::filesystem::create_directory(tmp_dir); - EXPECT_NO_THROW(sharg::output_directory_validator{}(tmp_dir)); - - // This path exists but is not writable. - std::filesystem::permissions(tmp_dir, - std::filesystem::perms::owner_write | std::filesystem::perms::group_write - | std::filesystem::perms::others_write, - std::filesystem::perm_options::remove); - - if (!sharg::test::write_access(tmp_dir)) // Do not execute with root permissions. - { - EXPECT_THROW(sharg::output_file_validator{sharg::output_file_open_options::create_new}(tmp_dir), - sharg::validation_error); - } - - // make sure we can remove the directory. - std::filesystem::permissions(tmp_dir, - std::filesystem::perms::owner_write | std::filesystem::perms::group_write - | std::filesystem::perms::others_write, - std::filesystem::perm_options::add); + // Parent path is not writable. + std::filesystem::permissions(tmp_dir.parent_path(), + std::filesystem::perms::owner_write | std::filesystem::perms::group_write + | std::filesystem::perms::others_write, + std::filesystem::perm_options::remove); + + if (!sharg::test::write_access(tmp_dir)) // Do not execute with root permissions. + { + EXPECT_THROW(my_validator(tmp_dir), sharg::validation_error); } + + // make sure we can remove the directories. + std::filesystem::permissions(tmp_dir, + std::filesystem::perms::owner_write | std::filesystem::perms::group_write + | std::filesystem::perms::others_write, + std::filesystem::perm_options::add); + std::filesystem::permissions(tmp_dir.parent_path(), + std::filesystem::perms::owner_write | std::filesystem::perms::group_write + | std::filesystem::perms::others_write, + std::filesystem::perm_options::add); } -TEST(validator_test, arithmetic_range_validator_success) +TEST_F(validator_test, outputdir_not_writable) { - int option_value{0}; - std::vector option_vector{}; + sharg::test::tmp_filename const tmp_name{"dir"}; + std::filesystem::path const tmp_dir{tmp_name.get_path()}; - // option - char const * argv[] = {"./parser_test", "-i", "10"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option(option_value, - sharg::config{.short_id = 'i', .validator = sharg::arithmetic_range_validator{1, 20}}); + std::filesystem::create_directory(tmp_dir); + + EXPECT_NO_THROW(sharg::output_directory_validator{}(tmp_dir)); + + // This path exists but is not writable. + std::filesystem::permissions(tmp_dir, + std::filesystem::perms::owner_write | std::filesystem::perms::group_write + | std::filesystem::perms::others_write, + std::filesystem::perm_options::remove); + + if (!sharg::test::write_access(tmp_dir)) // Do not execute with root permissions. + { + EXPECT_THROW(sharg::output_file_validator{}(tmp_dir), sharg::validation_error); + } - testing::internal::CaptureStderr(); + // make sure we can remove the directory. + std::filesystem::permissions(tmp_dir, + std::filesystem::perms::owner_write | std::filesystem::perms::group_write + | std::filesystem::perms::others_write, + std::filesystem::perm_options::add); +} + +TEST_F(validator_test, arithmetic_range_validator_success) +{ + int value{}; + double value2{}; + std::vector vector{}; + + // option + auto parser = get_parser("-i", "10"); + parser.add_option(value, sharg::config{.short_id = 'i', .validator = sharg::arithmetic_range_validator{1, 20}}); EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, 10); + EXPECT_EQ(value, 10); // option - negative values - char const * argv2[] = {"./parser_test", "-i", "-10"}; - sharg::parser parser2{"test_parser", 3, argv2, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser2, 80); - parser2.add_option(option_value, - sharg::config{.short_id = 'i', .validator = sharg::arithmetic_range_validator{-20, 20}}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser2.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, -10); + parser = get_parser("-i", "-10"); + parser.add_option(value, sharg::config{.short_id = 'i', .validator = sharg::arithmetic_range_validator{-20, 20}}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(value, -10); // positional option - char const * argv3[] = {"./parser_test", "10"}; - sharg::parser parser3{"test_parser", 2, argv3, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser3, 80); - parser3.add_positional_option(option_value, sharg::config{.validator = sharg::arithmetic_range_validator{1, 20}}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser3.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, 10); + parser = get_parser("10"); + parser.add_positional_option(value, sharg::config{.validator = sharg::arithmetic_range_validator{1, 20}}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(value, 10); // positional option - negative values - char const * argv4[] = {"./parser_test", "--", "-10"}; - sharg::parser parser4{"test_parser", 3, argv4, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser4, 80); - parser4.add_positional_option(option_value, sharg::config{.validator = sharg::arithmetic_range_validator{-20, 20}}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser4.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, -10); + parser = get_parser("--", "-10"); + parser.add_positional_option(value, sharg::config{.validator = sharg::arithmetic_range_validator{-20, 20}}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(value, -10); // option - vector - char const * argv5[] = {"./parser_test", "-i", "-10", "-i", "48"}; - sharg::parser parser5{"test_parser", 5, argv5, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser5, 80); - parser5.add_option(option_vector, - sharg::config{.short_id = 'i', .validator = sharg::arithmetic_range_validator{-50, 50}}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser5.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_vector[0], -10); - EXPECT_EQ(option_vector[1], 48); + parser = get_parser("-i", "-10", "-i", "48"); + parser.add_option(vector, sharg::config{.short_id = 'i', .validator = sharg::arithmetic_range_validator{-50, 50}}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(vector.size(), 2u); + EXPECT_EQ(vector[0], -10); + EXPECT_EQ(vector[1], 48); // positional option - vector - option_vector.clear(); - char const * argv6[] = {"./parser_test", "--", "-10", "1"}; - sharg::parser parser6{"test_parser", 4, argv6, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser6, 80); - parser6.add_positional_option(option_vector, - sharg::config{.validator = sharg::arithmetic_range_validator{-20, 20}}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser6.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_vector[0], -10); - EXPECT_EQ(option_vector[1], 1); + parser = get_parser("--", "-10", "1"); + parser.add_positional_option(vector, sharg::config{.validator = sharg::arithmetic_range_validator{-20, 20}}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(vector.size(), 2u); + EXPECT_EQ(vector[0], -10); + EXPECT_EQ(vector[1], 1); // get help page message - option_vector.clear(); - char const * argv7[] = {"./parser_test", "-h"}; - sharg::parser parser7{"test_parser", 2, argv7, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser7, 80); - parser7.add_positional_option( - option_vector, + vector.clear(); + parser = get_parser("-h"); + parser.add_positional_option( + vector, sharg::config{.description = "desc", .validator = sharg::arithmetic_range_validator{-20, 20}}); - - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser7.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std::string my_stdout = testing::internal::GetCapturedStdout(); std::string expected = std::string("test_parser\n" "===========\n" "\n" @@ -779,89 +624,65 @@ TEST(validator_test, arithmetic_range_validator_success) " desc Default: []. Value must be in range [-20,20].\n" "\nOPTIONS\n\n" + basic_options_str + "\n" + basic_version_str); - EXPECT_EQ(my_stdout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); // option - double value - double double_option_value; - char const * argv8[] = {"./parser_test", "-i", "10.9"}; - sharg::parser parser8{"test_parser", 3, argv8, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser8, 80); - parser8.add_option(double_option_value, - sharg::config{.short_id = 'i', .validator = sharg::arithmetic_range_validator{1, 20}}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser8.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_FLOAT_EQ(double_option_value, 10.9); + parser = get_parser("-i", "10.9"); + parser.add_option(value2, sharg::config{.short_id = 'i', .validator = sharg::arithmetic_range_validator{1, 20}}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_FLOAT_EQ(value2, 10.9); } -TEST(validator_test, arithmetic_range_validator_error) +TEST_F(validator_test, arithmetic_range_validator_error) { - int option_value; - std::vector option_vector; + int value{}; + double value2{}; + std::vector vector{}; // option - above max - char const * argv[] = {"./parser_test", "-i", "30"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option(option_value, - sharg::config{.short_id = 'i', .validator = sharg::arithmetic_range_validator{1, 20}}); - + auto parser = get_parser("-i", "30"); + parser.add_option(value, sharg::config{.short_id = 'i', .validator = sharg::arithmetic_range_validator{1, 20}}); EXPECT_THROW(parser.parse(), sharg::validation_error); + EXPECT_EQ(value, 30); // option - below min - char const * argv2[] = {"./parser_test", "-i", "-21"}; - sharg::parser parser2{"test_parser", 3, argv2, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser2, 80); - parser2.add_option(option_value, - sharg::config{.short_id = 'i', .validator = sharg::arithmetic_range_validator{-20, 20}}); - - EXPECT_THROW(parser2.parse(), sharg::validation_error); + parser = get_parser("-i", "-21"); + parser.add_option(value, sharg::config{.short_id = 'i', .validator = sharg::arithmetic_range_validator{-20, 20}}); + EXPECT_THROW(parser.parse(), sharg::validation_error); + EXPECT_EQ(value, -21); // positional option - above max - char const * argv3[] = {"./parser_test", "30"}; - sharg::parser parser3{"test_parser", 2, argv3, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser3, 80); - parser3.add_positional_option(option_value, sharg::config{.validator = sharg::arithmetic_range_validator{1, 20}}); - - EXPECT_THROW(parser3.parse(), sharg::validation_error); + parser = get_parser("30"); + parser.add_positional_option(value, sharg::config{.validator = sharg::arithmetic_range_validator{1, 20}}); + EXPECT_THROW(parser.parse(), sharg::validation_error); + EXPECT_EQ(value, 30); // positional option - below min - char const * argv4[] = {"./parser_test", "--", "-21"}; - sharg::parser parser4{"test_parser", 3, argv4, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser4, 80); - parser4.add_positional_option(option_value, sharg::config{.validator = sharg::arithmetic_range_validator{-20, 20}}); - - EXPECT_THROW(parser4.parse(), sharg::validation_error); + parser = get_parser("--", "-21"); + parser.add_positional_option(value, sharg::config{.validator = sharg::arithmetic_range_validator{-20, 20}}); + EXPECT_THROW(parser.parse(), sharg::validation_error); + EXPECT_EQ(value, -21); // option - vector - char const * argv5[] = {"./parser_test", "-i", "-100"}; - sharg::parser parser5{"test_parser", 3, argv5, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser5, 80); - parser5.add_option(option_vector, - sharg::config{.short_id = 'i', .validator = sharg::arithmetic_range_validator{-50, 50}}); - - EXPECT_THROW(parser5.parse(), sharg::validation_error); + parser = get_parser("-i", "-100"); + parser.add_option(vector, sharg::config{.short_id = 'i', .validator = sharg::arithmetic_range_validator{-50, 50}}); + EXPECT_THROW(parser.parse(), sharg::validation_error); + EXPECT_TRUE(vector.size() == 1u); + EXPECT_EQ(vector[0], -100); // positional option - vector - option_vector.clear(); - char const * argv6[] = {"./parser_test", "--", "-10", "100"}; - sharg::parser parser6{"test_parser", 4, argv6, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser6, 80); - parser6.add_positional_option(option_vector, - sharg::config{.validator = sharg::arithmetic_range_validator{-20, 20}}); - - EXPECT_THROW(parser6.parse(), sharg::validation_error); + parser = get_parser("--", "-10", "100"); + parser.add_positional_option(vector, sharg::config{.validator = sharg::arithmetic_range_validator{-20, 20}}); + EXPECT_THROW(parser.parse(), sharg::validation_error); + EXPECT_TRUE(vector.size() == 2u); + EXPECT_EQ(vector[0], -10); + EXPECT_EQ(vector[1], 100); // option - double value - double double_option_value; - char const * argv7[] = {"./parser_test", "-i", "0.9"}; - sharg::parser parser7{"test_parser", 3, argv7, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser7, 80); - parser7.add_option(double_option_value, - sharg::config{.short_id = 'i', .validator = sharg::arithmetic_range_validator{1, 20}}); - - EXPECT_THROW(parser7.parse(), sharg::validation_error); + parser = get_parser("-i", "0.9"); + parser.add_option(value2, sharg::config{.short_id = 'i', .validator = sharg::arithmetic_range_validator{1, 20}}); + EXPECT_THROW(parser.parse(), sharg::validation_error); + EXPECT_FLOAT_EQ(value2, 0.9); } enum class foo @@ -876,592 +697,439 @@ auto enumeration_names(foo) return std::unordered_map{{"one", foo::one}, {"two", foo::two}, {"three", foo::three}}; } -TEST(validator_test, value_list_validator_success) +TEST_F(validator_test, value_list_validator_success) { // type deduction // -------------- // all arithmetic types are deduced to their common type in order to easily allow chaining of arithmetic validators - EXPECT_TRUE((std::same_as, decltype(sharg::value_list_validator{1})>)); + EXPECT_SAME_TYPE(sharg::value_list_validator, decltype(sharg::value_list_validator{1})); + // except char - EXPECT_TRUE((std::same_as, decltype(sharg::value_list_validator{'c'})>)); + EXPECT_SAME_TYPE(sharg::value_list_validator, decltype(sharg::value_list_validator{'c'})); + // The same holds for a range of arithmetic types std::vector v{1, 2, 3}; - EXPECT_TRUE((std::same_as, decltype(sharg::value_list_validator{v})>)); - EXPECT_TRUE((std::same_as, - decltype(sharg::value_list_validator{v | std::views::take(2)})>)); - std::vector v_char{'1', '2', '3'}; - EXPECT_TRUE((std::same_as, decltype(sharg::value_list_validator{v_char})>)); - EXPECT_TRUE((std::same_as, - decltype(sharg::value_list_validator{v_char | std::views::take(2)})>)); + EXPECT_SAME_TYPE(sharg::value_list_validator, decltype(sharg::value_list_validator{v})); + EXPECT_SAME_TYPE(sharg::value_list_validator, decltype(sharg::value_list_validator{v | std::views::take(2)})); + + std::vector v2{'1', '2', '3'}; + EXPECT_SAME_TYPE(sharg::value_list_validator, decltype(sharg::value_list_validator{v2})); + EXPECT_SAME_TYPE(sharg::value_list_validator, + decltype(sharg::value_list_validator{v2 | std::views::take(2)})); + // const char * is deduced to std::string - std::vector v2{"ha", "ba", "ma"}; - EXPECT_TRUE((std::same_as, decltype(sharg::value_list_validator{"ha"})>)); - EXPECT_TRUE((std::same_as, - decltype(sharg::value_list_validator{"ha", "ba", "ma"})>)); - EXPECT_TRUE((std::same_as, decltype(sharg::value_list_validator{v2})>)); - EXPECT_TRUE((std::same_as, - decltype(sharg::value_list_validator{v2 | std::views::take(2)})>)); + std::vector v3{"ha", "ba", "ma"}; + EXPECT_SAME_TYPE(sharg::value_list_validator, decltype(sharg::value_list_validator{"ha"})); + EXPECT_SAME_TYPE(sharg::value_list_validator, decltype(sharg::value_list_validator{"ha", "ba", "ma"})); + EXPECT_SAME_TYPE(sharg::value_list_validator, decltype(sharg::value_list_validator{v3})); + EXPECT_SAME_TYPE(sharg::value_list_validator, + decltype(sharg::value_list_validator{v3 | std::views::take(2)})); + // custom types are used as is - EXPECT_TRUE( - (std::same_as, decltype(sharg::value_list_validator{foo::one, foo::two})>)); + EXPECT_SAME_TYPE(sharg::value_list_validator, decltype(sharg::value_list_validator{foo::one, foo::two})); // usage // ----- - std::string option_value; - int option_value_int; - std::vector option_vector; - std::vector option_vector_int; + std::string str_value{}; + int int_value{}; + std::vector str_vector{}; + std::vector int_vector{}; + std::vector const valid_str_values{"ha", "ba", "ma"}; // option - std::vector valid_str_values{"ha", "ba", "ma"}; - char const * argv[] = {"./parser_test", "-s", "ba"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option(option_value, + auto parser = get_parser("-s", "ba"); + parser.add_option(str_value, sharg::config{.short_id = 's', .validator = sharg::value_list_validator{valid_str_values | std::views::take(2)}}); - - testing::internal::CaptureStderr(); EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, "ba"); + EXPECT_EQ(str_value, "ba"); // option with integers - char const * argv2[] = {"./parser_test", "-i", "-21"}; - sharg::parser parser2{"test_parser", 3, argv2, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser2, 80); - parser2.add_option(option_value_int, - sharg::config{.short_id = 'i', .validator = sharg::value_list_validator{0, -21, 10}}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser2.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value_int, -21); + parser = get_parser("-i", "-21"); + parser.add_option(int_value, + sharg::config{.short_id = 'i', .validator = sharg::value_list_validator{0, -21, 10}}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(int_value, -21); // positional option - char const * argv3[] = {"./parser_test", "ma"}; - sharg::parser parser3{"test_parser", 2, argv3, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser3, 80); - parser3.add_positional_option(option_value, - sharg::config{.validator = sharg::value_list_validator{valid_str_values}}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser3.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, "ma"); + parser = get_parser("ma"); + parser.add_positional_option(str_value, sharg::config{.validator = sharg::value_list_validator{valid_str_values}}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(str_value, "ma"); // positional option - vector - char const * argv4[] = {"./parser_test", "ha", "ma"}; - sharg::parser parser4{"test_parser", 3, argv4, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser4, 80); - parser4.add_positional_option(option_vector, - sharg::config{.validator = sharg::value_list_validator{"ha", "ba", "ma"}}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser4.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_vector[0], "ha"); - EXPECT_EQ(option_vector[1], "ma"); + parser = get_parser("ha", "ma"); + parser.add_positional_option(str_vector, sharg::config{.validator = sharg::value_list_validator{"ha", "ba", "ma"}}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(str_vector.size(), 2u); + EXPECT_EQ(str_vector[0], "ha"); + EXPECT_EQ(str_vector[1], "ma"); // option - vector - char const * argv5[] = {"./parser_test", "-i", "-10", "-i", "48"}; - sharg::parser parser5{"test_parser", 5, argv5, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser5, 80); - parser5.add_option(option_vector_int, - sharg::config{.short_id = 'i', .validator = sharg::value_list_validator{-10, 48, 50}}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser5.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_vector_int[0], -10); - EXPECT_EQ(option_vector_int[1], 48); + parser = get_parser("-i", "-10", "-i", "48"); + parser.add_option(int_vector, + sharg::config{.short_id = 'i', .validator = sharg::value_list_validator{-10, 48, 50}}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(int_vector.size(), 2u); + EXPECT_EQ(int_vector[0], -10); + EXPECT_EQ(int_vector[1], 48); // get help page message - option_vector_int.clear(); - char const * argv7[] = {"./parser_test", "-h"}; - sharg::parser parser7{"test_parser", 2, argv7, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser7, 80); - parser7.add_option(option_vector_int, - sharg::config{.short_id = 'i', - .long_id = "int-option", - .description = "desc", - .validator = sharg::value_list_validator{-10, 48, 50}}); - - option_vector_int.clear(); - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser7.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std::string my_stdout = testing::internal::GetCapturedStdout(); + int_vector.clear(); + parser = get_parser("-h"); + parser.add_option(int_vector, + sharg::config{.short_id = 'i', + .long_id = "int-option", + .description = "desc", + .validator = sharg::value_list_validator{-10, 48, 50}}); std::string expected = std::string("test_parser\n" "===========\n" "\nOPTIONS\n" " -i, --int-option (List of signed 32 bit integer)\n" " desc Default: []. Value must be one of [-10, 48, 50].\n\n" + basic_options_str + "\n" + basic_version_str); - EXPECT_EQ(my_stdout, expected); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(validator_test, value_list_validator_error) +TEST_F(validator_test, value_list_validator_error) { - std::string option_value; - int option_value_int; - std::vector option_vector; - std::vector option_vector_int; + std::string str_value{}; + int int_value{}; + std::vector str_vector{}; + std::vector int_vector{}; // option - char const * argv[] = {"./parser_test", "-s", "sa"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option(option_value, + auto parser = get_parser("-s", "sa"); + parser.add_option(str_value, sharg::config{.short_id = 's', .validator = sharg::value_list_validator{"ha", "ba", "ma"}}); - EXPECT_THROW(parser.parse(), sharg::validation_error); + EXPECT_EQ(str_value, "sa"); // positional option - char const * argv3[] = {"./parser_test", "30"}; - sharg::parser parser3{"test_parser", 2, argv3, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser3, 80); - parser3.add_positional_option(option_value_int, sharg::config{.validator = sharg::value_list_validator{0, 5, 10}}); - - EXPECT_THROW(parser3.parse(), sharg::validation_error); + parser = get_parser("30"); + parser.add_positional_option(int_value, sharg::config{.validator = sharg::value_list_validator{0, 5, 10}}); + EXPECT_THROW(parser.parse(), sharg::validation_error); + EXPECT_EQ(int_value, 30); // positional option - vector - char const * argv4[] = {"./parser_test", "fo", "ma"}; - sharg::parser parser4{"test_parser", 3, argv4, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser4, 80); - parser4.add_positional_option(option_vector, - sharg::config{.validator = sharg::value_list_validator{"ha", "ba", "ma"}}); - - EXPECT_THROW(parser4.parse(), sharg::validation_error); + parser = get_parser("fo", "ma"); + parser.add_positional_option(str_vector, sharg::config{.validator = sharg::value_list_validator{"ha", "ba", "ma"}}); + EXPECT_THROW(parser.parse(), sharg::validation_error); + EXPECT_EQ(str_vector.size(), 2u); + EXPECT_EQ(str_vector[0], "fo"); + EXPECT_EQ(str_vector[1], "ma"); // option - vector - char const * argv5[] = {"./parser_test", "-i", "-10", "-i", "488"}; - sharg::parser parser5{"test_parser", 5, argv5, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser5, 80); - parser5.add_option(option_vector_int, - sharg::config{.short_id = 'i', .validator = sharg::value_list_validator{-10, 48, 50}}); - - EXPECT_THROW(parser5.parse(), sharg::validation_error); + parser = get_parser("-i", "-10", "-i", "488"); + parser.add_option(int_vector, + sharg::config{.short_id = 'i', .validator = sharg::value_list_validator{-10, 48, 50}}); + EXPECT_THROW(parser.parse(), sharg::validation_error); + EXPECT_EQ(int_vector.size(), 2u); + EXPECT_EQ(int_vector[0], -10); + EXPECT_EQ(int_vector[1], 488); } // https://github.com/seqan/sharg-parser/issues/178 -TEST(validator_test, value_list_validator_issue178) +TEST_F(validator_test, value_list_validator_issue178) { - std::filesystem::path option_value; - std::vector option_vector; + std::filesystem::path value{}; + std::vector vector{}; constexpr std::array valid_values{"ha", "ba", "ma"}; // option - char const * argv[] = {"./parser_test", "-s", "ba"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option(option_value, - sharg::config{.short_id = 's', .validator = sharg::value_list_validator{valid_values}}); - - testing::internal::CaptureStderr(); + auto parser = get_parser("-s", "ba"); + parser.add_option(value, sharg::config{.short_id = 's', .validator = sharg::value_list_validator{valid_values}}); EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, "ba"); + EXPECT_EQ(value, "ba"); // option - vector - char const * argv2[] = {"./parser_test", "-s", "ha", "-s", "ba"}; - sharg::parser parser2{"test_parser", 5, argv2, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser2, 80); - parser2.add_option(option_vector, - sharg::config{.short_id = 's', .validator = sharg::value_list_validator{valid_values}}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser2.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_vector[0], "ha"); - EXPECT_EQ(option_vector[1], "ba"); + parser = get_parser("-s", "ha", "-s", "ba"); + parser.add_option(vector, sharg::config{.short_id = 's', .validator = sharg::value_list_validator{valid_values}}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(vector.size(), 2u); + EXPECT_EQ(vector[0], "ha"); + EXPECT_EQ(vector[1], "ba"); } -TEST(validator_test, regex_validator_success) +TEST_F(validator_test, regex_validator_success) { - std::string option_value; - std::vector option_vector; + std::string value{}; + std::filesystem::path path{}; + std::vector vector{}; sharg::regex_validator email_validator("[a-zA-Z]+@[a-zA-Z]+\\.com"); sharg::regex_validator email_vector_validator("[a-zA-Z]+@[a-zA-Z]+\\.com"); - { // option - char const * argv[] = {"./parser_test", "-s", "ballo@rollo.com"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option(option_value, sharg::config{.short_id = 's', .validator = email_validator}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, "ballo@rollo.com"); - } - - { // positional option - char const * argv[] = {"./parser_test", "chr1"}; - sharg::parser parser{"test_parser", 2, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_positional_option(option_value, sharg::config{.validator = sharg::regex_validator{"^chr[0-9]+"}}); + // option + auto parser = get_parser("-s", "ballo@rollo.com"); + parser.add_option(value, sharg::config{.short_id = 's', .validator = email_validator}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(value, "ballo@rollo.com"); - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, "chr1"); - } + // positional option + parser = get_parser("chr1"); + parser.add_positional_option(value, sharg::config{.validator = sharg::regex_validator{"^chr[0-9]+"}}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(value, "chr1"); - { // positional option - vector - char const * argv[] = {"./parser_test", "rollo", "bollo", "lollo"}; - sharg::parser parser{"test_parser", 4, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_positional_option(option_vector, sharg::config{.validator = sharg::regex_validator{".*oll.*"}}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_vector[0], "rollo"); - EXPECT_EQ(option_vector[1], "bollo"); - EXPECT_EQ(option_vector[2], "lollo"); - } + // positional option - vector + parser = get_parser("rollo", "bollo", "lollo"); + parser.add_positional_option(vector, sharg::config{.validator = sharg::regex_validator{".*oll.*"}}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(vector.size(), 3u); + EXPECT_EQ(vector[0], "rollo"); + EXPECT_EQ(vector[1], "bollo"); + EXPECT_EQ(vector[2], "lollo"); - { // option - vector - option_vector.clear(); - char const * argv[] = {"./parser_test", "-s", "rita@rambo.com", "-s", "tina@rambo.com"}; - sharg::parser parser{"test_parser", 5, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option(option_vector, sharg::config{.short_id = 's', .validator = email_vector_validator}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_vector[0], "rita@rambo.com"); - EXPECT_EQ(option_vector[1], "tina@rambo.com"); - } + // option - vector + parser = get_parser("-s", "rita@rambo.com", "-s", "tina@rambo.com"); + parser.add_option(vector, sharg::config{.short_id = 's', .validator = email_vector_validator}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(vector.size(), 2u); + EXPECT_EQ(vector[0], "rita@rambo.com"); + EXPECT_EQ(vector[1], "tina@rambo.com"); - { // option - std::filesystem::path - std::filesystem::path path_option; - char const * argv[] = {"./parser_test", "-s", "rita@rambo.com"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option(path_option, sharg::config{.short_id = 's', .validator = email_vector_validator}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(path_option, "rita@rambo.com"); - } + // option - std::filesystem::path + parser = get_parser("-s", "rita@rambo.com"); + parser.add_option(path, sharg::config{.short_id = 's', .validator = email_vector_validator}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(path, "rita@rambo.com"); - { // get help page message - option_vector.clear(); - char const * argv[] = {"./parser_test", "-h"}; - sharg::parser parser{"test_parser", 2, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option(option_vector, - sharg::config{.short_id = 's', - .long_id = "string-option", - .description = "desc", - .validator = email_vector_validator}); - - option_vector.clear(); - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std::string my_stdout = testing::internal::GetCapturedStdout(); - std::string expected = std::string("test_parser\n" - "===========\n" - "\nOPTIONS\n" - " -s, --string-option (List of std::string)\n" - " desc Default: []. Value must match the pattern\n" - " '[a-zA-Z]+@[a-zA-Z]+\\.com'.\n" - "\n" - + basic_options_str + "\n" + basic_version_str); - EXPECT_EQ(my_stdout, expected); - } + // get help page message + vector.clear(); + parser = get_parser("-h"); + parser.add_option(vector, + sharg::config{.short_id = 's', + .long_id = "string-option", + .description = "desc", + .validator = email_vector_validator}); + std::string expected = std::string("test_parser\n" + "===========\n" + "\nOPTIONS\n" + " -s, --string-option (List of std::string)\n" + " desc Default: []. Value must match the pattern\n" + " '[a-zA-Z]+@[a-zA-Z]+\\.com'.\n" + "\n" + + basic_options_str + "\n" + basic_version_str); + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); } -TEST(validator_test, regex_validator_error) +TEST_F(validator_test, regex_validator_error) { - std::string option_value; - std::vector option_vector; + std::string value{}; + std::vector vector{}; // option - char const * argv[] = {"./parser_test", "--string-option", "sally"}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option(option_value, - sharg::config{.long_id = "string-option", .validator = sharg::regex_validator{"tt"}}); - + auto parser = get_parser("--string-option", "sally"); + parser.add_option(value, sharg::config{.long_id = "string-option", .validator = sharg::regex_validator{"tt"}}); EXPECT_THROW(parser.parse(), sharg::validation_error); + EXPECT_EQ(value, "sally"); // positional option - char const * argv2[] = {"./parser_test", "jessy"}; - sharg::parser parser2{"test_parser", 2, argv2, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser2, 80); - parser2.add_positional_option(option_value, sharg::config{.validator = sharg::regex_validator{"[0-9]"}}); - - EXPECT_THROW(parser2.parse(), sharg::validation_error); + parser = get_parser("jessy"); + parser.add_positional_option(value, sharg::config{.validator = sharg::regex_validator{"[0-9]"}}); + EXPECT_THROW(parser.parse(), sharg::validation_error); + EXPECT_EQ(value, "jessy"); // positional option - vector - char const * argv3[] = {"./parser_test", "rollo", "bttllo", "lollo"}; - sharg::parser parser3{"test_parser", 4, argv3, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser3, 80); - parser3.add_positional_option(option_vector, sharg::config{.validator = sharg::regex_validator{".*oll.*"}}); - - EXPECT_THROW(parser3.parse(), sharg::validation_error); + parser = get_parser("rollo", "bttllo", "lollo"); + parser.add_positional_option(vector, sharg::config{.validator = sharg::regex_validator{".*oll.*"}}); + EXPECT_THROW(parser.parse(), sharg::validation_error); + EXPECT_EQ(vector.size(), 3u); + EXPECT_EQ(vector[0], "rollo"); + EXPECT_EQ(vector[1], "bttllo"); + EXPECT_EQ(vector[2], "lollo"); // option - vector - option_vector.clear(); - char const * argv4[] = {"./parser_test", "-s", "gh", "-s", "tt"}; - sharg::parser parser4{"test_parser", 5, argv4, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser4, 80); - parser4.add_option(option_vector, sharg::config{.short_id = 's', .validator = sharg::regex_validator{"tt"}}); - - EXPECT_THROW(parser4.parse(), sharg::validation_error); + parser = get_parser("-s", "gh", "-s", "tt"); + parser.add_option(vector, sharg::config{.short_id = 's', .validator = sharg::regex_validator{"tt"}}); + EXPECT_THROW(parser.parse(), sharg::validation_error); + EXPECT_EQ(vector.size(), 2u); + EXPECT_EQ(vector[0], "gh"); + EXPECT_EQ(vector[1], "tt"); } -TEST(validator_test, chaining_validators_common_type) +TEST_F(validator_test, chaining_validators_common_type) { // chaining integral options stay integral - { - int max_int = std::numeric_limits::max(); - std::vector v_int{1, 2, 3, max_int}; - std::vector v_unsigned{4u, static_cast(max_int)}; - - EXPECT_TRUE((std::same_as, decltype(v_int)>)); - EXPECT_TRUE((std::same_as, decltype(v_unsigned)>)); + constexpr int max_int = std::numeric_limits::max(); - sharg::value_list_validator validator_int{v_int}; - sharg::value_list_validator validator_unsigned{v_unsigned}; + std::vector v_int{1, 2, 3, max_int}; + EXPECT_SAME_TYPE(std::vector, decltype(v_int)); - EXPECT_TRUE((std::same_as, decltype(validator_int)>)); - EXPECT_TRUE((std::same_as)); + std::vector v_unsigned{4u, static_cast(max_int)}; + EXPECT_SAME_TYPE(std::vector, decltype(v_unsigned)); - EXPECT_TRUE((std::same_as, decltype(validator_unsigned)>)); - EXPECT_TRUE((std::same_as)); + sharg::value_list_validator validator_int{v_int}; + EXPECT_SAME_TYPE(sharg::value_list_validator, decltype(validator_int)); + EXPECT_SAME_TYPE(int, decltype(validator_int)::option_value_type); - auto validator = validator_int | validator_unsigned; + sharg::value_list_validator validator_unsigned{v_unsigned}; + EXPECT_SAME_TYPE(sharg::value_list_validator, decltype(validator_unsigned)); + EXPECT_SAME_TYPE(unsigned, decltype(validator_unsigned)::option_value_type); - EXPECT_TRUE((std::same_as>)); - EXPECT_TRUE((std::same_as)); + auto validator = validator_int | validator_unsigned; + EXPECT_SAME_TYPE(unsigned, std::common_type_t); + EXPECT_SAME_TYPE(unsigned, decltype(validator)::option_value_type); - // max_int is part of both validators - EXPECT_NO_THROW(validator_int(max_int)); - EXPECT_NO_THROW(validator_unsigned(max_int)); - EXPECT_NO_THROW(validator(max_int)); - } + // max_int is part of both validators + EXPECT_NO_THROW(validator_int(max_int)); + EXPECT_NO_THROW(validator_unsigned(max_int)); + EXPECT_NO_THROW(validator(max_int)); // chaining mixed arithmetic options will be highest common arithmetic type - { - // note: this number is not representable by double and multiple integer values represent the same double value - int64_t max_int64 = std::numeric_limits::max(); - EXPECT_EQ(static_cast(max_int64), static_cast(max_int64 - 1)); - - std::vector v_int64{1, 2, 3, max_int64}; - std::vector v_uint64{4u, static_cast(max_int64)}; - std::vector v_double{4.0, static_cast(max_int64)}; - - sharg::value_list_validator validator_int64{v_int64}; - sharg::value_list_validator validator_uint64{v_uint64}; - sharg::value_list_validator validator_double{v_double}; - - EXPECT_TRUE((std::same_as, decltype(validator_int64)>)); - EXPECT_TRUE((std::same_as)); - - EXPECT_TRUE((std::same_as, decltype(validator_uint64)>)); - EXPECT_TRUE((std::same_as)); - - EXPECT_TRUE((std::same_as, decltype(validator_double)>)); - EXPECT_TRUE((std::same_as)); - - auto validator = validator_int64 | validator_uint64 | validator_double; - - EXPECT_TRUE((std::same_as>)); - EXPECT_TRUE((std::same_as)); - - // max_int64 is an exact match for the two integral validators - // note: double will decay the integer to a smaller double value, - // but this is consistent, because it is the same given value - // note: chained validator passes the value as it is through, - // so validator_[u]int64 will be called with the integer value - EXPECT_NO_THROW(validator_int64(max_int64)); - EXPECT_NO_THROW(validator_uint64(max_int64)); - EXPECT_NO_THROW(validator_double(max_int64)); - EXPECT_NO_THROW(validator(max_int64)); - - // integers have exact match - // note: double accepts that value, even though it is not within that list. - EXPECT_THROW(validator_int64(max_int64 - 1), sharg::validation_error); - EXPECT_THROW(validator_uint64(max_int64 - 1), sharg::validation_error); - EXPECT_NO_THROW(validator_double(max_int64 - 1)); - EXPECT_THROW(validator(max_int64 - 1), sharg::validation_error); - } + constexpr int64_t max_int64 = std::numeric_limits::max(); + + // note: this number is not representable by double and multiple integer values represent the same double value + EXPECT_EQ(static_cast(max_int64), static_cast(max_int64 - 1)); + + std::vector v_int64{1, 2, 3, max_int64}; + sharg::value_list_validator validator_int64{v_int64}; + EXPECT_SAME_TYPE(sharg::value_list_validator, decltype(validator_int64)); + EXPECT_SAME_TYPE(int64_t, decltype(validator_int64)::option_value_type); + + std::vector v_uint64{4u, static_cast(max_int64)}; + sharg::value_list_validator validator_uint64{v_uint64}; + EXPECT_SAME_TYPE(sharg::value_list_validator, decltype(validator_uint64)); + EXPECT_SAME_TYPE(uint64_t, decltype(validator_uint64)::option_value_type); + + std::vector v_double{4.0, static_cast(max_int64)}; + sharg::value_list_validator validator_double{v_double}; + EXPECT_SAME_TYPE(sharg::value_list_validator, decltype(validator_double)); + EXPECT_SAME_TYPE(double, decltype(validator_double)::option_value_type); + + auto validator2 = validator_int64 | validator_uint64 | validator_double; + EXPECT_SAME_TYPE(double, std::common_type_t); + EXPECT_SAME_TYPE(double, decltype(validator2)::option_value_type); + + // max_int64 is an exact match for the two integral validators + // note: double will decay the integer to a smaller double value, + // but this is consistent, because it is the same given value + // note: chained validator passes the value as it is through, + // so validator_[u]int64 will be called with the integer value + EXPECT_NO_THROW(validator_int64(max_int64)); + EXPECT_NO_THROW(validator_uint64(max_int64)); + EXPECT_NO_THROW(validator_double(static_cast(max_int64))); + EXPECT_NO_THROW(validator2(max_int64)); + + // integers have exact match + // note: double accepts that value, even though it is not within that list. + EXPECT_THROW(validator_int64(max_int64 - 1), sharg::validation_error); + EXPECT_THROW(validator_uint64(max_int64 - 1), sharg::validation_error); + EXPECT_NO_THROW(validator_double(static_cast(max_int64 - 1))); + EXPECT_THROW(validator2(max_int64 - 1), sharg::validation_error); } -TEST(validator_test, chaining_validators) +TEST_F(validator_test, chaining_validators) { - std::string option_value{}; + std::string value{}; std::vector option_vector{}; sharg::regex_validator absolute_path_validator{"(/[^/]+)+/.*\\.[^/\\.]+$"}; sharg::output_file_validator my_file_ext_validator{sharg::output_file_open_options::create_new, {"sa", "so"}}; - sharg::test::tmp_filename tmp_name{"file.sa"}; + sharg::test::tmp_filename const tmp_name{"file.sa"}; + std::string const tmp_string{tmp_name.get_path().string()}; + std::string const tmp_relative_string{tmp_name.get_path().relative_path().string()}; std::filesystem::path invalid_extension{tmp_name.get_path()}; invalid_extension.replace_extension(".invalid"); + std::string const invalid_extension_string{invalid_extension.string()}; // option - { - std::string const & path = tmp_name.get_path().string(); - char const * argv[] = {"./parser_test", "-s", path.c_str()}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option(option_value, - sharg::config{.short_id = 's', .validator = absolute_path_validator | my_file_ext_validator}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, path); - } + auto option_validator = absolute_path_validator | my_file_ext_validator; - { - auto rel_path = tmp_name.get_path().relative_path().string(); - char const * argv[] = {"./parser_test", "-s", rel_path.c_str()}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option(option_value, - sharg::config{.short_id = 's', .validator = absolute_path_validator | my_file_ext_validator}); - - EXPECT_THROW(parser.parse(), sharg::validation_error); - } + auto parser = get_parser("-s", tmp_string); + parser.add_option(value, sharg::config{.short_id = 's', .validator = option_validator}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(value, tmp_string); - { - std::string const & path = invalid_extension.string(); - char const * argv[] = {"./parser_test", "-s", path.c_str()}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option(option_value, - sharg::config{.short_id = 's', .validator = absolute_path_validator | my_file_ext_validator}); - - EXPECT_THROW(parser.parse(), sharg::validation_error); - } + parser = get_parser("-s", tmp_relative_string); + parser.add_option(value, sharg::config{.short_id = 's', .validator = option_validator}); + EXPECT_THROW(parser.parse(), sharg::validation_error); + EXPECT_EQ(value, tmp_relative_string); + + parser = get_parser("-s", invalid_extension_string); + parser.add_option(value, sharg::config{.short_id = 's', .validator = option_validator}); + EXPECT_THROW(parser.parse(), sharg::validation_error); + EXPECT_EQ(value, invalid_extension_string); // with temporary validators - { - std::string const & path = tmp_name.get_path().string(); - char const * argv[] = {"./parser_test", "-s", path.c_str()}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option( - option_value, - sharg::config{ - .short_id = 's', - .validator = sharg::regex_validator{"(/[^/]+)+/.*\\.[^/\\.]+$"} - | sharg::output_file_validator{sharg::output_file_open_options::create_new, {"sa", "so"}}}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, path); - } + parser = get_parser("-s", tmp_string); + parser.add_option( + value, + sharg::config{.short_id = 's', + .validator = + sharg::regex_validator{"(/[^/]+)+/.*\\.[^/\\.]+$"} + | sharg::output_file_validator{sharg::output_file_open_options::create_new, {"sa", "so"}}}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(value, tmp_string); // three validators - { - std::string const & path = tmp_name.get_path().string(); - char const * argv[] = {"./parser_test", "-s", path.c_str()}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option( - option_value, - sharg::config{.short_id = 's', - .validator = - sharg::regex_validator{"(/[^/]+)+/.*\\.[^/\\.]+$"} - | sharg::output_file_validator{sharg::output_file_open_options::create_new, {"sa", "so"}} - | sharg::regex_validator{".*"}}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_value, path); - } + parser = get_parser("-s", tmp_string); + parser.add_option( + value, + sharg::config{.short_id = 's', + .validator = + sharg::regex_validator{"(/[^/]+)+/.*\\.[^/\\.]+$"} + | sharg::output_file_validator{sharg::output_file_open_options::create_new, {"sa", "so"}} + | sharg::regex_validator{".*"}}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(value, tmp_string); // help page message - { - option_value.clear(); - char const * argv[] = {"./parser_test", "-h"}; - sharg::parser parser{"test_parser", 2, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option( - option_value, - sharg::config{.short_id = 's', - .long_id = "string-option", - .description = "desc", - .validator = - sharg::regex_validator{"(/[^/]+)+/.*\\.[^/\\.]+$"} - | sharg::output_file_validator{sharg::output_file_open_options::create_new, {"sa", "so"}} - | sharg::regex_validator{".*"}}); - - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std::string my_stdout = testing::internal::GetCapturedStdout(); - std::string expected = - std::string{"test_parser\n" - "===========\n" - "\nOPTIONS\n" - " -s, --string-option (std::string)\n" - " desc Default: \"\". Value must match the pattern\n" - " '(/[^/]+)+/.*\\.[^/\\.]+$'. The output file must not exist already and\n" - " write permissions must be granted. Valid file extensions are: [sa,\n" - " so]. Value must match the pattern '.*'.\n" - "\n"} - + basic_options_str + "\n" + basic_version_str; - EXPECT_EQ(my_stdout, expected); - } + value.clear(); + parser = get_parser("-h"); + parser.add_option( + value, + sharg::config{.short_id = 's', + .long_id = "string-option", + .description = "desc", + .validator = + sharg::regex_validator{"(/[^/]+)+/.*\\.[^/\\.]+$"} + | sharg::output_file_validator{sharg::output_file_open_options::create_new, {"sa", "so"}} + | sharg::regex_validator{".*"}}); + std::string expected = + std::string{"test_parser\n" + "===========\n" + "\nOPTIONS\n" + " -s, --string-option (std::string)\n" + " desc Default: \"\". Value must match the pattern\n" + " '(/[^/]+)+/.*\\.[^/\\.]+$'. The output file must not exist already and\n" + " write permissions must be granted. Valid file extensions are: [sa,\n" + " so]. Value must match the pattern '.*'.\n" + "\n"} + + basic_options_str + "\n" + basic_version_str; + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); // help page message (allow overwriting) - { - option_value.clear(); - char const * argv[] = {"./parser_test", "-h"}; - sharg::parser parser{"test_parser", 2, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option( - option_value, - sharg::config{ - .short_id = 's', - .long_id = "string-option", - .description = "desc", - .validator = sharg::regex_validator{"(/[^/]+)+/.*\\.[^/\\.]+$"} - | sharg::output_file_validator{sharg::output_file_open_options::open_or_create, {"sa", "so"}} - | sharg::regex_validator{".*"}}); - - testing::internal::CaptureStdout(); - EXPECT_EXIT(parser.parse(), ::testing::ExitedWithCode(EXIT_SUCCESS), ""); - std::string my_stdout = testing::internal::GetCapturedStdout(); - std::string expected = - std::string{"test_parser\n" - "===========\n" - "\nOPTIONS\n" - " -s, --string-option (std::string)\n" - " desc Default: \"\". Value must match the pattern\n" - " '(/[^/]+)+/.*\\.[^/\\.]+$'. Write permissions must be granted. Valid\n" - " file extensions are: [sa, so]. Value must match the pattern '.*'.\n" - "\n"} - + basic_options_str + "\n" + basic_version_str; - EXPECT_EQ(my_stdout, expected); - } + parser = get_parser("-h"); + parser.add_option( + value, + sharg::config{.short_id = 's', + .long_id = "string-option", + .description = "desc", + .validator = + sharg::regex_validator{"(/[^/]+)+/.*\\.[^/\\.]+$"} + | sharg::output_file_validator{sharg::output_file_open_options::open_or_create, {"sa", "so"}} + | sharg::regex_validator{".*"}}); + expected = std::string{"test_parser\n" + "===========\n" + "\nOPTIONS\n" + " -s, --string-option (std::string)\n" + " desc Default: \"\". Value must match the pattern\n" + " '(/[^/]+)+/.*\\.[^/\\.]+$'. Write permissions must be granted. Valid\n" + " file extensions are: [sa, so]. Value must match the pattern '.*'.\n" + "\n"} + + basic_options_str + "\n" + basic_version_str; + EXPECT_EQ(get_parse_cout_on_exit(parser), expected); // chaining with a container option value type - { - std::vector option_list_value{}; - std::string const & path = tmp_name.get_path().string(); - char const * argv[] = {"./parser_test", "-s", path.c_str()}; - sharg::parser parser{"test_parser", 3, argv, sharg::update_notifications::off}; - sharg::detail::test_accessor::set_terminal_width(parser, 80); - parser.add_option( - option_list_value, - sharg::config{ - .short_id = 's', - .validator = sharg::regex_validator{"(/[^/]+)+/.*\\.[^/\\.]+$"} - | sharg::output_file_validator{sharg::output_file_open_options::create_new, {"sa", "so"}}}); - - testing::internal::CaptureStderr(); - EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); - EXPECT_EQ(option_list_value[0], path); - } + parser = get_parser("-s", tmp_string); + parser.add_option( + option_vector, + sharg::config{.short_id = 's', + .validator = + sharg::regex_validator{"(/[^/]+)+/.*\\.[^/\\.]+$"} + | sharg::output_file_validator{sharg::output_file_open_options::create_new, {"sa", "so"}}}); + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(option_vector.size(), 1u); + EXPECT_EQ(option_vector[0], tmp_string); } diff --git a/test/unit/parser/parser_design_error_test.cpp b/test/unit/parser/parser_design_error_test.cpp index 268d47b8..fb737717 100644 --- a/test/unit/parser/parser_design_error_test.cpp +++ b/test/unit/parser/parser_design_error_test.cpp @@ -5,14 +5,18 @@ #include #include +#include -TEST(design_error, app_name_validation) +class design_error_test : public sharg::test::test_fixture +{}; + +TEST_F(design_error_test, app_name_validation) { char const * argv[] = {"./parser_test", "-i", "3"}; int const argc{3}; - int option_value; + int option_value{}; - auto create_parser = [&](std::string && app_name) + auto create_parser = [&](std::string app_name) { sharg::parser parser{std::move(app_name), argc, argv, sharg::update_notifications::off}; parser.add_option(option_value, sharg::config{.short_id = 'i'}); @@ -32,62 +36,58 @@ TEST(design_error, app_name_validation) // option config verification // ----------------------------------------------------------------------------- -TEST(verify_option_config_test, short_option_was_used_before) -{ - int option_value; +class verify_option_config_test : public sharg::test::test_fixture +{}; - char const * argv[] = {"./parser_test"}; - sharg::parser parser{"test_parser", 1, argv}; +TEST_F(verify_option_config_test, short_option_was_used_before) +{ + int option_value{}; + auto parser = get_parser(); parser.add_option(option_value, sharg::config{.short_id = 'i'}); EXPECT_THROW(parser.add_option(option_value, sharg::config{.short_id = 'i'}), sharg::design_error); } -TEST(verify_option_config_test, long_option_was_used_before) +TEST_F(verify_option_config_test, long_option_was_used_before) { - int option_value; + int option_value{}; - char const * argv[] = {"./parser_test"}; - sharg::parser parser{"test_parser", 1, argv}; + auto parser = get_parser(); parser.add_option(option_value, sharg::config{.long_id = "int"}); EXPECT_THROW(parser.add_option(option_value, sharg::config{.long_id = "int"}), sharg::design_error); } -TEST(verify_option_config_test, short_and_long_id_empty) +TEST_F(verify_option_config_test, short_and_long_id_empty) { - int option_value; + int option_value{}; - char const * argv[] = {"./parser_test"}; - sharg::parser parser{"test_parser", 1, argv}; + auto parser = get_parser(); EXPECT_THROW(parser.add_option(option_value, sharg::config{}), sharg::design_error); } -TEST(verify_option_config_test, special_identifiers) +TEST_F(verify_option_config_test, special_identifiers) { - int option_value; + int option_value{}; - char const * argv[] = {"./parser_test"}; - sharg::parser parser{"test_parser", 1, argv}; + auto parser = get_parser(); EXPECT_THROW(parser.add_option(option_value, sharg::config{.short_id = 'h'}), sharg::design_error); EXPECT_THROW(parser.add_option(option_value, sharg::config{.long_id = "help"}), sharg::design_error); EXPECT_THROW(parser.add_option(option_value, sharg::config{.long_id = "advanced-help"}), sharg::design_error); EXPECT_THROW(parser.add_option(option_value, sharg::config{.long_id = "export-help"}), sharg::design_error); } -TEST(verify_option_config_test, single_character_long_id) +TEST_F(verify_option_config_test, single_character_long_id) { - int option_value; + int option_value{}; - char const * argv[] = {"./parser_test"}; - sharg::parser parser{"test_parser", 1, argv}; + auto parser = get_parser(); EXPECT_THROW(parser.add_option(option_value, sharg::config{.long_id = "z"}), sharg::design_error); } -TEST(verify_option_config_test, non_printable_characters) +TEST_F(verify_option_config_test, non_printable_characters) { - int option_value; + int option_value{}; - char const * argv[] = {"./parser_test"}; - sharg::parser parser{"test_parser", 1, argv}; + auto parser = get_parser(); EXPECT_THROW(parser.add_option(option_value, sharg::config{.short_id = '\t'}), sharg::design_error); EXPECT_THROW(parser.add_option(option_value, sharg::config{.long_id = "no\n"}), sharg::design_error); EXPECT_THROW(parser.add_option(option_value, sharg::config{.long_id = "-no"}), sharg::design_error); @@ -97,101 +97,98 @@ TEST(verify_option_config_test, non_printable_characters) // flag config verification // ----------------------------------------------------------------------------- -TEST(verify_flag_config_test, default_value_is_true) +class verify_flag_config_test : public sharg::test::test_fixture +{}; + +TEST_F(verify_flag_config_test, default_value_is_true) { bool true_value{true}; - char const * argv[] = {"./parser_test"}; - sharg::parser parser{"test_parser", 1, argv}; + auto parser = get_parser(); EXPECT_THROW(parser.add_flag(true_value, sharg::config{.short_id = 'i'}), sharg::design_error); } -TEST(verify_flag_config_test, short_option_was_used_before) +TEST_F(verify_flag_config_test, short_option_was_used_before) { bool flag_value{false}; - char const * argv[] = {"./parser_test"}; - sharg::parser parser{"test_parser", 1, argv}; + auto parser = get_parser(); parser.add_flag(flag_value, sharg::config{.short_id = 'i'}); EXPECT_THROW(parser.add_flag(flag_value, sharg::config{.short_id = 'i'}), sharg::design_error); } -TEST(verify_flag_config_test, long_option_was_used_before) +TEST_F(verify_flag_config_test, long_option_was_used_before) { bool flag_value{false}; - char const * argv[] = {"./parser_test"}; - sharg::parser parser6{"test_parser", 1, argv}; - parser6.add_flag(flag_value, sharg::config{.long_id = "int"}); - EXPECT_THROW(parser6.add_flag(flag_value, sharg::config{.long_id = "int"}), sharg::design_error); + auto parser = get_parser(); + parser.add_flag(flag_value, sharg::config{.long_id = "int"}); + EXPECT_THROW(parser.add_flag(flag_value, sharg::config{.long_id = "int"}), sharg::design_error); } -TEST(verify_flag_config_test, short_and_long_id_empty) +TEST_F(verify_flag_config_test, short_and_long_id_empty) { bool flag_value{false}; - char const * argv[] = {"./parser_test"}; - sharg::parser parser{"test_parser", 1, argv}; + auto parser = get_parser(); EXPECT_THROW(parser.add_flag(flag_value, sharg::config{}), sharg::design_error); } -TEST(verify_flag_config_test, single_character_long_id) +TEST_F(verify_flag_config_test, single_character_long_id) { - bool flag_value; + bool flag_value{false}; - char const * argv[] = {"./parser_test"}; - sharg::parser parser10{"test_parser", 1, argv}; - EXPECT_THROW(parser10.add_flag(flag_value, sharg::config{.long_id = "z"}), sharg::design_error); + auto parser = get_parser(); + EXPECT_THROW(parser.add_flag(flag_value, sharg::config{.long_id = "z"}), sharg::design_error); } // ----------------------------------------------------------------------------- -// flag config verification +// positional option verification // ----------------------------------------------------------------------------- -TEST(verify_positional_option_config_test, list_option_not_the_very_last_option) +class verify_positional_option_config_test : public sharg::test::test_fixture +{}; + +TEST_F(verify_positional_option_config_test, list_option_not_the_very_last_option) { - int option_value; - std::vector vec; + int option_value{}; + std::vector vec{}; + + auto parser = get_parser("arg1", "arg2", "arg3"); - char const * argv[] = {"./parser_test", "arg1", "arg2", "arg3"}; - sharg::parser parser{"test_parser", 4, argv}; parser.add_positional_option(vec, sharg::config{}); EXPECT_THROW(parser.add_positional_option(option_value, sharg::config{}), sharg::design_error); } -TEST(verify_positional_option_config_test, short_id_set) +TEST_F(verify_positional_option_config_test, short_id_set) { - int option_value; + int option_value{}; - char const * argv[] = {"./parser_test", "arg1"}; - sharg::parser parser{"test_parser", 2, argv}; + auto parser = get_parser("arg1"); EXPECT_THROW(parser.add_positional_option(option_value, sharg::config{.short_id = 'a'}), sharg::design_error); } -TEST(verify_positional_option_config_test, long_id_set) +TEST_F(verify_positional_option_config_test, long_id_set) { - int option_value; + int option_value{}; - char const * argv[] = {"./parser_test", "arg1"}; - sharg::parser parser{"test_parser", 2, argv}; + auto parser = get_parser("arg1"); EXPECT_THROW(parser.add_positional_option(option_value, sharg::config{.long_id = "abc"}), sharg::design_error); } -TEST(verify_positional_option_config_test, advanced_config_set) +TEST_F(verify_positional_option_config_test, advanced_config_set) { - int option_value; + int option_value{}; - char const * argv[] = {"./parser_test", "arg1"}; - sharg::parser parser{"test_parser", 2, argv}; + auto parser = get_parser("arg1"); EXPECT_THROW(parser.add_positional_option(option_value, sharg::config{.advanced = true}), sharg::design_error); } -TEST(verify_positional_option_config_test, hidden_config_set) +TEST_F(verify_positional_option_config_test, hidden_config_set) { - int option_value; + int option_value{}; - char const * argv[] = {"./parser_test", "arg1"}; - sharg::parser parser{"test_parser", 2, argv}; + auto parser = get_parser("arg1"); EXPECT_THROW(parser.add_positional_option(option_value, sharg::config{.hidden = true}), sharg::design_error); } @@ -199,33 +196,33 @@ TEST(verify_positional_option_config_test, hidden_config_set) // default_message config verification // ----------------------------------------------------------------------------- -TEST(verify_default_message_config_test, required_option_set) +class verify_default_message_config_test : public sharg::test::test_fixture +{}; + +TEST_F(verify_default_message_config_test, required_option_set) { - int option_value; + int option_value{}; - char const * argv[] = {"./parser_test", "arg1"}; - sharg::parser parser{"test_parser", 2, argv}; + auto parser = get_parser("arg1"); EXPECT_THROW(parser.add_option(option_value, sharg::config{.long_id = "int", .default_message = "Some number", .required = true}), sharg::design_error); } -TEST(verify_default_message_config_test, positional_option_set) +TEST_F(verify_default_message_config_test, positional_option_set) { - int option_value; + int option_value{}; - char const * argv[] = {"./parser_test", "arg1"}; - sharg::parser parser{"test_parser", 2, argv}; + auto parser = get_parser("arg1"); EXPECT_THROW(parser.add_positional_option(option_value, sharg::config{.default_message = "Some number"}), sharg::design_error); } -TEST(verify_default_message_config_test, flag_set) +TEST_F(verify_default_message_config_test, flag_set) { bool value{}; - char const * argv[] = {"./parser_test"}; - sharg::parser parser{"test_parser", 1, argv}; + auto parser = get_parser(); EXPECT_THROW(parser.add_flag(value, sharg::config{.short_id = 'i', .default_message = "false"}), sharg::design_error); } @@ -234,62 +231,51 @@ TEST(verify_default_message_config_test, flag_set) // general // ----------------------------------------------------------------------------- -TEST(parse_test, parse_called_twice) +TEST_F(design_error_test, parse_called_twice) { - std::string option_value; + std::string option_value{}; - char const * argv[] = {"./parser_test", "--version-check", "false", "-s", "option_string"}; - sharg::parser parser{"test_parser", 5, argv}; + auto parser = get_parser("--version-check", "false", "-s", "option_string"); parser.add_option(option_value, sharg::config{.short_id = 's'}); - testing::internal::CaptureStderr(); EXPECT_NO_THROW(parser.parse()); - EXPECT_TRUE((testing::internal::GetCapturedStderr()).empty()); EXPECT_EQ(option_value, "option_string"); - EXPECT_THROW(parser.parse(), sharg::design_error); } -TEST(parse_test, subcommand_parser_error) +TEST_F(design_error_test, subcommand_parser_error) { bool flag_value{false}; // subcommand parsing was not enabled on construction but get_sub_parser() is called - { - char const * argv[]{"./top_level", "-f"}; - sharg::parser top_level_parser{"top_level", 2, argv, sharg::update_notifications::off}; - top_level_parser.add_flag(flag_value, sharg::config{.short_id = 'f'}); + auto parser = get_parser("-f"); + parser.add_flag(flag_value, sharg::config{.short_id = 'f'}); - EXPECT_NO_THROW(top_level_parser.parse()); - EXPECT_EQ(true, flag_value); - - EXPECT_THROW(top_level_parser.get_sub_parser(), sharg::design_error); - } + EXPECT_NO_THROW(parser.parse()); + EXPECT_EQ(true, flag_value); + EXPECT_THROW(parser.get_sub_parser(), sharg::design_error); // subcommand key word must only contain alpha numeric characters - { - char const * argv[]{"./top_level", "-f"}; - EXPECT_THROW((sharg::parser{"top_level", 2, argv, sharg::update_notifications::off, {"with space"}}), - sharg::design_error); - } + EXPECT_THROW((sharg::parser{"top_level", {"./test_parser"}, sharg::update_notifications::off, {"with space"}}), + sharg::design_error); // no positional/options are allowed - { - char const * argv[]{"./top_level", "foo"}; - sharg::parser top_level_parser{"top_level", 2, argv, sharg::update_notifications::off, {"foo"}}; - - EXPECT_THROW((top_level_parser.add_option(flag_value, sharg::config{.short_id = 'f'})), sharg::design_error); - EXPECT_THROW((top_level_parser.add_positional_option(flag_value, sharg::config{})), sharg::design_error); - } + parser = get_subcommand_parser({"foo"}, {"foo"}); + EXPECT_THROW((parser.add_option(flag_value, sharg::config{.short_id = 'f'})), sharg::design_error); + EXPECT_THROW((parser.add_positional_option(flag_value, sharg::config{})), sharg::design_error); + + // Todo: Allowed + parser = get_subcommand_parser({""}, {"foo"}); + EXPECT_NO_THROW((parser.add_option(flag_value, sharg::config{.short_id = 'f'}))); + EXPECT_NO_THROW((parser.add_positional_option(flag_value, sharg::config{}))); } -TEST(parse_test, not_allowed_after_parse) +TEST_F(design_error_test, not_allowed_after_parse) { int32_t value{}; bool flag{}; - std::vector const arguments{"./parser_test", "-i", "3"}; - sharg::parser parser{"test_parser", arguments}; + auto parser = get_parser("-i", "3"); parser.add_option(value, sharg::config{.short_id = 'i'}); EXPECT_NO_THROW(parser.parse());