aboutsummaryrefslogtreecommitdiffstats
path: root/tests/SelfTest/IntrospectiveTests
diff options
context:
space:
mode:
authorLexi Winter <lexi@le-fay.org>2025-06-29 19:25:29 +0100
committerLexi Winter <lexi@le-fay.org>2025-06-29 19:25:29 +0100
commitbc524d70253a4ab2fe40c3ca3e5666e267c0a4d1 (patch)
tree1e629e7b46b1d9972a973bc93fd100bcebd395be /tests/SelfTest/IntrospectiveTests
downloadnihil-vendor/catch2.tar.gz
nihil-vendor/catch2.tar.bz2
Diffstat (limited to 'tests/SelfTest/IntrospectiveTests')
-rw-r--r--tests/SelfTest/IntrospectiveTests/Algorithms.tests.cpp94
-rw-r--r--tests/SelfTest/IntrospectiveTests/AssertionHandler.tests.cpp17
-rw-r--r--tests/SelfTest/IntrospectiveTests/Clara.tests.cpp88
-rw-r--r--tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp467
-rw-r--r--tests/SelfTest/IntrospectiveTests/CmdLineHelpers.tests.cpp111
-rw-r--r--tests/SelfTest/IntrospectiveTests/ColourImpl.tests.cpp64
-rw-r--r--tests/SelfTest/IntrospectiveTests/Details.tests.cpp172
-rw-r--r--tests/SelfTest/IntrospectiveTests/FloatingPoint.tests.cpp139
-rw-r--r--tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp575
-rw-r--r--tests/SelfTest/IntrospectiveTests/Integer.tests.cpp224
-rw-r--r--tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp455
-rw-r--r--tests/SelfTest/IntrospectiveTests/Json.tests.cpp152
-rw-r--r--tests/SelfTest/IntrospectiveTests/Parse.tests.cpp38
-rw-r--r--tests/SelfTest/IntrospectiveTests/PartTracker.tests.cpp254
-rw-r--r--tests/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp609
-rw-r--r--tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp330
-rw-r--r--tests/SelfTest/IntrospectiveTests/Sharding.tests.cpp45
-rw-r--r--tests/SelfTest/IntrospectiveTests/Stream.tests.cpp32
-rw-r--r--tests/SelfTest/IntrospectiveTests/String.tests.cpp212
-rw-r--r--tests/SelfTest/IntrospectiveTests/StringManip.tests.cpp94
-rw-r--r--tests/SelfTest/IntrospectiveTests/Tag.tests.cpp117
-rw-r--r--tests/SelfTest/IntrospectiveTests/TestCaseInfoHasher.tests.cpp72
-rw-r--r--tests/SelfTest/IntrospectiveTests/TestSpec.tests.cpp365
-rw-r--r--tests/SelfTest/IntrospectiveTests/TestSpecParser.tests.cpp55
-rw-r--r--tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp400
-rw-r--r--tests/SelfTest/IntrospectiveTests/ToString.tests.cpp120
-rw-r--r--tests/SelfTest/IntrospectiveTests/Traits.tests.cpp45
-rw-r--r--tests/SelfTest/IntrospectiveTests/UniquePtr.tests.cpp141
-rw-r--r--tests/SelfTest/IntrospectiveTests/Xml.tests.cpp183
29 files changed, 5670 insertions, 0 deletions
diff --git a/tests/SelfTest/IntrospectiveTests/Algorithms.tests.cpp b/tests/SelfTest/IntrospectiveTests/Algorithms.tests.cpp
new file mode 100644
index 0000000..fa17cf8
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/Algorithms.tests.cpp
@@ -0,0 +1,94 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/internal/catch_is_permutation.hpp>
+
+#include <helpers/range_test_helpers.hpp>
+
+#include <array>
+
+namespace {
+ template <typename Range1, typename Range2>
+ static bool is_permutation(Range1 const& r1, Range2 const& r2) {
+ using std::begin; using std::end;
+ return Catch::Detail::is_permutation(
+ begin( r1 ), end( r1 ), begin( r2 ), end( r2 ), std::equal_to<>{} );
+ }
+}
+
+TEST_CASE("is_permutation", "[algorithms][approvals]") {
+ SECTION( "Handle empty ranges" ) {
+ std::array<int, 0> empty;
+ std::array<int, 2> non_empty{ { 2, 3 } };
+ REQUIRE( is_permutation( empty, empty ) );
+ REQUIRE_FALSE( is_permutation( empty, non_empty ) );
+ REQUIRE_FALSE( is_permutation( non_empty, empty ) );
+ }
+ SECTION( "Different length ranges" ) {
+ std::array<int, 6> arr1{ { 1, 3, 5, 7, 8, 9 } };
+ // arr2 is prefix of arr1
+ std::array<int, 4> arr2{ { 1, 3, 5, 7 } };
+ // arr3 shares prefix with arr1 and arr2, but is not a permutation
+ std::array<int, 5> arr3{ { 1, 3, 5, 9, 8 } };
+ REQUIRE_FALSE( is_permutation( arr1, arr2 ) );
+ REQUIRE_FALSE( is_permutation( arr1, arr3 ) );
+ REQUIRE_FALSE( is_permutation( arr2, arr3 ) );
+ }
+ SECTION( "Same length ranges" ) {
+ SECTION( "Shared elements, but different counts" ) {
+ const std::array<int, 6>
+ arr1{ { 1, 1, 1, 1, 2, 2 } },
+ arr2{ { 1, 1, 2, 2, 2, 2 } };
+ REQUIRE_FALSE( is_permutation( arr1, arr2 ) );
+ }
+ SECTION( "Identical ranges" ) {
+ const std::array<int, 6>
+ arr1{ { 1, 1, 1, 1, 2, 2 } },
+ arr2{ { 1, 1, 2, 2, 2, 2 } };
+ REQUIRE( is_permutation( arr1, arr1 ) );
+ REQUIRE( is_permutation( arr2, arr2 ) );
+ }
+ SECTION( "Completely distinct elements" ) {
+ // Completely distinct elements
+ const std::array<int, 4>
+ arr1{ { 1, 2, 3, 4 } },
+ arr2{ { 10, 20, 30, 40 } };
+ REQUIRE_FALSE( is_permutation( arr1, arr2 ) );
+ }
+ SECTION( "Reverse ranges" ) {
+ const std::array<int, 5>
+ arr1{ { 1, 2, 3, 4, 5 } },
+ arr2{ { 5, 4, 3, 2, 1 } };
+ REQUIRE( is_permutation( arr1, arr2 ) );
+ }
+ SECTION( "Shared prefix & permuted elements" ) {
+ const std::array<int, 5>
+ arr1{ { 1, 1, 2, 3, 4 } },
+ arr2{ { 1, 1, 4, 2, 3 } };
+ REQUIRE( is_permutation( arr1, arr2 ) );
+ }
+ SECTION( "Permutations with element count > 1" ) {
+ const std::array<int, 7>
+ arr1{ { 2, 2, 3, 3, 3, 1, 1 } },
+ arr2{ { 3, 2, 1, 3, 2, 1, 3 } };
+ REQUIRE( is_permutation( arr1, arr2 ) );
+ }
+ }
+}
+
+TEST_CASE("is_permutation supports iterator + sentinel pairs",
+ "[algorithms][is-permutation][approvals]") {
+ const has_different_begin_end_types<int>
+ range_1{ 1, 2, 3, 4 },
+ range_2{ 4, 3, 2, 1 };
+ REQUIRE( is_permutation( range_1, range_2 ) );
+
+ const has_different_begin_end_types<int> range_3{ 3, 3, 2, 1 };
+ REQUIRE_FALSE( is_permutation( range_1, range_3 ) );
+}
diff --git a/tests/SelfTest/IntrospectiveTests/AssertionHandler.tests.cpp b/tests/SelfTest/IntrospectiveTests/AssertionHandler.tests.cpp
new file mode 100644
index 0000000..ab09607
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/AssertionHandler.tests.cpp
@@ -0,0 +1,17 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+
+TEST_CASE( "Incomplete AssertionHandler", "[assertion-handler][!shouldfail]" ) {
+ Catch::AssertionHandler catchAssertionHandler(
+ "REQUIRE"_catch_sr,
+ CATCH_INTERNAL_LINEINFO,
+ "Dummy",
+ Catch::ResultDisposition::Normal );
+}
diff --git a/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp b/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp
new file mode 100644
index 0000000..53023b5
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp
@@ -0,0 +1,88 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/internal/catch_clara.hpp>
+
+
+#include <string>
+
+TEST_CASE("is_unary_function", "[clara][compilation]") {
+ auto unary1 = [](int) {};
+ auto unary2 = [](std::string const&) {};
+ auto const unary3 = [](std::string const&) {};
+ auto unary4 = [](int) { return 42; };
+ void unary5(char);
+ double unary6(long);
+
+ double binary1(long, int);
+ auto binary2 = [](int, char) {};
+ auto nullary1 = []() {};
+ auto nullary2 = []() {return 42;};
+
+ STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function<decltype(unary1)>::value);
+ STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function<decltype(unary2)>::value);
+ STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function<decltype(unary3)>::value);
+ STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function<decltype(unary4)>::value);
+ STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function<decltype(unary5)>::value);
+ STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function<decltype(unary6)>::value);
+
+ STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function<decltype(binary1)>::value);
+ STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function<decltype(binary2)>::value);
+ STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function<decltype(nullary1)>::value);
+ STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function<decltype(nullary2)>::value);
+ STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function<int>::value);
+ STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function<std::string const&>::value);
+}
+
+
+TEST_CASE("Clara::Arg supports single-arg parse the way Opt does", "[clara][arg][compilation]") {
+ std::string name;
+ auto p = Catch::Clara::Arg(name, "just one arg");
+
+ CHECK(name.empty());
+
+ p.parse( Catch::Clara::Args{ "UnitTest", "foo" } );
+ REQUIRE(name == "foo");
+}
+
+TEST_CASE("Clara::Arg does not crash on incomplete input", "[clara][arg][compilation]") {
+ std::string name;
+ auto p = Catch::Clara::Arg(name, "-");
+
+ CHECK(name.empty());
+
+ auto result = p.parse( Catch::Clara::Args{ "UnitTest", "-" } );
+ CHECK( result );
+ CHECK( result.type() == Catch::Clara::Detail::ResultType::Ok );
+ const auto& parsed = result.value();
+ CHECK( parsed.type() == Catch::Clara::ParseResultType::NoMatch );
+ CHECK( parsed.remainingTokens().count() == 2 );
+ CHECK( name.empty() );
+}
+
+TEST_CASE("Clara::Opt supports accept-many lambdas", "[clara][opt]") {
+ using namespace Catch::Clara;
+ std::vector<std::string> res;
+ const auto push_to_res = [&](std::string const& s) {
+ res.push_back(s);
+ return ParserResult::ok( ParseResultType::Matched );
+ };
+
+ SECTION("Parsing fails on multiple options without accept_many") {
+ auto p = Parser() | Opt(push_to_res, "value")["-o"];
+ auto parse_result = p.parse( Args{ "UnitTest", "-o", "aaa", "-o", "bbb" } );
+ CHECK_FALSE(parse_result);
+ }
+ SECTION("Parsing succeeds on multiple options with accept_many") {
+ auto p = Parser() | Opt(accept_many, push_to_res, "value")["-o"];
+ auto parse_result = p.parse( Args{ "UnitTest", "-o", "aaa", "-o", "bbb" } );
+ CHECK(parse_result);
+ CHECK(res == std::vector<std::string>{ "aaa", "bbb" });
+ }
+}
diff --git a/tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp b/tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp
new file mode 100644
index 0000000..404bad2
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp
@@ -0,0 +1,467 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_config.hpp>
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_string.hpp>
+#include <catch2/internal/catch_test_spec_parser.hpp>
+#include <catch2/catch_user_config.hpp>
+#include <catch2/catch_test_case_info.hpp>
+#include <catch2/internal/catch_commandline.hpp>
+#include <catch2/generators/catch_generators.hpp>
+#include <catch2/internal/catch_compiler_capabilities.hpp>
+
+
+namespace {
+ auto fakeTestCase(const char* name, const char* desc = "") { return Catch::makeTestCaseInfo("", { name, desc }, CATCH_INTERNAL_LINEINFO); }
+}
+
+TEST_CASE( "Process can be configured on command line", "[config][command-line]" ) {
+
+ using namespace Catch::Matchers;
+
+ Catch::ConfigData config;
+ auto cli = Catch::makeCommandLineParser(config);
+
+ SECTION("empty args don't cause a crash") {
+ auto result = cli.parse({""});
+ CHECK(result);
+ CHECK(config.processName == "");
+ }
+
+ SECTION("default - no arguments") {
+ auto result = cli.parse({"test"});
+ CHECK(result);
+ CHECK(config.processName == "test");
+ CHECK(config.shouldDebugBreak == false);
+ CHECK(config.abortAfter == -1);
+ CHECK(config.noThrow == false);
+ CHECK( config.reporterSpecifications.empty() );
+
+ Catch::Config cfg(config);
+ CHECK_FALSE(cfg.hasTestFilters());
+
+ // The Config is responsible for mixing in the default reporter
+ auto expectedReporter =
+#if defined( CATCH_CONFIG_DEFAULT_REPORTER )
+ CATCH_CONFIG_DEFAULT_REPORTER
+#else
+ "console"
+#endif
+ ;
+
+ CHECK( cfg.getReporterSpecs().size() == 1 );
+ CHECK( cfg.getReporterSpecs()[0] ==
+ Catch::ReporterSpec{ expectedReporter, {}, {}, {} } );
+ CHECK( cfg.getProcessedReporterSpecs().size() == 1 );
+ CHECK( cfg.getProcessedReporterSpecs()[0] ==
+ Catch::ProcessedReporterSpec{ expectedReporter,
+ std::string{},
+ Catch::ColourMode::PlatformDefault,
+ {} } );
+ }
+
+ SECTION("test lists") {
+ SECTION("Specify one test case using") {
+ auto result = cli.parse({"test", "test1"});
+ CHECK(result);
+
+ Catch::Config cfg(config);
+ REQUIRE(cfg.hasTestFilters());
+ REQUIRE(cfg.testSpec().matches(*fakeTestCase("notIncluded")) == false);
+ REQUIRE(cfg.testSpec().matches(*fakeTestCase("test1")));
+ }
+ SECTION("Specify one test case exclusion using exclude:") {
+ auto result = cli.parse({"test", "exclude:test1"});
+ CHECK(result);
+
+ Catch::Config cfg(config);
+ REQUIRE(cfg.hasTestFilters());
+ REQUIRE(cfg.testSpec().matches(*fakeTestCase("test1")) == false);
+ REQUIRE(cfg.testSpec().matches(*fakeTestCase("alwaysIncluded")));
+ }
+
+ SECTION("Specify one test case exclusion using ~") {
+ auto result = cli.parse({"test", "~test1"});
+ CHECK(result);
+
+ Catch::Config cfg(config);
+ REQUIRE(cfg.hasTestFilters());
+ REQUIRE(cfg.testSpec().matches(*fakeTestCase("test1")) == false);
+ REQUIRE(cfg.testSpec().matches(*fakeTestCase("alwaysIncluded")));
+ }
+
+ }
+
+ SECTION("reporter") {
+ using vec_Specs = std::vector<Catch::ReporterSpec>;
+ using namespace std::string_literals;
+ SECTION("-r/console") {
+ auto result = cli.parse({"test", "-r", "console"});
+ CAPTURE(result.errorMessage());
+ CHECK(result);
+
+ REQUIRE( config.reporterSpecifications ==
+ vec_Specs{ { "console", {}, {}, {} } } );
+ }
+ SECTION("-r/xml") {
+ auto result = cli.parse({"test", "-r", "xml"});
+ CAPTURE(result.errorMessage());
+ CHECK(result);
+
+ REQUIRE( config.reporterSpecifications ==
+ vec_Specs{ { "xml", {}, {}, {} } } );
+ }
+ SECTION("--reporter/junit") {
+ auto result = cli.parse({"test", "--reporter", "junit"});
+ CAPTURE(result.errorMessage());
+ CHECK(result);
+
+ REQUIRE( config.reporterSpecifications ==
+ vec_Specs{ { "junit", {}, {}, {} } } );
+ }
+ SECTION("must match one of the available ones") {
+ auto result = cli.parse({"test", "--reporter", "unsupported"});
+ CHECK(!result);
+
+ REQUIRE_THAT(result.errorMessage(), ContainsSubstring("Unrecognized reporter"));
+ }
+ SECTION("With output file") {
+ auto result = cli.parse({ "test", "-r", "console::out=out.txt" });
+ CAPTURE(result.errorMessage());
+ CHECK(result);
+ REQUIRE( config.reporterSpecifications ==
+ vec_Specs{ { "console", "out.txt"s, {}, {} } } );
+ }
+ SECTION("With Windows-like absolute path as output file") {
+ auto result = cli.parse({ "test", "-r", "console::out=C:\\Temp\\out.txt" });
+ CAPTURE(result.errorMessage());
+ CHECK(result);
+ REQUIRE( config.reporterSpecifications ==
+ vec_Specs{ { "console", "C:\\Temp\\out.txt"s, {}, {} } } );
+ }
+ SECTION("Multiple reporters") {
+ SECTION("All with output files") {
+ CHECK(cli.parse({ "test", "-r", "xml::out=output.xml", "-r", "junit::out=output-junit.xml" }));
+ REQUIRE( config.reporterSpecifications ==
+ vec_Specs{ { "xml", "output.xml"s, {}, {} },
+ { "junit", "output-junit.xml"s, {}, {} } } );
+ }
+ SECTION("Mixed output files and default output") {
+ CHECK(cli.parse({ "test", "-r", "xml::out=output.xml", "-r", "console" }));
+ REQUIRE( config.reporterSpecifications ==
+ vec_Specs{ { "xml", "output.xml"s, {}, {} },
+ { "console", {}, {}, {} } } );
+ }
+ SECTION("cannot have multiple reporters with default output") {
+ auto result = cli.parse({ "test", "-r", "console", "-r", "xml::out=output.xml", "-r", "junit" });
+ CHECK(!result);
+ REQUIRE_THAT(result.errorMessage(), ContainsSubstring("Only one reporter may have unspecified output file."));
+ }
+ }
+ }
+
+ SECTION("debugger") {
+ SECTION("-b") {
+ CHECK(cli.parse({"test", "-b"}));
+
+ REQUIRE(config.shouldDebugBreak == true);
+ }
+ SECTION("--break") {
+ CHECK(cli.parse({"test", "--break"}));
+
+ REQUIRE(config.shouldDebugBreak);
+ }
+ }
+
+
+ SECTION("abort") {
+ SECTION("-a aborts after first failure") {
+ CHECK(cli.parse({"test", "-a"}));
+
+ REQUIRE(config.abortAfter == 1);
+ }
+ SECTION("-x 2 aborts after two failures") {
+ CHECK(cli.parse({"test", "-x", "2"}));
+
+ REQUIRE(config.abortAfter == 2);
+ }
+ SECTION("-x must be numeric") {
+ auto result = cli.parse({"test", "-x", "oops"});
+ CHECK(!result);
+ REQUIRE_THAT(result.errorMessage(), ContainsSubstring("convert") && ContainsSubstring("oops"));
+ }
+
+ SECTION("wait-for-keypress") {
+ SECTION("Accepted options") {
+ using tuple_type = std::tuple<char const*, Catch::WaitForKeypress::When>;
+ auto input = GENERATE(table<char const*, Catch::WaitForKeypress::When>({
+ tuple_type{"never", Catch::WaitForKeypress::Never},
+ tuple_type{"start", Catch::WaitForKeypress::BeforeStart},
+ tuple_type{"exit", Catch::WaitForKeypress::BeforeExit},
+ tuple_type{"both", Catch::WaitForKeypress::BeforeStartAndExit},
+ }));
+ CHECK(cli.parse({"test", "--wait-for-keypress", std::get<0>(input)}));
+
+ REQUIRE(config.waitForKeypress == std::get<1>(input));
+ }
+
+ SECTION("invalid options are reported") {
+ auto result = cli.parse({"test", "--wait-for-keypress", "sometimes"});
+ CHECK(!result);
+
+#ifndef CATCH_CONFIG_DISABLE_MATCHERS
+ REQUIRE_THAT(result.errorMessage(), ContainsSubstring("never") && ContainsSubstring("both"));
+#endif
+ }
+ }
+ }
+
+ SECTION("nothrow") {
+ SECTION("-e") {
+ CHECK(cli.parse({"test", "-e"}));
+
+ REQUIRE(config.noThrow);
+ }
+ SECTION("--nothrow") {
+ CHECK(cli.parse({"test", "--nothrow"}));
+
+ REQUIRE(config.noThrow);
+ }
+ }
+
+ SECTION("output filename") {
+ SECTION("-o filename") {
+ CHECK(cli.parse({"test", "-o", "filename.ext"}));
+
+ REQUIRE(config.defaultOutputFilename == "filename.ext");
+ }
+ SECTION("--out") {
+ CHECK(cli.parse({"test", "--out", "filename.ext"}));
+
+ REQUIRE(config.defaultOutputFilename == "filename.ext");
+ }
+ }
+
+ SECTION("combinations") {
+ SECTION("Single character flags can be combined") {
+ CHECK(cli.parse({"test", "-abe"}));
+
+ CHECK(config.abortAfter == 1);
+ CHECK(config.shouldDebugBreak);
+ CHECK(config.noThrow == true);
+ }
+ }
+
+
+ SECTION( "use-colour") {
+
+ using Catch::ColourMode;
+
+ SECTION( "without option" ) {
+ CHECK(cli.parse({"test"}));
+
+ REQUIRE( config.defaultColourMode == ColourMode::PlatformDefault );
+ }
+
+ SECTION( "auto" ) {
+ CHECK( cli.parse( { "test", "--colour-mode", "default" } ) );
+
+ REQUIRE( config.defaultColourMode == ColourMode::PlatformDefault );
+ }
+
+ SECTION( "yes" ) {
+ CHECK(cli.parse({"test", "--colour-mode", "ansi"}));
+
+ REQUIRE( config.defaultColourMode == ColourMode::ANSI );
+ }
+
+ SECTION( "no" ) {
+ CHECK(cli.parse({"test", "--colour-mode", "none"}));
+
+ REQUIRE( config.defaultColourMode == ColourMode::None );
+ }
+
+ SECTION( "error" ) {
+ auto result = cli.parse({"test", "--colour-mode", "wrong"});
+ CHECK( !result );
+ CHECK_THAT( result.errorMessage(), ContainsSubstring( "colour mode must be one of" ) );
+ }
+ }
+
+ SECTION("Benchmark options") {
+ SECTION("samples") {
+ CHECK(cli.parse({ "test", "--benchmark-samples=200" }));
+
+ REQUIRE(config.benchmarkSamples == 200);
+ }
+
+ SECTION("resamples") {
+ CHECK(cli.parse({ "test", "--benchmark-resamples=20000" }));
+
+ REQUIRE(config.benchmarkResamples == 20000);
+ }
+
+ SECTION("confidence-interval") {
+ CHECK(cli.parse({ "test", "--benchmark-confidence-interval=0.99" }));
+
+ REQUIRE(config.benchmarkConfidenceInterval == Catch::Approx(0.99));
+ }
+
+ SECTION("no-analysis") {
+ CHECK(cli.parse({ "test", "--benchmark-no-analysis" }));
+
+ REQUIRE(config.benchmarkNoAnalysis);
+ }
+
+ SECTION("warmup-time") {
+ CHECK(cli.parse({ "test", "--benchmark-warmup-time=10" }));
+
+ REQUIRE(config.benchmarkWarmupTime == 10);
+ }
+ }
+}
+
+TEST_CASE("Parsing sharding-related cli flags", "[sharding]") {
+ using namespace Catch::Matchers;
+
+ Catch::ConfigData config;
+ auto cli = Catch::makeCommandLineParser(config);
+
+ SECTION("shard-count") {
+ CHECK(cli.parse({ "test", "--shard-count=8" }));
+
+ REQUIRE(config.shardCount == 8);
+ }
+
+ SECTION("Negative shard count reports error") {
+ auto result = cli.parse({ "test", "--shard-count=-1" });
+
+ CHECK_FALSE(result);
+ REQUIRE_THAT(
+ result.errorMessage(),
+ ContainsSubstring( "Could not parse '-1' as shard count" ) );
+ }
+
+ SECTION("Zero shard count reports error") {
+ auto result = cli.parse({ "test", "--shard-count=0" });
+
+ CHECK_FALSE(result);
+ REQUIRE_THAT(
+ result.errorMessage(),
+ ContainsSubstring( "Shard count must be positive" ) );
+ }
+
+ SECTION("shard-index") {
+ CHECK(cli.parse({ "test", "--shard-index=2" }));
+
+ REQUIRE(config.shardIndex == 2);
+ }
+
+ SECTION("Negative shard index reports error") {
+ auto result = cli.parse({ "test", "--shard-index=-12" });
+
+ CHECK_FALSE(result);
+ REQUIRE_THAT(
+ result.errorMessage(),
+ ContainsSubstring( "Could not parse '-12' as shard index" ) );
+ }
+
+ SECTION("Shard index 0 is accepted") {
+ CHECK(cli.parse({ "test", "--shard-index=0" }));
+
+ REQUIRE(config.shardIndex == 0);
+ }
+}
+
+TEST_CASE( "Parsing warnings", "[cli][warnings]" ) {
+ using Catch::WarnAbout;
+
+ Catch::ConfigData config;
+ auto cli = Catch::makeCommandLineParser( config );
+
+ SECTION( "NoAssertions" ) {
+ REQUIRE(cli.parse( { "test", "-w", "NoAssertions" } ));
+ REQUIRE( config.warnings == WarnAbout::NoAssertions );
+ }
+ SECTION( "NoTests is no longer supported" ) {
+ REQUIRE_FALSE(cli.parse( { "test", "-w", "NoTests" } ));
+ }
+ SECTION( "Combining multiple warnings" ) {
+ REQUIRE( cli.parse( { "test",
+ "--warn", "NoAssertions",
+ "--warn", "UnmatchedTestSpec" } ) );
+
+ REQUIRE( config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec ) );
+ }
+}
+
+TEST_CASE("Test with special, characters \"in name", "[cli][regression]") {
+ // This test case succeeds if we can invoke it from the CLI
+ SUCCEED();
+}
+
+TEST_CASE("Various suspicious reporter specs are rejected",
+ "[cli][reporter-spec][approvals]") {
+ Catch::ConfigData config;
+ auto cli = Catch::makeCommandLineParser( config );
+
+ auto spec = GENERATE( as<std::string>{},
+ "",
+ "::console",
+ "console::",
+ "console::some-file::",
+ "::console::some-file::" );
+ CAPTURE( spec );
+
+ auto result = cli.parse( { "test", "--reporter", spec } );
+ REQUIRE_FALSE( result );
+}
+
+TEST_CASE("Win32 colour implementation is compile-time optional",
+ "[approvals][cli][colours]") {
+ Catch::ConfigData config;
+ auto cli = Catch::makeCommandLineParser( config );
+
+ auto result = cli.parse( { "test", "--colour-mode", "win32" } );
+
+#if defined( CATCH_CONFIG_COLOUR_WIN32 )
+ REQUIRE( result );
+#else
+ REQUIRE_FALSE( result );
+#endif
+}
+
+TEST_CASE( "Parse rng seed in different formats", "[approvals][cli][rng-seed]" ) {
+ Catch::ConfigData config;
+ auto cli = Catch::makeCommandLineParser( config );
+
+ SECTION("well formed cases") {
+ char const* seed_string;
+ uint32_t seed_value;
+ // GCC-5 workaround
+ using gen_type = std::tuple<char const*, uint32_t>;
+ std::tie( seed_string, seed_value ) = GENERATE( table<char const*, uint32_t>({
+ gen_type{ "0xBEEF", 0xBEEF },
+ gen_type{ "12345678", 12345678 }
+ } ) );
+ CAPTURE( seed_string );
+
+ auto result = cli.parse( { "tests", "--rng-seed", seed_string } );
+
+ REQUIRE( result );
+ REQUIRE( config.rngSeed == seed_value );
+ }
+ SECTION( "Error cases" ) {
+ auto seed_string =
+ GENERATE( "0xSEED", "999999999999", "08888", "BEEF", "123 456" );
+ CAPTURE( seed_string );
+ REQUIRE_FALSE( cli.parse( { "tests", "--rng-seed", seed_string } ) );
+ }
+}
diff --git a/tests/SelfTest/IntrospectiveTests/CmdLineHelpers.tests.cpp b/tests/SelfTest/IntrospectiveTests/CmdLineHelpers.tests.cpp
new file mode 100644
index 0000000..4df8ecf
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/CmdLineHelpers.tests.cpp
@@ -0,0 +1,111 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/internal/catch_reporter_spec_parser.hpp>
+#include <catch2/matchers/catch_matchers_vector.hpp>
+#include <catch2/interfaces/catch_interfaces_config.hpp>
+
+TEST_CASE("Reporter spec splitting", "[reporter-spec][cli][approvals]") {
+ using Catch::Detail::splitReporterSpec;
+ using Catch::Matchers::Equals;
+ using namespace std::string_literals;
+
+ SECTION("Various edge cases") {
+ REQUIRE_THAT( splitReporterSpec( "" ),
+ Equals( std::vector<std::string>{ ""s } ) );
+ REQUIRE_THAT( splitReporterSpec( "::" ),
+ Equals( std::vector<std::string>{ "", "" } ) );
+ REQUIRE_THAT( splitReporterSpec( "::rep" ),
+ Equals( std::vector<std::string>{ "", "rep" } ) );
+ REQUIRE_THAT( splitReporterSpec( "rep::" ),
+ Equals( std::vector<std::string>{ "rep", "" } ) );
+
+ }
+
+ SECTION("Validish specs") {
+ REQUIRE_THAT( splitReporterSpec( "newReporter" ),
+ Equals( std::vector<std::string>{ "newReporter"s } ) );
+ REQUIRE_THAT(
+ splitReporterSpec( "foo-reporter::key1=value1::key2=value with "
+ "space::key with space=some-value" ),
+ Equals(
+ std::vector<std::string>{ "foo-reporter"s,
+ "key1=value1"s,
+ "key2=value with space"s,
+ "key with space=some-value"s } ) );
+ REQUIRE_THAT(
+ splitReporterSpec( "spaced reporter name::key:key=value:value" ),
+ Equals( std::vector<std::string>{ "spaced reporter name"s,
+ "key:key=value:value"s } ) );
+ }
+}
+
+TEST_CASE( "Parsing colour mode", "[cli][colour][approvals]" ) {
+ using Catch::Detail::stringToColourMode;
+ using Catch::ColourMode;
+ SECTION("Valid strings") {
+ REQUIRE( stringToColourMode( "none" ) == ColourMode::None );
+ REQUIRE( stringToColourMode( "ansi" ) == ColourMode::ANSI );
+ REQUIRE( stringToColourMode( "win32" ) == ColourMode::Win32 );
+ REQUIRE( stringToColourMode( "default" ) ==
+ ColourMode::PlatformDefault );
+ }
+ SECTION("Wrong strings") {
+ REQUIRE_FALSE( stringToColourMode( "NONE" ) );
+ REQUIRE_FALSE( stringToColourMode( "-" ) );
+ REQUIRE_FALSE( stringToColourMode( "asdbjsdb kasbd" ) );
+ }
+}
+
+
+TEST_CASE("Parsing reporter specs", "[cli][reporter-spec][approvals]") {
+ using Catch::parseReporterSpec;
+ using Catch::ReporterSpec;
+ using namespace std::string_literals;
+
+ SECTION( "Correct specs" ) {
+ REQUIRE( parseReporterSpec( "someReporter" ) ==
+ ReporterSpec( "someReporter"s, {}, {}, {} ) );
+ REQUIRE( parseReporterSpec( "otherReporter::Xk=v::out=c:\\blah" ) ==
+ ReporterSpec(
+ "otherReporter"s, "c:\\blah"s, {}, { { "Xk"s, "v"s } } ) );
+ REQUIRE( parseReporterSpec( "diffReporter::Xk1=v1::Xk2==v2" ) ==
+ ReporterSpec( "diffReporter",
+ {},
+ {},
+ { { "Xk1"s, "v1"s }, { "Xk2"s, "=v2"s } } ) );
+ REQUIRE( parseReporterSpec(
+ "Foo:bar:reporter::colour-mode=ansi::Xk 1=v 1::Xk2=v:3" ) ==
+ ReporterSpec( "Foo:bar:reporter",
+ {},
+ Catch::ColourMode::ANSI,
+ { { "Xk 1"s, "v 1"s }, { "Xk2"s, "v:3"s } } ) );
+ }
+
+ SECTION( "Bad specs" ) {
+ REQUIRE_FALSE( parseReporterSpec( "::" ) );
+ // Unknown Catch2 arg (should be "out")
+ REQUIRE_FALSE( parseReporterSpec( "reporter::output=filename" ) );
+ // Wrong colour spec
+ REQUIRE_FALSE( parseReporterSpec( "reporter::colour-mode=custom" ) );
+ // Duplicated colour spec
+ REQUIRE_FALSE( parseReporterSpec( "reporter::colour-mode=ansi::colour-mode=ansi" ) );
+ // Duplicated out arg
+ REQUIRE_FALSE( parseReporterSpec( "reporter::out=f.txt::out=z.txt" ) );
+ // Duplicated custom arg
+ REQUIRE_FALSE( parseReporterSpec( "reporter::Xa=foo::Xa=bar" ) );
+ // Empty key
+ REQUIRE_FALSE( parseReporterSpec( "reporter::X=foo" ) );
+ REQUIRE_FALSE( parseReporterSpec( "reporter::=foo" ) );
+ // Empty value
+ REQUIRE_FALSE( parseReporterSpec( "reporter::Xa=" ) );
+ // non-key value later field
+ REQUIRE_FALSE( parseReporterSpec( "reporter::Xab" ) );
+ }
+}
diff --git a/tests/SelfTest/IntrospectiveTests/ColourImpl.tests.cpp b/tests/SelfTest/IntrospectiveTests/ColourImpl.tests.cpp
new file mode 100644
index 0000000..615fda1
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/ColourImpl.tests.cpp
@@ -0,0 +1,64 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/internal/catch_console_colour.hpp>
+#include <catch2/internal/catch_istream.hpp>
+
+#include <sstream>
+
+namespace {
+ class TestColourImpl : public Catch::ColourImpl {
+ using Catch::ColourImpl::ColourImpl;
+ // Inherited via ColourImpl
+ void use( Catch::Colour::Code colourCode ) const override {
+ m_stream->stream() << "Using code: " << colourCode << '\n';
+ }
+ };
+
+ class TestStringStream : public Catch::IStream {
+ std::stringstream m_stream;
+ public:
+ std::ostream& stream() override {
+ return m_stream;
+ }
+
+ std::string str() const { return m_stream.str(); }
+ };
+}
+
+TEST_CASE("ColourGuard behaviour", "[console-colours]") {
+ TestStringStream streamWrapper;
+ TestColourImpl colourImpl( &streamWrapper );
+ auto& stream = streamWrapper.stream();
+
+ SECTION("ColourGuard is disengaged by default") {
+ { auto guard = colourImpl.guardColour( Catch::Colour::Red ); }
+
+ REQUIRE( streamWrapper.str().empty() );
+ }
+
+ SECTION("ColourGuard is engaged by op<<") {
+ stream << "1\n" << colourImpl.guardColour( Catch::Colour::Red ) << "2\n";
+ stream << "3\n";
+
+ REQUIRE( streamWrapper.str() == "1\nUsing code: 2\n2\nUsing code: 0\n3\n" );
+ }
+
+ SECTION("ColourGuard can be engaged explicitly") {
+ {
+ auto guard =
+ colourImpl.guardColour( Catch::Colour::Red ).engage( stream );
+ stream << "A\n"
+ << "B\n";
+ }
+ stream << "C\n";
+ REQUIRE( streamWrapper.str() ==
+ "Using code: 2\nA\nB\nUsing code: 0\nC\n" );
+ }
+}
diff --git a/tests/SelfTest/IntrospectiveTests/Details.tests.cpp b/tests/SelfTest/IntrospectiveTests/Details.tests.cpp
new file mode 100644
index 0000000..5566bb5
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/Details.tests.cpp
@@ -0,0 +1,172 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/internal/catch_enforce.hpp>
+#include <catch2/internal/catch_case_insensitive_comparisons.hpp>
+#include <catch2/internal/catch_optional.hpp>
+
+#include <helpers/type_with_lit_0_comparisons.hpp>
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable:4702) // unreachable code in the macro expansions
+#endif
+
+TEST_CASE("Check that our error handling macros throw the right exceptions", "[!throws][internals][approvals]") {
+ REQUIRE_THROWS_AS(CATCH_INTERNAL_ERROR(""), std::logic_error);
+ REQUIRE_THROWS_AS(CATCH_ERROR(""), std::domain_error);
+ REQUIRE_THROWS_AS(CATCH_RUNTIME_ERROR(""), std::runtime_error);
+ REQUIRE_THROWS_AS([](){CATCH_ENFORCE(false, "");}(), std::domain_error);
+ REQUIRE_NOTHROW([](){CATCH_ENFORCE(true, "");}());
+}
+
+#if defined(_MSC_VER)
+#pragma warning(pop) // unreachable code in the macro expansions
+#endif
+
+TEST_CASE("CaseInsensitiveLess is case insensitive", "[comparisons][string-case]") {
+ Catch::Detail::CaseInsensitiveLess lt;
+ SECTION( "Degenerate cases" ) {
+ REQUIRE( lt( "", "a" ) );
+ REQUIRE_FALSE( lt( "a", "a" ) );
+ REQUIRE_FALSE( lt( "", "" ) );
+ }
+ SECTION("Plain comparisons") {
+ REQUIRE( lt( "a", "b" ) );
+ REQUIRE( lt( "a", "B" ) );
+ REQUIRE( lt( "A", "b" ) );
+ REQUIRE( lt( "A", "B" ) );
+ }
+}
+
+TEST_CASE( "CaseInsensitiveEqualsTo is case insensitive",
+ "[comparisons][string-case]" ) {
+ Catch::Detail::CaseInsensitiveEqualTo eq;
+ SECTION( "Degenerate cases" ) {
+ REQUIRE( eq( "", "" ) );
+ REQUIRE_FALSE( eq( "", "a" ) );
+ }
+ SECTION( "Plain comparisons" ) {
+ REQUIRE( eq( "a", "a" ) );
+ REQUIRE( eq( "a", "A" ) );
+ REQUIRE( eq( "A", "a" ) );
+ REQUIRE( eq( "A", "A" ) );
+ REQUIRE_FALSE( eq( "a", "b" ) );
+ REQUIRE_FALSE( eq( "a", "B" ) );
+ }
+}
+
+TEST_CASE("Optional comparison ops", "[optional][approvals]") {
+ using Catch::Optional;
+
+ Optional<int> a, b;
+
+ SECTION( "Empty optionals are equal" ) {
+ REQUIRE( a == b );
+ REQUIRE_FALSE( a != b );
+ }
+ SECTION( "Empty and non-empty optionals are never equal" ) {
+ a = 1;
+ REQUIRE_FALSE( a == b );
+ REQUIRE( a != b );
+ }
+ SECTION(
+ "non-empty optionals are equal if the contained elements are equal") {
+ a = 1;
+ b = 2;
+ REQUIRE( a != b );
+ REQUIRE_FALSE( a == b );
+
+ a = 2;
+ REQUIRE( a == b );
+ REQUIRE_FALSE( a != b );
+ }
+}
+
+namespace {
+ struct MoveChecker {
+ bool has_moved = false;
+ MoveChecker() = default;
+ MoveChecker( MoveChecker const& rhs ) = default;
+ MoveChecker& operator=( MoveChecker const& rhs ) = default;
+ MoveChecker( MoveChecker&& rhs ) noexcept { rhs.has_moved = true; }
+ MoveChecker& operator=( MoveChecker&& rhs ) noexcept {
+ rhs.has_moved = true;
+ return *this;
+ }
+ };
+}
+
+TEST_CASE( "Optional supports move ops", "[optional][approvals]" ) {
+ using Catch::Optional;
+ MoveChecker a;
+ Optional<MoveChecker> opt_A( a );
+ REQUIRE_FALSE( a.has_moved );
+ REQUIRE_FALSE( opt_A->has_moved );
+
+ SECTION( "Move construction from element" ) {
+ Optional<MoveChecker> opt_B( CATCH_MOVE( a ) );
+ REQUIRE( a.has_moved );
+ }
+ SECTION( "Move assignment from element" ) {
+ opt_A = CATCH_MOVE( a );
+ REQUIRE( a.has_moved );
+ }
+ SECTION( "Move construction from optional" ) {
+ Optional<MoveChecker> opt_B( CATCH_MOVE( opt_A ) );
+ REQUIRE( opt_A->has_moved ); // NOLINT(clang-analyzer-cplusplus.Move)
+ }
+ SECTION( "Move assignment from optional" ) {
+ Optional<MoveChecker> opt_B( opt_A );
+ REQUIRE_FALSE( opt_A->has_moved );
+ opt_B = CATCH_MOVE( opt_A );
+ REQUIRE( opt_A->has_moved ); // NOLINT(clang-analyzer-cplusplus.Move)
+ }
+}
+
+TEST_CASE( "Decomposer checks that the argument is 0 when handling "
+ "only-0-comparable types",
+ "[decomposition][approvals]" ) {
+ TypeWithLit0Comparisons t{};
+
+ CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
+ CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS
+
+ REQUIRE_THROWS( Catch::Decomposer{} <= t == 42 );
+ REQUIRE_THROWS( Catch::Decomposer{} <= 42 == t );
+ REQUIRE_NOTHROW( Catch::Decomposer{} <= t == 0 );
+ REQUIRE_NOTHROW( Catch::Decomposer{} <= 0 == t );
+
+ REQUIRE_THROWS( Catch::Decomposer{} <= t != 42 );
+ REQUIRE_THROWS( Catch::Decomposer{} <= 42 != t );
+ REQUIRE_NOTHROW( Catch::Decomposer{} <= t != 0 );
+ REQUIRE_NOTHROW( Catch::Decomposer{} <= 0 != t );
+
+ REQUIRE_THROWS( Catch::Decomposer{} <= t < 42 );
+ REQUIRE_THROWS( Catch::Decomposer{} <= 42 < t );
+ REQUIRE_NOTHROW( Catch::Decomposer{} <= t < 0 );
+ REQUIRE_NOTHROW( Catch::Decomposer{} <= 0 < t );
+
+ REQUIRE_THROWS( Catch::Decomposer{} <= t <= 42 );
+ REQUIRE_THROWS( Catch::Decomposer{} <= 42 <= t );
+ REQUIRE_NOTHROW( Catch::Decomposer{} <= t <= 0 );
+ REQUIRE_NOTHROW( Catch::Decomposer{} <= 0 <= t );
+
+ REQUIRE_THROWS( Catch::Decomposer{} <= t > 42 );
+ REQUIRE_THROWS( Catch::Decomposer{} <= 42 > t );
+ REQUIRE_NOTHROW( Catch::Decomposer{} <= t > 0 );
+ REQUIRE_NOTHROW( Catch::Decomposer{} <= 0 > t );
+
+ REQUIRE_THROWS( Catch::Decomposer{} <= t >= 42 );
+ REQUIRE_THROWS( Catch::Decomposer{} <= 42 >= t );
+ REQUIRE_NOTHROW( Catch::Decomposer{} <= t >= 0 );
+ REQUIRE_NOTHROW( Catch::Decomposer{} <= 0 >= t );
+
+ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
+}
diff --git a/tests/SelfTest/IntrospectiveTests/FloatingPoint.tests.cpp b/tests/SelfTest/IntrospectiveTests/FloatingPoint.tests.cpp
new file mode 100644
index 0000000..d218170
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/FloatingPoint.tests.cpp
@@ -0,0 +1,139 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/catch_template_test_macros.hpp>
+#include <catch2/internal/catch_floating_point_helpers.hpp>
+#include <catch2/internal/catch_random_floating_point_helpers.hpp>
+
+#include <limits>
+
+TEST_CASE("convertToBits", "[floating-point][conversion]") {
+ using Catch::Detail::convertToBits;
+
+ CHECK( convertToBits( 0.f ) == 0 );
+ CHECK( convertToBits( -0.f ) == ( 1ULL << 31 ) );
+ CHECK( convertToBits( 0. ) == 0 );
+ CHECK( convertToBits( -0. ) == ( 1ULL << 63 ) );
+ CHECK( convertToBits( std::numeric_limits<float>::denorm_min() ) == 1 );
+ CHECK( convertToBits( std::numeric_limits<double>::denorm_min() ) == 1 );
+}
+
+TEMPLATE_TEST_CASE("type-shared ulpDistance tests", "[floating-point][ulp][approvals]", float, double) {
+ using FP = TestType;
+ using Catch::ulpDistance;
+
+ // Distance between zeros is zero
+ CHECK( ulpDistance( FP{}, FP{} ) == 0 );
+ CHECK( ulpDistance( FP{}, -FP{} ) == 0 );
+ CHECK( ulpDistance( -FP{}, -FP{} ) == 0 );
+
+ // Distance between same-sign infinities is zero
+ static constexpr FP infinity = std::numeric_limits<FP>::infinity();
+ CHECK( ulpDistance( infinity, infinity ) == 0 );
+ CHECK( ulpDistance( -infinity, -infinity ) == 0 );
+
+ // Distance between max-finite-val and same sign infinity is 1
+ static constexpr FP max_finite = std::numeric_limits<FP>::max();
+ CHECK( ulpDistance( max_finite, infinity ) == 1 );
+ CHECK( ulpDistance( -max_finite, -infinity ) == 1 );
+
+ // Distance between X and 0 is half of distance between X and -X
+ CHECK( ulpDistance( -infinity, infinity ) ==
+ 2 * ulpDistance( infinity, FP{} ) );
+ CHECK( 2 * ulpDistance( FP{ -2. }, FP{} ) ==
+ ulpDistance( FP{ -2. }, FP{ 2. } ) );
+ CHECK( 2 * ulpDistance( FP{ 2. }, FP{} ) ==
+ ulpDistance( FP{ -2. }, FP{ 2. } ) );
+
+ // Denorms are supported
+ CHECK( ulpDistance( std::numeric_limits<FP>::denorm_min(), FP{} ) == 1 );
+ CHECK( ulpDistance( std::numeric_limits<FP>::denorm_min(), -FP{} ) == 1 );
+ CHECK( ulpDistance( -std::numeric_limits<FP>::denorm_min(), FP{} ) == 1 );
+ CHECK( ulpDistance( -std::numeric_limits<FP>::denorm_min(), -FP{} ) == 1 );
+ CHECK( ulpDistance( std::numeric_limits<FP>::denorm_min(),
+ -std::numeric_limits<FP>::denorm_min() ) == 2 );
+
+ // Machine epsilon
+ CHECK( ulpDistance( FP{ 1. },
+ FP{ 1. } + std::numeric_limits<FP>::epsilon() ) == 1 );
+ CHECK( ulpDistance( -FP{ 1. },
+ -FP{ 1. } - std::numeric_limits<FP>::epsilon() ) == 1 );
+}
+
+TEST_CASE("UlpDistance", "[floating-point][ulp][approvals]") {
+ using Catch::ulpDistance;
+
+ CHECK( ulpDistance( 1., 2. ) == 0x10'00'00'00'00'00'00 );
+ CHECK( ulpDistance( -2., 2. ) == 0x80'00'00'00'00'00'00'00 );
+ CHECK( ulpDistance( 1.f, 2.f ) == 0x80'00'00 );
+ CHECK( ulpDistance( -2.f, 2.f ) == 0x80'00'00'00 );
+}
+
+
+
+TEMPLATE_TEST_CASE("gamma", "[approvals][floating-point][ulp][gamma]", float, double) {
+ using Catch::Detail::gamma;
+ using Catch::Detail::directCompare;
+
+ // We need to butcher the equal tests with the directCompare helper,
+ // because the Wfloat-equal triggers in decomposer rather than here,
+ // so we cannot locally disable it. Goddamn GCC.
+ CHECK( directCompare( gamma( TestType( -1. ), TestType( 1. ) ),
+ gamma( TestType( 0.2332 ), TestType( 1.0 ) ) ) );
+ CHECK( directCompare( gamma( TestType( -2. ), TestType( 0 ) ),
+ gamma( TestType( 1. ), TestType( 1.5 ) ) ) );
+ CHECK( gamma( TestType( 0. ), TestType( 1.0 ) ) <
+ gamma( TestType( 1.0 ), TestType( 1.5 ) ) );
+ CHECK( gamma( TestType( 0 ), TestType( 1. ) ) <
+ std::numeric_limits<TestType>::epsilon() );
+ CHECK( gamma( TestType( -1. ), TestType( -0. ) ) <
+ std::numeric_limits<TestType>::epsilon() );
+ CHECK( directCompare( gamma( TestType( 1. ), TestType( 2. ) ),
+ std::numeric_limits<TestType>::epsilon() ) );
+ CHECK( directCompare( gamma( TestType( -2. ), TestType( -1. ) ),
+ std::numeric_limits<TestType>::epsilon() ) );
+}
+
+TEMPLATE_TEST_CASE("count_equidistant_floats",
+ "[approvals][floating-point][distance]",
+ float,
+ double) {
+ using Catch::Detail::count_equidistant_floats;
+ auto count_steps = []( TestType a, TestType b ) {
+ return count_equidistant_floats( a, b, Catch::Detail::gamma( a, b ) );
+ };
+
+ CHECK( count_steps( TestType( -1. ), TestType( 1. ) ) ==
+ 2 * count_steps( TestType( 0. ), TestType( 1. ) ) );
+}
+
+TEST_CASE( "count_equidistant_floats",
+ "[approvals][floating-point][distance]" ) {
+ using Catch::Detail::count_equidistant_floats;
+ auto count_floats_with_scaled_ulp = []( auto a, auto b ) {
+ return count_equidistant_floats( a, b, Catch::Detail::gamma( a, b ) );
+ };
+
+ CHECK( count_floats_with_scaled_ulp( 1., 1.5 ) == 1ull << 51 );
+ CHECK( count_floats_with_scaled_ulp( 1.25, 1.5 ) == 1ull << 50 );
+ CHECK( count_floats_with_scaled_ulp( 1.f, 1.5f ) == 1 << 22 );
+ CHECK( count_floats_with_scaled_ulp( -std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max() ) ==
+ 33554430 ); // (1 << 25) - 2 due to not including infinities
+ CHECK( count_floats_with_scaled_ulp( -std::numeric_limits<double>::max(),
+ std::numeric_limits<double>::max() ) ==
+ 18014398509481982 ); // (1 << 54) - 2 due to not including infinities
+
+ STATIC_REQUIRE( std::is_same<std::uint64_t,
+ decltype( count_floats_with_scaled_ulp(
+ 0., 1. ) )>::value );
+ STATIC_REQUIRE( std::is_same<std::uint32_t,
+ decltype( count_floats_with_scaled_ulp(
+ 0.f, 1.f ) )>::value );
+}
diff --git a/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp b/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp
new file mode 100644
index 0000000..14c9011
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp
@@ -0,0 +1,575 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#if defined( __GNUC__ ) || defined( __clang__ )
+# pragma GCC diagnostic ignored "-Wfloat-equal"
+#endif
+
+#include <helpers/range_test_helpers.hpp>
+
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/generators/catch_generator_exception.hpp>
+#include <catch2/generators/catch_generators_adapters.hpp>
+#include <catch2/generators/catch_generators_random.hpp>
+#include <catch2/generators/catch_generators_range.hpp>
+
+// Tests of generator implementation details
+TEST_CASE("Generators internals", "[generators][internals]") {
+ using namespace Catch::Generators;
+
+ SECTION("Single value") {
+ auto gen = value(123);
+ REQUIRE(gen.get() == 123);
+ REQUIRE_FALSE(gen.next());
+ }
+ SECTION("Preset values") {
+ auto gen = values({ 1, 3, 5 });
+ REQUIRE(gen.get() == 1);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 3);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 5);
+ REQUIRE_FALSE(gen.next());
+ }
+ SECTION("Generator combinator") {
+ auto gen = makeGenerators(1, 5, values({ 2, 4 }), 0);
+ REQUIRE(gen.get() == 1);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 5);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 2);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 4);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 0);
+ REQUIRE_FALSE(gen.next());
+ }
+ SECTION("Explicitly typed generator sequence") {
+ auto gen = makeGenerators(as<std::string>{}, "aa", "bb", "cc");
+ // This just checks that the type is std::string:
+ REQUIRE(gen.get().size() == 2);
+ // Iterate over the generator
+ REQUIRE(gen.get() == "aa");
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == "bb");
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == "cc");
+ REQUIRE_FALSE(gen.next());
+ }
+ SECTION("Filter generator") {
+ // Normal usage
+ SECTION("Simple filtering") {
+ auto gen = filter([](int i) { return i != 2; }, values({ 2, 1, 2, 3, 2, 2 }));
+ REQUIRE(gen.get() == 1);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 3);
+ REQUIRE_FALSE(gen.next());
+ }
+ SECTION("Filter out multiple elements at the start and end") {
+ auto gen = filter([](int i) { return i != 2; }, values({ 2, 2, 1, 3, 2, 2 }));
+ REQUIRE(gen.get() == 1);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 3);
+ REQUIRE_FALSE(gen.next());
+ }
+
+ SECTION("Throws on construction if it can't get initial element") {
+ REQUIRE_THROWS_AS(filter([](int) { return false; }, value(1)), Catch::GeneratorException);
+ REQUIRE_THROWS_AS(
+ filter([](int) { return false; }, values({ 1, 2, 3 })),
+ Catch::GeneratorException);
+ }
+ }
+ SECTION("Take generator") {
+ SECTION("Take less") {
+ auto gen = take(2, values({ 1, 2, 3 }));
+ REQUIRE(gen.get() == 1);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 2);
+ REQUIRE_FALSE(gen.next());
+ }
+ SECTION("Take more") {
+ auto gen = take(2, value(1));
+ REQUIRE(gen.get() == 1);
+ REQUIRE_FALSE(gen.next());
+ }
+ }
+ SECTION("Map with explicit return type") {
+ auto gen = map<double>([] (int i) {return 2.0 * i; }, values({ 1, 2, 3 }));
+ REQUIRE(gen.get() == 2.0);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 4.0);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 6.0);
+ REQUIRE_FALSE(gen.next());
+ }
+ SECTION("Map with deduced return type") {
+ auto gen = map([] (int i) {return 2.0 * i; }, values({ 1, 2, 3 }));
+ REQUIRE(gen.get() == 2.0);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 4.0);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 6.0);
+ REQUIRE_FALSE(gen.next());
+ }
+ SECTION("Repeat") {
+ SECTION("Singular repeat") {
+ auto gen = repeat(1, value(3));
+ REQUIRE(gen.get() == 3);
+ REQUIRE_FALSE(gen.next());
+ }
+ SECTION("Actual repeat") {
+ auto gen = repeat(2, values({ 1, 2, 3 }));
+ REQUIRE(gen.get() == 1);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 2);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 3);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 1);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 2);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 3);
+ REQUIRE_FALSE(gen.next());
+ }
+ }
+ SECTION("Range") {
+ SECTION("Positive auto step") {
+ SECTION("Integer") {
+ auto gen = range(-2, 2);
+ REQUIRE(gen.get() == -2);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == -1);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 0);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 1);
+ REQUIRE_FALSE(gen.next());
+ }
+ }
+ SECTION("Negative auto step") {
+ SECTION("Integer") {
+ auto gen = range(2, -2);
+ REQUIRE(gen.get() == 2);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 1);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 0);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == -1);
+ REQUIRE_FALSE(gen.next());
+ }
+ }
+ SECTION("Positive manual step") {
+ SECTION("Integer") {
+ SECTION("Exact") {
+ auto gen = range(-7, 5, 3);
+ REQUIRE(gen.get() == -7);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == -4);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == -1);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 2);
+ REQUIRE_FALSE(gen.next());
+ }
+ SECTION("Slightly over end") {
+ auto gen = range(-7, 4, 3);
+ REQUIRE(gen.get() == -7);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == -4);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == -1);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 2);
+ REQUIRE_FALSE(gen.next());
+ }
+ SECTION("Slightly under end") {
+ auto gen = range(-7, 6, 3);
+ REQUIRE(gen.get() == -7);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == -4);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == -1);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 2);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 5);
+ REQUIRE_FALSE(gen.next());
+ }
+ }
+
+ SECTION("Floating Point") {
+ using Catch::Approx;
+ SECTION("Exact") {
+ const auto rangeStart = -1.;
+ const auto rangeEnd = 1.;
+ const auto step = .1;
+
+ auto gen = range(rangeStart, rangeEnd, step);
+ auto expected = rangeStart;
+ while( (rangeEnd - expected) > step ) {
+ INFO( "Current expected value is " << expected );
+ REQUIRE(gen.get() == Approx(expected));
+ REQUIRE(gen.next());
+
+ expected += step;
+ }
+ REQUIRE(gen.get() == Approx( rangeEnd ) );
+ REQUIRE_FALSE(gen.next());
+ }
+ SECTION("Slightly over end") {
+ const auto rangeStart = -1.;
+ const auto rangeEnd = 1.;
+ const auto step = .3;
+
+ auto gen = range(rangeStart, rangeEnd, step);
+ auto expected = rangeStart;
+ while( (rangeEnd - expected) > step ) {
+ INFO( "Current expected value is " << expected );
+ REQUIRE(gen.get() == Approx(expected));
+ REQUIRE(gen.next());
+
+ expected += step;
+ }
+ REQUIRE_FALSE(gen.next());
+ }
+ SECTION("Slightly under end") {
+ const auto rangeStart = -1.;
+ const auto rangeEnd = .9;
+ const auto step = .3;
+
+ auto gen = range(rangeStart, rangeEnd, step);
+ auto expected = rangeStart;
+ while( (rangeEnd - expected) > step ) {
+ INFO( "Current expected value is " << expected );
+ REQUIRE(gen.get() == Approx(expected));
+ REQUIRE(gen.next());
+
+ expected += step;
+ }
+ REQUIRE_FALSE(gen.next());
+ }
+ }
+ }
+ SECTION("Negative manual step") {
+ SECTION("Integer") {
+ SECTION("Exact") {
+ auto gen = range(5, -7, -3);
+ REQUIRE(gen.get() == 5);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 2);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == -1);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == -4);
+ REQUIRE_FALSE(gen.next());
+ }
+ SECTION("Slightly over end") {
+ auto gen = range(5, -6, -3);
+ REQUIRE(gen.get() == 5);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 2);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == -1);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == -4);
+ REQUIRE_FALSE(gen.next());
+ }
+ SECTION("Slightly under end") {
+ auto gen = range(5, -8, -3);
+ REQUIRE(gen.get() == 5);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == 2);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == -1);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == -4);
+ REQUIRE(gen.next());
+ REQUIRE(gen.get() == -7);
+ REQUIRE_FALSE(gen.next());
+ }
+ }
+ }
+ }
+
+}
+
+
+// todo: uncopyable type used in a generator
+// idea: uncopyable tag type for a stupid generator
+
+namespace {
+struct non_copyable {
+ non_copyable() = default;
+ non_copyable(non_copyable const&) = delete;
+ non_copyable& operator=(non_copyable const&) = delete;
+ int value = -1;
+};
+
+// This class shows how to implement a simple generator for Catch tests
+class TestGen : public Catch::Generators::IGenerator<int> {
+ int current_number;
+public:
+
+ TestGen(non_copyable const& nc):
+ current_number(nc.value) {}
+
+ int const& get() const override;
+ bool next() override {
+ return false;
+ }
+};
+
+// Avoids -Wweak-vtables
+int const& TestGen::get() const {
+ return current_number;
+}
+
+}
+
+TEST_CASE("GENERATE capture macros", "[generators][internals][approvals]") {
+ auto value = GENERATE(take(10, random(0, 10)));
+
+ non_copyable nc; nc.value = value;
+ // neither `GENERATE_COPY` nor plain `GENERATE` would compile here
+ auto value2 = GENERATE_REF(Catch::Generators::GeneratorWrapper<int>(Catch::Detail::make_unique<TestGen>(nc)));
+ REQUIRE(value == value2);
+}
+
+TEST_CASE("#1809 - GENERATE_COPY and SingleValueGenerator does not compile", "[generators][compilation][approvals]") {
+ // Verify Issue #1809 fix, only needs to compile.
+ auto a = GENERATE_COPY(1, 2);
+ (void)a;
+ auto b = GENERATE_COPY(as<long>{}, 1, 2);
+ (void)b;
+ int i = 1;
+ int j = 2;
+ auto c = GENERATE_COPY(i, j);
+ (void)c;
+ auto d = GENERATE_COPY(as<long>{}, i, j);
+ (void)d;
+ SUCCEED();
+}
+
+TEST_CASE("Multiple random generators in one test case output different values", "[generators][internals][approvals]") {
+ SECTION("Integer") {
+ auto random1 = Catch::Generators::random(0, 1000);
+ auto random2 = Catch::Generators::random(0, 1000);
+ size_t same = 0;
+ for (size_t i = 0; i < 1000; ++i) {
+ same += random1.get() == random2.get();
+ random1.next(); random2.next();
+ }
+ // Because the previous low bound failed CI couple of times,
+ // we use a very high threshold of 20% before failure is reported.
+ REQUIRE(same < 200);
+ }
+ SECTION("Float") {
+ auto random1 = Catch::Generators::random(0., 1000.);
+ auto random2 = Catch::Generators::random(0., 1000.);
+ size_t same = 0;
+ for (size_t i = 0; i < 1000; ++i) {
+ same += random1.get() == random2.get();
+ random1.next(); random2.next();
+ }
+ // Because the previous low bound failed CI couple of times,
+ // we use a very high threshold of 20% before failure is reported.
+ REQUIRE(same < 200);
+ }
+}
+
+TEST_CASE("#2040 - infinite compilation recursion in GENERATE with MSVC", "[generators][compilation][approvals]") {
+ int x = 42;
+ auto test = GENERATE_COPY(1, x, 2 * x);
+ CHECK(test < 100);
+}
+
+namespace {
+ static bool always_true(int) {
+ return true;
+ }
+
+ static bool is_even(int n) {
+ return n % 2 == 0;
+ }
+
+ static bool is_multiple_of_3(int n) {
+ return n % 3 == 0;
+ }
+}
+
+TEST_CASE("GENERATE handles function (pointers)", "[generators][compilation][approvals]") {
+ auto f = GENERATE(always_true, is_even, is_multiple_of_3);
+ REQUIRE(f(6));
+}
+
+TEST_CASE("GENERATE decays arrays", "[generators][compilation][approvals]") {
+ auto str = GENERATE("abc", "def", "gh");
+ (void)str;
+ STATIC_REQUIRE(std::is_same<decltype(str), const char*>::value);
+}
+
+TEST_CASE("Generators count returned elements", "[generators][approvals]") {
+ auto generator = Catch::Generators::FixedValuesGenerator<int>( { 1, 2, 3 } );
+ REQUIRE( generator.currentElementIndex() == 0 );
+ REQUIRE( generator.countedNext() );
+ REQUIRE( generator.currentElementIndex() == 1 );
+ REQUIRE( generator.countedNext() );
+ REQUIRE( generator.currentElementIndex() == 2 );
+ REQUIRE_FALSE( generator.countedNext() );
+ REQUIRE( generator.currentElementIndex() == 2 );
+}
+
+TEST_CASE( "Generators can stringify their elements",
+ "[generators][approvals]" ) {
+ auto generator =
+ Catch::Generators::FixedValuesGenerator<int>( { 1, 2, 3 } );
+
+ REQUIRE( generator.currentElementAsString() == "1"_catch_sr );
+ REQUIRE( generator.countedNext() );
+ REQUIRE( generator.currentElementAsString() == "2"_catch_sr );
+ REQUIRE( generator.countedNext() );
+ REQUIRE( generator.currentElementAsString() == "3"_catch_sr );
+}
+
+namespace {
+ class CustomStringifyGenerator
+ : public Catch::Generators::IGenerator<bool> {
+ bool m_first = true;
+
+ std::string stringifyImpl() const override {
+ return m_first ? "first" : "second";
+ }
+
+ bool next() override {
+ if ( m_first ) {
+ m_first = false;
+ return true;
+ }
+ return false;
+ }
+
+ public:
+ bool const& get() const override;
+ };
+
+ // Avoids -Wweak-vtables
+ bool const& CustomStringifyGenerator::get() const { return m_first; }
+} // namespace
+
+TEST_CASE( "Generators can override element stringification",
+ "[generators][approvals]" ) {
+ CustomStringifyGenerator generator;
+ REQUIRE( generator.currentElementAsString() == "first"_catch_sr );
+ REQUIRE( generator.countedNext() );
+ REQUIRE( generator.currentElementAsString() == "second"_catch_sr );
+}
+
+namespace {
+ class StringifyCountingGenerator
+ : public Catch::Generators::IGenerator<bool> {
+ bool m_first = true;
+ mutable size_t m_stringificationCalls = 0;
+
+ std::string stringifyImpl() const override {
+ ++m_stringificationCalls;
+ return m_first ? "first" : "second";
+ }
+
+ bool next() override {
+ if ( m_first ) {
+ m_first = false;
+ return true;
+ }
+ return false;
+ }
+
+ public:
+
+ bool const& get() const override;
+ size_t stringificationCalls() const { return m_stringificationCalls; }
+ };
+
+ // Avoids -Wweak-vtables
+ bool const& StringifyCountingGenerator::get() const { return m_first; }
+
+} // namespace
+
+TEST_CASE( "Generator element stringification is cached",
+ "[generators][approvals]" ) {
+ StringifyCountingGenerator generator;
+ REQUIRE( generator.currentElementAsString() == "first"_catch_sr );
+ REQUIRE( generator.currentElementAsString() == "first"_catch_sr );
+ REQUIRE( generator.currentElementAsString() == "first"_catch_sr );
+ REQUIRE( generator.currentElementAsString() == "first"_catch_sr );
+ REQUIRE( generator.currentElementAsString() == "first"_catch_sr );
+
+ REQUIRE( generator.stringificationCalls() == 1 );
+}
+
+TEST_CASE( "Random generators can be seeded", "[generators][approvals]" ) {
+ SECTION( "Integer generator" ) {
+ using Catch::Generators::RandomIntegerGenerator;
+ RandomIntegerGenerator<int> rng1( 0, 100, 0x1234 ),
+ rng2( 0, 100, 0x1234 );
+
+ for ( size_t i = 0; i < 10; ++i ) {
+ REQUIRE( rng1.get() == rng2.get() );
+ rng1.next(); rng2.next();
+ }
+ }
+ SECTION("Float generator") {
+ using Catch::Generators::RandomFloatingGenerator;
+ RandomFloatingGenerator<double> rng1( 0., 100., 0x1234 ),
+ rng2( 0., 100., 0x1234 );
+ for ( size_t i = 0; i < 10; ++i ) {
+ REQUIRE( rng1.get() == rng2.get() );
+ rng1.next();
+ rng2.next();
+ }
+ }
+}
+
+TEST_CASE("Filter generator throws exception for empty generator",
+ "[generators]") {
+ using namespace Catch::Generators;
+
+ REQUIRE_THROWS_AS(
+ filter( []( int ) { return false; }, value( 3 ) ),
+ Catch::GeneratorException );
+}
+
+TEST_CASE("from_range(container) supports ADL begin/end and arrays", "[generators][from-range][approvals]") {
+ using namespace Catch::Generators;
+
+ SECTION("C array") {
+ int arr[3]{ 5, 6, 7 };
+ auto gen = from_range( arr );
+ REQUIRE( gen.get() == 5 );
+ REQUIRE( gen.next() );
+ REQUIRE( gen.get() == 6 );
+ REQUIRE( gen.next() );
+ REQUIRE( gen.get() == 7 );
+ REQUIRE_FALSE( gen.next() );
+ }
+
+ SECTION( "ADL range" ) {
+ unrelated::needs_ADL_begin<int> range{ 1, 2, 3 };
+ auto gen = from_range( range );
+ REQUIRE( gen.get() == 1 );
+ REQUIRE( gen.next() );
+ REQUIRE( gen.get() == 2 );
+ REQUIRE( gen.next() );
+ REQUIRE( gen.get() == 3 );
+ REQUIRE_FALSE( gen.next() );
+ }
+
+}
diff --git a/tests/SelfTest/IntrospectiveTests/Integer.tests.cpp b/tests/SelfTest/IntrospectiveTests/Integer.tests.cpp
new file mode 100644
index 0000000..8955f40
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/Integer.tests.cpp
@@ -0,0 +1,224 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/internal/catch_random_integer_helpers.hpp>
+#include <random>
+
+namespace {
+ template <typename Int>
+ static void
+ CommutativeMultCheck( Int a, Int b, Int upper_result, Int lower_result ) {
+ using Catch::Detail::extendedMult;
+ using Catch::Detail::ExtendedMultResult;
+ CHECK( extendedMult( a, b ) ==
+ ExtendedMultResult<Int>{ upper_result, lower_result } );
+ CHECK( extendedMult( b, a ) ==
+ ExtendedMultResult<Int>{ upper_result, lower_result } );
+ }
+
+ // Simple (and slow) implmentation of extended multiplication for tests
+ constexpr Catch::Detail::ExtendedMultResult<std::uint64_t>
+ extendedMultNaive( std::uint64_t lhs, std::uint64_t rhs ) {
+ // This is a simple long multiplication, where we split lhs and rhs
+ // into two 32-bit "digits", so that we can do ops with carry in 64-bits.
+ //
+ // 32b 32b 32b 32b
+ // lhs L1 L2
+ // * rhs R1 R2
+ // ------------------------
+ // | R2 * L2 |
+ // | R2 * L1 |
+ // | R1 * L2 |
+ // | R1 * L1 |
+ // -------------------------
+ // | a | b | c | d |
+
+#define CarryBits( x ) ( x >> 32 )
+#define Digits( x ) ( x & 0xFF'FF'FF'FF )
+
+ auto r2l2 = Digits( rhs ) * Digits( lhs );
+ auto r2l1 = Digits( rhs ) * CarryBits( lhs );
+ auto r1l2 = CarryBits( rhs ) * Digits( lhs );
+ auto r1l1 = CarryBits( rhs ) * CarryBits( lhs );
+
+ // Sum to columns first
+ auto d = Digits( r2l2 );
+ auto c = CarryBits( r2l2 ) + Digits( r2l1 ) + Digits( r1l2 );
+ auto b = CarryBits( r2l1 ) + CarryBits( r1l2 ) + Digits( r1l1 );
+ auto a = CarryBits( r1l1 );
+
+ // Propagate carries between columns
+ c += CarryBits( d );
+ b += CarryBits( c );
+ a += CarryBits( b );
+
+ // Remove the used carries
+ c = Digits( c );
+ b = Digits( b );
+ a = Digits( a );
+
+#undef CarryBits
+#undef Digits
+
+ return {
+ a << 32 | b, // upper 64 bits
+ c << 32 | d // lower 64 bits
+ };
+ }
+
+
+} // namespace
+
+TEST_CASE( "extendedMult 64x64", "[Integer][approvals]" ) {
+ // a x 0 == 0
+ CommutativeMultCheck<uint64_t>( 0x1234'5678'9ABC'DEFF, 0, 0, 0 );
+
+ // bit carried from low half to upper half
+ CommutativeMultCheck<uint64_t>( uint64_t( 1 ) << 63, 2, 1, 0 );
+
+ // bits in upper half on one side, bits in lower half on other side
+ CommutativeMultCheck<uint64_t>( 0xcdcd'dcdc'0000'0000,
+ 0x0000'0000'aeae'aeae,
+ 0x0000'0000'8c6e'5a77,
+ 0x7391'a588'0000'0000 );
+
+ // Some input numbers without interesting patterns
+ CommutativeMultCheck<uint64_t>( 0xaaaa'aaaa'aaaa'aaaa,
+ 0xbbbb'bbbb'bbbb'bbbb,
+ 0x7d27'd27d'27d2'7d26,
+ 0xd82d'82d8'2d82'd82e );
+
+ CommutativeMultCheck<uint64_t>( 0x7d27'd27d'27d2'7d26,
+ 0xd82d'82d8'2d82'd82e,
+ 0x69af'd991'8256'b953,
+ 0x8724'8909'fcb6'8cd4 );
+
+ CommutativeMultCheck<uint64_t>( 0xdead'beef'dead'beef,
+ 0xfeed'feed'feed'feef,
+ 0xddbf'680b'2b0c'b558,
+ 0x7a36'b06f'2ce9'6321 );
+
+ CommutativeMultCheck<uint64_t>( 0xddbf'680b'2b0c'b558,
+ 0x7a36'b06f'2ce9'6321,
+ 0x69dc'96c9'294b'fc7f,
+ 0xd038'39fa'a3dc'6858 );
+
+ CommutativeMultCheck<uint64_t>( 0x61c8'8646'80b5'83eb,
+ 0x61c8'8646'80b5'83eb,
+ 0x2559'92d3'8220'8bbe,
+ 0xdf44'2d22'ce48'59b9 );
+}
+
+TEST_CASE("extendedMult 64x64 - all implementations", "[integer][approvals]") {
+ using Catch::Detail::extendedMult;
+ using Catch::Detail::extendedMultPortable;
+ using Catch::Detail::fillBitsFrom;
+
+ std::random_device rng;
+ for (size_t i = 0; i < 100; ++i) {
+ auto a = fillBitsFrom<std::uint64_t>( rng );
+ auto b = fillBitsFrom<std::uint64_t>( rng );
+ CAPTURE( a, b );
+
+ auto naive_ab = extendedMultNaive( a, b );
+
+ REQUIRE( naive_ab == extendedMultNaive( b, a ) );
+ REQUIRE( naive_ab == extendedMultPortable( a, b ) );
+ REQUIRE( naive_ab == extendedMultPortable( b, a ) );
+ REQUIRE( naive_ab == extendedMult( a, b ) );
+ REQUIRE( naive_ab == extendedMult( b, a ) );
+ }
+}
+
+TEST_CASE( "SizedUnsignedType helpers", "[integer][approvals]" ) {
+ using Catch::Detail::SizedUnsignedType_t;
+ using Catch::Detail::DoubleWidthUnsignedType_t;
+
+ STATIC_REQUIRE( sizeof( SizedUnsignedType_t<1> ) == 1 );
+ STATIC_REQUIRE( sizeof( SizedUnsignedType_t<2> ) == 2 );
+ STATIC_REQUIRE( sizeof( SizedUnsignedType_t<4> ) == 4 );
+ STATIC_REQUIRE( sizeof( SizedUnsignedType_t<8> ) == 8 );
+
+ STATIC_REQUIRE( sizeof( DoubleWidthUnsignedType_t<std::uint8_t> ) == 2 );
+ STATIC_REQUIRE( std::is_unsigned<DoubleWidthUnsignedType_t<std::uint8_t>>::value );
+ STATIC_REQUIRE( sizeof( DoubleWidthUnsignedType_t<std::uint16_t> ) == 4 );
+ STATIC_REQUIRE( std::is_unsigned<DoubleWidthUnsignedType_t<std::uint16_t>>::value );
+ STATIC_REQUIRE( sizeof( DoubleWidthUnsignedType_t<std::uint32_t> ) == 8 );
+ STATIC_REQUIRE( std::is_unsigned<DoubleWidthUnsignedType_t<std::uint32_t>>::value );
+}
+
+TEST_CASE( "extendedMult 32x32", "[integer][approvals]" ) {
+ // a x 0 == 0
+ CommutativeMultCheck<uint32_t>( 0x1234'5678, 0, 0, 0 );
+
+ // bit carried from low half to upper half
+ CommutativeMultCheck<uint32_t>( uint32_t(1) << 31, 2, 1, 0 );
+
+ // bits in upper half on one side, bits in lower half on other side
+ CommutativeMultCheck<uint32_t>( 0xdcdc'0000, 0x0000'aabb, 0x0000'934b, 0x6cb4'0000 );
+
+ // Some input numbers without interesting patterns
+ CommutativeMultCheck<uint32_t>(
+ 0xaaaa'aaaa, 0xbbbb'bbbb, 0x7d27'd27c, 0x2d82'd82e );
+
+ CommutativeMultCheck<uint32_t>(
+ 0x7d27'd27c, 0x2d82'd82e, 0x163f'f7e8, 0xc5b8'7248 );
+
+ CommutativeMultCheck<uint32_t>(
+ 0xdead'beef, 0xfeed'feed, 0xddbf'6809, 0x6f8d'e543 );
+
+ CommutativeMultCheck<uint32_t>(
+ 0xddbf'6809, 0x6f8d'e543, 0x60a0'e71e, 0x751d'475b );
+}
+
+TEST_CASE( "extendedMult 8x8", "[integer][approvals]" ) {
+ // a x 0 == 0
+ CommutativeMultCheck<uint8_t>( 0xcd, 0, 0, 0 );
+
+ // bit carried from low half to upper half
+ CommutativeMultCheck<uint8_t>( uint8_t( 1 ) << 7, 2, 1, 0 );
+
+ // bits in upper half on one side, bits in lower half on other side
+ CommutativeMultCheck<uint8_t>( 0x80, 0x03, 0x01, 0x80 );
+
+ // Some input numbers without interesting patterns
+ CommutativeMultCheck<uint8_t>( 0xaa, 0xbb, 0x7c, 0x2e );
+ CommutativeMultCheck<uint8_t>( 0x7c, 0x2e, 0x16, 0x48 );
+ CommutativeMultCheck<uint8_t>( 0xdc, 0xcd, 0xb0, 0x2c );
+ CommutativeMultCheck<uint8_t>( 0xb0, 0x2c, 0x1e, 0x40 );
+}
+
+
+TEST_CASE( "negative and positive signed integers keep their order after transposeToNaturalOrder",
+ "[integer][approvals]") {
+ using Catch::Detail::transposeToNaturalOrder;
+ int32_t negative( -1 );
+ int32_t positive( 1 );
+ uint32_t adjusted_negative =
+ transposeToNaturalOrder<int32_t>( static_cast<uint32_t>( negative ) );
+ uint32_t adjusted_positive =
+ transposeToNaturalOrder<int32_t>( static_cast<uint32_t>( positive ) );
+ REQUIRE( adjusted_negative < adjusted_positive );
+ REQUIRE( adjusted_positive - adjusted_negative == 2 );
+
+ // Conversion has to be reversible
+ REQUIRE( negative == static_cast<int32_t>( transposeToNaturalOrder<int32_t>(
+ adjusted_negative ) ) );
+ REQUIRE( positive == static_cast<int32_t>( transposeToNaturalOrder<int32_t>(
+ adjusted_positive ) ) );
+}
+
+TEST_CASE( "unsigned integers are unchanged by transposeToNaturalOrder",
+ "[integer][approvals]") {
+ using Catch::Detail::transposeToNaturalOrder;
+ uint32_t max = std::numeric_limits<uint32_t>::max();
+ uint32_t zero = 0;
+ REQUIRE( max == transposeToNaturalOrder<uint32_t>( max ) );
+ REQUIRE( zero == transposeToNaturalOrder<uint32_t>( zero ) );
+}
diff --git a/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp b/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp
new file mode 100644
index 0000000..69251d9
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp
@@ -0,0 +1,455 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+// Adapted from donated nonius code.
+
+
+#if defined( __GNUC__ ) || defined( __clang__ )
+# pragma GCC diagnostic ignored "-Wfloat-equal"
+#endif
+
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_config.hpp>
+#include <catch2/benchmark/catch_benchmark.hpp>
+#include <catch2/benchmark/catch_chronometer.hpp>
+#include <catch2/benchmark/detail/catch_analyse.hpp>
+#include <catch2/benchmark/detail/catch_benchmark_function.hpp>
+#include <catch2/benchmark/detail/catch_estimate_clock.hpp>
+
+#include <numeric>
+
+namespace {
+ struct manual_clock {
+ public:
+ using duration = std::chrono::nanoseconds;
+ using time_point = std::chrono::time_point<manual_clock, duration>;
+ using rep = duration::rep;
+ using period = duration::period;
+ enum { is_steady = true };
+
+ static time_point now() {
+ return time_point(duration(tick()));
+ }
+
+ static void advance(int ticks = 1) {
+ tick() += ticks;
+ }
+
+ private:
+ static rep& tick() {
+ static rep the_tick = 0;
+ return the_tick;
+ }
+ };
+
+ struct counting_clock {
+ public:
+ using duration = std::chrono::nanoseconds;
+ using time_point = std::chrono::time_point<counting_clock, duration>;
+ using rep = duration::rep;
+ using period = duration::period;
+ enum { is_steady = true };
+
+ static time_point now() {
+ static rep ticks = 0;
+ return time_point(duration(ticks += rate()));
+ }
+
+ static void set_rate(rep new_rate) { rate() = new_rate; }
+
+ private:
+ static rep& rate() {
+ static rep the_rate = 1;
+ return the_rate;
+ }
+ };
+
+ struct TestChronometerModel : Catch::Benchmark::Detail::ChronometerConcept {
+ int started = 0;
+ int finished = 0;
+
+ void start() override { ++started; }
+ void finish() override { ++finished; }
+ };
+} // namespace
+
+TEST_CASE("warmup", "[benchmark]") {
+ auto rate = 1000;
+ counting_clock::set_rate(rate);
+
+ auto start = counting_clock::now();
+ auto iterations = Catch::Benchmark::Detail::warmup<counting_clock>();
+ auto end = counting_clock::now();
+
+ REQUIRE((iterations * rate) > Catch::Benchmark::Detail::warmup_time.count());
+ REQUIRE((end - start) > Catch::Benchmark::Detail::warmup_time);
+}
+
+TEST_CASE("resolution", "[benchmark]") {
+ auto rate = 1000;
+ counting_clock::set_rate(rate);
+
+ size_t count = 10;
+ auto res = Catch::Benchmark::Detail::resolution<counting_clock>(static_cast<int>(count));
+
+ REQUIRE(res.size() == count);
+
+ for (size_t i = 1; i < count; ++i) {
+ REQUIRE(res[i] == rate);
+ }
+}
+
+TEST_CASE("estimate_clock_resolution", "[benchmark]") {
+ auto rate = 2'000;
+ counting_clock::set_rate(rate);
+
+ int iters = 160'000;
+ auto res = Catch::Benchmark::Detail::estimate_clock_resolution<counting_clock>(iters);
+
+ REQUIRE(res.mean.count() == rate);
+ REQUIRE(res.outliers.total() == 0);
+}
+
+TEST_CASE("benchmark function call", "[benchmark]") {
+ SECTION("without chronometer") {
+ auto called = 0;
+ auto model = TestChronometerModel{};
+ auto meter = Catch::Benchmark::Chronometer{ model, 1 };
+ auto fn = Catch::Benchmark::Detail::BenchmarkFunction{ [&] {
+ CHECK(model.started == 1);
+ CHECK(model.finished == 0);
+ ++called;
+ } };
+
+ fn(meter);
+
+ CHECK(model.started == 1);
+ CHECK(model.finished == 1);
+ CHECK(called == 1);
+ }
+
+ SECTION("with chronometer") {
+ auto called = 0;
+ auto model = TestChronometerModel{};
+ auto meter = Catch::Benchmark::Chronometer{ model, 1 };
+ auto fn = Catch::Benchmark::Detail::BenchmarkFunction{ [&](Catch::Benchmark::Chronometer) {
+ CHECK(model.started == 0);
+ CHECK(model.finished == 0);
+ ++called;
+ } };
+
+ fn(meter);
+
+ CHECK(model.started == 0);
+ CHECK(model.finished == 0);
+ CHECK(called == 1);
+ }
+}
+
+TEST_CASE("uniform samples", "[benchmark]") {
+ std::vector<double> samples(100);
+ std::fill(samples.begin(), samples.end(), 23);
+
+ auto e = Catch::Benchmark::Detail::bootstrap(
+ 0.95,
+ samples.data(),
+ samples.data() + samples.size(),
+ samples,
+ []( double const* a, double const* b ) {
+ auto sum = std::accumulate(a, b, 0.);
+ return sum / (b - a);
+ });
+ CHECK(e.point == 23);
+ CHECK(e.upper_bound == 23);
+ CHECK(e.lower_bound == 23);
+ CHECK(e.confidence_interval == 0.95);
+}
+
+
+TEST_CASE("normal_cdf", "[benchmark][approvals]") {
+ using Catch::Benchmark::Detail::normal_cdf;
+ using Catch::Approx;
+ CHECK(normal_cdf(0.000000) == Approx(0.50000000000000000));
+ CHECK(normal_cdf(1.000000) == Approx(0.84134474606854293));
+ CHECK(normal_cdf(-1.000000) == Approx(0.15865525393145705));
+ CHECK(normal_cdf(2.809729) == Approx(0.99752083845315409));
+ CHECK(normal_cdf(-1.352570) == Approx(0.08809652095066035));
+}
+
+TEST_CASE("erfc_inv", "[benchmark]") {
+ using Catch::Benchmark::Detail::erfc_inv;
+ using Catch::Approx;
+ CHECK(erfc_inv(1.103560) == Approx(-0.09203687623843015));
+ CHECK(erfc_inv(1.067400) == Approx(-0.05980291115763361));
+ CHECK(erfc_inv(0.050000) == Approx(1.38590382434967796));
+}
+
+TEST_CASE("normal_quantile", "[benchmark]") {
+ using Catch::Benchmark::Detail::normal_quantile;
+ using Catch::Approx;
+ CHECK(normal_quantile(0.551780) == Approx(0.13015979861484198));
+ CHECK(normal_quantile(0.533700) == Approx(0.08457408802851875));
+ CHECK(normal_quantile(0.025000) == Approx(-1.95996398454005449));
+}
+
+
+TEST_CASE("mean", "[benchmark]") {
+ std::vector<double> x{ 10., 20., 14., 16., 30., 24. };
+
+ auto m = Catch::Benchmark::Detail::mean(x.data(), x.data() + x.size());
+
+ REQUIRE(m == 19.);
+}
+
+TEST_CASE("weighted_average_quantile", "[benchmark]") {
+ std::vector<double> x{ 10., 20., 14., 16., 30., 24. };
+
+ auto q1 = Catch::Benchmark::Detail::weighted_average_quantile(1, 4, x.data(), x.data() + x.size());
+ auto med = Catch::Benchmark::Detail::weighted_average_quantile(1, 2, x.data(), x.data() + x.size());
+ auto q3 = Catch::Benchmark::Detail::weighted_average_quantile(3, 4, x.data(), x.data() + x.size());
+
+ REQUIRE(q1 == 14.5);
+ REQUIRE(med == 18.);
+ REQUIRE(q3 == 23.);
+}
+
+TEST_CASE("classify_outliers", "[benchmark]") {
+ auto require_outliers = [](Catch::Benchmark::OutlierClassification o, int los, int lom, int him, int his) {
+ REQUIRE(o.low_severe == los);
+ REQUIRE(o.low_mild == lom);
+ REQUIRE(o.high_mild == him);
+ REQUIRE(o.high_severe == his);
+ REQUIRE(o.total() == los + lom + him + his);
+ };
+
+ SECTION("none") {
+ std::vector<double> x{ 10., 20., 14., 16., 30., 24. };
+
+ auto o = Catch::Benchmark::Detail::classify_outliers(
+ x.data(), x.data() + x.size() );
+
+ REQUIRE(o.samples_seen == static_cast<int>(x.size()));
+ require_outliers(o, 0, 0, 0, 0);
+ }
+ SECTION("low severe") {
+ std::vector<double> x{ -12., 20., 14., 16., 30., 24. };
+
+ auto o = Catch::Benchmark::Detail::classify_outliers(
+ x.data(), x.data() + x.size() );
+
+ REQUIRE(o.samples_seen == static_cast<int>(x.size()));
+ require_outliers(o, 1, 0, 0, 0);
+ }
+ SECTION("low mild") {
+ std::vector<double> x{ 1., 20., 14., 16., 30., 24. };
+
+ auto o = Catch::Benchmark::Detail::classify_outliers(
+ x.data(), x.data() + x.size() );
+
+ REQUIRE(o.samples_seen == static_cast<int>(x.size()));
+ require_outliers(o, 0, 1, 0, 0);
+ }
+ SECTION("high mild") {
+ std::vector<double> x{ 10., 20., 14., 16., 36., 24. };
+
+ auto o = Catch::Benchmark::Detail::classify_outliers(
+ x.data(), x.data() + x.size() );
+
+ REQUIRE(o.samples_seen == static_cast<int>(x.size()));
+ require_outliers(o, 0, 0, 1, 0);
+ }
+ SECTION("high severe") {
+ std::vector<double> x{ 10., 20., 14., 16., 49., 24. };
+
+ auto o = Catch::Benchmark::Detail::classify_outliers(
+ x.data(), x.data() + x.size() );
+
+ REQUIRE(o.samples_seen == static_cast<int>(x.size()));
+ require_outliers(o, 0, 0, 0, 1);
+ }
+ SECTION("mixed") {
+ std::vector<double> x{ -20., 20., 14., 16., 39., 24. };
+
+ auto o = Catch::Benchmark::Detail::classify_outliers(
+ x.data(), x.data() + x.size() );
+
+ REQUIRE(o.samples_seen == static_cast<int>(x.size()));
+ require_outliers(o, 1, 0, 1, 0);
+ }
+}
+
+TEST_CASE("analyse", "[approvals][benchmark]") {
+ Catch::ConfigData data{};
+ data.benchmarkConfidenceInterval = 0.95;
+ data.benchmarkNoAnalysis = false;
+ data.benchmarkResamples = 1000;
+ data.benchmarkSamples = 99;
+ Catch::Config config{data};
+
+ using FDuration = Catch::Benchmark::FDuration;
+ std::vector<FDuration> samples(99);
+ for (size_t i = 0; i < samples.size(); ++i) {
+ samples[i] = FDuration(23 + (i % 3 - 1));
+ }
+
+ auto analysis = Catch::Benchmark::Detail::analyse(config, samples.data(), samples.data() + samples.size());
+ CHECK( analysis.mean.point.count() == 23 );
+ CHECK( analysis.mean.lower_bound.count() < 23 );
+ CHECK(analysis.mean.lower_bound.count() > 22);
+ CHECK(analysis.mean.upper_bound.count() > 23);
+ CHECK(analysis.mean.upper_bound.count() < 24);
+
+ CHECK(analysis.standard_deviation.point.count() > 0.5);
+ CHECK(analysis.standard_deviation.point.count() < 1);
+ CHECK(analysis.standard_deviation.lower_bound.count() > 0.5);
+ CHECK(analysis.standard_deviation.lower_bound.count() < 1);
+ CHECK(analysis.standard_deviation.upper_bound.count() > 0.5);
+ CHECK(analysis.standard_deviation.upper_bound.count() < 1);
+
+ CHECK(analysis.outliers.total() == 0);
+ CHECK(analysis.outliers.low_mild == 0);
+ CHECK(analysis.outliers.low_severe == 0);
+ CHECK(analysis.outliers.high_mild == 0);
+ CHECK(analysis.outliers.high_severe == 0);
+ CHECK(analysis.outliers.samples_seen == static_cast<int>(samples.size()));
+
+ CHECK(analysis.outlier_variance < 0.5);
+ CHECK(analysis.outlier_variance > 0);
+}
+
+TEST_CASE("analyse no analysis", "[benchmark]") {
+ Catch::ConfigData data{};
+ data.benchmarkConfidenceInterval = 0.95;
+ data.benchmarkNoAnalysis = true;
+ data.benchmarkResamples = 1000;
+ data.benchmarkSamples = 99;
+ Catch::Config config{ data };
+
+ using FDuration = Catch::Benchmark::FDuration;
+ std::vector<FDuration> samples(99);
+ for (size_t i = 0; i < samples.size(); ++i) {
+ samples[i] = FDuration(23 + (i % 3 - 1));
+ }
+
+ auto analysis = Catch::Benchmark::Detail::analyse(config, samples.data(), samples.data() + samples.size());
+ CHECK(analysis.mean.point.count() == 23);
+ CHECK(analysis.mean.lower_bound.count() == 23);
+ CHECK(analysis.mean.upper_bound.count() == 23);
+
+ CHECK(analysis.standard_deviation.point.count() == 0);
+ CHECK(analysis.standard_deviation.lower_bound.count() == 0);
+ CHECK(analysis.standard_deviation.upper_bound.count() == 0);
+
+ CHECK(analysis.outliers.total() == 0);
+ CHECK(analysis.outliers.low_mild == 0);
+ CHECK(analysis.outliers.low_severe == 0);
+ CHECK(analysis.outliers.high_mild == 0);
+ CHECK(analysis.outliers.high_severe == 0);
+ CHECK(analysis.outliers.samples_seen == 0);
+
+ CHECK(analysis.outlier_variance == 0);
+}
+
+TEST_CASE("run_for_at_least, int", "[benchmark]") {
+ manual_clock::duration time(100);
+
+ int old_x = 1;
+ auto Timing = Catch::Benchmark::Detail::run_for_at_least<manual_clock>(time, 1, [&old_x](int x) -> int {
+ CHECK(x >= old_x);
+ manual_clock::advance(x);
+ old_x = x;
+ return x + 17;
+ });
+
+ REQUIRE(Timing.elapsed >= time);
+ REQUIRE(Timing.result == Timing.iterations + 17);
+ REQUIRE(Timing.iterations >= time.count());
+}
+
+TEST_CASE("run_for_at_least, chronometer", "[benchmark]") {
+ manual_clock::duration time(100);
+
+ int old_runs = 1;
+ auto Timing = Catch::Benchmark::Detail::run_for_at_least<manual_clock>(time, 1, [&old_runs](Catch::Benchmark::Chronometer meter) -> int {
+ CHECK(meter.runs() >= old_runs);
+ manual_clock::advance(100);
+ meter.measure([] {
+ manual_clock::advance(1);
+ });
+ old_runs = meter.runs();
+ return meter.runs() + 17;
+ });
+
+ REQUIRE(Timing.elapsed >= time);
+ REQUIRE(Timing.result == Timing.iterations + 17);
+ REQUIRE(Timing.iterations >= time.count());
+}
+
+
+TEST_CASE("measure", "[benchmark]") {
+ auto r = Catch::Benchmark::Detail::measure<manual_clock>([](int x) -> int {
+ CHECK(x == 17);
+ manual_clock::advance(42);
+ return 23;
+ }, 17);
+ auto s = Catch::Benchmark::Detail::measure<manual_clock>([](int x) -> int {
+ CHECK(x == 23);
+ manual_clock::advance(69);
+ return 17;
+ }, 23);
+
+ CHECK(r.elapsed.count() == 42);
+ CHECK(r.result == 23);
+ CHECK(r.iterations == 1);
+
+ CHECK(s.elapsed.count() == 69);
+ CHECK(s.result == 17);
+ CHECK(s.iterations == 1);
+}
+
+TEST_CASE("run benchmark", "[benchmark][approvals]") {
+ counting_clock::set_rate(1000);
+ auto start = counting_clock::now();
+
+ Catch::Benchmark::Benchmark bench{ "Test Benchmark", [](Catch::Benchmark::Chronometer meter) {
+ counting_clock::set_rate(100000);
+ meter.measure([] { return counting_clock::now(); });
+ } };
+
+ bench.run<counting_clock>();
+ auto end = counting_clock::now();
+
+ CHECK((end - start).count() == 2867251000);
+}
+
+TEST_CASE("Failing benchmarks", "[!benchmark][.approvals]") {
+ SECTION("empty", "Benchmark that has been optimized away (because it is empty)") {
+ BENCHMARK("Empty benchmark") {};
+ }
+ SECTION("throw", "Benchmark that throws an exception") {
+ BENCHMARK("Throwing benchmark") {
+ throw "just a plain literal, bleh";
+ };
+ }
+ SECTION("assert", "Benchmark that asserts inside") {
+ BENCHMARK("Asserting benchmark") {
+ REQUIRE(1 == 2);
+ };
+ }
+ SECTION("fail", "Benchmark that fails inside") {
+ BENCHMARK("FAIL'd benchmark") {
+ FAIL("This benchmark only fails, nothing else");
+ };
+ }
+}
+
+TEST_CASE( "Failing benchmark respects should-fail",
+ "[!shouldfail][!benchmark][approvals]" ) {
+ BENCHMARK( "Asserting benchmark" ) { REQUIRE( 1 == 2 ); };
+}
diff --git a/tests/SelfTest/IntrospectiveTests/Json.tests.cpp b/tests/SelfTest/IntrospectiveTests/Json.tests.cpp
new file mode 100644
index 0000000..8204e3c
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/Json.tests.cpp
@@ -0,0 +1,152 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/internal/catch_jsonwriter.hpp>
+#include <catch2/matchers/catch_matchers_string.hpp>
+
+#include <sstream>
+
+namespace {
+ struct Custom {};
+ static std::ostream& operator<<( std::ostream& os, Custom const& ) {
+ return os << "custom";
+ }
+} // namespace
+
+TEST_CASE( "JsonWriter", "[JSON][JsonWriter]" ) {
+
+ std::stringstream stream;
+ SECTION( "Newly constructed JsonWriter does nothing" ) {
+ Catch::JsonValueWriter writer{ stream };
+ REQUIRE( stream.str() == "" );
+ }
+
+ SECTION( "Calling writeObject will create an empty pair of braces" ) {
+ { auto writer = Catch::JsonValueWriter{ stream }.writeObject(); }
+ REQUIRE( stream.str() == "{\n}" );
+ }
+
+ SECTION( "Calling writeObject with key will create an object to write the "
+ "value" ) {
+ using Catch::Matchers::ContainsSubstring;
+ {
+ auto writer = Catch::JsonValueWriter{ stream }.writeObject();
+ writer.write( "int" ).write( 1 );
+ writer.write( "double" ).write( 1.5 );
+ writer.write( "true" ).write( true );
+ writer.write( "false" ).write( false );
+ writer.write( "string" ).write( "this is a string" );
+ writer.write( "array" ).writeArray().write( 1 ).write( 2 );
+ }
+ REQUIRE_THAT(
+ stream.str(),
+ ContainsSubstring( "\"int\": 1," ) &&
+ ContainsSubstring( "\"double\": 1.5," ) &&
+ ContainsSubstring( "\"true\": true," ) &&
+ ContainsSubstring( "\"false\": false," ) &&
+ ContainsSubstring( "\"string\": \"this is a string\"," ) &&
+ ContainsSubstring( "\"array\": [\n 1,\n 2\n ]\n}" ) );
+ }
+
+ SECTION( "nesting objects" ) {
+ using Catch::Matchers::ContainsSubstring;
+ {
+ auto writer = Catch::JsonValueWriter{ stream }.writeObject();
+ writer.write( "empty_object" ).writeObject();
+ writer.write( "fully_object" )
+ .writeObject()
+ .write( "key" )
+ .write( 1 );
+ }
+ REQUIRE_THAT( stream.str(),
+ ContainsSubstring( "\"empty_object\": {\n }," ) &&
+ ContainsSubstring(
+ "\"fully_object\": {\n \"key\": 1\n }" ) );
+ }
+
+ SECTION( "Calling writeArray will create an empty pair of braces" ) {
+ { auto writer = Catch::JsonValueWriter{ stream }.writeArray(); }
+ REQUIRE( stream.str() == "[\n]" );
+ }
+
+ SECTION( "Calling writeArray creates array to write the values to" ) {
+ {
+ auto writer = Catch::JsonValueWriter{ stream }.writeArray();
+ writer.write( 1 );
+ writer.write( 1.5 );
+ writer.write( true );
+ writer.write( false );
+ writer.write( "this is a string" );
+ writer.writeObject().write( "object" ).write( 42 );
+ writer.writeArray().write( "array" ).write( 42.5 );
+ }
+ REQUIRE( stream.str() == "[\n 1,\n 1.5,\n true,\n false,\n \"this is a string\",\n {\n \"object\": 42\n },\n [\n \"array\",\n 42.5\n ]\n]" );
+ }
+
+ SECTION(
+ "Moved from JsonObjectWriter shall not insert superfluous brace" ) {
+ {
+ auto writer = Catch::JsonObjectWriter{ stream };
+ auto another_writer = std::move( writer );
+ }
+ REQUIRE( stream.str() == "{\n}" );
+ }
+ SECTION(
+ "Moved from JsonArrayWriter shall not insert superfluous bracket" ) {
+ {
+ auto writer = Catch::JsonArrayWriter{ stream };
+ auto another_writer = std::move( writer );
+ }
+ REQUIRE( stream.str() == "[\n]" );
+ }
+ SECTION( "Custom class shall be quoted" ) {
+ Catch::JsonValueWriter{ stream }.write( Custom{} );
+ REQUIRE( stream.str() == "\"custom\"" );
+ }
+}
+
+TEST_CASE( "JsonWriter escapes charaters in strings properly", "[JsonWriter]" ) {
+ std::stringstream sstream;
+ SECTION( "Quote in a string is escaped" ) {
+ Catch::JsonValueWriter{ sstream }.write( "\"" );
+ REQUIRE( sstream.str() == "\"\\\"\"" );
+ }
+ SECTION("Backslash in a string is escaped") {
+ Catch::JsonValueWriter{ sstream }.write( "\\" );
+ REQUIRE( sstream.str() == "\"\\\\\"" );
+ }
+ SECTION( "Forward slash in a string is **not** escaped" ) {
+ Catch::JsonValueWriter{ sstream }.write( "/" );
+ REQUIRE( sstream.str() == "\"/\"" );
+ }
+ SECTION( "Backspace in a string is escaped" ) {
+ Catch::JsonValueWriter{ sstream }.write( "\b" );
+ REQUIRE( sstream.str() == "\"\\b\"" );
+ }
+ SECTION( "Formfeed in a string is escaped" ) {
+ Catch::JsonValueWriter{ sstream }.write( "\f" );
+ REQUIRE( sstream.str() == "\"\\f\"" );
+ }
+ SECTION( "linefeed in a string is escaped" ) {
+ Catch::JsonValueWriter{ sstream }.write( "\n" );
+ REQUIRE( sstream.str() == "\"\\n\"" );
+ }
+ SECTION( "carriage return in a string is escaped" ) {
+ Catch::JsonValueWriter{ sstream }.write( "\r" );
+ REQUIRE( sstream.str() == "\"\\r\"" );
+ }
+ SECTION( "tab in a string is escaped" ) {
+ Catch::JsonValueWriter{ sstream }.write( "\t" );
+ REQUIRE( sstream.str() == "\"\\t\"" );
+ }
+ SECTION( "combination of characters is escaped" ) {
+ Catch::JsonValueWriter{ sstream }.write( "\\/\t\r\n" );
+ REQUIRE( sstream.str() == "\"\\\\/\\t\\r\\n\"" );
+ }
+}
diff --git a/tests/SelfTest/IntrospectiveTests/Parse.tests.cpp b/tests/SelfTest/IntrospectiveTests/Parse.tests.cpp
new file mode 100644
index 0000000..7791355
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/Parse.tests.cpp
@@ -0,0 +1,38 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <catch2/internal/catch_parse_numbers.hpp>
+
+TEST_CASE("Parse uints", "[parse-numbers]") {
+ using Catch::parseUInt;
+ using Catch::Optional;
+
+ SECTION("proper inputs") {
+ REQUIRE( parseUInt( "0" ) == Optional<unsigned int>{ 0 } );
+ REQUIRE( parseUInt( "100" ) == Optional<unsigned int>{ 100 } );
+ REQUIRE( parseUInt( "4294967295" ) ==
+ Optional<unsigned int>{ 4294967295 } );
+ REQUIRE( parseUInt( "0xFF", 16 ) == Optional<unsigned int>{ 255 } );
+ }
+ SECTION( "Bad inputs" ) {
+ // empty
+ REQUIRE_FALSE( parseUInt( "" ) );
+ // random noise
+ REQUIRE_FALSE( parseUInt( "!!KJHF*#" ) );
+ // negative
+ REQUIRE_FALSE( parseUInt( "-1" ) );
+ // too large
+ REQUIRE_FALSE( parseUInt( "4294967296" ) );
+ REQUIRE_FALSE( parseUInt( "42949672964294967296429496729642949672964294967296" ) );
+ REQUIRE_FALSE( parseUInt( "2 4" ) );
+ // hex with base 10
+ REQUIRE_FALSE( parseUInt( "0xFF", 10 ) );
+ }
+}
diff --git a/tests/SelfTest/IntrospectiveTests/PartTracker.tests.cpp b/tests/SelfTest/IntrospectiveTests/PartTracker.tests.cpp
new file mode 100644
index 0000000..c13ec57
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/PartTracker.tests.cpp
@@ -0,0 +1,254 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/generators/catch_generators.hpp>
+#include <catch2/internal/catch_test_case_tracker.hpp>
+
+
+using namespace Catch;
+
+namespace {
+Catch::TestCaseTracking::NameAndLocationRef makeNAL( StringRef name ) {
+ return Catch::TestCaseTracking::NameAndLocationRef( name, Catch::SourceLineInfo("",0) );
+}
+}
+
+TEST_CASE( "Tracker" ) {
+
+ TrackerContext ctx;
+ ctx.startRun();
+ ctx.startCycle();
+
+
+ ITracker& testCase = SectionTracker::acquire( ctx, makeNAL( "Testcase" ) );
+ REQUIRE( testCase.isOpen() );
+
+ ITracker& s1 = SectionTracker::acquire( ctx, makeNAL( "S1" ) );
+ REQUIRE( s1.isOpen() );
+
+ SECTION( "successfully close one section" ) {
+ s1.close();
+ REQUIRE( s1.isSuccessfullyCompleted() );
+ REQUIRE( testCase.isComplete() == false );
+
+ testCase.close();
+ REQUIRE( ctx.completedCycle() );
+ REQUIRE( testCase.isSuccessfullyCompleted() );
+ }
+
+ SECTION( "fail one section" ) {
+ s1.fail();
+ REQUIRE( s1.isComplete() );
+ REQUIRE( s1.isSuccessfullyCompleted() == false );
+ REQUIRE( testCase.isComplete() == false );
+
+ testCase.close();
+ REQUIRE( ctx.completedCycle() );
+ REQUIRE( testCase.isSuccessfullyCompleted() == false );
+
+ SECTION( "re-enter after failed section" ) {
+ ctx.startCycle();
+ ITracker& testCase2 = SectionTracker::acquire( ctx, makeNAL( "Testcase" ) );
+ REQUIRE( testCase2.isOpen() );
+
+ ITracker& s1b = SectionTracker::acquire( ctx, makeNAL( "S1" ) );
+ REQUIRE( s1b.isOpen() == false );
+
+ testCase2.close();
+ REQUIRE( ctx.completedCycle() );
+ REQUIRE( testCase.isComplete() );
+ REQUIRE( testCase.isSuccessfullyCompleted() );
+ }
+ SECTION( "re-enter after failed section and find next section" ) {
+ ctx.startCycle();
+ ITracker& testCase2 = SectionTracker::acquire( ctx, makeNAL( "Testcase" ) );
+ REQUIRE( testCase2.isOpen() );
+
+ ITracker& s1b = SectionTracker::acquire( ctx, makeNAL( "S1" ) );
+ REQUIRE( s1b.isOpen() == false );
+
+ ITracker& s2 = SectionTracker::acquire( ctx, makeNAL( "S2" ) );
+ REQUIRE( s2.isOpen() );
+
+ s2.close();
+ REQUIRE( ctx.completedCycle() );
+
+ testCase2.close();
+ REQUIRE( testCase.isComplete() );
+ REQUIRE( testCase.isSuccessfullyCompleted() );
+ }
+ }
+
+ SECTION( "successfully close one section, then find another" ) {
+ s1.close();
+
+ ITracker& s2 = SectionTracker::acquire( ctx, makeNAL( "S2" ) );
+ REQUIRE( s2.isOpen() == false );
+
+ testCase.close();
+ REQUIRE( testCase.isComplete() == false );
+
+ SECTION( "Re-enter - skips S1 and enters S2" ) {
+ ctx.startCycle();
+ ITracker& testCase2 = SectionTracker::acquire( ctx, makeNAL( "Testcase" ) );
+ REQUIRE( testCase2.isOpen() );
+
+ ITracker& s1b = SectionTracker::acquire( ctx, makeNAL( "S1" ) );
+ REQUIRE( s1b.isOpen() == false );
+
+ ITracker& s2b = SectionTracker::acquire( ctx, makeNAL( "S2" ) );
+ REQUIRE( s2b.isOpen() );
+
+ REQUIRE( ctx.completedCycle() == false );
+
+ SECTION ("Successfully close S2") {
+ s2b.close();
+ REQUIRE( ctx.completedCycle() );
+
+ REQUIRE( s2b.isSuccessfullyCompleted() );
+ REQUIRE( testCase2.isComplete() == false );
+
+ testCase2.close();
+ REQUIRE( testCase2.isSuccessfullyCompleted() );
+ }
+ SECTION ("fail S2") {
+ s2b.fail();
+ REQUIRE( ctx.completedCycle() );
+
+ REQUIRE( s2b.isComplete() );
+ REQUIRE( s2b.isSuccessfullyCompleted() == false );
+
+ testCase2.close();
+ REQUIRE( testCase2.isSuccessfullyCompleted() == false );
+
+ // Need a final cycle
+ ctx.startCycle();
+ ITracker& testCase3 = SectionTracker::acquire( ctx, makeNAL( "Testcase" ) );
+ REQUIRE( testCase3.isOpen() );
+
+ ITracker& s1c = SectionTracker::acquire( ctx, makeNAL( "S1" ) );
+ REQUIRE( s1c.isOpen() == false );
+
+ ITracker& s2c = SectionTracker::acquire( ctx, makeNAL( "S2" ) );
+ REQUIRE( s2c.isOpen() == false );
+
+ testCase3.close();
+ REQUIRE( testCase3.isSuccessfullyCompleted() );
+ }
+ }
+ }
+
+ SECTION( "open a nested section" ) {
+ ITracker& s2 = SectionTracker::acquire( ctx, makeNAL( "S2" ) );
+ REQUIRE( s2.isOpen() );
+
+ s2.close();
+ REQUIRE( s2.isComplete() );
+ REQUIRE( s1.isComplete() == false );
+
+ s1.close();
+ REQUIRE( s1.isComplete() );
+ REQUIRE( testCase.isComplete() == false );
+
+ testCase.close();
+ REQUIRE( testCase.isComplete() );
+ }
+}
+
+static bool previouslyRun = false;
+static bool previouslyRunNested = false;
+
+TEST_CASE( "#1394", "[.][approvals][tracker]" ) {
+ // -- Don't re-run after specified section is done
+ REQUIRE(previouslyRun == false);
+
+ SECTION( "RunSection" ) {
+ previouslyRun = true;
+ }
+ SECTION( "SkipSection" ) {
+ // cause an error if this section is called because it shouldn't be
+ REQUIRE(1 == 0);
+ }
+}
+
+TEST_CASE( "#1394 nested", "[.][approvals][tracker]" ) {
+ REQUIRE(previouslyRunNested == false);
+
+ SECTION( "NestedRunSection" ) {
+ SECTION( "s1" ) {
+ previouslyRunNested = true;
+ }
+ }
+ SECTION( "NestedSkipSection" ) {
+ // cause an error if this section is called because it shouldn't be
+ REQUIRE(1 == 0);
+ }
+}
+
+// Selecting a "not last" section inside a test case via -c "section" would
+// previously only run the first subsection, instead of running all of them.
+// This allows us to check that `"#1670 regression check" -c A` leads to
+// 2 successful assertions.
+TEST_CASE("#1670 regression check", "[.approvals][tracker]") {
+ SECTION("A") {
+ SECTION("1") SUCCEED();
+ SECTION("2") SUCCEED();
+ }
+ SECTION("B") {
+ SECTION("1") SUCCEED();
+ SECTION("2") SUCCEED();
+ }
+}
+
+// #1938 required a rework on how generator tracking works, so that `GENERATE`
+// supports being sandwiched between two `SECTION`s. The following tests check
+// various other scenarios through checking output in approval tests.
+TEST_CASE("#1938 - GENERATE after a section", "[.][regression][generators]") {
+ SECTION("A") {
+ SUCCEED("A");
+ }
+ auto m = GENERATE(1, 2, 3);
+ SECTION("B") {
+ REQUIRE(m);
+ }
+}
+
+TEST_CASE("#1938 - flat generate", "[.][regression][generators]") {
+ auto m = GENERATE(1, 2, 3);
+ REQUIRE(m);
+}
+
+TEST_CASE("#1938 - nested generate", "[.][regression][generators]") {
+ auto m = GENERATE(1, 2, 3);
+ auto n = GENERATE(1, 2, 3);
+ REQUIRE(m);
+ REQUIRE(n);
+}
+
+TEST_CASE("#1938 - mixed sections and generates", "[.][regression][generators]") {
+ auto i = GENERATE(1, 2);
+ SECTION("A") {
+ SUCCEED("A");
+ }
+ auto j = GENERATE(3, 4);
+ SECTION("B") {
+ SUCCEED("B");
+ }
+ auto k = GENERATE(5, 6);
+ CAPTURE(i, j, k);
+ SUCCEED();
+}
+
+TEST_CASE("#1938 - Section followed by flat generate", "[.][regression][generators]") {
+ SECTION("A") {
+ REQUIRE(1);
+ }
+ auto m = GENERATE(2, 3);
+ REQUIRE(m);
+}
diff --git a/tests/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp b/tests/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp
new file mode 100644
index 0000000..8932321
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp
@@ -0,0 +1,609 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/catch_template_test_macros.hpp>
+#include <catch2/internal/catch_floating_point_helpers.hpp>
+#include <catch2/internal/catch_random_integer_helpers.hpp>
+#include <catch2/internal/catch_random_number_generator.hpp>
+#include <catch2/internal/catch_random_seed_generation.hpp>
+#include <catch2/internal/catch_uniform_floating_point_distribution.hpp>
+#include <catch2/internal/catch_uniform_integer_distribution.hpp>
+#include <catch2/generators/catch_generators.hpp>
+#include <catch2/matchers/catch_matchers_range_equals.hpp>
+
+#include <random>
+
+TEST_CASE("Our PCG implementation provides expected results for known seeds", "[rng]") {
+ Catch::SimplePcg32 rng;
+ SECTION("Default seeded") {
+ REQUIRE(rng() == 0xfcdb943b);
+ REQUIRE(rng() == 0x6f55b921);
+ REQUIRE(rng() == 0x4c17a916);
+ REQUIRE(rng() == 0x71eae25f);
+ REQUIRE(rng() == 0x6ce7909c);
+ }
+ SECTION("Specific seed") {
+ rng.seed(0xabcd1234);
+ REQUIRE(rng() == 0x57c08495);
+ REQUIRE(rng() == 0x33c956ac);
+ REQUIRE(rng() == 0x2206fd76);
+ REQUIRE(rng() == 0x3501a35b);
+ REQUIRE(rng() == 0xfdffb30f);
+
+ // Also check repeated output after reseeding
+ rng.seed(0xabcd1234);
+ REQUIRE(rng() == 0x57c08495);
+ REQUIRE(rng() == 0x33c956ac);
+ REQUIRE(rng() == 0x2206fd76);
+ REQUIRE(rng() == 0x3501a35b);
+ REQUIRE(rng() == 0xfdffb30f);
+ }
+}
+
+TEST_CASE("Comparison ops", "[rng]") {
+ using Catch::SimplePcg32;
+ REQUIRE(SimplePcg32{} == SimplePcg32{});
+ REQUIRE(SimplePcg32{ 0 } != SimplePcg32{});
+ REQUIRE_FALSE(SimplePcg32{ 1 } == SimplePcg32{ 2 });
+ REQUIRE_FALSE(SimplePcg32{ 1 } != SimplePcg32{ 1 });
+}
+
+TEST_CASE("Random seed generation reports unknown methods", "[rng][seed]") {
+ REQUIRE_THROWS(Catch::generateRandomSeed(static_cast<Catch::GenerateFrom>(77)));
+}
+
+TEST_CASE("Random seed generation accepts known methods", "[rng][seed]") {
+ using Catch::GenerateFrom;
+ const auto method = GENERATE(
+ GenerateFrom::Time,
+ GenerateFrom::RandomDevice,
+ GenerateFrom::Default
+ );
+
+ REQUIRE_NOTHROW(Catch::generateRandomSeed(method));
+}
+
+TEMPLATE_TEST_CASE("uniform_floating_point_distribution never returns infs from finite range",
+ "[rng][distribution][floating-point][approvals]", float, double) {
+ std::random_device rd{};
+ Catch::SimplePcg32 pcg( rd() );
+ Catch::uniform_floating_point_distribution<TestType> dist(
+ -std::numeric_limits<TestType>::max(),
+ std::numeric_limits<TestType>::max() );
+
+ for (size_t i = 0; i < 10'000; ++i) {
+ auto ret = dist( pcg );
+ REQUIRE_FALSE( std::isinf( ret ) );
+ REQUIRE_FALSE( std::isnan( ret ) );
+ }
+}
+
+TEST_CASE( "fillBitsFrom - shortening and stretching", "[rng][approvals]" ) {
+ using Catch::Detail::fillBitsFrom;
+
+ // The seed is not important, but the numbers below have to be repeatable.
+ // They should also exhibit the same general pattern of being prefixes
+ Catch::SimplePcg32 pcg( 0xaabb'ccdd );
+
+ SECTION( "Shorten to 8 bits" ) {
+ // We cast the result to avoid dealing with char-like type in uint8_t
+ auto shortened = static_cast<uint32_t>( fillBitsFrom<uint8_t>( pcg ) );
+ REQUIRE( shortened == 0xcc );
+ }
+ SECTION( "Shorten to 16 bits" ) {
+ auto shortened = fillBitsFrom<uint16_t>( pcg );
+ REQUIRE( shortened == 0xccbe );
+ }
+ SECTION( "Keep at 32 bits" ) {
+ auto n = fillBitsFrom<uint32_t>( pcg );
+ REQUIRE( n == 0xccbe'5f04 );
+ }
+ SECTION( "Stretch to 64 bits" ) {
+ auto stretched = fillBitsFrom<uint64_t>( pcg );
+ REQUIRE( stretched == 0xccbe'5f04'a424'a486 );
+ }
+}
+
+TEST_CASE("uniform_integer_distribution can return the bounds", "[rng][distribution]") {
+ Catch::uniform_integer_distribution<int32_t> dist( -10, 10 );
+ REQUIRE( dist.a() == -10 );
+ REQUIRE( dist.b() == 10 );
+}
+
+namespace {
+ template <typename T>
+ static void CheckReturnValue(Catch::uniform_integer_distribution<T>& dist,
+ Catch::SimplePcg32& rng,
+ T target) {
+ REQUIRE( dist.a() == dist.b() );
+ for (int i = 0; i < 1'000; ++i) {
+ REQUIRE( dist( rng ) == target );
+ }
+ }
+}
+
+TEMPLATE_TEST_CASE( "uniform_integer_distribution can handle unit ranges",
+ "[rng][distribution][approvals]",
+ unsigned char,
+ signed char,
+ char,
+ uint8_t,
+ int8_t,
+ uint16_t,
+ int16_t,
+ uint32_t,
+ int32_t,
+ uint64_t,
+ int64_t,
+ size_t,
+ ptrdiff_t) {
+ // We want random seed to sample different parts of the rng state,
+ // the output is predetermined anyway
+ std::random_device rd;
+ auto seed = rd();
+ CAPTURE( seed );
+ Catch::SimplePcg32 pcg( seed );
+
+ // We check unitary ranges of 3 different values, min for type, max for type,
+ // some value inbetween just to make sure
+ SECTION("lowest value") {
+ constexpr auto lowest = std::numeric_limits<TestType>::min();
+ Catch::uniform_integer_distribution<TestType> dist( lowest, lowest );
+ CheckReturnValue( dist, pcg, lowest );
+ }
+ SECTION( "highest value" ) {
+ constexpr auto highest = std::numeric_limits<TestType>::max();
+ Catch::uniform_integer_distribution<TestType> dist( highest, highest );
+ CheckReturnValue( dist, pcg, highest );
+ }
+ SECTION( "some value" ) {
+ constexpr auto some = TestType( 42 );
+ Catch::uniform_integer_distribution<TestType> dist( some, some );
+ CheckReturnValue( dist, pcg, some );
+ }
+}
+
+// Bool needs its own test because it doesn't have a valid "third" value
+TEST_CASE( "uniform_integer_distribution can handle boolean unit ranges",
+ "[rng][distribution][approvals]" ) {
+ // We want random seed to sample different parts of the rng state,
+ // the output is predetermined anyway
+ std::random_device rd;
+ auto seed = rd();
+ CAPTURE( seed );
+ Catch::SimplePcg32 pcg( seed );
+
+ // We check unitary ranges of 3 different values, min for type, max for
+ // type, some value inbetween just to make sure
+ SECTION( "lowest value" ) {
+ Catch::uniform_integer_distribution<bool> dist( false, false );
+ CheckReturnValue( dist, pcg, false );
+ }
+ SECTION( "highest value" ) {
+ Catch::uniform_integer_distribution<bool> dist( true, true );
+ CheckReturnValue( dist, pcg, true );
+ }
+}
+
+TEMPLATE_TEST_CASE( "uniform_integer_distribution can handle full width ranges",
+ "[rng][distribution][approvals]",
+ unsigned char,
+ signed char,
+ char,
+ uint8_t,
+ int8_t,
+ uint16_t,
+ int16_t,
+ uint32_t,
+ int32_t,
+ uint64_t,
+ int64_t ) {
+ // We want random seed to sample different parts of the rng state,
+ // the output is predetermined anyway
+ std::random_device rd;
+ auto seed = rd();
+ CAPTURE( seed );
+ Catch::SimplePcg32 pcg( seed );
+
+ constexpr auto lowest = std::numeric_limits<TestType>::min();
+ constexpr auto highest = std::numeric_limits<TestType>::max();
+ Catch::uniform_integer_distribution<TestType> dist( lowest, highest );
+ STATIC_REQUIRE( std::is_same<TestType, decltype( dist( pcg ) )>::value );
+
+ // We need to do bit operations on the results, so we will have to
+ // cast them to unsigned type.
+ using BitType = std::make_unsigned_t<TestType>;
+ BitType ORs = 0;
+ BitType ANDs = BitType(-1);
+ for (int i = 0; i < 100; ++i) {
+ auto bits = static_cast<BitType>( dist( pcg ) );
+ ORs |= bits;
+ ANDs &= bits;
+ }
+ // Assuming both our RNG and distribution are unbiased, asking for
+ // the full range should essentially give us random bit generator.
+ // Over long run, OR of all the generated values should have all
+ // bits set to 1, while AND should have all bits set to 0.
+ // The chance of this test failing for unbiased pipeline is
+ // 1 / 2**iters, which for 100 iterations is astronomical.
+ REQUIRE( ORs == BitType( -1 ) );
+ REQUIRE( ANDs == 0 );
+}
+
+namespace {
+ template <typename T>
+ struct uniform_integer_test_params;
+
+ template <>
+ struct uniform_integer_test_params<bool> {
+ static constexpr bool lowest = false;
+ static constexpr bool highest = true;
+ // This seems weird, but it is an artifact of the specific seed
+ static constexpr bool expected[] = { true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ false,
+ true,
+ true,
+ true,
+ true,
+ true,
+ false,
+ true,
+ true };
+ };
+
+ template <>
+ struct uniform_integer_test_params<char> {
+ static constexpr char lowest = 32;
+ static constexpr char highest = 126;
+ static constexpr char expected[] = { 'k',
+ '\\',
+ 'Z',
+ 'X',
+ '`',
+ 'Q',
+ ';',
+ 'o',
+ ']',
+ 'T',
+ 'v',
+ 'p',
+ ':',
+ 'S',
+ 't' };
+ };
+
+ template <>
+ struct uniform_integer_test_params<uint8_t> {
+ static constexpr uint8_t lowest = 3;
+ static constexpr uint8_t highest = 123;
+ static constexpr uint8_t expected[] = { 'c',
+ 'P',
+ 'M',
+ 'J',
+ 'U',
+ 'A',
+ '%',
+ 'h',
+ 'Q',
+ 'F',
+ 'q',
+ 'i',
+ '$',
+ 'E',
+ 'o' };
+ };
+
+ template <>
+ struct uniform_integer_test_params<int8_t> {
+ static constexpr int8_t lowest = -27;
+ static constexpr int8_t highest = 73;
+ static constexpr int8_t expected[] = { '5',
+ '%',
+ '#',
+ ' ',
+ '*',
+ 25,
+ 2,
+ '9',
+ '&',
+ 29,
+ 'A',
+ ':',
+ 1,
+ 28,
+ '?' };
+ };
+
+ template <>
+ struct uniform_integer_test_params<uint16_t> {
+ static constexpr uint16_t lowest = 123;
+ static constexpr uint16_t highest = 33333;
+ static constexpr uint16_t expected[] = { 26684,
+ 21417,
+ 20658,
+ 19791,
+ 22896,
+ 17433,
+ 9806,
+ 27948,
+ 21767,
+ 18588,
+ 30556,
+ 28244,
+ 9439,
+ 18293,
+ 29949 };
+ };
+
+ template <>
+ struct uniform_integer_test_params<int16_t> {
+ static constexpr int16_t lowest = -17222;
+ static constexpr int16_t highest = 17222;
+ static constexpr int16_t expected[] = { 10326,
+ 4863,
+ 4076,
+ 3177,
+ 6397,
+ 731,
+ -7179,
+ 11637,
+ 5226,
+ 1929,
+ 14342,
+ 11944,
+ -7560,
+ 1623,
+ 13712 };
+ };
+
+ template <>
+ struct uniform_integer_test_params<uint32_t> {
+ static constexpr uint32_t lowest = 17222;
+ static constexpr uint32_t highest = 234234;
+ static constexpr uint32_t expected[] = { 190784,
+ 156367,
+ 151409,
+ 145743,
+ 166032,
+ 130337,
+ 80501,
+ 199046,
+ 158654,
+ 137883,
+ 216091,
+ 200981,
+ 78099,
+ 135954,
+ 212120 };
+ };
+
+ template <>
+ struct uniform_integer_test_params<int32_t> {
+ static constexpr int32_t lowest = -237272;
+ static constexpr int32_t highest = 234234;
+ static constexpr int32_t expected[] = { 139829,
+ 65050,
+ 54278,
+ 41969,
+ 86051,
+ 8494,
+ -99785,
+ 157781,
+ 70021,
+ 24890,
+ 194815,
+ 161985,
+ -105004,
+ 20699,
+ 186186 };
+ };
+
+ template <>
+ struct uniform_integer_test_params<uint64_t> {
+ static constexpr uint64_t lowest = 1234;
+ static constexpr uint64_t highest = 1234567890;
+ static constexpr uint64_t expected[] = { 987382749,
+ 763380386,
+ 846572137,
+ 359990258,
+ 804599765,
+ 1131353566,
+ 346324913,
+ 1108760730,
+ 1141693933,
+ 856999148,
+ 879390623,
+ 1149485521,
+ 900556586,
+ 952385958,
+ 807916408 };
+ };
+
+ template <>
+ struct uniform_integer_test_params<int64_t> {
+ static constexpr int64_t lowest = -1234567890;
+ static constexpr int64_t highest = 1234567890;
+ static constexpr int64_t expected[] = { 740197113,
+ 292191940,
+ 458575608,
+ -514589122,
+ 374630781,
+ 1028139036,
+ -541919840,
+ 982953318,
+ 1048819790,
+ 479429651,
+ 524212647,
+ 1064402981,
+ 566544615,
+ 670203462,
+ 381264073 };
+ };
+
+ // We need these definitions for C++14 and earlier, but
+ // GCC will complain about them in newer C++ standards
+#if __cplusplus <= 201402L
+ constexpr bool uniform_integer_test_params<bool>::expected[];
+ constexpr char uniform_integer_test_params<char>::expected[];
+ constexpr uint8_t uniform_integer_test_params<uint8_t>::expected[];
+ constexpr int8_t uniform_integer_test_params<int8_t>::expected[];
+ constexpr uint16_t uniform_integer_test_params<uint16_t>::expected[];
+ constexpr int16_t uniform_integer_test_params<int16_t>::expected[];
+ constexpr uint32_t uniform_integer_test_params<uint32_t>::expected[];
+ constexpr int32_t uniform_integer_test_params<int32_t>::expected[];
+ constexpr uint64_t uniform_integer_test_params<uint64_t>::expected[];
+ constexpr int64_t uniform_integer_test_params<int64_t>::expected[];
+#endif
+
+}
+
+TEMPLATE_TEST_CASE( "uniform_integer_distribution is reproducible",
+ "[rng][distribution][approvals]",
+ bool,
+ char,
+ uint8_t,
+ int8_t,
+ uint16_t,
+ int16_t,
+ uint32_t,
+ int32_t,
+ uint64_t,
+ int64_t) {
+ Catch::SimplePcg32 pcg( 0xaabb'ccdd );
+
+ constexpr auto lowest = uniform_integer_test_params<TestType>::lowest;
+ constexpr auto highest = uniform_integer_test_params<TestType>::highest;
+ Catch::uniform_integer_distribution<TestType> dist(lowest, highest);
+
+ constexpr auto iters = 15;
+ std::array<TestType, iters> generated;
+ for (int i = 0; i < iters; ++i) {
+ generated[i] = dist( pcg );
+ }
+
+ REQUIRE_THAT(generated, Catch::Matchers::RangeEquals(uniform_integer_test_params<TestType>::expected));
+}
+
+// The reproducibility tests assume that operations on `float`/`double`
+// happen in the same precision as the operated-upon type. This is
+// generally true, unless the code is compiled for 32 bit targets without
+// SSE2 enabled, in which case the operations are done in the x87 FPU,
+// which usually implies doing math in 80 bit floats, and then rounding
+// into smaller type when the type is saved into memory. This obviously
+// leads to a different answer, than doing the math in the correct precision.
+#if ( defined( _MSC_VER ) && _M_IX86_FP < 2 ) || \
+ ( defined( __GNUC__ ) && \
+ ( ( defined( __i386__ ) || defined( __x86_64__ ) ) ) && \
+ !defined( __SSE2_MATH__ ) )
+# define CATCH_TEST_CONFIG_DISABLE_FLOAT_REPRODUCIBILITY_TESTS
+#endif
+
+#if !defined( CATCH_TEST_CONFIG_DISABLE_FLOAT_REPRODUCIBILITY_TESTS )
+
+namespace {
+ template <typename T>
+ struct uniform_fp_test_params;
+
+ template<>
+ struct uniform_fp_test_params<float> {
+ // These are exactly representable
+ static constexpr float lowest = -256.125f;
+ static constexpr float highest = 385.125f;
+ // These are just round-trip formatted
+ static constexpr float expected[] = { 92.56961f,
+ -23.170044f,
+ 310.81833f,
+ -53.023132f,
+ 105.03287f,
+ 198.77591f,
+ -172.72931f,
+ 51.805176f,
+ -241.10156f,
+ 64.66101f,
+ 212.12509f,
+ -49.24292f,
+ -177.1399f,
+ 245.23679f,
+ 173.22421f };
+ };
+ template <>
+ struct uniform_fp_test_params<double> {
+ // These are exactly representable
+ static constexpr double lowest = -234582.9921875;
+ static constexpr double highest = 261238.015625;
+ // These are just round-trip formatted
+ static constexpr double expected[] = { 35031.207052832615,
+ 203783.3401838024,
+ 44667.940405848756,
+ -170100.5877224467,
+ -222966.7418051684,
+ 127472.72630072923,
+ -173510.88209096913,
+ 97394.16172239158,
+ 119123.6921592663,
+ 22595.741022785165,
+ 8988.68409120926,
+ 136906.86520606978,
+ 33369.19104222473,
+ 60912.7615841752,
+ -149060.05936760217 };
+ };
+
+// We need these definitions for C++14 and earlier, but
+// GCC will complain about them in newer C++ standards
+#if __cplusplus <= 201402L
+ constexpr float uniform_fp_test_params<float>::expected[];
+ constexpr double uniform_fp_test_params<double>::expected[];
+#endif
+} // namespace
+
+TEMPLATE_TEST_CASE( "uniform_floating_point_distribution is reproducible",
+ "[rng][distribution][floating-point][approvals]",
+ float,
+ double ) {
+ Catch::SimplePcg32 pcg( 0xaabb'aabb );
+
+ const auto lowest = uniform_fp_test_params<TestType>::lowest;
+ const auto highest = uniform_fp_test_params<TestType>::highest;
+ Catch::uniform_floating_point_distribution<TestType> dist( lowest, highest );
+
+ constexpr auto iters = 15;
+ std::array<TestType, iters> generated;
+ for ( int i = 0; i < iters; ++i ) {
+ generated[i] = dist( pcg );
+ }
+
+ REQUIRE_THAT( generated, Catch::Matchers::RangeEquals( uniform_fp_test_params<TestType>::expected ) );
+}
+
+#endif // ^^ float reproducibility tests are enabled
+
+TEMPLATE_TEST_CASE( "uniform_floating_point_distribution can handle unitary ranges",
+ "[rng][distribution][floating-point][approvals]",
+ float,
+ double ) {
+ std::random_device rd;
+ auto seed = rd();
+ CAPTURE( seed );
+ Catch::SimplePcg32 pcg( seed );
+
+ const auto highest = TestType(385.125);
+ Catch::uniform_floating_point_distribution<TestType> dist( highest,
+ highest );
+
+ constexpr auto iters = 20;
+ for (int i = 0; i < iters; ++i) {
+ REQUIRE( Catch::Detail::directCompare( dist( pcg ), highest ) );
+ }
+}
diff --git a/tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp b/tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp
new file mode 100644
index 0000000..86ba117
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp
@@ -0,0 +1,330 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <catch2/catch_test_case_info.hpp>
+#include <catch2/catch_config.hpp>
+#include <catch2/interfaces/catch_interfaces_reporter.hpp>
+#include <catch2/interfaces/catch_interfaces_reporter_factory.hpp>
+#include <catch2/internal/catch_console_colour.hpp>
+#include <catch2/internal/catch_enforce.hpp>
+#include <catch2/internal/catch_list.hpp>
+#include <catch2/internal/catch_reporter_registry.hpp>
+#include <catch2/internal/catch_istream.hpp>
+#include <catch2/matchers/catch_matchers_string.hpp>
+#include <catch2/reporters/catch_reporter_helpers.hpp>
+#include <catch2/reporters/catch_reporter_event_listener.hpp>
+#include <catch2/reporters/catch_reporter_streaming_base.hpp>
+#include <catch2/reporters/catch_reporter_multi.hpp>
+#include <catch2/internal/catch_move_and_forward.hpp>
+
+#include <sstream>
+
+namespace {
+ class StringIStream : public Catch::IStream {
+ public:
+ std::ostream& stream() override { return sstr; }
+ std::string str() const { return sstr.str(); }
+ private:
+ std::stringstream sstr;
+ };
+
+ //! config must outlive the function
+ Catch::ReporterConfig makeDummyRepConfig( Catch::Config const& config ) {
+ return Catch::ReporterConfig{
+ &config,
+ Catch::Detail::make_unique<StringIStream>(),
+ Catch::ColourMode::None,
+ {} };
+ }
+}
+
+TEST_CASE( "The default listing implementation write to provided stream",
+ "[reporters][reporter-helpers]" ) {
+ using Catch::Matchers::ContainsSubstring;
+ using namespace std::string_literals;
+
+ StringIStream sstream;
+ SECTION( "Listing tags" ) {
+ std::vector<Catch::TagInfo> tags(1);
+ tags[0].add("fakeTag"_catch_sr);
+ Catch::defaultListTags(sstream.stream(), tags, false);
+
+ auto listingString = sstream.str();
+ REQUIRE_THAT(listingString, ContainsSubstring("[fakeTag]"s));
+ }
+ SECTION( "Listing reporters" ) {
+ std::vector<Catch::ReporterDescription> reporters(
+ { { "fake reporter", "fake description" } } );
+ Catch::defaultListReporters(sstream.stream(), reporters, Catch::Verbosity::Normal);
+
+ auto listingString = sstream.str();
+ REQUIRE_THAT( listingString,
+ ContainsSubstring( "fake reporter"s ) &&
+ ContainsSubstring( "fake description"s ) );
+ }
+ SECTION( "Listing tests" ) {
+ Catch::TestCaseInfo fakeInfo{
+ ""s,
+ { "fake test name"_catch_sr, "[fakeTestTag]"_catch_sr },
+ { "fake-file.cpp", 123456789 } };
+ std::vector<Catch::TestCaseHandle> tests({ {&fakeInfo, nullptr} });
+ auto colour = Catch::makeColourImpl( Catch::ColourMode::None, &sstream);
+ Catch::defaultListTests(sstream.stream(), colour.get(), tests, false, Catch::Verbosity::Normal);
+
+ auto listingString = sstream.str();
+ REQUIRE_THAT( listingString,
+ ContainsSubstring( "fake test name"s ) &&
+ ContainsSubstring( "fakeTestTag"s ) );
+ }
+ SECTION( "Listing listeners" ) {
+ std::vector<Catch::ListenerDescription> listeners(
+ { { "fakeListener"_catch_sr, "fake description" } } );
+
+ Catch::defaultListListeners( sstream.stream(), listeners );
+ auto listingString = sstream.str();
+ REQUIRE_THAT( listingString,
+ ContainsSubstring( "fakeListener"s ) &&
+ ContainsSubstring( "fake description"s ) );
+ }
+}
+
+TEST_CASE( "Reporter's write listings to provided stream", "[reporters]" ) {
+ using Catch::Matchers::ContainsSubstring;
+ using namespace std::string_literals;
+
+ auto const& factories = Catch::getRegistryHub().getReporterRegistry().getFactories();
+ // If there are no reporters, the test would pass falsely
+ // while there is something obviously broken
+ REQUIRE_FALSE(factories.empty());
+
+ for (auto const& factory : factories) {
+ INFO("Tested reporter: " << factory.first);
+ auto sstream = Catch::Detail::make_unique<StringIStream>();
+ auto& sstreamRef = *sstream;
+
+ Catch::ConfigData cfg_data;
+ cfg_data.rngSeed = 1234;
+ Catch::Config config( cfg_data );
+ auto reporter = factory.second->create( Catch::ReporterConfig{
+ &config, CATCH_MOVE( sstream ), Catch::ColourMode::None, {} } );
+
+ DYNAMIC_SECTION( factory.first << " reporter lists tags" ) {
+ std::vector<Catch::TagInfo> tags(1);
+ tags[0].add("fakeTag"_catch_sr);
+ reporter->listTags(tags);
+
+ auto listingString = sstreamRef.str();
+ REQUIRE_THAT(listingString, ContainsSubstring("fakeTag"s));
+ }
+
+ DYNAMIC_SECTION( factory.first << " reporter lists reporters" ) {
+ std::vector<Catch::ReporterDescription> reporters(
+ { { "fake reporter", "fake description" } } );
+ reporter->listReporters(reporters);
+
+ auto listingString = sstreamRef.str();
+ REQUIRE_THAT(listingString, ContainsSubstring("fake reporter"s));
+ }
+
+ DYNAMIC_SECTION( factory.first << " reporter lists tests" ) {
+ Catch::TestCaseInfo fakeInfo{
+ ""s,
+ { "fake test name"_catch_sr, "[fakeTestTag]"_catch_sr },
+ { "fake-file.cpp", 123456789 } };
+ std::vector<Catch::TestCaseHandle> tests({ {&fakeInfo, nullptr} });
+ reporter->listTests(tests);
+
+ auto listingString = sstreamRef.str();
+ REQUIRE_THAT( listingString,
+ ContainsSubstring( "fake test name"s ) &&
+ ContainsSubstring( "fakeTestTag"s ) );
+ }
+ }
+}
+
+
+TEST_CASE("Reproducer for #2309 - a very long description past 80 chars (default console width) with a late colon : blablabla", "[console-reporter]") {
+ SUCCEED();
+}
+
+namespace {
+ // A listener that writes provided string into destination,
+ // to record order of testRunStarting invocation.
+ class MockListener : public Catch::EventListenerBase {
+ std::string m_witness;
+ std::vector<std::string>& m_recorder;
+ public:
+ MockListener( std::string witness,
+ std::vector<std::string>& recorder,
+ Catch::IConfig const* config ):
+ EventListenerBase( config ),
+ m_witness( CATCH_MOVE(witness) ),
+ m_recorder( recorder )
+ {}
+
+ void testRunStarting( Catch::TestRunInfo const& ) override {
+ m_recorder.push_back( m_witness );
+ }
+ };
+ // A reporter that writes provided string into destination,
+ // to record order of testRunStarting invocation.
+ class MockReporter : public Catch::StreamingReporterBase {
+ std::string m_witness;
+ std::vector<std::string>& m_recorder;
+ public:
+ MockReporter( std::string witness,
+ std::vector<std::string>& recorder,
+ Catch::ReporterConfig&& config ):
+ StreamingReporterBase( CATCH_MOVE(config) ),
+ m_witness( CATCH_MOVE(witness) ),
+ m_recorder( recorder )
+ {}
+
+ void testRunStarting( Catch::TestRunInfo const& ) override {
+ m_recorder.push_back( m_witness );
+ }
+ };
+} // namespace
+
+TEST_CASE("Multireporter calls reporters and listeners in correct order",
+ "[reporters][multi-reporter]") {
+ Catch::Config config( Catch::ConfigData{} );
+
+ // We add reporters before listeners, to check that internally they
+ // get sorted properly, and listeners are called first anyway.
+ Catch::MultiReporter multiReporter( &config );
+ std::vector<std::string> records;
+ multiReporter.addReporter( Catch::Detail::make_unique<MockReporter>(
+ "Goodbye", records, makeDummyRepConfig(config) ) );
+ multiReporter.addListener(
+ Catch::Detail::make_unique<MockListener>( "Hello", records, &config ) );
+ multiReporter.addListener(
+ Catch::Detail::make_unique<MockListener>( "world", records, &config ) );
+ multiReporter.addReporter( Catch::Detail::make_unique<MockReporter>(
+ "world", records, makeDummyRepConfig(config) ) );
+ multiReporter.testRunStarting( { "" } );
+
+ std::vector<std::string> expected( { "Hello", "world", "Goodbye", "world" } );
+ REQUIRE( records == expected );
+}
+
+namespace {
+ // A listener that sets it preferences to test that multireporter,
+ // properly sets up its own preferences
+ class PreferenceListener : public Catch::EventListenerBase {
+ public:
+ PreferenceListener( bool redirectStdout,
+ bool reportAllAssertions,
+ Catch::IConfig const* config ):
+ EventListenerBase( config ) {
+ m_preferences.shouldRedirectStdOut = redirectStdout;
+ m_preferences.shouldReportAllAssertions = reportAllAssertions;
+ }
+ };
+ // A reporter that sets it preferences to test that multireporter,
+ // properly sets up its own preferences
+ class PreferenceReporter : public Catch::StreamingReporterBase {
+ public:
+ PreferenceReporter( bool redirectStdout,
+ bool reportAllAssertions,
+ Catch::ReporterConfig&& config ):
+ StreamingReporterBase( CATCH_MOVE(config) ) {
+ m_preferences.shouldRedirectStdOut = redirectStdout;
+ m_preferences.shouldReportAllAssertions = reportAllAssertions;
+ }
+ };
+} // namespace
+
+TEST_CASE("Multireporter updates ReporterPreferences properly",
+ "[reporters][multi-reporter]") {
+
+ Catch::Config config( Catch::ConfigData{} );
+ Catch::MultiReporter multiReporter( &config );
+
+ // Post init defaults
+ REQUIRE( multiReporter.getPreferences().shouldRedirectStdOut == false );
+ REQUIRE( multiReporter.getPreferences().shouldReportAllAssertions == false );
+
+ SECTION( "Adding listeners" ) {
+ multiReporter.addListener(
+ Catch::Detail::make_unique<PreferenceListener>(
+ true, false, &config ) );
+ REQUIRE( multiReporter.getPreferences().shouldRedirectStdOut == true );
+ REQUIRE( multiReporter.getPreferences().shouldReportAllAssertions == false );
+
+ multiReporter.addListener(
+ Catch::Detail::make_unique<PreferenceListener>(
+ false, true, &config ) );
+ REQUIRE( multiReporter.getPreferences().shouldRedirectStdOut == true );
+ REQUIRE( multiReporter.getPreferences().shouldReportAllAssertions == true);
+
+ multiReporter.addListener(
+ Catch::Detail::make_unique<PreferenceListener>(
+ false, false, &config ) );
+ REQUIRE( multiReporter.getPreferences().shouldRedirectStdOut == true );
+ REQUIRE( multiReporter.getPreferences().shouldReportAllAssertions == true );
+ }
+ SECTION( "Adding reporters" ) {
+ multiReporter.addReporter(
+ Catch::Detail::make_unique<PreferenceReporter>(
+ true, false, makeDummyRepConfig(config) ) );
+ REQUIRE( multiReporter.getPreferences().shouldRedirectStdOut == true );
+ REQUIRE( multiReporter.getPreferences().shouldReportAllAssertions == false );
+
+ multiReporter.addReporter(
+ Catch::Detail::make_unique<PreferenceReporter>(
+ false, true, makeDummyRepConfig( config ) ) );
+ REQUIRE( multiReporter.getPreferences().shouldRedirectStdOut == true );
+ REQUIRE( multiReporter.getPreferences().shouldReportAllAssertions == true );
+
+ multiReporter.addReporter(
+ Catch::Detail::make_unique<PreferenceReporter>(
+ false, false, makeDummyRepConfig( config ) ) );
+ REQUIRE( multiReporter.getPreferences().shouldRedirectStdOut == true );
+ REQUIRE( multiReporter.getPreferences().shouldReportAllAssertions == true );
+ }
+}
+
+namespace {
+ class TestReporterFactory : public Catch::IReporterFactory {
+ Catch::IEventListenerPtr create( Catch::ReporterConfig&& ) const override {
+ CATCH_INTERNAL_ERROR(
+ "This factory should never create a reporter" );
+ }
+ std::string getDescription() const override {
+ return "Fake test factory";
+ }
+ };
+}
+
+TEST_CASE("Registering reporter with '::' in name fails",
+ "[reporters][registration]") {
+ Catch::ReporterRegistry registry;
+
+ REQUIRE_THROWS_WITH( registry.registerReporter(
+ "with::doublecolons",
+ Catch::Detail::make_unique<TestReporterFactory>() ),
+ "'::' is not allowed in reporter name: 'with::doublecolons'" );
+}
+
+TEST_CASE("Registering multiple reporters with the same name fails",
+ "[reporters][registration][approvals]") {
+ Catch::ReporterRegistry registry;
+
+ registry.registerReporter(
+ "some-reporter-name",
+ Catch::Detail::make_unique<TestReporterFactory>() );
+
+ REQUIRE_THROWS_WITH(
+ registry.registerReporter(
+ "some-reporter-name",
+ Catch::Detail::make_unique<TestReporterFactory>() ),
+ "reporter using 'some-reporter-name' as name was already registered" );
+}
diff --git a/tests/SelfTest/IntrospectiveTests/Sharding.tests.cpp b/tests/SelfTest/IntrospectiveTests/Sharding.tests.cpp
new file mode 100644
index 0000000..8e6009d
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/Sharding.tests.cpp
@@ -0,0 +1,45 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/generators/catch_generators_all.hpp>
+
+#include <catch2/internal/catch_sharding.hpp>
+
+#include <unordered_map>
+#include <vector>
+
+TEST_CASE("Sharding Function", "[approvals]") {
+ std::vector<int> testContainer = { 0, 1, 2, 3, 4, 5, 6 };
+ std::unordered_map<int, std::vector<std::size_t>> expectedShardSizes = {
+ {1, {7}},
+ {2, {4, 3}},
+ {3, {3, 2, 2}},
+ {4, {2, 2, 2, 1}},
+ {5, {2, 2, 1, 1, 1}},
+ {6, {2, 1, 1, 1, 1, 1}},
+ {7, {1, 1, 1, 1, 1, 1, 1}},
+ };
+
+ auto shardCount = GENERATE(range(1, 7));
+ auto shardIndex = GENERATE_COPY(filter([=](int i) { return i < shardCount; }, range(0, 6)));
+
+ std::vector<int> result = Catch::createShard(testContainer, shardCount, shardIndex);
+
+ auto& sizes = expectedShardSizes[shardCount];
+ REQUIRE(result.size() == sizes[shardIndex]);
+
+ std::size_t startIndex = 0;
+ for(int i = 0; i < shardIndex; i++) {
+ startIndex += sizes[i];
+ }
+
+ for(std::size_t i = 0; i < sizes[shardIndex]; i++) {
+ CHECK(result[i] == testContainer[i + startIndex]);
+ }
+}
diff --git a/tests/SelfTest/IntrospectiveTests/Stream.tests.cpp b/tests/SelfTest/IntrospectiveTests/Stream.tests.cpp
new file mode 100644
index 0000000..738cb52
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/Stream.tests.cpp
@@ -0,0 +1,32 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <catch2/internal/catch_istream.hpp>
+
+TEST_CASE( "Cout stream properly declares it writes to stdout", "[streams]" ) {
+ REQUIRE( Catch::makeStream( "-" )->isConsole() );
+}
+
+TEST_CASE( "Empty stream name opens cout stream", "[streams]" ) {
+ REQUIRE( Catch::makeStream( "" )->isConsole() );
+}
+
+TEST_CASE( "stdout and stderr streams have %-starting name", "[streams]" ) {
+ REQUIRE( Catch::makeStream( "%stderr" )->isConsole() );
+ REQUIRE( Catch::makeStream( "%stdout" )->isConsole() );
+}
+
+TEST_CASE( "request an unknown %-starting stream fails", "[streams]" ) {
+ REQUIRE_THROWS( Catch::makeStream( "%somestream" ) );
+}
+
+TEST_CASE( "makeStream recognizes %debug stream name", "[streams]" ) {
+ REQUIRE_NOTHROW( Catch::makeStream( "%debug" ) );
+}
diff --git a/tests/SelfTest/IntrospectiveTests/String.tests.cpp b/tests/SelfTest/IntrospectiveTests/String.tests.cpp
new file mode 100644
index 0000000..43c58b4
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/String.tests.cpp
@@ -0,0 +1,212 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/internal/catch_stringref.hpp>
+
+#include <cstring>
+
+TEST_CASE( "StringRef", "[Strings][StringRef]" ) {
+ using Catch::StringRef;
+
+ SECTION( "Empty string" ) {
+ StringRef empty;
+ REQUIRE( empty.empty() );
+ REQUIRE( empty.size() == 0 );
+ REQUIRE( std::strcmp( empty.data(), "" ) == 0 );
+ }
+
+ SECTION( "From string literal" ) {
+ StringRef s = "hello";
+ REQUIRE( s.empty() == false );
+ REQUIRE( s.size() == 5 );
+
+ auto rawChars = s.data();
+ REQUIRE( std::strcmp( rawChars, "hello" ) == 0 );
+
+ REQUIRE(s.data() == rawChars);
+ }
+ SECTION( "From sub-string" ) {
+ StringRef original = StringRef( "original string" ).substr(0, 8);
+ REQUIRE( original == "original" );
+
+ REQUIRE_NOTHROW(original.data());
+ }
+ SECTION( "Copy construction is shallow" ) {
+ StringRef original = StringRef( "original string" );
+ StringRef copy = original;
+ REQUIRE(original.begin() == copy.begin());
+ }
+ SECTION( "Copy assignment is shallow" ) {
+ StringRef original = StringRef( "original string" );
+ StringRef copy;
+ copy = original;
+ REQUIRE(original.begin() == copy.begin());
+ }
+
+ SECTION( "Substrings" ) {
+ StringRef s = "hello world!";
+ StringRef ss = s.substr(0, 5);
+
+ SECTION( "zero-based substring" ) {
+ REQUIRE( ss.empty() == false );
+ REQUIRE( ss.size() == 5 );
+ REQUIRE( std::strncmp( ss.data(), "hello", 5 ) == 0 );
+ REQUIRE( ss == "hello" );
+ }
+
+ SECTION( "non-zero-based substring") {
+ ss = s.substr( 6, 6 );
+ REQUIRE( ss.size() == 6 );
+ REQUIRE( std::strcmp( ss.data(), "world!" ) == 0 );
+ }
+
+ SECTION( "Pointer values of full refs should match" ) {
+ StringRef s2 = s;
+ REQUIRE( s.data() == s2.data() );
+ }
+
+ SECTION( "Pointer values of substring refs should also match" ) {
+ REQUIRE( s.data() == ss.data() );
+ }
+
+ SECTION("Past the end substring") {
+ REQUIRE(s.substr(s.size() + 1, 123).empty());
+ }
+
+ SECTION("Substring off the end are trimmed") {
+ ss = s.substr(6, 123);
+ REQUIRE(std::strcmp(ss.data(), "world!") == 0);
+ }
+ SECTION("substring start after the end is empty") {
+ REQUIRE(s.substr(1'000'000, 1).empty());
+ }
+ }
+
+ SECTION( "Comparisons are deep" ) {
+ char buffer1[] = "Hello";
+ char buffer2[] = "Hello";
+ CHECK(reinterpret_cast<char*>(buffer1) != reinterpret_cast<char*>(buffer2));
+
+ StringRef left(buffer1), right(buffer2);
+ REQUIRE( left == right );
+ REQUIRE(left != left.substr(0, 3));
+ }
+
+ SECTION( "from std::string" ) {
+ std::string stdStr = "a standard string";
+
+ SECTION( "implicitly constructed" ) {
+ StringRef sr = stdStr;
+ REQUIRE( sr == "a standard string" );
+ REQUIRE( sr.size() == stdStr.size() );
+ }
+ SECTION( "explicitly constructed" ) {
+ StringRef sr( stdStr );
+ REQUIRE( sr == "a standard string" );
+ REQUIRE( sr.size() == stdStr.size() );
+ }
+ SECTION( "assigned" ) {
+ StringRef sr;
+ sr = stdStr;
+ REQUIRE( sr == "a standard string" );
+ REQUIRE( sr.size() == stdStr.size() );
+ }
+ }
+
+ SECTION( "to std::string" ) {
+ StringRef sr = "a stringref";
+
+ SECTION( "explicitly constructed" ) {
+ std::string stdStr( sr );
+ REQUIRE( stdStr == "a stringref" );
+ REQUIRE( stdStr.size() == sr.size() );
+ }
+ SECTION( "assigned" ) {
+ std::string stdStr;
+ stdStr = static_cast<std::string>(sr);
+ REQUIRE( stdStr == "a stringref" );
+ REQUIRE( stdStr.size() == sr.size() );
+ }
+ }
+
+ SECTION("std::string += StringRef") {
+ StringRef sr = "the stringref contents";
+ std::string lhs("some string += ");
+ lhs += sr;
+ REQUIRE(lhs == "some string += the stringref contents");
+ }
+ SECTION("StringRef + StringRef") {
+ StringRef sr1 = "abraka", sr2 = "dabra";
+ std::string together = sr1 + sr2;
+ REQUIRE(together == "abrakadabra");
+ }
+}
+
+TEST_CASE("StringRef at compilation time", "[Strings][StringRef][constexpr]") {
+ using Catch::StringRef;
+ SECTION("Simple constructors") {
+ constexpr StringRef empty{};
+ STATIC_REQUIRE(empty.size() == 0);
+ STATIC_REQUIRE(empty.begin() == empty.end());
+
+ constexpr char const* const abc = "abc";
+
+ constexpr StringRef stringref(abc, 3);
+ STATIC_REQUIRE(stringref.size() == 3);
+ STATIC_REQUIRE(stringref.data() == abc);
+ STATIC_REQUIRE(stringref.begin() == abc);
+ STATIC_REQUIRE(stringref.begin() != stringref.end());
+ STATIC_REQUIRE(stringref.substr(10, 0).empty());
+ STATIC_REQUIRE(stringref.substr(2, 1).data() == abc + 2);
+ STATIC_REQUIRE(stringref[1] == 'b');
+
+
+ constexpr StringRef shortened(abc, 2);
+ STATIC_REQUIRE(shortened.size() == 2);
+ STATIC_REQUIRE(shortened.data() == abc);
+ STATIC_REQUIRE(shortened.begin() != shortened.end());
+ }
+ SECTION("UDL construction") {
+ constexpr auto sr1 = "abc"_catch_sr;
+ STATIC_REQUIRE_FALSE(sr1.empty());
+ STATIC_REQUIRE(sr1.size() == 3);
+
+ using Catch::operator""_sr;
+ constexpr auto sr2 = ""_sr;
+ STATIC_REQUIRE(sr2.empty());
+ STATIC_REQUIRE(sr2.size() == 0);
+ }
+}
+
+TEST_CASE("StringRef::compare", "[Strings][StringRef][approvals]") {
+ using Catch::StringRef;
+
+ SECTION("Same length on both sides") {
+ StringRef sr1("abcdc");
+ StringRef sr2("abcdd");
+ StringRef sr3("abcdc");
+
+ REQUIRE(sr1.compare(sr2) < 0);
+ REQUIRE(sr2.compare(sr1) > 0);
+ REQUIRE(sr1.compare(sr3) == 0);
+ REQUIRE(sr3.compare(sr1) == 0);
+ }
+ SECTION("Different lengths") {
+ StringRef sr1("def");
+ StringRef sr2("deff");
+ StringRef sr3("ab");
+
+ REQUIRE(sr1.compare(sr2) < 0);
+ REQUIRE(sr2.compare(sr1) > 0);
+ REQUIRE(sr1.compare(sr3) > 0);
+ REQUIRE(sr2.compare(sr3) > 0);
+ REQUIRE(sr3.compare(sr1) < 0);
+ REQUIRE(sr3.compare(sr2) < 0);
+ }
+}
diff --git a/tests/SelfTest/IntrospectiveTests/StringManip.tests.cpp b/tests/SelfTest/IntrospectiveTests/StringManip.tests.cpp
new file mode 100644
index 0000000..f30573c
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/StringManip.tests.cpp
@@ -0,0 +1,94 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_vector.hpp>
+#include <catch2/internal/catch_string_manip.hpp>
+
+static const char * const no_whitespace = "There is no extra whitespace here";
+static const char * const leading_whitespace = " \r \t\n There is no extra whitespace here";
+static const char * const trailing_whitespace = "There is no extra whitespace here \t \n \r ";
+static const char * const whitespace_at_both_ends = " \r\n \t There is no extra whitespace here \t\t\t \n";
+
+TEST_CASE("Trim strings", "[string-manip]") {
+ using Catch::trim; using Catch::StringRef;
+ static_assert(std::is_same<std::string, decltype(trim(std::string{}))>::value, "Trimming std::string should return std::string");
+ static_assert(std::is_same<StringRef, decltype(trim(StringRef{}))>::value, "Trimming StringRef should return StringRef");
+
+ REQUIRE(trim(std::string(no_whitespace)) == no_whitespace);
+ REQUIRE(trim(std::string(leading_whitespace)) == no_whitespace);
+ REQUIRE(trim(std::string(trailing_whitespace)) == no_whitespace);
+ REQUIRE(trim(std::string(whitespace_at_both_ends)) == no_whitespace);
+
+ REQUIRE(trim(StringRef(no_whitespace)) == StringRef(no_whitespace));
+ REQUIRE(trim(StringRef(leading_whitespace)) == StringRef(no_whitespace));
+ REQUIRE(trim(StringRef(trailing_whitespace)) == StringRef(no_whitespace));
+ REQUIRE(trim(StringRef(whitespace_at_both_ends)) == StringRef(no_whitespace));
+}
+
+TEST_CASE("replaceInPlace", "[string-manip]") {
+ std::string letters = "abcdefcg";
+ SECTION("replace single char") {
+ CHECK(Catch::replaceInPlace(letters, "b", "z"));
+ CHECK(letters == "azcdefcg");
+ }
+ SECTION("replace two chars") {
+ CHECK(Catch::replaceInPlace(letters, "c", "z"));
+ CHECK(letters == "abzdefzg");
+ }
+ SECTION("replace first char") {
+ CHECK(Catch::replaceInPlace(letters, "a", "z"));
+ CHECK(letters == "zbcdefcg");
+ }
+ SECTION("replace last char") {
+ CHECK(Catch::replaceInPlace(letters, "g", "z"));
+ CHECK(letters == "abcdefcz");
+ }
+ SECTION("replace all chars") {
+ CHECK(Catch::replaceInPlace(letters, letters, "replaced"));
+ CHECK(letters == "replaced");
+ }
+ SECTION("replace no chars") {
+ CHECK_FALSE(Catch::replaceInPlace(letters, "x", "z"));
+ CHECK(letters == letters);
+ }
+ SECTION("no replace in already-replaced string") {
+ SECTION("lengthening") {
+ CHECK(Catch::replaceInPlace(letters, "c", "cc"));
+ CHECK(letters == "abccdefccg");
+ }
+ SECTION("shortening") {
+ std::string s = "----";
+ CHECK(Catch::replaceInPlace(s, "--", "-"));
+ CHECK(s == "--");
+ }
+ }
+ SECTION("escape '") {
+ std::string s = "didn't";
+ CHECK(Catch::replaceInPlace(s, "'", "|'"));
+ CHECK(s == "didn|'t");
+ }
+}
+
+TEST_CASE("splitString", "[string-manip]") {
+ using namespace Catch::Matchers;
+ using Catch::splitStringRef;
+ using Catch::StringRef;
+
+ CHECK_THAT(splitStringRef("", ','), Equals(std::vector<StringRef>()));
+ CHECK_THAT(splitStringRef("abc", ','), Equals(std::vector<StringRef>{"abc"}));
+ CHECK_THAT(splitStringRef("abc,def", ','), Equals(std::vector<StringRef>{"abc", "def"}));
+}
+
+TEST_CASE("startsWith", "[string-manip]") {
+ using Catch::startsWith;
+
+ CHECK_FALSE(startsWith("", 'c'));
+ CHECK(startsWith(std::string("abc"), 'a'));
+ CHECK(startsWith("def"_catch_sr, 'd'));
+}
diff --git a/tests/SelfTest/IntrospectiveTests/Tag.tests.cpp b/tests/SelfTest/IntrospectiveTests/Tag.tests.cpp
new file mode 100644
index 0000000..4372375
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/Tag.tests.cpp
@@ -0,0 +1,117 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/matchers/catch_matchers_string.hpp>
+#include <catch2/matchers/catch_matchers_vector.hpp>
+#include <catch2/internal/catch_tag_alias_registry.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/catch_test_case_info.hpp>
+
+TEST_CASE( "Tag alias can be registered against tag patterns" ) {
+
+ Catch::TagAliasRegistry registry;
+
+ registry.add( "[@zzz]", "[one][two]", Catch::SourceLineInfo( "file", 2 ) );
+
+ SECTION( "The same tag alias can only be registered once" ) {
+
+ try {
+ registry.add( "[@zzz]", "[one][two]", Catch::SourceLineInfo( "file", 10 ) );
+ FAIL( "expected exception" );
+ }
+ catch( std::exception& ex ) {
+ std::string what = ex.what();
+ using namespace Catch::Matchers;
+ CHECK_THAT( what, ContainsSubstring( "[@zzz]" ) );
+ CHECK_THAT( what, ContainsSubstring( "file" ) );
+ CHECK_THAT( what, ContainsSubstring( "2" ) );
+ CHECK_THAT( what, ContainsSubstring( "10" ) );
+ }
+ }
+
+ SECTION( "Tag aliases must be of the form [@name]" ) {
+ CHECK_THROWS( registry.add( "[no ampersat]", "", Catch::SourceLineInfo( "file", 3 ) ) );
+ CHECK_THROWS( registry.add( "[the @ is not at the start]", "", Catch::SourceLineInfo( "file", 3 ) ) );
+ CHECK_THROWS( registry.add( "@no square bracket at start]", "", Catch::SourceLineInfo( "file", 3 ) ) );
+ CHECK_THROWS( registry.add( "[@no square bracket at end", "", Catch::SourceLineInfo( "file", 3 ) ) );
+ }
+}
+
+// Dummy line info for creating dummy test cases below
+static constexpr Catch::SourceLineInfo dummySourceLineInfo = CATCH_INTERNAL_LINEINFO;
+
+TEST_CASE("shortened hide tags are split apart", "[tags]") {
+ using Catch::StringRef;
+ using Catch::Tag;
+ using Catch::Matchers::VectorContains;
+
+ Catch::TestCaseInfo testcase("", {"fake test name", "[.magic-tag]"}, dummySourceLineInfo);
+ REQUIRE_THAT( testcase.tags, VectorContains( Tag( "magic-tag" ) )
+ && VectorContains( Tag( "."_catch_sr ) ) );
+}
+
+TEST_CASE("tags with dots in later positions are not parsed as hidden", "[tags]") {
+ using Catch::StringRef;
+ using Catch::Matchers::VectorContains;
+ Catch::TestCaseInfo testcase("", { "fake test name", "[magic.tag]" }, dummySourceLineInfo);
+
+ REQUIRE(testcase.tags.size() == 1);
+ REQUIRE(testcase.tags[0].original == "magic.tag"_catch_sr);
+}
+
+TEST_CASE( "empty tags are not allowed", "[tags]" ) {
+ REQUIRE_THROWS(
+ Catch::TestCaseInfo("", { "test with an empty tag", "[]" }, dummySourceLineInfo)
+ );
+}
+
+TEST_CASE( "Tags with spaces and non-alphanumerical characters are accepted",
+ "[tags]" ) {
+ using Catch::Tag;
+ using Catch::Matchers::VectorContains;
+
+ Catch::TestCaseInfo testCase(
+ "",
+ { "fake test name", "[tag with spaces][I said \"good day\" sir!]" },
+ dummySourceLineInfo );
+
+ REQUIRE( testCase.tags.size() == 2 );
+ REQUIRE_THAT( testCase.tags,
+ VectorContains( Tag( "tag with spaces" ) ) &&
+ VectorContains( Tag( "I said \"good day\" sir!"_catch_sr ) ) );
+}
+
+TEST_CASE( "Test case with identical tags keeps just one", "[tags]" ) {
+ using Catch::Tag;
+
+ Catch::TestCaseInfo testCase(
+ "",
+ { "fake test name", "[TaG1][tAg1][TAG1][tag1]" },
+ dummySourceLineInfo );
+
+ REQUIRE( testCase.tags.size() == 1 );
+ REQUIRE( testCase.tags[0] == Tag( "tag1" ) );
+}
+
+TEST_CASE("Mismatched square brackets in tags are caught and reported",
+ "[tags][approvals]") {
+ using Catch::TestCaseInfo;
+ using Catch::Matchers::ContainsSubstring;
+ REQUIRE_THROWS_WITH( TestCaseInfo( "",
+ { "test with unclosed tag", "[abc" },
+ dummySourceLineInfo ),
+ ContainsSubstring("registering test case 'test with unclosed tag'") );
+ REQUIRE_THROWS_WITH( TestCaseInfo( "",
+ { "test with nested tags", "[abc[def]]" },
+ dummySourceLineInfo ),
+ ContainsSubstring("registering test case 'test with nested tags'") );
+ REQUIRE_THROWS_WITH( TestCaseInfo( "",
+ { "test with superfluous close tags", "[abc][def]]" },
+ dummySourceLineInfo ),
+ ContainsSubstring("registering test case 'test with superfluous close tags'") );
+}
diff --git a/tests/SelfTest/IntrospectiveTests/TestCaseInfoHasher.tests.cpp b/tests/SelfTest/IntrospectiveTests/TestCaseInfoHasher.tests.cpp
new file mode 100644
index 0000000..03cb3f0
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/TestCaseInfoHasher.tests.cpp
@@ -0,0 +1,72 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/catch_test_case_info.hpp>
+#include <catch2/internal/catch_test_case_info_hasher.hpp>
+
+static constexpr Catch::SourceLineInfo dummySourceLineInfo = CATCH_INTERNAL_LINEINFO;
+
+using Catch::TestCaseInfo;
+using Catch::TestCaseInfoHasher;
+
+TEST_CASE("Hashers with same seed produce same hash", "[test-case-hash]") {
+ TestCaseInfo dummy( "", { "name", "[a-tag]" }, dummySourceLineInfo );
+
+ TestCaseInfoHasher h1( 0x12345678 );
+ TestCaseInfoHasher h2( 0x12345678 );
+
+ REQUIRE( h1( dummy ) == h2( dummy ) );
+}
+
+TEST_CASE(
+ "Hashers with different seed produce different hash with same test case",
+ "[test-case-hash]") {
+ TestCaseInfo dummy( "", { "name", "[a-tag]" }, dummySourceLineInfo );
+
+ TestCaseInfoHasher h1( 0x12345678 );
+ TestCaseInfoHasher h2( 0x87654321 );
+
+ REQUIRE( h1( dummy ) != h2( dummy ) );
+}
+
+TEST_CASE("Hashing test case produces same hash across multiple calls",
+ "[test-case-hash]") {
+ TestCaseInfo dummy( "", { "name", "[a-tag]" }, dummySourceLineInfo );
+
+ TestCaseInfoHasher h( 0x12345678 );
+
+ REQUIRE( h( dummy ) == h( dummy ) );
+}
+
+TEST_CASE("Hashing different test cases produces different result", "[test-case-hash]") {
+ TestCaseInfoHasher h( 0x12345678 );
+ SECTION("Different test name") {
+ TestCaseInfo dummy1( "class", { "name-1", "[a-tag]" }, dummySourceLineInfo );
+ TestCaseInfo dummy2(
+ "class", { "name-2", "[a-tag]" }, dummySourceLineInfo );
+
+ REQUIRE( h( dummy1 ) != h( dummy2 ) );
+ }
+ SECTION("Different classname") {
+ TestCaseInfo dummy1(
+ "class-1", { "name", "[a-tag]" }, dummySourceLineInfo );
+ TestCaseInfo dummy2(
+ "class-2", { "name", "[a-tag]" }, dummySourceLineInfo );
+
+ REQUIRE( h( dummy1 ) != h( dummy2 ) );
+ }
+ SECTION("Different tags") {
+ TestCaseInfo dummy1(
+ "class", { "name", "[a-tag]" }, dummySourceLineInfo );
+ TestCaseInfo dummy2(
+ "class", { "name", "[b-tag]" }, dummySourceLineInfo );
+
+ REQUIRE( h( dummy1 ) != h( dummy2 ) );
+ }
+}
diff --git a/tests/SelfTest/IntrospectiveTests/TestSpec.tests.cpp b/tests/SelfTest/IntrospectiveTests/TestSpec.tests.cpp
new file mode 100644
index 0000000..11a7a7a
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/TestSpec.tests.cpp
@@ -0,0 +1,365 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_config.hpp>
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_string.hpp>
+#include <catch2/internal/catch_test_spec_parser.hpp>
+#include <catch2/catch_user_config.hpp>
+#include <catch2/catch_test_case_info.hpp>
+#include <catch2/internal/catch_commandline.hpp>
+#include <catch2/generators/catch_generators.hpp>
+#include <catch2/internal/catch_compiler_capabilities.hpp>
+
+#include <helpers/parse_test_spec.hpp>
+
+namespace {
+ auto fakeTestCase(const char* name, const char* desc = "") { return Catch::makeTestCaseInfo("", { name, desc }, CATCH_INTERNAL_LINEINFO); }
+}
+
+TEST_CASE( "Parse test names and tags", "[command-line][test-spec][approvals]" ) {
+ using Catch::parseTestSpec;
+ using Catch::TestSpec;
+
+ auto tcA = fakeTestCase( "a" );
+ auto tcB = fakeTestCase( "b", "[one][x]" );
+ auto tcC = fakeTestCase( "longer name with spaces", "[two][three][.][x]" );
+ auto tcD = fakeTestCase( "zlonger name with spacesz" );
+
+ SECTION( "Empty test spec should have no filters" ) {
+ TestSpec spec;
+ CHECK( spec.hasFilters() == false );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == false );
+ }
+
+ SECTION( "Test spec from empty string should have no filters" ) {
+ TestSpec spec = parseTestSpec( "" );
+ CHECK( spec.hasFilters() == false );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == false );
+ }
+
+ SECTION( "Test spec from just a comma should have no filters" ) {
+ TestSpec spec = parseTestSpec( "," );
+ CHECK( spec.hasFilters() == false );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == false );
+ }
+
+ SECTION( "Test spec from name should have one filter" ) {
+ TestSpec spec = parseTestSpec( "b" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == true );
+ }
+
+ SECTION( "Test spec from quoted name should have one filter" ) {
+ TestSpec spec = parseTestSpec( "\"b\"" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == true );
+ }
+
+ SECTION( "Test spec from name should have one filter" ) {
+ TestSpec spec = parseTestSpec( "b" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == true );
+ CHECK( spec.matches( *tcC ) == false );
+ }
+
+ SECTION( "Wildcard at the start" ) {
+ TestSpec spec = parseTestSpec( "*spaces" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == false );
+ CHECK( spec.matches( *tcC ) == true );
+ CHECK( spec.matches( *tcD ) == false );
+ CHECK( parseTestSpec( "*a" ).matches( *tcA ) == true );
+ }
+ SECTION( "Wildcard at the end" ) {
+ TestSpec spec = parseTestSpec( "long*" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == false );
+ CHECK( spec.matches( *tcC ) == true );
+ CHECK( spec.matches( *tcD ) == false );
+ CHECK( parseTestSpec( "a*" ).matches( *tcA ) == true );
+ }
+ SECTION( "Wildcard at both ends" ) {
+ TestSpec spec = parseTestSpec( "*name*" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == false );
+ CHECK( spec.matches( *tcC ) == true );
+ CHECK( spec.matches( *tcD ) == true );
+ CHECK( parseTestSpec( "*a*" ).matches( *tcA ) == true );
+ }
+ SECTION( "Redundant wildcard at the start" ) {
+ TestSpec spec = parseTestSpec( "*a" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == true );
+ CHECK( spec.matches( *tcB ) == false );
+ }
+ SECTION( "Redundant wildcard at the end" ) {
+ TestSpec spec = parseTestSpec( "a*" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == true );
+ CHECK( spec.matches( *tcB ) == false );
+ }
+ SECTION( "Redundant wildcard at both ends" ) {
+ TestSpec spec = parseTestSpec( "*a*" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == true );
+ CHECK( spec.matches( *tcB ) == false );
+ }
+ SECTION( "Wildcard at both ends, redundant at start" ) {
+ TestSpec spec = parseTestSpec( "*longer*" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == false );
+ CHECK( spec.matches( *tcC ) == true );
+ CHECK( spec.matches( *tcD ) == true );
+ }
+ SECTION( "Just wildcard" ) {
+ TestSpec spec = parseTestSpec( "*" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == true );
+ CHECK( spec.matches( *tcB ) == true );
+ CHECK( spec.matches( *tcC ) == true );
+ CHECK( spec.matches( *tcD ) == true );
+ }
+
+ SECTION( "Single tag" ) {
+ TestSpec spec = parseTestSpec( "[one]" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == true );
+ CHECK( spec.matches( *tcC ) == false );
+ }
+ SECTION( "Single tag, two matches" ) {
+ TestSpec spec = parseTestSpec( "[x]" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == true );
+ CHECK( spec.matches( *tcC ) == true );
+ }
+ SECTION( "Two tags" ) {
+ TestSpec spec = parseTestSpec( "[two][x]" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == false );
+ CHECK( spec.matches( *tcC ) == true );
+ }
+ SECTION( "Two tags, spare separated" ) {
+ TestSpec spec = parseTestSpec( "[two] [x]" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == false );
+ CHECK( spec.matches( *tcC ) == true );
+ }
+ SECTION( "Wildcarded name and tag" ) {
+ TestSpec spec = parseTestSpec( "*name*[x]" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == false );
+ CHECK( spec.matches( *tcC ) == true );
+ CHECK( spec.matches( *tcD ) == false );
+ }
+ SECTION( "Single tag exclusion" ) {
+ TestSpec spec = parseTestSpec( "~[one]" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == true );
+ CHECK( spec.matches( *tcB ) == false );
+ CHECK( spec.matches( *tcC ) == false );
+ }
+ SECTION( "One tag exclusion and one tag inclusion" ) {
+ TestSpec spec = parseTestSpec( "~[two][x]" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == true );
+ CHECK( spec.matches( *tcC ) == false );
+ }
+ SECTION( "One tag exclusion and one wldcarded name inclusion" ) {
+ TestSpec spec = parseTestSpec( "~[two]*name*" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == false );
+ CHECK( spec.matches( *tcC ) == false );
+ CHECK( spec.matches( *tcD ) == true );
+ }
+ SECTION( "One tag exclusion, using exclude:, and one wldcarded name inclusion" ) {
+ TestSpec spec = parseTestSpec( "exclude:[two]*name*" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == false );
+ CHECK( spec.matches( *tcC ) == false );
+ CHECK( spec.matches( *tcD ) == true );
+ }
+ SECTION( "name exclusion" ) {
+ TestSpec spec = parseTestSpec( "~b" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == true );
+ CHECK( spec.matches( *tcB ) == false );
+ CHECK( spec.matches( *tcC ) == false );
+ CHECK( spec.matches( *tcD ) == true );
+ }
+ SECTION( "wildcarded name exclusion" ) {
+ TestSpec spec = parseTestSpec( "~*name*" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == true );
+ CHECK( spec.matches( *tcB ) == true );
+ CHECK( spec.matches( *tcC ) == false );
+ CHECK( spec.matches( *tcD ) == false );
+ }
+ SECTION( "wildcarded name exclusion with tag inclusion" ) {
+ TestSpec spec = parseTestSpec( "~*name*,[three]" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == true );
+ CHECK( spec.matches( *tcB ) == true );
+ CHECK( spec.matches( *tcC ) == true );
+ CHECK( spec.matches( *tcD ) == false );
+ }
+ SECTION( "wildcarded name exclusion, using exclude:, with tag inclusion" ) {
+ TestSpec spec = parseTestSpec( "exclude:*name*,[three]" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == true );
+ CHECK( spec.matches( *tcB ) == true );
+ CHECK( spec.matches( *tcC ) == true );
+ CHECK( spec.matches( *tcD ) == false );
+ }
+ SECTION( "two wildcarded names" ) {
+ TestSpec spec = parseTestSpec( R"("longer*""*spaces")" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == false );
+ CHECK( spec.matches( *tcC ) == true );
+ CHECK( spec.matches( *tcD ) == false );
+ }
+ SECTION( "empty tag" ) {
+ TestSpec spec = parseTestSpec( "[]" );
+ CHECK( spec.hasFilters() == false );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == false );
+ CHECK( spec.matches( *tcC ) == false );
+ CHECK( spec.matches( *tcD ) == false );
+ }
+ SECTION( "empty quoted name" ) {
+ TestSpec spec = parseTestSpec( "\"\"" );
+ CHECK( spec.hasFilters() == false );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == false );
+ CHECK( spec.matches( *tcC ) == false );
+ CHECK( spec.matches( *tcD ) == false );
+ }
+ SECTION( "quoted string followed by tag exclusion" ) {
+ TestSpec spec = parseTestSpec( "\"*name*\"~[.]" );
+ CHECK( spec.hasFilters() == true );
+ CHECK( spec.matches( *tcA ) == false );
+ CHECK( spec.matches( *tcB ) == false );
+ CHECK( spec.matches( *tcC ) == false );
+ CHECK( spec.matches( *tcD ) == true );
+ }
+ SECTION( "Leading and trailing spaces in test spec" ) {
+ TestSpec spec = parseTestSpec( "\" aardvark \"" );
+ CHECK( spec.matches( *fakeTestCase( " aardvark " ) ) );
+ CHECK( spec.matches( *fakeTestCase( " aardvark" ) ) );
+ CHECK( spec.matches( *fakeTestCase( " aardvark " ) ) );
+ CHECK( spec.matches( *fakeTestCase( "aardvark " ) ) );
+ CHECK( spec.matches( *fakeTestCase( "aardvark" ) ) );
+
+ }
+ SECTION( "Leading and trailing spaces in test name" ) {
+ TestSpec spec = parseTestSpec( "aardvark" );
+ CHECK( spec.matches( *fakeTestCase( " aardvark " ) ) );
+ CHECK( spec.matches( *fakeTestCase( " aardvark" ) ) );
+ CHECK( spec.matches( *fakeTestCase( " aardvark " ) ) );
+ CHECK( spec.matches( *fakeTestCase( "aardvark " ) ) );
+ CHECK( spec.matches( *fakeTestCase( "aardvark" ) ) );
+ }
+ SECTION("Shortened hide tags are split apart when parsing") {
+ TestSpec spec = parseTestSpec("[.foo]");
+ CHECK(spec.matches(*fakeTestCase("hidden and foo", "[.][foo]")));
+ CHECK_FALSE(spec.matches(*fakeTestCase("only foo", "[foo]")));
+ }
+ SECTION("Shortened hide tags also properly handle exclusion") {
+ TestSpec spec = parseTestSpec("~[.foo]");
+ CHECK_FALSE(spec.matches(*fakeTestCase("hidden and foo", "[.][foo]")));
+ CHECK_FALSE(spec.matches(*fakeTestCase("only foo", "[foo]")));
+ CHECK_FALSE(spec.matches(*fakeTestCase("only hidden", "[.]")));
+ CHECK(spec.matches(*fakeTestCase("neither foo nor hidden", "[bar]")));
+ }
+}
+
+TEST_CASE("#1905 -- test spec parser properly clears internal state between compound tests", "[command-line][test-spec]") {
+ using Catch::parseTestSpec;
+ using Catch::TestSpec;
+ // We ask for one of 2 different tests and the latter one of them has a , in name that needs escaping
+ TestSpec spec = parseTestSpec(R"("spec . char","spec \, char")");
+
+ REQUIRE(spec.matches(*fakeTestCase("spec . char")));
+ REQUIRE(spec.matches(*fakeTestCase("spec , char")));
+ REQUIRE_FALSE(spec.matches(*fakeTestCase(R"(spec \, char)")));
+}
+
+TEST_CASE("#1912 -- test spec parser handles escaping", "[command-line][test-spec]") {
+ using Catch::parseTestSpec;
+ using Catch::TestSpec;
+
+ SECTION("Various parentheses") {
+ TestSpec spec = parseTestSpec(R"(spec {a} char,spec \[a] char)");
+
+ REQUIRE(spec.matches(*fakeTestCase(R"(spec {a} char)")));
+ REQUIRE(spec.matches(*fakeTestCase(R"(spec [a] char)")));
+ REQUIRE_FALSE(spec.matches(*fakeTestCase("differs but has similar tag", "[a]")));
+ }
+ SECTION("backslash in test name") {
+ TestSpec spec = parseTestSpec(R"(spec \\ char)");
+
+ REQUIRE(spec.matches(*fakeTestCase(R"(spec \ char)")));
+ }
+}
+
+TEST_CASE("Test spec serialization is round-trippable", "[test-spec][serialization][approvals]") {
+ using Catch::parseTestSpec;
+ using Catch::TestSpec;
+
+ auto serializedTestSpec = []( std::string const& spec ) {
+ Catch::ReusableStringStream sstr;
+ sstr << parseTestSpec( spec );
+ return sstr.str();
+ };
+
+ SECTION("Spaces are normalized") {
+ CHECK( serializedTestSpec( "[abc][def]" ) == "[abc] [def]" );
+ CHECK( serializedTestSpec( "[def] [abc]" ) == "[def] [abc]" );
+ CHECK( serializedTestSpec( "[def] [abc]" ) == "[def] [abc]" );
+ }
+ SECTION("Output is order dependent") {
+ CHECK( serializedTestSpec( "[abc][def]" ) == "[abc] [def]" );
+ CHECK( serializedTestSpec( "[def][abc]" ) == "[def] [abc]" );
+ }
+ SECTION("Multiple disjunct filters") {
+ CHECK( serializedTestSpec( "[abc],[def]" ) == "[abc],[def]" );
+ CHECK( serializedTestSpec( "[def],[abc],[idkfa]" ) == "[def],[abc],[idkfa]" );
+ }
+ SECTION("Test names are enclosed in string") {
+ CHECK( serializedTestSpec( "Some test" ) == "\"Some test\"" );
+ CHECK( serializedTestSpec( "*Some test" ) == "\"*Some test\"" );
+ CHECK( serializedTestSpec( "* Some test" ) == "\"* Some test\"" );
+ CHECK( serializedTestSpec( "* Some test *" ) == "\"* Some test *\"" );
+ }
+ SECTION( "Mixing test names and tags" ) {
+ CHECK( serializedTestSpec( "some test[abcd]" ) ==
+ "\"some test\" [abcd]" );
+ CHECK( serializedTestSpec( "[ab]some test[cd]" ) ==
+ "[ab] \"some test\" [cd]" );
+ }
+}
diff --git a/tests/SelfTest/IntrospectiveTests/TestSpecParser.tests.cpp b/tests/SelfTest/IntrospectiveTests/TestSpecParser.tests.cpp
new file mode 100644
index 0000000..ae27b40
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/TestSpecParser.tests.cpp
@@ -0,0 +1,55 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/generators/catch_generators.hpp>
+#include <catch2/catch_test_case_info.hpp>
+#include <catch2/internal/catch_tag_alias_registry.hpp>
+#include <catch2/internal/catch_test_spec_parser.hpp>
+
+namespace {
+ static constexpr Catch::SourceLineInfo dummySourceLineInfo = CATCH_INTERNAL_LINEINFO;
+
+ static Catch::TestSpec parseAndCreateSpec(std::string const& str) {
+ Catch::TagAliasRegistry registry;
+ Catch::TestSpecParser parser( registry );
+
+ parser.parse( str );
+ auto spec = parser.testSpec();
+ REQUIRE( spec.hasFilters() );
+ REQUIRE( spec.getInvalidSpecs().empty());
+
+ return spec;
+ }
+
+}
+
+TEST_CASE( "Parsing tags with non-alphabetical characters is pass-through",
+ "[test-spec][test-spec-parser]" ) {
+ auto const& tagString = GENERATE( as<std::string>{},
+ "[tag with spaces]",
+ "[I said \"good day\" sir!]" );
+ CAPTURE(tagString);
+
+ auto spec = parseAndCreateSpec( tagString );
+
+ Catch::TestCaseInfo testCase(
+ "", { "fake test name", tagString }, dummySourceLineInfo );
+
+ REQUIRE( spec.matches( testCase ) );
+}
+
+TEST_CASE("Parsed tags are matched case insensitive",
+ "[test-spec][test-spec-parser]") {
+ auto spec = parseAndCreateSpec( "[CASED tag]" );
+
+ Catch::TestCaseInfo testCase(
+ "", { "fake test name", "[cased TAG]" }, dummySourceLineInfo );
+
+ REQUIRE( spec.matches( testCase ) );
+}
diff --git a/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp b/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp
new file mode 100644
index 0000000..de03ed0
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp
@@ -0,0 +1,400 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/internal/catch_textflow.hpp>
+
+#include <sstream>
+
+using Catch::TextFlow::Column;
+using Catch::TextFlow::AnsiSkippingString;
+
+namespace {
+ static std::string as_written(Column const& c) {
+ std::stringstream sstr;
+ sstr << c;
+ return sstr.str();
+ }
+}
+
+TEST_CASE( "TextFlow::Column one simple line",
+ "[TextFlow][column][approvals]" ) {
+ Column col( "simple short line" );
+
+ REQUIRE(as_written(col) == "simple short line");
+}
+
+TEST_CASE( "TextFlow::Column respects already present newlines",
+ "[TextFlow][column][approvals]" ) {
+ Column col( "abc\ndef" );
+ REQUIRE( as_written( col ) == "abc\ndef" );
+}
+
+TEST_CASE( "TextFlow::Column respects width setting",
+ "[TextFlow][column][approvals]" ) {
+ Column col( "The quick brown fox jumped over the lazy dog" );
+
+ SECTION( "width=20" ) {
+ col.width( 20 );
+ REQUIRE( as_written( col ) == "The quick brown fox\n"
+ "jumped over the lazy\n"
+ "dog" );
+ }
+ SECTION("width=10") {
+ col.width( 10 );
+ REQUIRE( as_written( col ) == "The quick\n"
+ "brown fox\n"
+ "jumped\n"
+ "over the\n"
+ "lazy dog" );
+ }
+ SECTION("width=5") {
+ // This is so small some words will have to be split with hyphen
+ col.width(5);
+ REQUIRE( as_written( col ) == "The\n"
+ "quick\n"
+ "brown\n"
+ "fox\n"
+ "jump-\n"
+ "ed\n"
+ "over\n"
+ "the\n"
+ "lazy\n"
+ "dog" );
+ }
+}
+
+TEST_CASE( "TextFlow::Column respects indentation setting",
+ "[TextFlow][column][approvals]" ) {
+ Column col( "First line\nSecond line\nThird line" );
+
+ SECTION("Default: no indentation at all") {
+ REQUIRE(as_written(col) == "First line\nSecond line\nThird line");
+ }
+ SECTION("Indentation on first line only") {
+ col.initialIndent(3);
+ REQUIRE(as_written(col) == " First line\nSecond line\nThird line");
+ }
+ SECTION("Indentation on all lines") {
+ col.indent(3);
+ REQUIRE(as_written(col) == " First line\n Second line\n Third line");
+ }
+ SECTION("Indentation on later lines only") {
+ col.indent(5).initialIndent(0);
+ REQUIRE(as_written(col) == "First line\n Second line\n Third line");
+ }
+ SECTION("Different indentation on first and later lines") {
+ col.initialIndent(1).indent(2);
+ REQUIRE(as_written(col) == " First line\n Second line\n Third line");
+ }
+}
+
+TEST_CASE("TextFlow::Column indentation respects whitespace", "[TextFlow][column][approvals]") {
+ Column col(" text with whitespace\n after newlines");
+
+ SECTION("No extra indentation") {
+ col.initialIndent(0).indent(0);
+ REQUIRE(as_written(col) == " text with whitespace\n after newlines");
+ }
+ SECTION("Different indentation on first and later lines") {
+ col.initialIndent(1).indent(2);
+ REQUIRE(as_written(col) == " text with whitespace\n after newlines");
+ }
+}
+
+TEST_CASE( "TextFlow::Column linebreaking prefers boundary characters",
+ "[TextFlow][column][approvals]" ) {
+ SECTION("parentheses") {
+ Column col("(Hello)aaa(World)");
+ SECTION("width=20") {
+ col.width(20);
+ REQUIRE(as_written(col) == "(Hello)aaa(World)");
+ }
+ SECTION("width=15") {
+ col.width(15);
+ REQUIRE(as_written(col) == "(Hello)aaa\n(World)");
+ }
+ SECTION("width=8") {
+ col.width(8);
+ REQUIRE(as_written(col) == "(Hello)\naaa\n(World)");
+ }
+ }
+ SECTION("commas") {
+ Column col("Hello, world");
+ col.width(8);
+
+ REQUIRE(as_written(col) == "Hello,\nworld");
+ }
+}
+
+
+TEST_CASE( "TextFlow::Column respects indentation for empty lines",
+ "[TextFlow][column][approvals][!shouldfail]" ) {
+ // This is currently bugged and does not do what it should
+ Column col("\n\nthird line");
+ col.indent(2);
+
+ //auto b = col.begin();
+ //auto e = col.end();
+
+ //auto b1 = *b;
+ //++b;
+ //auto b2 = *b;
+ //++b;
+ //auto b3 = *b;
+ //++b;
+
+ //REQUIRE(b == e);
+
+ std::string written = as_written(col);
+
+ REQUIRE(written == " \n \n third line");
+}
+
+TEST_CASE( "TextFlow::Column leading/trailing whitespace",
+ "[TextFlow][column][approvals]" ) {
+ SECTION("Trailing whitespace") {
+ Column col("some trailing whitespace: \t");
+ REQUIRE(as_written(col) == "some trailing whitespace: \t");
+ }
+ SECTION("Some leading whitespace") {
+ Column col("\t \t whitespace wooo");
+ REQUIRE(as_written(col) == "\t \t whitespace wooo");
+ }
+ SECTION("both") {
+ Column col(" abc ");
+ REQUIRE(as_written(col) == " abc ");
+ }
+ SECTION("whitespace only") {
+ Column col("\t \t");
+ REQUIRE(as_written(col) == "\t \t");
+ }
+}
+
+TEST_CASE( "TextFlow::Column can handle empty string",
+ "[TextFlow][column][approvals]" ) {
+ Column col("");
+ REQUIRE(as_written(col) == "");
+}
+
+TEST_CASE( "#1400 - TextFlow::Column wrapping would sometimes duplicate words",
+ "[TextFlow][column][regression][approvals]" ) {
+ const auto long_string = std::string(
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque nisl \n"
+ "massa, luctus ut ligula vitae, suscipit tempus velit. Vivamus sodales, quam in \n"
+ "convallis posuere, libero nisi ultricies orci, nec lobortis.\n");
+
+ auto col = Column(long_string)
+ .width(79)
+ .indent(2);
+
+ REQUIRE(as_written(col) ==
+ " Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque nisl \n"
+ " massa, luctus ut ligula vitae, suscipit tempus velit. Vivamus sodales, quam\n"
+ " in \n"
+ " convallis posuere, libero nisi ultricies orci, nec lobortis.");
+}
+
+TEST_CASE( "TextFlow::AnsiSkippingString skips ansi sequences",
+ "[TextFlow][ansiskippingstring][approvals]" ) {
+
+ SECTION("basic string") {
+ std::string text = "a\033[38;2;98;174;239mb\033[38mc\033[0md\033[me";
+ AnsiSkippingString str(text);
+
+ SECTION( "iterates forward" ) {
+ auto it = str.begin();
+ CHECK(*it == 'a');
+ ++it;
+ CHECK(*it == 'b');
+ ++it;
+ CHECK(*it == 'c');
+ ++it;
+ CHECK(*it == 'd');
+ ++it;
+ CHECK(*it == 'e');
+ ++it;
+ CHECK(it == str.end());
+ }
+ SECTION( "iterates backwards" ) {
+ auto it = str.end();
+ --it;
+ CHECK(*it == 'e');
+ --it;
+ CHECK(*it == 'd');
+ --it;
+ CHECK(*it == 'c');
+ --it;
+ CHECK(*it == 'b');
+ --it;
+ CHECK(*it == 'a');
+ CHECK(it == str.begin());
+ }
+ }
+
+ SECTION( "ansi escape sequences at the start" ) {
+ std::string text = "\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38mc\033[0md\033[me";
+ AnsiSkippingString str(text);
+ auto it = str.begin();
+ CHECK(*it == 'a');
+ ++it;
+ CHECK(*it == 'b');
+ ++it;
+ CHECK(*it == 'c');
+ ++it;
+ CHECK(*it == 'd');
+ ++it;
+ CHECK(*it == 'e');
+ ++it;
+ CHECK(it == str.end());
+ --it;
+ CHECK(*it == 'e');
+ --it;
+ CHECK(*it == 'd');
+ --it;
+ CHECK(*it == 'c');
+ --it;
+ CHECK(*it == 'b');
+ --it;
+ CHECK(*it == 'a');
+ CHECK(it == str.begin());
+ }
+
+ SECTION( "ansi escape sequences at the end" ) {
+ std::string text = "a\033[38;2;98;174;239mb\033[38mc\033[0md\033[me\033[38;2;98;174;239m";
+ AnsiSkippingString str(text);
+ auto it = str.begin();
+ CHECK(*it == 'a');
+ ++it;
+ CHECK(*it == 'b');
+ ++it;
+ CHECK(*it == 'c');
+ ++it;
+ CHECK(*it == 'd');
+ ++it;
+ CHECK(*it == 'e');
+ ++it;
+ CHECK(it == str.end());
+ --it;
+ CHECK(*it == 'e');
+ --it;
+ CHECK(*it == 'd');
+ --it;
+ CHECK(*it == 'c');
+ --it;
+ CHECK(*it == 'b');
+ --it;
+ CHECK(*it == 'a');
+ CHECK(it == str.begin());
+ }
+
+ SECTION( "skips consecutive escapes" ) {
+ std::string text = "\033[38;2;98;174;239m\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38m\033[38m\033[38mc\033[0md\033[me";
+ AnsiSkippingString str(text);
+ auto it = str.begin();
+ CHECK(*it == 'a');
+ ++it;
+ CHECK(*it == 'b');
+ ++it;
+ CHECK(*it == 'c');
+ ++it;
+ CHECK(*it == 'd');
+ ++it;
+ CHECK(*it == 'e');
+ ++it;
+ CHECK(it == str.end());
+ --it;
+ CHECK(*it == 'e');
+ --it;
+ CHECK(*it == 'd');
+ --it;
+ CHECK(*it == 'c');
+ --it;
+ CHECK(*it == 'b');
+ --it;
+ CHECK(*it == 'a');
+ CHECK(it == str.begin());
+ }
+
+ SECTION( "handles incomplete ansi sequences" ) {
+ std::string text = "a\033[b\033[30c\033[30;d\033[30;2e";
+ AnsiSkippingString str(text);
+ CHECK(std::string(str.begin(), str.end()) == text);
+ }
+}
+
+TEST_CASE( "TextFlow::AnsiSkippingString computes the size properly",
+ "[TextFlow][ansiskippingstring][approvals]" ) {
+ std::string text = "\033[38;2;98;174;239m\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38m\033[38m\033[38mc\033[0md\033[me";
+ AnsiSkippingString str(text);
+ CHECK(str.size() == 5);
+}
+
+TEST_CASE( "TextFlow::AnsiSkippingString substrings properly",
+ "[TextFlow][ansiskippingstring][approvals]" ) {
+ SECTION("basic test") {
+ std::string text = "a\033[38;2;98;174;239mb\033[38mc\033[0md\033[me";
+ AnsiSkippingString str(text);
+ auto a = str.begin();
+ auto b = str.begin();
+ ++b;
+ ++b;
+ CHECK(str.substring(a, b) == "a\033[38;2;98;174;239mb\033[38m");
+ ++a;
+ ++b;
+ CHECK(str.substring(a, b) == "b\033[38mc\033[0m");
+ CHECK(str.substring(a, str.end()) == "b\033[38mc\033[0md\033[me");
+ CHECK(str.substring(str.begin(), str.end()) == text);
+ }
+ SECTION("escapes at the start") {
+ std::string text = "\033[38;2;98;174;239m\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38m\033[38m\033[38mc\033[0md\033[me";
+ AnsiSkippingString str(text);
+ auto a = str.begin();
+ auto b = str.begin();
+ ++b;
+ ++b;
+ CHECK(str.substring(a, b) == "\033[38;2;98;174;239m\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38m\033[38m\033[38m");
+ ++a;
+ ++b;
+ CHECK(str.substring(a, b) == "b\033[38m\033[38m\033[38mc\033[0m");
+ CHECK(str.substring(a, str.end()) == "b\033[38m\033[38m\033[38mc\033[0md\033[me");
+ CHECK(str.substring(str.begin(), str.end()) == text);
+ }
+ SECTION("escapes at the end") {
+ std::string text = "a\033[38;2;98;174;239mb\033[38mc\033[0md\033[me\033[38m";
+ AnsiSkippingString str(text);
+ auto a = str.begin();
+ auto b = str.begin();
+ ++b;
+ ++b;
+ CHECK(str.substring(a, b) == "a\033[38;2;98;174;239mb\033[38m");
+ ++a;
+ ++b;
+ CHECK(str.substring(a, b) == "b\033[38mc\033[0m");
+ CHECK(str.substring(a, str.end()) == "b\033[38mc\033[0md\033[me\033[38m");
+ CHECK(str.substring(str.begin(), str.end()) == text);
+ }
+}
+
+TEST_CASE( "TextFlow::Column skips ansi escape sequences",
+ "[TextFlow][column][approvals]" ) {
+ std::string text = "\033[38;2;98;174;239m\033[38;2;198;120;221mThe quick brown \033[38;2;198;120;221mfox jumped over the lazy dog\033[0m";
+ Column col(text);
+
+ SECTION( "width=20" ) {
+ col.width( 20 );
+ REQUIRE( as_written( col ) == "\033[38;2;98;174;239m\033[38;2;198;120;221mThe quick brown \033[38;2;198;120;221mfox\n"
+ "jumped over the lazy\n"
+ "dog\033[0m" );
+ }
+
+ SECTION( "width=80" ) {
+ col.width( 80 );
+ REQUIRE( as_written( col ) == text );
+ }
+}
diff --git a/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp b/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp
new file mode 100644
index 0000000..e190460
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp
@@ -0,0 +1,120 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/internal/catch_enum_values_registry.hpp>
+#include <catch2/matchers/catch_matchers_string.hpp>
+#include <catch2/matchers/catch_matchers_vector.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/catch_template_test_macros.hpp>
+
+#include <chrono>
+
+enum class EnumClass3 { Value1, Value2, Value3, Value4 };
+
+struct UsesSentinel {
+ using const_iterator = int const*;
+ using const_sentinel = std::nullptr_t;
+
+ const_iterator begin() const { return nullptr; }
+ const_iterator end() const { return nullptr; }
+};
+
+TEST_CASE( "parseEnums", "[Strings][enums]" ) {
+ using namespace Catch::Matchers;
+ using Catch::Detail::parseEnums;
+
+ SECTION( "No enums" )
+ CHECK_THAT( parseEnums( "" ), Equals( std::vector<Catch::StringRef>{} ) );
+
+ SECTION( "One enum value" ) {
+ CHECK_THAT( parseEnums( "ClassName::EnumName::Value1" ),
+ Equals(std::vector<Catch::StringRef>{"Value1"} ) );
+ CHECK_THAT( parseEnums( "Value1" ),
+ Equals( std::vector<Catch::StringRef>{"Value1"} ) );
+ CHECK_THAT( parseEnums( "EnumName::Value1" ),
+ Equals(std::vector<Catch::StringRef>{"Value1"} ) );
+ }
+
+ SECTION( "Multiple enum values" ) {
+ CHECK_THAT( parseEnums( "ClassName::EnumName::Value1, ClassName::EnumName::Value2" ),
+ Equals( std::vector<Catch::StringRef>{"Value1", "Value2"} ) );
+ CHECK_THAT( parseEnums( "ClassName::EnumName::Value1, ClassName::EnumName::Value2, ClassName::EnumName::Value3" ),
+ Equals( std::vector<Catch::StringRef>{"Value1", "Value2", "Value3"} ) );
+ CHECK_THAT( parseEnums( "ClassName::EnumName::Value1,ClassName::EnumName::Value2 , ClassName::EnumName::Value3" ),
+ Equals( std::vector<Catch::StringRef>{"Value1", "Value2", "Value3"} ) );
+ }
+}
+
+TEST_CASE( "Directly creating an EnumInfo" ) {
+
+ using namespace Catch::Detail;
+ auto enumInfo = makeEnumInfo( "EnumName", "EnumName::Value1, EnumName::Value2", {0, 1} );
+
+ CHECK( enumInfo->lookup(0) == "Value1" );
+ CHECK( enumInfo->lookup(1) == "Value2" );
+ CHECK( enumInfo->lookup(3) == "{** unexpected enum value **}" );
+}
+
+TEST_CASE("Range type with sentinel") {
+ CHECK( Catch::Detail::stringify(UsesSentinel{}) == "{ }" );
+}
+
+TEST_CASE("convertIntoString stringification helper", "[toString][approvals]") {
+ using namespace std::string_literals;
+ using Catch::Detail::convertIntoString;
+ using namespace Catch;
+
+ SECTION("No escaping") {
+ CHECK(convertIntoString(""_sr, false) == R"("")"s);
+ CHECK(convertIntoString("abcd"_sr, false) == R"("abcd")"s);
+ CHECK(convertIntoString("ab\ncd"_sr, false) == "\"ab\ncd\""s);
+ CHECK(convertIntoString("ab\r\ncd"_sr, false) == "\"ab\r\ncd\""s);
+ CHECK(convertIntoString("ab\"cd"_sr, false) == R"("ab"cd")"s);
+ }
+ SECTION("Escaping invisibles") {
+ CHECK(convertIntoString(""_sr, true) == R"("")"s);
+ CHECK(convertIntoString("ab\ncd"_sr, true) == R"("ab\ncd")"s);
+ CHECK(convertIntoString("ab\r\ncd"_sr, true) == R"("ab\r\ncd")"s);
+ CHECK(convertIntoString("ab\tcd"_sr, true) == R"("ab\tcd")"s);
+ CHECK(convertIntoString("ab\fcd"_sr, true) == R"("ab\fcd")"s);
+ CHECK(convertIntoString("ab\"cd"_sr, true) == R"("ab"cd")"s);
+ }
+}
+
+TEMPLATE_TEST_CASE( "Stringifying char arrays with statically known sizes",
+ "[toString]",
+ char,
+ signed char,
+ unsigned char ) {
+ using namespace std::string_literals;
+ TestType with_null_terminator[10] = "abc";
+ CHECK( ::Catch::Detail::stringify( with_null_terminator ) == R"("abc")"s );
+
+ TestType no_null_terminator[3] = { 'a', 'b', 'c' };
+ CHECK( ::Catch::Detail::stringify( no_null_terminator ) == R"("abc")"s );
+}
+
+TEST_CASE( "#2944 - Stringifying dates before 1970 should not crash", "[.approvals]" ) {
+ using Catch::Matchers::Equals;
+ using Days = std::chrono::duration<int32_t, std::ratio<86400>>;
+ using SysDays = std::chrono::time_point<std::chrono::system_clock, Days>;
+ Catch::StringMaker<std::chrono::system_clock::time_point> sm;
+
+ // Check simple date first
+ const SysDays post1970{ Days{ 1 } };
+ auto converted_post = sm.convert( post1970 );
+ REQUIRE( converted_post == "1970-01-02T00:00:00Z" );
+
+ const SysDays pre1970{ Days{ -1 } };
+ auto converted_pre = sm.convert( pre1970 );
+ REQUIRE_THAT(
+ converted_pre,
+ Equals( "1969-12-31T00:00:00Z" ) ||
+ Equals( "gmtime from provided timepoint has failed. This "
+ "happens e.g. with pre-1970 dates using Microsoft libc" ) );
+}
diff --git a/tests/SelfTest/IntrospectiveTests/Traits.tests.cpp b/tests/SelfTest/IntrospectiveTests/Traits.tests.cpp
new file mode 100644
index 0000000..459e0d4
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/Traits.tests.cpp
@@ -0,0 +1,45 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/internal/catch_compare_traits.hpp>
+#include <helpers/type_with_lit_0_comparisons.hpp>
+
+
+#define ADD_TRAIT_TEST_CASE( op ) \
+ TEST_CASE( "is_" #op "_comparable", \
+ "[traits][is_comparable][approvals]" ) { \
+ using Catch::Detail::is_##op##_0_comparable; \
+ using Catch::Detail::is_##op##_comparable; \
+ \
+ STATIC_REQUIRE( is_##op##_comparable<int, int>::value ); \
+ STATIC_REQUIRE( \
+ is_##op##_comparable<std::string, std::string>::value ); \
+ STATIC_REQUIRE( !is_##op##_comparable<int, std::string>::value ); \
+ STATIC_REQUIRE( \
+ !is_##op##_comparable<TypeWithLit0Comparisons, int>::value ); \
+ STATIC_REQUIRE( \
+ !is_##op##_comparable<int, TypeWithLit0Comparisons>::value ); \
+ \
+ STATIC_REQUIRE( is_##op##_0_comparable<int>::value ); \
+ STATIC_REQUIRE( \
+ is_##op##_0_comparable<TypeWithLit0Comparisons>::value ); \
+ STATIC_REQUIRE( !is_##op##_0_comparable<std::string>::value ); \
+ \
+ /* This test fails with MSVC in permissive mode, because of course it does */ \
+ /* STATIC_REQUIRE( !is_##op##_0_comparable<int*>::value ); */ \
+}
+
+ADD_TRAIT_TEST_CASE(lt)
+ADD_TRAIT_TEST_CASE(gt)
+ADD_TRAIT_TEST_CASE(le)
+ADD_TRAIT_TEST_CASE(ge)
+ADD_TRAIT_TEST_CASE(eq)
+ADD_TRAIT_TEST_CASE(ne)
+
+#undef ADD_TRAIT_TEST_CASE
diff --git a/tests/SelfTest/IntrospectiveTests/UniquePtr.tests.cpp b/tests/SelfTest/IntrospectiveTests/UniquePtr.tests.cpp
new file mode 100644
index 0000000..420bf1b
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/UniquePtr.tests.cpp
@@ -0,0 +1,141 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/internal/catch_unique_ptr.hpp>
+
+#include <tuple>
+
+namespace {
+ struct unique_ptr_test_helper {
+ bool dummy = false;
+ };
+} // end unnamed namespace
+
+TEST_CASE("unique_ptr reimplementation: basic functionality", "[internals][unique-ptr]") {
+ using Catch::Detail::unique_ptr;
+ SECTION("Default constructed unique_ptr is empty") {
+ unique_ptr<int> ptr;
+ REQUIRE_FALSE(ptr);
+ REQUIRE(ptr.get() == nullptr);
+ }
+ SECTION("Take ownership of allocation") {
+ auto naked_ptr = new int{ 0 };
+ unique_ptr<int> ptr(naked_ptr);
+ REQUIRE(ptr);
+ REQUIRE(*ptr == 0);
+ REQUIRE(ptr.get() == naked_ptr);
+ SECTION("Plain reset deallocates") {
+ ptr.reset(); // this makes naked_ptr dangling!
+ REQUIRE_FALSE(ptr);
+ REQUIRE(ptr.get() == nullptr);
+ }
+ SECTION("Reset replaces ownership") {
+ ptr.reset(new int{ 2 });
+ REQUIRE(ptr);
+ REQUIRE(ptr.get() != nullptr);
+ REQUIRE(*ptr == 2);
+ }
+ }
+ SECTION("Release releases ownership") {
+ auto naked_ptr = new int{ 1 };
+ unique_ptr<int> ptr(naked_ptr);
+ ptr.release();
+ CHECK_FALSE(ptr);
+ CHECK(ptr.get() == nullptr);
+ delete naked_ptr;
+ }
+ SECTION("Move constructor") {
+ unique_ptr<int> ptr1(new int{ 1 });
+ auto ptr2(std::move(ptr1));
+ REQUIRE_FALSE(ptr1);
+ REQUIRE(ptr2);
+ REQUIRE(*ptr2 == 1);
+ }
+ SECTION("Move assignment") {
+ unique_ptr<int> ptr1(new int{ 1 }), ptr2(new int{ 2 });
+ ptr1 = std::move(ptr2);
+ REQUIRE_FALSE(ptr2);
+ REQUIRE(ptr1);
+ REQUIRE(*ptr1 == 2);
+ }
+ SECTION("free swap") {
+ unique_ptr<int> ptr1(new int{ 1 }), ptr2(new int{ 2 });
+ swap(ptr1, ptr2);
+ REQUIRE(*ptr1 == 2);
+ REQUIRE(*ptr2 == 1);
+ }
+}
+
+
+namespace {
+ struct base {
+ int i;
+ base(int i_) :i(i_) {}
+ };
+ struct derived : base { using base::base; };
+ struct unrelated {};
+
+} // end unnamed namespace
+
+static_assert( std::is_constructible<Catch::Detail::unique_ptr<base>,
+ Catch::Detail::unique_ptr<derived>>::value, "Upcasting is supported");
+static_assert(!std::is_constructible<Catch::Detail::unique_ptr<derived>,
+ Catch::Detail::unique_ptr<base>>::value, "Downcasting is not supported");
+static_assert(!std::is_constructible<Catch::Detail::unique_ptr<base>,
+ Catch::Detail::unique_ptr<unrelated>>::value, "Cannot just convert one ptr type to another");
+
+TEST_CASE("Upcasting special member functions", "[internals][unique-ptr]") {
+ using Catch::Detail::unique_ptr;
+
+ unique_ptr<derived> dptr(new derived{3});
+ SECTION("Move constructor") {
+ unique_ptr<base> bptr(std::move(dptr));
+ REQUIRE(bptr->i == 3);
+ }
+ SECTION("move assignment") {
+ unique_ptr<base> bptr(new base{ 1 });
+ bptr = std::move(dptr);
+ REQUIRE(bptr->i == 3);
+ }
+}
+
+namespace {
+ struct move_detector {
+ bool has_moved = false;
+ move_detector() = default;
+ move_detector(move_detector const& rhs) = default;
+ move_detector& operator=(move_detector const& rhs) = default;
+
+ move_detector(move_detector&& rhs) noexcept {
+ rhs.has_moved = true;
+ }
+ move_detector& operator=(move_detector&& rhs) noexcept {
+ rhs.has_moved = true;
+ return *this;
+ }
+ };
+} // end unnamed namespace
+
+TEST_CASE("make_unique reimplementation", "[internals][unique-ptr]") {
+ using Catch::Detail::make_unique;
+ SECTION("From lvalue copies") {
+ move_detector lval;
+ auto ptr = make_unique<move_detector>(lval);
+ REQUIRE_FALSE(lval.has_moved);
+ }
+ SECTION("From rvalue moves") {
+ move_detector rval;
+ auto ptr = make_unique<move_detector>(std::move(rval));
+ REQUIRE(rval.has_moved);
+ }
+ SECTION("Variadic constructor") {
+ auto ptr = make_unique<std::tuple<int, double, int>>(1, 2., 3);
+ REQUIRE(*ptr == std::tuple<int, double, int>{1, 2., 3});
+ }
+}
diff --git a/tests/SelfTest/IntrospectiveTests/Xml.tests.cpp b/tests/SelfTest/IntrospectiveTests/Xml.tests.cpp
new file mode 100644
index 0000000..b5982b8
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/Xml.tests.cpp
@@ -0,0 +1,183 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/internal/catch_xmlwriter.hpp>
+
+#include <catch2/internal/catch_reusable_string_stream.hpp>
+#include <catch2/matchers/catch_matchers_string.hpp>
+
+#include <sstream>
+
+static std::string encode( std::string const& str, Catch::XmlEncode::ForWhat forWhat = Catch::XmlEncode::ForTextNodes ) {
+ Catch::ReusableStringStream oss;
+ oss << Catch::XmlEncode( str, forWhat );
+ return oss.str();
+}
+
+TEST_CASE( "XmlEncode", "[XML]" ) {
+ SECTION( "normal string" ) {
+ REQUIRE( encode( "normal string" ) == "normal string" );
+ }
+ SECTION( "empty string" ) {
+ REQUIRE( encode( "" ) == "" );
+ }
+ SECTION( "string with ampersand" ) {
+ REQUIRE( encode( "smith & jones" ) == "smith &amp; jones" );
+ }
+ SECTION( "string with less-than" ) {
+ REQUIRE( encode( "smith < jones" ) == "smith &lt; jones" );
+ }
+ SECTION( "string with greater-than" ) {
+ REQUIRE( encode( "smith > jones" ) == "smith > jones" );
+ REQUIRE( encode( "smith ]]> jones" ) == "smith ]]&gt; jones" );
+ }
+ SECTION( "string with quotes" ) {
+ std::string stringWithQuotes = "don't \"quote\" me on that";
+ REQUIRE( encode( stringWithQuotes ) == stringWithQuotes );
+ REQUIRE( encode( stringWithQuotes, Catch::XmlEncode::ForAttributes ) == "don't &quot;quote&quot; me on that" );
+ }
+ SECTION( "string with control char (1)" ) {
+ REQUIRE( encode( "[\x01]" ) == "[\\x01]" );
+ }
+ SECTION( "string with control char (x7F)" ) {
+ REQUIRE( encode( "[\x7F]" ) == "[\\x7F]" );
+ }
+}
+
+// Thanks to Peter Bindels (dascandy) for some of the tests
+TEST_CASE("XmlEncode: UTF-8", "[XML][UTF-8][approvals]") {
+ SECTION("Valid utf-8 strings") {
+ CHECK(encode("Here be 👾") == "Here be 👾");
+ CHECK(encode("šš") == "šš");
+
+ CHECK(encode("\xDF\xBF") == "\xDF\xBF"); // 0x7FF
+ CHECK(encode("\xE0\xA0\x80") == "\xE0\xA0\x80"); // 0x800
+ CHECK(encode("\xED\x9F\xBF") == "\xED\x9F\xBF"); // 0xD7FF
+ CHECK(encode("\xEE\x80\x80") == "\xEE\x80\x80"); // 0xE000
+ CHECK(encode("\xEF\xBF\xBF") == "\xEF\xBF\xBF"); // 0xFFFF
+ CHECK(encode("\xF0\x90\x80\x80") == "\xF0\x90\x80\x80"); // 0x10000
+ CHECK(encode("\xF4\x8F\xBF\xBF") == "\xF4\x8F\xBF\xBF"); // 0x10FFFF
+ }
+ SECTION("Invalid utf-8 strings") {
+ SECTION("Various broken strings") {
+ CHECK(encode("Here \xFF be \xF0\x9F\x91\xBE") == "Here \\xFF be 👾");
+ CHECK(encode("\xFF") == "\\xFF");
+ CHECK(encode("\xC5\xC5\xA0") == "\\xC5Å ");
+ CHECK(encode("\xF4\x90\x80\x80") == "\\xF4\\x90\\x80\\x80"); // 0x110000 -- out of unicode range
+ }
+
+ SECTION("Overlong encodings") {
+ CHECK(encode("\xC0\x80") == "\\xC0\\x80"); // \0
+ CHECK(encode("\xF0\x80\x80\x80") == "\\xF0\\x80\\x80\\x80"); // Super-over-long \0
+ CHECK(encode("\xC1\xBF") == "\\xC1\\xBF"); // ASCII char as UTF-8 (0x7F)
+ CHECK(encode("\xE0\x9F\xBF") == "\\xE0\\x9F\\xBF"); // 0x7FF
+ CHECK(encode("\xF0\x8F\xBF\xBF") == "\\xF0\\x8F\\xBF\\xBF"); // 0xFFFF
+ }
+
+ // Note that we actually don't modify surrogate pairs, as we do not do strict checking
+ SECTION("Surrogate pairs") {
+ CHECK(encode("\xED\xA0\x80") == "\xED\xA0\x80"); // Invalid surrogate half 0xD800
+ CHECK(encode("\xED\xAF\xBF") == "\xED\xAF\xBF"); // Invalid surrogate half 0xDBFF
+ CHECK(encode("\xED\xB0\x80") == "\xED\xB0\x80"); // Invalid surrogate half 0xDC00
+ CHECK(encode("\xED\xBF\xBF") == "\xED\xBF\xBF"); // Invalid surrogate half 0xDFFF
+ }
+
+ SECTION("Invalid start byte") {
+ CHECK(encode("\x80") == "\\x80");
+ CHECK(encode("\x81") == "\\x81");
+ CHECK(encode("\xBC") == "\\xBC");
+ CHECK(encode("\xBF") == "\\xBF");
+ // Out of range
+ CHECK(encode("\xF5\x80\x80\x80") == "\\xF5\\x80\\x80\\x80");
+ CHECK(encode("\xF6\x80\x80\x80") == "\\xF6\\x80\\x80\\x80");
+ CHECK(encode("\xF7\x80\x80\x80") == "\\xF7\\x80\\x80\\x80");
+ }
+
+ SECTION("Missing continuation byte(s)") {
+ // Missing first continuation byte
+ CHECK(encode("\xDE") == "\\xDE");
+ CHECK(encode("\xDF") == "\\xDF");
+ CHECK(encode("\xE0") == "\\xE0");
+ CHECK(encode("\xEF") == "\\xEF");
+ CHECK(encode("\xF0") == "\\xF0");
+ CHECK(encode("\xF4") == "\\xF4");
+
+ // Missing second continuation byte
+ CHECK(encode("\xE0\x80") == "\\xE0\\x80");
+ CHECK(encode("\xE0\xBF") == "\\xE0\\xBF");
+ CHECK(encode("\xE1\x80") == "\\xE1\\x80");
+ CHECK(encode("\xF0\x80") == "\\xF0\\x80");
+ CHECK(encode("\xF4\x80") == "\\xF4\\x80");
+
+ // Missing third continuation byte
+ CHECK(encode("\xF0\x80\x80") == "\\xF0\\x80\\x80");
+ CHECK(encode("\xF4\x80\x80") == "\\xF4\\x80\\x80");
+ }
+ }
+}
+
+TEST_CASE("XmlWriter writes boolean attributes as true/false", "[XML][XmlWriter]") {
+ using Catch::Matchers::ContainsSubstring;
+ std::stringstream stream;
+ {
+ Catch::XmlWriter xml(stream);
+
+ xml.scopedElement("Element1")
+ .writeAttribute("attr1", true)
+ .writeAttribute("attr2", false);
+ }
+
+ REQUIRE_THAT( stream.str(),
+ ContainsSubstring(R"(attr1="true")") &&
+ ContainsSubstring(R"(attr2="false")") );
+}
+
+TEST_CASE("XmlWriter does not escape comments", "[XML][XmlWriter][approvals]") {
+ using Catch::Matchers::ContainsSubstring;
+ std::stringstream stream;
+ {
+ Catch::XmlWriter xml(stream);
+
+ xml.writeComment(R"(unescaped special chars: < > ' " &)");
+ }
+ REQUIRE_THAT( stream.str(),
+ ContainsSubstring(R"(<!-- unescaped special chars: < > ' " & -->)"));
+}
+
+TEST_CASE("XmlWriter errors out when writing text without enclosing element", "[XmlWriter][approvals]") {
+ std::stringstream stream;
+ Catch::XmlWriter xml(stream);
+ REQUIRE_THROWS(xml.writeText("some text"));
+}
+
+TEST_CASE("XmlWriter escapes text properly", "[XML][XmlWriter][approvals]") {
+ using Catch::Matchers::ContainsSubstring;
+ std::stringstream stream;
+ {
+ Catch::XmlWriter xml(stream);
+ xml.scopedElement("root")
+ .writeText(R"(Special chars need escaping: < > ' " &)");
+ }
+
+ REQUIRE_THAT( stream.str(),
+ ContainsSubstring(R"(Special chars need escaping: &lt; > ' " &amp;)"));
+}
+
+TEST_CASE("XmlWriter escapes attributes properly", "[XML][XmlWriter][approvals]") {
+ using Catch::Matchers::ContainsSubstring;
+ std::stringstream stream;
+ {
+ Catch::XmlWriter xml(stream);
+ xml.scopedElement("root")
+ .writeAttribute("some-attribute", R"(Special chars need escaping: < > ' " &)");
+ }
+
+ REQUIRE_THAT(stream.str(),
+ ContainsSubstring(R"(some-attribute="Special chars need escaping: &lt; > ' &quot; &amp;")"));
+}