ctest: Add option to specify the --schedule-random seed
When `--schedule-random` is used in automated CI jobs, failures may occur due to test order. We now log the seed. Provide a way for developers to re-run the same order by specifying the seed. Fixes: #26760 Co-authored-by: Brad King <brad.king@kitware.com>
This commit is contained in:
parent
3dc8e59bdc
commit
d3455f38de
@ -445,6 +445,15 @@ Run Tests
|
|||||||
This option will run the tests in a random order. It is commonly
|
This option will run the tests in a random order. It is commonly
|
||||||
used to detect implicit dependencies in a test suite.
|
used to detect implicit dependencies in a test suite.
|
||||||
|
|
||||||
|
.. option:: --schedule-random-seed
|
||||||
|
|
||||||
|
.. versionadded:: 4.1
|
||||||
|
|
||||||
|
Override the random order seed
|
||||||
|
|
||||||
|
This option is used to allow recreating failures owing to
|
||||||
|
random order of execution by ``--schedule-random``.
|
||||||
|
|
||||||
.. option:: --submit-index
|
.. option:: --submit-index
|
||||||
|
|
||||||
Legacy option for old Dart2 dashboard server feature.
|
Legacy option for old Dart2 dashboard server feature.
|
||||||
|
7
Help/release/dev/ctest-schedule-random-seed.rst
Normal file
7
Help/release/dev/ctest-schedule-random-seed.rst
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
ctest-schedule-random-seed
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
* :manual:`ctest(1)` gained a
|
||||||
|
:option:`--schedule-random-seed <ctest --schedule-random-seed>`
|
||||||
|
option to specify a numeric random seed to make
|
||||||
|
:option:`ctest --schedule-random` deterministic for reproduction.
|
@ -43,6 +43,7 @@ protected:
|
|||||||
cm::optional<ArgumentParser::Maybe<std::string>> ParallelLevel;
|
cm::optional<ArgumentParser::Maybe<std::string>> ParallelLevel;
|
||||||
std::string Repeat;
|
std::string Repeat;
|
||||||
std::string ScheduleRandom;
|
std::string ScheduleRandom;
|
||||||
|
std::string ScheduleRandomSeed;
|
||||||
std::string StopTime;
|
std::string StopTime;
|
||||||
std::string TestLoad;
|
std::string TestLoad;
|
||||||
std::string ResourceSpecFile;
|
std::string ResourceSpecFile;
|
||||||
@ -70,6 +71,7 @@ protected:
|
|||||||
.Bind("PARALLEL_LEVEL"_s, &TestArguments::ParallelLevel)
|
.Bind("PARALLEL_LEVEL"_s, &TestArguments::ParallelLevel)
|
||||||
.Bind("REPEAT"_s, &TestArguments::Repeat)
|
.Bind("REPEAT"_s, &TestArguments::Repeat)
|
||||||
.Bind("SCHEDULE_RANDOM"_s, &TestArguments::ScheduleRandom)
|
.Bind("SCHEDULE_RANDOM"_s, &TestArguments::ScheduleRandom)
|
||||||
|
.Bind("SCHEDULE_RANDOM_SEED"_s, &TestArguments::ScheduleRandomSeed)
|
||||||
.Bind("STOP_TIME"_s, &TestArguments::StopTime)
|
.Bind("STOP_TIME"_s, &TestArguments::StopTime)
|
||||||
.Bind("TEST_LOAD"_s, &TestArguments::TestLoad)
|
.Bind("TEST_LOAD"_s, &TestArguments::TestLoad)
|
||||||
.Bind("RESOURCE_SPEC_FILE"_s, &TestArguments::ResourceSpecFile)
|
.Bind("RESOURCE_SPEC_FILE"_s, &TestArguments::ResourceSpecFile)
|
||||||
|
@ -1324,12 +1324,14 @@ bool cmCTestTestHandler::ProcessDirectory(std::vector<std::string>& passed,
|
|||||||
|
|
||||||
bool randomSchedule = this->CTest->GetScheduleType() == "Random";
|
bool randomSchedule = this->CTest->GetScheduleType() == "Random";
|
||||||
if (randomSchedule) {
|
if (randomSchedule) {
|
||||||
unsigned int seed = static_cast<unsigned>(time(nullptr));
|
cm::optional<unsigned int> scheduleRandomSeed =
|
||||||
srand(seed);
|
this->CTest->GetRandomSeed();
|
||||||
*this->LogFile
|
if (!scheduleRandomSeed.has_value()) {
|
||||||
<< "Test order random seed: " << seed << std::endl
|
scheduleRandomSeed = static_cast<unsigned int>(time(nullptr));
|
||||||
<< "----------------------------------------------------------"
|
}
|
||||||
<< std::endl;
|
srand(*scheduleRandomSeed);
|
||||||
|
*this->LogFile << "Test order random seed: " << *scheduleRandomSeed
|
||||||
|
<< std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (cmCTestTestProperties& p : this->TestList) {
|
for (cmCTestTestProperties& p : this->TestList) {
|
||||||
|
@ -34,6 +34,7 @@ struct cmCTestTestOptions
|
|||||||
bool ScheduleRandom = false;
|
bool ScheduleRandom = false;
|
||||||
bool StopOnFailure = false;
|
bool StopOnFailure = false;
|
||||||
bool UseUnion = false;
|
bool UseUnion = false;
|
||||||
|
cm::optional<unsigned int> ScheduleRandomSeed;
|
||||||
|
|
||||||
int OutputSizePassed = 1 * 1024;
|
int OutputSizePassed = 1 * 1024;
|
||||||
int OutputSizeFailed = 300 * 1024;
|
int OutputSizeFailed = 300 * 1024;
|
||||||
|
@ -2527,6 +2527,20 @@ int cmCTest::Run(std::vector<std::string> const& args)
|
|||||||
this->Impl->TestOptions.ScheduleRandom = true;
|
this->Impl->TestOptions.ScheduleRandom = true;
|
||||||
return true;
|
return true;
|
||||||
} },
|
} },
|
||||||
|
CommandArgument{
|
||||||
|
"--schedule-random-seed", CommandArgument::Values::One,
|
||||||
|
[this](std::string const& sz) -> bool {
|
||||||
|
unsigned long seed_value;
|
||||||
|
if (cmStrToULong(sz, &seed_value)) {
|
||||||
|
this->Impl->TestOptions.ScheduleRandomSeed =
|
||||||
|
static_cast<unsigned int>(seed_value);
|
||||||
|
} else {
|
||||||
|
cmCTestLog(this, WARNING,
|
||||||
|
"Invalid value for '--schedule-random-seed': " << sz
|
||||||
|
<< "\n");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} },
|
||||||
CommandArgument{ "--rerun-failed", CommandArgument::Values::Zero,
|
CommandArgument{ "--rerun-failed", CommandArgument::Values::Zero,
|
||||||
[this](std::string const&) -> bool {
|
[this](std::string const&) -> bool {
|
||||||
this->Impl->TestOptions.RerunFailed = true;
|
this->Impl->TestOptions.RerunFailed = true;
|
||||||
@ -2795,6 +2809,11 @@ void cmCTest::SetStopTime(std::string const& time_str)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cm::optional<unsigned int> cmCTest::GetRandomSeed() const
|
||||||
|
{
|
||||||
|
return this->Impl->TestOptions.ScheduleRandomSeed;
|
||||||
|
}
|
||||||
|
|
||||||
std::string cmCTest::GetScheduleType() const
|
std::string cmCTest::GetScheduleType() const
|
||||||
{
|
{
|
||||||
return this->Impl->ScheduleType;
|
return this->Impl->ScheduleType;
|
||||||
|
@ -198,6 +198,8 @@ public:
|
|||||||
std::string GetScheduleType() const;
|
std::string GetScheduleType() const;
|
||||||
void SetScheduleType(std::string const& type);
|
void SetScheduleType(std::string const& type);
|
||||||
|
|
||||||
|
cm::optional<unsigned int> GetRandomSeed() const;
|
||||||
|
|
||||||
/** The max output width */
|
/** The max output width */
|
||||||
int GetMaxTestNameWidth() const;
|
int GetMaxTestNameWidth() const;
|
||||||
void SetMaxTestNameWidth(int w);
|
void SetMaxTestNameWidth(int w);
|
||||||
|
@ -149,6 +149,7 @@ cmDocumentationEntry const cmDocumentationOptions[] = {
|
|||||||
{ "--extra-submit <file>[;<file>]", "Submit extra files to the dashboard." },
|
{ "--extra-submit <file>[;<file>]", "Submit extra files to the dashboard." },
|
||||||
{ "--http-header <header>", "Append HTTP header when submitting" },
|
{ "--http-header <header>", "Append HTTP header when submitting" },
|
||||||
{ "--schedule-random", "Use a random order for scheduling tests" },
|
{ "--schedule-random", "Use a random order for scheduling tests" },
|
||||||
|
{ "--schedule-random-seed", "Override seed for random order of tests" },
|
||||||
{ "--submit-index",
|
{ "--submit-index",
|
||||||
"Submit individual dashboard tests with specific index" },
|
"Submit individual dashboard tests with specific index" },
|
||||||
{ "--timeout <seconds>", "Set the default test timeout." },
|
{ "--timeout <seconds>", "Set the default test timeout." },
|
||||||
|
@ -656,3 +656,17 @@ set_tests_properties(test1 PROPERTIES TIMEOUT_SIGNAL_GRACE_PERIOD 1000)
|
|||||||
run_cmake_command(TimeoutSignalBad ${CMAKE_CTEST_COMMAND})
|
run_cmake_command(TimeoutSignalBad ${CMAKE_CTEST_COMMAND})
|
||||||
endblock()
|
endblock()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
block()
|
||||||
|
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/ScheduleRandomSeed)
|
||||||
|
set(RunCMake_TEST_NO_CLEAN 1)
|
||||||
|
file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
|
||||||
|
file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
|
||||||
|
file(WRITE "${RunCMake_TEST_BINARY_DIR}/CTestTestfile.cmake" "
|
||||||
|
foreach(i RANGE 1 5)
|
||||||
|
add_test(test\${i} \"${CMAKE_COMMAND}\" -E true)
|
||||||
|
endforeach()
|
||||||
|
")
|
||||||
|
run_cmake_command(ScheduleRandomSeed1 ${CMAKE_CTEST_COMMAND} --schedule-random --schedule-random-seed 42)
|
||||||
|
run_cmake_command(ScheduleRandomSeed2 ${CMAKE_CTEST_COMMAND} --schedule-random --schedule-random-seed 42)
|
||||||
|
endblock()
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
string(REGEX MATCHALL "Start [1-5]" ScheduleRandomSeed1_ORDER "${actual_stdout}")
|
||||||
|
set_property(DIRECTORY PROPERTY ScheduleRandomSeed1_ORDER "${ScheduleRandomSeed1_ORDER}")
|
@ -0,0 +1,10 @@
|
|||||||
|
string(REGEX MATCHALL "Start [1-5]" ScheduleRandomSeed2_ORDER "${actual_stdout}")
|
||||||
|
get_property(ScheduleRandomSeed1_ORDER DIRECTORY PROPERTY ScheduleRandomSeed1_ORDER)
|
||||||
|
if(NOT "${ScheduleRandomSeed1_ORDER}" STREQUAL "${ScheduleRandomSeed2_ORDER}")
|
||||||
|
string(CONCAT RunCMake_TEST_FAILED
|
||||||
|
"ScheduleRandomSeed1 order:\n"
|
||||||
|
" ${ScheduleRandomSeed1_ORDER}\n"
|
||||||
|
"does not match ScheduleRandomSeed2 order:\n"
|
||||||
|
" ${ScheduleRandomSeed2_ORDER}\n"
|
||||||
|
)
|
||||||
|
endif()
|
Loading…
Reference in New Issue
Block a user