aboutsummaryrefslogtreecommitdiffhomepage
path: root/tests/SelfTest/IntrospectiveTests
diff options
context:
space:
mode:
authorMerry <[email protected]>2022-12-31 17:28:39 +0000
committerMerry <[email protected]>2022-12-31 17:28:39 +0000
commit6879e5bb1c598a9a517d7bdec8ba1a0bace3ef10 (patch)
tree93366e12251f5e25017fa5dc2050d423cdf3a30b /tests/SelfTest/IntrospectiveTests
downloaddynarmic-6879e5bb1c598a9a517d7bdec8ba1a0bace3ef10.tar.gz
dynarmic-6879e5bb1c598a9a517d7bdec8ba1a0bace3ef10.zip
Squashed 'externals/catch/' content from commit ab6c7375b
git-subtree-dir: externals/catch git-subtree-split: ab6c7375be9a8e71ee84c6f8537113f9f47daf99
Diffstat (limited to 'tests/SelfTest/IntrospectiveTests')
-rw-r--r--tests/SelfTest/IntrospectiveTests/Clara.tests.cpp73
-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.cpp131
-rw-r--r--tests/SelfTest/IntrospectiveTests/FloatingPoint.tests.cpp74
-rw-r--r--tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp536
-rw-r--r--tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp447
-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.cpp62
-rw-r--r--tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp329
-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.cpp83
-rw-r--r--tests/SelfTest/IntrospectiveTests/Tag.tests.cpp104
-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.cpp200
-rw-r--r--tests/SelfTest/IntrospectiveTests/ToString.tests.cpp97
-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
25 files changed, 4220 insertions, 0 deletions
diff --git a/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp b/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp
new file mode 100644
index 00000000..14ba433d
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp
@@ -0,0 +1,73 @@
+
+// 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::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 00000000..404bad27
--- /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 00000000..4df8ecf9
--- /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 00000000..615fda1d
--- /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 00000000..a5a43926
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/Details.tests.cpp
@@ -0,0 +1,131 @@
+
+// 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 );
+ }
+}
+
+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 00000000..08a579c9
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/FloatingPoint.tests.cpp
@@ -0,0 +1,74 @@
+
+// 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>
+
+
+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 );
+}
diff --git a/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp b/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp
new file mode 100644
index 00000000..1c203b6b
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp
@@ -0,0 +1,536 @@
+
+// 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 <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");
+ 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();
+ }
+ }
+}
diff --git a/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp b/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp
new file mode 100644
index 00000000..96c0977b
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/InternalBenchmark.tests.cpp
@@ -0,0 +1,447 @@
+
+// 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>
+
+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);
+
+ using it = std::vector<double>::iterator;
+ auto e = Catch::Benchmark::Detail::bootstrap(0.95, samples.begin(), samples.end(), samples, [](it a, it 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]") {
+ 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.begin(), x.end());
+
+ 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.begin(), x.end());
+ auto med = Catch::Benchmark::Detail::weighted_average_quantile(1, 2, x.begin(), x.end());
+ auto q3 = Catch::Benchmark::Detail::weighted_average_quantile(3, 4, x.begin(), x.end());
+
+ 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.begin(), x.end());
+
+ 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.begin(), x.end());
+
+ 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.begin(), x.end());
+
+ 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.begin(), x.end());
+
+ 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.begin(), x.end());
+
+ 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.begin(), x.end());
+
+ 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 Duration = Catch::Benchmark::FloatDuration<Catch::Benchmark::default_clock>;
+
+ Catch::Benchmark::Environment<Duration> env;
+ std::vector<Duration> samples(99);
+ for (size_t i = 0; i < samples.size(); ++i) {
+ samples[i] = Duration(23 + (i % 3 - 1));
+ }
+
+ auto analysis = Catch::Benchmark::Detail::analyse(config, env, samples.begin(), samples.end());
+ 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 Duration = Catch::Benchmark::FloatDuration<Catch::Benchmark::default_clock>;
+
+ Catch::Benchmark::Environment<Duration> env;
+ std::vector<Duration> samples(99);
+ for (size_t i = 0; i < samples.size(); ++i) {
+ samples[i] = Duration(23 + (i % 3 - 1));
+ }
+
+ auto analysis = Catch::Benchmark::Detail::analyse(config, env, samples.begin(), samples.end());
+ 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/Parse.tests.cpp b/tests/SelfTest/IntrospectiveTests/Parse.tests.cpp
new file mode 100644
index 00000000..7791355f
--- /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 00000000..ac8be645
--- /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::NameAndLocation makeNAL( std::string const& name ) {
+ return Catch::TestCaseTracking::NameAndLocation( 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 00000000..8018b7eb
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/RandomNumberGeneration.tests.cpp
@@ -0,0 +1,62 @@
+
+// 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_number_generator.hpp>
+#include <catch2/internal/catch_random_seed_generation.hpp>
+#include <catch2/generators/catch_generators.hpp>
+
+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));
+}
diff --git a/tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp b/tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp
new file mode 100644
index 00000000..54c26a7a
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp
@@ -0,0 +1,329 @@
+
+// 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/interfaces/catch_interfaces_reporter_registry.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.get();
+
+ Catch::Config config( Catch::ConfigData{} );
+ 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( 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( 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 00000000..8e6009dd
--- /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 00000000..738cb529
--- /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 00000000..7a0b3b4a
--- /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 00000000..36554ddc
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/StringManip.tests.cpp
@@ -0,0 +1,83 @@
+
+// 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("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 00000000..ef321b27
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/Tag.tests.cpp
@@ -0,0 +1,104 @@
+
+// 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( "Empty tag is not allowed" ) {
+ REQUIRE_THROWS( Catch::TestCaseInfo(
+ "", { "fake test name", "[]" }, dummySourceLineInfo ) );
+}
diff --git a/tests/SelfTest/IntrospectiveTests/TestCaseInfoHasher.tests.cpp b/tests/SelfTest/IntrospectiveTests/TestCaseInfoHasher.tests.cpp
new file mode 100644
index 00000000..03cb3f0e
--- /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 00000000..9c4eb03b
--- /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( "\"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 00000000..ae27b406
--- /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 00000000..82de5a27
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp
@@ -0,0 +1,200 @@
+
+// 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;
+
+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(as_written(col) == " \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.");
+}
diff --git a/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp b/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp
new file mode 100644
index 00000000..e50e4c5e
--- /dev/null
+++ b/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp
@@ -0,0 +1,97 @@
+
+// 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_vector.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/catch_template_test_macros.hpp>
+
+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 );
+}
diff --git a/tests/SelfTest/IntrospectiveTests/Traits.tests.cpp b/tests/SelfTest/IntrospectiveTests/Traits.tests.cpp
new file mode 100644
index 00000000..459e0d48
--- /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 00000000..420bf1b3
--- /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 00000000..b5982b85
--- /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;")"));
+}