diff --git a/src/catch2/catch_config.cpp b/src/catch2/catch_config.cpp index b4584f3cc5..26aa6575b3 100644 --- a/src/catch2/catch_config.cpp +++ b/src/catch2/catch_config.cpp @@ -207,6 +207,7 @@ namespace Catch { double Config::minDuration() const { return m_data.minDuration; } TestRunOrder Config::runOrder() const { return m_data.runOrder; } uint32_t Config::rngSeed() const { return m_data.rngSeed; } + bool Config::rngSeedWasFixed() const { return m_data.rngSeedWasFixed; } unsigned int Config::shardCount() const { return m_data.shardCount; } unsigned int Config::shardIndex() const { return m_data.shardIndex; } ColourMode Config::defaultColourMode() const { return m_data.defaultColourMode; } @@ -271,6 +272,7 @@ namespace Catch { << bazelRandomSeed << "') as proper seed.\n"; } else { m_data.rngSeed = *parsedSeed; + m_data.rngSeedWasFixed = true; } } } diff --git a/src/catch2/catch_config.hpp b/src/catch2/catch_config.hpp index c3fea5616c..45788190b5 100644 --- a/src/catch2/catch_config.hpp +++ b/src/catch2/catch_config.hpp @@ -62,6 +62,7 @@ namespace Catch { int abortAfter = -1; uint32_t rngSeed = generateRandomSeed(GenerateFrom::Default); + bool rngSeedWasFixed = false; unsigned int shardCount = 1; unsigned int shardIndex = 0; @@ -133,6 +134,7 @@ namespace Catch { double minDuration() const override; TestRunOrder runOrder() const override; uint32_t rngSeed() const override; + bool rngSeedWasFixed() const; unsigned int shardCount() const override; unsigned int shardIndex() const override; ColourMode defaultColourMode() const override; diff --git a/src/catch2/catch_session.cpp b/src/catch2/catch_session.cpp index 0487fe7522..6b0677ae95 100644 --- a/src/catch2/catch_session.cpp +++ b/src/catch2/catch_session.cpp @@ -346,6 +346,19 @@ namespace Catch { CATCH_TRY { config(); // Force config to be constructed + if ( m_config->shardCount() > 1 && + m_config->runOrder() == TestRunOrder::Randomized && + !m_config->rngSeedWasFixed() ) { + Catch::cerr() + << "Warning: using sharding (--shard-count) with random " + "order (--order rand, the default) and without a fixed " + "numeric --rng-seed does not guarantee disjoint coverage " + "between shard invocations. Pass the same numeric " + "--rng-seed to every shard, or use --order decl or " + "--order lex instead.\n" + << std::flush; + } + // We need to retrieve potential Bazel config with the full Config // constructor, so we have to create the guard file after it is created. setUpGuardFile( m_config->getExitGuardFilePath() ); diff --git a/src/catch2/internal/catch_commandline.cpp b/src/catch2/internal/catch_commandline.cpp index 4ce4239724..22948c279f 100644 --- a/src/catch2/internal/catch_commandline.cpp +++ b/src/catch2/internal/catch_commandline.cpp @@ -75,9 +75,11 @@ namespace Catch { auto const setRngSeed = [&]( std::string const& seed ) { if( seed == "time" ) { config.rngSeed = generateRandomSeed(GenerateFrom::Time); + config.rngSeedWasFixed = false; return ParserResult::ok(ParseResultType::Matched); } else if (seed == "random-device") { config.rngSeed = generateRandomSeed(GenerateFrom::RandomDevice); + config.rngSeedWasFixed = false; return ParserResult::ok(ParseResultType::Matched); } @@ -88,6 +90,7 @@ namespace Catch { return ParserResult::runtimeError( "Could not parse '" + seed + "' as seed" ); } config.rngSeed = *parsedSeed; + config.rngSeedWasFixed = true; return ParserResult::ok( ParseResultType::Matched ); }; auto const setDefaultColourMode = [&]( std::string const& colourMode ) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6a2028b655..620cf7ef8d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -712,5 +712,37 @@ set_tests_properties("Bazel::RngSeedEnvVar::MalformedValueIsIgnored" PASS_REGULAR_EXPRESSION "Randomness seeded to: 17171717" ) +set(CATCH_SHARDING_WARNING_REGEX "Warning: using sharding .* with random order") + +add_test(NAME "Sharding::WarnsWithoutFixedSeed" + COMMAND + $ --shard-index 0 --shard-count 2 --list-tests +) +set_tests_properties("Sharding::WarnsWithoutFixedSeed" + PROPERTIES + PASS_REGULAR_EXPRESSION "${CATCH_SHARDING_WARNING_REGEX}" +) + +add_test(NAME "Sharding::NoWarningOnRandomOrderWithFixedSeed" + COMMAND + $ --shard-index 0 --shard-count 2 --rng-seed 12345 --list-tests +) +set_tests_properties("Sharding::NoWarningOnRandomOrderWithFixedSeed" + PROPERTIES + FAIL_REGULAR_EXPRESSION "${CATCH_SHARDING_WARNING_REGEX}" +) + +foreach(shardOrder decl lex) + string(CAPITALIZE ${shardOrder} shardOrderName) + add_test(NAME "Sharding::NoWarningOn${shardOrderName}OrderWithoutFixedSeed" + COMMAND + $ --shard-index 0 --shard-count 2 --order ${shardOrder} --list-tests + ) + set_tests_properties("Sharding::NoWarningOn${shardOrderName}OrderWithoutFixedSeed" + PROPERTIES + FAIL_REGULAR_EXPRESSION "${CATCH_SHARDING_WARNING_REGEX}" + ) +endforeach() + list(APPEND CATCH_TEST_TARGETS SelfTest) set(CATCH_TEST_TARGETS ${CATCH_TEST_TARGETS} PARENT_SCOPE) diff --git a/tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp b/tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp index 154d8ed9f9..4b02ce857b 100644 --- a/tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp @@ -475,12 +475,24 @@ TEST_CASE( "Parse rng seed in different formats", "[approvals][cli][rng-seed]" ) REQUIRE( result ); REQUIRE( config.rngSeed == seed_value ); + REQUIRE( config.rngSeedWasFixed ); + } + SECTION( "time seed is not considered fixed" ) { + auto result = cli.parse( { "tests", "--rng-seed", "time" } ); + REQUIRE( result ); + REQUIRE_FALSE( config.rngSeedWasFixed ); + } + SECTION( "random-device seed is not considered fixed" ) { + auto result = cli.parse( { "tests", "--rng-seed", "random-device" } ); + REQUIRE( result ); + REQUIRE_FALSE( config.rngSeedWasFixed ); } 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 } ) ); + REQUIRE_FALSE( config.rngSeedWasFixed ); } }