From af6bbc1d4719f28b2ce65bf26a54ef5e9cd4096a Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Tue, 26 Oct 2021 12:58:24 -0700 Subject: [PATCH 01/61] Skip --version test until it can be made to work Because program.parse_args( { "test", "--version" }) calls std::exit(0), the REQUIRE line never runs and this test is less useful. Because tests execution stops here, the doctest status report is not output. If --version can be made to not exit during this test, then the test could be restored. Signed-off-by: Sean Robinson --- test/test_version.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_version.cpp b/test/test_version.cpp index 103f96d..4f5487a 100644 --- a/test/test_version.cpp +++ b/test/test_version.cpp @@ -3,7 +3,8 @@ using doctest::test_suite; -TEST_CASE("Users can print version and exit" * test_suite("version")) { +TEST_CASE("Users can print version and exit" * test_suite("version") + * doctest::skip()) { argparse::ArgumentParser program("cli-test", "1.9.0"); program.add_argument("-d", "--dir") .required(); From 500bc9277e289c1e4b609e77923fa88a64f62406 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Wed, 27 Oct 2021 07:24:45 -0700 Subject: [PATCH 02/61] Add ArgumentParser::mIsParsed to copied members MSVC 19.16 appears to be doing a copy rather than a move in test_const_correct. The copy ctor does not handle mIsParsed, so the initial false value is kept. This commit adds copying mIsParsed during copy construction. Signed-off-by: Sean Robinson --- include/argparse/argparse.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index cb427cd..75437f8 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -830,6 +830,7 @@ public: ArgumentParser(const ArgumentParser &other) : mProgramName(other.mProgramName), + mIsParsed(other.mIsParsed), mPositionalArguments(other.mPositionalArguments), mOptionalArguments(other.mOptionalArguments) { for (auto it = std::begin(mPositionalArguments); it != std::end(mPositionalArguments); From 8772b37aabf9c3679c6b346c113fc7b82a247452 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Tue, 26 Oct 2021 12:58:24 -0700 Subject: [PATCH 03/61] Update examples from exit() to std::exit() argparse seems to use the "std::" qualifier for std namespace members, continue that in the documentation. Signed-off-by: Sean Robinson --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index fa6f120..8df359f 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ int main(int argc, char *argv[]) { catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; - exit(0); + std::exit(0); } auto input = program.get("square"); @@ -103,7 +103,7 @@ try { catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; - exit(0); + std::exit(0); } if (program["--verbose"] == true) { @@ -169,7 +169,7 @@ try { catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; - exit(0); + std::exit(0); } auto color = program.get("--color"); // "orange" @@ -192,7 +192,7 @@ try { catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; - exit(0); + std::exit(0); } auto colors = program.get>("--color"); // {"red", "green", "blue"} @@ -222,7 +222,7 @@ try { catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; - exit(0); + std::exit(0); } // Some code to print arguments @@ -255,7 +255,7 @@ try { catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; - exit(0); + std::exit(0); } int input = program.get("square"); @@ -314,7 +314,7 @@ try { catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; - exit(0); + std::exit(0); } auto files = program.get>("--input_files"); // {"config.yml", "System.xml"} @@ -343,7 +343,7 @@ try { catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; - exit(0); + std::exit(0); } auto query_point = program.get>("--query_point"); // {3.5, 4.7, 9.2} @@ -375,7 +375,7 @@ try { catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; - exit(0); + std::exit(0); } auto a = program.get("-a"); // true @@ -459,7 +459,7 @@ try { catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; - exit(0); + std::exit(0); } try { @@ -506,7 +506,7 @@ try { catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; - exit(0); + std::exit(0); } auto output_filename = program.get("-o"); @@ -588,7 +588,7 @@ try { catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; - exit(0); + std::exit(0); } nlohmann::json config = program.get("config"); @@ -624,7 +624,7 @@ try { catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; - exit(0); + std::exit(0); } auto numbers = program.get>("numbers"); // {1, 2, 3} @@ -666,7 +666,7 @@ try { catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << program; - exit(0); + std::exit(0); } auto input = program.get("input"); From 58777d8c845f75bae2d5eae195084fd708066a3f Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Tue, 26 Oct 2021 12:58:24 -0700 Subject: [PATCH 04/61] Replace spaces with underscores in example program names These examples give a false impression that a space in the middle of the application name is well handled. While a space might be possible, it must be escaped in shells, i.e. a common environment for a CLI argument parser. Signed-off-by: Sean Robinson --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8df359f..fd8860f 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Simply include argparse.hpp and you're good to go. To start parsing command-line arguments, create an ```ArgumentParser```. ```cpp -argparse::ArgumentParser program("program name"); +argparse::ArgumentParser program("program_name"); ``` **NOTE:** There is an optional second argument to the `ArgumentParser` which is the program version. Example: `argparse::ArgumentParser program("libfoo", "1.9.0");` @@ -49,7 +49,7 @@ Here's an example of a ***positional argument***: #include int main(int argc, char *argv[]) { - argparse::ArgumentParser program("program name"); + argparse::ArgumentParser program("program_name"); program.add_argument("square") .help("display the square of a given integer") From 748bc95cf5881394680cdc91c8027984427a5e6f Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Tue, 26 Oct 2021 12:58:24 -0700 Subject: [PATCH 05/61] Rename inner scope variables to differ from outer scope These variables with the same name are not the same variables because of scope rules. While the compiler is not confused by this naming, it may be less readable by someone attempting to edit this code. Signed-off-by: Sean Robinson --- include/argparse/argparse.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 75437f8..8a6dff8 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -453,18 +453,18 @@ public: struct action_apply { void operator()(valued_action &f) { - std::transform(start, end, std::back_inserter(self.mValues), f); + std::transform(first, last, std::back_inserter(self.mValues), f); } void operator()(void_action &f) { - std::for_each(start, end, f); + std::for_each(first, last, f); if (!self.mDefaultValue.has_value()) { if (auto expected = self.maybe_nargs()) self.mValues.resize(*expected); } } - Iterator start, end; + Iterator first, last; Argument &self; }; std::visit(action_apply{start, end, *this}, mAction); From 2b05334a3c37f471e4a26dbebd19ab5fee6e1aa4 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Tue, 26 Oct 2021 12:58:24 -0700 Subject: [PATCH 06/61] Run Argumnet::action functor for zero-parameter arguments Previously, only arguments with one or more parameters would run actions. But, at times it can be useful to run an action when an argument does not expect any parameters. Closes #104 Signed-off-by: Sean Robinson --- README.md | 18 ++++++++++++++++++ include/argparse/argparse.hpp | 1 + test/test_actions.cpp | 24 ++++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/README.md b/README.md index fd8860f..db53e64 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,24 @@ auto colors = program.get>("--color"); // {"red", "gre Notice that ```.default_value``` is given an explicit template parameter to match the type you want to ```.get```. +#### Repeating an argument to increase a value + +A common pattern is to repeat an argument to indicate a greater value. + +```cpp +int verbosity = 0; +program.add_argument("-V", "--verbose") + .action([&](const auto &) { ++verbosity; }) + .append() + .default_value(false) + .implicit_value(true) + .nargs(0); + +program.parse_args(argc, argv); // Example: ./main -VVVV + +std::cout << "verbose level: " << verbosity << std::endl; // verbose level: 4 +``` + ### Negative Numbers Optional arguments start with ```-```. Can ```argparse``` handle negative numbers? The answer is yes! diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 8a6dff8..980ebc7 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -442,6 +442,7 @@ public: mUsedName = usedName; if (mNumArgs == 0) { mValues.emplace_back(mImplicitValue); + std::visit([](auto &aAction) { aAction({}); }, mAction); return start; } else if (mNumArgs <= std::distance(start, end)) { if (auto expected = maybe_nargs()) { diff --git a/test/test_actions.cpp b/test/test_actions.cpp index 6d624af..7c5fd10 100644 --- a/test/test_actions.cpp +++ b/test/test_actions.cpp @@ -133,3 +133,27 @@ TEST_CASE("Users can use actions on remaining arguments" * program.parse_args({"sum", "42", "100", "-3", "-20"}); REQUIRE(result == 119); } + +TEST_CASE("Users can run actions on parameterless optional arguments" * + test_suite("actions")) { + argparse::ArgumentParser program("test"); + + GIVEN("a flag argument with a counting action") { + int count = 0; + program.add_argument("-V", "--verbose") + .action([&](const auto &) { ++count; }) + .append() + .default_value(false) + .implicit_value(true) + .nargs(0); + + WHEN("the flag is repeated") { + program.parse_args({"test", "-VVVV"}); + + THEN("the count increments once per use") { + REQUIRE(program.get("-V")); + REQUIRE(count == 4); + } + } + } +} From ea1f7ef663899e799001f50055cc93c733a9fff7 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Tue, 26 Oct 2021 12:58:24 -0700 Subject: [PATCH 07/61] Allow removal of default arguments (i.e. --help and --version) The help and version arguments are still included by default, but which default arguments to include can be overridden at ArgumentParser creation. argparse generally copies Python argparse behavior. This includes a default `--help`/`-h` argument to print a help message and exit. Some developers using argparse find the automatic exit to be undesirable. The Python argparse has an opt-out parameter when constructing an ArgumentParser. Using `add_help=False` avoids adding a default `--help` argument and allows the developer to implement a custom help. This commit adds a similar opt-out to our C++ argparse, but keeps the current behavior as the default. The `--help`/`-h` and `--version`/`-v` Arguments handle their own output and exit rather than specially treating them in ArgumentParser::parse_args_internal. Closes #119 Closes #138 Closes #139 Signed-off-by: Sean Robinson --- README.md | 21 ++++++++++++++ include/argparse/argparse.hpp | 54 ++++++++++++++++++++++------------- test/CMakeLists.txt | 1 + test/test_default_args.cpp | 19 ++++++++++++ test/test_help.cpp | 24 ++++++++++++++++ test/test_version.cpp | 26 +++++++++++++++++ 6 files changed, 125 insertions(+), 20 deletions(-) create mode 100644 test/test_default_args.cpp diff --git a/README.md b/README.md index db53e64..6801047 100644 --- a/README.md +++ b/README.md @@ -455,6 +455,27 @@ The grammar follows `std::from_chars`, but does not exactly duplicate it. For ex | 'u' | decimal (unsigned) | | 'x' or 'X' | hexadecimal (unsigned) | +### Default Arguments + +`argparse` provides predefined arguments and actions for `-h`/`--help` and `-v`/`--version`. These default actions exit the program after displaying a help or version message, respectively. These defaults arguments can be disabled during `ArgumentParser` creation so that you can handle these arguments in your own way. (Note that a program name and version must be included when choosing default arguments.) + +```cpp +argparse::ArgumentParser program("test", "1.0", default_arguments::none); + +program.add_argument("-h", "--help") + .action([=](const std::string& s) { + std::cout << help().str(); + }) + .default_value(false) + .help("shows help message") + .implicit_value(true) + .nargs(0); +``` + +The above code snippet outputs a help message and continues to run. It does not support a `--version` argument. + +The default is `default_arguments::all` for included arguments. No default arguments will be added with `default_arguments::none`. `default_arguments::help` and `default_arguments::version` will individually add `--help` and `--version`. + ### Gathering Remaining Arguments `argparse` supports gathering "remaining" arguments at the end of the command, e.g., for use in a compiler: diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 980ebc7..cda03e5 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -312,6 +312,17 @@ template struct parse_number { } // namespace details +enum class default_arguments : unsigned int { + none = 0, + help = 1, + version = 2, + all = help | version, +}; + +inline bool operator& (const default_arguments &a, const default_arguments &b) { + return static_cast(a) & static_cast(b); +} + class ArgumentParser; class Argument { @@ -814,16 +825,31 @@ private: class ArgumentParser { public: explicit ArgumentParser(std::string aProgramName = {}, - std::string aVersion = "1.0") + std::string aVersion = "1.0", + default_arguments aArgs = default_arguments::all) : mProgramName(std::move(aProgramName)), mVersion(std::move(aVersion)) { - add_argument("-h", "--help").help("shows help message and exits").nargs(0); -#ifndef ARGPARSE_LONG_VERSION_ARG_ONLY - add_argument("-v", "--version") -#else - add_argument("--version") -#endif - .help("prints version information and exits") + if (aArgs & default_arguments::help) { + add_argument("-h", "--help") + .action([&](const auto &) { + std::cout << help().str(); + std::exit(0); + }) + .default_value(false) + .help("shows help message and exits") + .implicit_value(true) .nargs(0); + } + if (aArgs & default_arguments::version) { + add_argument("-v", "--version") + .action([&](const auto &) { + std::cout << mVersion; + std::exit(0); + }) + .default_value(false) + .help("prints version information and exits") + .implicit_value(true) + .nargs(0); + } } ArgumentParser(ArgumentParser &&) noexcept = default; @@ -1051,18 +1077,6 @@ private: auto tIterator = mArgumentMap.find(tCurrentArgument); if (tIterator != mArgumentMap.end()) { auto tArgument = tIterator->second; - - // the first optional argument is --help - if (tArgument == mOptionalArguments.begin()) { - std::cout << *this; - std::exit(0); - } - // the second optional argument is --version - else if (tArgument == std::next(mOptionalArguments.begin(), 1)) { - std::cout << mVersion << "\n"; - std::exit(0); - } - it = tArgument->consume(std::next(it), end, tIterator->first); } else if (const auto &tCompoundArgument = tCurrentArgument; tCompoundArgument.size() > 1 && tCompoundArgument[0] == '-' && diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e8f7d89..3c502d9 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_default_args.cpp test_get.cpp test_help.cpp test_invalid_arguments.cpp diff --git a/test/test_default_args.cpp b/test/test_default_args.cpp new file mode 100644 index 0000000..046b88e --- /dev/null +++ b/test/test_default_args.cpp @@ -0,0 +1,19 @@ +#include +#include + +using doctest::test_suite; + +TEST_CASE("Include all default arguments" * test_suite("default_args")) { + argparse::ArgumentParser parser("test"); + auto help_msg { parser.help().str() }; + REQUIRE(help_msg.find("shows help message") != std::string::npos); + REQUIRE(help_msg.find("prints version information") != std::string::npos); +} + +TEST_CASE("Do not include default arguments" * test_suite("default_args")) { + argparse::ArgumentParser parser("test", "1.0", + argparse::default_arguments::none); + parser.parse_args({"test"}); + REQUIRE_THROWS_AS(parser.get("--help"), std::logic_error); + REQUIRE_THROWS_AS(parser.get("--version"), std::logic_error); +} diff --git a/test/test_help.cpp b/test/test_help.cpp index 78438ea..e3bb225 100644 --- a/test/test_help.cpp +++ b/test/test_help.cpp @@ -1,5 +1,6 @@ #include #include +#include using doctest::test_suite; @@ -51,3 +52,26 @@ TEST_CASE("Users can override the help options" * test_suite("help")) { } } } + +TEST_CASE("Users can disable default -h/--help" * test_suite("help")) { + argparse::ArgumentParser program("test", "1.0", + argparse::default_arguments::version); + REQUIRE_THROWS_AS(program.parse_args({"test", "-h"}), std::runtime_error); +} + +TEST_CASE("Users can replace default -h/--help" * test_suite("help")) { + argparse::ArgumentParser program("test", "1.0", + argparse::default_arguments::version); + std::stringstream buffer; + program.add_argument("-h", "--help") + .action([&](const auto &) { + buffer << program; + }) + .default_value(false) + .implicit_value(true) + .nargs(0); + + REQUIRE(buffer.str().empty()); + program.parse_args({"test", "--help"}); + REQUIRE_FALSE(buffer.str().empty()); +} diff --git a/test/test_version.cpp b/test/test_version.cpp index 4f5487a..ad71b25 100644 --- a/test/test_version.cpp +++ b/test/test_version.cpp @@ -1,5 +1,6 @@ #include #include +#include using doctest::test_suite; @@ -11,3 +12,28 @@ TEST_CASE("Users can print version and exit" * test_suite("version") program.parse_args( { "test", "--version" }); REQUIRE(program.get("--version") == "1.9.0"); } + +TEST_CASE("Users can disable default -v/--version" * test_suite("version")) { + argparse::ArgumentParser program("test", "1.0", + argparse::default_arguments::help); + REQUIRE_THROWS_AS(program.parse_args({"test", "--version"}), + std::runtime_error); +} + +TEST_CASE("Users can replace default -v/--version" * test_suite("version")) { + std::string version { "3.1415" }; + argparse::ArgumentParser program("test", version, + argparse::default_arguments::help); + std::stringstream buffer; + program.add_argument("-v", "--version") + .action([&](const auto &) { + buffer << version; + }) + .default_value(true) + .implicit_value(false) + .nargs(0); + + REQUIRE(buffer.str().empty()); + program.parse_args({"test", "--version"}); + REQUIRE_FALSE(buffer.str().empty()); +} From 5cceb98e3c1f5cf23d3323853062e4b59086381b Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Tue, 26 Oct 2021 12:58:24 -0700 Subject: [PATCH 08/61] Update "Printing Help" documentation Help output has changed format over time. This updates the README example to reflect current practice by running the example code and copy-pasting its output. Signed-off-by: Sean Robinson --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6801047..f2561a3 100644 --- a/README.md +++ b/README.md @@ -257,7 +257,7 @@ As you can see here, ```argparse``` supports negative integers, negative floats ### Combining Positional and Optional Arguments ```cpp -argparse::ArgumentParser program("test"); +argparse::ArgumentParser program("main"); program.add_argument("square") .help("display the square of a given number") @@ -303,14 +303,15 @@ The square of 4 is 16 ``` $ ./main --help -Usage: ./main [options] square +Usage: main [options] square Positional arguments: -square display a square of a given number +square display the square of a given number Optional arguments: --h, --help show this help message and exit --v, --verbose enable verbose logging +-h --help shows help message and exits [default: false] +-v --version prints version information and exits [default: false] +--verbose [default: false] ``` You may also get the help message in string via `program.help().str()`. From 2c55a2e423cdff0ce872d2ef3759005fcd9a49fa Mon Sep 17 00:00:00 2001 From: Ashwin Rohit Date: Sun, 31 Oct 2021 12:29:19 -0700 Subject: [PATCH 09/61] Modify README.md to show printing errors to stderr instead of stdout --- README.md | 84 +++++++++++++++++++++++++++---------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index f2561a3..83cf92f 100644 --- a/README.md +++ b/README.md @@ -59,9 +59,9 @@ int main(int argc, char *argv[]) { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto input = program.get("square"); @@ -101,9 +101,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } if (program["--verbose"] == true) { @@ -167,9 +167,9 @@ try { program.parse_args(argc, argv); // Example: ./main --color orange } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto color = program.get("--color"); // "orange" @@ -190,9 +190,9 @@ try { program.parse_args(argc, argv); // Example: ./main --color red --color green --color blue } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto colors = program.get>("--color"); // {"red", "green", "blue"} @@ -238,9 +238,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } // Some code to print arguments @@ -271,9 +271,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } int input = program.get("square"); @@ -331,9 +331,9 @@ try { program.parse_args(argc, argv); // Example: ./main --input_files config.yml System.xml } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto files = program.get>("--input_files"); // {"config.yml", "System.xml"} @@ -360,9 +360,9 @@ try { program.parse_args(argc, argv); // Example: ./main --query_point 3.5 4.7 9.2 } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto query_point = program.get>("--query_point"); // {3.5, 4.7, 9.2} @@ -392,9 +392,9 @@ try { program.parse_args(argc, argv); // Example: ./main -abc 1.95 2.47 } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto a = program.get("-a"); // true @@ -497,9 +497,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } try { @@ -544,9 +544,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto output_filename = program.get("-o"); @@ -626,9 +626,9 @@ try { program.parse_args({"./test", "config.json"}); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } nlohmann::json config = program.get("config"); @@ -662,9 +662,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto numbers = program.get>("numbers"); // {1, 2, 3} @@ -704,9 +704,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto input = program.get("input"); From 9c729f54b1d2c3fda5554897adddb66bcfe93c25 Mon Sep 17 00:00:00 2001 From: Pranav Date: Sun, 31 Oct 2021 17:34:51 -0500 Subject: [PATCH 10/61] Updated contributors --- README.md | 103 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 61 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 83cf92f..ecfc6de 100644 --- a/README.md +++ b/README.md @@ -59,9 +59,9 @@ int main(int argc, char *argv[]) { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; - std::cerr << program; - std::exit(1); + std::cout << err.what() << std::endl; + std::cout << program; + std::exit(0); } auto input = program.get("square"); @@ -101,9 +101,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; - std::cerr << program; - std::exit(1); + std::cout << err.what() << std::endl; + std::cout << program; + std::exit(0); } if (program["--verbose"] == true) { @@ -167,9 +167,9 @@ try { program.parse_args(argc, argv); // Example: ./main --color orange } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; - std::cerr << program; - std::exit(1); + std::cout << err.what() << std::endl; + std::cout << program; + std::exit(0); } auto color = program.get("--color"); // "orange" @@ -190,9 +190,9 @@ try { program.parse_args(argc, argv); // Example: ./main --color red --color green --color blue } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; - std::cerr << program; - std::exit(1); + std::cout << err.what() << std::endl; + std::cout << program; + std::exit(0); } auto colors = program.get>("--color"); // {"red", "green", "blue"} @@ -238,9 +238,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; - std::cerr << program; - std::exit(1); + std::cout << err.what() << std::endl; + std::cout << program; + std::exit(0); } // Some code to print arguments @@ -271,9 +271,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; - std::cerr << program; - std::exit(1); + std::cout << err.what() << std::endl; + std::cout << program; + std::exit(0); } int input = program.get("square"); @@ -331,9 +331,9 @@ try { program.parse_args(argc, argv); // Example: ./main --input_files config.yml System.xml } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; - std::cerr << program; - std::exit(1); + std::cout << err.what() << std::endl; + std::cout << program; + std::exit(0); } auto files = program.get>("--input_files"); // {"config.yml", "System.xml"} @@ -360,9 +360,9 @@ try { program.parse_args(argc, argv); // Example: ./main --query_point 3.5 4.7 9.2 } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; - std::cerr << program; - std::exit(1); + std::cout << err.what() << std::endl; + std::cout << program; + std::exit(0); } auto query_point = program.get>("--query_point"); // {3.5, 4.7, 9.2} @@ -392,9 +392,9 @@ try { program.parse_args(argc, argv); // Example: ./main -abc 1.95 2.47 } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; - std::cerr << program; - std::exit(1); + std::cout << err.what() << std::endl; + std::cout << program; + std::exit(0); } auto a = program.get("-a"); // true @@ -497,9 +497,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; - std::cerr << program; - std::exit(1); + std::cout << err.what() << std::endl; + std::cout << program; + std::exit(0); } try { @@ -544,9 +544,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; - std::cerr << program; - std::exit(1); + std::cout << err.what() << std::endl; + std::cout << program; + std::exit(0); } auto output_filename = program.get("-o"); @@ -626,9 +626,9 @@ try { program.parse_args({"./test", "config.json"}); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; - std::cerr << program; - std::exit(1); + std::cout << err.what() << std::endl; + std::cout << program; + std::exit(0); } nlohmann::json config = program.get("config"); @@ -662,9 +662,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; - std::cerr << program; - std::exit(1); + std::cout << err.what() << std::endl; + std::cout << program; + std::exit(0); } auto numbers = program.get>("numbers"); // {1, 2, 3} @@ -704,9 +704,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; - std::cerr << program; - std::exit(1); + std::cout << err.what() << std::endl; + std::cout << program; + std::exit(0); } auto input = program.get("input"); @@ -747,6 +747,25 @@ Thanks goes to these wonderful people: mupp
mupp
Ethan Slattery
Ethan Slattery
+ skrobinson
skrobinson
+ Mike Zozu
Mike Zozu
+ Chuvi-w
Chuvi-w
+ KOLANICH
KOLANICH
+ + + Rafał Będźkowski
Rafał Będźkowski
+ Yoshihiro Hokazono
Yoshihiro Hokazono
+ Kenny Shen
Kenny Shen
+ The 42nd Mu00
The 42nd Mu00
+ Daniel Marshall
Daniel Marshall
+ Ubpa
Ubpa
+ + + Oliver Smith
Oliver Smith
+ Joseph Durel
Joseph Durel
+ rysson
rysson
+ aashwinr
aashwinr
+ bufferbase
bufferbase
From 2e25423db851a4aff6af2ba7b60ff6c6f43bb360 Mon Sep 17 00:00:00 2001 From: Ashwin Rohit Date: Sun, 31 Oct 2021 12:29:19 -0700 Subject: [PATCH 11/61] Modify README.md to show printing errors to stderr instead of stdout --- README.md | 84 +++++++++++++++++++++++++++---------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index ecfc6de..e3a0cd8 100644 --- a/README.md +++ b/README.md @@ -59,9 +59,9 @@ int main(int argc, char *argv[]) { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto input = program.get("square"); @@ -101,9 +101,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } if (program["--verbose"] == true) { @@ -167,9 +167,9 @@ try { program.parse_args(argc, argv); // Example: ./main --color orange } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto color = program.get("--color"); // "orange" @@ -190,9 +190,9 @@ try { program.parse_args(argc, argv); // Example: ./main --color red --color green --color blue } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto colors = program.get>("--color"); // {"red", "green", "blue"} @@ -238,9 +238,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } // Some code to print arguments @@ -271,9 +271,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } int input = program.get("square"); @@ -331,9 +331,9 @@ try { program.parse_args(argc, argv); // Example: ./main --input_files config.yml System.xml } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto files = program.get>("--input_files"); // {"config.yml", "System.xml"} @@ -360,9 +360,9 @@ try { program.parse_args(argc, argv); // Example: ./main --query_point 3.5 4.7 9.2 } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto query_point = program.get>("--query_point"); // {3.5, 4.7, 9.2} @@ -392,9 +392,9 @@ try { program.parse_args(argc, argv); // Example: ./main -abc 1.95 2.47 } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto a = program.get("-a"); // true @@ -497,9 +497,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } try { @@ -544,9 +544,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto output_filename = program.get("-o"); @@ -626,9 +626,9 @@ try { program.parse_args({"./test", "config.json"}); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } nlohmann::json config = program.get("config"); @@ -662,9 +662,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto numbers = program.get>("numbers"); // {1, 2, 3} @@ -704,9 +704,9 @@ try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << program; - std::exit(0); + std::cerr << err.what() << std::endl; + std::cerr << program; + std::exit(1); } auto input = program.get("input"); From 6f1a90f1014e0757991243a97b107b976679adf5 Mon Sep 17 00:00:00 2001 From: Pranav Srinivas Kumar Date: Wed, 3 Nov 2021 08:31:09 -0500 Subject: [PATCH 12/61] Removed duplicate entry for Daniel Marshall --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index e3a0cd8..f42c6e0 100644 --- a/README.md +++ b/README.md @@ -757,11 +757,10 @@ Thanks goes to these wonderful people: Yoshihiro Hokazono
Yoshihiro Hokazono
Kenny Shen
Kenny Shen
The 42nd Mu00
The 42nd Mu00
- Daniel Marshall
Daniel Marshall
Ubpa
Ubpa
+ Oliver Smith
Oliver Smith
- Oliver Smith
Oliver Smith
Joseph Durel
Joseph Durel
rysson
rysson
aashwinr
aashwinr
From 87afaba6ba3fca3e0e3c3aca9bebe0a94f294d25 Mon Sep 17 00:00:00 2001 From: Maciej Patro Date: Fri, 5 Nov 2021 09:56:17 +0100 Subject: [PATCH 13/61] Improve thrown message in case of invalid argument. Now message contains information which argument is the source of error. It's easier to spot typo/understand which part of more complex command is the source of problem. --- include/argparse/argparse.hpp | 4 ++-- test/test_invalid_arguments.cpp | 3 ++- test/test_version.cpp | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index cda03e5..aa5f959 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -1089,11 +1089,11 @@ private: auto tArgument = tIterator2->second; it = tArgument->consume(it, end, tIterator2->first); } else { - throw std::runtime_error("Unknown argument"); + throw std::runtime_error("Unknown argument: " + tCurrentArgument); } } } else { - throw std::runtime_error("Unknown argument"); + throw std::runtime_error("Unknown argument: " + tCurrentArgument); } } mIsParsed = true; diff --git a/test/test_invalid_arguments.cpp b/test/test_invalid_arguments.cpp index 9750a39..bb44f1e 100644 --- a/test/test_invalid_arguments.cpp +++ b/test/test_invalid_arguments.cpp @@ -36,5 +36,6 @@ TEST_CASE("Parse unknown optional argument" * .scan<'u', unsigned long long>() .help("memory in MB to give the VMM when loading"); - REQUIRE_THROWS(bfm.parse_args({ "./test.exe", "-om" })); + REQUIRE_THROWS_WITH_AS(bfm.parse_args({"./test.exe", "-om"}), + "Unknown argument: -om", std::runtime_error); } diff --git a/test/test_version.cpp b/test/test_version.cpp index ad71b25..aba2e26 100644 --- a/test/test_version.cpp +++ b/test/test_version.cpp @@ -16,8 +16,8 @@ TEST_CASE("Users can print version and exit" * test_suite("version") TEST_CASE("Users can disable default -v/--version" * test_suite("version")) { argparse::ArgumentParser program("test", "1.0", argparse::default_arguments::help); - REQUIRE_THROWS_AS(program.parse_args({"test", "--version"}), - std::runtime_error); + REQUIRE_THROWS_WITH_AS(program.parse_args({"test", "--version"}), + "Unknown argument: --version", std::runtime_error); } TEST_CASE("Users can replace default -v/--version" * test_suite("version")) { From 18a229e849b5448ed84987b35158f353cc0c72d5 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Wed, 10 Nov 2021 13:54:25 -0700 Subject: [PATCH 14/61] Add a minimal binary to generate compile_commands.json file clang-tidy needs compile_commands.json to generate a set of source files to parse. tidy-base is not built but is used as a source file in which included headers can be parsed. Without this process, clang-tidy will process many other source files (e.g. doctest.hpp) that we do not want to worry about. Signed-off-by: Sean Robinson --- test/CMakeLists.txt | 7 +++++++ test/tidy-base.cpp | 4 ++++ 2 files changed, 11 insertions(+) create mode 100644 test/tidy-base.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3c502d9..02572a0 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -57,3 +57,10 @@ set_property(TARGET ARGPARSE PROPERTY CXX_STANDARD 17) # Set ${PROJECT_NAME} as the startup project set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ARGPARSE) + +file(GLOB ARGPARSE_LINT_SOURCES + tidy-base.cpp +) +ADD_EXECUTABLE(ARGPARSE_LINT ${ARGPARSE_LINT_SOURCES}) +set_target_properties(ARGPARSE_LINT PROPERTIES OUTPUT_NAME tidy-base) +set_property(TARGET ARGPARSE_LINT PROPERTY CXX_STANDARD 17) diff --git a/test/tidy-base.cpp b/test/tidy-base.cpp new file mode 100644 index 0000000..87b47a1 --- /dev/null +++ b/test/tidy-base.cpp @@ -0,0 +1,4 @@ + +#include "argparse/argparse.hpp" + +int main(int argc, const char* argv[]) {} From 9a9c3042aa63c597751ea8b5754ca7f7e5c9c882 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Wed, 10 Nov 2021 13:54:27 -0700 Subject: [PATCH 15/61] Add a clang-tidy configuration file This file matches the current practices of the argparse project. Signed-off-by: Sean Robinson --- .clang-tidy | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .clang-tidy diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..56d7f64 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,24 @@ +Checks: + -*, + readability-*, + -readability-braces-around-statements, + -readability-container-size-empty, + -readability-else-after-return, + -readability-implicit-bool-conversion, + -readability-function-cognitive-complexity, + -readability-magic-numbers, + -readability-named-parameter, + -readability-qualified-auto, + -readability-static-accessed-through-instance, + +CheckOptions: + - { key: readability-identifier-naming.ClassCase, value: CamelCase } + - { key: readability-identifier-naming.ConstexprVariableCase, value: lower_case } + - { key: readability-identifier-naming.FunctionCase, value: lower_case } + - { key: readability-identifier-naming.LocalVariableIgnoredRegexp, value: "^[a-z][a-z_]+" } + - { key: readability-identifier-naming.NamespaceCase, value: lower_case } + - { key: readability-identifier-naming.PrivateMemberPrefix, value: m } + - { key: readability-identifier-naming.StructCase, value: lower_case } + - { key: readability-identifier-naming.VariableCase, value: camelBack } + +HeaderFilterRegex: '.*' From 8d8282bac36cde915bbbdd2272ea1ff437516bb6 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Wed, 10 Nov 2021 13:54:29 -0700 Subject: [PATCH 16/61] Add Static Analysis action to run on Pull Request Unit test source files are not currently checked. Hopefully, these can be added so that all source files in a pull request are verified. Signed-off-by: Sean Robinson --- .github/workflows/static_analysis.yml | 42 +++++++++++++++++++++++++++ tools/static_analysis_setup.sh | 4 +++ 2 files changed, 46 insertions(+) create mode 100644 .github/workflows/static_analysis.yml create mode 100755 tools/static_analysis_setup.sh diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml new file mode 100644 index 0000000..0d47ee1 --- /dev/null +++ b/.github/workflows/static_analysis.yml @@ -0,0 +1,42 @@ + +name: Static Analysis + +on: pull_request + +jobs: + + static_analysis: + + name: ${{ matrix.toolchain }} + runs-on: ${{ matrix.os }} + + strategy: + + matrix: + + toolchain: + - ubuntu-latest + + include: + - toolchain: ubuntu-latest + os: ubuntu-latest + compiler: clang + + steps: + + - name: Checkout Code + uses: actions/checkout@v2 + + - name: Analyze + uses: JacobDomagala/StaticAnalysis@master + with: + clang_tidy_args: >- + --config-file=$GITHUB_WORKSPACE/.clang-tidy + --extra-arg=-I$GITHUB_WORKSPACE/include --extra-arg=-std=c++17 + cppcheck_args: >- + --enable=all --inconclusive --inline-suppr + -i$GITHUB_WORKSPACE/test/main.cpp + -i$GITHUB_WORKSPACE/test/test_*.cpp + --suppress=missingInclude + --suppress='*:$GITHUB_WORKSPACE/test/doctest.hpp' + init_script: tools/static_analysis_setup.sh diff --git a/tools/static_analysis_setup.sh b/tools/static_analysis_setup.sh new file mode 100755 index 0000000..cd0dc06 --- /dev/null +++ b/tools/static_analysis_setup.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Change to the "test" subdir before "build" subdir is made. +cd test From 6530a067470e6f1b1c398d875b9f6b430b567fa3 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Wed, 10 Nov 2021 13:54:31 -0700 Subject: [PATCH 17/61] Copy more members in ArgumentParser copy constructor This showed as a cppcheck warning: uninitMemberVar. Signed-off-by: Sean Robinson --- include/argparse/argparse.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index aa5f959..ab3c564 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -857,6 +857,9 @@ public: ArgumentParser(const ArgumentParser &other) : mProgramName(other.mProgramName), + mVersion(other.mVersion), + mDescription(other.mDescription), + mEpilog(other.mEpilog), mIsParsed(other.mIsParsed), mPositionalArguments(other.mPositionalArguments), mOptionalArguments(other.mOptionalArguments) { From abb220614129fb96fbf53308d2150dbf8016dddf Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Wed, 10 Nov 2021 13:54:33 -0700 Subject: [PATCH 18/61] Declare lambda parameter and ArgumentParser::print_help as const These were respectively reported as constParameter and functionConst style issues by cppcheck. Signed-off-by: Sean Robinson --- include/argparse/argparse.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index ab3c564..34dab79 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -453,7 +453,7 @@ public: mUsedName = usedName; if (mNumArgs == 0) { mValues.emplace_back(mImplicitValue); - std::visit([](auto &aAction) { aAction({}); }, mAction); + std::visit([](const auto &aAction) { aAction({}); }, mAction); return start; } else if (mNumArgs <= std::distance(start, end)) { if (auto expected = maybe_nargs()) { @@ -1049,7 +1049,7 @@ public: // Printing the one and only help message // I've stuck with a simple message format, nothing fancy. [[deprecated("Use cout << program; instead. See also help().")]] std::string - print_help() { + print_help() const { auto out = help(); std::cout << out.rdbuf(); return out.str(); From c0bbcf613c0fc6c89391c45b1e44d147a4e91971 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Wed, 10 Nov 2021 13:54:35 -0700 Subject: [PATCH 19/61] Remove sentry check in ArgumentParser::operator<< cppcheck reports "Variable 'sen' is assigned a value that is never used. [unreadVariable]" for this line. As far as I understand, std::ostream::sentry is used to prepare access to the stream buffer. But, we are never directly accessing the stream buffer. Stream access in this function uses other operator<< functions. Most noise in this patch is about unindenting after if() removal. Signed-off-by: Sean Robinson --- include/argparse/argparse.hpp | 60 +++++++++++++++++------------------ 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 34dab79..2aa06fd 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -1002,39 +1002,37 @@ public: // Print help message friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) -> std::ostream & { - if (auto sen = std::ostream::sentry(stream)) { - stream.setf(std::ios_base::left); - stream << "Usage: " << parser.mProgramName << " [options] "; - std::size_t tLongestArgumentLength = parser.get_length_of_longest_argument(); + stream.setf(std::ios_base::left); + stream << "Usage: " << parser.mProgramName << " [options] "; + std::size_t tLongestArgumentLength = parser.get_length_of_longest_argument(); - for (const auto &argument : parser.mPositionalArguments) { - stream << argument.mNames.front() << " "; - } - stream << "\n\n"; - - if (!parser.mDescription.empty()) - stream << parser.mDescription << "\n\n"; - - if (!parser.mPositionalArguments.empty()) - stream << "Positional arguments:\n"; - - for (const auto &mPositionalArgument : parser.mPositionalArguments) { - stream.width(tLongestArgumentLength); - stream << mPositionalArgument; - } - - if (!parser.mOptionalArguments.empty()) - stream << (parser.mPositionalArguments.empty() ? "" : "\n") - << "Optional arguments:\n"; - - for (const auto &mOptionalArgument : parser.mOptionalArguments) { - stream.width(tLongestArgumentLength); - stream << mOptionalArgument; - } - - if (!parser.mEpilog.empty()) - stream << parser.mEpilog << "\n\n"; + for (const auto &argument : parser.mPositionalArguments) { + stream << argument.mNames.front() << " "; } + stream << "\n\n"; + + if (!parser.mDescription.empty()) + stream << parser.mDescription << "\n\n"; + + if (!parser.mPositionalArguments.empty()) + stream << "Positional arguments:\n"; + + for (const auto &mPositionalArgument : parser.mPositionalArguments) { + stream.width(tLongestArgumentLength); + stream << mPositionalArgument; + } + + if (!parser.mOptionalArguments.empty()) + stream << (parser.mPositionalArguments.empty() ? "" : "\n") + << "Optional arguments:\n"; + + for (const auto &mOptionalArgument : parser.mOptionalArguments) { + stream.width(tLongestArgumentLength); + stream << mOptionalArgument; + } + + if (!parser.mEpilog.empty()) + stream << parser.mEpilog << "\n\n"; return stream; } From 6246a9df0e001b9f7c06757f91fb6f96ca9ea5af Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Tue, 11 Jan 2022 06:48:00 -0700 Subject: [PATCH 20/61] Change Static Analysis trigger event to pull_request_target The GH security model restricts comment posting from PR actions. StaticAnalysis has added support for pull_request_target to mitigate risks while still allowing comments by the bot. Signed-off-by: Sean Robinson --- .github/workflows/static_analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 0d47ee1..d5a2e50 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -1,7 +1,7 @@ name: Static Analysis -on: pull_request +on: pull_request_target jobs: From 736099ef3f1852bf4f982171b48ce9c2c0a9532f Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Thu, 9 Dec 2021 12:17:55 -0700 Subject: [PATCH 21/61] Remove Windows Server 2016 from test matrix The Windows Server 2016 test environment is scheduled for removal on March 15, 2022. Signed-off-by: Sean Robinson --- .github/workflows/ci.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7e4648..32379e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,6 @@ jobs: - macos-latest-clang - ubuntu-latest-clang - ubuntu-latest-gcc - - windows-2016-msvc - windows-latest-msvc include: @@ -42,11 +41,6 @@ jobs: c_compiler: msvc cxx_compiler: msvc - - toolchain: windows-2016-msvc - os: windows-2016 - c_compiler: msvc - cxx_compiler: msvc - steps: - name: Checkout Code From 5a1b4c30680f923192c73ee828f9cdad3ab0b746 Mon Sep 17 00:00:00 2001 From: Sam4uk Date: Fri, 4 Feb 2022 23:36:26 +0200 Subject: [PATCH 22/61] edit CMakeLists.txt --- CMakeLists.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index db224c0..da19424 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,11 @@ cmake_minimum_required(VERSION 3.8) -project(argparse VERSION 1.0.0 LANGUAGES CXX) -set("PROJECT_DESCRIPTION" "A single header argument parser for C++17") -set("PROJECT_HOMEPAGE_URL" "https://github.com/p-ranav/argparse") +project(argparse + VERSION 1.0.0 + DESCRIPTION "A single header argument parser for C++17" + HOMEPAGE_URL "https://github.com/p-ranav/argparse" + LANGUAGES CXX +) option(ARGPARSE_BUILD_TESTS OFF) option(ARGPARSE_LONG_VERSION_ARG_ONLY OFF) From 97204363b56fa941a7c688b93fc214b81e007b71 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 7 Feb 2022 11:51:22 -0700 Subject: [PATCH 23/61] Refactor configuration for StaticAnalysis Upstream StaticAnalysis looks to be changing for more flexibility with source files to process. While these changes may eventually benefit argparse, the public interfaces are in flux and we need a stable tool. argparse also needs a SA change which is not yet upstream. Trying to run clang-tidy via StaticAnalysis on a single file in a directory with many source file is not easy, so move the analysis kernel to a location (i.e. tools) where it is the only C++ source file. Another benefit is cppcheck no longer needs to be told to ignore the test sources. Signed-off-by: Sean Robinson --- .github/workflows/static_analysis.yml | 4 +--- test/CMakeLists.txt | 7 ------- tools/CMakeLists.txt | 10 ++++++++++ tools/static_analysis_setup.sh | 4 ++-- {test => tools}/tidy-base.cpp | 0 5 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 tools/CMakeLists.txt rename {test => tools}/tidy-base.cpp (100%) diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index d5a2e50..2724a71 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -28,15 +28,13 @@ jobs: uses: actions/checkout@v2 - name: Analyze - uses: JacobDomagala/StaticAnalysis@master + uses: skrobinson/StaticAnalysis@stable-for-argparse with: clang_tidy_args: >- --config-file=$GITHUB_WORKSPACE/.clang-tidy --extra-arg=-I$GITHUB_WORKSPACE/include --extra-arg=-std=c++17 cppcheck_args: >- --enable=all --inconclusive --inline-suppr - -i$GITHUB_WORKSPACE/test/main.cpp - -i$GITHUB_WORKSPACE/test/test_*.cpp --suppress=missingInclude --suppress='*:$GITHUB_WORKSPACE/test/doctest.hpp' init_script: tools/static_analysis_setup.sh diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 02572a0..3c502d9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -57,10 +57,3 @@ set_property(TARGET ARGPARSE PROPERTY CXX_STANDARD 17) # Set ${PROJECT_NAME} as the startup project set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ARGPARSE) - -file(GLOB ARGPARSE_LINT_SOURCES - tidy-base.cpp -) -ADD_EXECUTABLE(ARGPARSE_LINT ${ARGPARSE_LINT_SOURCES}) -set_target_properties(ARGPARSE_LINT PROPERTIES OUTPUT_NAME tidy-base) -set_property(TARGET ARGPARSE_LINT PROPERTY CXX_STANDARD 17) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..cff613b --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.12) +project(argparse) + +file(GLOB ARGPARSE_LINT_SOURCES + tidy-base.cpp +) +ADD_EXECUTABLE(ARGPARSE_LINT ${ARGPARSE_LINT_SOURCES}) +set_target_properties(ARGPARSE_LINT PROPERTIES OUTPUT_NAME tidy-base) +set_property(TARGET ARGPARSE_LINT PROPERTY CXX_STANDARD 17) +INCLUDE_DIRECTORIES("../include" ".") diff --git a/tools/static_analysis_setup.sh b/tools/static_analysis_setup.sh index cd0dc06..350eb82 100755 --- a/tools/static_analysis_setup.sh +++ b/tools/static_analysis_setup.sh @@ -1,4 +1,4 @@ #!/bin/bash -# Change to the "test" subdir before "build" subdir is made. -cd test +# Change to the "tools" subdir before "build" subdir is made. +cd tools diff --git a/test/tidy-base.cpp b/tools/tidy-base.cpp similarity index 100% rename from test/tidy-base.cpp rename to tools/tidy-base.cpp From 06f687b5940b86845b7bdce2e998bea1b3690f5d Mon Sep 17 00:00:00 2001 From: Sam4uk Date: Mon, 7 Feb 2022 22:09:36 +0200 Subject: [PATCH 24/61] update `cmake_minimum_required` to 3.12.4 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index da19424..8a962b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.12.4) project(argparse VERSION 1.0.0 From 158b8a0d2f9f62377010a5d35157a9abb5b6565f Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 7 Feb 2022 13:10:03 -0700 Subject: [PATCH 25/61] Enable clang-tidy readability-braces-around-statements check All tests still pass. Signed-off-by: Sean Robinson --- .clang-tidy | 1 - include/argparse/argparse.hpp | 178 +++++++++++++++++++++------------- 2 files changed, 108 insertions(+), 71 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 56d7f64..e156a4a 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,7 +1,6 @@ Checks: -*, readability-*, - -readability-braces-around-statements, -readability-container-size-empty, -readability-else-after-return, -readability-implicit-bool-conversion, diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 2aa06fd..2c31cd9 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -101,13 +101,15 @@ template std::string repr(T const &val) { std::next(val.begin()), std::next(val.begin(), std::min(size, repr_max_container_size) - 1), [&out](const auto &v) { out << " " << repr(v); }); - if (size <= repr_max_container_size) + if (size <= repr_max_container_size) { out << " "; - else + } else { out << "..."; + } } - if (size > 0) + if (size > 0) { out << repr(*std::prev(val.end())); + } out << "}"; return out.str(); } else if constexpr (is_streamable_v) { @@ -197,10 +199,11 @@ inline auto do_from_chars(std::string_view s) -> T { auto [first, last] = pointer_range(s); auto [ptr, ec] = std::from_chars(first, last, x, Param); if (ec == std::errc()) { - if (ptr == last) + if (ptr == last) { return x; - else + } else { throw std::invalid_argument{"pattern does not match to the end"}; + } } else if (ec == std::errc::invalid_argument) { throw std::invalid_argument{"pattern not found"}; } else if (ec == std::errc::result_out_of_range) { @@ -218,21 +221,23 @@ template struct parse_number { template struct parse_number { auto operator()(std::string_view s) -> T { - if (auto [ok, rest] = consume_hex_prefix(s); ok) + if (auto [ok, rest] = consume_hex_prefix(s); ok) { return do_from_chars(rest); - else + } else { throw std::invalid_argument{"pattern not found"}; + } } }; template struct parse_number { auto operator()(std::string_view s) -> T { - if (auto [ok, rest] = consume_hex_prefix(s); ok) + if (auto [ok, rest] = consume_hex_prefix(s); ok) { return do_from_chars(rest); - else if (starts_with("0"sv, s)) + } else if (starts_with("0"sv, s)) { return do_from_chars(rest); - else + } else { return do_from_chars(rest); + } } }; @@ -246,18 +251,20 @@ template <> constexpr auto generic_strtod = strtold; } // namespace template inline auto do_strtod(std::string const &s) -> T { - if (isspace(static_cast(s[0])) || s[0] == '+') + if (isspace(static_cast(s[0])) || s[0] == '+') { throw std::invalid_argument{"pattern not found"}; + } auto [first, last] = pointer_range(s); char *ptr; errno = 0; if (auto x = generic_strtod(first, &ptr); errno == 0) { - if (ptr == last) + if (ptr == last) { return x; - else + } else { throw std::invalid_argument{"pattern does not match to the end"}; + } } else if (errno == ERANGE) { throw std::range_error{"not representable"}; } else { @@ -267,9 +274,10 @@ template inline auto do_strtod(std::string const &s) -> T { template struct parse_number { auto operator()(std::string const &s) -> T { - if (auto r = consume_hex_prefix(s); r.is_hexadecimal) + if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { throw std::invalid_argument{ "chars_format::general does not parse hexfloat"}; + } return do_strtod(s); } @@ -277,8 +285,9 @@ template struct parse_number { template struct parse_number { auto operator()(std::string const &s) -> T { - if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) + if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) { throw std::invalid_argument{"chars_format::hex parses hexfloat"}; + } return do_strtod(s); } @@ -286,12 +295,14 @@ template struct parse_number { template struct parse_number { auto operator()(std::string const &s) -> T { - if (auto r = consume_hex_prefix(s); r.is_hexadecimal) + if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { throw std::invalid_argument{ "chars_format::scientific does not parse hexfloat"}; - if (s.find_first_of("eE") == s.npos) + } + if (s.find_first_of("eE") == s.npos) { throw std::invalid_argument{ "chars_format::scientific requires exponent part"}; + } return do_strtod(s); } @@ -299,12 +310,14 @@ template struct parse_number { template struct parse_number { auto operator()(std::string const &s) -> T { - if (auto r = consume_hex_prefix(s); r.is_hexadecimal) + if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { throw std::invalid_argument{ "chars_format::fixed does not parse hexfloat"}; - if (s.find_first_of("eE") != s.npos) + } + if (s.find_first_of("eE") != s.npos) { throw std::invalid_argument{ "chars_format::fixed does not parse exponent part"}; + } return do_strtod(s); } @@ -375,15 +388,16 @@ public: using action_type = std::conditional_t< std::is_void_v>, void_action, valued_action>; - if constexpr (sizeof...(Args) == 0) + if constexpr (sizeof...(Args) == 0) { mAction.emplace(std::forward(aAction)); - else + } else { mAction.emplace( [f = std::forward(aAction), tup = std::make_tuple(std::forward(aBound)...)]( std::string const &opt) mutable { return details::apply_plus_one(f, tup, opt); }); + } return *this; } @@ -400,40 +414,42 @@ public: return ((c == x) || ...); }; - if constexpr (is_one_of(Shape, 'd') && details::standard_integer) + if constexpr (is_one_of(Shape, 'd') && details::standard_integer) { action(details::parse_number()); - else if constexpr (is_one_of(Shape, 'i') && details::standard_integer) + } else if constexpr (is_one_of(Shape, 'i') && details::standard_integer) { action(details::parse_number()); - else if constexpr (is_one_of(Shape, 'u') && - details::standard_unsigned_integer) + } else if constexpr (is_one_of(Shape, 'u') && + details::standard_unsigned_integer) { action(details::parse_number()); - else if constexpr (is_one_of(Shape, 'o') && - details::standard_unsigned_integer) + } else if constexpr (is_one_of(Shape, 'o') && + details::standard_unsigned_integer) { action(details::parse_number()); - else if constexpr (is_one_of(Shape, 'x', 'X') && - details::standard_unsigned_integer) + } else if constexpr (is_one_of(Shape, 'x', 'X') && + details::standard_unsigned_integer) { action(details::parse_number()); - else if constexpr (is_one_of(Shape, 'a', 'A') && - std::is_floating_point_v) + } else if constexpr (is_one_of(Shape, 'a', 'A') && + std::is_floating_point_v) { action(details::parse_number()); - else if constexpr (is_one_of(Shape, 'e', 'E') && - std::is_floating_point_v) + } else if constexpr (is_one_of(Shape, 'e', 'E') && + std::is_floating_point_v) { action(details::parse_number()); - else if constexpr (is_one_of(Shape, 'f', 'F') && - std::is_floating_point_v) + } else if constexpr (is_one_of(Shape, 'f', 'F') && + std::is_floating_point_v) { action(details::parse_number()); - else if constexpr (is_one_of(Shape, 'g', 'G') && - std::is_floating_point_v) + } else if constexpr (is_one_of(Shape, 'g', 'G') && + std::is_floating_point_v) { action(details::parse_number()); - else + } else { static_assert(alignof(T) == 0, "No scan specification for T"); + } return *this; } Argument &nargs(int aNumArgs) { - if (aNumArgs < 0) + if (aNumArgs < 0) { throw std::logic_error("Number of arguments must be non-negative"); + } mNumArgs = aNumArgs; return *this; } @@ -471,8 +487,9 @@ public: void operator()(void_action &f) { std::for_each(first, last, f); if (!self.mDefaultValue.has_value()) { - if (auto expected = self.maybe_nargs()) + if (auto expected = self.maybe_nargs()) { self.mValues.resize(*expected); + } } } @@ -517,8 +534,9 @@ public: } else { if (mValues.size() != expected && !mDefaultValue.has_value()) { std::stringstream stream; - if (!mUsedName.empty()) + if (!mUsedName.empty()) { stream << mUsedName << ": "; + } stream << *expected << " argument(s) expected. " << mValues.size() << " provided."; throw std::runtime_error(stream.str()); @@ -528,10 +546,11 @@ public: } auto maybe_nargs() const -> std::optional { - if (mNumArgs < 0) + if (mNumArgs < 0) { return std::nullopt; - else + } else { return static_cast(mNumArgs); + } } std::size_t get_arguments_length() const { @@ -549,12 +568,14 @@ public: std::ostream_iterator(nameStream, " ")); stream << nameStream.str() << "\t" << argument.mHelp; if (argument.mDefaultValue.has_value()) { - if (!argument.mHelp.empty()) + if (!argument.mHelp.empty()) { stream << " "; + } stream << "[default: " << argument.mDefaultValueRepr << "]"; } else if (argument.mIsRequired) { - if (!argument.mHelp.empty()) + if (!argument.mHelp.empty()) { stream << " "; + } stream << "[required]"; } stream << "\n"; @@ -586,10 +607,11 @@ private: static constexpr int eof = std::char_traits::eof(); static auto lookahead(std::string_view s) -> int { - if (s.empty()) + if (s.empty()) { return eof; - else + } else { return static_cast(static_cast(s[0])); + } } /* @@ -648,10 +670,11 @@ private: switch (lookahead(s)) { case '0': { s.remove_prefix(1); - if (s.empty()) + if (s.empty()) { return true; - else + } else { goto integer_part; + } } case '1': case '2': @@ -663,10 +686,11 @@ private: case '8': case '9': { s = consume_digits(s); - if (s.empty()) + if (s.empty()) { return true; - else + } else { goto integer_part_consumed; + } } case '.': { s.remove_prefix(1); @@ -682,10 +706,11 @@ private: switch (lookahead(s)) { case '.': { s.remove_prefix(1); - if (is_digit(lookahead(s))) + if (is_digit(lookahead(s))) { goto post_decimal_point; - else + } else { goto exponent_part_opt; + } } case 'e': case 'E': { @@ -748,10 +773,11 @@ private: return true; case '-': { aName.remove_prefix(1); - if (aName.empty()) + if (aName.empty()) { return true; - else + } else { return is_decimal_literal(aName); + } } default: return true; @@ -764,10 +790,11 @@ private: */ template T get() const { if (!mValues.empty()) { - if constexpr (details::is_container_v) + if constexpr (details::is_container_v) { return any_cast_container(mValues); - else + } else { return std::any_cast(mValues.front()); + } } if (mDefaultValue.has_value()) { return std::any_cast(mDefaultValue); @@ -781,15 +808,17 @@ private: * @returns The stored value if any, std::nullopt otherwise. */ template auto present() const -> std::optional { - if (mDefaultValue.has_value()) + if (mDefaultValue.has_value()) { throw std::logic_error("Argument with default value always presents"); + } - if (mValues.empty()) + if (mValues.empty()) { return std::nullopt; - else if constexpr (details::is_container_v) + } else if constexpr (details::is_container_v) { return any_cast_container(mValues); - else + } else { return std::any_cast(mValues.front()); + } } template @@ -864,11 +893,13 @@ public: mPositionalArguments(other.mPositionalArguments), mOptionalArguments(other.mOptionalArguments) { for (auto it = std::begin(mPositionalArguments); it != std::end(mPositionalArguments); - ++it) + ++it) { index_argument(it); + } for (auto it = std::begin(mOptionalArguments); it != std::end(mOptionalArguments); - ++it) + ++it) { index_argument(it); + } } ArgumentParser &operator=(const ArgumentParser &other) { @@ -884,9 +915,10 @@ public: auto tArgument = mOptionalArguments.emplace(cend(mOptionalArguments), array_of_sv{Fargs...}); - if (!tArgument->mIsOptional) + if (!tArgument->mIsOptional) { mPositionalArguments.splice(cend(mPositionalArguments), mOptionalArguments, tArgument); + } index_argument(tArgument); return *tArgument; @@ -1011,28 +1043,32 @@ public: } stream << "\n\n"; - if (!parser.mDescription.empty()) + if (!parser.mDescription.empty()) { stream << parser.mDescription << "\n\n"; + } - if (!parser.mPositionalArguments.empty()) + if (!parser.mPositionalArguments.empty()) { stream << "Positional arguments:\n"; + } for (const auto &mPositionalArgument : parser.mPositionalArguments) { stream.width(tLongestArgumentLength); stream << mPositionalArgument; } - if (!parser.mOptionalArguments.empty()) + if (!parser.mOptionalArguments.empty()) { stream << (parser.mPositionalArguments.empty() ? "" : "\n") << "Optional arguments:\n"; + } for (const auto &mOptionalArgument : parser.mOptionalArguments) { stream.width(tLongestArgumentLength); stream << mOptionalArgument; } - if (!parser.mEpilog.empty()) + if (!parser.mEpilog.empty()) { stream << parser.mEpilog << "\n\n"; + } return stream; } @@ -1114,8 +1150,9 @@ private: // Used by print_help. std::size_t get_length_of_longest_argument() const { - if (mArgumentMap.empty()) + if (mArgumentMap.empty()) { return 0; + } std::vector argumentLengths(mArgumentMap.size()); std::transform(std::begin(mArgumentMap), std::end(mArgumentMap), std::begin(argumentLengths), [](const auto &argPair) { @@ -1129,8 +1166,9 @@ private: using list_iterator = std::list::iterator; void index_argument(list_iterator argIt) { - for (auto &mName : std::as_const(argIt->mNames)) + for (auto &mName : std::as_const(argIt->mNames)) { mArgumentMap.insert_or_assign(mName, argIt); + } } std::string mProgramName; From 6a8b1318ec485b188cc84d3aa93ed767848256c2 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 7 Feb 2022 14:27:06 -0700 Subject: [PATCH 26/61] Enable clang-tidy readability-container-size-empty check Signed-off-by: Sean Robinson --- .clang-tidy | 1 - include/argparse/argparse.hpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index e156a4a..f48b448 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,7 +1,6 @@ Checks: -*, readability-*, - -readability-container-size-empty, -readability-else-after-return, -readability-implicit-bool-conversion, -readability-function-cognitive-complexity, diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 2c31cd9..f5bb232 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -525,7 +525,7 @@ public: stream << mNames[0] << ": required."; throw std::runtime_error(stream.str()); } - if (mIsUsed && mIsRequired && mValues.size() == 0) { + if (mIsUsed && mIsRequired && mValues.empty()) { std::stringstream stream; stream << mUsedName << ": no value provided."; throw std::runtime_error(stream.str()); From 7cbc66f65b6b46ca196e7ab4e4944315905953b3 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 7 Feb 2022 14:27:06 -0700 Subject: [PATCH 27/61] Return default_arguments from operator& of two default_arguments operator& should return combined values of the same type, not a new type (i.e. bool). This is much more verbose, but easier to reason about without implied conversion. Signed-off-by: Sean Robinson --- include/argparse/argparse.hpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index f5bb232..317ed2b 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -332,8 +332,11 @@ enum class default_arguments : unsigned int { all = help | version, }; -inline bool operator& (const default_arguments &a, const default_arguments &b) { - return static_cast(a) & static_cast(b); +inline default_arguments operator& (const default_arguments &a, + const default_arguments &b) { + return static_cast( + static_cast::type>(a) & + static_cast::type>(b)); } class ArgumentParser; @@ -857,7 +860,7 @@ public: std::string aVersion = "1.0", default_arguments aArgs = default_arguments::all) : mProgramName(std::move(aProgramName)), mVersion(std::move(aVersion)) { - if (aArgs & default_arguments::help) { + if ((aArgs & default_arguments::help) == default_arguments::help) { add_argument("-h", "--help") .action([&](const auto &) { std::cout << help().str(); @@ -868,7 +871,7 @@ public: .implicit_value(true) .nargs(0); } - if (aArgs & default_arguments::version) { + if ((aArgs & default_arguments::version) == default_arguments::version) { add_argument("-v", "--version") .action([&](const auto &) { std::cout << mVersion; From b918763adf032daa1ef8df27873c8c9511438886 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 7 Feb 2022 14:27:06 -0700 Subject: [PATCH 28/61] Enable clang-tidy readability-implicit-bool-conversion check Enabling this check caught the implicit bool conversion with default_arguments::operator& fixed in the previous commit. Signed-off-by: Sean Robinson --- .clang-tidy | 1 - 1 file changed, 1 deletion(-) diff --git a/.clang-tidy b/.clang-tidy index f48b448..e099fa8 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,7 +2,6 @@ Checks: -*, readability-*, -readability-else-after-return, - -readability-implicit-bool-conversion, -readability-function-cognitive-complexity, -readability-magic-numbers, -readability-named-parameter, From 9eb1fe5cefa7b87e7f9d1c371e5eaf763d7955a3 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 7 Feb 2022 14:29:25 -0700 Subject: [PATCH 29/61] Enable clang-tidy readability-magic-numbers check Adds names for integer bases used with details::parse_number. Signed-off-by: Sean Robinson --- .clang-tidy | 1 - include/argparse/argparse.hpp | 22 +++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index e099fa8..812baac 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -3,7 +3,6 @@ Checks: readability-*, -readability-else-after-return, -readability-function-cognitive-complexity, - -readability-magic-numbers, -readability-named-parameter, -readability-qualified-auto, -readability-static-accessed-through-instance, diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 317ed2b..ec27b9d 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -140,6 +140,10 @@ constexpr bool standard_unsigned_integer = true; } // namespace +constexpr int radix_8 = 8; +constexpr int radix_10 = 10; +constexpr int radix_16 = 16; + template constexpr bool standard_integer = standard_signed_integer || standard_unsigned_integer; @@ -219,10 +223,10 @@ template struct parse_number { } }; -template struct parse_number { +template struct parse_number { auto operator()(std::string_view s) -> T { if (auto [ok, rest] = consume_hex_prefix(s); ok) { - return do_from_chars(rest); + return do_from_chars(rest); } else { throw std::invalid_argument{"pattern not found"}; } @@ -232,11 +236,11 @@ template struct parse_number { template struct parse_number { auto operator()(std::string_view s) -> T { if (auto [ok, rest] = consume_hex_prefix(s); ok) { - return do_from_chars(rest); + return do_from_chars(rest); } else if (starts_with("0"sv, s)) { - return do_from_chars(rest); + return do_from_chars(rest); } else { - return do_from_chars(rest); + return do_from_chars(rest); } } }; @@ -418,18 +422,18 @@ public: }; if constexpr (is_one_of(Shape, 'd') && details::standard_integer) { - action(details::parse_number()); + action(details::parse_number()); } else if constexpr (is_one_of(Shape, 'i') && details::standard_integer) { action(details::parse_number()); } else if constexpr (is_one_of(Shape, 'u') && details::standard_unsigned_integer) { - action(details::parse_number()); + action(details::parse_number()); } else if constexpr (is_one_of(Shape, 'o') && details::standard_unsigned_integer) { - action(details::parse_number()); + action(details::parse_number()); } else if constexpr (is_one_of(Shape, 'x', 'X') && details::standard_unsigned_integer) { - action(details::parse_number()); + action(details::parse_number()); } else if constexpr (is_one_of(Shape, 'a', 'A') && std::is_floating_point_v) { action(details::parse_number()); From b5fb663bc8e6aad92c84588cde750815258cbcab Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 7 Feb 2022 14:29:32 -0700 Subject: [PATCH 30/61] Enable clang-tidy readability-named-parameter check Adds names recommended by clang-tidy (e.g. "unused"). Note that clang-tidy v12 appears to detect unnamed parameters in lambdas, while clang-tidy v13 does not. Signed-off-by: Sean Robinson --- .clang-tidy | 1 - include/argparse/argparse.hpp | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 812baac..107ed93 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -3,7 +3,6 @@ Checks: readability-*, -readability-else-after-return, -readability-function-cognitive-complexity, - -readability-named-parameter, -readability-qualified-auto, -readability-static-accessed-through-instance, diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index ec27b9d..8bce3ab 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -150,7 +150,7 @@ constexpr bool standard_integer = template constexpr decltype(auto) apply_plus_one_impl(F &&f, Tuple &&t, Extra &&x, - std::index_sequence) { + std::index_sequence unused) { return std::invoke(std::forward(f), std::get(std::forward(t))..., std::forward(x)); } @@ -347,11 +347,11 @@ class ArgumentParser; class Argument { friend class ArgumentParser; - friend auto operator<<(std::ostream &, ArgumentParser const &) + friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) -> std::ostream &; template - explicit Argument(std::string_view(&&a)[N], std::index_sequence) + explicit Argument(std::string_view(&&a)[N], std::index_sequence unused) : mIsOptional((is_optional(a[I]) || ...)), mIsRequired(false), mIsRepeatable(false), mIsUsed(false) { ((void)mNames.emplace_back(a[I]), ...); @@ -866,7 +866,7 @@ public: : mProgramName(std::move(aProgramName)), mVersion(std::move(aVersion)) { if ((aArgs & default_arguments::help) == default_arguments::help) { add_argument("-h", "--help") - .action([&](const auto &) { + .action([&](const auto &unused) { std::cout << help().str(); std::exit(0); }) @@ -877,7 +877,7 @@ public: } if ((aArgs & default_arguments::version) == default_arguments::version) { add_argument("-v", "--version") - .action([&](const auto &) { + .action([&](const auto &unused) { std::cout << mVersion; std::exit(0); }) From bd4837f2404f20c0516d6e3579b7a0173df37d39 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Wed, 9 Feb 2022 15:27:16 -0700 Subject: [PATCH 31/61] Enable clang-tidy's readability-qualified-auto check Following the clang-tidy suggested fix in consume_digits causes compile failures with MSVC 19.29 in our CI. Signed-off-by: Sean Robinson --- .clang-tidy | 1 - include/argparse/argparse.hpp | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 107ed93..419f5ee 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -3,7 +3,6 @@ Checks: readability-*, -readability-else-after-return, -readability-function-cognitive-complexity, - -readability-qualified-auto, -readability-static-accessed-through-instance, CheckOptions: diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 8bce3ab..ddade05 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -670,6 +670,7 @@ private: // precondition: we have consumed or will consume at least one digit auto consume_digits = [=](std::string_view s) { + // NOLINTNEXTLINE(readability-qualified-auto) auto it = std::find_if_not(std::begin(s), std::end(s), is_digit); return s.substr(it - std::begin(s)); }; @@ -936,12 +937,12 @@ public: template ArgumentParser &add_parents(const Targs &... Fargs) { for (const ArgumentParser &tParentParser : {std::ref(Fargs)...}) { - for (auto &tArgument : tParentParser.mPositionalArguments) { + for (const auto &tArgument : tParentParser.mPositionalArguments) { auto it = mPositionalArguments.insert(cend(mPositionalArguments), tArgument); index_argument(it); } - for (auto &tArgument : tParentParser.mOptionalArguments) { + for (const auto &tArgument : tParentParser.mOptionalArguments) { auto it = mOptionalArguments.insert(cend(mOptionalArguments), tArgument); index_argument(it); @@ -1173,7 +1174,7 @@ private: using list_iterator = std::list::iterator; void index_argument(list_iterator argIt) { - for (auto &mName : std::as_const(argIt->mNames)) { + for (const auto &mName : std::as_const(argIt->mNames)) { mArgumentMap.insert_or_assign(mName, argIt); } } From c25a959597671a38395f0947f96efd9d188fe77d Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 7 Feb 2022 14:29:32 -0700 Subject: [PATCH 32/61] Enable clang-tidy readability-static-accessed-through-instance check Signed-off-by: Sean Robinson --- .clang-tidy | 1 - include/argparse/argparse.hpp | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 419f5ee..c1dd300 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -3,7 +3,6 @@ Checks: readability-*, -readability-else-after-return, -readability-function-cognitive-complexity, - -readability-static-accessed-through-instance, CheckOptions: - { key: readability-identifier-naming.ClassCase, value: CamelCase } diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index ddade05..112dbb0 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -303,7 +303,7 @@ template struct parse_number { throw std::invalid_argument{ "chars_format::scientific does not parse hexfloat"}; } - if (s.find_first_of("eE") == s.npos) { + if (s.find_first_of("eE") == std::string::npos) { throw std::invalid_argument{ "chars_format::scientific requires exponent part"}; } @@ -318,7 +318,7 @@ template struct parse_number { throw std::invalid_argument{ "chars_format::fixed does not parse hexfloat"}; } - if (s.find_first_of("eE") != s.npos) { + if (s.find_first_of("eE") != std::string::npos) { throw std::invalid_argument{ "chars_format::fixed does not parse exponent part"}; } From 0b74da54d43f021325e2a1cbfb78a7adc6fda9a9 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 7 Feb 2022 14:29:32 -0700 Subject: [PATCH 33/61] Enable clang-tidy cppcoreguidelines-special-member-functions check Also adds a default destructor, as recommended by the check. Signed-off-by: Sean Robinson --- .clang-tidy | 1 + include/argparse/argparse.hpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.clang-tidy b/.clang-tidy index c1dd300..6dd99da 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,5 +1,6 @@ Checks: -*, + cppcoreguidelines-special-member-functions, readability-*, -readability-else-after-return, -readability-function-cognitive-complexity, diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 112dbb0..9b08ea0 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -910,6 +910,8 @@ public: } } + ~ArgumentParser() = default; + ArgumentParser &operator=(const ArgumentParser &other) { auto tmp = other; std::swap(*this, tmp); From 3c317ddd2d9537133bba0e04f59159098d5f43d0 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 7 Feb 2022 14:29:32 -0700 Subject: [PATCH 34/61] Enable clang-tidy clang-analyzer default checks Surprisingly, no fixes are required in argparse when turning on this suite of checks. Signed-off-by: Sean Robinson --- .clang-tidy | 1 + 1 file changed, 1 insertion(+) diff --git a/.clang-tidy b/.clang-tidy index 6dd99da..8977e68 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,5 +1,6 @@ Checks: -*, + clang-analyzer-*, cppcoreguidelines-special-member-functions, readability-*, -readability-else-after-return, From d0a492ccbaf01ef924e473a87ed6c33c2fed9797 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 7 Feb 2022 14:29:32 -0700 Subject: [PATCH 35/61] Enable clang-tidy readability-else-after-return check A common pattern in the previous code was goto/return/throw if a condition is true, else goto/return/throw something different. The new pattern uses the fact that the second goto/return/throw is only reachable when the first goto/return/throw is not called. Signed-off-by: Sean Robinson --- .clang-tidy | 1 - include/argparse/argparse.hpp | 125 ++++++++++++++++------------------ 2 files changed, 57 insertions(+), 69 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 8977e68..62c77f1 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -3,7 +3,6 @@ Checks: clang-analyzer-*, cppcoreguidelines-special-member-functions, readability-*, - -readability-else-after-return, -readability-function-cognitive-complexity, CheckOptions: diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 9b08ea0..483efb9 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -192,9 +192,8 @@ constexpr auto consume_hex_prefix(std::string_view s) if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { s.remove_prefix(2); return {true, s}; - } else { - return {false, s}; } + return {false, s}; } template @@ -205,16 +204,16 @@ inline auto do_from_chars(std::string_view s) -> T { if (ec == std::errc()) { if (ptr == last) { return x; - } else { - throw std::invalid_argument{"pattern does not match to the end"}; } - } else if (ec == std::errc::invalid_argument) { - throw std::invalid_argument{"pattern not found"}; - } else if (ec == std::errc::result_out_of_range) { - throw std::range_error{"not representable"}; - } else { - return x; // unreachable + throw std::invalid_argument{"pattern does not match to the end"}; } + if (ec == std::errc::invalid_argument) { + throw std::invalid_argument{"pattern not found"}; + } + if (ec == std::errc::result_out_of_range) { + throw std::range_error{"not representable"}; + } + return x; // unreachable } template struct parse_number { @@ -227,21 +226,21 @@ template struct parse_number { auto operator()(std::string_view s) -> T { if (auto [ok, rest] = consume_hex_prefix(s); ok) { return do_from_chars(rest); - } else { - throw std::invalid_argument{"pattern not found"}; } + throw std::invalid_argument{"pattern not found"}; } }; template struct parse_number { auto operator()(std::string_view s) -> T { - if (auto [ok, rest] = consume_hex_prefix(s); ok) { + auto [ok, rest] = consume_hex_prefix(s); + if (ok) { return do_from_chars(rest); - } else if (starts_with("0"sv, s)) { - return do_from_chars(rest); - } else { - return do_from_chars(rest); } + if (starts_with("0"sv, s)) { + return do_from_chars(rest); + } + return do_from_chars(rest); } }; @@ -263,17 +262,17 @@ template inline auto do_strtod(std::string const &s) -> T { char *ptr; errno = 0; - if (auto x = generic_strtod(first, &ptr); errno == 0) { + auto x = generic_strtod(first, &ptr); + if (errno == 0) { if (ptr == last) { return x; - } else { - throw std::invalid_argument{"pattern does not match to the end"}; } - } else if (errno == ERANGE) { - throw std::range_error{"not representable"}; - } else { - return x; // unreachable + throw std::invalid_argument{"pattern does not match to the end"}; } + if (errno == ERANGE) { + throw std::range_error{"not representable"}; + } + return x; // unreachable } template struct parse_number { @@ -478,7 +477,8 @@ public: mValues.emplace_back(mImplicitValue); std::visit([](const auto &aAction) { aAction({}); }, mAction); return start; - } else if (mNumArgs <= std::distance(start, end)) { + } + if (mNumArgs <= std::distance(start, end)) { if (auto expected = maybe_nargs()) { end = std::next(start, *expected); if (std::any_of(start, end, Argument::is_optional)) { @@ -505,12 +505,12 @@ public: }; std::visit(action_apply{start, end, *this}, mAction); return end; - } else if (mDefaultValue.has_value()) { - return start; - } else { - throw std::runtime_error("Too few arguments for '" + - std::string(mUsedName) + "'."); } + if (mDefaultValue.has_value()) { + return start; + } + throw std::runtime_error("Too few arguments for '" + + std::string(mUsedName) + "'."); } /* @@ -525,29 +525,26 @@ public: stream << mUsedName << ": expected " << *expected << " argument(s). " << mValues.size() << " provided."; throw std::runtime_error(stream.str()); - } else { - // TODO: check if an implicit value was programmed for this argument - if (!mIsUsed && !mDefaultValue.has_value() && mIsRequired) { - std::stringstream stream; - stream << mNames[0] << ": required."; - throw std::runtime_error(stream.str()); - } - if (mIsUsed && mIsRequired && mValues.empty()) { - std::stringstream stream; - stream << mUsedName << ": no value provided."; - throw std::runtime_error(stream.str()); - } } - } else { - if (mValues.size() != expected && !mDefaultValue.has_value()) { + // TODO: check if an implicit value was programmed for this argument + if (!mIsUsed && !mDefaultValue.has_value() && mIsRequired) { std::stringstream stream; - if (!mUsedName.empty()) { - stream << mUsedName << ": "; - } - stream << *expected << " argument(s) expected. " << mValues.size() - << " provided."; + stream << mNames[0] << ": required."; throw std::runtime_error(stream.str()); } + if (mIsUsed && mIsRequired && mValues.empty()) { + std::stringstream stream; + stream << mUsedName << ": no value provided."; + throw std::runtime_error(stream.str()); + } + } else if (mValues.size() != expected && !mDefaultValue.has_value()) { + std::stringstream stream; + if (!mUsedName.empty()) { + stream << mUsedName << ": "; + } + stream << *expected << " argument(s) expected. " << mValues.size() + << " provided."; + throw std::runtime_error(stream.str()); } } } @@ -555,9 +552,8 @@ public: auto maybe_nargs() const -> std::optional { if (mNumArgs < 0) { return std::nullopt; - } else { - return static_cast(mNumArgs); } + return static_cast(mNumArgs); } std::size_t get_arguments_length() const { @@ -616,9 +612,8 @@ private: static auto lookahead(std::string_view s) -> int { if (s.empty()) { return eof; - } else { - return static_cast(static_cast(s[0])); } + return static_cast(static_cast(s[0])); } /* @@ -680,9 +675,8 @@ private: s.remove_prefix(1); if (s.empty()) { return true; - } else { - goto integer_part; } + goto integer_part; } case '1': case '2': @@ -696,9 +690,8 @@ private: s = consume_digits(s); if (s.empty()) { return true; - } else { - goto integer_part_consumed; } + goto integer_part_consumed; } case '.': { s.remove_prefix(1); @@ -733,9 +726,8 @@ private: if (is_digit(lookahead(s))) { s = consume_digits(s); goto exponent_part_opt; - } else { - return false; } + return false; exponent_part_opt: switch (lookahead(s)) { @@ -759,9 +751,8 @@ private: if (is_digit(lookahead(s))) { s = consume_digits(s); return s.empty(); - } else { - return false; } + return false; } static bool is_optional(std::string_view aName) { @@ -783,9 +774,8 @@ private: aName.remove_prefix(1); if (aName.empty()) { return true; - } else { - return is_decimal_literal(aName); } + return is_decimal_literal(aName); } default: return true; @@ -819,14 +809,13 @@ private: if (mDefaultValue.has_value()) { throw std::logic_error("Argument with default value always presents"); } - if (mValues.empty()) { return std::nullopt; - } else if constexpr (details::is_container_v) { - return any_cast_container(mValues); - } else { - return std::any_cast(mValues.front()); } + if constexpr (details::is_container_v) { + return any_cast_container(mValues); + } + return std::any_cast(mValues.front()); } template From 65648399715efa449746bd016d9b047c260ed648 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 7 Feb 2022 14:29:32 -0700 Subject: [PATCH 36/61] Enable clang-tidy readability-function-cognitive-complexity check The two functions previously flagged by this check, Argument::is_decimal_literal and Argument::validate, fell below the default Cognitive Complexity threshold as a result of the branch simplification for the readability-else-after-return check. Signed-off-by: Sean Robinson --- .clang-tidy | 1 - 1 file changed, 1 deletion(-) diff --git a/.clang-tidy b/.clang-tidy index 62c77f1..1920dcf 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -3,7 +3,6 @@ Checks: clang-analyzer-*, cppcoreguidelines-special-member-functions, readability-*, - -readability-function-cognitive-complexity, CheckOptions: - { key: readability-identifier-naming.ClassCase, value: CamelCase } From a915728da09b7a93f5f96182712ee4d66bca6919 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 7 Feb 2022 14:29:32 -0700 Subject: [PATCH 37/61] Enable clang-tidy cppcoreguidelines-avoid-c-arrays check Also converts most C-style arrays to a std::array onjects. The check is disabled for ArgumentParser::parse_args(int, const char *const[]) as this is a helper function to convert away from a common input format. Signed-off-by: Sean Robinson --- .clang-tidy | 1 + include/argparse/argparse.hpp | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 1920dcf..fad0e71 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,6 +1,7 @@ Checks: -*, clang-analyzer-*, + cppcoreguidelines-avoid-c-arrays, cppcoreguidelines-special-member-functions, readability-*, diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 483efb9..9f8cde2 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -31,6 +31,7 @@ SOFTWARE. #pragma once #include #include +#include #include #include #include @@ -350,7 +351,8 @@ class Argument { -> std::ostream &; template - explicit Argument(std::string_view(&&a)[N], std::index_sequence unused) + explicit Argument(std::array &&a, + std::index_sequence unused) : mIsOptional((is_optional(a[I]) || ...)), mIsRequired(false), mIsRepeatable(false), mIsUsed(false) { ((void)mNames.emplace_back(a[I]), ...); @@ -362,7 +364,7 @@ class Argument { public: template - explicit Argument(std::string_view(&&a)[N]) + explicit Argument(std::array &&a) : Argument(std::move(a), std::make_index_sequence{}) {} Argument &help(std::string aHelp) { @@ -910,7 +912,7 @@ public: // Parameter packing // Call add_argument with variadic number of string arguments template Argument &add_argument(Targs... Fargs) { - using array_of_sv = std::string_view[sizeof...(Targs)]; + using array_of_sv = std::array; auto tArgument = mOptionalArguments.emplace(cend(mOptionalArguments), array_of_sv{Fargs...}); @@ -966,6 +968,7 @@ public: * ArgumentParser * @throws std::runtime_error in case of any invalid argument */ + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) void parse_args(int argc, const char *const argv[]) { std::vector arguments; std::copy(argv, argv + argc, std::back_inserter(arguments)); From 486bfdaf8d308c891c05dd717805a10eb1589a97 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 7 Feb 2022 14:29:32 -0700 Subject: [PATCH 38/61] Update clang-format configuration standard to C++17 The "Cpp11" alias is deprecated for Latest (a rolling standard). For now, stick to C++17. Includes end-of-line whitespace removal. Signed-off-by: Sean Robinson --- .clang-format | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.clang-format b/.clang-format index 35cdf3c..2cfd8b1 100644 --- a/.clang-format +++ b/.clang-format @@ -20,7 +20,7 @@ AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: false BinPackArguments: true BinPackParameters: true -BraceWrapping: +BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false @@ -55,12 +55,12 @@ DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true -ForEachMacros: +ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IncludeBlocks: Preserve -IncludeCategories: +IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|gmock|isl|json)/)' @@ -110,7 +110,7 @@ SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false -Standard: Cpp11 +Standard: c++17 TabWidth: 8 UseTab: Never ... From cb2777db6e868fec0f1ea8bece12dc6323bb1f78 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 7 Feb 2022 14:33:16 -0700 Subject: [PATCH 39/61] Update header source format with clang-format Most changes are to better fit within "ColumnLimit: 80". The change from "T &&... var" to "T &&...var" is caused by "PointerAlignment: Right". Member functions chained from add_argument() use ContinuationIndentWidth, which is set to 4. Setting ContinuationIndentWidth to 2 causes many other continuation lines to change. So, this commit uses the original value (i.e. 4) as the preferred size. Signed-off-by: Sean Robinson --- include/argparse/argparse.hpp | 69 +++++++++++++++++------------------ 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 9f8cde2..af20f0b 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -55,17 +55,15 @@ namespace argparse { namespace details { // namespace for helper methods -template -struct is_container : std::false_type {}; +template struct is_container : std::false_type {}; template <> struct is_container : std::false_type {}; template -struct is_container().begin()), - decltype(std::declval().end()), - decltype(std::declval().size())>> - : std::true_type {}; +struct is_container< + T, std::void_t().begin()), + decltype(std::declval().end()), + decltype(std::declval().size())>> : std::true_type {}; template static constexpr bool is_container_v = is_container::value; @@ -74,9 +72,9 @@ template struct is_streamable : std::false_type {}; template -struct is_streamable< - T, std::void_t() << std::declval())>> - : std::true_type {}; +struct is_streamable() + << std::declval())>> + : std::true_type {}; template static constexpr bool is_streamable_v = is_streamable::value; @@ -100,7 +98,8 @@ template std::string repr(T const &val) { out << repr(*val.begin()); std::for_each( std::next(val.begin()), - std::next(val.begin(), std::min(size, repr_max_container_size) - 1), + std::next(val.begin(), + std::min(size, repr_max_container_size) - 1), [&out](const auto &v) { out << " " << repr(v); }); if (size <= repr_max_container_size) { out << " "; @@ -336,8 +335,8 @@ enum class default_arguments : unsigned int { all = help | version, }; -inline default_arguments operator& (const default_arguments &a, - const default_arguments &b) { +inline default_arguments operator&(const default_arguments &a, + const default_arguments &b) { return static_cast( static_cast::type>(a) & static_cast::type>(b)); @@ -390,7 +389,7 @@ public: } template - auto action(F &&aAction, Args &&... aBound) + auto action(F &&aAction, Args &&...aBound) -> std::enable_if_t, Argument &> { using action_type = std::conditional_t< @@ -858,25 +857,25 @@ public: : mProgramName(std::move(aProgramName)), mVersion(std::move(aVersion)) { if ((aArgs & default_arguments::help) == default_arguments::help) { add_argument("-h", "--help") - .action([&](const auto &unused) { - std::cout << help().str(); - std::exit(0); - }) - .default_value(false) - .help("shows help message and exits") - .implicit_value(true) - .nargs(0); + .action([&](const auto &unused) { + std::cout << help().str(); + std::exit(0); + }) + .default_value(false) + .help("shows help message and exits") + .implicit_value(true) + .nargs(0); } if ((aArgs & default_arguments::version) == default_arguments::version) { add_argument("-v", "--version") - .action([&](const auto &unused) { - std::cout << mVersion; - std::exit(0); - }) - .default_value(false) - .help("prints version information and exits") - .implicit_value(true) - .nargs(0); + .action([&](const auto &unused) { + std::cout << mVersion; + std::exit(0); + }) + .default_value(false) + .help("prints version information and exits") + .implicit_value(true) + .nargs(0); } } @@ -891,12 +890,12 @@ public: mIsParsed(other.mIsParsed), mPositionalArguments(other.mPositionalArguments), mOptionalArguments(other.mOptionalArguments) { - for (auto it = std::begin(mPositionalArguments); it != std::end(mPositionalArguments); - ++it) { + for (auto it = std::begin(mPositionalArguments); + it != std::end(mPositionalArguments); ++it) { index_argument(it); } - for (auto it = std::begin(mOptionalArguments); it != std::end(mOptionalArguments); - ++it) { + for (auto it = std::begin(mOptionalArguments); + it != std::end(mOptionalArguments); ++it) { index_argument(it); } } @@ -928,7 +927,7 @@ public: // Parameter packed add_parents method // Accepts a variadic number of ArgumentParser objects template - ArgumentParser &add_parents(const Targs &... Fargs) { + ArgumentParser &add_parents(const Targs &...Fargs) { for (const ArgumentParser &tParentParser : {std::ref(Fargs)...}) { for (const auto &tArgument : tParentParser.mPositionalArguments) { auto it = From c50346c1dff00af89db9a9a19533a3a34caaa190 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 7 Feb 2022 14:33:24 -0700 Subject: [PATCH 40/61] Remove is_representable_v trait This trait is unused and removing it does not cause any tests to fail. Signed-off-by: Sean Robinson --- include/argparse/argparse.hpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index af20f0b..e64beeb 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -79,10 +79,6 @@ struct is_streamable() template static constexpr bool is_streamable_v = is_streamable::value; -template -static constexpr bool is_representable_v = - is_streamable_v || is_container_v; - constexpr std::size_t repr_max_container_size = 5; template std::string repr(T const &val) { From 985ea8666d973202e2644ecbe5a4dd13120c37c2 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 7 Feb 2022 14:33:24 -0700 Subject: [PATCH 41/61] Update identifier names for structs The new naming pattern is CamelCase for structs, except parse_number as a primarily callable interface. Trait structs are named Has*Traits and constexpr variables to the struct template are named Is*. Signed-off-by: Sean Robinson --- .clang-tidy | 4 +++- include/argparse/argparse.hpp | 36 ++++++++++++++++++----------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index fad0e71..88642ae 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -8,11 +8,13 @@ Checks: CheckOptions: - { key: readability-identifier-naming.ClassCase, value: CamelCase } - { key: readability-identifier-naming.ConstexprVariableCase, value: lower_case } + - { key: readability-identifier-naming.ConstexprVariableIgnoredRegexp, value: "^Is.+" } - { key: readability-identifier-naming.FunctionCase, value: lower_case } - { key: readability-identifier-naming.LocalVariableIgnoredRegexp, value: "^[a-z][a-z_]+" } - { key: readability-identifier-naming.NamespaceCase, value: lower_case } - { key: readability-identifier-naming.PrivateMemberPrefix, value: m } - - { key: readability-identifier-naming.StructCase, value: lower_case } + - { key: readability-identifier-naming.StructCase, value: CamelCase } + - { key: readability-identifier-naming.StructIgnoredRegexp, value: "parse_number" } - { key: readability-identifier-naming.VariableCase, value: camelBack } HeaderFilterRegex: '.*' diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index e64beeb..21a2c11 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -55,29 +55,31 @@ namespace argparse { namespace details { // namespace for helper methods -template struct is_container : std::false_type {}; +template +struct HasContainerTraits : std::false_type {}; -template <> struct is_container : std::false_type {}; +template <> struct HasContainerTraits : std::false_type {}; template -struct is_container< +struct HasContainerTraits< T, std::void_t().begin()), decltype(std::declval().end()), decltype(std::declval().size())>> : std::true_type {}; template -static constexpr bool is_container_v = is_container::value; +static constexpr bool IsContainer = HasContainerTraits::value; template -struct is_streamable : std::false_type {}; +struct HasStreamableTraits : std::false_type {}; template -struct is_streamable() - << std::declval())>> +struct HasStreamableTraits< + T, + std::void_t() << std::declval())>> : std::true_type {}; template -static constexpr bool is_streamable_v = is_streamable::value; +static constexpr bool IsStreamable = HasStreamableTraits::value; constexpr std::size_t repr_max_container_size = 5; @@ -86,7 +88,7 @@ template std::string repr(T const &val) { return val ? "true" : "false"; } else if constexpr (std::is_convertible_v) { return '"' + std::string{std::string_view{val}} + '"'; - } else if constexpr (is_container_v) { + } else if constexpr (IsContainer) { std::stringstream out; out << "{"; const auto size = val.size(); @@ -108,7 +110,7 @@ template std::string repr(T const &val) { } out << "}"; return out.str(); - } else if constexpr (is_streamable_v) { + } else if constexpr (IsStreamable) { std::stringstream out; out << val; return out.str(); @@ -176,7 +178,7 @@ enum class chars_format { general = fixed | scientific }; -struct consume_hex_prefix_result { +struct ConsumeHexPrefixResult { bool is_hexadecimal; std::string_view rest; }; @@ -184,7 +186,7 @@ struct consume_hex_prefix_result { using namespace std::literals; constexpr auto consume_hex_prefix(std::string_view s) - -> consume_hex_prefix_result { + -> ConsumeHexPrefixResult { if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { s.remove_prefix(2); return {true, s}; @@ -483,7 +485,7 @@ public: } } - struct action_apply { + struct ActionApply { void operator()(valued_action &f) { std::transform(first, last, std::back_inserter(self.mValues), f); } @@ -500,7 +502,7 @@ public: Iterator first, last; Argument &self; }; - std::visit(action_apply{start, end, *this}, mAction); + std::visit(ActionApply{start, end, *this}, mAction); return end; } if (mDefaultValue.has_value()) { @@ -591,7 +593,7 @@ public: * @throws std::logic_error in case of incompatible types */ template bool operator==(const T &aRhs) const { - if constexpr (!details::is_container_v) { + if constexpr (!details::IsContainer) { return get() == aRhs; } else { using ValueType = typename T::value_type; @@ -785,7 +787,7 @@ private: */ template T get() const { if (!mValues.empty()) { - if constexpr (details::is_container_v) { + if constexpr (details::IsContainer) { return any_cast_container(mValues); } else { return std::any_cast(mValues.front()); @@ -809,7 +811,7 @@ private: if (mValues.empty()) { return std::nullopt; } - if constexpr (details::is_container_v) { + if constexpr (details::IsContainer) { return any_cast_container(mValues); } return std::any_cast(mValues.front()); From ce3c43eb9b3d8eb93030ca04608f732314e23719 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Thu, 10 Feb 2022 09:41:20 -0700 Subject: [PATCH 42/61] Rename identifiers There are no functional changes in this commit. Signed-off-by: Sean Robinson --- .clang-tidy | 7 +- include/argparse/argparse.hpp | 471 +++++++++++++++++----------------- 2 files changed, 239 insertions(+), 239 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 88642ae..d0b28f4 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -10,11 +10,12 @@ CheckOptions: - { key: readability-identifier-naming.ConstexprVariableCase, value: lower_case } - { key: readability-identifier-naming.ConstexprVariableIgnoredRegexp, value: "^Is.+" } - { key: readability-identifier-naming.FunctionCase, value: lower_case } - - { key: readability-identifier-naming.LocalVariableIgnoredRegexp, value: "^[a-z][a-z_]+" } - { key: readability-identifier-naming.NamespaceCase, value: lower_case } - - { key: readability-identifier-naming.PrivateMemberPrefix, value: m } + - { key: readability-identifier-naming.ParameterCase, value: lower_case } + - { key: readability-identifier-naming.PrivateMemberCase, value: lower_case } + - { key: readability-identifier-naming.PrivateMemberPrefix, value: m_ } - { key: readability-identifier-naming.StructCase, value: CamelCase } - { key: readability-identifier-naming.StructIgnoredRegexp, value: "parse_number" } - - { key: readability-identifier-naming.VariableCase, value: camelBack } + - { key: readability-identifier-naming.VariableCase, value: lower_case } HeaderFilterRegex: '.*' diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 21a2c11..4cb17b8 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -350,11 +350,11 @@ class Argument { template explicit Argument(std::array &&a, std::index_sequence unused) - : mIsOptional((is_optional(a[I]) || ...)), mIsRequired(false), - mIsRepeatable(false), mIsUsed(false) { - ((void)mNames.emplace_back(a[I]), ...); + : m_is_optional((is_optional(a[I]) || ...)), m_is_required(false), + m_is_repeatable(false), m_is_used(false) { + ((void)m_names.emplace_back(a[I]), ...); std::sort( - mNames.begin(), mNames.end(), [](const auto &lhs, const auto &rhs) { + m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) { return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size(); }); } @@ -364,41 +364,41 @@ public: explicit Argument(std::array &&a) : Argument(std::move(a), std::make_index_sequence{}) {} - Argument &help(std::string aHelp) { - mHelp = std::move(aHelp); + Argument &help(std::string help_text) { + m_help = std::move(help_text); return *this; } - template Argument &default_value(T &&aDefaultValue) { - mDefaultValueRepr = details::repr(aDefaultValue); - mDefaultValue = std::forward(aDefaultValue); + template Argument &default_value(T &&value) { + m_default_value_repr = details::repr(value); + m_default_value = std::forward(value); return *this; } Argument &required() { - mIsRequired = true; + m_is_required = true; return *this; } - Argument &implicit_value(std::any aImplicitValue) { - mImplicitValue = std::move(aImplicitValue); - mNumArgs = 0; + Argument &implicit_value(std::any value) { + m_implicit_value = std::move(value); + m_num_args = 0; return *this; } template - auto action(F &&aAction, Args &&...aBound) + auto action(F &&callable, Args &&...bound_args) -> std::enable_if_t, Argument &> { using action_type = std::conditional_t< std::is_void_v>, void_action, valued_action>; if constexpr (sizeof...(Args) == 0) { - mAction.emplace(std::forward(aAction)); + m_action.emplace(std::forward(callable)); } else { - mAction.emplace( - [f = std::forward(aAction), - tup = std::make_tuple(std::forward(aBound)...)]( + m_action.emplace( + [f = std::forward(callable), + tup = std::make_tuple(std::forward(bound_args)...)]( std::string const &opt) mutable { return details::apply_plus_one(f, tup, opt); }); @@ -407,7 +407,7 @@ public: } auto &append() { - mIsRepeatable = true; + m_is_repeatable = true; return *this; } @@ -451,33 +451,33 @@ public: return *this; } - Argument &nargs(int aNumArgs) { - if (aNumArgs < 0) { + Argument &nargs(int num_args) { + if (num_args < 0) { throw std::logic_error("Number of arguments must be non-negative"); } - mNumArgs = aNumArgs; + m_num_args = num_args; return *this; } Argument &remaining() { - mNumArgs = -1; + m_num_args = -1; return *this; } template Iterator consume(Iterator start, Iterator end, - std::string_view usedName = {}) { - if (!mIsRepeatable && mIsUsed) { + std::string_view used_name = {}) { + if (!m_is_repeatable && m_is_used) { throw std::runtime_error("Duplicate argument"); } - mIsUsed = true; - mUsedName = usedName; - if (mNumArgs == 0) { - mValues.emplace_back(mImplicitValue); - std::visit([](const auto &aAction) { aAction({}); }, mAction); + m_is_used = true; + m_used_name = used_name; + if (m_num_args == 0) { + m_values.emplace_back(m_implicit_value); + std::visit([](const auto &f) { f({}); }, m_action); return start; } - if (mNumArgs <= std::distance(start, end)) { + if (m_num_args <= std::distance(start, end)) { if (auto expected = maybe_nargs()) { end = std::next(start, *expected); if (std::any_of(start, end, Argument::is_optional)) { @@ -487,14 +487,14 @@ public: struct ActionApply { void operator()(valued_action &f) { - std::transform(first, last, std::back_inserter(self.mValues), f); + std::transform(first, last, std::back_inserter(self.m_values), f); } void operator()(void_action &f) { std::for_each(first, last, f); - if (!self.mDefaultValue.has_value()) { + if (!self.m_default_value.has_value()) { if (auto expected = self.maybe_nargs()) { - self.mValues.resize(*expected); + self.m_values.resize(*expected); } } } @@ -502,14 +502,14 @@ public: Iterator first, last; Argument &self; }; - std::visit(ActionApply{start, end, *this}, mAction); + std::visit(ActionApply{start, end, *this}, m_action); return end; } - if (mDefaultValue.has_value()) { + if (m_default_value.has_value()) { return start; } throw std::runtime_error("Too few arguments for '" + - std::string(mUsedName) + "'."); + std::string(m_used_name) + "'."); } /* @@ -517,31 +517,31 @@ public: */ void validate() const { if (auto expected = maybe_nargs()) { - if (mIsOptional) { - if (mIsUsed && mValues.size() != *expected && !mIsRepeatable && - !mDefaultValue.has_value()) { + if (m_is_optional) { + if (m_is_used && m_values.size() != *expected && !m_is_repeatable && + !m_default_value.has_value()) { std::stringstream stream; - stream << mUsedName << ": expected " << *expected << " argument(s). " - << mValues.size() << " provided."; + stream << m_used_name << ": expected " << *expected + << " argument(s). " << m_values.size() << " provided."; throw std::runtime_error(stream.str()); } // TODO: check if an implicit value was programmed for this argument - if (!mIsUsed && !mDefaultValue.has_value() && mIsRequired) { + if (!m_is_used && !m_default_value.has_value() && m_is_required) { std::stringstream stream; - stream << mNames[0] << ": required."; + stream << m_names[0] << ": required."; throw std::runtime_error(stream.str()); } - if (mIsUsed && mIsRequired && mValues.empty()) { + if (m_is_used && m_is_required && m_values.empty()) { std::stringstream stream; - stream << mUsedName << ": no value provided."; + stream << m_used_name << ": no value provided."; throw std::runtime_error(stream.str()); } - } else if (mValues.size() != expected && !mDefaultValue.has_value()) { + } else if (m_values.size() != expected && !m_default_value.has_value()) { std::stringstream stream; - if (!mUsedName.empty()) { - stream << mUsedName << ": "; + if (!m_used_name.empty()) { + stream << m_used_name << ": "; } - stream << *expected << " argument(s) expected. " << mValues.size() + stream << *expected << " argument(s) expected. " << m_values.size() << " provided."; throw std::runtime_error(stream.str()); } @@ -549,15 +549,15 @@ public: } auto maybe_nargs() const -> std::optional { - if (mNumArgs < 0) { + if (m_num_args < 0) { return std::nullopt; } - return static_cast(mNumArgs); + return static_cast(m_num_args); } std::size_t get_arguments_length() const { - return std::accumulate(std::begin(mNames), std::end(mNames), std::size_t(0), - [](const auto &sum, const auto &s) { + return std::accumulate(std::begin(m_names), std::end(m_names), + std::size_t(0), [](const auto &sum, const auto &s) { return sum + s.size() + 1; // +1 for space between names }); @@ -565,17 +565,17 @@ public: friend std::ostream &operator<<(std::ostream &stream, const Argument &argument) { - std::stringstream nameStream; - std::copy(std::begin(argument.mNames), std::end(argument.mNames), - std::ostream_iterator(nameStream, " ")); - stream << nameStream.str() << "\t" << argument.mHelp; - if (argument.mDefaultValue.has_value()) { - if (!argument.mHelp.empty()) { + std::stringstream name_stream; + std::copy(std::begin(argument.m_names), std::end(argument.m_names), + std::ostream_iterator(name_stream, " ")); + stream << name_stream.str() << "\t" << argument.m_help; + if (argument.m_default_value.has_value()) { + if (!argument.m_help.empty()) { stream << " "; } - stream << "[default: " << argument.mDefaultValueRepr << "]"; - } else if (argument.mIsRequired) { - if (!argument.mHelp.empty()) { + stream << "[default: " << argument.m_default_value_repr << "]"; + } else if (argument.m_is_required) { + if (!argument.m_help.empty()) { stream << " "; } stream << "[required]"; @@ -584,22 +584,22 @@ public: return stream; } - template bool operator!=(const T &aRhs) const { - return !(*this == aRhs); + template bool operator!=(const T &rhs) const { + return !(*this == rhs); } /* * Compare to an argument value of known type * @throws std::logic_error in case of incompatible types */ - template bool operator==(const T &aRhs) const { + template bool operator==(const T &rhs) const { if constexpr (!details::IsContainer) { - return get() == aRhs; + return get() == rhs; } else { using ValueType = typename T::value_type; - auto tLhs = get(); - return std::equal(std::begin(tLhs), std::end(tLhs), std::begin(aRhs), - std::end(aRhs), [](const auto &lhs, const auto &rhs) { + auto lhs = get(); + return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs), + std::end(rhs), [](const auto &lhs, const auto &rhs) { return std::any_cast(lhs) == rhs; }); } @@ -754,8 +754,8 @@ private: return false; } - static bool is_optional(std::string_view aName) { - return !is_positional(aName); + static bool is_optional(std::string_view name) { + return !is_positional(name); } /* @@ -765,16 +765,16 @@ private: * '-' decimal-literal * !'-' anything */ - static bool is_positional(std::string_view aName) { - switch (lookahead(aName)) { + static bool is_positional(std::string_view name) { + switch (lookahead(name)) { case eof: return true; case '-': { - aName.remove_prefix(1); - if (aName.empty()) { + name.remove_prefix(1); + if (name.empty()) { return true; } - return is_decimal_literal(aName); + return is_decimal_literal(name); } default: return true; @@ -786,17 +786,17 @@ private: * @throws std::logic_error in case of incompatible types */ template T get() const { - if (!mValues.empty()) { + if (!m_values.empty()) { if constexpr (details::IsContainer) { - return any_cast_container(mValues); + return any_cast_container(m_values); } else { - return std::any_cast(mValues.front()); + return std::any_cast(m_values.front()); } } - if (mDefaultValue.has_value()) { - return std::any_cast(mDefaultValue); + if (m_default_value.has_value()) { + return std::any_cast(m_default_value); } - throw std::logic_error("No value provided for '" + mNames.back() + "'."); + throw std::logic_error("No value provided for '" + m_names.back() + "'."); } /* @@ -805,55 +805,55 @@ private: * @returns The stored value if any, std::nullopt otherwise. */ template auto present() const -> std::optional { - if (mDefaultValue.has_value()) { + if (m_default_value.has_value()) { throw std::logic_error("Argument with default value always presents"); } - if (mValues.empty()) { + if (m_values.empty()) { return std::nullopt; } if constexpr (details::IsContainer) { - return any_cast_container(mValues); + return any_cast_container(m_values); } - return std::any_cast(mValues.front()); + return std::any_cast(m_values.front()); } template - static auto any_cast_container(const std::vector &aOperand) -> T { + static auto any_cast_container(const std::vector &operand) -> T { using ValueType = typename T::value_type; - T tResult; + T result; std::transform( - std::begin(aOperand), std::end(aOperand), std::back_inserter(tResult), + std::begin(operand), std::end(operand), std::back_inserter(result), [](const auto &value) { return std::any_cast(value); }); - return tResult; + return result; } - std::vector mNames; - std::string_view mUsedName; - std::string mHelp; - std::any mDefaultValue; - std::string mDefaultValueRepr; - std::any mImplicitValue; + std::vector m_names; + std::string_view m_used_name; + std::string m_help; + std::any m_default_value; + std::string m_default_value_repr; + std::any m_implicit_value; using valued_action = std::function; using void_action = std::function; - std::variant mAction{ + std::variant m_action{ std::in_place_type, - [](const std::string &aValue) { return aValue; }}; - std::vector mValues; - int mNumArgs = 1; - bool mIsOptional : true; - bool mIsRequired : true; - bool mIsRepeatable : true; - bool mIsUsed : true; // True if the optional argument is used by user + [](const std::string &value) { return value; }}; + std::vector m_values; + int m_num_args = 1; + bool m_is_optional : true; + bool m_is_required : true; + bool m_is_repeatable : true; + bool m_is_used : true; // True if the optional argument is used by user }; class ArgumentParser { public: - explicit ArgumentParser(std::string aProgramName = {}, - std::string aVersion = "1.0", - default_arguments aArgs = default_arguments::all) - : mProgramName(std::move(aProgramName)), mVersion(std::move(aVersion)) { - if ((aArgs & default_arguments::help) == default_arguments::help) { + explicit ArgumentParser(std::string program_name = {}, + std::string version = "1.0", + default_arguments add_args = default_arguments::all) + : m_program_name(std::move(program_name)), m_version(std::move(version)) { + if ((add_args & default_arguments::help) == default_arguments::help) { add_argument("-h", "--help") .action([&](const auto &unused) { std::cout << help().str(); @@ -864,10 +864,10 @@ public: .implicit_value(true) .nargs(0); } - if ((aArgs & default_arguments::version) == default_arguments::version) { + if ((add_args & default_arguments::version) == default_arguments::version) { add_argument("-v", "--version") .action([&](const auto &unused) { - std::cout << mVersion; + std::cout << m_version; std::exit(0); }) .default_value(false) @@ -881,19 +881,19 @@ public: ArgumentParser &operator=(ArgumentParser &&) = default; ArgumentParser(const ArgumentParser &other) - : mProgramName(other.mProgramName), - mVersion(other.mVersion), - mDescription(other.mDescription), - mEpilog(other.mEpilog), - mIsParsed(other.mIsParsed), - mPositionalArguments(other.mPositionalArguments), - mOptionalArguments(other.mOptionalArguments) { - for (auto it = std::begin(mPositionalArguments); - it != std::end(mPositionalArguments); ++it) { + : m_program_name(other.m_program_name), + m_version(other.m_version), + m_description(other.m_description), + m_epilog(other.m_epilog), + m_is_parsed(other.m_is_parsed), + m_positional_arguments(other.m_positional_arguments), + m_optional_arguments(other.m_optional_arguments) { + for (auto it = std::begin(m_positional_arguments); + it != std::end(m_positional_arguments); ++it) { index_argument(it); } - for (auto it = std::begin(mOptionalArguments); - it != std::end(mOptionalArguments); ++it) { + for (auto it = std::begin(m_optional_arguments); + it != std::end(m_optional_arguments); ++it) { index_argument(it); } } @@ -908,46 +908,46 @@ public: // Parameter packing // Call add_argument with variadic number of string arguments - template Argument &add_argument(Targs... Fargs) { + template Argument &add_argument(Targs... f_args) { using array_of_sv = std::array; - auto tArgument = mOptionalArguments.emplace(cend(mOptionalArguments), - array_of_sv{Fargs...}); + auto argument = m_optional_arguments.emplace(cend(m_optional_arguments), + array_of_sv{f_args...}); - if (!tArgument->mIsOptional) { - mPositionalArguments.splice(cend(mPositionalArguments), - mOptionalArguments, tArgument); + if (!argument->m_is_optional) { + m_positional_arguments.splice(cend(m_positional_arguments), + m_optional_arguments, argument); } - index_argument(tArgument); - return *tArgument; + index_argument(argument); + return *argument; } // Parameter packed add_parents method // Accepts a variadic number of ArgumentParser objects template - ArgumentParser &add_parents(const Targs &...Fargs) { - for (const ArgumentParser &tParentParser : {std::ref(Fargs)...}) { - for (const auto &tArgument : tParentParser.mPositionalArguments) { - auto it = - mPositionalArguments.insert(cend(mPositionalArguments), tArgument); + ArgumentParser &add_parents(const Targs &...f_args) { + for (const ArgumentParser &parent_parser : {std::ref(f_args)...}) { + for (const auto &argument : parent_parser.m_positional_arguments) { + auto it = m_positional_arguments.insert(cend(m_positional_arguments), + argument); index_argument(it); } - for (const auto &tArgument : tParentParser.mOptionalArguments) { + for (const auto &argument : parent_parser.m_optional_arguments) { auto it = - mOptionalArguments.insert(cend(mOptionalArguments), tArgument); + m_optional_arguments.insert(cend(m_optional_arguments), argument); index_argument(it); } } return *this; } - ArgumentParser &add_description(std::string aDescription) { - mDescription = std::move(aDescription); + ArgumentParser &add_description(std::string description) { + m_description = std::move(description); return *this; } - ArgumentParser &add_epilog(std::string aEpilog) { - mEpilog = std::move(aEpilog); + ArgumentParser &add_epilog(std::string epilog) { + m_epilog = std::move(epilog); return *this; } @@ -956,8 +956,8 @@ public: * This variant is used mainly for testing * @throws std::runtime_error in case of any invalid argument */ - void parse_args(const std::vector &aArguments) { - parse_args_internal(aArguments); + void parse_args(const std::vector &arguments) { + parse_args_internal(arguments); parse_args_validate(); } @@ -978,12 +978,11 @@ public: * @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) { + template T get(std::string_view arg_name) const { + if (!m_is_parsed) { throw std::logic_error("Nothing parsed, no arguments are available."); } - return (*this)[aArgumentName].get(); + return (*this)[arg_name].get(); } /* Getter for options without default values. @@ -992,81 +991,81 @@ public: * @throws std::bad_any_cast if the option is not of type T */ template - auto present(std::string_view aArgumentName) const -> std::optional { - return (*this)[aArgumentName].present(); + auto present(std::string_view arg_name) const -> std::optional { + return (*this)[arg_name].present(); } /* Getter that returns true for user-supplied options. Returns false if not * user-supplied, even with a default value. */ - auto is_used(std::string_view aArgumentName) const { - return (*this)[aArgumentName].mIsUsed; + auto is_used(std::string_view arg_name) const { + return (*this)[arg_name].m_is_used; } /* Indexing operator. Return a reference to an Argument object * Used in conjuction with Argument.operator== e.g., parser["foo"] == true * @throws std::logic_error in case of an invalid argument name */ - Argument &operator[](std::string_view aArgumentName) const { - auto tIterator = mArgumentMap.find(aArgumentName); - if (tIterator != mArgumentMap.end()) { - return *(tIterator->second); + Argument &operator[](std::string_view arg_name) const { + auto it = m_argument_map.find(arg_name); + if (it != m_argument_map.end()) { + return *(it->second); } - if (aArgumentName.front() != '-') { - std::string nameStr(aArgumentName); - // "-" + aArgumentName - nameStr = "-" + nameStr; - tIterator = mArgumentMap.find(nameStr); - if (tIterator != mArgumentMap.end()) { - return *(tIterator->second); + if (arg_name.front() != '-') { + std::string name(arg_name); + // "-" + arg_name + name = "-" + name; + it = m_argument_map.find(name); + if (it != m_argument_map.end()) { + return *(it->second); } - // "--" + aArgumentName - nameStr = "-" + nameStr; - tIterator = mArgumentMap.find(nameStr); - if (tIterator != mArgumentMap.end()) { - return *(tIterator->second); + // "--" + arg_name + name = "-" + name; + it = m_argument_map.find(name); + if (it != m_argument_map.end()) { + return *(it->second); } } - throw std::logic_error("No such argument: " + std::string(aArgumentName)); + throw std::logic_error("No such argument: " + std::string(arg_name)); } // Print help message friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) -> std::ostream & { stream.setf(std::ios_base::left); - stream << "Usage: " << parser.mProgramName << " [options] "; - std::size_t tLongestArgumentLength = parser.get_length_of_longest_argument(); + stream << "Usage: " << parser.m_program_name << " [options] "; + std::size_t longest_arg_length = parser.get_length_of_longest_argument(); - for (const auto &argument : parser.mPositionalArguments) { - stream << argument.mNames.front() << " "; + for (const auto &argument : parser.m_positional_arguments) { + stream << argument.m_names.front() << " "; } stream << "\n\n"; - if (!parser.mDescription.empty()) { - stream << parser.mDescription << "\n\n"; + if (!parser.m_description.empty()) { + stream << parser.m_description << "\n\n"; } - if (!parser.mPositionalArguments.empty()) { + if (!parser.m_positional_arguments.empty()) { stream << "Positional arguments:\n"; } - for (const auto &mPositionalArgument : parser.mPositionalArguments) { - stream.width(tLongestArgumentLength); - stream << mPositionalArgument; + for (const auto &argument : parser.m_positional_arguments) { + stream.width(longest_arg_length); + stream << argument; } - if (!parser.mOptionalArguments.empty()) { - stream << (parser.mPositionalArguments.empty() ? "" : "\n") + if (!parser.m_optional_arguments.empty()) { + stream << (parser.m_positional_arguments.empty() ? "" : "\n") << "Optional arguments:\n"; } - for (const auto &mOptionalArgument : parser.mOptionalArguments) { - stream.width(tLongestArgumentLength); - stream << mOptionalArgument; + for (const auto &argument : parser.m_optional_arguments) { + stream.width(longest_arg_length); + stream << argument; } - if (!parser.mEpilog.empty()) { - stream << parser.mEpilog << "\n\n"; + if (!parser.m_epilog.empty()) { + stream << parser.m_epilog << "\n\n"; } return stream; @@ -1092,47 +1091,47 @@ private: /* * @throws std::runtime_error in case of any invalid argument */ - void parse_args_internal(const std::vector &aArguments) { - if (mProgramName.empty() && !aArguments.empty()) { - mProgramName = aArguments.front(); + void parse_args_internal(const std::vector &arguments) { + if (m_program_name.empty() && !arguments.empty()) { + m_program_name = arguments.front(); } - auto end = std::end(aArguments); - auto positionalArgumentIt = std::begin(mPositionalArguments); - for (auto it = std::next(std::begin(aArguments)); it != end;) { - const auto &tCurrentArgument = *it; - if (Argument::is_positional(tCurrentArgument)) { - if (positionalArgumentIt == std::end(mPositionalArguments)) { + auto end = std::end(arguments); + auto positional_argument_it = std::begin(m_positional_arguments); + for (auto it = std::next(std::begin(arguments)); it != end;) { + const auto ¤t_argument = *it; + if (Argument::is_positional(current_argument)) { + if (positional_argument_it == std::end(m_positional_arguments)) { throw std::runtime_error( "Maximum number of positional arguments exceeded"); } - auto tArgument = positionalArgumentIt++; - it = tArgument->consume(it, end); + auto argument = positional_argument_it++; + it = argument->consume(it, end); continue; } - auto tIterator = mArgumentMap.find(tCurrentArgument); - if (tIterator != mArgumentMap.end()) { - auto tArgument = tIterator->second; - it = tArgument->consume(std::next(it), end, tIterator->first); - } else if (const auto &tCompoundArgument = tCurrentArgument; - tCompoundArgument.size() > 1 && tCompoundArgument[0] == '-' && - tCompoundArgument[1] != '-') { + auto arg_map_it = m_argument_map.find(current_argument); + if (arg_map_it != m_argument_map.end()) { + auto argument = arg_map_it->second; + it = argument->consume(std::next(it), end, arg_map_it->first); + } else if (const auto &compound_arg = current_argument; + compound_arg.size() > 1 && compound_arg[0] == '-' && + compound_arg[1] != '-') { ++it; - for (std::size_t j = 1; j < tCompoundArgument.size(); j++) { - auto tHypotheticalArgument = std::string{'-', tCompoundArgument[j]}; - auto tIterator2 = mArgumentMap.find(tHypotheticalArgument); - if (tIterator2 != mArgumentMap.end()) { - auto tArgument = tIterator2->second; - it = tArgument->consume(it, end, tIterator2->first); + for (std::size_t j = 1; j < compound_arg.size(); j++) { + auto hypothetical_arg = std::string{'-', compound_arg[j]}; + auto arg_map_it2 = m_argument_map.find(hypothetical_arg); + if (arg_map_it2 != m_argument_map.end()) { + auto argument = arg_map_it2->second; + it = argument->consume(it, end, arg_map_it2->first); } else { - throw std::runtime_error("Unknown argument: " + tCurrentArgument); + throw std::runtime_error("Unknown argument: " + current_argument); } } } else { - throw std::runtime_error("Unknown argument: " + tCurrentArgument); + throw std::runtime_error("Unknown argument: " + current_argument); } } - mIsParsed = true; + m_is_parsed = true; } /* @@ -1140,44 +1139,44 @@ private: */ void parse_args_validate() { // Check if all arguments are parsed - std::for_each(std::begin(mArgumentMap), std::end(mArgumentMap), - [](const auto &argPair) { - const auto &tArgument = argPair.second; - tArgument->validate(); + std::for_each(std::begin(m_argument_map), std::end(m_argument_map), + [](const auto &pair) { + const auto &argument = pair.second; + argument->validate(); }); } // Used by print_help. std::size_t get_length_of_longest_argument() const { - if (mArgumentMap.empty()) { + if (m_argument_map.empty()) { return 0; } - std::vector argumentLengths(mArgumentMap.size()); - std::transform(std::begin(mArgumentMap), std::end(mArgumentMap), - std::begin(argumentLengths), [](const auto &argPair) { - const auto &tArgument = argPair.second; - return tArgument->get_arguments_length(); + std::vector argument_lengths(m_argument_map.size()); + std::transform(std::begin(m_argument_map), std::end(m_argument_map), + std::begin(argument_lengths), [](const auto &pair) { + const auto &argument = pair.second; + return argument->get_arguments_length(); }); - return *std::max_element(std::begin(argumentLengths), - std::end(argumentLengths)); + return *std::max_element(std::begin(argument_lengths), + std::end(argument_lengths)); } using list_iterator = std::list::iterator; - void index_argument(list_iterator argIt) { - for (const auto &mName : std::as_const(argIt->mNames)) { - mArgumentMap.insert_or_assign(mName, argIt); + void index_argument(list_iterator it) { + for (const auto &name : std::as_const(it->m_names)) { + m_argument_map.insert_or_assign(name, it); } } - std::string mProgramName; - std::string mVersion; - std::string mDescription; - std::string mEpilog; - bool mIsParsed = false; - std::list mPositionalArguments; - std::list mOptionalArguments; - std::map> mArgumentMap; + std::string m_program_name; + std::string m_version; + std::string m_description; + std::string m_epilog; + bool m_is_parsed = false; + std::list m_positional_arguments; + std::list m_optional_arguments; + std::map> m_argument_map; }; } // namespace argparse From 0d868fdca8a7b0d5e1e9c7fd9d6fdc8c2ad1c9df Mon Sep 17 00:00:00 2001 From: Aayush Anand Date: Mon, 28 Feb 2022 13:20:08 -0700 Subject: [PATCH 43/61] Modify ArgumentParser::parse_args_validate() method to use a for loop instead of std::for_each() Suggested-by: Aayush Anand [skrobinson: Updated for latest formatting and naming conventions] Signed-off-by: Sean Robinson --- include/argparse/argparse.hpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 4cb17b8..6c8daf2 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -1139,11 +1139,9 @@ private: */ void parse_args_validate() { // Check if all arguments are parsed - std::for_each(std::begin(m_argument_map), std::end(m_argument_map), - [](const auto &pair) { - const auto &argument = pair.second; - argument->validate(); - }); + for (const auto& [unused, argument] : m_argument_map) { + argument->validate(); + } } // Used by print_help. From 063d708c3e9826a9ad9dfe0429b0960815ca3cb9 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 28 Feb 2022 13:20:08 -0700 Subject: [PATCH 44/61] Modify ArgumentParser::get_length_of_longest_argument method run in O(1) space Closes #124 Reported-by: Aayush Anand Signed-off-by: Sean Robinson --- include/argparse/argparse.hpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 6c8daf2..8395675 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -1149,14 +1149,11 @@ private: if (m_argument_map.empty()) { return 0; } - std::vector argument_lengths(m_argument_map.size()); - std::transform(std::begin(m_argument_map), std::end(m_argument_map), - std::begin(argument_lengths), [](const auto &pair) { - const auto &argument = pair.second; - return argument->get_arguments_length(); - }); - return *std::max_element(std::begin(argument_lengths), - std::end(argument_lengths)); + std::size_t max_size = 0; + for (const auto& [unused, argument] : m_argument_map) { + max_size = std::max(max_size, argument->get_arguments_length()); + } + return max_size; } using list_iterator = std::list::iterator; From 843e4eefb0265a446f8576275fa22bbc1f395040 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 28 Feb 2022 13:20:08 -0700 Subject: [PATCH 45/61] Move ArgumentParser::parse_args_validate logic into ::parse_args This code was previously moved from ::parse_args to its own method, but has since been simplified. Moving the actual work to Argument::validate in commit 603e87ae6 leaves a single loop that fits back into ::parse_args. Signed-off-by: Sean Robinson --- include/argparse/argparse.hpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 8395675..f54ec44 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -958,7 +958,10 @@ public: */ void parse_args(const std::vector &arguments) { parse_args_internal(arguments); - parse_args_validate(); + // Check if all arguments are parsed + for (const auto& [unused, argument] : m_argument_map) { + argument->validate(); + } } /* Main entry point for parsing command-line arguments using this @@ -1134,16 +1137,6 @@ private: m_is_parsed = true; } - /* - * @throws std::runtime_error in case of any invalid argument - */ - void parse_args_validate() { - // Check if all arguments are parsed - for (const auto& [unused, argument] : m_argument_map) { - argument->validate(); - } - } - // Used by print_help. std::size_t get_length_of_longest_argument() const { if (m_argument_map.empty()) { From 67d2e4476fc2b6de245566914ae87164a486cb25 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Thu, 24 Mar 2022 14:04:46 -0700 Subject: [PATCH 46/61] Annotate range-for loops' structured bindings as [[maybe_unused]] The 'unused' variable in both cases is most-definitely unused in the loop. This is a cppcheck warning that appeared after moving these two loops to range-for. Signed-off-by: Sean Robinson --- include/argparse/argparse.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index f54ec44..38e3244 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -959,7 +959,7 @@ public: void parse_args(const std::vector &arguments) { parse_args_internal(arguments); // Check if all arguments are parsed - for (const auto& [unused, argument] : m_argument_map) { + for ([[maybe_unused]] const auto& [unused, argument] : m_argument_map) { argument->validate(); } } @@ -1143,7 +1143,7 @@ private: return 0; } std::size_t max_size = 0; - for (const auto& [unused, argument] : m_argument_map) { + for ([[maybe_unused]] const auto& [unused, argument] : m_argument_map) { max_size = std::max(max_size, argument->get_arguments_length()); } return max_size; From eaa8214962a780af31d194d869c3f95582b6f4ec Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Mon, 28 Mar 2022 13:03:05 -0700 Subject: [PATCH 47/61] Remove StaticAnalysis Action I am seeing too many errors due to bad tool configuration, not from problems in project code. So, disable SA until I can make it work more reliably. Signed-off-by: Sean Robinson --- .github/workflows/static_analysis.yml | 40 --------------------------- tools/CMakeLists.txt | 10 ------- tools/static_analysis_setup.sh | 4 --- tools/tidy-base.cpp | 4 --- 4 files changed, 58 deletions(-) delete mode 100644 .github/workflows/static_analysis.yml delete mode 100644 tools/CMakeLists.txt delete mode 100755 tools/static_analysis_setup.sh delete mode 100644 tools/tidy-base.cpp diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml deleted file mode 100644 index 2724a71..0000000 --- a/.github/workflows/static_analysis.yml +++ /dev/null @@ -1,40 +0,0 @@ - -name: Static Analysis - -on: pull_request_target - -jobs: - - static_analysis: - - name: ${{ matrix.toolchain }} - runs-on: ${{ matrix.os }} - - strategy: - - matrix: - - toolchain: - - ubuntu-latest - - include: - - toolchain: ubuntu-latest - os: ubuntu-latest - compiler: clang - - steps: - - - name: Checkout Code - uses: actions/checkout@v2 - - - name: Analyze - uses: skrobinson/StaticAnalysis@stable-for-argparse - with: - clang_tidy_args: >- - --config-file=$GITHUB_WORKSPACE/.clang-tidy - --extra-arg=-I$GITHUB_WORKSPACE/include --extra-arg=-std=c++17 - cppcheck_args: >- - --enable=all --inconclusive --inline-suppr - --suppress=missingInclude - --suppress='*:$GITHUB_WORKSPACE/test/doctest.hpp' - init_script: tools/static_analysis_setup.sh diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt deleted file mode 100644 index cff613b..0000000 --- a/tools/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -cmake_minimum_required(VERSION 3.12) -project(argparse) - -file(GLOB ARGPARSE_LINT_SOURCES - tidy-base.cpp -) -ADD_EXECUTABLE(ARGPARSE_LINT ${ARGPARSE_LINT_SOURCES}) -set_target_properties(ARGPARSE_LINT PROPERTIES OUTPUT_NAME tidy-base) -set_property(TARGET ARGPARSE_LINT PROPERTY CXX_STANDARD 17) -INCLUDE_DIRECTORIES("../include" ".") diff --git a/tools/static_analysis_setup.sh b/tools/static_analysis_setup.sh deleted file mode 100755 index 350eb82..0000000 --- a/tools/static_analysis_setup.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -# Change to the "tools" subdir before "build" subdir is made. -cd tools diff --git a/tools/tidy-base.cpp b/tools/tidy-base.cpp deleted file mode 100644 index 87b47a1..0000000 --- a/tools/tidy-base.cpp +++ /dev/null @@ -1,4 +0,0 @@ - -#include "argparse/argparse.hpp" - -int main(int argc, const char* argv[]) {} From 1a552dfd60cb8a478f7d7cb63e4814ac66ca2891 Mon Sep 17 00:00:00 2001 From: Pranav Srinivas Kumar Date: Fri, 1 Apr 2022 19:05:46 -0500 Subject: [PATCH 48/61] Bumped library version to v2.3 --- CMakeLists.txt | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a962b0..6806731 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.12.4) project(argparse - VERSION 1.0.0 + VERSION 2.3.0 DESCRIPTION "A single header argument parser for C++17" HOMEPAGE_URL "https://github.com/p-ranav/argparse" LANGUAGES CXX diff --git a/README.md b/README.md index f42c6e0..b23cda5 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ license - version + version

## Highlights From 3b89546fd09daf69458d8502b384ac536a3ec6b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Nogueira?= Date: Wed, 13 Apr 2022 10:29:37 -0300 Subject: [PATCH 49/61] Fix regression in version printing. This regression was caused by commit ea1f7ef663899e799001f50055cc93c733a9fff, which didn't add "\n" to the version printing statement. We use std::endl now for compatibility across platforms. --- include/argparse/argparse.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 4cb17b8..fb4f25a 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -867,7 +867,7 @@ public: if ((add_args & default_arguments::version) == default_arguments::version) { add_argument("-v", "--version") .action([&](const auto &unused) { - std::cout << m_version; + std::cout << m_version << std::endl; std::exit(0); }) .default_value(false) From f56aec307fb441d3affcbef70875b397f8cd4156 Mon Sep 17 00:00:00 2001 From: Ameya Vikram Singh Date: Sat, 16 Apr 2022 20:34:35 +0530 Subject: [PATCH 50/61] Updates doctest from v2.3.5 to v2.4.8 Fixes the build error on latest GNU/Linux Systems: (https://github.com/doctest/doctest/issues/473) Signed-off-by: Ameya Vikram Singh --- test/doctest.hpp | 2719 ++++++++++++++++++++++++++++++---------------- 1 file changed, 1797 insertions(+), 922 deletions(-) diff --git a/test/doctest.hpp b/test/doctest.hpp index 8b76419..d25f526 100644 --- a/test/doctest.hpp +++ b/test/doctest.hpp @@ -4,14 +4,14 @@ // // doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD // -// Copyright (c) 2016-2019 Viktor Kirilov +// Copyright (c) 2016-2021 Viktor Kirilov // // Distributed under the MIT Software License // See accompanying file LICENSE.txt or copy at // https://opensource.org/licenses/MIT // // The documentation can be found at the library's page: -// https://github.com/onqtam/doctest/blob/master/doc/markdown/readme.md +// https://github.com/doctest/doctest/blob/master/doc/markdown/readme.md // // ================================================================================================= // ================================================================================================= @@ -47,9 +47,17 @@ // ================================================================================================= #define DOCTEST_VERSION_MAJOR 2 -#define DOCTEST_VERSION_MINOR 3 -#define DOCTEST_VERSION_PATCH 5 -#define DOCTEST_VERSION_STR "2.3.5" +#define DOCTEST_VERSION_MINOR 4 +#define DOCTEST_VERSION_PATCH 8 + +// util we need here +#define DOCTEST_TOSTR_IMPL(x) #x +#define DOCTEST_TOSTR(x) DOCTEST_TOSTR_IMPL(x) + +#define DOCTEST_VERSION_STR \ + DOCTEST_TOSTR(DOCTEST_VERSION_MAJOR) "." \ + DOCTEST_TOSTR(DOCTEST_VERSION_MINOR) "." \ + DOCTEST_TOSTR(DOCTEST_VERSION_PATCH) #define DOCTEST_VERSION \ (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH) @@ -137,85 +145,93 @@ // == COMPILER WARNINGS ============================================================================ // ================================================================================================= +// both the header and the implementation suppress all of these, +// so it only makes sense to aggregrate them like so +#define DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH \ + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") \ + \ + DOCTEST_GCC_SUPPRESS_WARNING_PUSH \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") \ + \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ + /* these 4 also disabled globally via cmake: */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4514) /* unreferenced inline function has been removed */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4571) /* SEH related */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4710) /* function not inlined */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4711) /* function selected for inline expansion*/ \ + /* */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4616) /* invalid compiler warning */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4619) /* invalid compiler warning */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4996) /* The compiler encountered a deprecated declaration */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4706) /* assignment within conditional expression */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4512) /* 'class' : assignment operator could not be generated */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4127) /* conditional expression is constant */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4640) /* construction of local static object not thread-safe */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ + /* static analysis */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26439) /* Function may not throw. Declare it 'noexcept' */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26495) /* Always initialize a member variable */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26451) /* Arithmetic overflow ... */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26444) /* Avoid unnamed objects with custom ctor and dtor... */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26812) /* Prefer 'enum class' over 'enum' */ + +#define DOCTEST_SUPPRESS_COMMON_WARNINGS_POP \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP \ + DOCTEST_GCC_SUPPRESS_WARNING_POP \ + DOCTEST_MSVC_SUPPRESS_WARNING_POP + +DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") DOCTEST_GCC_SUPPRESS_WARNING_PUSH -DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") -DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") -DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") -DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") -DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy") -DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor") -DOCTEST_GCC_SUPPRESS_WARNING("-Winline") -DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") -DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") -DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo") DOCTEST_MSVC_SUPPRESS_WARNING_PUSH -DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning -DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning -DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration -DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression -DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated -DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant -DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding -DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe -// static analysis -DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' -DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable -DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... -DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtr... - -// 4548 - expression before comma has no effect; expected expression with side - effect -// 4265 - class has virtual functions, but destructor is not virtual -// 4986 - exception specification does not match previous declaration -// 4350 - behavior change: 'member1' called instead of 'member2' -// 4668 - 'x' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' -// 4365 - conversion from 'int' to 'unsigned long', signed/unsigned mismatch -// 4774 - format string expected in argument 'x' is not a string literal -// 4820 - padding in structs - -// only 4 should be disabled globally: -// - 4514 # unreferenced inline function has been removed -// - 4571 # SEH related -// - 4710 # function not inlined -// - 4711 # function 'x' selected for automatic inline expansion #define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN \ DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ - DOCTEST_MSVC_SUPPRESS_WARNING(4548) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4265) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4986) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4350) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4668) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4365) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4774) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4820) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4625) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4626) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5027) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5026) \ - DOCTEST_MSVC_SUPPRESS_WARNING(4623) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5039) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5045) \ - DOCTEST_MSVC_SUPPRESS_WARNING(5105) + DOCTEST_MSVC_SUPPRESS_WARNING(4548) /* before comma no effect; expected side - effect */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4265) /* virtual functions, but destructor is not virtual */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4986) /* exception specification does not match previous */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4350) /* 'member1' called instead of 'member2' */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4668) /* not defined as a preprocessor macro */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4365) /* signed/unsigned mismatch */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4774) /* format string not a string literal */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4623) /* default constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5039) /* pointer to pot. throwing function passed to extern C */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */ #define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP @@ -228,6 +244,7 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom constr // GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html // MSVC version table: // https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering +// MSVC++ 14.3 (17) _MSC_VER == 1930 (Visual Studio 2022) // MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019) // MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017) // MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) @@ -237,6 +254,10 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom constr // MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) // MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) +// Universal Windows Platform support +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +#define DOCTEST_CONFIG_NO_WINDOWS_SEH +#endif // WINAPI_FAMILY #if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH) #define DOCTEST_CONFIG_WINDOWS_SEH #endif // MSVC @@ -301,11 +322,39 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom constr #define DOCTEST_NOINLINE __declspec(noinline) #define DOCTEST_UNUSED #define DOCTEST_ALIGNMENT(x) -#else // MSVC +#elif DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 5, 0) +#define DOCTEST_NOINLINE +#define DOCTEST_UNUSED +#define DOCTEST_ALIGNMENT(x) +#else #define DOCTEST_NOINLINE __attribute__((noinline)) #define DOCTEST_UNUSED __attribute__((unused)) #define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x))) -#endif // MSVC +#endif + +#ifndef DOCTEST_NORETURN +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_NORETURN +#else // DOCTEST_MSVC +#define DOCTEST_NORETURN [[noreturn]] +#endif // DOCTEST_MSVC +#endif // DOCTEST_NORETURN + +#ifndef DOCTEST_NOEXCEPT +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_NOEXCEPT +#else // DOCTEST_MSVC +#define DOCTEST_NOEXCEPT noexcept +#endif // DOCTEST_MSVC +#endif // DOCTEST_NOEXCEPT + +#ifndef DOCTEST_CONSTEXPR +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_CONSTEXPR const +#else // DOCTEST_MSVC +#define DOCTEST_CONSTEXPR constexpr +#endif // DOCTEST_MSVC +#endif // DOCTEST_CONSTEXPR // ================================================================================================= // == FEATURE DETECTION END ======================================================================== @@ -320,8 +369,6 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom constr #define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__) #endif // __COUNTER__ -#define DOCTEST_TOSTR(x) #x - #ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE #define DOCTEST_REF_WRAP(x) x& #else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE @@ -339,16 +386,31 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom constr #define DOCTEST_PLATFORM_LINUX #endif // DOCTEST_PLATFORM -#define DOCTEST_GLOBAL_NO_WARNINGS(var) \ +namespace doctest { namespace detail { + static DOCTEST_CONSTEXPR int consume(const int*, int) { return 0; } +}} + +#define DOCTEST_GLOBAL_NO_WARNINGS(var, ...) \ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \ - DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-variable") \ - static int var DOCTEST_UNUSED // NOLINT(fuchsia-statically-constructed-objects,cert-err58-cpp) -#define DOCTEST_GLOBAL_NO_WARNINGS_END() DOCTEST_CLANG_SUPPRESS_WARNING_POP + static const int var = doctest::detail::consume(&var, __VA_ARGS__); \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP #ifndef DOCTEST_BREAK_INTO_DEBUGGER // should probably take a look at https://github.com/scottt/debugbreak -#ifdef DOCTEST_PLATFORM_MAC -#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) +#ifdef DOCTEST_PLATFORM_LINUX +#if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) +// Break at the location of the failing check if possible +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler) +#else +#include +#define DOCTEST_BREAK_INTO_DEBUGGER() raise(SIGTRAP) +#endif +#elif defined(DOCTEST_PLATFORM_MAC) +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(__i386) +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler) +#else +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT (hicpp-no-assembler) +#endif #elif DOCTEST_MSVC #define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak() #elif defined(__MINGW32__) @@ -357,7 +419,7 @@ extern "C" __declspec(dllimport) void __stdcall DebugBreak(); DOCTEST_GCC_SUPPRESS_WARNING_POP #define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak() #else // linux -#define DOCTEST_BREAK_INTO_DEBUGGER() ((void)0) +#define DOCTEST_BREAK_INTO_DEBUGGER() (static_cast(0)) #endif // linux #endif // DOCTEST_BREAK_INTO_DEBUGGER @@ -366,32 +428,31 @@ DOCTEST_GCC_SUPPRESS_WARNING_POP #define DOCTEST_CONFIG_USE_STD_HEADERS #endif // DOCTEST_CONFIG_USE_IOSFWD -#ifdef DOCTEST_CONFIG_USE_STD_HEADERS -#include -#include -#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) -// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 -#include -#endif // VS 2019 -#else // DOCTEST_CONFIG_USE_STD_HEADERS - +// for clang - always include ciso646 (which drags some std stuff) because +// we want to check if we are using libc++ with the _LIBCPP_VERSION macro in +// which case we don't want to forward declare stuff from std - for reference: +// https://github.com/doctest/doctest/issues/126 +// https://github.com/doctest/doctest/issues/356 #if DOCTEST_CLANG -// to detect if libc++ is being used with clang (the _LIBCPP_VERSION identifier) #include +#ifdef _LIBCPP_VERSION +#define DOCTEST_CONFIG_USE_STD_HEADERS +#endif // _LIBCPP_VERSION #endif // clang -#ifdef _LIBCPP_VERSION -#define DOCTEST_STD_NAMESPACE_BEGIN _LIBCPP_BEGIN_NAMESPACE_STD -#define DOCTEST_STD_NAMESPACE_END _LIBCPP_END_NAMESPACE_STD -#else // _LIBCPP_VERSION -#define DOCTEST_STD_NAMESPACE_BEGIN namespace std { -#define DOCTEST_STD_NAMESPACE_END } -#endif // _LIBCPP_VERSION +#ifdef DOCTEST_CONFIG_USE_STD_HEADERS +#ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#include +#include +#include +#else // DOCTEST_CONFIG_USE_STD_HEADERS // Forward declaring 'X' in namespace std is not permitted by the C++ Standard. DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643) -DOCTEST_STD_NAMESPACE_BEGIN // NOLINT (cert-dcl58-cpp) +namespace std { // NOLINT (cert-dcl58-cpp) typedef decltype(nullptr) nullptr_t; template struct char_traits; @@ -400,17 +461,20 @@ struct char_traits; template class basic_ostream; typedef basic_ostream> ostream; +template +class basic_istream; +typedef basic_istream> istream; template class tuple; #if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) -// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 -template +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 +template class allocator; -template +template class basic_string; using string = basic_string, allocator>; #endif // VS 2019 -DOCTEST_STD_NAMESPACE_END +} // namespace std DOCTEST_MSVC_SUPPRESS_WARNING_POP @@ -462,6 +526,8 @@ class DOCTEST_INTERFACE String view data; }; + char* allocate(unsigned sz); + bool isOnStack() const { return (buf[last] & 128) == 0; } void setOnHeap(); void setLast(unsigned in = last); @@ -476,11 +542,12 @@ public: String(const char* in); String(const char* in, unsigned in_size); + String(std::istream& in, unsigned in_size); + String(const String& other); String& operator=(const String& other); String& operator+=(const String& other); - String operator+(const String& other) const; String(String&& other); String& operator=(String&& other); @@ -503,6 +570,8 @@ public: int compare(const String& other, bool no_case = false) const; }; +DOCTEST_INTERFACE String operator+(const String& lhs, const String& rhs); + DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs); DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs); DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs); @@ -632,12 +701,14 @@ DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file); struct DOCTEST_INTERFACE TestCaseData { - const char* m_file; // the file in which the test was registered + String m_file; // the file in which the test was registered (using String - see #350) unsigned m_line; // the line where the test was registered const char* m_name; // name of the test case const char* m_test_suite; // the test suite in which the test was added const char* m_description; bool m_skip; + bool m_no_breaks; + bool m_no_output; bool m_may_fail; bool m_should_fail; int m_expected_failures; @@ -677,7 +748,7 @@ struct DOCTEST_INTERFACE MessageData struct DOCTEST_INTERFACE SubcaseSignature { - const char* m_name; + String m_name; const char* m_file; int m_line; @@ -691,11 +762,16 @@ struct DOCTEST_INTERFACE IContextScope virtual void stringify(std::ostream*) const = 0; }; +namespace detail { + struct DOCTEST_INTERFACE TestCase; +} // namespace detail + struct ContextOptions //!OCLINT too many fields { - std::ostream* cout; // stdout stream - std::cout by default - std::ostream* cerr; // stderr stream - std::cerr by default - String binary_name; // the test binary name + std::ostream* cout = nullptr; // stdout stream + String binary_name; // the test binary name + + const detail::TestCase* currentTest = nullptr; // == parameters from the command line String out; // output filename @@ -712,9 +788,12 @@ struct ContextOptions //!OCLINT too many fields bool case_sensitive; // if filtering should be case sensitive bool exit; // if the program should be exited after the tests are ran/whatever bool duration; // print the time duration of each test case + bool minimal; // minimal console output (only test failures) + bool quiet; // no console output bool no_throw; // to skip exceptions-related assertion macros bool no_exitcode; // if the framework should return 0 as the exitcode bool no_run; // to not run the tests at all (can be done with an "*" exclude) + bool no_intro; // to not print the intro of the framework bool no_version; // to not print the version of the framework bool no_colors; // if output to the console should be colorized bool force_colors; // forces the use of colors even when a tty cannot be detected @@ -723,7 +802,9 @@ struct ContextOptions //!OCLINT too many fields bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x): bool no_path_in_filenames; // if the path to files should be removed from the output bool no_line_numbers; // if source code line numbers should be omitted from the output + bool no_debug_output; // no output in the debug console when a debugger is attached bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!! + bool no_time_in_output; // omit any time/timestamps from output !!! UNDOCUMENTED !!! bool help; // to print the help bool version; // to print the version @@ -734,7 +815,6 @@ struct ContextOptions //!OCLINT too many fields }; namespace detail { -#if defined(DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || defined(DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS) template struct enable_if {}; @@ -742,15 +822,48 @@ namespace detail { template struct enable_if { typedef TYPE type; }; -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS // clang-format off template struct remove_reference { typedef T type; }; template struct remove_reference { typedef T type; }; template struct remove_reference { typedef T type; }; + template U declval(int); + + template T declval(long); + + template auto declval() DOCTEST_NOEXCEPT -> decltype(declval(0)) ; + + template struct is_lvalue_reference { const static bool value=false; }; + template struct is_lvalue_reference { const static bool value=true; }; + + template struct is_rvalue_reference { const static bool value=false; }; + template struct is_rvalue_reference { const static bool value=true; }; + + template + inline T&& forward(typename remove_reference::type& t) DOCTEST_NOEXCEPT + { + return static_cast(t); + } + + template + inline T&& forward(typename remove_reference::type&& t) DOCTEST_NOEXCEPT + { + static_assert(!is_lvalue_reference::value, + "Can not forward an rvalue as an lvalue."); + return static_cast(t); + } + template struct remove_const { typedef T type; }; template struct remove_const { typedef T type; }; +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template struct is_enum : public std::is_enum {}; + template struct underlying_type : public std::underlying_type {}; +#else + // Use compiler intrinsics + template struct is_enum { DOCTEST_CONSTEXPR static bool value = __is_enum(T); }; + template struct underlying_type { typedef __underlying_type(T) type; }; +#endif // clang-format on template @@ -759,38 +872,27 @@ namespace detail { { static const bool value = false; }; namespace has_insertion_operator_impl { - typedef char no; - typedef char yes[2]; + std::ostream &os(); + template + DOCTEST_REF_WRAP(T) val(); - struct any_t - { - template - // cppcheck-suppress noExplicitConstructor - any_t(const DOCTEST_REF_WRAP(T)); + template + struct check { + static DOCTEST_CONSTEXPR bool value = false; }; - yes& testStreamable(std::ostream&); - no testStreamable(no); - - no operator<<(const std::ostream&, const any_t&); - - template - struct has_insertion_operator - { - static std::ostream& s; - static const DOCTEST_REF_WRAP(T) t; - static const bool value = sizeof(decltype(testStreamable(s << t))) == sizeof(yes); + template + struct check(), void())> { + static DOCTEST_CONSTEXPR bool value = true; }; } // namespace has_insertion_operator_impl - template - struct has_insertion_operator : has_insertion_operator_impl::has_insertion_operator - {}; + template + using has_insertion_operator = has_insertion_operator_impl::check; - DOCTEST_INTERFACE void my_memcpy(void* dest, const void* src, unsigned num); + DOCTEST_INTERFACE std::ostream* tlssPush(); + DOCTEST_INTERFACE String tlssPop(); - DOCTEST_INTERFACE std::ostream* getTlsOss(); // returns a thread-local ostringstream - DOCTEST_INTERFACE String getTlsOssResult(); template struct StringMakerBase @@ -801,13 +903,61 @@ namespace detail { } }; + // Vector and various type other than pointer or array. + template + struct filldata + { + static void fill(std::ostream* stream, const T &in) { + *stream << in; + } + }; + + template + struct filldata + { + static void fill(std::ostream* stream, const T (&in)[N]) { + for (unsigned long i = 0; i < N; i++) { + *stream << in[i]; + } + } + }; + + // Specialized since we don't want the terminating null byte! + template + struct filldata + { + static void fill(std::ostream* stream, const char(&in)[N]) { + *stream << in; + } + }; + + template + void filloss(std::ostream* stream, const T& in) { + filldata::fill(stream, in); + } + + template + void filloss(std::ostream* stream, const T (&in)[N]) { + // T[N], T(&)[N], T(&&)[N] have same behaviour. + // Hence remove reference. + filldata::type>::fill(stream, in); + } + template <> struct StringMakerBase { template static String convert(const DOCTEST_REF_WRAP(T) in) { - *getTlsOss() << in; - return getTlsOssResult(); + /* When parameter "in" is a null terminated const char* it works. + * When parameter "in" is a T arr[N] without '\0' we can fill the + * stringstream with N objects (T=char).If in is char pointer * + * without '\0' , it would cause segfault + * stepping over unaccessible memory. + */ + + std::ostream* stream = tlssPush(); + filloss(stream, in); + return tlssPop(); } }; @@ -849,7 +999,7 @@ struct StringMaker } }; -template +template ::value, bool>::type = true> String toString(const DOCTEST_REF_WRAP(T) value) { return StringMaker::convert(value); } @@ -876,8 +1026,14 @@ DOCTEST_INTERFACE String toString(int long long in); DOCTEST_INTERFACE String toString(int long long unsigned in); DOCTEST_INTERFACE String toString(std::nullptr_t in); +template ::value, bool>::type = true> +String toString(const DOCTEST_REF_WRAP(T) value) { + typedef typename detail::underlying_type::type UT; + return toString(static_cast(value)); +} + #if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) -// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 DOCTEST_INTERFACE String toString(const std::string& in); #endif // VS 2019 @@ -990,7 +1146,7 @@ namespace detail { DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at); #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - [[noreturn]] + DOCTEST_NORETURN #endif // DOCTEST_CONFIG_NO_EXCEPTIONS DOCTEST_INTERFACE void throwException(); @@ -999,7 +1155,7 @@ namespace detail { SubcaseSignature m_signature; bool m_entered = false; - Subcase(const char* name, const char* file, int line); + Subcase(const String& name, const char* file, int line); ~Subcase(); operator bool() const; @@ -1008,13 +1164,33 @@ namespace detail { template String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op, const DOCTEST_REF_WRAP(R) rhs) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) return toString(lhs) + op + toString(rhs); } +#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison") +#endif + +// This will check if there is any way it could find a operator like member or friend and uses it. +// If not it doesn't find the operator or if the operator at global scope is defined after +// this template, the template won't be instantiated due to SFINAE. Once the template is not +// instantiated it can look for global operator using normal conversions. +#define SFINAE_OP(ret,op) decltype((void)(doctest::detail::declval() op doctest::detail::declval()),ret{}) + #define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \ template \ - DOCTEST_NOINLINE Result operator op(const DOCTEST_REF_WRAP(R) rhs) { \ - bool res = op_macro(lhs, rhs); \ + DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(const R&& rhs) { \ + bool res = op_macro(doctest::detail::forward(lhs), doctest::detail::forward(rhs)); \ + if(m_at & assertType::is_false) \ + res = !res; \ + if(!res || doctest::getContextOptions()->success) \ + return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \ + return Result(res); \ + } \ + template ::value, void >::type* = nullptr> \ + DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(const R& rhs) { \ + bool res = op_macro(doctest::detail::forward(lhs), rhs); \ if(m_at & assertType::is_false) \ res = !res; \ if(!res || doctest::getContextOptions()->success) \ @@ -1038,9 +1214,10 @@ namespace detail { bool m_passed; String m_decomp; + Result() = default; Result(bool passed, const String& decomposition = String()); - // forbidding some expressions based on this table: http://en.cppreference.com/w/cpp/language/operator_precedence + // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence DOCTEST_FORBIT_EXPRESSION(Result, &) DOCTEST_FORBIT_EXPRESSION(Result, ^) DOCTEST_FORBIT_EXPRESSION(Result, |) @@ -1082,7 +1259,7 @@ namespace detail { //DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal") DOCTEST_MSVC_SUPPRESS_WARNING_PUSH - // http://stackoverflow.com/questions/39479163 what's the difference between 4018 and 4389 + // https://stackoverflow.com/questions/39479163 what's the difference between 4018 and 4389 DOCTEST_MSVC_SUPPRESS_WARNING(4388) // signed/unsigned mismatch DOCTEST_MSVC_SUPPRESS_WARNING(4389) // 'operator' : signed/unsigned mismatch DOCTEST_MSVC_SUPPRESS_WARNING(4018) // 'expression' : signed/unsigned mismatch @@ -1095,6 +1272,7 @@ namespace detail { #define DOCTEST_COMPARISON_RETURN_TYPE bool #else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING #define DOCTEST_COMPARISON_RETURN_TYPE typename enable_if::value || can_use_op::value, bool>::type + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); } inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); } inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); } @@ -1141,12 +1319,15 @@ namespace detail { L lhs; assertType::Enum m_at; - explicit Expression_lhs(L in, assertType::Enum at) - : lhs(in) + explicit Expression_lhs(L&& in, assertType::Enum at) + : lhs(doctest::detail::forward(in)) , m_at(at) {} DOCTEST_NOINLINE operator Result() { - bool res = !!lhs; +// this is needed only for MSVC 2015 +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4800) // 'int': forcing value to bool + bool res = static_cast(lhs); +DOCTEST_MSVC_SUPPRESS_WARNING_POP if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional res = !res; @@ -1155,6 +1336,9 @@ namespace detail { return Result(res); } + /* This is required for user-defined conversions from Expression_lhs to L */ + operator L() const { return lhs; } + // clang-format off DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional @@ -1164,7 +1348,7 @@ namespace detail { DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<=, " <= ", DOCTEST_CMP_LE) //!OCLINT bitwise operator in conditional // clang-format on - // forbidding some expressions based on this table: http://en.cppreference.com/w/cpp/language/operator_precedence + // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^) DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |) @@ -1195,6 +1379,10 @@ namespace detail { #endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION +#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) +DOCTEST_CLANG_SUPPRESS_WARNING_POP +#endif + struct DOCTEST_INTERFACE ExpressionDecomposer { assertType::Enum m_at; @@ -1206,20 +1394,27 @@ namespace detail { // https://github.com/catchorg/Catch2/issues/870 // https://github.com/catchorg/Catch2/issues/565 template - Expression_lhs operator<<(const DOCTEST_REF_WRAP(L) operand) { - return Expression_lhs(operand, m_at); + Expression_lhs operator<<(const L &&operand) { + return Expression_lhs(doctest::detail::forward(operand), m_at); + } + + template ::value,void >::type* = nullptr> + Expression_lhs operator<<(const L &operand) { + return Expression_lhs(operand, m_at); } }; struct DOCTEST_INTERFACE TestSuite { - const char* m_test_suite; - const char* m_description; - bool m_skip; - bool m_may_fail; - bool m_should_fail; - int m_expected_failures; - double m_timeout; + const char* m_test_suite = nullptr; + const char* m_description = nullptr; + bool m_skip = false; + bool m_no_breaks = false; + bool m_no_output = false; + bool m_may_fail = false; + bool m_should_fail = false; + int m_expected_failures = 0; + double m_timeout = 0; TestSuite& operator*(const char* in); @@ -1287,12 +1482,12 @@ namespace detail { template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } }; // clang-format on - DOCTEST_BINARY_RELATIONAL_OP(0, eq) - DOCTEST_BINARY_RELATIONAL_OP(1, ne) - DOCTEST_BINARY_RELATIONAL_OP(2, gt) - DOCTEST_BINARY_RELATIONAL_OP(3, lt) - DOCTEST_BINARY_RELATIONAL_OP(4, ge) - DOCTEST_BINARY_RELATIONAL_OP(5, le) + DOCTEST_BINARY_RELATIONAL_OP(0, doctest::detail::eq) + DOCTEST_BINARY_RELATIONAL_OP(1, doctest::detail::ne) + DOCTEST_BINARY_RELATIONAL_OP(2, doctest::detail::gt) + DOCTEST_BINARY_RELATIONAL_OP(3, doctest::detail::lt) + DOCTEST_BINARY_RELATIONAL_OP(4, doctest::detail::ge) + DOCTEST_BINARY_RELATIONAL_OP(5, doctest::detail::le) struct DOCTEST_INTERFACE ResultBuilder : public AssertData { @@ -1302,15 +1497,16 @@ namespace detail { void setResult(const Result& res); template - DOCTEST_NOINLINE void binary_assert(const DOCTEST_REF_WRAP(L) lhs, + DOCTEST_NOINLINE bool binary_assert(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { m_failed = !RelationalComparator()(lhs, rhs); if(m_failed || getContextOptions()->success) m_decomp = stringifyBinaryExpr(lhs, ", ", rhs); + return !m_failed; } template - DOCTEST_NOINLINE void unary_assert(const DOCTEST_REF_WRAP(L) val) { + DOCTEST_NOINLINE bool unary_assert(const DOCTEST_REF_WRAP(L) val) { m_failed = !val; if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional @@ -1318,6 +1514,8 @@ namespace detail { if(m_failed || getContextOptions()->success) m_decomp = toString(val); + + return !m_failed; } void translateException(); @@ -1337,7 +1535,7 @@ namespace detail { DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad); - DOCTEST_INTERFACE void decomp_assert(assertType::Enum at, const char* file, int line, + DOCTEST_INTERFACE bool decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, Result result); #define DOCTEST_ASSERT_OUT_OF_TESTS(decomp) \ @@ -1353,7 +1551,7 @@ namespace detail { if(checkIfShouldThrow(at)) \ throwException(); \ } \ - return; \ + return !failed; \ } \ } while(false) @@ -1368,7 +1566,7 @@ namespace detail { throwException() template - DOCTEST_NOINLINE void binary_assert(assertType::Enum at, const char* file, int line, + DOCTEST_NOINLINE bool binary_assert(assertType::Enum at, const char* file, int line, const char* expr, const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { bool failed = !RelationalComparator()(lhs, rhs); @@ -1379,10 +1577,11 @@ namespace detail { // ################################################################################### DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); + return !failed; } template - DOCTEST_NOINLINE void unary_assert(assertType::Enum at, const char* file, int line, + DOCTEST_NOINLINE bool unary_assert(assertType::Enum at, const char* file, int line, const char* expr, const DOCTEST_REF_WRAP(L) val) { bool failed = !val; @@ -1395,6 +1594,7 @@ namespace detail { // ################################################################################### DOCTEST_ASSERT_OUT_OF_TESTS(toString(val)); DOCTEST_ASSERT_IN_TESTS(toString(val)); + return !failed; } struct DOCTEST_INTERFACE IExceptionTranslator @@ -1419,9 +1619,9 @@ namespace detail { } catch(T ex) { // NOLINT res = m_translateFunction(ex); //!OCLINT parameter reassignment return true; - } catch(...) {} //!OCLINT - empty catch statement -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - ((void)res); // to silence -Wunused-parameter + } catch(...) {} //!OCLINT - empty catch statement +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + static_cast(res); // to silence -Wunused-parameter return false; } @@ -1488,38 +1688,57 @@ namespace detail { class DOCTEST_INTERFACE ContextScopeBase : public IContextScope { protected: ContextScopeBase(); + ContextScopeBase(ContextScopeBase&& other); void destroy(); + bool need_to_destroy{true}; }; - template class DOCTEST_INTERFACE ContextScope : public ContextScopeBase + template class ContextScope : public ContextScopeBase { - const L &lambda_; + const L lambda_; public: explicit ContextScope(const L &lambda) : lambda_(lambda) {} - ContextScope(ContextScope &&other) : lambda_(other.lambda_) {} + ContextScope(ContextScope &&other) : ContextScopeBase(static_cast(other)), lambda_(other.lambda_) {} void stringify(std::ostream* s) const override { lambda_(s); } - ~ContextScope() override { destroy(); } + ~ContextScope() override { + if (need_to_destroy) { + destroy(); + } + } }; struct DOCTEST_INTERFACE MessageBuilder : public MessageData { std::ostream* m_stream; + bool logged = false; MessageBuilder(const char* file, int line, assertType::Enum severity); MessageBuilder() = delete; ~MessageBuilder(); + // the preferred way of chaining parameters for stringification template - MessageBuilder& operator<<(const T& in) { + MessageBuilder& operator,(const T& in) { toStream(m_stream, in); return *this; } + // kept here just for backwards-compatibility - the comma operator should be preferred now + template + MessageBuilder& operator<<(const T& in) { return this->operator,(in); } + + // the `,` operator has the lowest operator precedence - if `<<` is used by the user then + // the `,` operator will be called last which is not what we want and thus the `*` operator + // is used first (has higher operator precedence compared to `<<`) so that we guarantee that + // an operator of the MessageBuilder class is called first before the rest of the parameters + template + MessageBuilder& operator*(const T& in) { return this->operator,(in); } + bool log(); void react(); }; @@ -1543,6 +1762,8 @@ namespace detail { DOCTEST_DEFINE_DECORATOR(test_suite, const char*, ""); DOCTEST_DEFINE_DECORATOR(description, const char*, ""); DOCTEST_DEFINE_DECORATOR(skip, bool, true); +DOCTEST_DEFINE_DECORATOR(no_breaks, bool, true); +DOCTEST_DEFINE_DECORATOR(no_output, bool, true); DOCTEST_DEFINE_DECORATOR(timeout, double, 0); DOCTEST_DEFINE_DECORATOR(may_fail, bool, true); DOCTEST_DEFINE_DECORATOR(should_fail, bool, true); @@ -1593,6 +1814,7 @@ public: void addFilter(const char* filter, const char* value); void clearFilters(); + void setOption(const char* option, bool value); void setOption(const char* option, int value); void setOption(const char* option, const char* value); @@ -1602,6 +1824,8 @@ public: void setAssertHandler(detail::assert_handler ah); + void setCout(std::ostream* out); + int run(); }; @@ -1628,6 +1852,7 @@ struct DOCTEST_INTERFACE CurrentTestCaseStats int numAssertsFailedCurrentTest; double seconds; int failure_flags; // use TestCaseFailureReason::Enum + bool testCaseSuccess; }; struct DOCTEST_INTERFACE TestCaseException @@ -1648,9 +1873,9 @@ struct DOCTEST_INTERFACE TestRunStats struct QueryData { - const TestRunStats* run_stats = nullptr; - String* data = nullptr; - unsigned num_data = 0; + const TestRunStats* run_stats = nullptr; + const TestCaseData** data = nullptr; + unsigned num_data = 0; }; struct DOCTEST_INTERFACE IReporter @@ -1725,10 +1950,11 @@ int registerReporter(const char* name, int priority, bool isReporter) { #if !defined(DOCTEST_CONFIG_DISABLE) // common code in asserts - for convenience -#define DOCTEST_ASSERT_LOG_AND_REACT(b) \ +#define DOCTEST_ASSERT_LOG_REACT_RETURN(b) \ if(b.log()) \ DOCTEST_BREAK_INTO_DEBUGGER(); \ - b.react() + b.react(); \ + return !b.m_failed #ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS #define DOCTEST_WRAP_IN_TRY(x) x; @@ -1736,27 +1962,26 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_WRAP_IN_TRY(x) \ try { \ x; \ - } catch(...) { _DOCTEST_RB.translateException(); } + } catch(...) { DOCTEST_RB.translateException(); } #endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS #ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS -#define DOCTEST_CAST_TO_VOID(x) \ +#define DOCTEST_CAST_TO_VOID(...) \ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wuseless-cast") \ - static_cast(x); \ + static_cast(__VA_ARGS__); \ DOCTEST_GCC_SUPPRESS_WARNING_POP #else // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS -#define DOCTEST_CAST_TO_VOID(x) x; +#define DOCTEST_CAST_TO_VOID(...) __VA_ARGS__; #endif // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS // registers the test by initializing a dummy var with a function #define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators) \ - global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ + global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), \ doctest::detail::regTest( \ doctest::detail::TestCase( \ f, __FILE__, __LINE__, \ doctest_detail_test_suite_ns::getCurrentTestSuite()) * \ - decorators); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() + decorators)) #define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators) \ namespace { \ @@ -1779,18 +2004,18 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators) \ static doctest::detail::funcType proxy() { return f; } \ - DOCTEST_REGISTER_FUNCTION(inline const, proxy(), decorators) \ + DOCTEST_REGISTER_FUNCTION(inline, proxy(), decorators) \ static void f() // for registering tests #define DOCTEST_TEST_CASE(decorators) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators) // for registering tests in classes - requires C++17 for inline variables! #if __cplusplus >= 201703L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 12, 0) && _MSVC_LANG >= 201703L) #define DOCTEST_TEST_CASE_CLASS(decorators) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_PROXY_), \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_PROXY_), \ decorators) #else // DOCTEST_TEST_CASE_CLASS #define DOCTEST_TEST_CASE_CLASS(...) \ @@ -1799,8 +2024,8 @@ int registerReporter(const char* name, int priority, bool isReporter) { // for registering tests with a fixture #define DOCTEST_TEST_CASE_FIXTURE(c, decorators) \ - DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), c, \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), c, \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators) // for converting types to strings without the header and demangling #define DOCTEST_TYPE_TO_STRING_IMPL(...) \ @@ -1813,7 +2038,7 @@ int registerReporter(const char* name, int priority, bool isReporter) { DOCTEST_TYPE_TO_STRING_IMPL(__VA_ARGS__) \ } \ } \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + static_assert(true, "") #define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func) \ template \ @@ -1844,20 +2069,20 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id) \ DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR), \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)) + DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)) #define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY)) = \ - doctest::detail::instantiationHelper(DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0));\ - DOCTEST_GLOBAL_NO_WARNINGS_END() + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY), \ + doctest::detail::instantiationHelper( \ + DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0))) #define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \ + static_assert(true, "") #define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) \ + static_assert(true, "") #define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...) \ DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon); \ @@ -1866,11 +2091,11 @@ int registerReporter(const char* name, int priority, bool isReporter) { static void anon() #define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) + DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) // for subcases #define DOCTEST_SUBCASE(name) \ - if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \ + if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \ doctest::detail::Subcase(name, __FILE__, __LINE__)) // for grouping tests in test suites by using code blocks @@ -1879,10 +2104,12 @@ int registerReporter(const char* name, int priority, bool isReporter) { static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() { \ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \ - static doctest::detail::TestSuite data; \ + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmissing-field-initializers") \ + static doctest::detail::TestSuite data{}; \ static bool inited = false; \ DOCTEST_MSVC_SUPPRESS_WARNING_POP \ DOCTEST_CLANG_SUPPRESS_WARNING_POP \ + DOCTEST_GCC_SUPPRESS_WARNING_POP \ if(!inited) { \ data* decorators; \ inited = true; \ @@ -1894,79 +2121,79 @@ int registerReporter(const char* name, int priority, bool isReporter) { namespace ns_name #define DOCTEST_TEST_SUITE(decorators) \ - DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUITE_)) + DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(DOCTEST_ANON_SUITE_)) // for starting a testsuite block #define DOCTEST_TEST_SUITE_BEGIN(decorators) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ - doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators)) \ + static_assert(true, "") // for ending a testsuite block #define DOCTEST_TEST_SUITE_END \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ - doctest::detail::setTestSuite(doctest::detail::TestSuite() * ""); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * "")) \ + typedef int DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) // for registering exception translators #define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature) \ inline doctest::String translatorName(signature); \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)) = \ - doctest::registerExceptionTranslator(translatorName); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), \ + doctest::registerExceptionTranslator(translatorName)) \ doctest::String translatorName(signature) #define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ - DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_), \ + DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), \ signature) // for registering reporters #define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \ - doctest::registerReporter(name, priority, true); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), \ + doctest::registerReporter(name, priority, true)) \ + static_assert(true, "") // for registering listeners #define DOCTEST_REGISTER_LISTENER(name, priority, reporter) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \ - doctest::registerReporter(name, priority, false); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -// for logging -#define DOCTEST_INFO(expression) \ - DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), \ - DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), expression) - -#define DOCTEST_INFO_IMPL(lambda_name, mb_name, s_name, expression) \ - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4626) \ - auto lambda_name = [&](std::ostream* s_name) { \ - doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \ - mb_name.m_stream = s_name; \ - mb_name << expression; \ - }; \ - DOCTEST_MSVC_SUPPRESS_WARNING_POP \ - auto DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope(lambda_name) - -#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := " << x) - -#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, x) \ - do { \ - doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \ - mb << x; \ - DOCTEST_ASSERT_LOG_AND_REACT(mb); \ - } while((void)0, 0) + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), \ + doctest::registerReporter(name, priority, false)) \ + static_assert(true, "") // clang-format off -#define DOCTEST_ADD_MESSAGE_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) -#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) -#define DOCTEST_ADD_FAIL_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) +// for logging - disabling formatting because it's important to have these on 2 separate lines - see PR #557 +#define DOCTEST_INFO(...) \ + DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_), \ + DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_OTHER_), \ + __VA_ARGS__) // clang-format on -#define DOCTEST_MESSAGE(x) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, x) -#define DOCTEST_FAIL_CHECK(x) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, x) -#define DOCTEST_FAIL(x) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, x) +#define DOCTEST_INFO_IMPL(mb_name, s_name, ...) \ + auto DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope( \ + [&](std::ostream* s_name) { \ + doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \ + mb_name.m_stream = s_name; \ + mb_name * __VA_ARGS__; \ + }) + +#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := ", x) + +#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, ...) \ + [&] { \ + doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \ + mb * __VA_ARGS__; \ + if(mb.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + mb.react(); \ + }() + +// clang-format off +#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) +// clang-format on + +#define DOCTEST_MESSAGE(...) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, __VA_ARGS__) +#define DOCTEST_FAIL_CHECK(...) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, __VA_ARGS__) +#define DOCTEST_FAIL(...) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, __VA_ARGS__) #define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility. @@ -1974,18 +2201,18 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...) \ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, #__VA_ARGS__); \ - DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.setResult( \ + DOCTEST_WRAP_IN_TRY(DOCTEST_RB.setResult( \ doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ << __VA_ARGS__)) \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB) \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB) \ DOCTEST_CLANG_SUPPRESS_WARNING_POP #define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ - do { \ + [&] { \ DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__); \ - } while((void)0, 0) + }() #else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS @@ -2009,109 +2236,113 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__) // clang-format off -#define DOCTEST_WARN_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } while((void)0, 0) -#define DOCTEST_CHECK_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } while((void)0, 0) -#define DOCTEST_REQUIRE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } while((void)0, 0) -#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } while((void)0, 0) -#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } while((void)0, 0) -#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } while((void)0, 0) +#define DOCTEST_WARN_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); }() +#define DOCTEST_CHECK_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); }() +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); }() +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); }() +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); }() +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); }() // clang-format on #define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...) \ - do { \ + [&] { \ if(!doctest::getContextOptions()->no_throw) { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, #expr, #__VA_ARGS__, message); \ try { \ DOCTEST_CAST_TO_VOID(expr) \ - } catch(const doctest::detail::remove_const< \ - doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) { \ - _DOCTEST_RB.translateException(); \ - _DOCTEST_RB.m_threw_as = true; \ - } catch(...) { _DOCTEST_RB.translateException(); } \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } catch(const typename doctest::detail::remove_const< \ + typename doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) { \ + DOCTEST_RB.translateException(); \ + DOCTEST_RB.m_threw_as = true; \ + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } else { \ + return false; \ } \ - } while((void)0, 0) + }() -#define DOCTEST_ASSERT_THROWS_WITH(expr, assert_type, ...) \ - do { \ +#define DOCTEST_ASSERT_THROWS_WITH(expr, expr_str, assert_type, ...) \ + [&] { \ if(!doctest::getContextOptions()->no_throw) { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #expr, "", __VA_ARGS__); \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, expr_str, "", __VA_ARGS__); \ try { \ DOCTEST_CAST_TO_VOID(expr) \ - } catch(...) { _DOCTEST_RB.translateException(); } \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } else { \ + return false; \ } \ - } while((void)0, 0) + }() -#define DOCTEST_ASSERT_NOTHROW(expr, assert_type) \ - do { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #expr); \ +#define DOCTEST_ASSERT_NOTHROW(assert_type, ...) \ + [&] { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ try { \ - DOCTEST_CAST_TO_VOID(expr) \ - } catch(...) { _DOCTEST_RB.translateException(); } \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while((void)0, 0) + DOCTEST_CAST_TO_VOID(__VA_ARGS__) \ + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + }() // clang-format off -#define DOCTEST_WARN_THROWS(expr) DOCTEST_ASSERT_THROWS_WITH(expr, DT_WARN_THROWS, "") -#define DOCTEST_CHECK_THROWS(expr) DOCTEST_ASSERT_THROWS_WITH(expr, DT_CHECK_THROWS, "") -#define DOCTEST_REQUIRE_THROWS(expr) DOCTEST_ASSERT_THROWS_WITH(expr, DT_REQUIRE_THROWS, "") +#define DOCTEST_WARN_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_WARN_THROWS, "") +#define DOCTEST_CHECK_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_CHECK_THROWS, "") +#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_REQUIRE_THROWS, "") #define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_AS, "", __VA_ARGS__) #define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_AS, "", __VA_ARGS__) #define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_AS, "", __VA_ARGS__) -#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, DT_WARN_THROWS_WITH, __VA_ARGS__) -#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, DT_CHECK_THROWS_WITH, __VA_ARGS__) -#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_WARN_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_CHECK_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__) #define DOCTEST_WARN_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_WITH_AS, message, __VA_ARGS__) #define DOCTEST_CHECK_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_WITH_AS, message, __VA_ARGS__) #define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_WITH_AS, message, __VA_ARGS__) -#define DOCTEST_WARN_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_WARN_NOTHROW) -#define DOCTEST_CHECK_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_CHECK_NOTHROW) -#define DOCTEST_REQUIRE_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_REQUIRE_NOTHROW) +#define DOCTEST_WARN_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_WARN_NOTHROW, __VA_ARGS__) +#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_CHECK_NOTHROW, __VA_ARGS__) +#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_REQUIRE_NOTHROW, __VA_ARGS__) -#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS(expr); } while((void)0, 0) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS(expr); } while((void)0, 0) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS(expr); } while((void)0, 0) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_AS(expr, ex); } while((void)0, 0) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_AS(expr, ex); } while((void)0, 0) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } while((void)0, 0) -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_WITH(expr, with); } while((void)0, 0) -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_WITH(expr, with); } while((void)0, 0) -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } while((void)0, 0) -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } while((void)0, 0) -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } while((void)0, 0) -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } while((void)0, 0) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_NOTHROW(expr); } while((void)0, 0) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_NOTHROW(expr); } while((void)0, 0) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_NOTHROW(expr); } while((void)0, 0) +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS(expr); }() +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS(expr); }() +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS(expr); }() +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_AS(expr, ex); }() +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_AS(expr, ex); }() +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_AS(expr, ex); }() +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH(expr, with); }() +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH(expr, with); }() +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH(expr, with); }() +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); }() +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); }() +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); }() +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_NOTHROW(expr); }() +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_NOTHROW(expr); }() +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_NOTHROW(expr); }() // clang-format on #ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS #define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \ - do { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + [&] { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, #__VA_ARGS__); \ DOCTEST_WRAP_IN_TRY( \ - _DOCTEST_RB.binary_assert( \ + DOCTEST_RB.binary_assert( \ __VA_ARGS__)) \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while((void)0, 0) + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + }() #define DOCTEST_UNARY_ASSERT(assert_type, ...) \ - do { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + [&] { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ __LINE__, #__VA_ARGS__); \ - DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.unary_assert(__VA_ARGS__)) \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while((void)0, 0) + DOCTEST_WRAP_IN_TRY(DOCTEST_RB.unary_assert(__VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + }() #else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS @@ -2187,37 +2418,37 @@ int registerReporter(const char* name, int priority, bool isReporter) { #ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS -#define DOCTEST_WARN_THROWS(expr) ((void)0) -#define DOCTEST_CHECK_THROWS(expr) ((void)0) -#define DOCTEST_REQUIRE_THROWS(expr) ((void)0) -#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_WARN_NOTHROW(expr) ((void)0) -#define DOCTEST_CHECK_NOTHROW(expr) ((void)0) -#define DOCTEST_REQUIRE_NOTHROW(expr) ((void)0) +#define DOCTEST_WARN_THROWS(...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS(...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS(...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_AS(expr, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) +#define DOCTEST_WARN_NOTHROW(...) ([] { return false; }) +#define DOCTEST_CHECK_NOTHROW(...) ([] { return false; }) +#define DOCTEST_REQUIRE_NOTHROW(...) ([] { return false; }) -#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) #else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS @@ -2259,35 +2490,32 @@ int registerReporter(const char* name, int priority, bool isReporter) { // for registering tests #define DOCTEST_TEST_CASE(name) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) // for registering tests in classes #define DOCTEST_TEST_CASE_CLASS(name) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) // for registering tests with a fixture #define DOCTEST_TEST_CASE_FIXTURE(x, name) \ - DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), x, \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), x, \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) // for converting types to strings without the header and demangling -#define DOCTEST_TYPE_TO_STRING(...) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) +#define DOCTEST_TYPE_TO_STRING(...) static_assert(true, "") #define DOCTEST_TYPE_TO_STRING_IMPL(...) // for typed tests #define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...) \ template \ - inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() + inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)() #define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id) \ template \ - inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() + inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)() -#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) static_assert(true, "") +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) static_assert(true, "") // for subcases #define DOCTEST_SUBCASE(name) @@ -2296,98 +2524,159 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_TEST_SUITE(name) namespace // for starting a testsuite block -#define DOCTEST_TEST_SUITE_BEGIN(name) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) +#define DOCTEST_TEST_SUITE_BEGIN(name) static_assert(true, "") // for ending a testsuite block -#define DOCTEST_TEST_SUITE_END typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) +#define DOCTEST_TEST_SUITE_END typedef int DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) #define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ template \ - static inline doctest::String DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)(signature) + static inline doctest::String DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_)(signature) #define DOCTEST_REGISTER_REPORTER(name, priority, reporter) #define DOCTEST_REGISTER_LISTENER(name, priority, reporter) -#define DOCTEST_INFO(x) ((void)0) -#define DOCTEST_CAPTURE(x) ((void)0) -#define DOCTEST_ADD_MESSAGE_AT(file, line, x) ((void)0) -#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) ((void)0) -#define DOCTEST_ADD_FAIL_AT(file, line, x) ((void)0) -#define DOCTEST_MESSAGE(x) ((void)0) -#define DOCTEST_FAIL_CHECK(x) ((void)0) -#define DOCTEST_FAIL(x) ((void)0) +#define DOCTEST_INFO(...) (static_cast(0)) +#define DOCTEST_CAPTURE(x) (static_cast(0)) +#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) (static_cast(0)) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) (static_cast(0)) +#define DOCTEST_ADD_FAIL_AT(file, line, ...) (static_cast(0)) +#define DOCTEST_MESSAGE(...) (static_cast(0)) +#define DOCTEST_FAIL_CHECK(...) (static_cast(0)) +#define DOCTEST_FAIL(...) (static_cast(0)) -#define DOCTEST_WARN(...) ((void)0) -#define DOCTEST_CHECK(...) ((void)0) -#define DOCTEST_REQUIRE(...) ((void)0) -#define DOCTEST_WARN_FALSE(...) ((void)0) -#define DOCTEST_CHECK_FALSE(...) ((void)0) -#define DOCTEST_REQUIRE_FALSE(...) ((void)0) +#ifdef DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED -#define DOCTEST_WARN_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_CHECK_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_REQUIRE_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_WARN(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_CHECK(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_REQUIRE(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_WARN_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_CHECK_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_FALSE(...) [&] { return !(__VA_ARGS__); }() -#define DOCTEST_WARN_THROWS(expr) ((void)0) -#define DOCTEST_CHECK_THROWS(expr) ((void)0) -#define DOCTEST_REQUIRE_THROWS(expr) ((void)0) -#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_WARN_NOTHROW(expr) ((void)0) -#define DOCTEST_CHECK_NOTHROW(expr) ((void)0) -#define DOCTEST_REQUIRE_NOTHROW(expr) ((void)0) +#define DOCTEST_WARN_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_CHECK_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() -#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0) +namespace doctest { +namespace detail { +#define DOCTEST_RELATIONAL_OP(name, op) \ + template \ + bool name(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs op rhs; } -#define DOCTEST_WARN_EQ(...) ((void)0) -#define DOCTEST_CHECK_EQ(...) ((void)0) -#define DOCTEST_REQUIRE_EQ(...) ((void)0) -#define DOCTEST_WARN_NE(...) ((void)0) -#define DOCTEST_CHECK_NE(...) ((void)0) -#define DOCTEST_REQUIRE_NE(...) ((void)0) -#define DOCTEST_WARN_GT(...) ((void)0) -#define DOCTEST_CHECK_GT(...) ((void)0) -#define DOCTEST_REQUIRE_GT(...) ((void)0) -#define DOCTEST_WARN_LT(...) ((void)0) -#define DOCTEST_CHECK_LT(...) ((void)0) -#define DOCTEST_REQUIRE_LT(...) ((void)0) -#define DOCTEST_WARN_GE(...) ((void)0) -#define DOCTEST_CHECK_GE(...) ((void)0) -#define DOCTEST_REQUIRE_GE(...) ((void)0) -#define DOCTEST_WARN_LE(...) ((void)0) -#define DOCTEST_CHECK_LE(...) ((void)0) -#define DOCTEST_REQUIRE_LE(...) ((void)0) + DOCTEST_RELATIONAL_OP(eq, ==) + DOCTEST_RELATIONAL_OP(ne, !=) + DOCTEST_RELATIONAL_OP(lt, <) + DOCTEST_RELATIONAL_OP(gt, >) + DOCTEST_RELATIONAL_OP(le, <=) + DOCTEST_RELATIONAL_OP(ge, >=) +} // namespace detail +} // namespace doctest -#define DOCTEST_WARN_UNARY(...) ((void)0) -#define DOCTEST_CHECK_UNARY(...) ((void)0) -#define DOCTEST_REQUIRE_UNARY(...) ((void)0) -#define DOCTEST_WARN_UNARY_FALSE(...) ((void)0) -#define DOCTEST_CHECK_UNARY_FALSE(...) ((void)0) -#define DOCTEST_REQUIRE_UNARY_FALSE(...) ((void)0) +#define DOCTEST_WARN_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_CHECK_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_WARN_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_CHECK_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_WARN_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_CHECK_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_WARN_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_CHECK_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_WARN_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_CHECK_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_WARN_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_CHECK_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_WARN_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_CHECK_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_REQUIRE_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_WARN_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_CHECK_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() + +#else // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED + +#define DOCTEST_WARN(...) ([] { return false; }) +#define DOCTEST_CHECK(...) ([] { return false; }) +#define DOCTEST_REQUIRE(...) ([] { return false; }) +#define DOCTEST_WARN_FALSE(...) ([] { return false; }) +#define DOCTEST_CHECK_FALSE(...) ([] { return false; }) +#define DOCTEST_REQUIRE_FALSE(...) ([] { return false; }) + +#define DOCTEST_WARN_MESSAGE(cond, ...) ([] { return false; }) +#define DOCTEST_CHECK_MESSAGE(cond, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) ([] { return false; }) +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) ([] { return false; }) +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) ([] { return false; }) + +#define DOCTEST_WARN_EQ(...) ([] { return false; }) +#define DOCTEST_CHECK_EQ(...) ([] { return false; }) +#define DOCTEST_REQUIRE_EQ(...) ([] { return false; }) +#define DOCTEST_WARN_NE(...) ([] { return false; }) +#define DOCTEST_CHECK_NE(...) ([] { return false; }) +#define DOCTEST_REQUIRE_NE(...) ([] { return false; }) +#define DOCTEST_WARN_GT(...) ([] { return false; }) +#define DOCTEST_CHECK_GT(...) ([] { return false; }) +#define DOCTEST_REQUIRE_GT(...) ([] { return false; }) +#define DOCTEST_WARN_LT(...) ([] { return false; }) +#define DOCTEST_CHECK_LT(...) ([] { return false; }) +#define DOCTEST_REQUIRE_LT(...) ([] { return false; }) +#define DOCTEST_WARN_GE(...) ([] { return false; }) +#define DOCTEST_CHECK_GE(...) ([] { return false; }) +#define DOCTEST_REQUIRE_GE(...) ([] { return false; }) +#define DOCTEST_WARN_LE(...) ([] { return false; }) +#define DOCTEST_CHECK_LE(...) ([] { return false; }) +#define DOCTEST_REQUIRE_LE(...) ([] { return false; }) + +#define DOCTEST_WARN_UNARY(...) ([] { return false; }) +#define DOCTEST_CHECK_UNARY(...) ([] { return false; }) +#define DOCTEST_REQUIRE_UNARY(...) ([] { return false; }) +#define DOCTEST_WARN_UNARY_FALSE(...) ([] { return false; }) +#define DOCTEST_CHECK_UNARY_FALSE(...) ([] { return false; }) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) ([] { return false; }) + +#endif // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED + +// TODO: think about if these also need to work properly even when doctest is disabled +#define DOCTEST_WARN_THROWS(...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS(...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS(...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_AS(expr, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ([] { return false; }) +#define DOCTEST_WARN_NOTHROW(...) ([] { return false; }) +#define DOCTEST_CHECK_NOTHROW(...) ([] { return false; }) +#define DOCTEST_REQUIRE_NOTHROW(...) ([] { return false; }) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; }) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; }) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) ([] { return false; }) #endif // DOCTEST_CONFIG_DISABLE @@ -2419,7 +2708,7 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_FAST_CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE #define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE -#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INVOKE +#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id,__VA_ARGS__) // clang-format on // BDD style macros @@ -2439,138 +2728,138 @@ int registerReporter(const char* name, int priority, bool isReporter) { // == SHORT VERSIONS OF THE MACROS #if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES) -#define TEST_CASE DOCTEST_TEST_CASE -#define TEST_CASE_CLASS DOCTEST_TEST_CASE_CLASS -#define TEST_CASE_FIXTURE DOCTEST_TEST_CASE_FIXTURE -#define TYPE_TO_STRING DOCTEST_TYPE_TO_STRING -#define TEST_CASE_TEMPLATE DOCTEST_TEST_CASE_TEMPLATE -#define TEST_CASE_TEMPLATE_DEFINE DOCTEST_TEST_CASE_TEMPLATE_DEFINE -#define TEST_CASE_TEMPLATE_INVOKE DOCTEST_TEST_CASE_TEMPLATE_INVOKE -#define TEST_CASE_TEMPLATE_APPLY DOCTEST_TEST_CASE_TEMPLATE_APPLY -#define SUBCASE DOCTEST_SUBCASE -#define TEST_SUITE DOCTEST_TEST_SUITE -#define TEST_SUITE_BEGIN DOCTEST_TEST_SUITE_BEGIN +#define TEST_CASE(name) DOCTEST_TEST_CASE(name) +#define TEST_CASE_CLASS(name) DOCTEST_TEST_CASE_CLASS(name) +#define TEST_CASE_FIXTURE(x, name) DOCTEST_TEST_CASE_FIXTURE(x, name) +#define TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING(__VA_ARGS__) +#define TEST_CASE_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(name, T, __VA_ARGS__) +#define TEST_CASE_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, T, id) +#define TEST_CASE_TEMPLATE_INVOKE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, __VA_ARGS__) +#define TEST_CASE_TEMPLATE_APPLY(id, ...) DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, __VA_ARGS__) +#define SUBCASE(name) DOCTEST_SUBCASE(name) +#define TEST_SUITE(decorators) DOCTEST_TEST_SUITE(decorators) +#define TEST_SUITE_BEGIN(name) DOCTEST_TEST_SUITE_BEGIN(name) #define TEST_SUITE_END DOCTEST_TEST_SUITE_END -#define REGISTER_EXCEPTION_TRANSLATOR DOCTEST_REGISTER_EXCEPTION_TRANSLATOR -#define REGISTER_REPORTER DOCTEST_REGISTER_REPORTER -#define REGISTER_LISTENER DOCTEST_REGISTER_LISTENER -#define INFO DOCTEST_INFO -#define CAPTURE DOCTEST_CAPTURE -#define ADD_MESSAGE_AT DOCTEST_ADD_MESSAGE_AT -#define ADD_FAIL_CHECK_AT DOCTEST_ADD_FAIL_CHECK_AT -#define ADD_FAIL_AT DOCTEST_ADD_FAIL_AT -#define MESSAGE DOCTEST_MESSAGE -#define FAIL_CHECK DOCTEST_FAIL_CHECK -#define FAIL DOCTEST_FAIL -#define TO_LVALUE DOCTEST_TO_LVALUE +#define REGISTER_EXCEPTION_TRANSLATOR(signature) DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) +#define REGISTER_REPORTER(name, priority, reporter) DOCTEST_REGISTER_REPORTER(name, priority, reporter) +#define REGISTER_LISTENER(name, priority, reporter) DOCTEST_REGISTER_LISTENER(name, priority, reporter) +#define INFO(...) DOCTEST_INFO(__VA_ARGS__) +#define CAPTURE(x) DOCTEST_CAPTURE(x) +#define ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_MESSAGE_AT(file, line, __VA_ARGS__) +#define ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_FAIL_CHECK_AT(file, line, __VA_ARGS__) +#define ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_FAIL_AT(file, line, __VA_ARGS__) +#define MESSAGE(...) DOCTEST_MESSAGE(__VA_ARGS__) +#define FAIL_CHECK(...) DOCTEST_FAIL_CHECK(__VA_ARGS__) +#define FAIL(...) DOCTEST_FAIL(__VA_ARGS__) +#define TO_LVALUE(...) DOCTEST_TO_LVALUE(__VA_ARGS__) -#define WARN DOCTEST_WARN -#define WARN_FALSE DOCTEST_WARN_FALSE -#define WARN_THROWS DOCTEST_WARN_THROWS -#define WARN_THROWS_AS DOCTEST_WARN_THROWS_AS -#define WARN_THROWS_WITH DOCTEST_WARN_THROWS_WITH -#define WARN_THROWS_WITH_AS DOCTEST_WARN_THROWS_WITH_AS -#define WARN_NOTHROW DOCTEST_WARN_NOTHROW -#define CHECK DOCTEST_CHECK -#define CHECK_FALSE DOCTEST_CHECK_FALSE -#define CHECK_THROWS DOCTEST_CHECK_THROWS -#define CHECK_THROWS_AS DOCTEST_CHECK_THROWS_AS -#define CHECK_THROWS_WITH DOCTEST_CHECK_THROWS_WITH -#define CHECK_THROWS_WITH_AS DOCTEST_CHECK_THROWS_WITH_AS -#define CHECK_NOTHROW DOCTEST_CHECK_NOTHROW -#define REQUIRE DOCTEST_REQUIRE -#define REQUIRE_FALSE DOCTEST_REQUIRE_FALSE -#define REQUIRE_THROWS DOCTEST_REQUIRE_THROWS -#define REQUIRE_THROWS_AS DOCTEST_REQUIRE_THROWS_AS -#define REQUIRE_THROWS_WITH DOCTEST_REQUIRE_THROWS_WITH -#define REQUIRE_THROWS_WITH_AS DOCTEST_REQUIRE_THROWS_WITH_AS -#define REQUIRE_NOTHROW DOCTEST_REQUIRE_NOTHROW +#define WARN(...) DOCTEST_WARN(__VA_ARGS__) +#define WARN_FALSE(...) DOCTEST_WARN_FALSE(__VA_ARGS__) +#define WARN_THROWS(...) DOCTEST_WARN_THROWS(__VA_ARGS__) +#define WARN_THROWS_AS(expr, ...) DOCTEST_WARN_THROWS_AS(expr, __VA_ARGS__) +#define WARN_THROWS_WITH(expr, ...) DOCTEST_WARN_THROWS_WITH(expr, __VA_ARGS__) +#define WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_WARN_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define WARN_NOTHROW(...) DOCTEST_WARN_NOTHROW(__VA_ARGS__) +#define CHECK(...) DOCTEST_CHECK(__VA_ARGS__) +#define CHECK_FALSE(...) DOCTEST_CHECK_FALSE(__VA_ARGS__) +#define CHECK_THROWS(...) DOCTEST_CHECK_THROWS(__VA_ARGS__) +#define CHECK_THROWS_AS(expr, ...) DOCTEST_CHECK_THROWS_AS(expr, __VA_ARGS__) +#define CHECK_THROWS_WITH(expr, ...) DOCTEST_CHECK_THROWS_WITH(expr, __VA_ARGS__) +#define CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define CHECK_NOTHROW(...) DOCTEST_CHECK_NOTHROW(__VA_ARGS__) +#define REQUIRE(...) DOCTEST_REQUIRE(__VA_ARGS__) +#define REQUIRE_FALSE(...) DOCTEST_REQUIRE_FALSE(__VA_ARGS__) +#define REQUIRE_THROWS(...) DOCTEST_REQUIRE_THROWS(__VA_ARGS__) +#define REQUIRE_THROWS_AS(expr, ...) DOCTEST_REQUIRE_THROWS_AS(expr, __VA_ARGS__) +#define REQUIRE_THROWS_WITH(expr, ...) DOCTEST_REQUIRE_THROWS_WITH(expr, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define REQUIRE_NOTHROW(...) DOCTEST_REQUIRE_NOTHROW(__VA_ARGS__) -#define WARN_MESSAGE DOCTEST_WARN_MESSAGE -#define WARN_FALSE_MESSAGE DOCTEST_WARN_FALSE_MESSAGE -#define WARN_THROWS_MESSAGE DOCTEST_WARN_THROWS_MESSAGE -#define WARN_THROWS_AS_MESSAGE DOCTEST_WARN_THROWS_AS_MESSAGE -#define WARN_THROWS_WITH_MESSAGE DOCTEST_WARN_THROWS_WITH_MESSAGE -#define WARN_THROWS_WITH_AS_MESSAGE DOCTEST_WARN_THROWS_WITH_AS_MESSAGE -#define WARN_NOTHROW_MESSAGE DOCTEST_WARN_NOTHROW_MESSAGE -#define CHECK_MESSAGE DOCTEST_CHECK_MESSAGE -#define CHECK_FALSE_MESSAGE DOCTEST_CHECK_FALSE_MESSAGE -#define CHECK_THROWS_MESSAGE DOCTEST_CHECK_THROWS_MESSAGE -#define CHECK_THROWS_AS_MESSAGE DOCTEST_CHECK_THROWS_AS_MESSAGE -#define CHECK_THROWS_WITH_MESSAGE DOCTEST_CHECK_THROWS_WITH_MESSAGE -#define CHECK_THROWS_WITH_AS_MESSAGE DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE -#define CHECK_NOTHROW_MESSAGE DOCTEST_CHECK_NOTHROW_MESSAGE -#define REQUIRE_MESSAGE DOCTEST_REQUIRE_MESSAGE -#define REQUIRE_FALSE_MESSAGE DOCTEST_REQUIRE_FALSE_MESSAGE -#define REQUIRE_THROWS_MESSAGE DOCTEST_REQUIRE_THROWS_MESSAGE -#define REQUIRE_THROWS_AS_MESSAGE DOCTEST_REQUIRE_THROWS_AS_MESSAGE -#define REQUIRE_THROWS_WITH_MESSAGE DOCTEST_REQUIRE_THROWS_WITH_MESSAGE -#define REQUIRE_THROWS_WITH_AS_MESSAGE DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE -#define REQUIRE_NOTHROW_MESSAGE DOCTEST_REQUIRE_NOTHROW_MESSAGE +#define WARN_MESSAGE(cond, ...) DOCTEST_WARN_MESSAGE(cond, __VA_ARGS__) +#define WARN_FALSE_MESSAGE(cond, ...) DOCTEST_WARN_FALSE_MESSAGE(cond, __VA_ARGS__) +#define WARN_THROWS_MESSAGE(expr, ...) DOCTEST_WARN_THROWS_MESSAGE(expr, __VA_ARGS__) +#define WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_WARN_NOTHROW_MESSAGE(expr, __VA_ARGS__) +#define CHECK_MESSAGE(cond, ...) DOCTEST_CHECK_MESSAGE(cond, __VA_ARGS__) +#define CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_CHECK_FALSE_MESSAGE(cond, __VA_ARGS__) +#define CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_CHECK_THROWS_MESSAGE(expr, __VA_ARGS__) +#define CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_CHECK_NOTHROW_MESSAGE(expr, __VA_ARGS__) +#define REQUIRE_MESSAGE(cond, ...) DOCTEST_REQUIRE_MESSAGE(cond, __VA_ARGS__) +#define REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_REQUIRE_FALSE_MESSAGE(cond, __VA_ARGS__) +#define REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_REQUIRE_THROWS_MESSAGE(expr, __VA_ARGS__) +#define REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, __VA_ARGS__) -#define SCENARIO DOCTEST_SCENARIO -#define SCENARIO_CLASS DOCTEST_SCENARIO_CLASS -#define SCENARIO_TEMPLATE DOCTEST_SCENARIO_TEMPLATE -#define SCENARIO_TEMPLATE_DEFINE DOCTEST_SCENARIO_TEMPLATE_DEFINE -#define GIVEN DOCTEST_GIVEN -#define WHEN DOCTEST_WHEN -#define AND_WHEN DOCTEST_AND_WHEN -#define THEN DOCTEST_THEN -#define AND_THEN DOCTEST_AND_THEN +#define SCENARIO(name) DOCTEST_SCENARIO(name) +#define SCENARIO_CLASS(name) DOCTEST_SCENARIO_CLASS(name) +#define SCENARIO_TEMPLATE(name, T, ...) DOCTEST_SCENARIO_TEMPLATE(name, T, __VA_ARGS__) +#define SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) +#define GIVEN(name) DOCTEST_GIVEN(name) +#define WHEN(name) DOCTEST_WHEN(name) +#define AND_WHEN(name) DOCTEST_AND_WHEN(name) +#define THEN(name) DOCTEST_THEN(name) +#define AND_THEN(name) DOCTEST_AND_THEN(name) -#define WARN_EQ DOCTEST_WARN_EQ -#define CHECK_EQ DOCTEST_CHECK_EQ -#define REQUIRE_EQ DOCTEST_REQUIRE_EQ -#define WARN_NE DOCTEST_WARN_NE -#define CHECK_NE DOCTEST_CHECK_NE -#define REQUIRE_NE DOCTEST_REQUIRE_NE -#define WARN_GT DOCTEST_WARN_GT -#define CHECK_GT DOCTEST_CHECK_GT -#define REQUIRE_GT DOCTEST_REQUIRE_GT -#define WARN_LT DOCTEST_WARN_LT -#define CHECK_LT DOCTEST_CHECK_LT -#define REQUIRE_LT DOCTEST_REQUIRE_LT -#define WARN_GE DOCTEST_WARN_GE -#define CHECK_GE DOCTEST_CHECK_GE -#define REQUIRE_GE DOCTEST_REQUIRE_GE -#define WARN_LE DOCTEST_WARN_LE -#define CHECK_LE DOCTEST_CHECK_LE -#define REQUIRE_LE DOCTEST_REQUIRE_LE -#define WARN_UNARY DOCTEST_WARN_UNARY -#define CHECK_UNARY DOCTEST_CHECK_UNARY -#define REQUIRE_UNARY DOCTEST_REQUIRE_UNARY -#define WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE -#define CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE -#define REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE +#define WARN_EQ(...) DOCTEST_WARN_EQ(__VA_ARGS__) +#define CHECK_EQ(...) DOCTEST_CHECK_EQ(__VA_ARGS__) +#define REQUIRE_EQ(...) DOCTEST_REQUIRE_EQ(__VA_ARGS__) +#define WARN_NE(...) DOCTEST_WARN_NE(__VA_ARGS__) +#define CHECK_NE(...) DOCTEST_CHECK_NE(__VA_ARGS__) +#define REQUIRE_NE(...) DOCTEST_REQUIRE_NE(__VA_ARGS__) +#define WARN_GT(...) DOCTEST_WARN_GT(__VA_ARGS__) +#define CHECK_GT(...) DOCTEST_CHECK_GT(__VA_ARGS__) +#define REQUIRE_GT(...) DOCTEST_REQUIRE_GT(__VA_ARGS__) +#define WARN_LT(...) DOCTEST_WARN_LT(__VA_ARGS__) +#define CHECK_LT(...) DOCTEST_CHECK_LT(__VA_ARGS__) +#define REQUIRE_LT(...) DOCTEST_REQUIRE_LT(__VA_ARGS__) +#define WARN_GE(...) DOCTEST_WARN_GE(__VA_ARGS__) +#define CHECK_GE(...) DOCTEST_CHECK_GE(__VA_ARGS__) +#define REQUIRE_GE(...) DOCTEST_REQUIRE_GE(__VA_ARGS__) +#define WARN_LE(...) DOCTEST_WARN_LE(__VA_ARGS__) +#define CHECK_LE(...) DOCTEST_CHECK_LE(__VA_ARGS__) +#define REQUIRE_LE(...) DOCTEST_REQUIRE_LE(__VA_ARGS__) +#define WARN_UNARY(...) DOCTEST_WARN_UNARY(__VA_ARGS__) +#define CHECK_UNARY(...) DOCTEST_CHECK_UNARY(__VA_ARGS__) +#define REQUIRE_UNARY(...) DOCTEST_REQUIRE_UNARY(__VA_ARGS__) +#define WARN_UNARY_FALSE(...) DOCTEST_WARN_UNARY_FALSE(__VA_ARGS__) +#define CHECK_UNARY_FALSE(...) DOCTEST_CHECK_UNARY_FALSE(__VA_ARGS__) +#define REQUIRE_UNARY_FALSE(...) DOCTEST_REQUIRE_UNARY_FALSE(__VA_ARGS__) // KEPT FOR BACKWARDS COMPATIBILITY -#define FAST_WARN_EQ DOCTEST_FAST_WARN_EQ -#define FAST_CHECK_EQ DOCTEST_FAST_CHECK_EQ -#define FAST_REQUIRE_EQ DOCTEST_FAST_REQUIRE_EQ -#define FAST_WARN_NE DOCTEST_FAST_WARN_NE -#define FAST_CHECK_NE DOCTEST_FAST_CHECK_NE -#define FAST_REQUIRE_NE DOCTEST_FAST_REQUIRE_NE -#define FAST_WARN_GT DOCTEST_FAST_WARN_GT -#define FAST_CHECK_GT DOCTEST_FAST_CHECK_GT -#define FAST_REQUIRE_GT DOCTEST_FAST_REQUIRE_GT -#define FAST_WARN_LT DOCTEST_FAST_WARN_LT -#define FAST_CHECK_LT DOCTEST_FAST_CHECK_LT -#define FAST_REQUIRE_LT DOCTEST_FAST_REQUIRE_LT -#define FAST_WARN_GE DOCTEST_FAST_WARN_GE -#define FAST_CHECK_GE DOCTEST_FAST_CHECK_GE -#define FAST_REQUIRE_GE DOCTEST_FAST_REQUIRE_GE -#define FAST_WARN_LE DOCTEST_FAST_WARN_LE -#define FAST_CHECK_LE DOCTEST_FAST_CHECK_LE -#define FAST_REQUIRE_LE DOCTEST_FAST_REQUIRE_LE +#define FAST_WARN_EQ(...) DOCTEST_FAST_WARN_EQ(__VA_ARGS__) +#define FAST_CHECK_EQ(...) DOCTEST_FAST_CHECK_EQ(__VA_ARGS__) +#define FAST_REQUIRE_EQ(...) DOCTEST_FAST_REQUIRE_EQ(__VA_ARGS__) +#define FAST_WARN_NE(...) DOCTEST_FAST_WARN_NE(__VA_ARGS__) +#define FAST_CHECK_NE(...) DOCTEST_FAST_CHECK_NE(__VA_ARGS__) +#define FAST_REQUIRE_NE(...) DOCTEST_FAST_REQUIRE_NE(__VA_ARGS__) +#define FAST_WARN_GT(...) DOCTEST_FAST_WARN_GT(__VA_ARGS__) +#define FAST_CHECK_GT(...) DOCTEST_FAST_CHECK_GT(__VA_ARGS__) +#define FAST_REQUIRE_GT(...) DOCTEST_FAST_REQUIRE_GT(__VA_ARGS__) +#define FAST_WARN_LT(...) DOCTEST_FAST_WARN_LT(__VA_ARGS__) +#define FAST_CHECK_LT(...) DOCTEST_FAST_CHECK_LT(__VA_ARGS__) +#define FAST_REQUIRE_LT(...) DOCTEST_FAST_REQUIRE_LT(__VA_ARGS__) +#define FAST_WARN_GE(...) DOCTEST_FAST_WARN_GE(__VA_ARGS__) +#define FAST_CHECK_GE(...) DOCTEST_FAST_CHECK_GE(__VA_ARGS__) +#define FAST_REQUIRE_GE(...) DOCTEST_FAST_REQUIRE_GE(__VA_ARGS__) +#define FAST_WARN_LE(...) DOCTEST_FAST_WARN_LE(__VA_ARGS__) +#define FAST_CHECK_LE(...) DOCTEST_FAST_CHECK_LE(__VA_ARGS__) +#define FAST_REQUIRE_LE(...) DOCTEST_FAST_REQUIRE_LE(__VA_ARGS__) -#define FAST_WARN_UNARY DOCTEST_FAST_WARN_UNARY -#define FAST_CHECK_UNARY DOCTEST_FAST_CHECK_UNARY -#define FAST_REQUIRE_UNARY DOCTEST_FAST_REQUIRE_UNARY -#define FAST_WARN_UNARY_FALSE DOCTEST_FAST_WARN_UNARY_FALSE -#define FAST_CHECK_UNARY_FALSE DOCTEST_FAST_CHECK_UNARY_FALSE -#define FAST_REQUIRE_UNARY_FALSE DOCTEST_FAST_REQUIRE_UNARY_FALSE +#define FAST_WARN_UNARY(...) DOCTEST_FAST_WARN_UNARY(__VA_ARGS__) +#define FAST_CHECK_UNARY(...) DOCTEST_FAST_CHECK_UNARY(__VA_ARGS__) +#define FAST_REQUIRE_UNARY(...) DOCTEST_FAST_REQUIRE_UNARY(__VA_ARGS__) +#define FAST_WARN_UNARY_FALSE(...) DOCTEST_FAST_WARN_UNARY_FALSE(__VA_ARGS__) +#define FAST_CHECK_UNARY_FALSE(...) DOCTEST_FAST_CHECK_UNARY_FALSE(__VA_ARGS__) +#define FAST_REQUIRE_UNARY_FALSE(...) DOCTEST_FAST_REQUIRE_UNARY_FALSE(__VA_ARGS__) -#define TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE +#define TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, __VA_ARGS__) #endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES @@ -2607,6 +2896,8 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP +DOCTEST_SUPPRESS_COMMON_WARNINGS_POP + #endif // DOCTEST_LIBRARY_INCLUDED #ifndef DOCTEST_SINGLE_HEADER @@ -2626,13 +2917,11 @@ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-macros") DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors") DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32") DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations") @@ -2640,64 +2929,35 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch") DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum") DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default") DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion") DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces") DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") -DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wnonportable-system-include-path") DOCTEST_GCC_SUPPRESS_WARNING_PUSH -DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") -DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") -DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") -DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") -DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers") DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces") -DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") -DOCTEST_GCC_SUPPRESS_WARNING("-Winline") DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch") DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum") DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default") DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations") DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast") -DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") -DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function") DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance") -DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute") DOCTEST_MSVC_SUPPRESS_WARNING_PUSH -DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning -DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning -DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data -DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression -DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated -DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch -DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding in structs -DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C -DOCTEST_MSVC_SUPPRESS_WARNING(5045) // Spectre mitigation stuff -DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted -DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning) -// static analysis -DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' -DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable -DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... -DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtor... +DOCTEST_MSVC_SUPPRESS_WARNING(5245) // unreferenced function with internal linkage has been removed DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN @@ -2705,7 +2965,7 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #include #include #include -// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/onqtam/doctest/pull/37 +// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/doctest/doctest/pull/37 #ifdef __BORLANDC__ #include #endif // __BORLANDC__ @@ -2727,9 +2987,7 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #include #include #include -#ifdef DOCTEST_CONFIG_POSIX_SIGNALS #include -#endif // DOCTEST_CONFIG_POSIX_SIGNALS #include #include #include @@ -2754,7 +3012,7 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #ifdef __AFXDLL #include #else -#include +#include #endif #include @@ -2765,6 +3023,12 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #endif // DOCTEST_PLATFORM_WINDOWS +// this is a fix for https://github.com/doctest/doctest/issues/348 +// https://mail.gnome.org/archives/xml/2012-January/msg00000.html +#if !defined(HAVE_UNISTD_H) && !defined(STDOUT_FILENO) +#define STDOUT_FILENO fileno(stdout) +#endif // HAVE_UNISTD_H + DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END // counts the number of elements in a C array @@ -2781,7 +3045,19 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END #endif #ifndef DOCTEST_THREAD_LOCAL +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_THREAD_LOCAL +#else // DOCTEST_MSVC #define DOCTEST_THREAD_LOCAL thread_local +#endif // DOCTEST_MSVC +#endif // DOCTEST_THREAD_LOCAL + +#ifndef DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES +#define DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES 32 +#endif + +#ifndef DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE +#define DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE 64 #endif #ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS @@ -2790,12 +3066,38 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END #define DOCTEST_OPTIONS_PREFIX_DISPLAY "" #endif +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +#define DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS +#endif + +#ifndef DOCTEST_CDECL +#define DOCTEST_CDECL __cdecl +#endif + namespace doctest { bool is_running_in_test = false; namespace { using namespace detail; + + template + DOCTEST_NORETURN void throw_exception(Ex const& e) { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + throw e; +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + std::cerr << "doctest will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; + std::terminate(); +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } + +#ifndef DOCTEST_INTERNAL_ERROR +#define DOCTEST_INTERNAL_ERROR(msg) \ + throw_exception(std::logic_error( \ + __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) +#endif // DOCTEST_INTERNAL_ERROR + // case insensitive strcmp int stricmp(const char* a, const char* b) { for(;; a++, b++) { @@ -2839,8 +3141,6 @@ namespace { } // namespace namespace detail { - void my_memcpy(void* dest, const void* src, unsigned num) { memcpy(dest, src, num); } - String rawMemoryToString(const void* object, unsigned size) { // Reverse order for little endian architectures int i = 0, end = static_cast(size), inc = 1; @@ -2850,49 +3150,76 @@ namespace detail { } unsigned const char* bytes = static_cast(object); - std::ostringstream oss; - oss << "0x" << std::setfill('0') << std::hex; + std::ostream* oss = tlssPush(); + *oss << "0x" << std::setfill('0') << std::hex; for(; i != end; i += inc) - oss << std::setw(2) << static_cast(bytes[i]); - return oss.str().c_str(); + *oss << std::setw(2) << static_cast(bytes[i]); + return tlssPop(); } - DOCTEST_THREAD_LOCAL std::ostringstream g_oss; // NOLINT(cert-err58-cpp) + DOCTEST_THREAD_LOCAL class + { + std::vector stack; + std::stringstream ss; - std::ostream* getTlsOss() { - g_oss.clear(); // there shouldn't be anything worth clearing in the flags - g_oss.str(""); // the slow way of resetting a string stream - //g_oss.seekp(0); // optimal reset - as seen here: https://stackoverflow.com/a/624291/3162383 - return &g_oss; + public: + std::ostream* push() { + stack.push_back(ss.tellp()); + return &ss; + } + + String pop() { + if (stack.empty()) + DOCTEST_INTERNAL_ERROR("TLSS was empty when trying to pop!"); + + std::streampos pos = stack.back(); + stack.pop_back(); + unsigned sz = static_cast(ss.tellp() - pos); + ss.rdbuf()->pubseekpos(pos, std::ios::in | std::ios::out); + return String(ss, sz); + } + } g_oss; + + std::ostream* tlssPush() { + return g_oss.push(); } - String getTlsOssResult() { - //g_oss << std::ends; // needed - as shown here: https://stackoverflow.com/a/624291/3162383 - return g_oss.str().c_str(); + String tlssPop() { + return g_oss.pop(); } #ifndef DOCTEST_CONFIG_DISABLE - typedef uint64_t UInt64; +namespace timer_large_integer +{ + +#if defined(DOCTEST_PLATFORM_WINDOWS) + typedef ULONGLONG type; +#else // DOCTEST_PLATFORM_WINDOWS + typedef std::uint64_t type; +#endif // DOCTEST_PLATFORM_WINDOWS +} + +typedef timer_large_integer::type ticks_t; #ifdef DOCTEST_CONFIG_GETCURRENTTICKS - UInt64 getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); } + ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); } #elif defined(DOCTEST_PLATFORM_WINDOWS) - UInt64 getCurrentTicks() { - static UInt64 hz = 0, hzo = 0; - if(!hz) { - QueryPerformanceFrequency(reinterpret_cast(&hz)); - QueryPerformanceCounter(reinterpret_cast(&hzo)); + ticks_t getCurrentTicks() { + static LARGE_INTEGER hz = {0}, hzo = {0}; + if(!hz.QuadPart) { + QueryPerformanceFrequency(&hz); + QueryPerformanceCounter(&hzo); } - UInt64 t; - QueryPerformanceCounter(reinterpret_cast(&t)); - return ((t - hzo) * 1000000) / hz; + LARGE_INTEGER t; + QueryPerformanceCounter(&t); + return ((t.QuadPart - hzo.QuadPart) * LONGLONG(1000000)) / hz.QuadPart; } #else // DOCTEST_PLATFORM_WINDOWS - UInt64 getCurrentTicks() { + ticks_t getCurrentTicks() { timeval t; gettimeofday(&t, nullptr); - return static_cast(t.tv_sec) * 1000000 + static_cast(t.tv_usec); + return static_cast(t.tv_sec) * 1000000 + static_cast(t.tv_usec); } #endif // DOCTEST_PLATFORM_WINDOWS @@ -2905,24 +3232,111 @@ namespace detail { //unsigned int getElapsedMilliseconds() const { // return static_cast(getElapsedMicroseconds() / 1000); //} - double getElapsedSeconds() const { return getElapsedMicroseconds() / 1000000.0; } + double getElapsedSeconds() const { return static_cast(getCurrentTicks() - m_ticks) / 1000000.0; } private: - UInt64 m_ticks = 0; + ticks_t m_ticks = 0; }; +#ifdef DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS + template + using AtomicOrMultiLaneAtomic = std::atomic; +#else // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS + // Provides a multilane implementation of an atomic variable that supports add, sub, load, + // store. Instead of using a single atomic variable, this splits up into multiple ones, + // each sitting on a separate cache line. The goal is to provide a speedup when most + // operations are modifying. It achieves this with two properties: + // + // * Multiple atomics are used, so chance of congestion from the same atomic is reduced. + // * Each atomic sits on a separate cache line, so false sharing is reduced. + // + // The disadvantage is that there is a small overhead due to the use of TLS, and load/store + // is slower because all atomics have to be accessed. + template + class MultiLaneAtomic + { + struct CacheLineAlignedAtomic + { + std::atomic atomic{}; + char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(std::atomic)]; + }; + CacheLineAlignedAtomic m_atomics[DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES]; + + static_assert(sizeof(CacheLineAlignedAtomic) == DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE, + "guarantee one atomic takes exactly one cache line"); + + public: + T operator++() DOCTEST_NOEXCEPT { return fetch_add(1) + 1; } + + T operator++(int) DOCTEST_NOEXCEPT { return fetch_add(1); } + + T fetch_add(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + return myAtomic().fetch_add(arg, order); + } + + T fetch_sub(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + return myAtomic().fetch_sub(arg, order); + } + + operator T() const DOCTEST_NOEXCEPT { return load(); } + + T load(std::memory_order order = std::memory_order_seq_cst) const DOCTEST_NOEXCEPT { + auto result = T(); + for(auto const& c : m_atomics) { + result += c.atomic.load(order); + } + return result; + } + + T operator=(T desired) DOCTEST_NOEXCEPT { // lgtm [cpp/assignment-does-not-return-this] + store(desired); + return desired; + } + + void store(T desired, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + // first value becomes desired", all others become 0. + for(auto& c : m_atomics) { + c.atomic.store(desired, order); + desired = {}; + } + } + + private: + // Each thread has a different atomic that it operates on. If more than NumLanes threads + // use this, some will use the same atomic. So performance will degrade a bit, but still + // everything will work. + // + // The logic here is a bit tricky. The call should be as fast as possible, so that there + // is minimal to no overhead in determining the correct atomic for the current thread. + // + // 1. A global static counter laneCounter counts continuously up. + // 2. Each successive thread will use modulo operation of that counter so it gets an atomic + // assigned in a round-robin fashion. + // 3. This tlsLaneIdx is stored in the thread local data, so it is directly available with + // little overhead. + std::atomic& myAtomic() DOCTEST_NOEXCEPT { + static std::atomic laneCounter; + DOCTEST_THREAD_LOCAL size_t tlsLaneIdx = + laneCounter++ % DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES; + + return m_atomics[tlsLaneIdx].atomic; + } + }; + + template + using AtomicOrMultiLaneAtomic = MultiLaneAtomic; +#endif // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS + // this holds both parameters from the command line and runtime data for tests struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats { - std::atomic numAssertsCurrentTest_atomic; - std::atomic numAssertsFailedCurrentTest_atomic; + AtomicOrMultiLaneAtomic numAssertsCurrentTest_atomic; + AtomicOrMultiLaneAtomic numAssertsFailedCurrentTest_atomic; std::vector> filters = decltype(filters)(9); // 9 different filters std::vector reporters_currently_used; - const TestCase* currentTest = nullptr; - assert_handler ah = nullptr; Timer timer; @@ -2984,7 +3398,8 @@ namespace detail { (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags); // if any subcase has failed - the whole test case has failed - if(failure_flags && !ok_to_fail) + testCaseSuccess = !(failure_flags && !ok_to_fail); + if(!testCaseSuccess) numTestCasesFailed++; } }; @@ -2999,6 +3414,21 @@ namespace detail { #endif // DOCTEST_CONFIG_DISABLE } // namespace detail +char* String::allocate(unsigned sz) { + if (sz <= last) { + buf[sz] = '\0'; + setLast(last - sz); + return buf; + } else { + setOnHeap(); + data.size = sz; + data.capacity = data.size + 1; + data.ptr = new char[data.capacity]; + data.ptr[sz] = '\0'; + return data.ptr; + } +} + void String::setOnHeap() { *reinterpret_cast(&buf[last]) = 128; } void String::setLast(unsigned in) { buf[last] = char(in); } @@ -3006,11 +3436,7 @@ void String::copy(const String& other) { if(other.isOnStack()) { memcpy(buf, other.buf, len); } else { - setOnHeap(); - data.size = other.data.size; - data.capacity = data.size + 1; - data.ptr = new char[data.capacity]; - memcpy(data.ptr, other.data.ptr, data.size + 1); + memcpy(allocate(other.data.size), other.data.ptr, other.data.size); } } @@ -3022,22 +3448,18 @@ String::String() { String::~String() { if(!isOnStack()) delete[] data.ptr; + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) } String::String(const char* in) : String(in, strlen(in)) {} String::String(const char* in, unsigned in_size) { - if(in_size <= last) { - memcpy(buf, in, in_size + 1); - setLast(last - in_size); - } else { - setOnHeap(); - data.size = in_size; - data.capacity = data.size + 1; - data.ptr = new char[data.capacity]; - memcpy(data.ptr, in, in_size + 1); - } + memcpy(allocate(in_size), in, in_size); +} + +String::String(std::istream& in, unsigned in_size) { + in.read(allocate(in_size), in_size); } String::String(const String& other) { copy(other); } @@ -3061,6 +3483,7 @@ String& String::operator+=(const String& other) { if(total_size < len) { // append to the current stack space memcpy(buf + my_old_size, other.c_str(), other_size + 1); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) setLast(last - total_size); } else { // alloc new chunk @@ -3102,8 +3525,6 @@ String& String::operator+=(const String& other) { return *this; } -String String::operator+(const String& other) const { return String(*this) += other; } - String::String(String&& other) { memcpy(buf, other.buf, len); other.buf[0] = '\0'; @@ -3147,7 +3568,7 @@ unsigned String::capacity() const { int String::compare(const char* other, bool no_case) const { if(no_case) - return stricmp(c_str(), other); + return doctest::stricmp(c_str(), other); return std::strcmp(c_str(), other); } @@ -3155,6 +3576,9 @@ int String::compare(const String& other, bool no_case) const { return compare(other.c_str(), no_case); } +// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) +String operator+(const String& lhs, const String& rhs) { return String(lhs) += rhs; } + // clang-format off bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; } bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; } @@ -3254,6 +3678,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") // depending on the current options this will remove the path of filenames const char* skipPathFromFilename(const char* file) { +#ifndef DOCTEST_CONFIG_DISABLE if(getContextOptions()->no_path_in_filenames) { auto back = std::strrchr(file, '\\'); auto forward = std::strrchr(file, '/'); @@ -3263,6 +3688,7 @@ const char* skipPathFromFilename(const char* file) { return forward + 1; } } +#endif // DOCTEST_CONFIG_DISABLE return file; } DOCTEST_CLANG_SUPPRESS_WARNING_POP @@ -3273,7 +3699,7 @@ bool SubcaseSignature::operator<(const SubcaseSignature& other) const { return m_line < other.m_line; if(std::strcmp(m_file, other.m_file) != 0) return std::strcmp(m_file, other.m_file) < 0; - return std::strcmp(m_name, other.m_name) < 0; + return m_name.compare(other.m_name) < 0; } IContextScope::IContextScope() = default; @@ -3281,6 +3707,7 @@ IContextScope::~IContextScope() = default; #ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING String toString(char* in) { return toString(static_cast(in)); } +// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; } #endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING String toString(bool in) { return in ? "true" : "false"; } @@ -3310,7 +3737,7 @@ DOCTEST_TO_STRING_OVERLOAD(int long long unsigned, "%llu") String toString(std::nullptr_t) { return "NULL"; } #if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) -// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 String toString(const std::string& in) { return in.c_str(); } #endif // VS 2019 @@ -3353,7 +3780,8 @@ bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; } String toString(const Approx& in) { - return String("Approx( ") + doctest::toString(in.m_value) + " )"; + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + return "Approx( " + doctest::toString(in.m_value) + " )"; } const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); } @@ -3366,11 +3794,13 @@ Context::~Context() = default; void Context::applyCommandLine(int, const char* const*) {} void Context::addFilter(const char*, const char*) {} void Context::clearFilters() {} +void Context::setOption(const char*, bool) {} void Context::setOption(const char*, int) {} void Context::setOption(const char*, const char*) {} bool Context::shouldExit() { return false; } void Context::setAsDefaultForAssertsOutOfTestCases() {} void Context::setAssertHandler(detail::assert_handler) {} +void Context::setCout(std::ostream* out) {} int Context::run() { return 0; } IReporter::~IReporter() = default; @@ -3398,7 +3828,7 @@ int registerReporter(const char*, int, IReporter*) { return 0; } namespace doctest_detail_test_suite_ns { // holds the current test suite doctest::detail::TestSuite& getCurrentTestSuite() { - static doctest::detail::TestSuite data; + static doctest::detail::TestSuite data{}; return data; } } // namespace doctest_detail_test_suite_ns @@ -3437,7 +3867,7 @@ namespace detail { } #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - [[noreturn]] void throwException() { + DOCTEST_NORETURN void throwException() { g_cs->shouldLogCurrentException = false; throw TestFailureException(); } // NOLINT(cert-err60-cpp) @@ -3451,8 +3881,8 @@ namespace { // matching of a string against a wildcard mask (case sensitivity configurable) taken from // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing int wildcmp(const char* str, const char* wild, bool caseSensitive) { - const char* cp = nullptr; - const char* mp = nullptr; + const char* cp = str; + const char* mp = wild; while((*str) && (*wild != '*')) { if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) && @@ -3508,15 +3938,15 @@ namespace { } // namespace namespace detail { - Subcase::Subcase(const char* name, const char* file, int line) + Subcase::Subcase(const String& name, const char* file, int line) : m_signature({name, file, line}) { - ContextState* s = g_cs; + auto* s = g_cs; // check subcase filters if(s->subcasesStack.size() < size_t(s->subcase_filter_levels)) { - if(!matchesAny(m_signature.m_name, s->filters[6], true, s->case_sensitive)) + if(!matchesAny(m_signature.m_name.c_str(), s->filters[6], true, s->case_sensitive)) return; - if(matchesAny(m_signature.m_name, s->filters[7], false, s->case_sensitive)) + if(matchesAny(m_signature.m_name.c_str(), s->filters[7], false, s->case_sensitive)) return; } @@ -3544,6 +3974,7 @@ namespace detail { DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + Subcase::~Subcase() { if(m_entered) { // only mark the subcase stack as passed if no subcases have been skipped @@ -3551,7 +3982,12 @@ namespace detail { g_cs->subcasesPassed.insert(g_cs->subcasesStack); g_cs->subcasesStack.pop_back(); - if(std::uncaught_exception() && g_cs->shouldLogCurrentException) { +#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) + if(std::uncaught_exceptions() > 0 +#else + if(std::uncaught_exception() +#endif + && g_cs->shouldLogCurrentException) { DOCTEST_ITERATE_THROUGH_REPORTERS( test_case_exception, {"exception thrown in subcase - will translate later " "when the whole test case has been exited (cannot " @@ -3562,6 +3998,7 @@ namespace detail { DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); } } + DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP @@ -3577,13 +4014,6 @@ namespace detail { TestSuite& TestSuite::operator*(const char* in) { m_test_suite = in; - // clear state - m_description = nullptr; - m_skip = false; - m_may_fail = false; - m_should_fail = false; - m_expected_failures = 0; - m_timeout = 0; return *this; } @@ -3595,6 +4025,8 @@ namespace detail { m_test_suite = test_suite.m_test_suite; m_description = test_suite.m_description; m_skip = test_suite.m_skip; + m_no_breaks = test_suite.m_no_breaks; + m_no_output = test_suite.m_no_output; m_may_fail = test_suite.m_may_fail; m_should_fail = test_suite.m_should_fail; m_expected_failures = test_suite.m_expected_failures; @@ -3638,25 +4070,31 @@ namespace detail { } bool TestCase::operator<(const TestCase& other) const { + // this will be used only to differentiate between test cases - not relevant for sorting if(m_line != other.m_line) return m_line < other.m_line; - const int file_cmp = std::strcmp(m_file, other.m_file); + const int name_cmp = strcmp(m_name, other.m_name); + if(name_cmp != 0) + return name_cmp < 0; + const int file_cmp = m_file.compare(other.m_file); if(file_cmp != 0) return file_cmp < 0; return m_template_id < other.m_template_id; } + + // all the registered tests + std::set& getRegisteredTests() { + static std::set data; + return data; + } } // namespace detail namespace { using namespace detail; // for sorting tests by file/line bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) { -#if DOCTEST_MSVC // this is needed because MSVC gives different case for drive letters // for __FILE__ when evaluated in a header and a source file - const int res = stricmp(lhs->m_file, rhs->m_file); -#else // MSVC - const int res = std::strcmp(lhs->m_file, rhs->m_file); -#endif // MSVC + const int res = lhs->m_file.compare(rhs->m_file, bool(DOCTEST_MSVC)); if(res != 0) return res < 0; if(lhs->m_line != rhs->m_line) @@ -3680,39 +4118,10 @@ namespace { return suiteOrderComparator(lhs, rhs); } - // all the registered tests - std::set& getRegisteredTests() { - static std::set data; - return data; - } - -#ifdef DOCTEST_CONFIG_COLORS_WINDOWS - HANDLE g_stdoutHandle; - WORD g_origFgAttrs; - WORD g_origBgAttrs; - bool g_attrsInitted = false; - - int colors_init() { - if(!g_attrsInitted) { - g_stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); - g_attrsInitted = true; - CONSOLE_SCREEN_BUFFER_INFO csbiInfo; - GetConsoleScreenBufferInfo(g_stdoutHandle, &csbiInfo); - g_origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | - BACKGROUND_BLUE | BACKGROUND_INTENSITY); - g_origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | - FOREGROUND_BLUE | FOREGROUND_INTENSITY); - } - return 0; - } - - int dumy_init_console_colors = colors_init(); -#endif // DOCTEST_CONFIG_COLORS_WINDOWS - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") void color_to_stream(std::ostream& s, Color::Enum code) { - ((void)s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS - ((void)code); // for DOCTEST_CONFIG_COLORS_NONE + static_cast(s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS + static_cast(code); // for DOCTEST_CONFIG_COLORS_NONE #ifdef DOCTEST_CONFIG_COLORS_ANSI if(g_no_colors || (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false)) @@ -3742,10 +4151,26 @@ namespace { #ifdef DOCTEST_CONFIG_COLORS_WINDOWS if(g_no_colors || - (isatty(fileno(stdout)) == false && getContextOptions()->force_colors == false)) + (_isatty(_fileno(stdout)) == false && getContextOptions()->force_colors == false)) return; -#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(g_stdoutHandle, x | g_origBgAttrs) + static struct ConsoleHelper { + HANDLE stdoutHandle; + WORD origFgAttrs; + WORD origBgAttrs; + + ConsoleHelper() { + stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo(stdoutHandle, &csbiInfo); + origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | + BACKGROUND_BLUE | BACKGROUND_INTENSITY); + origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | + FOREGROUND_BLUE | FOREGROUND_INTENSITY); + } + } ch; + +#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(ch.stdoutHandle, x | ch.origBgAttrs) // clang-format off switch (code) { @@ -3762,7 +4187,7 @@ namespace { case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; case Color::None: case Color::Bright: // invalid - default: DOCTEST_SET_ATTR(g_origFgAttrs); + default: DOCTEST_SET_ATTR(ch.origFgAttrs); } // clang-format on #endif // DOCTEST_CONFIG_COLORS_WINDOWS @@ -3818,9 +4243,30 @@ namespace detail { #ifdef DOCTEST_IS_DEBUGGER_ACTIVE bool isDebuggerActive() { return DOCTEST_IS_DEBUGGER_ACTIVE(); } #else // DOCTEST_IS_DEBUGGER_ACTIVE -#ifdef DOCTEST_PLATFORM_MAC +#ifdef DOCTEST_PLATFORM_LINUX + class ErrnoGuard { + public: + ErrnoGuard() : m_oldErrno(errno) {} + ~ErrnoGuard() { errno = m_oldErrno; } + private: + int m_oldErrno; + }; + // See the comments in Catch2 for the reasoning behind this implementation: + // https://github.com/catchorg/Catch2/blob/v2.13.1/include/internal/catch_debugger.cpp#L79-L102 + bool isDebuggerActive() { + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for(std::string line; std::getline(in, line);) { + static const int PREFIX_LEN = 11; + if(line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0) { + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + return false; + } +#elif defined(DOCTEST_PLATFORM_MAC) // The following function is taken directly from the following technical note: - // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + // https://developer.apple.com/library/archive/qa/qa1361/_index.html // Returns true if the current process is being debugged (either // running under the debugger or has a debugger attached post facto). bool isDebuggerActive() { @@ -3845,7 +4291,7 @@ namespace detail { // We're being debugged if the P_TRACED flag is set. return ((info.kp_proc.p_flag & P_TRACED) != 0); } -#elif DOCTEST_MSVC || defined(__MINGW32__) +#elif DOCTEST_MSVC || defined(__MINGW32__) || defined(__MINGW64__) bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; } #else bool isDebuggerActive() { return false; } @@ -3885,41 +4331,47 @@ namespace detail { g_infoContexts.push_back(this); } + ContextScopeBase::ContextScopeBase(ContextScopeBase&& other) { + if (other.need_to_destroy) { + other.destroy(); + } + other.need_to_destroy = false; + g_infoContexts.push_back(this); + } + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + // destroy cannot be inlined into the destructor because that would mean calling stringify after // ContextScope has been destroyed (base class destructors run after derived class destructors). // Instead, ContextScope calls this method directly from its destructor. void ContextScopeBase::destroy() { +#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) + if(std::uncaught_exceptions() > 0) { +#else if(std::uncaught_exception()) { +#endif std::ostringstream s; this->stringify(&s); g_cs->stringifiedContexts.push_back(s.str().c_str()); } g_infoContexts.pop_back(); } + DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP - } // namespace detail namespace { using namespace detail; - std::ostream& file_line_to_stream(std::ostream& s, const char* file, int line, - const char* tail = "") { - const auto opt = getContextOptions(); - s << Color::LightGrey << skipPathFromFilename(file) << (opt->gnu_file_line ? ":" : "(") - << (opt->no_line_numbers ? 0 : line) // 0 or the real num depending on the option - << (opt->gnu_file_line ? ":" : "):") << tail; - return s; - } - #if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH) struct FatalConditionHandler { - void reset() {} + static void reset() {} + static void allocateAltStackMem() {} + static void freeAltStackMem() {} }; #else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH @@ -3936,43 +4388,113 @@ namespace { // Windows can easily distinguish between SO and SigSegV, // but SigInt, SigTerm, etc are handled differently. SignalDefs signalDefs[] = { - {EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal"}, - {EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow"}, - {EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal"}, - {EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error"}, + {static_cast(EXCEPTION_ILLEGAL_INSTRUCTION), + "SIGILL - Illegal instruction signal"}, + {static_cast(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow"}, + {static_cast(EXCEPTION_ACCESS_VIOLATION), + "SIGSEGV - Segmentation violation signal"}, + {static_cast(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error"}, }; struct FatalConditionHandler { - static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { - for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { - if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { - reportFatal(signalDefs[i].name); + static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) { + // Multiple threads may enter this filter/handler at once. We want the error message to be printed on the + // console just once no matter how many threads have crashed. + static std::mutex mutex; + static bool execute = true; + { + std::lock_guard lock(mutex); + if(execute) { + bool reported = false; + for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { + reportFatal(signalDefs[i].name); + reported = true; + break; + } + } + if(reported == false) + reportFatal("Unhandled SEH exception caught"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); } + execute = false; } - // If its not an exception we care about, pass it along. - // This stops us from eating debugger breaks etc. - return EXCEPTION_CONTINUE_SEARCH; + std::exit(EXIT_FAILURE); } + static void allocateAltStackMem() {} + static void freeAltStackMem() {} + FatalConditionHandler() { isSet = true; // 32k seems enough for doctest to handle stack overflow, // but the value was found experimentally, so there is no strong guarantee guaranteeSize = 32 * 1024; - exceptionHandlerHandle = nullptr; - // Register as first handler in current chain - exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); + // Register an unhandled exception filter + previousTop = SetUnhandledExceptionFilter(handleException); // Pass in guarantee size to be filled SetThreadStackGuarantee(&guaranteeSize); + + // On Windows uncaught exceptions from another thread, exceptions from + // destructors, or calls to std::terminate are not a SEH exception + + // The terminal handler gets called when: + // - std::terminate is called FROM THE TEST RUNNER THREAD + // - an exception is thrown from a destructor FROM THE TEST RUNNER THREAD + original_terminate_handler = std::get_terminate(); + std::set_terminate([]() DOCTEST_NOEXCEPT { + reportFatal("Terminate handler called"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); + std::exit(EXIT_FAILURE); // explicitly exit - otherwise the SIGABRT handler may be called as well + }); + + // SIGABRT is raised when: + // - std::terminate is called FROM A DIFFERENT THREAD + // - an exception is thrown from a destructor FROM A DIFFERENT THREAD + // - an uncaught exception is thrown FROM A DIFFERENT THREAD + prev_sigabrt_handler = std::signal(SIGABRT, [](int signal) DOCTEST_NOEXCEPT { + if(signal == SIGABRT) { + reportFatal("SIGABRT - Abort (abnormal termination) signal"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); + std::exit(EXIT_FAILURE); + } + }); + + // The following settings are taken from google test, and more + // specifically from UnitTest::Run() inside of gtest.cc + + // the user does not want to see pop-up dialogs about crashes + prev_error_mode_1 = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | + SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); + // This forces the abort message to go to stderr in all circumstances. + prev_error_mode_2 = _set_error_mode(_OUT_TO_STDERR); + // In the debug version, Visual Studio pops up a separate dialog + // offering a choice to debug the aborted program - we want to disable that. + prev_abort_behavior = _set_abort_behavior(0x0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + // In debug mode, the Windows CRT can crash with an assertion over invalid + // input (e.g. passing an invalid file descriptor). The default handling + // for these assertions is to pop up a dialog and wait for user input. + // Instead ask the CRT to dump such assertions to stderr non-interactively. + prev_report_mode = _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + prev_report_file = _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); } static void reset() { if(isSet) { // Unregister handler and restore the old guarantee - RemoveVectoredExceptionHandler(exceptionHandlerHandle); + SetUnhandledExceptionFilter(previousTop); SetThreadStackGuarantee(&guaranteeSize); - exceptionHandlerHandle = nullptr; + std::set_terminate(original_terminate_handler); + std::signal(SIGABRT, prev_sigabrt_handler); + SetErrorMode(prev_error_mode_1); + _set_error_mode(prev_error_mode_2); + _set_abort_behavior(prev_abort_behavior, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + static_cast(_CrtSetReportMode(_CRT_ASSERT, prev_report_mode)); + static_cast(_CrtSetReportFile(_CRT_ASSERT, prev_report_file)); isSet = false; } } @@ -3980,14 +4502,28 @@ namespace { ~FatalConditionHandler() { reset(); } private: + static UINT prev_error_mode_1; + static int prev_error_mode_2; + static unsigned int prev_abort_behavior; + static int prev_report_mode; + static _HFILE prev_report_file; + static void (DOCTEST_CDECL *prev_sigabrt_handler)(int); + static std::terminate_handler original_terminate_handler; static bool isSet; static ULONG guaranteeSize; - static PVOID exceptionHandlerHandle; + static LPTOP_LEVEL_EXCEPTION_FILTER previousTop; }; + UINT FatalConditionHandler::prev_error_mode_1; + int FatalConditionHandler::prev_error_mode_2; + unsigned int FatalConditionHandler::prev_abort_behavior; + int FatalConditionHandler::prev_report_mode; + _HFILE FatalConditionHandler::prev_report_file; + void (DOCTEST_CDECL *FatalConditionHandler::prev_sigabrt_handler)(int); + std::terminate_handler FatalConditionHandler::original_terminate_handler; bool FatalConditionHandler::isSet = false; ULONG FatalConditionHandler::guaranteeSize = 0; - PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; + LPTOP_LEVEL_EXCEPTION_FILTER FatalConditionHandler::previousTop = nullptr; #else // DOCTEST_PLATFORM_WINDOWS @@ -4008,7 +4544,8 @@ namespace { static bool isSet; static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)]; static stack_t oldSigStack; - static char altStackMem[4 * SIGSTKSZ]; + static size_t altStackSize; + static char* altStackMem; static void handleSignal(int sig) { const char* name = ""; @@ -4024,11 +4561,19 @@ namespace { raise(sig); } + static void allocateAltStackMem() { + altStackMem = new char[altStackSize]; + } + + static void freeAltStackMem() { + delete[] altStackMem; + } + FatalConditionHandler() { isSet = true; stack_t sigStack; sigStack.ss_sp = altStackMem; - sigStack.ss_size = sizeof(altStackMem); + sigStack.ss_size = altStackSize; sigStack.ss_flags = 0; sigaltstack(&sigStack, &oldSigStack); struct sigaction sa = {}; @@ -4053,10 +4598,11 @@ namespace { } }; - bool FatalConditionHandler::isSet = false; + bool FatalConditionHandler::isSet = false; struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {}; - stack_t FatalConditionHandler::oldSigStack = {}; - char FatalConditionHandler::altStackMem[] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + size_t FatalConditionHandler::altStackSize = 4 * SIGSTKSZ; + char* FatalConditionHandler::altStackMem = nullptr; #endif // DOCTEST_PLATFORM_WINDOWS #endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH @@ -4146,7 +4692,7 @@ namespace detail { } if(m_exception.size()) - m_exception = String("\"") + m_exception + "\""; + m_exception = "\"" + m_exception + "\""; if(is_running_in_test) { addAssert(m_at); @@ -4158,8 +4704,8 @@ namespace detail { failed_out_of_a_testing_context(*this); } - return m_failed && isDebuggerActive() && - !getContextOptions()->no_breaks; // break into debugger + return m_failed && isDebuggerActive() && !getContextOptions()->no_breaks && + (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger } void ResultBuilder::react() const { @@ -4174,7 +4720,7 @@ namespace detail { std::abort(); } - void decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, + bool decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, Result result) { bool failed = !result.m_passed; @@ -4184,20 +4730,31 @@ namespace detail { // ################################################################################### DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp); DOCTEST_ASSERT_IN_TESTS(result.m_decomp); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + return !failed; } MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) { - m_stream = getTlsOss(); + m_stream = tlssPush(); m_file = file; m_line = line; m_severity = severity; } + MessageBuilder::~MessageBuilder() { + if (!logged) + tlssPop(); + } + IExceptionTranslator::IExceptionTranslator() = default; IExceptionTranslator::~IExceptionTranslator() = default; bool MessageBuilder::log() { - m_string = getTlsOssResult(); + if (!logged) { + m_string = tlssPop(); + logged = true; + } + DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this); const bool isWarn = m_severity & assertType::is_warn; @@ -4208,34 +4765,18 @@ namespace detail { addFailedAssert(m_severity); } - return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn; // break + return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn && + (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger } void MessageBuilder::react() { if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional throwException(); } - - MessageBuilder::~MessageBuilder() = default; } // namespace detail namespace { using namespace detail; - template - [[noreturn]] void throw_exception(Ex const& e) { -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - throw e; -#else // DOCTEST_CONFIG_NO_EXCEPTIONS - std::cerr << "doctest will terminate because it needed to throw an exception.\n" - << "The message was: " << e.what() << '\n'; - std::terminate(); -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - } - -#define DOCTEST_INTERNAL_ERROR(msg) \ - throw_exception(std::logic_error( \ - __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) - // clang-format off // ================================================================================================= @@ -4265,8 +4806,8 @@ namespace { public: ScopedElement( XmlWriter* writer ); - ScopedElement( ScopedElement&& other ) noexcept; - ScopedElement& operator=( ScopedElement&& other ) noexcept; + ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT; + ScopedElement& operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT; ~ScopedElement(); @@ -4382,7 +4923,7 @@ namespace { void XmlEncode::encodeTo( std::ostream& os ) const { // Apostrophe escaping not necessary if we always use " to write attributes - // (see: http://www.w3.org/TR/xml/#syntax) + // (see: https://www.w3.org/TR/xml/#syntax) for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { uchar c = m_str[idx]; @@ -4391,7 +4932,7 @@ namespace { case '&': os << "&"; break; case '>': - // See: http://www.w3.org/TR/xml/#syntax + // See: https://www.w3.org/TR/xml/#syntax if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') os << ">"; else @@ -4409,7 +4950,7 @@ namespace { // Check for control characters and invalid utf-8 // Escape control characters in standard ascii - // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + // see https://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { hexEscapeChar(os, c); break; @@ -4483,11 +5024,11 @@ namespace { : m_writer( writer ) {} - XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT : m_writer( other.m_writer ){ other.m_writer = nullptr; } - XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT { if ( m_writer ) { m_writer->endElement(); } @@ -4649,7 +5190,7 @@ namespace { void test_case_start_impl(const TestCaseData& in) { bool open_ts_tag = false; if(tc != nullptr) { // we have already opened a test suite - if(strcmp(tc->m_test_suite, in.m_test_suite) != 0) { + if(std::strcmp(tc->m_test_suite, in.m_test_suite) != 0) { xml.endElement(); open_ts_tag = true; } @@ -4666,7 +5207,7 @@ namespace { tc = ∈ xml.startElement("TestCase") .writeAttribute("name", in.m_name) - .writeAttribute("filename", skipPathFromFilename(in.m_file)) + .writeAttribute("filename", skipPathFromFilename(in.m_file.c_str())) .writeAttribute("line", line(in.m_line)) .writeAttribute("description", in.m_description); @@ -4694,13 +5235,18 @@ namespace { .writeAttribute("priority", curr.first.first) .writeAttribute("name", curr.first.second); } else if(opt.count || opt.list_test_cases) { - for(unsigned i = 0; i < in.num_data; ++i) - xml.scopedElement("TestCase").writeAttribute("name", in.data[i]); + for(unsigned i = 0; i < in.num_data; ++i) { + xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name) + .writeAttribute("testsuite", in.data[i]->m_test_suite) + .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file.c_str())) + .writeAttribute("line", line(in.data[i]->m_line)) + .writeAttribute("skipped", in.data[i]->m_skip); + } xml.scopedElement("OverallResultsTestCases") .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); } else if(opt.list_test_suites) { for(unsigned i = 0; i < in.num_data; ++i) - xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]); + xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]->m_test_suite); xml.scopedElement("OverallResultsTestCases") .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); xml.scopedElement("OverallResultsTestSuites") @@ -4764,7 +5310,8 @@ namespace { xml.startElement("OverallResultsAsserts") .writeAttribute("successes", st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest) - .writeAttribute("failures", st.numAssertsFailedCurrentTest); + .writeAttribute("failures", st.numAssertsFailedCurrentTest) + .writeAttribute("test_case_success", st.testCaseSuccess); if(opt.duration) xml.writeAttribute("duration", st.seconds); if(tc->m_expected_failures) @@ -4783,8 +5330,6 @@ namespace { } void subcase_start(const SubcaseSignature& in) override { - std::lock_guard lock(mutex); - xml.startElement("SubCase") .writeAttribute("name", in.m_name) .writeAttribute("filename", skipPathFromFilename(in.m_file)) @@ -4849,6 +5394,278 @@ namespace { DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter); + void fulltext_log_assert_to_stream(std::ostream& s, const AssertData& rb) { + if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) == + 0) //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) " + << Color::None; + + if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n"; + } else if((rb.m_at & assertType::is_throws_as) && + (rb.m_at & assertType::is_throws_with)) { //!OCLINT + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" + << rb.m_exception_string << "\", " << rb.m_exception_type << " ) " << Color::None; + if(rb.m_threw) { + if(!rb.m_failed) { + s << "threw as expected!\n"; + } else { + s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n"; + } + } else { + s << "did NOT throw at all!\n"; + } + } else if(rb.m_at & + assertType::is_throws_as) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", " + << rb.m_exception_type << " ) " << Color::None + << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & + assertType::is_throws_with) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" + << rb.m_exception_string << "\" ) " << Color::None + << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan + << rb.m_exception << "\n"; + } else { + s << (rb.m_threw ? "THREW exception: " : + (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")); + if(rb.m_threw) + s << rb.m_exception << "\n"; + else + s << " values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n"; + } + } + + // TODO: + // - log_message() + // - respond to queries + // - honor remaining options + // - more attributes in tags + struct JUnitReporter : public IReporter + { + XmlWriter xml; + std::mutex mutex; + Timer timer; + std::vector deepestSubcaseStackNames; + + struct JUnitTestCaseData + { + static std::string getCurrentTimestamp() { + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + + std::tm timeInfo; +#ifdef DOCTEST_PLATFORM_WINDOWS + gmtime_s(&timeInfo, &rawtime); +#else // DOCTEST_PLATFORM_WINDOWS + gmtime_r(&rawtime, &timeInfo); +#endif // DOCTEST_PLATFORM_WINDOWS + + char timeStamp[timeStampSize]; + const char* const fmt = "%Y-%m-%dT%H:%M:%SZ"; + + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); + return std::string(timeStamp); + } + + struct JUnitTestMessage + { + JUnitTestMessage(const std::string& _message, const std::string& _type, const std::string& _details) + : message(_message), type(_type), details(_details) {} + + JUnitTestMessage(const std::string& _message, const std::string& _details) + : message(_message), type(), details(_details) {} + + std::string message, type, details; + }; + + struct JUnitTestCase + { + JUnitTestCase(const std::string& _classname, const std::string& _name) + : classname(_classname), name(_name), time(0), failures() {} + + std::string classname, name; + double time; + std::vector failures, errors; + }; + + void add(const std::string& classname, const std::string& name) { + testcases.emplace_back(classname, name); + } + + void appendSubcaseNamesToLastTestcase(std::vector nameStack) { + for(auto& curr: nameStack) + if(curr.size()) + testcases.back().name += std::string("/") + curr.c_str(); + } + + void addTime(double time) { + if(time < 1e-4) + time = 0; + testcases.back().time = time; + totalSeconds += time; + } + + void addFailure(const std::string& message, const std::string& type, const std::string& details) { + testcases.back().failures.emplace_back(message, type, details); + ++totalFailures; + } + + void addError(const std::string& message, const std::string& details) { + testcases.back().errors.emplace_back(message, details); + ++totalErrors; + } + + std::vector testcases; + double totalSeconds = 0; + int totalErrors = 0, totalFailures = 0; + }; + + JUnitTestCaseData testCaseData; + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc = nullptr; + + JUnitReporter(const ContextOptions& co) + : xml(*co.cout) + , opt(co) {} + + unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData&) override {} + + void test_run_start() override {} + + void test_run_end(const TestRunStats& p) override { + // remove .exe extension - mainly to have the same output on UNIX and Windows + std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); +#ifdef DOCTEST_PLATFORM_WINDOWS + if(binary_name.rfind(".exe") != std::string::npos) + binary_name = binary_name.substr(0, binary_name.length() - 4); +#endif // DOCTEST_PLATFORM_WINDOWS + xml.startElement("testsuites"); + xml.startElement("testsuite").writeAttribute("name", binary_name) + .writeAttribute("errors", testCaseData.totalErrors) + .writeAttribute("failures", testCaseData.totalFailures) + .writeAttribute("tests", p.numAsserts); + if(opt.no_time_in_output == false) { + xml.writeAttribute("time", testCaseData.totalSeconds); + xml.writeAttribute("timestamp", JUnitTestCaseData::getCurrentTimestamp()); + } + if(opt.no_version == false) + xml.writeAttribute("doctest_version", DOCTEST_VERSION_STR); + + for(const auto& testCase : testCaseData.testcases) { + xml.startElement("testcase") + .writeAttribute("classname", testCase.classname) + .writeAttribute("name", testCase.name); + if(opt.no_time_in_output == false) + xml.writeAttribute("time", testCase.time); + // This is not ideal, but it should be enough to mimic gtest's junit output. + xml.writeAttribute("status", "run"); + + for(const auto& failure : testCase.failures) { + xml.scopedElement("failure") + .writeAttribute("message", failure.message) + .writeAttribute("type", failure.type) + .writeText(failure.details, false); + } + + for(const auto& error : testCase.errors) { + xml.scopedElement("error") + .writeAttribute("message", error.message) + .writeText(error.details); + } + + xml.endElement(); + } + xml.endElement(); + xml.endElement(); + } + + void test_case_start(const TestCaseData& in) override { + testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); + timer.start(); + } + + void test_case_reenter(const TestCaseData& in) override { + testCaseData.addTime(timer.getElapsedSeconds()); + testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); + deepestSubcaseStackNames.clear(); + + timer.start(); + testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); + } + + void test_case_end(const CurrentTestCaseStats&) override { + testCaseData.addTime(timer.getElapsedSeconds()); + testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); + deepestSubcaseStackNames.clear(); + } + + void test_case_exception(const TestCaseException& e) override { + std::lock_guard lock(mutex); + testCaseData.addError("exception", e.error_string.c_str()); + } + + void subcase_start(const SubcaseSignature& in) override { + deepestSubcaseStackNames.push_back(in.m_name); + } + + void subcase_end() override {} + + void log_assert(const AssertData& rb) override { + if(!rb.m_failed) // report only failures & ignore the `success` option + return; + + std::lock_guard lock(mutex); + + std::ostringstream os; + os << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(") + << line(rb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl; + + fulltext_log_assert_to_stream(os, rb); + log_contexts(os); + testCaseData.addFailure(rb.m_decomp.c_str(), assertString(rb.m_at), os.str()); + } + + void log_message(const MessageData&) override {} + + void test_case_skipped(const TestCaseData&) override {} + + void log_contexts(std::ostringstream& s) { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + + s << " logged: "; + for(int i = 0; i < num_contexts; ++i) { + s << (i == 0 ? "" : " "); + contexts[i]->stringify(&s); + s << std::endl; + } + } + } + }; + + DOCTEST_REGISTER_REPORTER("junit", 0, JUnitReporter); + struct Whitespace { int nrSpaces; @@ -4867,6 +5684,7 @@ namespace { std::ostream& s; bool hasLoggedCurrentTestStart; std::vector subcasesStack; + size_t currentSubcaseLevel; std::mutex mutex; // caching pointers/references to objects of these types - safe to do @@ -4925,23 +5743,40 @@ namespace { s << "\n"; } + // this was requested to be made virtual so users could override it + virtual void file_line_to_stream(const char* file, int line, + const char* tail = "") { + s << Color::LightGrey << skipPathFromFilename(file) << (opt.gnu_file_line ? ":" : "(") + << (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option + << (opt.gnu_file_line ? ":" : "):") << tail; + } + void logTestStart() { if(hasLoggedCurrentTestStart) return; separator_to_stream(); - file_line_to_stream(s, tc->m_file, tc->m_line, "\n"); + file_line_to_stream(tc->m_file.c_str(), tc->m_line, "\n"); if(tc->m_description) s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n"; if(tc->m_test_suite && tc->m_test_suite[0] != '\0') s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n"; if(strncmp(tc->m_name, " Scenario:", 11) != 0) - s << Color::None << "TEST CASE: "; + s << Color::Yellow << "TEST CASE: "; s << Color::None << tc->m_name << "\n"; - for(auto& curr : subcasesStack) - if(curr.m_name[0] != '\0') - s << " " << curr.m_name << "\n"; + for(size_t i = 0; i < currentSubcaseLevel; ++i) { + if(subcasesStack[i].m_name[0] != '\0') + s << " " << subcasesStack[i].m_name << "\n"; + } + + if(currentSubcaseLevel != subcasesStack.size()) { + s << Color::Yellow << "\nDEEPEST SUBCASE STACK REACHED (DIFFERENT FROM THE CURRENT ONE):\n" << Color::None; + for(size_t i = 0; i < subcasesStack.size(); ++i) { + if(subcasesStack[i].m_name[0] != '\0') + s << " " << subcasesStack[i].m_name << "\n"; + } + } s << "\n"; @@ -4955,9 +5790,11 @@ namespace { } void printIntro() { - printVersion(); - s << Color::Cyan << "[doctest] " << Color::None - << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n"; + if(opt.no_intro == false) { + printVersion(); + s << Color::Cyan << "[doctest] " << Color::None + << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n"; + } } void printHelp() { @@ -5019,7 +5856,7 @@ namespace { << Whitespace(sizePrefixDisplay*1) << "output filename\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by= " << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n"; - s << Whitespace(sizePrefixDisplay*3) << " - by [file/suite/name/rand]\n"; + s << Whitespace(sizePrefixDisplay*3) << " - [file/suite/name/rand/none]\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed= " << Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first= " @@ -5042,12 +5879,18 @@ namespace { << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration= " << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "m, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "minimal= " + << Whitespace(sizePrefixDisplay*1) << "minimal console output (only failures)\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "q, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "quiet= " + << Whitespace(sizePrefixDisplay*1) << "no console output\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw= " << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode= " << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run= " << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ni, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-intro= " + << Whitespace(sizePrefixDisplay*1) << "omit the framework intro in the output\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version= " << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors= " @@ -5085,22 +5928,6 @@ namespace { printReporters(getReporters(), "reporters"); } - void list_query_results() { - separator_to_stream(); - if(opt.count || opt.list_test_cases) { - s << Color::Cyan << "[doctest] " << Color::None - << "unskipped test cases passing the current filters: " - << g_cs->numTestCasesPassingFilters << "\n"; - } else if(opt.list_test_suites) { - s << Color::Cyan << "[doctest] " << Color::None - << "unskipped test cases passing the current filters: " - << g_cs->numTestCasesPassingFilters << "\n"; - s << Color::Cyan << "[doctest] " << Color::None - << "test suites with unskipped test cases passing the current filters: " - << g_cs->numTestSuitesPassingFilters << "\n"; - } - } - // ========================================================================================= // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE // ========================================================================================= @@ -5120,7 +5947,7 @@ namespace { } for(unsigned i = 0; i < in.num_data; ++i) - s << Color::None << in.data[i] << "\n"; + s << Color::None << in.data[i]->m_name << "\n"; separator_to_stream(); @@ -5133,7 +5960,7 @@ namespace { separator_to_stream(); for(unsigned i = 0; i < in.num_data; ++i) - s << Color::None << in.data[i] << "\n"; + s << Color::None << in.data[i]->m_test_suite << "\n"; separator_to_stream(); @@ -5146,30 +5973,40 @@ namespace { } } - void test_run_start() override { printIntro(); } + void test_run_start() override { + if(!opt.minimal) + printIntro(); + } void test_run_end(const TestRunStats& p) override { - separator_to_stream(); + if(opt.minimal && p.numTestCasesFailed == 0) + return; + separator_to_stream(); + s << std::dec; + + auto totwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters, static_cast(p.numAsserts))) + 1))); + auto passwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast(p.numAsserts - p.numAssertsFailed))) + 1))); + auto failwidth = int(std::ceil(log10((std::max(p.numTestCasesFailed, static_cast(p.numAssertsFailed))) + 1))); const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0; - s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(6) + s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(totwidth) << p.numTestCasesPassingFilters << " | " << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None : Color::Green) - << std::setw(6) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed" + << std::setw(passwidth) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed" << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None) - << std::setw(6) << p.numTestCasesFailed << " failed" << Color::None << " | "; + << std::setw(failwidth) << p.numTestCasesFailed << " failed" << Color::None << " |"; if(opt.no_skipped_summary == false) { const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters; - s << (numSkipped == 0 ? Color::None : Color::Yellow) << std::setw(6) << numSkipped + s << " " << (numSkipped == 0 ? Color::None : Color::Yellow) << numSkipped << " skipped" << Color::None; } s << "\n"; - s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(6) + s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(totwidth) << p.numAsserts << " | " << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green) - << std::setw(6) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None - << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(6) + << std::setw(passwidth) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None + << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(failwidth) << p.numAssertsFailed << " failed" << Color::None << " |\n"; s << Color::Cyan << "[doctest] " << Color::None << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green) @@ -5179,11 +6016,18 @@ namespace { void test_case_start(const TestCaseData& in) override { hasLoggedCurrentTestStart = false; tc = ∈ + subcasesStack.clear(); + currentSubcaseLevel = 0; } - void test_case_reenter(const TestCaseData&) override {} + void test_case_reenter(const TestCaseData&) override { + subcasesStack.clear(); + } void test_case_end(const CurrentTestCaseStats& st) override { + if(tc->m_no_output) + return; + // log the preamble of the test case only if there is something // else to print - something other than that an assert has failed if(opt.duration || @@ -5218,9 +6062,13 @@ namespace { } void test_case_exception(const TestCaseException& e) override { + std::lock_guard lock(mutex); + if(tc->m_no_output) + return; + logTestStart(); - file_line_to_stream(s, tc->m_file, tc->m_line, " "); + file_line_to_stream(tc->m_file.c_str(), tc->m_line, " "); successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require : assertType::is_check); s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ") @@ -5239,84 +6087,41 @@ namespace { } void subcase_start(const SubcaseSignature& subc) override { - std::lock_guard lock(mutex); subcasesStack.push_back(subc); + ++currentSubcaseLevel; hasLoggedCurrentTestStart = false; } void subcase_end() override { - std::lock_guard lock(mutex); - subcasesStack.pop_back(); + --currentSubcaseLevel; hasLoggedCurrentTestStart = false; } void log_assert(const AssertData& rb) override { - if(!rb.m_failed && !opt.success) + if((!rb.m_failed && !opt.success) || tc->m_no_output) return; std::lock_guard lock(mutex); logTestStart(); - file_line_to_stream(s, rb.m_file, rb.m_line, " "); + file_line_to_stream(rb.m_file, rb.m_line, " "); successOrFailColoredStringToStream(!rb.m_failed, rb.m_at); - if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) == - 0) //!OCLINT bitwise operator in conditional - s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) " - << Color::None; - if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional - s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n"; - } else if((rb.m_at & assertType::is_throws_as) && - (rb.m_at & assertType::is_throws_with)) { //!OCLINT - s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" - << rb.m_exception_string << "\", " << rb.m_exception_type << " ) " << Color::None; - if(rb.m_threw) { - if(!rb.m_failed) { - s << "threw as expected!\n"; - } else { - s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n"; - } - } else { - s << "did NOT throw at all!\n"; - } - } else if(rb.m_at & - assertType::is_throws_as) { //!OCLINT bitwise operator in conditional - s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", " - << rb.m_exception_type << " ) " << Color::None - << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" : - "threw a DIFFERENT exception: ") : - "did NOT throw at all!") - << Color::Cyan << rb.m_exception << "\n"; - } else if(rb.m_at & - assertType::is_throws_with) { //!OCLINT bitwise operator in conditional - s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" - << rb.m_exception_string << "\" ) " << Color::None - << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" : - "threw a DIFFERENT exception: ") : - "did NOT throw at all!") - << Color::Cyan << rb.m_exception << "\n"; - } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional - s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan - << rb.m_exception << "\n"; - } else { - s << (rb.m_threw ? "THREW exception: " : - (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")); - if(rb.m_threw) - s << rb.m_exception << "\n"; - else - s << " values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n"; - } + fulltext_log_assert_to_stream(s, rb); log_contexts(); } void log_message(const MessageData& mb) override { + if(tc->m_no_output) + return; + std::lock_guard lock(mutex); logTestStart(); - file_line_to_stream(s, mb.m_file, mb.m_line, " "); + file_line_to_stream(mb.m_file, mb.m_line, " "); s << getSuccessOrFailColor(false, mb.m_severity) << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity, "MESSAGE") << ": "; @@ -5342,8 +6147,10 @@ namespace { bool with_col = g_no_colors; \ g_no_colors = false; \ ConsoleReporter::func(arg); \ - DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \ - oss.str(""); \ + if(oss.tellp() != std::streampos{}) { \ + DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \ + oss.str(""); \ + } \ g_no_colors = with_col; \ } @@ -5421,18 +6228,42 @@ namespace { std::vector& res) { String filtersString; if(parseOption(argc, argv, pattern, &filtersString)) { - // tokenize with "," as a separator - // cppcheck-suppress strtokCalled - DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") - auto pch = std::strtok(filtersString.c_str(), ","); // modifies the string - while(pch != nullptr) { - if(strlen(pch)) - res.push_back(pch); - // uses the strtok() internal state to go to the next token - // cppcheck-suppress strtokCalled - pch = std::strtok(nullptr, ","); + // tokenize with "," as a separator, unless escaped with backslash + std::ostringstream s; + auto flush = [&s, &res]() { + auto string = s.str(); + if(string.size() > 0) { + res.push_back(string.c_str()); + } + s.str(""); + }; + + bool seenBackslash = false; + const char* current = filtersString.c_str(); + const char* end = current + strlen(current); + while(current != end) { + char character = *current++; + if(seenBackslash) { + seenBackslash = false; + if(character == ',') { + s.put(','); + continue; + } + s.put('\\'); + } + if(character == '\\') { + seenBackslash = true; + } else if(character == ',') { + flush(); + } else { + s.put(character); + } } - DOCTEST_CLANG_SUPPRESS_WARNING_POP + + if(seenBackslash) { + s.put('\\'); + } + flush(); return true; } return false; @@ -5530,7 +6361,7 @@ void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { #define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default) \ if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) || \ parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes)) \ - p->var = !!intRes; \ + p->var = static_cast(intRes); \ else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) || \ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname)) \ p->var = true; \ @@ -5565,9 +6396,12 @@ void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("minimal", "m", minimal, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("quiet", "q", quiet, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-intro", "ni", no_intro, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false); @@ -5576,7 +6410,9 @@ void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { DOCTEST_PARSE_AS_BOOL_OR_FLAG("gnu-file-line", "gfl", gnu_file_line, !bool(DOCTEST_MSVC)); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-path-filenames", "npf", no_path_in_filenames, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-line-numbers", "nln", no_line_numbers, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-debug-output", "ndo", no_debug_output, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skipped-summary", "nss", no_skipped_summary, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-time-in-output", "ntio", no_time_in_output, false); // clang-format on if(withDefaults) { @@ -5629,9 +6465,15 @@ void Context::clearFilters() { curr.clear(); } -// allows the user to override procedurally the int/bool options from the command line +// allows the user to override procedurally the bool options from the command line +void Context::setOption(const char* option, bool value) { + setOption(option, value ? "true" : "false"); +} + +// allows the user to override procedurally the int options from the command line void Context::setOption(const char* option, int value) { setOption(option, toString(value).c_str()); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) } // allows the user to override procedurally the string options from the command line @@ -5648,6 +6490,31 @@ void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; } void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; } +void Context::setCout(std::ostream* out) { p->cout = out; } + +static class DiscardOStream : public std::ostream +{ +private: + class : public std::streambuf + { + private: + // allowing some buffering decreases the amount of calls to overflow + char buf[1024]; + + protected: + std::streamsize xsputn(const char_type*, std::streamsize count) override { return count; } + + int_type overflow(int_type ch) override { + setp(std::begin(buf), std::end(buf)); + return traits_type::not_eof(ch); + } + } discardBuf; + +public: + DiscardOStream() + : std::ostream(&discardBuf) {} +} discardOut; + // the main function that does all the filtering and test running int Context::run() { using namespace detail; @@ -5661,18 +6528,25 @@ int Context::run() { g_no_colors = p->no_colors; p->resetRunData(); - // stdout by default - p->cout = &std::cout; - p->cerr = &std::cerr; - - // or to a file if specified std::fstream fstr; - if(p->out.size()) { - fstr.open(p->out.c_str(), std::fstream::out); - p->cout = &fstr; + if(p->cout == nullptr) { + if(p->quiet) { + p->cout = &discardOut; + } else if(p->out.size()) { + // to a file if specified + fstr.open(p->out.c_str(), std::fstream::out); + p->cout = &fstr; + } else { + // stdout by default + p->cout = &std::cout; + } } + FatalConditionHandler::allocateAltStackMem(); + auto cleanup_and_return = [&]() { + FatalConditionHandler::freeAltStackMem(); + if(fstr.is_open()) fstr.close(); @@ -5707,7 +6581,7 @@ int Context::run() { p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs)); #ifdef DOCTEST_PLATFORM_WINDOWS - if(isDebuggerActive()) + if(isDebuggerActive() && p->no_debug_output == false) p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs)); #endif // DOCTEST_PLATFORM_WINDOWS @@ -5744,13 +6618,16 @@ int Context::run() { first[i] = first[idxToSwap]; first[idxToSwap] = temp; } + } else if(p->order_by.compare("none", true) == 0) { + // means no sorting - beneficial for death tests which call into the executable + // with a specific test case in mind - we don't want to slow down the startup times } } std::set testSuitesPassingFilt; - bool query_mode = p->count || p->list_test_cases || p->list_test_suites; - std::vector queryResults; + bool query_mode = p->count || p->list_test_cases || p->list_test_suites; + std::vector queryResults; if(!query_mode) DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, DOCTEST_EMPTY); @@ -5763,9 +6640,9 @@ int Context::run() { if(tc.m_skip && !p->no_skip) skip_me = true; - if(!matchesAny(tc.m_file, p->filters[0], true, p->case_sensitive)) + if(!matchesAny(tc.m_file.c_str(), p->filters[0], true, p->case_sensitive)) skip_me = true; - if(matchesAny(tc.m_file, p->filters[1], false, p->case_sensitive)) + if(matchesAny(tc.m_file.c_str(), p->filters[1], false, p->case_sensitive)) skip_me = true; if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive)) skip_me = true; @@ -5796,14 +6673,14 @@ int Context::run() { // print the name of the test and don't execute it if(p->list_test_cases) { - queryResults.push_back(tc.m_name); + queryResults.push_back(&tc); continue; } // print the name of the test suite if not done already and don't execute it if(p->list_test_suites) { if((testSuitesPassingFilt.count(tc.m_test_suite) == 0) && tc.m_test_suite[0] != '\0') { - queryResults.push_back(tc.m_test_suite); + queryResults.push_back(&tc); testSuitesPassingFilt.insert(tc.m_test_suite); p->numTestSuitesPassingFilters++; } @@ -5843,10 +6720,13 @@ int Context::run() { #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS try { #endif // DOCTEST_CONFIG_NO_EXCEPTIONS +// MSVC 2015 diagnoses fatalConditionHandler as unused (because reset() is a static method) +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4101) // unreferenced local variable FatalConditionHandler fatalConditionHandler; // Handle signals // execute the test tc.m_test(); fatalConditionHandler.reset(); +DOCTEST_MSVC_SUPPRESS_WARNING_POP #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS } catch(const TestFailureException&) { p->failure_flags |= TestCaseFailureReason::AssertFailure; @@ -5892,13 +6772,6 @@ int Context::run() { DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata); } - // see these issues on the reasoning for this: - // - https://github.com/onqtam/doctest/issues/143#issuecomment-414418903 - // - https://github.com/onqtam/doctest/issues/126 - auto DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS = []() DOCTEST_NOINLINE - { std::cout << std::string(); }; - DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS(); - return cleanup_and_return(); } @@ -5937,5 +6810,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP +DOCTEST_SUPPRESS_COMMON_WARNINGS_POP + #endif // DOCTEST_LIBRARY_IMPLEMENTATION #endif // DOCTEST_CONFIG_IMPLEMENT From 5c5c55b83cc7d542890a47aeee6530a8ee6d10f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Nogueira?= Date: Tue, 19 Apr 2022 13:59:08 -0300 Subject: [PATCH 51/61] Fix unused argument warnings. Solution suggested by skrobinson. This way, GCC and clang-tidy don't generate warnings. Fixes: https://github.com/p-ranav/argparse/issues/167 --- include/argparse/argparse.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index fb4f25a..36a30cc 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -148,7 +148,7 @@ constexpr bool standard_integer = template constexpr decltype(auto) apply_plus_one_impl(F &&f, Tuple &&t, Extra &&x, - std::index_sequence unused) { + std::index_sequence /*unused*/) { return std::invoke(std::forward(f), std::get(std::forward(t))..., std::forward(x)); } @@ -855,7 +855,7 @@ public: : m_program_name(std::move(program_name)), m_version(std::move(version)) { if ((add_args & default_arguments::help) == default_arguments::help) { add_argument("-h", "--help") - .action([&](const auto &unused) { + .action([&](const auto &/*unused*/) { std::cout << help().str(); std::exit(0); }) @@ -866,7 +866,7 @@ public: } if ((add_args & default_arguments::version) == default_arguments::version) { add_argument("-v", "--version") - .action([&](const auto &unused) { + .action([&](const auto &/*unused*/) { std::cout << m_version << std::endl; std::exit(0); }) From 4af831ef724224f751a25aea43689b92969ae612 Mon Sep 17 00:00:00 2001 From: Pranav Srinivas Kumar Date: Wed, 20 Apr 2022 08:37:27 -0500 Subject: [PATCH 52/61] Bumped library version to v2.4 --- CMakeLists.txt | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6806731..592622b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.12.4) project(argparse - VERSION 2.3.0 + VERSION 2.4.0 DESCRIPTION "A single header argument parser for C++17" HOMEPAGE_URL "https://github.com/p-ranav/argparse" LANGUAGES CXX diff --git a/README.md b/README.md index b23cda5..c8a359b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ license - version + version

## Highlights From 71a9a7a53ec78ee903632f51abfac0c10fecb3ab Mon Sep 17 00:00:00 2001 From: Dennis-Bbg <75340117+Dennis-Bbg@users.noreply.github.com> Date: Thu, 21 Apr 2022 11:52:12 +0200 Subject: [PATCH 53/61] Update README.md Fixed the example in section" #### Deciding if the value was given by the user". --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b23cda5..a12c3c8 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ If you want to know whether the user supplied a value for an argument that has a ```cpp program.add_argument("--color") - .default_value("orange") + .default_value(std::string{"orange"}) // might otherwise be type const char* leading to an error when trying program.get .help("specify the cat's fur color"); try { From 37a1f3b9e6ddb27ad70fb3b52c83266066949488 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Wed, 27 Apr 2022 08:00:17 -0700 Subject: [PATCH 54/61] Remove unused code in Argument::validate As far as I can tell, this statement is never true. When m_values.size() < *expected, ::consume has already thrown "Too few arguments..." before ::validate is called. When m_values.size() > *expected, ArgumentParser::parse_args_internal has already thrown "Maximum number of positional arguments exceeded" before ::validate is called. If ::remaining is used to avoid the last exception, this Argument will always consume the expected number of values, hence this expression is again false. Signed-off-by: Sean Robinson --- include/argparse/argparse.hpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index db99c16..f615c9b 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -518,13 +518,6 @@ public: void validate() const { if (auto expected = maybe_nargs()) { if (m_is_optional) { - if (m_is_used && m_values.size() != *expected && !m_is_repeatable && - !m_default_value.has_value()) { - std::stringstream stream; - stream << m_used_name << ": expected " << *expected - << " argument(s). " << m_values.size() << " provided."; - throw std::runtime_error(stream.str()); - } // TODO: check if an implicit value was programmed for this argument if (!m_is_used && !m_default_value.has_value() && m_is_required) { std::stringstream stream; From c4faf29feb72bb3d26b09500d1f3c125b34500e1 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Wed, 27 Apr 2022 08:00:51 -0700 Subject: [PATCH 55/61] Replace cend with std::cend Signed-off-by: Sean Robinson --- include/argparse/argparse.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index f615c9b..4b05057 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -903,11 +903,11 @@ public: // Call add_argument with variadic number of string arguments template Argument &add_argument(Targs... f_args) { using array_of_sv = std::array; - auto argument = m_optional_arguments.emplace(cend(m_optional_arguments), - array_of_sv{f_args...}); + auto argument = m_optional_arguments.emplace( + std::cend(m_optional_arguments), array_of_sv{f_args...}); if (!argument->m_is_optional) { - m_positional_arguments.splice(cend(m_positional_arguments), + m_positional_arguments.splice(std::cend(m_positional_arguments), m_optional_arguments, argument); } @@ -921,13 +921,13 @@ public: ArgumentParser &add_parents(const Targs &...f_args) { for (const ArgumentParser &parent_parser : {std::ref(f_args)...}) { for (const auto &argument : parent_parser.m_positional_arguments) { - auto it = m_positional_arguments.insert(cend(m_positional_arguments), - argument); + auto it = m_positional_arguments.insert( + std::cend(m_positional_arguments), argument); index_argument(it); } for (const auto &argument : parent_parser.m_optional_arguments) { - auto it = - m_optional_arguments.insert(cend(m_optional_arguments), argument); + auto it = m_optional_arguments.insert(std::cend(m_optional_arguments), + argument); index_argument(it); } } From 8b2357475f90878ba298a418066dc9d4fe2bcad1 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Wed, 27 Apr 2022 08:01:25 -0700 Subject: [PATCH 56/61] Add documentation for add_description and add_epilog Signed-off-by: Sean Robinson --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index 83f7ee5..6103003 100644 --- a/README.md +++ b/README.md @@ -316,6 +316,40 @@ Optional arguments: You may also get the help message in string via `program.help().str()`. +#### Adding a description and an epilog to help + +`ArgumentParser::add_description` will add text before the detailed argument +information. `ArgumentParser::add_epilog` will add text after all other help output. + +```cpp +argparse::ArgumentParser program("main"); + +program.add_argument("thing") + .help("Thing to use."); +program.add_description("Forward a thing to the next member."); +program.add_epilog("Possible things include betingalw, chiz, and res."); + +program.parse_args(argc, argv); + +std::cout << program << std::endl; +``` + +```bash +$ ./main --help +Usage: main thing + +Forward a thing to the next member. + +Positional arguments: +thing Thing to use. + +Optional arguments: +-h --help shows help message and exits [default: false] +-v --version prints version information and exits [default: false] + +Possible things include betingalw, chiz, and res. +``` + ### List of Arguments ArgumentParser objects usually associate a single command-line argument with a single action to be taken. The ```.nargs``` associates a different number of command-line arguments with a single action. When using ```nargs(N)```, N arguments from the command line will be gathered together into a list. From 82eed31d3ffd579610e5a35b5c9204d7674b788b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Nogueira?= Date: Mon, 9 May 2022 14:35:50 -0300 Subject: [PATCH 57/61] Fix remaining unused argument warnings. Commit 5c5c55b83cc7d542890a47aeee6530a8ee6d10f7 missed one such occurrence. --- include/argparse/argparse.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 4b05057..55828ed 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -349,7 +349,7 @@ class Argument { template explicit Argument(std::array &&a, - std::index_sequence unused) + std::index_sequence /*unused*/) : m_is_optional((is_optional(a[I]) || ...)), m_is_required(false), m_is_repeatable(false), m_is_used(false) { ((void)m_names.emplace_back(a[I]), ...); From 95d48506833624e28fa68fdab5cfeeb98b05edff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Sun, 15 May 2022 16:04:38 +0200 Subject: [PATCH 58/61] Add missing include MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After upgrading g++ package to 12.1.0 on archlinux I see the following compilation error: /usr/include/argparse/argparse.hpp: In member function ‘void argparse::ArgumentParser::index_argument(list_iterator)’: /usr/include/argparse/argparse.hpp:1167:34: error: ‘as_const’ is not a member of ‘std’; did you mean ‘is_const’? 1167 | for (const auto &name : std::as_const(it->m_names)) { | ^~~~~~~~ | is_const It turns out that std::as_const comes from header [1] which was not explicitly included. [1] https://en.cppreference.com/w/cpp/utility/as_const --- include/argparse/argparse.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 55828ed..112534b 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -48,6 +48,7 @@ SOFTWARE. #include #include #include +#include #include #include From 162866eb4b31c540fb07c73e68e12302d4dd1c30 Mon Sep 17 00:00:00 2001 From: Pranav Date: Fri, 27 May 2022 07:29:23 -0500 Subject: [PATCH 59/61] Bumped version to v2.5 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 592622b..0e267a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.12.4) project(argparse - VERSION 2.4.0 + VERSION 2.5.0 DESCRIPTION "A single header argument parser for C++17" HOMEPAGE_URL "https://github.com/p-ranav/argparse" LANGUAGES CXX From 571f8e3bff30de0efa5a7f4b6d2380d486537ce4 Mon Sep 17 00:00:00 2001 From: Pranav Date: Fri, 27 May 2022 07:29:42 -0500 Subject: [PATCH 60/61] Bumped version to v2.5 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6103003..a318b00 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ license - version + version

## Highlights From 234f0cde822abb67d3158089883598cfcb9473d5 Mon Sep 17 00:00:00 2001 From: Pranav Date: Fri, 27 May 2022 07:30:25 -0500 Subject: [PATCH 61/61] Bumped version to v2.5 --- conanfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conanfile.py b/conanfile.py index 1851287..4043e0b 100644 --- a/conanfile.py +++ b/conanfile.py @@ -2,9 +2,9 @@ from conans import ConanFile class ArgparseConan(ConanFile): name = "argparse" - version = "1.0" + version = "2.5" exports_sources = "include/argparse.hpp" no_copy_source = True def package(self): - self.copy("argparse.hpp") \ No newline at end of file + self.copy("argparse.hpp")