vendor Catch2 and ETL

This commit is contained in:
2024-10-29 10:49:46 +01:00
parent 3915e0d641
commit 5173292491
1763 changed files with 959387 additions and 71 deletions

View File

@ -0,0 +1,94 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_macros.hpp>
#include <catch2/internal/catch_is_permutation.hpp>
#include <helpers/range_test_helpers.hpp>
#include <array>
namespace {
template <typename Range1, typename Range2>
static bool is_permutation(Range1 const& r1, Range2 const& r2) {
using std::begin; using std::end;
return Catch::Detail::is_permutation(
begin( r1 ), end( r1 ), begin( r2 ), end( r2 ), std::equal_to<>{} );
}
}
TEST_CASE("is_permutation", "[algorithms][approvals]") {
SECTION( "Handle empty ranges" ) {
std::array<int, 0> empty;
std::array<int, 2> non_empty{ { 2, 3 } };
REQUIRE( is_permutation( empty, empty ) );
REQUIRE_FALSE( is_permutation( empty, non_empty ) );
REQUIRE_FALSE( is_permutation( non_empty, empty ) );
}
SECTION( "Different length ranges" ) {
std::array<int, 6> arr1{ { 1, 3, 5, 7, 8, 9 } };
// arr2 is prefix of arr1
std::array<int, 4> arr2{ { 1, 3, 5, 7 } };
// arr3 shares prefix with arr1 and arr2, but is not a permutation
std::array<int, 5> arr3{ { 1, 3, 5, 9, 8 } };
REQUIRE_FALSE( is_permutation( arr1, arr2 ) );
REQUIRE_FALSE( is_permutation( arr1, arr3 ) );
REQUIRE_FALSE( is_permutation( arr2, arr3 ) );
}
SECTION( "Same length ranges" ) {
SECTION( "Shared elements, but different counts" ) {
const std::array<int, 6>
arr1{ { 1, 1, 1, 1, 2, 2 } },
arr2{ { 1, 1, 2, 2, 2, 2 } };
REQUIRE_FALSE( is_permutation( arr1, arr2 ) );
}
SECTION( "Identical ranges" ) {
const std::array<int, 6>
arr1{ { 1, 1, 1, 1, 2, 2 } },
arr2{ { 1, 1, 2, 2, 2, 2 } };
REQUIRE( is_permutation( arr1, arr1 ) );
REQUIRE( is_permutation( arr2, arr2 ) );
}
SECTION( "Completely distinct elements" ) {
// Completely distinct elements
const std::array<int, 4>
arr1{ { 1, 2, 3, 4 } },
arr2{ { 10, 20, 30, 40 } };
REQUIRE_FALSE( is_permutation( arr1, arr2 ) );
}
SECTION( "Reverse ranges" ) {
const std::array<int, 5>
arr1{ { 1, 2, 3, 4, 5 } },
arr2{ { 5, 4, 3, 2, 1 } };
REQUIRE( is_permutation( arr1, arr2 ) );
}
SECTION( "Shared prefix & permuted elements" ) {
const std::array<int, 5>
arr1{ { 1, 1, 2, 3, 4 } },
arr2{ { 1, 1, 4, 2, 3 } };
REQUIRE( is_permutation( arr1, arr2 ) );
}
SECTION( "Permutations with element count > 1" ) {
const std::array<int, 7>
arr1{ { 2, 2, 3, 3, 3, 1, 1 } },
arr2{ { 3, 2, 1, 3, 2, 1, 3 } };
REQUIRE( is_permutation( arr1, arr2 ) );
}
}
}
TEST_CASE("is_permutation supports iterator + sentinel pairs",
"[algorithms][is-permutation][approvals]") {
const has_different_begin_end_types<int>
range_1{ 1, 2, 3, 4 },
range_2{ 4, 3, 2, 1 };
REQUIRE( is_permutation( range_1, range_2 ) );
const has_different_begin_end_types<int> range_3{ 3, 3, 2, 1 };
REQUIRE_FALSE( is_permutation( range_1, range_3 ) );
}

View File

@ -0,0 +1,17 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_macros.hpp>
TEST_CASE( "Incomplete AssertionHandler", "[assertion-handler][!shouldfail]" ) {
Catch::AssertionHandler catchAssertionHandler(
"REQUIRE"_catch_sr,
CATCH_INTERNAL_LINEINFO,
"Dummy",
Catch::ResultDisposition::Normal );
}

View File

@ -0,0 +1,88 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_macros.hpp>
#include <catch2/internal/catch_clara.hpp>
#include <string>
TEST_CASE("is_unary_function", "[clara][compilation]") {
auto unary1 = [](int) {};
auto unary2 = [](std::string const&) {};
auto const unary3 = [](std::string const&) {};
auto unary4 = [](int) { return 42; };
void unary5(char);
double unary6(long);
double binary1(long, int);
auto binary2 = [](int, char) {};
auto nullary1 = []() {};
auto nullary2 = []() {return 42;};
STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function<decltype(unary1)>::value);
STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function<decltype(unary2)>::value);
STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function<decltype(unary3)>::value);
STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function<decltype(unary4)>::value);
STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function<decltype(unary5)>::value);
STATIC_REQUIRE(Catch::Clara::Detail::is_unary_function<decltype(unary6)>::value);
STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function<decltype(binary1)>::value);
STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function<decltype(binary2)>::value);
STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function<decltype(nullary1)>::value);
STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function<decltype(nullary2)>::value);
STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function<int>::value);
STATIC_REQUIRE_FALSE(Catch::Clara::Detail::is_unary_function<std::string const&>::value);
}
TEST_CASE("Clara::Arg supports single-arg parse the way Opt does", "[clara][arg][compilation]") {
std::string name;
auto p = Catch::Clara::Arg(name, "just one arg");
CHECK(name.empty());
p.parse( Catch::Clara::Args{ "UnitTest", "foo" } );
REQUIRE(name == "foo");
}
TEST_CASE("Clara::Arg does not crash on incomplete input", "[clara][arg][compilation]") {
std::string name;
auto p = Catch::Clara::Arg(name, "-");
CHECK(name.empty());
auto result = p.parse( Catch::Clara::Args{ "UnitTest", "-" } );
CHECK( result );
CHECK( result.type() == Catch::Clara::Detail::ResultType::Ok );
const auto& parsed = result.value();
CHECK( parsed.type() == Catch::Clara::ParseResultType::NoMatch );
CHECK( parsed.remainingTokens().count() == 2 );
CHECK( name.empty() );
}
TEST_CASE("Clara::Opt supports accept-many lambdas", "[clara][opt]") {
using namespace Catch::Clara;
std::vector<std::string> res;
const auto push_to_res = [&](std::string const& s) {
res.push_back(s);
return ParserResult::ok( ParseResultType::Matched );
};
SECTION("Parsing fails on multiple options without accept_many") {
auto p = Parser() | Opt(push_to_res, "value")["-o"];
auto parse_result = p.parse( Args{ "UnitTest", "-o", "aaa", "-o", "bbb" } );
CHECK_FALSE(parse_result);
}
SECTION("Parsing succeeds on multiple options with accept_many") {
auto p = Parser() | Opt(accept_many, push_to_res, "value")["-o"];
auto parse_result = p.parse( Args{ "UnitTest", "-o", "aaa", "-o", "bbb" } );
CHECK(parse_result);
CHECK(res == std::vector<std::string>{ "aaa", "bbb" });
}
}

View File

@ -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 } ) );
}
}

View File

@ -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" ) );
}
}

View File

@ -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" );
}
}

View File

@ -0,0 +1,172 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_macros.hpp>
#include <catch2/internal/catch_enforce.hpp>
#include <catch2/internal/catch_case_insensitive_comparisons.hpp>
#include <catch2/internal/catch_optional.hpp>
#include <helpers/type_with_lit_0_comparisons.hpp>
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4702) // unreachable code in the macro expansions
#endif
TEST_CASE("Check that our error handling macros throw the right exceptions", "[!throws][internals][approvals]") {
REQUIRE_THROWS_AS(CATCH_INTERNAL_ERROR(""), std::logic_error);
REQUIRE_THROWS_AS(CATCH_ERROR(""), std::domain_error);
REQUIRE_THROWS_AS(CATCH_RUNTIME_ERROR(""), std::runtime_error);
REQUIRE_THROWS_AS([](){CATCH_ENFORCE(false, "");}(), std::domain_error);
REQUIRE_NOTHROW([](){CATCH_ENFORCE(true, "");}());
}
#if defined(_MSC_VER)
#pragma warning(pop) // unreachable code in the macro expansions
#endif
TEST_CASE("CaseInsensitiveLess is case insensitive", "[comparisons][string-case]") {
Catch::Detail::CaseInsensitiveLess lt;
SECTION( "Degenerate cases" ) {
REQUIRE( lt( "", "a" ) );
REQUIRE_FALSE( lt( "a", "a" ) );
REQUIRE_FALSE( lt( "", "" ) );
}
SECTION("Plain comparisons") {
REQUIRE( lt( "a", "b" ) );
REQUIRE( lt( "a", "B" ) );
REQUIRE( lt( "A", "b" ) );
REQUIRE( lt( "A", "B" ) );
}
}
TEST_CASE( "CaseInsensitiveEqualsTo is case insensitive",
"[comparisons][string-case]" ) {
Catch::Detail::CaseInsensitiveEqualTo eq;
SECTION( "Degenerate cases" ) {
REQUIRE( eq( "", "" ) );
REQUIRE_FALSE( eq( "", "a" ) );
}
SECTION( "Plain comparisons" ) {
REQUIRE( eq( "a", "a" ) );
REQUIRE( eq( "a", "A" ) );
REQUIRE( eq( "A", "a" ) );
REQUIRE( eq( "A", "A" ) );
REQUIRE_FALSE( eq( "a", "b" ) );
REQUIRE_FALSE( eq( "a", "B" ) );
}
}
TEST_CASE("Optional comparison ops", "[optional][approvals]") {
using Catch::Optional;
Optional<int> a, b;
SECTION( "Empty optionals are equal" ) {
REQUIRE( a == b );
REQUIRE_FALSE( a != b );
}
SECTION( "Empty and non-empty optionals are never equal" ) {
a = 1;
REQUIRE_FALSE( a == b );
REQUIRE( a != b );
}
SECTION(
"non-empty optionals are equal if the contained elements are equal") {
a = 1;
b = 2;
REQUIRE( a != b );
REQUIRE_FALSE( a == b );
a = 2;
REQUIRE( a == b );
REQUIRE_FALSE( a != b );
}
}
namespace {
struct MoveChecker {
bool has_moved = false;
MoveChecker() = default;
MoveChecker( MoveChecker const& rhs ) = default;
MoveChecker& operator=( MoveChecker const& rhs ) = default;
MoveChecker( MoveChecker&& rhs ) noexcept { rhs.has_moved = true; }
MoveChecker& operator=( MoveChecker&& rhs ) noexcept {
rhs.has_moved = true;
return *this;
}
};
}
TEST_CASE( "Optional supports move ops", "[optional][approvals]" ) {
using Catch::Optional;
MoveChecker a;
Optional<MoveChecker> opt_A( a );
REQUIRE_FALSE( a.has_moved );
REQUIRE_FALSE( opt_A->has_moved );
SECTION( "Move construction from element" ) {
Optional<MoveChecker> opt_B( CATCH_MOVE( a ) );
REQUIRE( a.has_moved );
}
SECTION( "Move assignment from element" ) {
opt_A = CATCH_MOVE( a );
REQUIRE( a.has_moved );
}
SECTION( "Move construction from optional" ) {
Optional<MoveChecker> opt_B( CATCH_MOVE( opt_A ) );
REQUIRE( opt_A->has_moved ); // NOLINT(clang-analyzer-cplusplus.Move)
}
SECTION( "Move assignment from optional" ) {
Optional<MoveChecker> opt_B( opt_A );
REQUIRE_FALSE( opt_A->has_moved );
opt_B = CATCH_MOVE( opt_A );
REQUIRE( opt_A->has_moved ); // NOLINT(clang-analyzer-cplusplus.Move)
}
}
TEST_CASE( "Decomposer checks that the argument is 0 when handling "
"only-0-comparable types",
"[decomposition][approvals]" ) {
TypeWithLit0Comparisons t{};
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS
REQUIRE_THROWS( Catch::Decomposer{} <= t == 42 );
REQUIRE_THROWS( Catch::Decomposer{} <= 42 == t );
REQUIRE_NOTHROW( Catch::Decomposer{} <= t == 0 );
REQUIRE_NOTHROW( Catch::Decomposer{} <= 0 == t );
REQUIRE_THROWS( Catch::Decomposer{} <= t != 42 );
REQUIRE_THROWS( Catch::Decomposer{} <= 42 != t );
REQUIRE_NOTHROW( Catch::Decomposer{} <= t != 0 );
REQUIRE_NOTHROW( Catch::Decomposer{} <= 0 != t );
REQUIRE_THROWS( Catch::Decomposer{} <= t < 42 );
REQUIRE_THROWS( Catch::Decomposer{} <= 42 < t );
REQUIRE_NOTHROW( Catch::Decomposer{} <= t < 0 );
REQUIRE_NOTHROW( Catch::Decomposer{} <= 0 < t );
REQUIRE_THROWS( Catch::Decomposer{} <= t <= 42 );
REQUIRE_THROWS( Catch::Decomposer{} <= 42 <= t );
REQUIRE_NOTHROW( Catch::Decomposer{} <= t <= 0 );
REQUIRE_NOTHROW( Catch::Decomposer{} <= 0 <= t );
REQUIRE_THROWS( Catch::Decomposer{} <= t > 42 );
REQUIRE_THROWS( Catch::Decomposer{} <= 42 > t );
REQUIRE_NOTHROW( Catch::Decomposer{} <= t > 0 );
REQUIRE_NOTHROW( Catch::Decomposer{} <= 0 > t );
REQUIRE_THROWS( Catch::Decomposer{} <= t >= 42 );
REQUIRE_THROWS( Catch::Decomposer{} <= 42 >= t );
REQUIRE_NOTHROW( Catch::Decomposer{} <= t >= 0 );
REQUIRE_NOTHROW( Catch::Decomposer{} <= 0 >= t );
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
}

View File

@ -0,0 +1,139 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/internal/catch_floating_point_helpers.hpp>
#include <catch2/internal/catch_random_floating_point_helpers.hpp>
#include <limits>
TEST_CASE("convertToBits", "[floating-point][conversion]") {
using Catch::Detail::convertToBits;
CHECK( convertToBits( 0.f ) == 0 );
CHECK( convertToBits( -0.f ) == ( 1ULL << 31 ) );
CHECK( convertToBits( 0. ) == 0 );
CHECK( convertToBits( -0. ) == ( 1ULL << 63 ) );
CHECK( convertToBits( std::numeric_limits<float>::denorm_min() ) == 1 );
CHECK( convertToBits( std::numeric_limits<double>::denorm_min() ) == 1 );
}
TEMPLATE_TEST_CASE("type-shared ulpDistance tests", "[floating-point][ulp][approvals]", float, double) {
using FP = TestType;
using Catch::ulpDistance;
// Distance between zeros is zero
CHECK( ulpDistance( FP{}, FP{} ) == 0 );
CHECK( ulpDistance( FP{}, -FP{} ) == 0 );
CHECK( ulpDistance( -FP{}, -FP{} ) == 0 );
// Distance between same-sign infinities is zero
static constexpr FP infinity = std::numeric_limits<FP>::infinity();
CHECK( ulpDistance( infinity, infinity ) == 0 );
CHECK( ulpDistance( -infinity, -infinity ) == 0 );
// Distance between max-finite-val and same sign infinity is 1
static constexpr FP max_finite = std::numeric_limits<FP>::max();
CHECK( ulpDistance( max_finite, infinity ) == 1 );
CHECK( ulpDistance( -max_finite, -infinity ) == 1 );
// Distance between X and 0 is half of distance between X and -X
CHECK( ulpDistance( -infinity, infinity ) ==
2 * ulpDistance( infinity, FP{} ) );
CHECK( 2 * ulpDistance( FP{ -2. }, FP{} ) ==
ulpDistance( FP{ -2. }, FP{ 2. } ) );
CHECK( 2 * ulpDistance( FP{ 2. }, FP{} ) ==
ulpDistance( FP{ -2. }, FP{ 2. } ) );
// Denorms are supported
CHECK( ulpDistance( std::numeric_limits<FP>::denorm_min(), FP{} ) == 1 );
CHECK( ulpDistance( std::numeric_limits<FP>::denorm_min(), -FP{} ) == 1 );
CHECK( ulpDistance( -std::numeric_limits<FP>::denorm_min(), FP{} ) == 1 );
CHECK( ulpDistance( -std::numeric_limits<FP>::denorm_min(), -FP{} ) == 1 );
CHECK( ulpDistance( std::numeric_limits<FP>::denorm_min(),
-std::numeric_limits<FP>::denorm_min() ) == 2 );
// Machine epsilon
CHECK( ulpDistance( FP{ 1. },
FP{ 1. } + std::numeric_limits<FP>::epsilon() ) == 1 );
CHECK( ulpDistance( -FP{ 1. },
-FP{ 1. } - std::numeric_limits<FP>::epsilon() ) == 1 );
}
TEST_CASE("UlpDistance", "[floating-point][ulp][approvals]") {
using Catch::ulpDistance;
CHECK( ulpDistance( 1., 2. ) == 0x10'00'00'00'00'00'00 );
CHECK( ulpDistance( -2., 2. ) == 0x80'00'00'00'00'00'00'00 );
CHECK( ulpDistance( 1.f, 2.f ) == 0x80'00'00 );
CHECK( ulpDistance( -2.f, 2.f ) == 0x80'00'00'00 );
}
TEMPLATE_TEST_CASE("gamma", "[approvals][floating-point][ulp][gamma]", float, double) {
using Catch::Detail::gamma;
using Catch::Detail::directCompare;
// We need to butcher the equal tests with the directCompare helper,
// because the Wfloat-equal triggers in decomposer rather than here,
// so we cannot locally disable it. Goddamn GCC.
CHECK( directCompare( gamma( TestType( -1. ), TestType( 1. ) ),
gamma( TestType( 0.2332 ), TestType( 1.0 ) ) ) );
CHECK( directCompare( gamma( TestType( -2. ), TestType( 0 ) ),
gamma( TestType( 1. ), TestType( 1.5 ) ) ) );
CHECK( gamma( TestType( 0. ), TestType( 1.0 ) ) <
gamma( TestType( 1.0 ), TestType( 1.5 ) ) );
CHECK( gamma( TestType( 0 ), TestType( 1. ) ) <
std::numeric_limits<TestType>::epsilon() );
CHECK( gamma( TestType( -1. ), TestType( -0. ) ) <
std::numeric_limits<TestType>::epsilon() );
CHECK( directCompare( gamma( TestType( 1. ), TestType( 2. ) ),
std::numeric_limits<TestType>::epsilon() ) );
CHECK( directCompare( gamma( TestType( -2. ), TestType( -1. ) ),
std::numeric_limits<TestType>::epsilon() ) );
}
TEMPLATE_TEST_CASE("count_equidistant_floats",
"[approvals][floating-point][distance]",
float,
double) {
using Catch::Detail::count_equidistant_floats;
auto count_steps = []( TestType a, TestType b ) {
return count_equidistant_floats( a, b, Catch::Detail::gamma( a, b ) );
};
CHECK( count_steps( TestType( -1. ), TestType( 1. ) ) ==
2 * count_steps( TestType( 0. ), TestType( 1. ) ) );
}
TEST_CASE( "count_equidistant_floats",
"[approvals][floating-point][distance]" ) {
using Catch::Detail::count_equidistant_floats;
auto count_floats_with_scaled_ulp = []( auto a, auto b ) {
return count_equidistant_floats( a, b, Catch::Detail::gamma( a, b ) );
};
CHECK( count_floats_with_scaled_ulp( 1., 1.5 ) == 1ull << 51 );
CHECK( count_floats_with_scaled_ulp( 1.25, 1.5 ) == 1ull << 50 );
CHECK( count_floats_with_scaled_ulp( 1.f, 1.5f ) == 1 << 22 );
CHECK( count_floats_with_scaled_ulp( -std::numeric_limits<float>::max(),
std::numeric_limits<float>::max() ) ==
33554430 ); // (1 << 25) - 2 due to not including infinities
CHECK( count_floats_with_scaled_ulp( -std::numeric_limits<double>::max(),
std::numeric_limits<double>::max() ) ==
18014398509481982 ); // (1 << 54) - 2 due to not including infinities
STATIC_REQUIRE( std::is_same<std::uint64_t,
decltype( count_floats_with_scaled_ulp(
0., 1. ) )>::value );
STATIC_REQUIRE( std::is_same<std::uint32_t,
decltype( count_floats_with_scaled_ulp(
0.f, 1.f ) )>::value );
}

View File

@ -0,0 +1,575 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#if defined( __GNUC__ ) || defined( __clang__ )
# pragma GCC diagnostic ignored "-Wfloat-equal"
#endif
#include <helpers/range_test_helpers.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generator_exception.hpp>
#include <catch2/generators/catch_generators_adapters.hpp>
#include <catch2/generators/catch_generators_random.hpp>
#include <catch2/generators/catch_generators_range.hpp>
// Tests of generator implementation details
TEST_CASE("Generators internals", "[generators][internals]") {
using namespace Catch::Generators;
SECTION("Single value") {
auto gen = value(123);
REQUIRE(gen.get() == 123);
REQUIRE_FALSE(gen.next());
}
SECTION("Preset values") {
auto gen = values({ 1, 3, 5 });
REQUIRE(gen.get() == 1);
REQUIRE(gen.next());
REQUIRE(gen.get() == 3);
REQUIRE(gen.next());
REQUIRE(gen.get() == 5);
REQUIRE_FALSE(gen.next());
}
SECTION("Generator combinator") {
auto gen = makeGenerators(1, 5, values({ 2, 4 }), 0);
REQUIRE(gen.get() == 1);
REQUIRE(gen.next());
REQUIRE(gen.get() == 5);
REQUIRE(gen.next());
REQUIRE(gen.get() == 2);
REQUIRE(gen.next());
REQUIRE(gen.get() == 4);
REQUIRE(gen.next());
REQUIRE(gen.get() == 0);
REQUIRE_FALSE(gen.next());
}
SECTION("Explicitly typed generator sequence") {
auto gen = makeGenerators(as<std::string>{}, "aa", "bb", "cc");
// This just checks that the type is std::string:
REQUIRE(gen.get().size() == 2);
// Iterate over the generator
REQUIRE(gen.get() == "aa");
REQUIRE(gen.next());
REQUIRE(gen.get() == "bb");
REQUIRE(gen.next());
REQUIRE(gen.get() == "cc");
REQUIRE_FALSE(gen.next());
}
SECTION("Filter generator") {
// Normal usage
SECTION("Simple filtering") {
auto gen = filter([](int i) { return i != 2; }, values({ 2, 1, 2, 3, 2, 2 }));
REQUIRE(gen.get() == 1);
REQUIRE(gen.next());
REQUIRE(gen.get() == 3);
REQUIRE_FALSE(gen.next());
}
SECTION("Filter out multiple elements at the start and end") {
auto gen = filter([](int i) { return i != 2; }, values({ 2, 2, 1, 3, 2, 2 }));
REQUIRE(gen.get() == 1);
REQUIRE(gen.next());
REQUIRE(gen.get() == 3);
REQUIRE_FALSE(gen.next());
}
SECTION("Throws on construction if it can't get initial element") {
REQUIRE_THROWS_AS(filter([](int) { return false; }, value(1)), Catch::GeneratorException);
REQUIRE_THROWS_AS(
filter([](int) { return false; }, values({ 1, 2, 3 })),
Catch::GeneratorException);
}
}
SECTION("Take generator") {
SECTION("Take less") {
auto gen = take(2, values({ 1, 2, 3 }));
REQUIRE(gen.get() == 1);
REQUIRE(gen.next());
REQUIRE(gen.get() == 2);
REQUIRE_FALSE(gen.next());
}
SECTION("Take more") {
auto gen = take(2, value(1));
REQUIRE(gen.get() == 1);
REQUIRE_FALSE(gen.next());
}
}
SECTION("Map with explicit return type") {
auto gen = map<double>([] (int i) {return 2.0 * i; }, values({ 1, 2, 3 }));
REQUIRE(gen.get() == 2.0);
REQUIRE(gen.next());
REQUIRE(gen.get() == 4.0);
REQUIRE(gen.next());
REQUIRE(gen.get() == 6.0);
REQUIRE_FALSE(gen.next());
}
SECTION("Map with deduced return type") {
auto gen = map([] (int i) {return 2.0 * i; }, values({ 1, 2, 3 }));
REQUIRE(gen.get() == 2.0);
REQUIRE(gen.next());
REQUIRE(gen.get() == 4.0);
REQUIRE(gen.next());
REQUIRE(gen.get() == 6.0);
REQUIRE_FALSE(gen.next());
}
SECTION("Repeat") {
SECTION("Singular repeat") {
auto gen = repeat(1, value(3));
REQUIRE(gen.get() == 3);
REQUIRE_FALSE(gen.next());
}
SECTION("Actual repeat") {
auto gen = repeat(2, values({ 1, 2, 3 }));
REQUIRE(gen.get() == 1);
REQUIRE(gen.next());
REQUIRE(gen.get() == 2);
REQUIRE(gen.next());
REQUIRE(gen.get() == 3);
REQUIRE(gen.next());
REQUIRE(gen.get() == 1);
REQUIRE(gen.next());
REQUIRE(gen.get() == 2);
REQUIRE(gen.next());
REQUIRE(gen.get() == 3);
REQUIRE_FALSE(gen.next());
}
}
SECTION("Range") {
SECTION("Positive auto step") {
SECTION("Integer") {
auto gen = range(-2, 2);
REQUIRE(gen.get() == -2);
REQUIRE(gen.next());
REQUIRE(gen.get() == -1);
REQUIRE(gen.next());
REQUIRE(gen.get() == 0);
REQUIRE(gen.next());
REQUIRE(gen.get() == 1);
REQUIRE_FALSE(gen.next());
}
}
SECTION("Negative auto step") {
SECTION("Integer") {
auto gen = range(2, -2);
REQUIRE(gen.get() == 2);
REQUIRE(gen.next());
REQUIRE(gen.get() == 1);
REQUIRE(gen.next());
REQUIRE(gen.get() == 0);
REQUIRE(gen.next());
REQUIRE(gen.get() == -1);
REQUIRE_FALSE(gen.next());
}
}
SECTION("Positive manual step") {
SECTION("Integer") {
SECTION("Exact") {
auto gen = range(-7, 5, 3);
REQUIRE(gen.get() == -7);
REQUIRE(gen.next());
REQUIRE(gen.get() == -4);
REQUIRE(gen.next());
REQUIRE(gen.get() == -1);
REQUIRE(gen.next());
REQUIRE(gen.get() == 2);
REQUIRE_FALSE(gen.next());
}
SECTION("Slightly over end") {
auto gen = range(-7, 4, 3);
REQUIRE(gen.get() == -7);
REQUIRE(gen.next());
REQUIRE(gen.get() == -4);
REQUIRE(gen.next());
REQUIRE(gen.get() == -1);
REQUIRE(gen.next());
REQUIRE(gen.get() == 2);
REQUIRE_FALSE(gen.next());
}
SECTION("Slightly under end") {
auto gen = range(-7, 6, 3);
REQUIRE(gen.get() == -7);
REQUIRE(gen.next());
REQUIRE(gen.get() == -4);
REQUIRE(gen.next());
REQUIRE(gen.get() == -1);
REQUIRE(gen.next());
REQUIRE(gen.get() == 2);
REQUIRE(gen.next());
REQUIRE(gen.get() == 5);
REQUIRE_FALSE(gen.next());
}
}
SECTION("Floating Point") {
using Catch::Approx;
SECTION("Exact") {
const auto rangeStart = -1.;
const auto rangeEnd = 1.;
const auto step = .1;
auto gen = range(rangeStart, rangeEnd, step);
auto expected = rangeStart;
while( (rangeEnd - expected) > step ) {
INFO( "Current expected value is " << expected );
REQUIRE(gen.get() == Approx(expected));
REQUIRE(gen.next());
expected += step;
}
REQUIRE(gen.get() == Approx( rangeEnd ) );
REQUIRE_FALSE(gen.next());
}
SECTION("Slightly over end") {
const auto rangeStart = -1.;
const auto rangeEnd = 1.;
const auto step = .3;
auto gen = range(rangeStart, rangeEnd, step);
auto expected = rangeStart;
while( (rangeEnd - expected) > step ) {
INFO( "Current expected value is " << expected );
REQUIRE(gen.get() == Approx(expected));
REQUIRE(gen.next());
expected += step;
}
REQUIRE_FALSE(gen.next());
}
SECTION("Slightly under end") {
const auto rangeStart = -1.;
const auto rangeEnd = .9;
const auto step = .3;
auto gen = range(rangeStart, rangeEnd, step);
auto expected = rangeStart;
while( (rangeEnd - expected) > step ) {
INFO( "Current expected value is " << expected );
REQUIRE(gen.get() == Approx(expected));
REQUIRE(gen.next());
expected += step;
}
REQUIRE_FALSE(gen.next());
}
}
}
SECTION("Negative manual step") {
SECTION("Integer") {
SECTION("Exact") {
auto gen = range(5, -7, -3);
REQUIRE(gen.get() == 5);
REQUIRE(gen.next());
REQUIRE(gen.get() == 2);
REQUIRE(gen.next());
REQUIRE(gen.get() == -1);
REQUIRE(gen.next());
REQUIRE(gen.get() == -4);
REQUIRE_FALSE(gen.next());
}
SECTION("Slightly over end") {
auto gen = range(5, -6, -3);
REQUIRE(gen.get() == 5);
REQUIRE(gen.next());
REQUIRE(gen.get() == 2);
REQUIRE(gen.next());
REQUIRE(gen.get() == -1);
REQUIRE(gen.next());
REQUIRE(gen.get() == -4);
REQUIRE_FALSE(gen.next());
}
SECTION("Slightly under end") {
auto gen = range(5, -8, -3);
REQUIRE(gen.get() == 5);
REQUIRE(gen.next());
REQUIRE(gen.get() == 2);
REQUIRE(gen.next());
REQUIRE(gen.get() == -1);
REQUIRE(gen.next());
REQUIRE(gen.get() == -4);
REQUIRE(gen.next());
REQUIRE(gen.get() == -7);
REQUIRE_FALSE(gen.next());
}
}
}
}
}
// todo: uncopyable type used in a generator
// idea: uncopyable tag type for a stupid generator
namespace {
struct non_copyable {
non_copyable() = default;
non_copyable(non_copyable const&) = delete;
non_copyable& operator=(non_copyable const&) = delete;
int value = -1;
};
// This class shows how to implement a simple generator for Catch tests
class TestGen : public Catch::Generators::IGenerator<int> {
int current_number;
public:
TestGen(non_copyable const& nc):
current_number(nc.value) {}
int const& get() const override;
bool next() override {
return false;
}
};
// Avoids -Wweak-vtables
int const& TestGen::get() const {
return current_number;
}
}
TEST_CASE("GENERATE capture macros", "[generators][internals][approvals]") {
auto value = GENERATE(take(10, random(0, 10)));
non_copyable nc; nc.value = value;
// neither `GENERATE_COPY` nor plain `GENERATE` would compile here
auto value2 = GENERATE_REF(Catch::Generators::GeneratorWrapper<int>(Catch::Detail::make_unique<TestGen>(nc)));
REQUIRE(value == value2);
}
TEST_CASE("#1809 - GENERATE_COPY and SingleValueGenerator does not compile", "[generators][compilation][approvals]") {
// Verify Issue #1809 fix, only needs to compile.
auto a = GENERATE_COPY(1, 2);
(void)a;
auto b = GENERATE_COPY(as<long>{}, 1, 2);
(void)b;
int i = 1;
int j = 2;
auto c = GENERATE_COPY(i, j);
(void)c;
auto d = GENERATE_COPY(as<long>{}, i, j);
(void)d;
SUCCEED();
}
TEST_CASE("Multiple random generators in one test case output different values", "[generators][internals][approvals]") {
SECTION("Integer") {
auto random1 = Catch::Generators::random(0, 1000);
auto random2 = Catch::Generators::random(0, 1000);
size_t same = 0;
for (size_t i = 0; i < 1000; ++i) {
same += random1.get() == random2.get();
random1.next(); random2.next();
}
// Because the previous low bound failed CI couple of times,
// we use a very high threshold of 20% before failure is reported.
REQUIRE(same < 200);
}
SECTION("Float") {
auto random1 = Catch::Generators::random(0., 1000.);
auto random2 = Catch::Generators::random(0., 1000.);
size_t same = 0;
for (size_t i = 0; i < 1000; ++i) {
same += random1.get() == random2.get();
random1.next(); random2.next();
}
// Because the previous low bound failed CI couple of times,
// we use a very high threshold of 20% before failure is reported.
REQUIRE(same < 200);
}
}
TEST_CASE("#2040 - infinite compilation recursion in GENERATE with MSVC", "[generators][compilation][approvals]") {
int x = 42;
auto test = GENERATE_COPY(1, x, 2 * x);
CHECK(test < 100);
}
namespace {
static bool always_true(int) {
return true;
}
static bool is_even(int n) {
return n % 2 == 0;
}
static bool is_multiple_of_3(int n) {
return n % 3 == 0;
}
}
TEST_CASE("GENERATE handles function (pointers)", "[generators][compilation][approvals]") {
auto f = GENERATE(always_true, is_even, is_multiple_of_3);
REQUIRE(f(6));
}
TEST_CASE("GENERATE decays arrays", "[generators][compilation][approvals]") {
auto str = GENERATE("abc", "def", "gh");
(void)str;
STATIC_REQUIRE(std::is_same<decltype(str), const char*>::value);
}
TEST_CASE("Generators count returned elements", "[generators][approvals]") {
auto generator = Catch::Generators::FixedValuesGenerator<int>( { 1, 2, 3 } );
REQUIRE( generator.currentElementIndex() == 0 );
REQUIRE( generator.countedNext() );
REQUIRE( generator.currentElementIndex() == 1 );
REQUIRE( generator.countedNext() );
REQUIRE( generator.currentElementIndex() == 2 );
REQUIRE_FALSE( generator.countedNext() );
REQUIRE( generator.currentElementIndex() == 2 );
}
TEST_CASE( "Generators can stringify their elements",
"[generators][approvals]" ) {
auto generator =
Catch::Generators::FixedValuesGenerator<int>( { 1, 2, 3 } );
REQUIRE( generator.currentElementAsString() == "1"_catch_sr );
REQUIRE( generator.countedNext() );
REQUIRE( generator.currentElementAsString() == "2"_catch_sr );
REQUIRE( generator.countedNext() );
REQUIRE( generator.currentElementAsString() == "3"_catch_sr );
}
namespace {
class CustomStringifyGenerator
: public Catch::Generators::IGenerator<bool> {
bool m_first = true;
std::string stringifyImpl() const override {
return m_first ? "first" : "second";
}
bool next() override {
if ( m_first ) {
m_first = false;
return true;
}
return false;
}
public:
bool const& get() const override;
};
// Avoids -Wweak-vtables
bool const& CustomStringifyGenerator::get() const { return m_first; }
} // namespace
TEST_CASE( "Generators can override element stringification",
"[generators][approvals]" ) {
CustomStringifyGenerator generator;
REQUIRE( generator.currentElementAsString() == "first"_catch_sr );
REQUIRE( generator.countedNext() );
REQUIRE( generator.currentElementAsString() == "second"_catch_sr );
}
namespace {
class StringifyCountingGenerator
: public Catch::Generators::IGenerator<bool> {
bool m_first = true;
mutable size_t m_stringificationCalls = 0;
std::string stringifyImpl() const override {
++m_stringificationCalls;
return m_first ? "first" : "second";
}
bool next() override {
if ( m_first ) {
m_first = false;
return true;
}
return false;
}
public:
bool const& get() const override;
size_t stringificationCalls() const { return m_stringificationCalls; }
};
// Avoids -Wweak-vtables
bool const& StringifyCountingGenerator::get() const { return m_first; }
} // namespace
TEST_CASE( "Generator element stringification is cached",
"[generators][approvals]" ) {
StringifyCountingGenerator generator;
REQUIRE( generator.currentElementAsString() == "first"_catch_sr );
REQUIRE( generator.currentElementAsString() == "first"_catch_sr );
REQUIRE( generator.currentElementAsString() == "first"_catch_sr );
REQUIRE( generator.currentElementAsString() == "first"_catch_sr );
REQUIRE( generator.currentElementAsString() == "first"_catch_sr );
REQUIRE( generator.stringificationCalls() == 1 );
}
TEST_CASE( "Random generators can be seeded", "[generators][approvals]" ) {
SECTION( "Integer generator" ) {
using Catch::Generators::RandomIntegerGenerator;
RandomIntegerGenerator<int> rng1( 0, 100, 0x1234 ),
rng2( 0, 100, 0x1234 );
for ( size_t i = 0; i < 10; ++i ) {
REQUIRE( rng1.get() == rng2.get() );
rng1.next(); rng2.next();
}
}
SECTION("Float generator") {
using Catch::Generators::RandomFloatingGenerator;
RandomFloatingGenerator<double> rng1( 0., 100., 0x1234 ),
rng2( 0., 100., 0x1234 );
for ( size_t i = 0; i < 10; ++i ) {
REQUIRE( rng1.get() == rng2.get() );
rng1.next();
rng2.next();
}
}
}
TEST_CASE("Filter generator throws exception for empty generator",
"[generators]") {
using namespace Catch::Generators;
REQUIRE_THROWS_AS(
filter( []( int ) { return false; }, value( 3 ) ),
Catch::GeneratorException );
}
TEST_CASE("from_range(container) supports ADL begin/end and arrays", "[generators][from-range][approvals]") {
using namespace Catch::Generators;
SECTION("C array") {
int arr[3]{ 5, 6, 7 };
auto gen = from_range( arr );
REQUIRE( gen.get() == 5 );
REQUIRE( gen.next() );
REQUIRE( gen.get() == 6 );
REQUIRE( gen.next() );
REQUIRE( gen.get() == 7 );
REQUIRE_FALSE( gen.next() );
}
SECTION( "ADL range" ) {
unrelated::needs_ADL_begin<int> range{ 1, 2, 3 };
auto gen = from_range( range );
REQUIRE( gen.get() == 1 );
REQUIRE( gen.next() );
REQUIRE( gen.get() == 2 );
REQUIRE( gen.next() );
REQUIRE( gen.get() == 3 );
REQUIRE_FALSE( gen.next() );
}
}

View File

@ -0,0 +1,224 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_macros.hpp>
#include <catch2/internal/catch_random_integer_helpers.hpp>
#include <random>
namespace {
template <typename Int>
static void
CommutativeMultCheck( Int a, Int b, Int upper_result, Int lower_result ) {
using Catch::Detail::extendedMult;
using Catch::Detail::ExtendedMultResult;
CHECK( extendedMult( a, b ) ==
ExtendedMultResult<Int>{ upper_result, lower_result } );
CHECK( extendedMult( b, a ) ==
ExtendedMultResult<Int>{ upper_result, lower_result } );
}
// Simple (and slow) implmentation of extended multiplication for tests
constexpr Catch::Detail::ExtendedMultResult<std::uint64_t>
extendedMultNaive( std::uint64_t lhs, std::uint64_t rhs ) {
// This is a simple long multiplication, where we split lhs and rhs
// into two 32-bit "digits", so that we can do ops with carry in 64-bits.
//
// 32b 32b 32b 32b
// lhs L1 L2
// * rhs R1 R2
// ------------------------
// | R2 * L2 |
// | R2 * L1 |
// | R1 * L2 |
// | R1 * L1 |
// -------------------------
// | a | b | c | d |
#define CarryBits( x ) ( x >> 32 )
#define Digits( x ) ( x & 0xFF'FF'FF'FF )
auto r2l2 = Digits( rhs ) * Digits( lhs );
auto r2l1 = Digits( rhs ) * CarryBits( lhs );
auto r1l2 = CarryBits( rhs ) * Digits( lhs );
auto r1l1 = CarryBits( rhs ) * CarryBits( lhs );
// Sum to columns first
auto d = Digits( r2l2 );
auto c = CarryBits( r2l2 ) + Digits( r2l1 ) + Digits( r1l2 );
auto b = CarryBits( r2l1 ) + CarryBits( r1l2 ) + Digits( r1l1 );
auto a = CarryBits( r1l1 );
// Propagate carries between columns
c += CarryBits( d );
b += CarryBits( c );
a += CarryBits( b );
// Remove the used carries
c = Digits( c );
b = Digits( b );
a = Digits( a );
#undef CarryBits
#undef Digits
return {
a << 32 | b, // upper 64 bits
c << 32 | d // lower 64 bits
};
}
} // namespace
TEST_CASE( "extendedMult 64x64", "[Integer][approvals]" ) {
// a x 0 == 0
CommutativeMultCheck<uint64_t>( 0x1234'5678'9ABC'DEFF, 0, 0, 0 );
// bit carried from low half to upper half
CommutativeMultCheck<uint64_t>( uint64_t( 1 ) << 63, 2, 1, 0 );
// bits in upper half on one side, bits in lower half on other side
CommutativeMultCheck<uint64_t>( 0xcdcd'dcdc'0000'0000,
0x0000'0000'aeae'aeae,
0x0000'0000'8c6e'5a77,
0x7391'a588'0000'0000 );
// Some input numbers without interesting patterns
CommutativeMultCheck<uint64_t>( 0xaaaa'aaaa'aaaa'aaaa,
0xbbbb'bbbb'bbbb'bbbb,
0x7d27'd27d'27d2'7d26,
0xd82d'82d8'2d82'd82e );
CommutativeMultCheck<uint64_t>( 0x7d27'd27d'27d2'7d26,
0xd82d'82d8'2d82'd82e,
0x69af'd991'8256'b953,
0x8724'8909'fcb6'8cd4 );
CommutativeMultCheck<uint64_t>( 0xdead'beef'dead'beef,
0xfeed'feed'feed'feef,
0xddbf'680b'2b0c'b558,
0x7a36'b06f'2ce9'6321 );
CommutativeMultCheck<uint64_t>( 0xddbf'680b'2b0c'b558,
0x7a36'b06f'2ce9'6321,
0x69dc'96c9'294b'fc7f,
0xd038'39fa'a3dc'6858 );
CommutativeMultCheck<uint64_t>( 0x61c8'8646'80b5'83eb,
0x61c8'8646'80b5'83eb,
0x2559'92d3'8220'8bbe,
0xdf44'2d22'ce48'59b9 );
}
TEST_CASE("extendedMult 64x64 - all implementations", "[integer][approvals]") {
using Catch::Detail::extendedMult;
using Catch::Detail::extendedMultPortable;
using Catch::Detail::fillBitsFrom;
std::random_device rng;
for (size_t i = 0; i < 100; ++i) {
auto a = fillBitsFrom<std::uint64_t>( rng );
auto b = fillBitsFrom<std::uint64_t>( rng );
CAPTURE( a, b );
auto naive_ab = extendedMultNaive( a, b );
REQUIRE( naive_ab == extendedMultNaive( b, a ) );
REQUIRE( naive_ab == extendedMultPortable( a, b ) );
REQUIRE( naive_ab == extendedMultPortable( b, a ) );
REQUIRE( naive_ab == extendedMult( a, b ) );
REQUIRE( naive_ab == extendedMult( b, a ) );
}
}
TEST_CASE( "SizedUnsignedType helpers", "[integer][approvals]" ) {
using Catch::Detail::SizedUnsignedType_t;
using Catch::Detail::DoubleWidthUnsignedType_t;
STATIC_REQUIRE( sizeof( SizedUnsignedType_t<1> ) == 1 );
STATIC_REQUIRE( sizeof( SizedUnsignedType_t<2> ) == 2 );
STATIC_REQUIRE( sizeof( SizedUnsignedType_t<4> ) == 4 );
STATIC_REQUIRE( sizeof( SizedUnsignedType_t<8> ) == 8 );
STATIC_REQUIRE( sizeof( DoubleWidthUnsignedType_t<std::uint8_t> ) == 2 );
STATIC_REQUIRE( std::is_unsigned<DoubleWidthUnsignedType_t<std::uint8_t>>::value );
STATIC_REQUIRE( sizeof( DoubleWidthUnsignedType_t<std::uint16_t> ) == 4 );
STATIC_REQUIRE( std::is_unsigned<DoubleWidthUnsignedType_t<std::uint16_t>>::value );
STATIC_REQUIRE( sizeof( DoubleWidthUnsignedType_t<std::uint32_t> ) == 8 );
STATIC_REQUIRE( std::is_unsigned<DoubleWidthUnsignedType_t<std::uint32_t>>::value );
}
TEST_CASE( "extendedMult 32x32", "[integer][approvals]" ) {
// a x 0 == 0
CommutativeMultCheck<uint32_t>( 0x1234'5678, 0, 0, 0 );
// bit carried from low half to upper half
CommutativeMultCheck<uint32_t>( uint32_t(1) << 31, 2, 1, 0 );
// bits in upper half on one side, bits in lower half on other side
CommutativeMultCheck<uint32_t>( 0xdcdc'0000, 0x0000'aabb, 0x0000'934b, 0x6cb4'0000 );
// Some input numbers without interesting patterns
CommutativeMultCheck<uint32_t>(
0xaaaa'aaaa, 0xbbbb'bbbb, 0x7d27'd27c, 0x2d82'd82e );
CommutativeMultCheck<uint32_t>(
0x7d27'd27c, 0x2d82'd82e, 0x163f'f7e8, 0xc5b8'7248 );
CommutativeMultCheck<uint32_t>(
0xdead'beef, 0xfeed'feed, 0xddbf'6809, 0x6f8d'e543 );
CommutativeMultCheck<uint32_t>(
0xddbf'6809, 0x6f8d'e543, 0x60a0'e71e, 0x751d'475b );
}
TEST_CASE( "extendedMult 8x8", "[integer][approvals]" ) {
// a x 0 == 0
CommutativeMultCheck<uint8_t>( 0xcd, 0, 0, 0 );
// bit carried from low half to upper half
CommutativeMultCheck<uint8_t>( uint8_t( 1 ) << 7, 2, 1, 0 );
// bits in upper half on one side, bits in lower half on other side
CommutativeMultCheck<uint8_t>( 0x80, 0x03, 0x01, 0x80 );
// Some input numbers without interesting patterns
CommutativeMultCheck<uint8_t>( 0xaa, 0xbb, 0x7c, 0x2e );
CommutativeMultCheck<uint8_t>( 0x7c, 0x2e, 0x16, 0x48 );
CommutativeMultCheck<uint8_t>( 0xdc, 0xcd, 0xb0, 0x2c );
CommutativeMultCheck<uint8_t>( 0xb0, 0x2c, 0x1e, 0x40 );
}
TEST_CASE( "negative and positive signed integers keep their order after transposeToNaturalOrder",
"[integer][approvals]") {
using Catch::Detail::transposeToNaturalOrder;
int32_t negative( -1 );
int32_t positive( 1 );
uint32_t adjusted_negative =
transposeToNaturalOrder<int32_t>( static_cast<uint32_t>( negative ) );
uint32_t adjusted_positive =
transposeToNaturalOrder<int32_t>( static_cast<uint32_t>( positive ) );
REQUIRE( adjusted_negative < adjusted_positive );
REQUIRE( adjusted_positive - adjusted_negative == 2 );
// Conversion has to be reversible
REQUIRE( negative == static_cast<int32_t>( transposeToNaturalOrder<int32_t>(
adjusted_negative ) ) );
REQUIRE( positive == static_cast<int32_t>( transposeToNaturalOrder<int32_t>(
adjusted_positive ) ) );
}
TEST_CASE( "unsigned integers are unchanged by transposeToNaturalOrder",
"[integer][approvals]") {
using Catch::Detail::transposeToNaturalOrder;
uint32_t max = std::numeric_limits<uint32_t>::max();
uint32_t zero = 0;
REQUIRE( max == transposeToNaturalOrder<uint32_t>( max ) );
REQUIRE( zero == transposeToNaturalOrder<uint32_t>( zero ) );
}

View File

@ -0,0 +1,455 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
// Adapted from donated nonius code.
#if defined( __GNUC__ ) || defined( __clang__ )
# pragma GCC diagnostic ignored "-Wfloat-equal"
#endif
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_config.hpp>
#include <catch2/benchmark/catch_benchmark.hpp>
#include <catch2/benchmark/catch_chronometer.hpp>
#include <catch2/benchmark/detail/catch_analyse.hpp>
#include <catch2/benchmark/detail/catch_benchmark_function.hpp>
#include <catch2/benchmark/detail/catch_estimate_clock.hpp>
#include <numeric>
namespace {
struct manual_clock {
public:
using duration = std::chrono::nanoseconds;
using time_point = std::chrono::time_point<manual_clock, duration>;
using rep = duration::rep;
using period = duration::period;
enum { is_steady = true };
static time_point now() {
return time_point(duration(tick()));
}
static void advance(int ticks = 1) {
tick() += ticks;
}
private:
static rep& tick() {
static rep the_tick = 0;
return the_tick;
}
};
struct counting_clock {
public:
using duration = std::chrono::nanoseconds;
using time_point = std::chrono::time_point<counting_clock, duration>;
using rep = duration::rep;
using period = duration::period;
enum { is_steady = true };
static time_point now() {
static rep ticks = 0;
return time_point(duration(ticks += rate()));
}
static void set_rate(rep new_rate) { rate() = new_rate; }
private:
static rep& rate() {
static rep the_rate = 1;
return the_rate;
}
};
struct TestChronometerModel : Catch::Benchmark::Detail::ChronometerConcept {
int started = 0;
int finished = 0;
void start() override { ++started; }
void finish() override { ++finished; }
};
} // namespace
TEST_CASE("warmup", "[benchmark]") {
auto rate = 1000;
counting_clock::set_rate(rate);
auto start = counting_clock::now();
auto iterations = Catch::Benchmark::Detail::warmup<counting_clock>();
auto end = counting_clock::now();
REQUIRE((iterations * rate) > Catch::Benchmark::Detail::warmup_time.count());
REQUIRE((end - start) > Catch::Benchmark::Detail::warmup_time);
}
TEST_CASE("resolution", "[benchmark]") {
auto rate = 1000;
counting_clock::set_rate(rate);
size_t count = 10;
auto res = Catch::Benchmark::Detail::resolution<counting_clock>(static_cast<int>(count));
REQUIRE(res.size() == count);
for (size_t i = 1; i < count; ++i) {
REQUIRE(res[i] == rate);
}
}
TEST_CASE("estimate_clock_resolution", "[benchmark]") {
auto rate = 2'000;
counting_clock::set_rate(rate);
int iters = 160'000;
auto res = Catch::Benchmark::Detail::estimate_clock_resolution<counting_clock>(iters);
REQUIRE(res.mean.count() == rate);
REQUIRE(res.outliers.total() == 0);
}
TEST_CASE("benchmark function call", "[benchmark]") {
SECTION("without chronometer") {
auto called = 0;
auto model = TestChronometerModel{};
auto meter = Catch::Benchmark::Chronometer{ model, 1 };
auto fn = Catch::Benchmark::Detail::BenchmarkFunction{ [&] {
CHECK(model.started == 1);
CHECK(model.finished == 0);
++called;
} };
fn(meter);
CHECK(model.started == 1);
CHECK(model.finished == 1);
CHECK(called == 1);
}
SECTION("with chronometer") {
auto called = 0;
auto model = TestChronometerModel{};
auto meter = Catch::Benchmark::Chronometer{ model, 1 };
auto fn = Catch::Benchmark::Detail::BenchmarkFunction{ [&](Catch::Benchmark::Chronometer) {
CHECK(model.started == 0);
CHECK(model.finished == 0);
++called;
} };
fn(meter);
CHECK(model.started == 0);
CHECK(model.finished == 0);
CHECK(called == 1);
}
}
TEST_CASE("uniform samples", "[benchmark]") {
std::vector<double> samples(100);
std::fill(samples.begin(), samples.end(), 23);
auto e = Catch::Benchmark::Detail::bootstrap(
0.95,
samples.data(),
samples.data() + samples.size(),
samples,
[]( double const* a, double const* b ) {
auto sum = std::accumulate(a, b, 0.);
return sum / (b - a);
});
CHECK(e.point == 23);
CHECK(e.upper_bound == 23);
CHECK(e.lower_bound == 23);
CHECK(e.confidence_interval == 0.95);
}
TEST_CASE("normal_cdf", "[benchmark][approvals]") {
using Catch::Benchmark::Detail::normal_cdf;
using Catch::Approx;
CHECK(normal_cdf(0.000000) == Approx(0.50000000000000000));
CHECK(normal_cdf(1.000000) == Approx(0.84134474606854293));
CHECK(normal_cdf(-1.000000) == Approx(0.15865525393145705));
CHECK(normal_cdf(2.809729) == Approx(0.99752083845315409));
CHECK(normal_cdf(-1.352570) == Approx(0.08809652095066035));
}
TEST_CASE("erfc_inv", "[benchmark]") {
using Catch::Benchmark::Detail::erfc_inv;
using Catch::Approx;
CHECK(erfc_inv(1.103560) == Approx(-0.09203687623843015));
CHECK(erfc_inv(1.067400) == Approx(-0.05980291115763361));
CHECK(erfc_inv(0.050000) == Approx(1.38590382434967796));
}
TEST_CASE("normal_quantile", "[benchmark]") {
using Catch::Benchmark::Detail::normal_quantile;
using Catch::Approx;
CHECK(normal_quantile(0.551780) == Approx(0.13015979861484198));
CHECK(normal_quantile(0.533700) == Approx(0.08457408802851875));
CHECK(normal_quantile(0.025000) == Approx(-1.95996398454005449));
}
TEST_CASE("mean", "[benchmark]") {
std::vector<double> x{ 10., 20., 14., 16., 30., 24. };
auto m = Catch::Benchmark::Detail::mean(x.data(), x.data() + x.size());
REQUIRE(m == 19.);
}
TEST_CASE("weighted_average_quantile", "[benchmark]") {
std::vector<double> x{ 10., 20., 14., 16., 30., 24. };
auto q1 = Catch::Benchmark::Detail::weighted_average_quantile(1, 4, x.data(), x.data() + x.size());
auto med = Catch::Benchmark::Detail::weighted_average_quantile(1, 2, x.data(), x.data() + x.size());
auto q3 = Catch::Benchmark::Detail::weighted_average_quantile(3, 4, x.data(), x.data() + x.size());
REQUIRE(q1 == 14.5);
REQUIRE(med == 18.);
REQUIRE(q3 == 23.);
}
TEST_CASE("classify_outliers", "[benchmark]") {
auto require_outliers = [](Catch::Benchmark::OutlierClassification o, int los, int lom, int him, int his) {
REQUIRE(o.low_severe == los);
REQUIRE(o.low_mild == lom);
REQUIRE(o.high_mild == him);
REQUIRE(o.high_severe == his);
REQUIRE(o.total() == los + lom + him + his);
};
SECTION("none") {
std::vector<double> x{ 10., 20., 14., 16., 30., 24. };
auto o = Catch::Benchmark::Detail::classify_outliers(
x.data(), x.data() + x.size() );
REQUIRE(o.samples_seen == static_cast<int>(x.size()));
require_outliers(o, 0, 0, 0, 0);
}
SECTION("low severe") {
std::vector<double> x{ -12., 20., 14., 16., 30., 24. };
auto o = Catch::Benchmark::Detail::classify_outliers(
x.data(), x.data() + x.size() );
REQUIRE(o.samples_seen == static_cast<int>(x.size()));
require_outliers(o, 1, 0, 0, 0);
}
SECTION("low mild") {
std::vector<double> x{ 1., 20., 14., 16., 30., 24. };
auto o = Catch::Benchmark::Detail::classify_outliers(
x.data(), x.data() + x.size() );
REQUIRE(o.samples_seen == static_cast<int>(x.size()));
require_outliers(o, 0, 1, 0, 0);
}
SECTION("high mild") {
std::vector<double> x{ 10., 20., 14., 16., 36., 24. };
auto o = Catch::Benchmark::Detail::classify_outliers(
x.data(), x.data() + x.size() );
REQUIRE(o.samples_seen == static_cast<int>(x.size()));
require_outliers(o, 0, 0, 1, 0);
}
SECTION("high severe") {
std::vector<double> x{ 10., 20., 14., 16., 49., 24. };
auto o = Catch::Benchmark::Detail::classify_outliers(
x.data(), x.data() + x.size() );
REQUIRE(o.samples_seen == static_cast<int>(x.size()));
require_outliers(o, 0, 0, 0, 1);
}
SECTION("mixed") {
std::vector<double> x{ -20., 20., 14., 16., 39., 24. };
auto o = Catch::Benchmark::Detail::classify_outliers(
x.data(), x.data() + x.size() );
REQUIRE(o.samples_seen == static_cast<int>(x.size()));
require_outliers(o, 1, 0, 1, 0);
}
}
TEST_CASE("analyse", "[approvals][benchmark]") {
Catch::ConfigData data{};
data.benchmarkConfidenceInterval = 0.95;
data.benchmarkNoAnalysis = false;
data.benchmarkResamples = 1000;
data.benchmarkSamples = 99;
Catch::Config config{data};
using FDuration = Catch::Benchmark::FDuration;
std::vector<FDuration> samples(99);
for (size_t i = 0; i < samples.size(); ++i) {
samples[i] = FDuration(23 + (i % 3 - 1));
}
auto analysis = Catch::Benchmark::Detail::analyse(config, samples.data(), samples.data() + samples.size());
CHECK( analysis.mean.point.count() == 23 );
CHECK( analysis.mean.lower_bound.count() < 23 );
CHECK(analysis.mean.lower_bound.count() > 22);
CHECK(analysis.mean.upper_bound.count() > 23);
CHECK(analysis.mean.upper_bound.count() < 24);
CHECK(analysis.standard_deviation.point.count() > 0.5);
CHECK(analysis.standard_deviation.point.count() < 1);
CHECK(analysis.standard_deviation.lower_bound.count() > 0.5);
CHECK(analysis.standard_deviation.lower_bound.count() < 1);
CHECK(analysis.standard_deviation.upper_bound.count() > 0.5);
CHECK(analysis.standard_deviation.upper_bound.count() < 1);
CHECK(analysis.outliers.total() == 0);
CHECK(analysis.outliers.low_mild == 0);
CHECK(analysis.outliers.low_severe == 0);
CHECK(analysis.outliers.high_mild == 0);
CHECK(analysis.outliers.high_severe == 0);
CHECK(analysis.outliers.samples_seen == static_cast<int>(samples.size()));
CHECK(analysis.outlier_variance < 0.5);
CHECK(analysis.outlier_variance > 0);
}
TEST_CASE("analyse no analysis", "[benchmark]") {
Catch::ConfigData data{};
data.benchmarkConfidenceInterval = 0.95;
data.benchmarkNoAnalysis = true;
data.benchmarkResamples = 1000;
data.benchmarkSamples = 99;
Catch::Config config{ data };
using FDuration = Catch::Benchmark::FDuration;
std::vector<FDuration> samples(99);
for (size_t i = 0; i < samples.size(); ++i) {
samples[i] = FDuration(23 + (i % 3 - 1));
}
auto analysis = Catch::Benchmark::Detail::analyse(config, samples.data(), samples.data() + samples.size());
CHECK(analysis.mean.point.count() == 23);
CHECK(analysis.mean.lower_bound.count() == 23);
CHECK(analysis.mean.upper_bound.count() == 23);
CHECK(analysis.standard_deviation.point.count() == 0);
CHECK(analysis.standard_deviation.lower_bound.count() == 0);
CHECK(analysis.standard_deviation.upper_bound.count() == 0);
CHECK(analysis.outliers.total() == 0);
CHECK(analysis.outliers.low_mild == 0);
CHECK(analysis.outliers.low_severe == 0);
CHECK(analysis.outliers.high_mild == 0);
CHECK(analysis.outliers.high_severe == 0);
CHECK(analysis.outliers.samples_seen == 0);
CHECK(analysis.outlier_variance == 0);
}
TEST_CASE("run_for_at_least, int", "[benchmark]") {
manual_clock::duration time(100);
int old_x = 1;
auto Timing = Catch::Benchmark::Detail::run_for_at_least<manual_clock>(time, 1, [&old_x](int x) -> int {
CHECK(x >= old_x);
manual_clock::advance(x);
old_x = x;
return x + 17;
});
REQUIRE(Timing.elapsed >= time);
REQUIRE(Timing.result == Timing.iterations + 17);
REQUIRE(Timing.iterations >= time.count());
}
TEST_CASE("run_for_at_least, chronometer", "[benchmark]") {
manual_clock::duration time(100);
int old_runs = 1;
auto Timing = Catch::Benchmark::Detail::run_for_at_least<manual_clock>(time, 1, [&old_runs](Catch::Benchmark::Chronometer meter) -> int {
CHECK(meter.runs() >= old_runs);
manual_clock::advance(100);
meter.measure([] {
manual_clock::advance(1);
});
old_runs = meter.runs();
return meter.runs() + 17;
});
REQUIRE(Timing.elapsed >= time);
REQUIRE(Timing.result == Timing.iterations + 17);
REQUIRE(Timing.iterations >= time.count());
}
TEST_CASE("measure", "[benchmark]") {
auto r = Catch::Benchmark::Detail::measure<manual_clock>([](int x) -> int {
CHECK(x == 17);
manual_clock::advance(42);
return 23;
}, 17);
auto s = Catch::Benchmark::Detail::measure<manual_clock>([](int x) -> int {
CHECK(x == 23);
manual_clock::advance(69);
return 17;
}, 23);
CHECK(r.elapsed.count() == 42);
CHECK(r.result == 23);
CHECK(r.iterations == 1);
CHECK(s.elapsed.count() == 69);
CHECK(s.result == 17);
CHECK(s.iterations == 1);
}
TEST_CASE("run benchmark", "[benchmark][approvals]") {
counting_clock::set_rate(1000);
auto start = counting_clock::now();
Catch::Benchmark::Benchmark bench{ "Test Benchmark", [](Catch::Benchmark::Chronometer meter) {
counting_clock::set_rate(100000);
meter.measure([] { return counting_clock::now(); });
} };
bench.run<counting_clock>();
auto end = counting_clock::now();
CHECK((end - start).count() == 2867251000);
}
TEST_CASE("Failing benchmarks", "[!benchmark][.approvals]") {
SECTION("empty", "Benchmark that has been optimized away (because it is empty)") {
BENCHMARK("Empty benchmark") {};
}
SECTION("throw", "Benchmark that throws an exception") {
BENCHMARK("Throwing benchmark") {
throw "just a plain literal, bleh";
};
}
SECTION("assert", "Benchmark that asserts inside") {
BENCHMARK("Asserting benchmark") {
REQUIRE(1 == 2);
};
}
SECTION("fail", "Benchmark that fails inside") {
BENCHMARK("FAIL'd benchmark") {
FAIL("This benchmark only fails, nothing else");
};
}
}
TEST_CASE( "Failing benchmark respects should-fail",
"[!shouldfail][!benchmark][approvals]" ) {
BENCHMARK( "Asserting benchmark" ) { REQUIRE( 1 == 2 ); };
}

View File

@ -0,0 +1,152 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_macros.hpp>
#include <catch2/internal/catch_jsonwriter.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include <sstream>
namespace {
struct Custom {};
static std::ostream& operator<<( std::ostream& os, Custom const& ) {
return os << "custom";
}
} // namespace
TEST_CASE( "JsonWriter", "[JSON][JsonWriter]" ) {
std::stringstream stream;
SECTION( "Newly constructed JsonWriter does nothing" ) {
Catch::JsonValueWriter writer{ stream };
REQUIRE( stream.str() == "" );
}
SECTION( "Calling writeObject will create an empty pair of braces" ) {
{ auto writer = Catch::JsonValueWriter{ stream }.writeObject(); }
REQUIRE( stream.str() == "{\n}" );
}
SECTION( "Calling writeObject with key will create an object to write the "
"value" ) {
using Catch::Matchers::ContainsSubstring;
{
auto writer = Catch::JsonValueWriter{ stream }.writeObject();
writer.write( "int" ).write( 1 );
writer.write( "double" ).write( 1.5 );
writer.write( "true" ).write( true );
writer.write( "false" ).write( false );
writer.write( "string" ).write( "this is a string" );
writer.write( "array" ).writeArray().write( 1 ).write( 2 );
}
REQUIRE_THAT(
stream.str(),
ContainsSubstring( "\"int\": 1," ) &&
ContainsSubstring( "\"double\": 1.5," ) &&
ContainsSubstring( "\"true\": true," ) &&
ContainsSubstring( "\"false\": false," ) &&
ContainsSubstring( "\"string\": \"this is a string\"," ) &&
ContainsSubstring( "\"array\": [\n 1,\n 2\n ]\n}" ) );
}
SECTION( "nesting objects" ) {
using Catch::Matchers::ContainsSubstring;
{
auto writer = Catch::JsonValueWriter{ stream }.writeObject();
writer.write( "empty_object" ).writeObject();
writer.write( "fully_object" )
.writeObject()
.write( "key" )
.write( 1 );
}
REQUIRE_THAT( stream.str(),
ContainsSubstring( "\"empty_object\": {\n }," ) &&
ContainsSubstring(
"\"fully_object\": {\n \"key\": 1\n }" ) );
}
SECTION( "Calling writeArray will create an empty pair of braces" ) {
{ auto writer = Catch::JsonValueWriter{ stream }.writeArray(); }
REQUIRE( stream.str() == "[\n]" );
}
SECTION( "Calling writeArray creates array to write the values to" ) {
{
auto writer = Catch::JsonValueWriter{ stream }.writeArray();
writer.write( 1 );
writer.write( 1.5 );
writer.write( true );
writer.write( false );
writer.write( "this is a string" );
writer.writeObject().write( "object" ).write( 42 );
writer.writeArray().write( "array" ).write( 42.5 );
}
REQUIRE( stream.str() == "[\n 1,\n 1.5,\n true,\n false,\n \"this is a string\",\n {\n \"object\": 42\n },\n [\n \"array\",\n 42.5\n ]\n]" );
}
SECTION(
"Moved from JsonObjectWriter shall not insert superfluous brace" ) {
{
auto writer = Catch::JsonObjectWriter{ stream };
auto another_writer = std::move( writer );
}
REQUIRE( stream.str() == "{\n}" );
}
SECTION(
"Moved from JsonArrayWriter shall not insert superfluous bracket" ) {
{
auto writer = Catch::JsonArrayWriter{ stream };
auto another_writer = std::move( writer );
}
REQUIRE( stream.str() == "[\n]" );
}
SECTION( "Custom class shall be quoted" ) {
Catch::JsonValueWriter{ stream }.write( Custom{} );
REQUIRE( stream.str() == "\"custom\"" );
}
}
TEST_CASE( "JsonWriter escapes charaters in strings properly", "[JsonWriter]" ) {
std::stringstream sstream;
SECTION( "Quote in a string is escaped" ) {
Catch::JsonValueWriter{ sstream }.write( "\"" );
REQUIRE( sstream.str() == "\"\\\"\"" );
}
SECTION("Backslash in a string is escaped") {
Catch::JsonValueWriter{ sstream }.write( "\\" );
REQUIRE( sstream.str() == "\"\\\\\"" );
}
SECTION( "Forward slash in a string is **not** escaped" ) {
Catch::JsonValueWriter{ sstream }.write( "/" );
REQUIRE( sstream.str() == "\"/\"" );
}
SECTION( "Backspace in a string is escaped" ) {
Catch::JsonValueWriter{ sstream }.write( "\b" );
REQUIRE( sstream.str() == "\"\\b\"" );
}
SECTION( "Formfeed in a string is escaped" ) {
Catch::JsonValueWriter{ sstream }.write( "\f" );
REQUIRE( sstream.str() == "\"\\f\"" );
}
SECTION( "linefeed in a string is escaped" ) {
Catch::JsonValueWriter{ sstream }.write( "\n" );
REQUIRE( sstream.str() == "\"\\n\"" );
}
SECTION( "carriage return in a string is escaped" ) {
Catch::JsonValueWriter{ sstream }.write( "\r" );
REQUIRE( sstream.str() == "\"\\r\"" );
}
SECTION( "tab in a string is escaped" ) {
Catch::JsonValueWriter{ sstream }.write( "\t" );
REQUIRE( sstream.str() == "\"\\t\"" );
}
SECTION( "combination of characters is escaped" ) {
Catch::JsonValueWriter{ sstream }.write( "\\/\t\r\n" );
REQUIRE( sstream.str() == "\"\\\\/\\t\\r\\n\"" );
}
}

View File

@ -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 ) );
}
}

View File

@ -0,0 +1,254 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/internal/catch_test_case_tracker.hpp>
using namespace Catch;
namespace {
Catch::TestCaseTracking::NameAndLocationRef makeNAL( StringRef name ) {
return Catch::TestCaseTracking::NameAndLocationRef( name, Catch::SourceLineInfo("",0) );
}
}
TEST_CASE( "Tracker" ) {
TrackerContext ctx;
ctx.startRun();
ctx.startCycle();
ITracker& testCase = SectionTracker::acquire( ctx, makeNAL( "Testcase" ) );
REQUIRE( testCase.isOpen() );
ITracker& s1 = SectionTracker::acquire( ctx, makeNAL( "S1" ) );
REQUIRE( s1.isOpen() );
SECTION( "successfully close one section" ) {
s1.close();
REQUIRE( s1.isSuccessfullyCompleted() );
REQUIRE( testCase.isComplete() == false );
testCase.close();
REQUIRE( ctx.completedCycle() );
REQUIRE( testCase.isSuccessfullyCompleted() );
}
SECTION( "fail one section" ) {
s1.fail();
REQUIRE( s1.isComplete() );
REQUIRE( s1.isSuccessfullyCompleted() == false );
REQUIRE( testCase.isComplete() == false );
testCase.close();
REQUIRE( ctx.completedCycle() );
REQUIRE( testCase.isSuccessfullyCompleted() == false );
SECTION( "re-enter after failed section" ) {
ctx.startCycle();
ITracker& testCase2 = SectionTracker::acquire( ctx, makeNAL( "Testcase" ) );
REQUIRE( testCase2.isOpen() );
ITracker& s1b = SectionTracker::acquire( ctx, makeNAL( "S1" ) );
REQUIRE( s1b.isOpen() == false );
testCase2.close();
REQUIRE( ctx.completedCycle() );
REQUIRE( testCase.isComplete() );
REQUIRE( testCase.isSuccessfullyCompleted() );
}
SECTION( "re-enter after failed section and find next section" ) {
ctx.startCycle();
ITracker& testCase2 = SectionTracker::acquire( ctx, makeNAL( "Testcase" ) );
REQUIRE( testCase2.isOpen() );
ITracker& s1b = SectionTracker::acquire( ctx, makeNAL( "S1" ) );
REQUIRE( s1b.isOpen() == false );
ITracker& s2 = SectionTracker::acquire( ctx, makeNAL( "S2" ) );
REQUIRE( s2.isOpen() );
s2.close();
REQUIRE( ctx.completedCycle() );
testCase2.close();
REQUIRE( testCase.isComplete() );
REQUIRE( testCase.isSuccessfullyCompleted() );
}
}
SECTION( "successfully close one section, then find another" ) {
s1.close();
ITracker& s2 = SectionTracker::acquire( ctx, makeNAL( "S2" ) );
REQUIRE( s2.isOpen() == false );
testCase.close();
REQUIRE( testCase.isComplete() == false );
SECTION( "Re-enter - skips S1 and enters S2" ) {
ctx.startCycle();
ITracker& testCase2 = SectionTracker::acquire( ctx, makeNAL( "Testcase" ) );
REQUIRE( testCase2.isOpen() );
ITracker& s1b = SectionTracker::acquire( ctx, makeNAL( "S1" ) );
REQUIRE( s1b.isOpen() == false );
ITracker& s2b = SectionTracker::acquire( ctx, makeNAL( "S2" ) );
REQUIRE( s2b.isOpen() );
REQUIRE( ctx.completedCycle() == false );
SECTION ("Successfully close S2") {
s2b.close();
REQUIRE( ctx.completedCycle() );
REQUIRE( s2b.isSuccessfullyCompleted() );
REQUIRE( testCase2.isComplete() == false );
testCase2.close();
REQUIRE( testCase2.isSuccessfullyCompleted() );
}
SECTION ("fail S2") {
s2b.fail();
REQUIRE( ctx.completedCycle() );
REQUIRE( s2b.isComplete() );
REQUIRE( s2b.isSuccessfullyCompleted() == false );
testCase2.close();
REQUIRE( testCase2.isSuccessfullyCompleted() == false );
// Need a final cycle
ctx.startCycle();
ITracker& testCase3 = SectionTracker::acquire( ctx, makeNAL( "Testcase" ) );
REQUIRE( testCase3.isOpen() );
ITracker& s1c = SectionTracker::acquire( ctx, makeNAL( "S1" ) );
REQUIRE( s1c.isOpen() == false );
ITracker& s2c = SectionTracker::acquire( ctx, makeNAL( "S2" ) );
REQUIRE( s2c.isOpen() == false );
testCase3.close();
REQUIRE( testCase3.isSuccessfullyCompleted() );
}
}
}
SECTION( "open a nested section" ) {
ITracker& s2 = SectionTracker::acquire( ctx, makeNAL( "S2" ) );
REQUIRE( s2.isOpen() );
s2.close();
REQUIRE( s2.isComplete() );
REQUIRE( s1.isComplete() == false );
s1.close();
REQUIRE( s1.isComplete() );
REQUIRE( testCase.isComplete() == false );
testCase.close();
REQUIRE( testCase.isComplete() );
}
}
static bool previouslyRun = false;
static bool previouslyRunNested = false;
TEST_CASE( "#1394", "[.][approvals][tracker]" ) {
// -- Don't re-run after specified section is done
REQUIRE(previouslyRun == false);
SECTION( "RunSection" ) {
previouslyRun = true;
}
SECTION( "SkipSection" ) {
// cause an error if this section is called because it shouldn't be
REQUIRE(1 == 0);
}
}
TEST_CASE( "#1394 nested", "[.][approvals][tracker]" ) {
REQUIRE(previouslyRunNested == false);
SECTION( "NestedRunSection" ) {
SECTION( "s1" ) {
previouslyRunNested = true;
}
}
SECTION( "NestedSkipSection" ) {
// cause an error if this section is called because it shouldn't be
REQUIRE(1 == 0);
}
}
// Selecting a "not last" section inside a test case via -c "section" would
// previously only run the first subsection, instead of running all of them.
// This allows us to check that `"#1670 regression check" -c A` leads to
// 2 successful assertions.
TEST_CASE("#1670 regression check", "[.approvals][tracker]") {
SECTION("A") {
SECTION("1") SUCCEED();
SECTION("2") SUCCEED();
}
SECTION("B") {
SECTION("1") SUCCEED();
SECTION("2") SUCCEED();
}
}
// #1938 required a rework on how generator tracking works, so that `GENERATE`
// supports being sandwiched between two `SECTION`s. The following tests check
// various other scenarios through checking output in approval tests.
TEST_CASE("#1938 - GENERATE after a section", "[.][regression][generators]") {
SECTION("A") {
SUCCEED("A");
}
auto m = GENERATE(1, 2, 3);
SECTION("B") {
REQUIRE(m);
}
}
TEST_CASE("#1938 - flat generate", "[.][regression][generators]") {
auto m = GENERATE(1, 2, 3);
REQUIRE(m);
}
TEST_CASE("#1938 - nested generate", "[.][regression][generators]") {
auto m = GENERATE(1, 2, 3);
auto n = GENERATE(1, 2, 3);
REQUIRE(m);
REQUIRE(n);
}
TEST_CASE("#1938 - mixed sections and generates", "[.][regression][generators]") {
auto i = GENERATE(1, 2);
SECTION("A") {
SUCCEED("A");
}
auto j = GENERATE(3, 4);
SECTION("B") {
SUCCEED("B");
}
auto k = GENERATE(5, 6);
CAPTURE(i, j, k);
SUCCEED();
}
TEST_CASE("#1938 - Section followed by flat generate", "[.][regression][generators]") {
SECTION("A") {
REQUIRE(1);
}
auto m = GENERATE(2, 3);
REQUIRE(m);
}

View File

@ -0,0 +1,609 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/internal/catch_floating_point_helpers.hpp>
#include <catch2/internal/catch_random_integer_helpers.hpp>
#include <catch2/internal/catch_random_number_generator.hpp>
#include <catch2/internal/catch_random_seed_generation.hpp>
#include <catch2/internal/catch_uniform_floating_point_distribution.hpp>
#include <catch2/internal/catch_uniform_integer_distribution.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/matchers/catch_matchers_range_equals.hpp>
#include <random>
TEST_CASE("Our PCG implementation provides expected results for known seeds", "[rng]") {
Catch::SimplePcg32 rng;
SECTION("Default seeded") {
REQUIRE(rng() == 0xfcdb943b);
REQUIRE(rng() == 0x6f55b921);
REQUIRE(rng() == 0x4c17a916);
REQUIRE(rng() == 0x71eae25f);
REQUIRE(rng() == 0x6ce7909c);
}
SECTION("Specific seed") {
rng.seed(0xabcd1234);
REQUIRE(rng() == 0x57c08495);
REQUIRE(rng() == 0x33c956ac);
REQUIRE(rng() == 0x2206fd76);
REQUIRE(rng() == 0x3501a35b);
REQUIRE(rng() == 0xfdffb30f);
// Also check repeated output after reseeding
rng.seed(0xabcd1234);
REQUIRE(rng() == 0x57c08495);
REQUIRE(rng() == 0x33c956ac);
REQUIRE(rng() == 0x2206fd76);
REQUIRE(rng() == 0x3501a35b);
REQUIRE(rng() == 0xfdffb30f);
}
}
TEST_CASE("Comparison ops", "[rng]") {
using Catch::SimplePcg32;
REQUIRE(SimplePcg32{} == SimplePcg32{});
REQUIRE(SimplePcg32{ 0 } != SimplePcg32{});
REQUIRE_FALSE(SimplePcg32{ 1 } == SimplePcg32{ 2 });
REQUIRE_FALSE(SimplePcg32{ 1 } != SimplePcg32{ 1 });
}
TEST_CASE("Random seed generation reports unknown methods", "[rng][seed]") {
REQUIRE_THROWS(Catch::generateRandomSeed(static_cast<Catch::GenerateFrom>(77)));
}
TEST_CASE("Random seed generation accepts known methods", "[rng][seed]") {
using Catch::GenerateFrom;
const auto method = GENERATE(
GenerateFrom::Time,
GenerateFrom::RandomDevice,
GenerateFrom::Default
);
REQUIRE_NOTHROW(Catch::generateRandomSeed(method));
}
TEMPLATE_TEST_CASE("uniform_floating_point_distribution never returns infs from finite range",
"[rng][distribution][floating-point][approvals]", float, double) {
std::random_device rd{};
Catch::SimplePcg32 pcg( rd() );
Catch::uniform_floating_point_distribution<TestType> dist(
-std::numeric_limits<TestType>::max(),
std::numeric_limits<TestType>::max() );
for (size_t i = 0; i < 10'000; ++i) {
auto ret = dist( pcg );
REQUIRE_FALSE( std::isinf( ret ) );
REQUIRE_FALSE( std::isnan( ret ) );
}
}
TEST_CASE( "fillBitsFrom - shortening and stretching", "[rng][approvals]" ) {
using Catch::Detail::fillBitsFrom;
// The seed is not important, but the numbers below have to be repeatable.
// They should also exhibit the same general pattern of being prefixes
Catch::SimplePcg32 pcg( 0xaabb'ccdd );
SECTION( "Shorten to 8 bits" ) {
// We cast the result to avoid dealing with char-like type in uint8_t
auto shortened = static_cast<uint32_t>( fillBitsFrom<uint8_t>( pcg ) );
REQUIRE( shortened == 0xcc );
}
SECTION( "Shorten to 16 bits" ) {
auto shortened = fillBitsFrom<uint16_t>( pcg );
REQUIRE( shortened == 0xccbe );
}
SECTION( "Keep at 32 bits" ) {
auto n = fillBitsFrom<uint32_t>( pcg );
REQUIRE( n == 0xccbe'5f04 );
}
SECTION( "Stretch to 64 bits" ) {
auto stretched = fillBitsFrom<uint64_t>( pcg );
REQUIRE( stretched == 0xccbe'5f04'a424'a486 );
}
}
TEST_CASE("uniform_integer_distribution can return the bounds", "[rng][distribution]") {
Catch::uniform_integer_distribution<int32_t> dist( -10, 10 );
REQUIRE( dist.a() == -10 );
REQUIRE( dist.b() == 10 );
}
namespace {
template <typename T>
static void CheckReturnValue(Catch::uniform_integer_distribution<T>& dist,
Catch::SimplePcg32& rng,
T target) {
REQUIRE( dist.a() == dist.b() );
for (int i = 0; i < 1'000; ++i) {
REQUIRE( dist( rng ) == target );
}
}
}
TEMPLATE_TEST_CASE( "uniform_integer_distribution can handle unit ranges",
"[rng][distribution][approvals]",
unsigned char,
signed char,
char,
uint8_t,
int8_t,
uint16_t,
int16_t,
uint32_t,
int32_t,
uint64_t,
int64_t,
size_t,
ptrdiff_t) {
// We want random seed to sample different parts of the rng state,
// the output is predetermined anyway
std::random_device rd;
auto seed = rd();
CAPTURE( seed );
Catch::SimplePcg32 pcg( seed );
// We check unitary ranges of 3 different values, min for type, max for type,
// some value inbetween just to make sure
SECTION("lowest value") {
constexpr auto lowest = std::numeric_limits<TestType>::min();
Catch::uniform_integer_distribution<TestType> dist( lowest, lowest );
CheckReturnValue( dist, pcg, lowest );
}
SECTION( "highest value" ) {
constexpr auto highest = std::numeric_limits<TestType>::max();
Catch::uniform_integer_distribution<TestType> dist( highest, highest );
CheckReturnValue( dist, pcg, highest );
}
SECTION( "some value" ) {
constexpr auto some = TestType( 42 );
Catch::uniform_integer_distribution<TestType> dist( some, some );
CheckReturnValue( dist, pcg, some );
}
}
// Bool needs its own test because it doesn't have a valid "third" value
TEST_CASE( "uniform_integer_distribution can handle boolean unit ranges",
"[rng][distribution][approvals]" ) {
// We want random seed to sample different parts of the rng state,
// the output is predetermined anyway
std::random_device rd;
auto seed = rd();
CAPTURE( seed );
Catch::SimplePcg32 pcg( seed );
// We check unitary ranges of 3 different values, min for type, max for
// type, some value inbetween just to make sure
SECTION( "lowest value" ) {
Catch::uniform_integer_distribution<bool> dist( false, false );
CheckReturnValue( dist, pcg, false );
}
SECTION( "highest value" ) {
Catch::uniform_integer_distribution<bool> dist( true, true );
CheckReturnValue( dist, pcg, true );
}
}
TEMPLATE_TEST_CASE( "uniform_integer_distribution can handle full width ranges",
"[rng][distribution][approvals]",
unsigned char,
signed char,
char,
uint8_t,
int8_t,
uint16_t,
int16_t,
uint32_t,
int32_t,
uint64_t,
int64_t ) {
// We want random seed to sample different parts of the rng state,
// the output is predetermined anyway
std::random_device rd;
auto seed = rd();
CAPTURE( seed );
Catch::SimplePcg32 pcg( seed );
constexpr auto lowest = std::numeric_limits<TestType>::min();
constexpr auto highest = std::numeric_limits<TestType>::max();
Catch::uniform_integer_distribution<TestType> dist( lowest, highest );
STATIC_REQUIRE( std::is_same<TestType, decltype( dist( pcg ) )>::value );
// We need to do bit operations on the results, so we will have to
// cast them to unsigned type.
using BitType = std::make_unsigned_t<TestType>;
BitType ORs = 0;
BitType ANDs = BitType(-1);
for (int i = 0; i < 100; ++i) {
auto bits = static_cast<BitType>( dist( pcg ) );
ORs |= bits;
ANDs &= bits;
}
// Assuming both our RNG and distribution are unbiased, asking for
// the full range should essentially give us random bit generator.
// Over long run, OR of all the generated values should have all
// bits set to 1, while AND should have all bits set to 0.
// The chance of this test failing for unbiased pipeline is
// 1 / 2**iters, which for 100 iterations is astronomical.
REQUIRE( ORs == BitType( -1 ) );
REQUIRE( ANDs == 0 );
}
namespace {
template <typename T>
struct uniform_integer_test_params;
template <>
struct uniform_integer_test_params<bool> {
static constexpr bool lowest = false;
static constexpr bool highest = true;
// This seems weird, but it is an artifact of the specific seed
static constexpr bool expected[] = { true,
true,
true,
true,
true,
true,
false,
true,
true,
true,
true,
true,
false,
true,
true };
};
template <>
struct uniform_integer_test_params<char> {
static constexpr char lowest = 32;
static constexpr char highest = 126;
static constexpr char expected[] = { 'k',
'\\',
'Z',
'X',
'`',
'Q',
';',
'o',
']',
'T',
'v',
'p',
':',
'S',
't' };
};
template <>
struct uniform_integer_test_params<uint8_t> {
static constexpr uint8_t lowest = 3;
static constexpr uint8_t highest = 123;
static constexpr uint8_t expected[] = { 'c',
'P',
'M',
'J',
'U',
'A',
'%',
'h',
'Q',
'F',
'q',
'i',
'$',
'E',
'o' };
};
template <>
struct uniform_integer_test_params<int8_t> {
static constexpr int8_t lowest = -27;
static constexpr int8_t highest = 73;
static constexpr int8_t expected[] = { '5',
'%',
'#',
' ',
'*',
25,
2,
'9',
'&',
29,
'A',
':',
1,
28,
'?' };
};
template <>
struct uniform_integer_test_params<uint16_t> {
static constexpr uint16_t lowest = 123;
static constexpr uint16_t highest = 33333;
static constexpr uint16_t expected[] = { 26684,
21417,
20658,
19791,
22896,
17433,
9806,
27948,
21767,
18588,
30556,
28244,
9439,
18293,
29949 };
};
template <>
struct uniform_integer_test_params<int16_t> {
static constexpr int16_t lowest = -17222;
static constexpr int16_t highest = 17222;
static constexpr int16_t expected[] = { 10326,
4863,
4076,
3177,
6397,
731,
-7179,
11637,
5226,
1929,
14342,
11944,
-7560,
1623,
13712 };
};
template <>
struct uniform_integer_test_params<uint32_t> {
static constexpr uint32_t lowest = 17222;
static constexpr uint32_t highest = 234234;
static constexpr uint32_t expected[] = { 190784,
156367,
151409,
145743,
166032,
130337,
80501,
199046,
158654,
137883,
216091,
200981,
78099,
135954,
212120 };
};
template <>
struct uniform_integer_test_params<int32_t> {
static constexpr int32_t lowest = -237272;
static constexpr int32_t highest = 234234;
static constexpr int32_t expected[] = { 139829,
65050,
54278,
41969,
86051,
8494,
-99785,
157781,
70021,
24890,
194815,
161985,
-105004,
20699,
186186 };
};
template <>
struct uniform_integer_test_params<uint64_t> {
static constexpr uint64_t lowest = 1234;
static constexpr uint64_t highest = 1234567890;
static constexpr uint64_t expected[] = { 987382749,
763380386,
846572137,
359990258,
804599765,
1131353566,
346324913,
1108760730,
1141693933,
856999148,
879390623,
1149485521,
900556586,
952385958,
807916408 };
};
template <>
struct uniform_integer_test_params<int64_t> {
static constexpr int64_t lowest = -1234567890;
static constexpr int64_t highest = 1234567890;
static constexpr int64_t expected[] = { 740197113,
292191940,
458575608,
-514589122,
374630781,
1028139036,
-541919840,
982953318,
1048819790,
479429651,
524212647,
1064402981,
566544615,
670203462,
381264073 };
};
// We need these definitions for C++14 and earlier, but
// GCC will complain about them in newer C++ standards
#if __cplusplus <= 201402L
constexpr bool uniform_integer_test_params<bool>::expected[];
constexpr char uniform_integer_test_params<char>::expected[];
constexpr uint8_t uniform_integer_test_params<uint8_t>::expected[];
constexpr int8_t uniform_integer_test_params<int8_t>::expected[];
constexpr uint16_t uniform_integer_test_params<uint16_t>::expected[];
constexpr int16_t uniform_integer_test_params<int16_t>::expected[];
constexpr uint32_t uniform_integer_test_params<uint32_t>::expected[];
constexpr int32_t uniform_integer_test_params<int32_t>::expected[];
constexpr uint64_t uniform_integer_test_params<uint64_t>::expected[];
constexpr int64_t uniform_integer_test_params<int64_t>::expected[];
#endif
}
TEMPLATE_TEST_CASE( "uniform_integer_distribution is reproducible",
"[rng][distribution][approvals]",
bool,
char,
uint8_t,
int8_t,
uint16_t,
int16_t,
uint32_t,
int32_t,
uint64_t,
int64_t) {
Catch::SimplePcg32 pcg( 0xaabb'ccdd );
constexpr auto lowest = uniform_integer_test_params<TestType>::lowest;
constexpr auto highest = uniform_integer_test_params<TestType>::highest;
Catch::uniform_integer_distribution<TestType> dist(lowest, highest);
constexpr auto iters = 15;
std::array<TestType, iters> generated;
for (int i = 0; i < iters; ++i) {
generated[i] = dist( pcg );
}
REQUIRE_THAT(generated, Catch::Matchers::RangeEquals(uniform_integer_test_params<TestType>::expected));
}
// The reproducibility tests assume that operations on `float`/`double`
// happen in the same precision as the operated-upon type. This is
// generally true, unless the code is compiled for 32 bit targets without
// SSE2 enabled, in which case the operations are done in the x87 FPU,
// which usually implies doing math in 80 bit floats, and then rounding
// into smaller type when the type is saved into memory. This obviously
// leads to a different answer, than doing the math in the correct precision.
#if ( defined( _MSC_VER ) && _M_IX86_FP < 2 ) || \
( defined( __GNUC__ ) && \
( ( defined( __i386__ ) || defined( __x86_64__ ) ) ) && \
!defined( __SSE2_MATH__ ) )
# define CATCH_TEST_CONFIG_DISABLE_FLOAT_REPRODUCIBILITY_TESTS
#endif
#if !defined( CATCH_TEST_CONFIG_DISABLE_FLOAT_REPRODUCIBILITY_TESTS )
namespace {
template <typename T>
struct uniform_fp_test_params;
template<>
struct uniform_fp_test_params<float> {
// These are exactly representable
static constexpr float lowest = -256.125f;
static constexpr float highest = 385.125f;
// These are just round-trip formatted
static constexpr float expected[] = { 92.56961f,
-23.170044f,
310.81833f,
-53.023132f,
105.03287f,
198.77591f,
-172.72931f,
51.805176f,
-241.10156f,
64.66101f,
212.12509f,
-49.24292f,
-177.1399f,
245.23679f,
173.22421f };
};
template <>
struct uniform_fp_test_params<double> {
// These are exactly representable
static constexpr double lowest = -234582.9921875;
static constexpr double highest = 261238.015625;
// These are just round-trip formatted
static constexpr double expected[] = { 35031.207052832615,
203783.3401838024,
44667.940405848756,
-170100.5877224467,
-222966.7418051684,
127472.72630072923,
-173510.88209096913,
97394.16172239158,
119123.6921592663,
22595.741022785165,
8988.68409120926,
136906.86520606978,
33369.19104222473,
60912.7615841752,
-149060.05936760217 };
};
// We need these definitions for C++14 and earlier, but
// GCC will complain about them in newer C++ standards
#if __cplusplus <= 201402L
constexpr float uniform_fp_test_params<float>::expected[];
constexpr double uniform_fp_test_params<double>::expected[];
#endif
} // namespace
TEMPLATE_TEST_CASE( "uniform_floating_point_distribution is reproducible",
"[rng][distribution][floating-point][approvals]",
float,
double ) {
Catch::SimplePcg32 pcg( 0xaabb'aabb );
const auto lowest = uniform_fp_test_params<TestType>::lowest;
const auto highest = uniform_fp_test_params<TestType>::highest;
Catch::uniform_floating_point_distribution<TestType> dist( lowest, highest );
constexpr auto iters = 15;
std::array<TestType, iters> generated;
for ( int i = 0; i < iters; ++i ) {
generated[i] = dist( pcg );
}
REQUIRE_THAT( generated, Catch::Matchers::RangeEquals( uniform_fp_test_params<TestType>::expected ) );
}
#endif // ^^ float reproducibility tests are enabled
TEMPLATE_TEST_CASE( "uniform_floating_point_distribution can handle unitary ranges",
"[rng][distribution][floating-point][approvals]",
float,
double ) {
std::random_device rd;
auto seed = rd();
CAPTURE( seed );
Catch::SimplePcg32 pcg( seed );
const auto highest = TestType(385.125);
Catch::uniform_floating_point_distribution<TestType> dist( highest,
highest );
constexpr auto iters = 20;
for (int i = 0; i < iters; ++i) {
REQUIRE( Catch::Detail::directCompare( dist( pcg ), highest ) );
}
}

View File

@ -0,0 +1,330 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_test_case_info.hpp>
#include <catch2/catch_config.hpp>
#include <catch2/interfaces/catch_interfaces_reporter.hpp>
#include <catch2/interfaces/catch_interfaces_reporter_factory.hpp>
#include <catch2/internal/catch_console_colour.hpp>
#include <catch2/internal/catch_enforce.hpp>
#include <catch2/internal/catch_list.hpp>
#include <catch2/internal/catch_reporter_registry.hpp>
#include <catch2/internal/catch_istream.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include <catch2/reporters/catch_reporter_helpers.hpp>
#include <catch2/reporters/catch_reporter_event_listener.hpp>
#include <catch2/reporters/catch_reporter_streaming_base.hpp>
#include <catch2/reporters/catch_reporter_multi.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
#include <sstream>
namespace {
class StringIStream : public Catch::IStream {
public:
std::ostream& stream() override { return sstr; }
std::string str() const { return sstr.str(); }
private:
std::stringstream sstr;
};
//! config must outlive the function
Catch::ReporterConfig makeDummyRepConfig( Catch::Config const& config ) {
return Catch::ReporterConfig{
&config,
Catch::Detail::make_unique<StringIStream>(),
Catch::ColourMode::None,
{} };
}
}
TEST_CASE( "The default listing implementation write to provided stream",
"[reporters][reporter-helpers]" ) {
using Catch::Matchers::ContainsSubstring;
using namespace std::string_literals;
StringIStream sstream;
SECTION( "Listing tags" ) {
std::vector<Catch::TagInfo> tags(1);
tags[0].add("fakeTag"_catch_sr);
Catch::defaultListTags(sstream.stream(), tags, false);
auto listingString = sstream.str();
REQUIRE_THAT(listingString, ContainsSubstring("[fakeTag]"s));
}
SECTION( "Listing reporters" ) {
std::vector<Catch::ReporterDescription> reporters(
{ { "fake reporter", "fake description" } } );
Catch::defaultListReporters(sstream.stream(), reporters, Catch::Verbosity::Normal);
auto listingString = sstream.str();
REQUIRE_THAT( listingString,
ContainsSubstring( "fake reporter"s ) &&
ContainsSubstring( "fake description"s ) );
}
SECTION( "Listing tests" ) {
Catch::TestCaseInfo fakeInfo{
""s,
{ "fake test name"_catch_sr, "[fakeTestTag]"_catch_sr },
{ "fake-file.cpp", 123456789 } };
std::vector<Catch::TestCaseHandle> tests({ {&fakeInfo, nullptr} });
auto colour = Catch::makeColourImpl( Catch::ColourMode::None, &sstream);
Catch::defaultListTests(sstream.stream(), colour.get(), tests, false, Catch::Verbosity::Normal);
auto listingString = sstream.str();
REQUIRE_THAT( listingString,
ContainsSubstring( "fake test name"s ) &&
ContainsSubstring( "fakeTestTag"s ) );
}
SECTION( "Listing listeners" ) {
std::vector<Catch::ListenerDescription> listeners(
{ { "fakeListener"_catch_sr, "fake description" } } );
Catch::defaultListListeners( sstream.stream(), listeners );
auto listingString = sstream.str();
REQUIRE_THAT( listingString,
ContainsSubstring( "fakeListener"s ) &&
ContainsSubstring( "fake description"s ) );
}
}
TEST_CASE( "Reporter's write listings to provided stream", "[reporters]" ) {
using Catch::Matchers::ContainsSubstring;
using namespace std::string_literals;
auto const& factories = Catch::getRegistryHub().getReporterRegistry().getFactories();
// If there are no reporters, the test would pass falsely
// while there is something obviously broken
REQUIRE_FALSE(factories.empty());
for (auto const& factory : factories) {
INFO("Tested reporter: " << factory.first);
auto sstream = Catch::Detail::make_unique<StringIStream>();
auto& sstreamRef = *sstream;
Catch::ConfigData cfg_data;
cfg_data.rngSeed = 1234;
Catch::Config config( cfg_data );
auto reporter = factory.second->create( Catch::ReporterConfig{
&config, CATCH_MOVE( sstream ), Catch::ColourMode::None, {} } );
DYNAMIC_SECTION( factory.first << " reporter lists tags" ) {
std::vector<Catch::TagInfo> tags(1);
tags[0].add("fakeTag"_catch_sr);
reporter->listTags(tags);
auto listingString = sstreamRef.str();
REQUIRE_THAT(listingString, ContainsSubstring("fakeTag"s));
}
DYNAMIC_SECTION( factory.first << " reporter lists reporters" ) {
std::vector<Catch::ReporterDescription> reporters(
{ { "fake reporter", "fake description" } } );
reporter->listReporters(reporters);
auto listingString = sstreamRef.str();
REQUIRE_THAT(listingString, ContainsSubstring("fake reporter"s));
}
DYNAMIC_SECTION( factory.first << " reporter lists tests" ) {
Catch::TestCaseInfo fakeInfo{
""s,
{ "fake test name"_catch_sr, "[fakeTestTag]"_catch_sr },
{ "fake-file.cpp", 123456789 } };
std::vector<Catch::TestCaseHandle> tests({ {&fakeInfo, nullptr} });
reporter->listTests(tests);
auto listingString = sstreamRef.str();
REQUIRE_THAT( listingString,
ContainsSubstring( "fake test name"s ) &&
ContainsSubstring( "fakeTestTag"s ) );
}
}
}
TEST_CASE("Reproducer for #2309 - a very long description past 80 chars (default console width) with a late colon : blablabla", "[console-reporter]") {
SUCCEED();
}
namespace {
// A listener that writes provided string into destination,
// to record order of testRunStarting invocation.
class MockListener : public Catch::EventListenerBase {
std::string m_witness;
std::vector<std::string>& m_recorder;
public:
MockListener( std::string witness,
std::vector<std::string>& recorder,
Catch::IConfig const* config ):
EventListenerBase( config ),
m_witness( CATCH_MOVE(witness) ),
m_recorder( recorder )
{}
void testRunStarting( Catch::TestRunInfo const& ) override {
m_recorder.push_back( m_witness );
}
};
// A reporter that writes provided string into destination,
// to record order of testRunStarting invocation.
class MockReporter : public Catch::StreamingReporterBase {
std::string m_witness;
std::vector<std::string>& m_recorder;
public:
MockReporter( std::string witness,
std::vector<std::string>& recorder,
Catch::ReporterConfig&& config ):
StreamingReporterBase( CATCH_MOVE(config) ),
m_witness( CATCH_MOVE(witness) ),
m_recorder( recorder )
{}
void testRunStarting( Catch::TestRunInfo const& ) override {
m_recorder.push_back( m_witness );
}
};
} // namespace
TEST_CASE("Multireporter calls reporters and listeners in correct order",
"[reporters][multi-reporter]") {
Catch::Config config( Catch::ConfigData{} );
// We add reporters before listeners, to check that internally they
// get sorted properly, and listeners are called first anyway.
Catch::MultiReporter multiReporter( &config );
std::vector<std::string> records;
multiReporter.addReporter( Catch::Detail::make_unique<MockReporter>(
"Goodbye", records, makeDummyRepConfig(config) ) );
multiReporter.addListener(
Catch::Detail::make_unique<MockListener>( "Hello", records, &config ) );
multiReporter.addListener(
Catch::Detail::make_unique<MockListener>( "world", records, &config ) );
multiReporter.addReporter( Catch::Detail::make_unique<MockReporter>(
"world", records, makeDummyRepConfig(config) ) );
multiReporter.testRunStarting( { "" } );
std::vector<std::string> expected( { "Hello", "world", "Goodbye", "world" } );
REQUIRE( records == expected );
}
namespace {
// A listener that sets it preferences to test that multireporter,
// properly sets up its own preferences
class PreferenceListener : public Catch::EventListenerBase {
public:
PreferenceListener( bool redirectStdout,
bool reportAllAssertions,
Catch::IConfig const* config ):
EventListenerBase( config ) {
m_preferences.shouldRedirectStdOut = redirectStdout;
m_preferences.shouldReportAllAssertions = reportAllAssertions;
}
};
// A reporter that sets it preferences to test that multireporter,
// properly sets up its own preferences
class PreferenceReporter : public Catch::StreamingReporterBase {
public:
PreferenceReporter( bool redirectStdout,
bool reportAllAssertions,
Catch::ReporterConfig&& config ):
StreamingReporterBase( CATCH_MOVE(config) ) {
m_preferences.shouldRedirectStdOut = redirectStdout;
m_preferences.shouldReportAllAssertions = reportAllAssertions;
}
};
} // namespace
TEST_CASE("Multireporter updates ReporterPreferences properly",
"[reporters][multi-reporter]") {
Catch::Config config( Catch::ConfigData{} );
Catch::MultiReporter multiReporter( &config );
// Post init defaults
REQUIRE( multiReporter.getPreferences().shouldRedirectStdOut == false );
REQUIRE( multiReporter.getPreferences().shouldReportAllAssertions == false );
SECTION( "Adding listeners" ) {
multiReporter.addListener(
Catch::Detail::make_unique<PreferenceListener>(
true, false, &config ) );
REQUIRE( multiReporter.getPreferences().shouldRedirectStdOut == true );
REQUIRE( multiReporter.getPreferences().shouldReportAllAssertions == false );
multiReporter.addListener(
Catch::Detail::make_unique<PreferenceListener>(
false, true, &config ) );
REQUIRE( multiReporter.getPreferences().shouldRedirectStdOut == true );
REQUIRE( multiReporter.getPreferences().shouldReportAllAssertions == true);
multiReporter.addListener(
Catch::Detail::make_unique<PreferenceListener>(
false, false, &config ) );
REQUIRE( multiReporter.getPreferences().shouldRedirectStdOut == true );
REQUIRE( multiReporter.getPreferences().shouldReportAllAssertions == true );
}
SECTION( "Adding reporters" ) {
multiReporter.addReporter(
Catch::Detail::make_unique<PreferenceReporter>(
true, false, makeDummyRepConfig(config) ) );
REQUIRE( multiReporter.getPreferences().shouldRedirectStdOut == true );
REQUIRE( multiReporter.getPreferences().shouldReportAllAssertions == false );
multiReporter.addReporter(
Catch::Detail::make_unique<PreferenceReporter>(
false, true, makeDummyRepConfig( config ) ) );
REQUIRE( multiReporter.getPreferences().shouldRedirectStdOut == true );
REQUIRE( multiReporter.getPreferences().shouldReportAllAssertions == true );
multiReporter.addReporter(
Catch::Detail::make_unique<PreferenceReporter>(
false, false, makeDummyRepConfig( config ) ) );
REQUIRE( multiReporter.getPreferences().shouldRedirectStdOut == true );
REQUIRE( multiReporter.getPreferences().shouldReportAllAssertions == true );
}
}
namespace {
class TestReporterFactory : public Catch::IReporterFactory {
Catch::IEventListenerPtr create( Catch::ReporterConfig&& ) const override {
CATCH_INTERNAL_ERROR(
"This factory should never create a reporter" );
}
std::string getDescription() const override {
return "Fake test factory";
}
};
}
TEST_CASE("Registering reporter with '::' in name fails",
"[reporters][registration]") {
Catch::ReporterRegistry registry;
REQUIRE_THROWS_WITH( registry.registerReporter(
"with::doublecolons",
Catch::Detail::make_unique<TestReporterFactory>() ),
"'::' is not allowed in reporter name: 'with::doublecolons'" );
}
TEST_CASE("Registering multiple reporters with the same name fails",
"[reporters][registration][approvals]") {
Catch::ReporterRegistry registry;
registry.registerReporter(
"some-reporter-name",
Catch::Detail::make_unique<TestReporterFactory>() );
REQUIRE_THROWS_WITH(
registry.registerReporter(
"some-reporter-name",
Catch::Detail::make_unique<TestReporterFactory>() ),
"reporter using 'some-reporter-name' as name was already registered" );
}

View File

@ -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]);
}
}

View File

@ -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" ) );
}

View File

@ -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);
}
}

View File

@ -0,0 +1,94 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_vector.hpp>
#include <catch2/internal/catch_string_manip.hpp>
static const char * const no_whitespace = "There is no extra whitespace here";
static const char * const leading_whitespace = " \r \t\n There is no extra whitespace here";
static const char * const trailing_whitespace = "There is no extra whitespace here \t \n \r ";
static const char * const whitespace_at_both_ends = " \r\n \t There is no extra whitespace here \t\t\t \n";
TEST_CASE("Trim strings", "[string-manip]") {
using Catch::trim; using Catch::StringRef;
static_assert(std::is_same<std::string, decltype(trim(std::string{}))>::value, "Trimming std::string should return std::string");
static_assert(std::is_same<StringRef, decltype(trim(StringRef{}))>::value, "Trimming StringRef should return StringRef");
REQUIRE(trim(std::string(no_whitespace)) == no_whitespace);
REQUIRE(trim(std::string(leading_whitespace)) == no_whitespace);
REQUIRE(trim(std::string(trailing_whitespace)) == no_whitespace);
REQUIRE(trim(std::string(whitespace_at_both_ends)) == no_whitespace);
REQUIRE(trim(StringRef(no_whitespace)) == StringRef(no_whitespace));
REQUIRE(trim(StringRef(leading_whitespace)) == StringRef(no_whitespace));
REQUIRE(trim(StringRef(trailing_whitespace)) == StringRef(no_whitespace));
REQUIRE(trim(StringRef(whitespace_at_both_ends)) == StringRef(no_whitespace));
}
TEST_CASE("replaceInPlace", "[string-manip]") {
std::string letters = "abcdefcg";
SECTION("replace single char") {
CHECK(Catch::replaceInPlace(letters, "b", "z"));
CHECK(letters == "azcdefcg");
}
SECTION("replace two chars") {
CHECK(Catch::replaceInPlace(letters, "c", "z"));
CHECK(letters == "abzdefzg");
}
SECTION("replace first char") {
CHECK(Catch::replaceInPlace(letters, "a", "z"));
CHECK(letters == "zbcdefcg");
}
SECTION("replace last char") {
CHECK(Catch::replaceInPlace(letters, "g", "z"));
CHECK(letters == "abcdefcz");
}
SECTION("replace all chars") {
CHECK(Catch::replaceInPlace(letters, letters, "replaced"));
CHECK(letters == "replaced");
}
SECTION("replace no chars") {
CHECK_FALSE(Catch::replaceInPlace(letters, "x", "z"));
CHECK(letters == letters);
}
SECTION("no replace in already-replaced string") {
SECTION("lengthening") {
CHECK(Catch::replaceInPlace(letters, "c", "cc"));
CHECK(letters == "abccdefccg");
}
SECTION("shortening") {
std::string s = "----";
CHECK(Catch::replaceInPlace(s, "--", "-"));
CHECK(s == "--");
}
}
SECTION("escape '") {
std::string s = "didn't";
CHECK(Catch::replaceInPlace(s, "'", "|'"));
CHECK(s == "didn|'t");
}
}
TEST_CASE("splitString", "[string-manip]") {
using namespace Catch::Matchers;
using Catch::splitStringRef;
using Catch::StringRef;
CHECK_THAT(splitStringRef("", ','), Equals(std::vector<StringRef>()));
CHECK_THAT(splitStringRef("abc", ','), Equals(std::vector<StringRef>{"abc"}));
CHECK_THAT(splitStringRef("abc,def", ','), Equals(std::vector<StringRef>{"abc", "def"}));
}
TEST_CASE("startsWith", "[string-manip]") {
using Catch::startsWith;
CHECK_FALSE(startsWith("", 'c'));
CHECK(startsWith(std::string("abc"), 'a'));
CHECK(startsWith("def"_catch_sr, 'd'));
}

View File

@ -0,0 +1,117 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/matchers/catch_matchers_string.hpp>
#include <catch2/matchers/catch_matchers_vector.hpp>
#include <catch2/internal/catch_tag_alias_registry.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_test_case_info.hpp>
TEST_CASE( "Tag alias can be registered against tag patterns" ) {
Catch::TagAliasRegistry registry;
registry.add( "[@zzz]", "[one][two]", Catch::SourceLineInfo( "file", 2 ) );
SECTION( "The same tag alias can only be registered once" ) {
try {
registry.add( "[@zzz]", "[one][two]", Catch::SourceLineInfo( "file", 10 ) );
FAIL( "expected exception" );
}
catch( std::exception& ex ) {
std::string what = ex.what();
using namespace Catch::Matchers;
CHECK_THAT( what, ContainsSubstring( "[@zzz]" ) );
CHECK_THAT( what, ContainsSubstring( "file" ) );
CHECK_THAT( what, ContainsSubstring( "2" ) );
CHECK_THAT( what, ContainsSubstring( "10" ) );
}
}
SECTION( "Tag aliases must be of the form [@name]" ) {
CHECK_THROWS( registry.add( "[no ampersat]", "", Catch::SourceLineInfo( "file", 3 ) ) );
CHECK_THROWS( registry.add( "[the @ is not at the start]", "", Catch::SourceLineInfo( "file", 3 ) ) );
CHECK_THROWS( registry.add( "@no square bracket at start]", "", Catch::SourceLineInfo( "file", 3 ) ) );
CHECK_THROWS( registry.add( "[@no square bracket at end", "", Catch::SourceLineInfo( "file", 3 ) ) );
}
}
// Dummy line info for creating dummy test cases below
static constexpr Catch::SourceLineInfo dummySourceLineInfo = CATCH_INTERNAL_LINEINFO;
TEST_CASE("shortened hide tags are split apart", "[tags]") {
using Catch::StringRef;
using Catch::Tag;
using Catch::Matchers::VectorContains;
Catch::TestCaseInfo testcase("", {"fake test name", "[.magic-tag]"}, dummySourceLineInfo);
REQUIRE_THAT( testcase.tags, VectorContains( Tag( "magic-tag" ) )
&& VectorContains( Tag( "."_catch_sr ) ) );
}
TEST_CASE("tags with dots in later positions are not parsed as hidden", "[tags]") {
using Catch::StringRef;
using Catch::Matchers::VectorContains;
Catch::TestCaseInfo testcase("", { "fake test name", "[magic.tag]" }, dummySourceLineInfo);
REQUIRE(testcase.tags.size() == 1);
REQUIRE(testcase.tags[0].original == "magic.tag"_catch_sr);
}
TEST_CASE( "empty tags are not allowed", "[tags]" ) {
REQUIRE_THROWS(
Catch::TestCaseInfo("", { "test with an empty tag", "[]" }, dummySourceLineInfo)
);
}
TEST_CASE( "Tags with spaces and non-alphanumerical characters are accepted",
"[tags]" ) {
using Catch::Tag;
using Catch::Matchers::VectorContains;
Catch::TestCaseInfo testCase(
"",
{ "fake test name", "[tag with spaces][I said \"good day\" sir!]" },
dummySourceLineInfo );
REQUIRE( testCase.tags.size() == 2 );
REQUIRE_THAT( testCase.tags,
VectorContains( Tag( "tag with spaces" ) ) &&
VectorContains( Tag( "I said \"good day\" sir!"_catch_sr ) ) );
}
TEST_CASE( "Test case with identical tags keeps just one", "[tags]" ) {
using Catch::Tag;
Catch::TestCaseInfo testCase(
"",
{ "fake test name", "[TaG1][tAg1][TAG1][tag1]" },
dummySourceLineInfo );
REQUIRE( testCase.tags.size() == 1 );
REQUIRE( testCase.tags[0] == Tag( "tag1" ) );
}
TEST_CASE("Mismatched square brackets in tags are caught and reported",
"[tags][approvals]") {
using Catch::TestCaseInfo;
using Catch::Matchers::ContainsSubstring;
REQUIRE_THROWS_WITH( TestCaseInfo( "",
{ "test with unclosed tag", "[abc" },
dummySourceLineInfo ),
ContainsSubstring("registering test case 'test with unclosed tag'") );
REQUIRE_THROWS_WITH( TestCaseInfo( "",
{ "test with nested tags", "[abc[def]]" },
dummySourceLineInfo ),
ContainsSubstring("registering test case 'test with nested tags'") );
REQUIRE_THROWS_WITH( TestCaseInfo( "",
{ "test with superfluous close tags", "[abc][def]]" },
dummySourceLineInfo ),
ContainsSubstring("registering test case 'test with superfluous close tags'") );
}

View File

@ -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 ) );
}
}

View File

@ -0,0 +1,365 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_config.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include <catch2/internal/catch_test_spec_parser.hpp>
#include <catch2/catch_user_config.hpp>
#include <catch2/catch_test_case_info.hpp>
#include <catch2/internal/catch_commandline.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/internal/catch_compiler_capabilities.hpp>
#include <helpers/parse_test_spec.hpp>
namespace {
auto fakeTestCase(const char* name, const char* desc = "") { return Catch::makeTestCaseInfo("", { name, desc }, CATCH_INTERNAL_LINEINFO); }
}
TEST_CASE( "Parse test names and tags", "[command-line][test-spec][approvals]" ) {
using Catch::parseTestSpec;
using Catch::TestSpec;
auto tcA = fakeTestCase( "a" );
auto tcB = fakeTestCase( "b", "[one][x]" );
auto tcC = fakeTestCase( "longer name with spaces", "[two][three][.][x]" );
auto tcD = fakeTestCase( "zlonger name with spacesz" );
SECTION( "Empty test spec should have no filters" ) {
TestSpec spec;
CHECK( spec.hasFilters() == false );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == false );
}
SECTION( "Test spec from empty string should have no filters" ) {
TestSpec spec = parseTestSpec( "" );
CHECK( spec.hasFilters() == false );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == false );
}
SECTION( "Test spec from just a comma should have no filters" ) {
TestSpec spec = parseTestSpec( "," );
CHECK( spec.hasFilters() == false );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == false );
}
SECTION( "Test spec from name should have one filter" ) {
TestSpec spec = parseTestSpec( "b" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == true );
}
SECTION( "Test spec from quoted name should have one filter" ) {
TestSpec spec = parseTestSpec( "\"b\"" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == true );
}
SECTION( "Test spec from name should have one filter" ) {
TestSpec spec = parseTestSpec( "b" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == true );
CHECK( spec.matches( *tcC ) == false );
}
SECTION( "Wildcard at the start" ) {
TestSpec spec = parseTestSpec( "*spaces" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == false );
CHECK( spec.matches( *tcC ) == true );
CHECK( spec.matches( *tcD ) == false );
CHECK( parseTestSpec( "*a" ).matches( *tcA ) == true );
}
SECTION( "Wildcard at the end" ) {
TestSpec spec = parseTestSpec( "long*" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == false );
CHECK( spec.matches( *tcC ) == true );
CHECK( spec.matches( *tcD ) == false );
CHECK( parseTestSpec( "a*" ).matches( *tcA ) == true );
}
SECTION( "Wildcard at both ends" ) {
TestSpec spec = parseTestSpec( "*name*" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == false );
CHECK( spec.matches( *tcC ) == true );
CHECK( spec.matches( *tcD ) == true );
CHECK( parseTestSpec( "*a*" ).matches( *tcA ) == true );
}
SECTION( "Redundant wildcard at the start" ) {
TestSpec spec = parseTestSpec( "*a" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == true );
CHECK( spec.matches( *tcB ) == false );
}
SECTION( "Redundant wildcard at the end" ) {
TestSpec spec = parseTestSpec( "a*" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == true );
CHECK( spec.matches( *tcB ) == false );
}
SECTION( "Redundant wildcard at both ends" ) {
TestSpec spec = parseTestSpec( "*a*" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == true );
CHECK( spec.matches( *tcB ) == false );
}
SECTION( "Wildcard at both ends, redundant at start" ) {
TestSpec spec = parseTestSpec( "*longer*" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == false );
CHECK( spec.matches( *tcC ) == true );
CHECK( spec.matches( *tcD ) == true );
}
SECTION( "Just wildcard" ) {
TestSpec spec = parseTestSpec( "*" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == true );
CHECK( spec.matches( *tcB ) == true );
CHECK( spec.matches( *tcC ) == true );
CHECK( spec.matches( *tcD ) == true );
}
SECTION( "Single tag" ) {
TestSpec spec = parseTestSpec( "[one]" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == true );
CHECK( spec.matches( *tcC ) == false );
}
SECTION( "Single tag, two matches" ) {
TestSpec spec = parseTestSpec( "[x]" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == true );
CHECK( spec.matches( *tcC ) == true );
}
SECTION( "Two tags" ) {
TestSpec spec = parseTestSpec( "[two][x]" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == false );
CHECK( spec.matches( *tcC ) == true );
}
SECTION( "Two tags, spare separated" ) {
TestSpec spec = parseTestSpec( "[two] [x]" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == false );
CHECK( spec.matches( *tcC ) == true );
}
SECTION( "Wildcarded name and tag" ) {
TestSpec spec = parseTestSpec( "*name*[x]" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == false );
CHECK( spec.matches( *tcC ) == true );
CHECK( spec.matches( *tcD ) == false );
}
SECTION( "Single tag exclusion" ) {
TestSpec spec = parseTestSpec( "~[one]" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == true );
CHECK( spec.matches( *tcB ) == false );
CHECK( spec.matches( *tcC ) == false );
}
SECTION( "One tag exclusion and one tag inclusion" ) {
TestSpec spec = parseTestSpec( "~[two][x]" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == true );
CHECK( spec.matches( *tcC ) == false );
}
SECTION( "One tag exclusion and one wldcarded name inclusion" ) {
TestSpec spec = parseTestSpec( "~[two]*name*" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == false );
CHECK( spec.matches( *tcC ) == false );
CHECK( spec.matches( *tcD ) == true );
}
SECTION( "One tag exclusion, using exclude:, and one wldcarded name inclusion" ) {
TestSpec spec = parseTestSpec( "exclude:[two]*name*" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == false );
CHECK( spec.matches( *tcC ) == false );
CHECK( spec.matches( *tcD ) == true );
}
SECTION( "name exclusion" ) {
TestSpec spec = parseTestSpec( "~b" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == true );
CHECK( spec.matches( *tcB ) == false );
CHECK( spec.matches( *tcC ) == false );
CHECK( spec.matches( *tcD ) == true );
}
SECTION( "wildcarded name exclusion" ) {
TestSpec spec = parseTestSpec( "~*name*" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == true );
CHECK( spec.matches( *tcB ) == true );
CHECK( spec.matches( *tcC ) == false );
CHECK( spec.matches( *tcD ) == false );
}
SECTION( "wildcarded name exclusion with tag inclusion" ) {
TestSpec spec = parseTestSpec( "~*name*,[three]" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == true );
CHECK( spec.matches( *tcB ) == true );
CHECK( spec.matches( *tcC ) == true );
CHECK( spec.matches( *tcD ) == false );
}
SECTION( "wildcarded name exclusion, using exclude:, with tag inclusion" ) {
TestSpec spec = parseTestSpec( "exclude:*name*,[three]" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == true );
CHECK( spec.matches( *tcB ) == true );
CHECK( spec.matches( *tcC ) == true );
CHECK( spec.matches( *tcD ) == false );
}
SECTION( "two wildcarded names" ) {
TestSpec spec = parseTestSpec( R"("longer*""*spaces")" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == false );
CHECK( spec.matches( *tcC ) == true );
CHECK( spec.matches( *tcD ) == false );
}
SECTION( "empty tag" ) {
TestSpec spec = parseTestSpec( "[]" );
CHECK( spec.hasFilters() == false );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == false );
CHECK( spec.matches( *tcC ) == false );
CHECK( spec.matches( *tcD ) == false );
}
SECTION( "empty quoted name" ) {
TestSpec spec = parseTestSpec( "\"\"" );
CHECK( spec.hasFilters() == false );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == false );
CHECK( spec.matches( *tcC ) == false );
CHECK( spec.matches( *tcD ) == false );
}
SECTION( "quoted string followed by tag exclusion" ) {
TestSpec spec = parseTestSpec( "\"*name*\"~[.]" );
CHECK( spec.hasFilters() == true );
CHECK( spec.matches( *tcA ) == false );
CHECK( spec.matches( *tcB ) == false );
CHECK( spec.matches( *tcC ) == false );
CHECK( spec.matches( *tcD ) == true );
}
SECTION( "Leading and trailing spaces in test spec" ) {
TestSpec spec = parseTestSpec( "\" aardvark \"" );
CHECK( spec.matches( *fakeTestCase( " aardvark " ) ) );
CHECK( spec.matches( *fakeTestCase( " aardvark" ) ) );
CHECK( spec.matches( *fakeTestCase( " aardvark " ) ) );
CHECK( spec.matches( *fakeTestCase( "aardvark " ) ) );
CHECK( spec.matches( *fakeTestCase( "aardvark" ) ) );
}
SECTION( "Leading and trailing spaces in test name" ) {
TestSpec spec = parseTestSpec( "aardvark" );
CHECK( spec.matches( *fakeTestCase( " aardvark " ) ) );
CHECK( spec.matches( *fakeTestCase( " aardvark" ) ) );
CHECK( spec.matches( *fakeTestCase( " aardvark " ) ) );
CHECK( spec.matches( *fakeTestCase( "aardvark " ) ) );
CHECK( spec.matches( *fakeTestCase( "aardvark" ) ) );
}
SECTION("Shortened hide tags are split apart when parsing") {
TestSpec spec = parseTestSpec("[.foo]");
CHECK(spec.matches(*fakeTestCase("hidden and foo", "[.][foo]")));
CHECK_FALSE(spec.matches(*fakeTestCase("only foo", "[foo]")));
}
SECTION("Shortened hide tags also properly handle exclusion") {
TestSpec spec = parseTestSpec("~[.foo]");
CHECK_FALSE(spec.matches(*fakeTestCase("hidden and foo", "[.][foo]")));
CHECK_FALSE(spec.matches(*fakeTestCase("only foo", "[foo]")));
CHECK_FALSE(spec.matches(*fakeTestCase("only hidden", "[.]")));
CHECK(spec.matches(*fakeTestCase("neither foo nor hidden", "[bar]")));
}
}
TEST_CASE("#1905 -- test spec parser properly clears internal state between compound tests", "[command-line][test-spec]") {
using Catch::parseTestSpec;
using Catch::TestSpec;
// We ask for one of 2 different tests and the latter one of them has a , in name that needs escaping
TestSpec spec = parseTestSpec(R"("spec . char","spec \, char")");
REQUIRE(spec.matches(*fakeTestCase("spec . char")));
REQUIRE(spec.matches(*fakeTestCase("spec , char")));
REQUIRE_FALSE(spec.matches(*fakeTestCase(R"(spec \, char)")));
}
TEST_CASE("#1912 -- test spec parser handles escaping", "[command-line][test-spec]") {
using Catch::parseTestSpec;
using Catch::TestSpec;
SECTION("Various parentheses") {
TestSpec spec = parseTestSpec(R"(spec {a} char,spec \[a] char)");
REQUIRE(spec.matches(*fakeTestCase(R"(spec {a} char)")));
REQUIRE(spec.matches(*fakeTestCase(R"(spec [a] char)")));
REQUIRE_FALSE(spec.matches(*fakeTestCase("differs but has similar tag", "[a]")));
}
SECTION("backslash in test name") {
TestSpec spec = parseTestSpec(R"(spec \\ char)");
REQUIRE(spec.matches(*fakeTestCase(R"(spec \ char)")));
}
}
TEST_CASE("Test spec serialization is round-trippable", "[test-spec][serialization][approvals]") {
using Catch::parseTestSpec;
using Catch::TestSpec;
auto serializedTestSpec = []( std::string const& spec ) {
Catch::ReusableStringStream sstr;
sstr << parseTestSpec( spec );
return sstr.str();
};
SECTION("Spaces are normalized") {
CHECK( serializedTestSpec( "[abc][def]" ) == "[abc] [def]" );
CHECK( serializedTestSpec( "[def] [abc]" ) == "[def] [abc]" );
CHECK( serializedTestSpec( "[def] [abc]" ) == "[def] [abc]" );
}
SECTION("Output is order dependent") {
CHECK( serializedTestSpec( "[abc][def]" ) == "[abc] [def]" );
CHECK( serializedTestSpec( "[def][abc]" ) == "[def] [abc]" );
}
SECTION("Multiple disjunct filters") {
CHECK( serializedTestSpec( "[abc],[def]" ) == "[abc],[def]" );
CHECK( serializedTestSpec( "[def],[abc],[idkfa]" ) == "[def],[abc],[idkfa]" );
}
SECTION("Test names are enclosed in string") {
CHECK( serializedTestSpec( "Some test" ) == "\"Some test\"" );
CHECK( serializedTestSpec( "*Some test" ) == "\"*Some test\"" );
CHECK( serializedTestSpec( "* Some test" ) == "\"* Some test\"" );
CHECK( serializedTestSpec( "* Some test *" ) == "\"* Some test *\"" );
}
SECTION( "Mixing test names and tags" ) {
CHECK( serializedTestSpec( "some test[abcd]" ) ==
"\"some test\" [abcd]" );
CHECK( serializedTestSpec( "[ab]some test[cd]" ) ==
"[ab] \"some test\" [cd]" );
}
}

View File

@ -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 ) );
}

View File

@ -0,0 +1,400 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_macros.hpp>
#include <catch2/internal/catch_textflow.hpp>
#include <sstream>
using Catch::TextFlow::Column;
using Catch::TextFlow::AnsiSkippingString;
namespace {
static std::string as_written(Column const& c) {
std::stringstream sstr;
sstr << c;
return sstr.str();
}
}
TEST_CASE( "TextFlow::Column one simple line",
"[TextFlow][column][approvals]" ) {
Column col( "simple short line" );
REQUIRE(as_written(col) == "simple short line");
}
TEST_CASE( "TextFlow::Column respects already present newlines",
"[TextFlow][column][approvals]" ) {
Column col( "abc\ndef" );
REQUIRE( as_written( col ) == "abc\ndef" );
}
TEST_CASE( "TextFlow::Column respects width setting",
"[TextFlow][column][approvals]" ) {
Column col( "The quick brown fox jumped over the lazy dog" );
SECTION( "width=20" ) {
col.width( 20 );
REQUIRE( as_written( col ) == "The quick brown fox\n"
"jumped over the lazy\n"
"dog" );
}
SECTION("width=10") {
col.width( 10 );
REQUIRE( as_written( col ) == "The quick\n"
"brown fox\n"
"jumped\n"
"over the\n"
"lazy dog" );
}
SECTION("width=5") {
// This is so small some words will have to be split with hyphen
col.width(5);
REQUIRE( as_written( col ) == "The\n"
"quick\n"
"brown\n"
"fox\n"
"jump-\n"
"ed\n"
"over\n"
"the\n"
"lazy\n"
"dog" );
}
}
TEST_CASE( "TextFlow::Column respects indentation setting",
"[TextFlow][column][approvals]" ) {
Column col( "First line\nSecond line\nThird line" );
SECTION("Default: no indentation at all") {
REQUIRE(as_written(col) == "First line\nSecond line\nThird line");
}
SECTION("Indentation on first line only") {
col.initialIndent(3);
REQUIRE(as_written(col) == " First line\nSecond line\nThird line");
}
SECTION("Indentation on all lines") {
col.indent(3);
REQUIRE(as_written(col) == " First line\n Second line\n Third line");
}
SECTION("Indentation on later lines only") {
col.indent(5).initialIndent(0);
REQUIRE(as_written(col) == "First line\n Second line\n Third line");
}
SECTION("Different indentation on first and later lines") {
col.initialIndent(1).indent(2);
REQUIRE(as_written(col) == " First line\n Second line\n Third line");
}
}
TEST_CASE("TextFlow::Column indentation respects whitespace", "[TextFlow][column][approvals]") {
Column col(" text with whitespace\n after newlines");
SECTION("No extra indentation") {
col.initialIndent(0).indent(0);
REQUIRE(as_written(col) == " text with whitespace\n after newlines");
}
SECTION("Different indentation on first and later lines") {
col.initialIndent(1).indent(2);
REQUIRE(as_written(col) == " text with whitespace\n after newlines");
}
}
TEST_CASE( "TextFlow::Column linebreaking prefers boundary characters",
"[TextFlow][column][approvals]" ) {
SECTION("parentheses") {
Column col("(Hello)aaa(World)");
SECTION("width=20") {
col.width(20);
REQUIRE(as_written(col) == "(Hello)aaa(World)");
}
SECTION("width=15") {
col.width(15);
REQUIRE(as_written(col) == "(Hello)aaa\n(World)");
}
SECTION("width=8") {
col.width(8);
REQUIRE(as_written(col) == "(Hello)\naaa\n(World)");
}
}
SECTION("commas") {
Column col("Hello, world");
col.width(8);
REQUIRE(as_written(col) == "Hello,\nworld");
}
}
TEST_CASE( "TextFlow::Column respects indentation for empty lines",
"[TextFlow][column][approvals][!shouldfail]" ) {
// This is currently bugged and does not do what it should
Column col("\n\nthird line");
col.indent(2);
//auto b = col.begin();
//auto e = col.end();
//auto b1 = *b;
//++b;
//auto b2 = *b;
//++b;
//auto b3 = *b;
//++b;
//REQUIRE(b == e);
std::string written = as_written(col);
REQUIRE(written == " \n \n third line");
}
TEST_CASE( "TextFlow::Column leading/trailing whitespace",
"[TextFlow][column][approvals]" ) {
SECTION("Trailing whitespace") {
Column col("some trailing whitespace: \t");
REQUIRE(as_written(col) == "some trailing whitespace: \t");
}
SECTION("Some leading whitespace") {
Column col("\t \t whitespace wooo");
REQUIRE(as_written(col) == "\t \t whitespace wooo");
}
SECTION("both") {
Column col(" abc ");
REQUIRE(as_written(col) == " abc ");
}
SECTION("whitespace only") {
Column col("\t \t");
REQUIRE(as_written(col) == "\t \t");
}
}
TEST_CASE( "TextFlow::Column can handle empty string",
"[TextFlow][column][approvals]" ) {
Column col("");
REQUIRE(as_written(col) == "");
}
TEST_CASE( "#1400 - TextFlow::Column wrapping would sometimes duplicate words",
"[TextFlow][column][regression][approvals]" ) {
const auto long_string = std::string(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque nisl \n"
"massa, luctus ut ligula vitae, suscipit tempus velit. Vivamus sodales, quam in \n"
"convallis posuere, libero nisi ultricies orci, nec lobortis.\n");
auto col = Column(long_string)
.width(79)
.indent(2);
REQUIRE(as_written(col) ==
" Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque nisl \n"
" massa, luctus ut ligula vitae, suscipit tempus velit. Vivamus sodales, quam\n"
" in \n"
" convallis posuere, libero nisi ultricies orci, nec lobortis.");
}
TEST_CASE( "TextFlow::AnsiSkippingString skips ansi sequences",
"[TextFlow][ansiskippingstring][approvals]" ) {
SECTION("basic string") {
std::string text = "a\033[38;2;98;174;239mb\033[38mc\033[0md\033[me";
AnsiSkippingString str(text);
SECTION( "iterates forward" ) {
auto it = str.begin();
CHECK(*it == 'a');
++it;
CHECK(*it == 'b');
++it;
CHECK(*it == 'c');
++it;
CHECK(*it == 'd');
++it;
CHECK(*it == 'e');
++it;
CHECK(it == str.end());
}
SECTION( "iterates backwards" ) {
auto it = str.end();
--it;
CHECK(*it == 'e');
--it;
CHECK(*it == 'd');
--it;
CHECK(*it == 'c');
--it;
CHECK(*it == 'b');
--it;
CHECK(*it == 'a');
CHECK(it == str.begin());
}
}
SECTION( "ansi escape sequences at the start" ) {
std::string text = "\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38mc\033[0md\033[me";
AnsiSkippingString str(text);
auto it = str.begin();
CHECK(*it == 'a');
++it;
CHECK(*it == 'b');
++it;
CHECK(*it == 'c');
++it;
CHECK(*it == 'd');
++it;
CHECK(*it == 'e');
++it;
CHECK(it == str.end());
--it;
CHECK(*it == 'e');
--it;
CHECK(*it == 'd');
--it;
CHECK(*it == 'c');
--it;
CHECK(*it == 'b');
--it;
CHECK(*it == 'a');
CHECK(it == str.begin());
}
SECTION( "ansi escape sequences at the end" ) {
std::string text = "a\033[38;2;98;174;239mb\033[38mc\033[0md\033[me\033[38;2;98;174;239m";
AnsiSkippingString str(text);
auto it = str.begin();
CHECK(*it == 'a');
++it;
CHECK(*it == 'b');
++it;
CHECK(*it == 'c');
++it;
CHECK(*it == 'd');
++it;
CHECK(*it == 'e');
++it;
CHECK(it == str.end());
--it;
CHECK(*it == 'e');
--it;
CHECK(*it == 'd');
--it;
CHECK(*it == 'c');
--it;
CHECK(*it == 'b');
--it;
CHECK(*it == 'a');
CHECK(it == str.begin());
}
SECTION( "skips consecutive escapes" ) {
std::string text = "\033[38;2;98;174;239m\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38m\033[38m\033[38mc\033[0md\033[me";
AnsiSkippingString str(text);
auto it = str.begin();
CHECK(*it == 'a');
++it;
CHECK(*it == 'b');
++it;
CHECK(*it == 'c');
++it;
CHECK(*it == 'd');
++it;
CHECK(*it == 'e');
++it;
CHECK(it == str.end());
--it;
CHECK(*it == 'e');
--it;
CHECK(*it == 'd');
--it;
CHECK(*it == 'c');
--it;
CHECK(*it == 'b');
--it;
CHECK(*it == 'a');
CHECK(it == str.begin());
}
SECTION( "handles incomplete ansi sequences" ) {
std::string text = "a\033[b\033[30c\033[30;d\033[30;2e";
AnsiSkippingString str(text);
CHECK(std::string(str.begin(), str.end()) == text);
}
}
TEST_CASE( "TextFlow::AnsiSkippingString computes the size properly",
"[TextFlow][ansiskippingstring][approvals]" ) {
std::string text = "\033[38;2;98;174;239m\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38m\033[38m\033[38mc\033[0md\033[me";
AnsiSkippingString str(text);
CHECK(str.size() == 5);
}
TEST_CASE( "TextFlow::AnsiSkippingString substrings properly",
"[TextFlow][ansiskippingstring][approvals]" ) {
SECTION("basic test") {
std::string text = "a\033[38;2;98;174;239mb\033[38mc\033[0md\033[me";
AnsiSkippingString str(text);
auto a = str.begin();
auto b = str.begin();
++b;
++b;
CHECK(str.substring(a, b) == "a\033[38;2;98;174;239mb\033[38m");
++a;
++b;
CHECK(str.substring(a, b) == "b\033[38mc\033[0m");
CHECK(str.substring(a, str.end()) == "b\033[38mc\033[0md\033[me");
CHECK(str.substring(str.begin(), str.end()) == text);
}
SECTION("escapes at the start") {
std::string text = "\033[38;2;98;174;239m\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38m\033[38m\033[38mc\033[0md\033[me";
AnsiSkippingString str(text);
auto a = str.begin();
auto b = str.begin();
++b;
++b;
CHECK(str.substring(a, b) == "\033[38;2;98;174;239m\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38m\033[38m\033[38m");
++a;
++b;
CHECK(str.substring(a, b) == "b\033[38m\033[38m\033[38mc\033[0m");
CHECK(str.substring(a, str.end()) == "b\033[38m\033[38m\033[38mc\033[0md\033[me");
CHECK(str.substring(str.begin(), str.end()) == text);
}
SECTION("escapes at the end") {
std::string text = "a\033[38;2;98;174;239mb\033[38mc\033[0md\033[me\033[38m";
AnsiSkippingString str(text);
auto a = str.begin();
auto b = str.begin();
++b;
++b;
CHECK(str.substring(a, b) == "a\033[38;2;98;174;239mb\033[38m");
++a;
++b;
CHECK(str.substring(a, b) == "b\033[38mc\033[0m");
CHECK(str.substring(a, str.end()) == "b\033[38mc\033[0md\033[me\033[38m");
CHECK(str.substring(str.begin(), str.end()) == text);
}
}
TEST_CASE( "TextFlow::Column skips ansi escape sequences",
"[TextFlow][column][approvals]" ) {
std::string text = "\033[38;2;98;174;239m\033[38;2;198;120;221mThe quick brown \033[38;2;198;120;221mfox jumped over the lazy dog\033[0m";
Column col(text);
SECTION( "width=20" ) {
col.width( 20 );
REQUIRE( as_written( col ) == "\033[38;2;98;174;239m\033[38;2;198;120;221mThe quick brown \033[38;2;198;120;221mfox\n"
"jumped over the lazy\n"
"dog\033[0m" );
}
SECTION( "width=80" ) {
col.width( 80 );
REQUIRE( as_written( col ) == text );
}
}

View File

@ -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 );
}

View File

@ -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

View File

@ -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});
}
}

View File

@ -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;")"));
}