From 67d56afef8cf11568f37a99a9f4eaf18285b2578 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Thu, 22 Jul 2021 06:45:44 -0700 Subject: [PATCH 1/6] Add test case for missing expected positional argument This fills a tiny gap in the positional_arguments suite. Most existing tests have positional arguments. The one case without an argument uses Argument::remaining so that ArgumentParserArgument::parse_args does not throw, instead ArgumentParser::get<> throws std::logic_error. Signed-off-by: Sean Robinson --- test/test_positional_arguments.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/test_positional_arguments.cpp b/test/test_positional_arguments.cpp index f0d936c..4e2489b 100644 --- a/test/test_positional_arguments.cpp +++ b/test/test_positional_arguments.cpp @@ -13,6 +13,15 @@ TEST_CASE("Parse positional arguments" * test_suite("positional_arguments")) { REQUIRE(program.get("output") == "thrust_profile.csv"); } +TEST_CASE("Missing expected positional argument" * + test_suite("positional_arguments")) { + argparse::ArgumentParser program("test"); + program.add_argument("input"); + REQUIRE_THROWS_WITH_AS(program.parse_args({ "test" }), + "1 argument(s) expected. 0 provided.", + std::runtime_error); +} + TEST_CASE("Parse positional arguments with fixed nargs" * test_suite("positional_arguments")) { argparse::ArgumentParser program("test"); From b6cedf4d56f8197a9e8a50cfda04ce0ab7d8f005 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Thu, 22 Jul 2021 06:45:44 -0700 Subject: [PATCH 2/6] Add test case for missing argument value While it's good to test around error conditions, the main purpose of this test is to catch future changes in the error type and message. Signed-off-by: Sean Robinson --- test/test_parse_args.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/test_parse_args.cpp b/test/test_parse_args.cpp index a3a0452..f77d122 100644 --- a/test/test_parse_args.cpp +++ b/test/test_parse_args.cpp @@ -3,6 +3,14 @@ using doctest::test_suite; +TEST_CASE("Missing argument" * test_suite("parse_args")) { + argparse::ArgumentParser program("test"); + program.add_argument("--config").nargs(1); + REQUIRE_THROWS_WITH_AS(program.parse_args({ "test", "--config" }), + "Too few arguments", + std::runtime_error); +} + TEST_CASE("Parse a string argument with value" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--config"); From 97993666abf808a918fea4f4906bf8eb366d5294 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Thu, 22 Jul 2021 06:45:44 -0700 Subject: [PATCH 3/6] Add tests for ArgumentParser::get These test the API shown in README.md, rather than the Argument::get function that does most of the work. Signed-off-by: Sean Robinson --- test/CMakeLists.txt | 1 + test/test_get.cpp | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 test/test_get.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e601965..e8f7d89 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -30,6 +30,7 @@ file(GLOB ARGPARSE_TEST_SOURCES test_compound_arguments.cpp test_container_arguments.cpp test_const_correct.cpp + test_get.cpp test_help.cpp test_invalid_arguments.cpp test_is_used.cpp diff --git a/test/test_get.cpp b/test/test_get.cpp new file mode 100644 index 0000000..d54e0f3 --- /dev/null +++ b/test/test_get.cpp @@ -0,0 +1,29 @@ +#include +#include + +using doctest::test_suite; + +TEST_CASE("Getting a simple argument" * test_suite("ArgumentParser::get")) { + argparse::ArgumentParser program("test"); + program.add_argument("-s", "--stuff"); + REQUIRE_NOTHROW(program.parse_args({ "test", "-s", "./src" })); + REQUIRE(program.get("--stuff") == "./src"); +} + +TEST_CASE("Missing argument" * test_suite("ArgumentParser::get")) { + argparse::ArgumentParser program("test"); + program.add_argument("-s", "--stuff"); + REQUIRE_NOTHROW(program.parse_args({ "test" })); + REQUIRE_THROWS_WITH_AS(program.get("--stuff"), + "No value provided", + std::logic_error); +} + +TEST_CASE("Implicit argument" * test_suite("ArgumentParser::get")) { + argparse::ArgumentParser program("test"); + program.add_argument("-s", "--stuff").nargs(1); + REQUIRE_NOTHROW(program.parse_args({ "test" })); + REQUIRE_THROWS_WITH_AS(program.get("--stuff"), + "No value provided", + std::logic_error); +} From 1c2fd8726d7ecc700aad0bf9317386301dfb90cd Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Thu, 22 Jul 2021 06:45:44 -0700 Subject: [PATCH 4/6] Add argument name in exception thrown by Argument::get As the user did not include the argument, the longest name for the unused argument is in the last position of mNames. This is an API change that may affect programs trying to match the specific "No value provided" message. The new error message appends the argument that caused the error. A solution which works with both versions is to look for "No value provided" at the beginning of the error message. - if (err.what() == "No value provided") + if (std:string(err.what()).rfind("No value provided", 0) == 0) Signed-off-by: Sean Robinson --- include/argparse/argparse.hpp | 2 +- test/test_get.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index cdbcd19..c7719fe 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -759,7 +759,7 @@ private: if (mDefaultValue.has_value()) { return std::any_cast(mDefaultValue); } - throw std::logic_error("No value provided"); + throw std::logic_error("No value provided for '" + mNames.back() + "'."); } /* diff --git a/test/test_get.cpp b/test/test_get.cpp index d54e0f3..35d9464 100644 --- a/test/test_get.cpp +++ b/test/test_get.cpp @@ -15,7 +15,7 @@ TEST_CASE("Missing argument" * test_suite("ArgumentParser::get")) { program.add_argument("-s", "--stuff"); REQUIRE_NOTHROW(program.parse_args({ "test" })); REQUIRE_THROWS_WITH_AS(program.get("--stuff"), - "No value provided", + "No value provided for '--stuff'.", std::logic_error); } @@ -24,6 +24,6 @@ TEST_CASE("Implicit argument" * test_suite("ArgumentParser::get")) { program.add_argument("-s", "--stuff").nargs(1); REQUIRE_NOTHROW(program.parse_args({ "test" })); REQUIRE_THROWS_WITH_AS(program.get("--stuff"), - "No value provided", + "No value provided for '--stuff'.", std::logic_error); } From 6344b5dcc7b17adcb44560e7a1d573725510f1ee Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Thu, 22 Jul 2021 06:45:44 -0700 Subject: [PATCH 5/6] Add argument name in exception thrown by Argument::consume Here, the user gave an argument name but failed to provide the required parameters to the argument. Tell the user which argument wants more. This is an API change that may affect programs trying to match the specific "Too few arguments" message. The new error message appends the user-supplied argument that caused the error. A solution which works with both versions is to look for "Too few arguments" at the beginning of the error message. - if (err.what() == "Too few arguments") + if (std:string(err.what()).rfind("Too few arguments", 0) == 0) Signed-off-by: Sean Robinson --- include/argparse/argparse.hpp | 3 ++- test/test_parse_args.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index c7719fe..8829f31 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -472,7 +472,8 @@ public: } else if (mDefaultValue.has_value()) { return start; } else { - throw std::runtime_error("Too few arguments"); + throw std::runtime_error("Too few arguments for '" + + std::string(mUsedName) + "'."); } } diff --git a/test/test_parse_args.cpp b/test/test_parse_args.cpp index f77d122..146dfff 100644 --- a/test/test_parse_args.cpp +++ b/test/test_parse_args.cpp @@ -7,7 +7,7 @@ TEST_CASE("Missing argument" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--config").nargs(1); REQUIRE_THROWS_WITH_AS(program.parse_args({ "test", "--config" }), - "Too few arguments", + "Too few arguments for '--config'.", std::runtime_error); } From f0d68de1343fc1b4412d8b0d1649b44f8a7f3e35 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Thu, 22 Jul 2021 06:45:44 -0700 Subject: [PATCH 6/6] Confirm arguments are parsed before allowing ArgumentParser::get If the developer forgot to call ArgumentParser::parse_args<>, attempts to use ::get, ::present, etc., would raise "No value provided...". With this change, the error better describes what went wrong. Signed-off-by: Sean Robinson --- include/argparse/argparse.hpp | 6 ++++++ test/test_get.cpp | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 8829f31..cb427cd 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -911,12 +911,16 @@ public: } /* Getter for options with default values. + * @throws std::logic_error if parse_args() has not been previously called * @throws std::logic_error if there is no such option * @throws std::logic_error if the option has no value * @throws std::bad_any_cast if the option is not of type T */ template T get(std::string_view aArgumentName) const { + if (!mIsParsed) { + throw std::logic_error("Nothing parsed, no arguments are available."); + } return (*this)[aArgumentName].get(); } @@ -1076,6 +1080,7 @@ private: throw std::runtime_error("Unknown argument"); } } + mIsParsed = true; } /* @@ -1115,6 +1120,7 @@ private: std::string mVersion; std::string mDescription; std::string mEpilog; + bool mIsParsed = false; std::list mPositionalArguments; std::list mOptionalArguments; std::map> mArgumentMap; diff --git a/test/test_get.cpp b/test/test_get.cpp index 35d9464..db316ba 100644 --- a/test/test_get.cpp +++ b/test/test_get.cpp @@ -10,6 +10,14 @@ TEST_CASE("Getting a simple argument" * test_suite("ArgumentParser::get")) { REQUIRE(program.get("--stuff") == "./src"); } +TEST_CASE("Skipped call to parse_args" * test_suite("ArgumentParser::get")) { + argparse::ArgumentParser program("test"); + program.add_argument("stuff"); + REQUIRE_THROWS_WITH_AS(program.get("stuff"), + "Nothing parsed, no arguments are available.", + std::logic_error); +} + TEST_CASE("Missing argument" * test_suite("ArgumentParser::get")) { argparse::ArgumentParser program("test"); program.add_argument("-s", "--stuff");