diff options
author | Brad King <brad.king@kitware.com> | 2019-11-07 16:27:48 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2019-11-07 19:21:18 (GMT) |
commit | 28994115e83b568ccba546db26a55381cbad244d (patch) | |
tree | 1b109db3fddad3a96a4c8ac2b91917bc24a15ae4 | |
parent | 42d5d8f425e655a07d3dcb0802d90cb5c167b164 (diff) | |
download | CMake-28994115e83b568ccba546db26a55381cbad244d.zip CMake-28994115e83b568ccba546db26a55381cbad244d.tar.gz CMake-28994115e83b568ccba546db26a55381cbad244d.tar.bz2 |
ctest_test: Add option to REPEAT tests
-rw-r--r-- | Help/command/ctest_test.rst | 20 | ||||
-rw-r--r-- | Help/release/dev/ctest-repeat-until-pass.rst | 3 | ||||
-rw-r--r-- | Source/CTest/cmCTestTestCommand.cxx | 4 | ||||
-rw-r--r-- | Source/CTest/cmCTestTestCommand.h | 1 | ||||
-rw-r--r-- | Source/CTest/cmCTestTestHandler.cxx | 32 | ||||
-rw-r--r-- | Source/CTest/cmCTestTestHandler.h | 4 | ||||
-rw-r--r-- | Tests/RunCMake/ctest_test/RunCMakeTest.cmake | 22 | ||||
-rw-r--r-- | Tests/RunCMake/ctest_test/TestRepeatAfterTimeout-stdout.txt | 10 | ||||
-rw-r--r-- | Tests/RunCMake/ctest_test/TestRepeatAfterTimeout.cmake | 10 | ||||
-rw-r--r-- | Tests/RunCMake/ctest_test/TestRepeatBad1-result.txt | 1 | ||||
-rw-r--r-- | Tests/RunCMake/ctest_test/TestRepeatBad1-stderr.txt | 1 | ||||
-rw-r--r-- | Tests/RunCMake/ctest_test/TestRepeatBad2-result.txt | 1 | ||||
-rw-r--r-- | Tests/RunCMake/ctest_test/TestRepeatBad2-stderr.txt | 1 | ||||
-rw-r--r-- | Tests/RunCMake/ctest_test/TestRepeatUntilFail-stdout.txt | 13 | ||||
-rw-r--r-- | Tests/RunCMake/ctest_test/TestRepeatUntilFail.cmake | 9 | ||||
-rw-r--r-- | Tests/RunCMake/ctest_test/TestRepeatUntilPass-stdout.txt | 10 | ||||
-rw-r--r-- | Tests/RunCMake/ctest_test/TestRepeatUntilPass.cmake | 9 |
17 files changed, 147 insertions, 4 deletions
diff --git a/Help/command/ctest_test.rst b/Help/command/ctest_test.rst index 7a3393b..5c67b2c 100644 --- a/Help/command/ctest_test.rst +++ b/Help/command/ctest_test.rst @@ -23,6 +23,7 @@ Perform the :ref:`CTest Test Step` as a :ref:`Dashboard Client`. [STOP_TIME <time-of-day>] [RETURN_VALUE <result-var>] [CAPTURE_CMAKE_ERROR <result-var>] + [REPEAT <mode>:<n>] [QUIET] ) @@ -95,6 +96,25 @@ The options are: and then the ``--test-load`` command-line argument to :manual:`ctest(1)`. See also the ``TestLoad`` setting in the :ref:`CTest Test Step`. +``REPEAT <mode>:<n>`` + Run tests repeatedly based on the given ``<mode>`` up to ``<n>`` times. + The modes are: + + ``UNTIL_FAIL`` + Require each test to run ``<n>`` times without failing in order to pass. + This is useful in finding sporadic failures in test cases. + + ``UNTIL_PASS`` + Allow each test to run up to ``<n>`` times in order to pass. + Repeats tests if they fail for any reason. + This is useful in tolerating sporadic failures in test cases. + + ``AFTER_TIMEOUT`` + Allow each test to run up to ``<n>`` times in order to pass. + Repeats tests only if they timeout. + This is useful in tolerating sporadic timeouts in test cases + on busy machines. + ``SCHEDULE_RANDOM <ON|OFF>`` Launch tests in a random order. This may be useful for detecting implicit test dependencies. diff --git a/Help/release/dev/ctest-repeat-until-pass.rst b/Help/release/dev/ctest-repeat-until-pass.rst index d177247..b5f6c26 100644 --- a/Help/release/dev/ctest-repeat-until-pass.rst +++ b/Help/release/dev/ctest-repeat-until-pass.rst @@ -4,3 +4,6 @@ ctest-repeat-until-pass * The :manual:`ctest(1)` tool learned new ``--repeat-until-pass <n>`` and ``--repeat-after-timeout <n>`` options to help tolerate sporadic test failures. + +* The :command:`ctest_test` command gained a ``REPEAT <mode>:<n>`` option + to specify conditions in which to repeat tests. diff --git a/Source/CTest/cmCTestTestCommand.cxx b/Source/CTest/cmCTestTestCommand.cxx index 9784214..0f9b695 100644 --- a/Source/CTest/cmCTestTestCommand.cxx +++ b/Source/CTest/cmCTestTestCommand.cxx @@ -29,6 +29,7 @@ void cmCTestTestCommand::BindArguments() this->Bind("EXCLUDE_FIXTURE_SETUP"_s, this->ExcludeFixtureSetup); this->Bind("EXCLUDE_FIXTURE_CLEANUP"_s, this->ExcludeFixtureCleanup); this->Bind("PARALLEL_LEVEL"_s, this->ParallelLevel); + this->Bind("REPEAT"_s, this->Repeat); this->Bind("SCHEDULE_RANDOM"_s, this->ScheduleRandom); this->Bind("STOP_TIME"_s, this->StopTime); this->Bind("TEST_LOAD"_s, this->TestLoad); @@ -85,6 +86,9 @@ cmCTestGenericHandler* cmCTestTestCommand::InitializeHandler() if (!this->ParallelLevel.empty()) { handler->SetOption("ParallelLevel", this->ParallelLevel.c_str()); } + if (!this->Repeat.empty()) { + handler->SetOption("Repeat", this->Repeat.c_str()); + } if (!this->ScheduleRandom.empty()) { handler->SetOption("ScheduleRandom", this->ScheduleRandom.c_str()); } diff --git a/Source/CTest/cmCTestTestCommand.h b/Source/CTest/cmCTestTestCommand.h index 4019694..2345afb 100644 --- a/Source/CTest/cmCTestTestCommand.h +++ b/Source/CTest/cmCTestTestCommand.h @@ -55,6 +55,7 @@ protected: std::string ExcludeFixtureSetup; std::string ExcludeFixtureCleanup; std::string ParallelLevel; + std::string Repeat; std::string ScheduleRandom; std::string StopTime; std::string TestLoad; diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx index 9a06083..37c7154 100644 --- a/Source/CTest/cmCTestTestHandler.cxx +++ b/Source/CTest/cmCTestTestHandler.cxx @@ -471,6 +471,30 @@ bool cmCTestTestHandler::ProcessOptions() if (cmIsOn(this->GetOption("ScheduleRandom"))) { this->CTest->SetScheduleType("Random"); } + if (const char* repeat = this->GetOption("Repeat")) { + cmsys::RegularExpression repeatRegex( + "^(UNTIL_FAIL|UNTIL_PASS|AFTER_TIMEOUT):([0-9]+)$"); + if (repeatRegex.find(repeat)) { + std::string const& count = repeatRegex.match(2); + unsigned long n = 1; + cmStrToULong(count, &n); // regex guarantees success + this->RepeatCount = static_cast<int>(n); + if (this->RepeatCount > 1) { + std::string const& mode = repeatRegex.match(1); + if (mode == "UNTIL_FAIL") { + this->RepeatMode = cmCTest::Repeat::UntilFail; + } else if (mode == "UNTIL_PASS") { + this->RepeatMode = cmCTest::Repeat::UntilPass; + } else if (mode == "AFTER_TIMEOUT") { + this->RepeatMode = cmCTest::Repeat::AfterTimeout; + } + } + } else { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Repeat option invalid value: " << repeat << std::endl); + return false; + } + } if (this->GetOption("ParallelLevel")) { this->CTest->SetParallelLevel(atoi(this->GetOption("ParallelLevel"))); } @@ -1231,8 +1255,12 @@ void cmCTestTestHandler::ProcessDirectory(std::vector<std::string>& passed, parallel->SetCTest(this->CTest); parallel->SetParallelLevel(this->CTest->GetParallelLevel()); parallel->SetTestHandler(this); - parallel->SetRepeatMode(this->CTest->GetRepeatMode(), - this->CTest->GetRepeatCount()); + if (this->RepeatMode != cmCTest::Repeat::Never) { + parallel->SetRepeatMode(this->RepeatMode, this->RepeatCount); + } else { + parallel->SetRepeatMode(this->CTest->GetRepeatMode(), + this->CTest->GetRepeatCount()); + } parallel->SetQuiet(this->Quiet); if (this->TestLoad > 0) { parallel->SetTestLoad(this->TestLoad); diff --git a/Source/CTest/cmCTestTestHandler.h b/Source/CTest/cmCTestTestHandler.h index eab75d0..55237f9 100644 --- a/Source/CTest/cmCTestTestHandler.h +++ b/Source/CTest/cmCTestTestHandler.h @@ -18,12 +18,12 @@ #include "cmsys/RegularExpression.hxx" +#include "cmCTest.h" #include "cmCTestGenericHandler.h" #include "cmCTestResourceSpec.h" #include "cmDuration.h" #include "cmListFileCache.h" -class cmCTest; class cmMakefile; class cmXMLWriter; @@ -353,6 +353,8 @@ private: std::ostream* LogFile; + cmCTest::Repeat RepeatMode = cmCTest::Repeat::Never; + int RepeatCount = 1; bool RerunFailed; }; diff --git a/Tests/RunCMake/ctest_test/RunCMakeTest.cmake b/Tests/RunCMake/ctest_test/RunCMakeTest.cmake index 6877e6a..84d1d66 100644 --- a/Tests/RunCMake/ctest_test/RunCMakeTest.cmake +++ b/Tests/RunCMake/ctest_test/RunCMakeTest.cmake @@ -1,6 +1,9 @@ include(RunCTest) set(RunCMake_TEST_TIMEOUT 60) +unset(ENV{CTEST_PARALLEL_LEVEL}) +unset(ENV{CTEST_OUTPUT_ON_FAILURE}) + set(CASE_CTEST_TEST_ARGS "") set(CASE_CTEST_TEST_LOAD "") @@ -71,7 +74,24 @@ add_test(NAME PassingTest COMMAND ${CMAKE_COMMAND} -E echo PassingTestOutput) add_test(NAME FailingTest COMMAND ${CMAKE_COMMAND} -E no_such_command) ]]) - unset(ENV{CTEST_PARALLEL_LEVEL}) run_ctest(TestOutputSize) endfunction() run_TestOutputSize() + +run_ctest_test(TestRepeatBad1 REPEAT UNKNOWN:3) +run_ctest_test(TestRepeatBad2 REPEAT UNTIL_FAIL:-1) + +function(run_TestRepeat case) + set(CASE_CTEST_TEST_ARGS EXCLUDE RunCMakeVersion ${ARGN}) + string(CONCAT CASE_CMAKELISTS_SUFFIX_CODE [[ +add_test(NAME testRepeat + COMMAND ${CMAKE_COMMAND} -D COUNT_FILE=${CMAKE_CURRENT_BINARY_DIR}/count.cmake + -P "]] "${RunCMake_SOURCE_DIR}/TestRepeat${case}" [[.cmake") +set_property(TEST testRepeat PROPERTY TIMEOUT 5) + ]]) + + run_ctest(TestRepeat${case}) +endfunction() +run_TestRepeat(UntilFail REPEAT UNTIL_FAIL:3) +run_TestRepeat(UntilPass REPEAT UNTIL_PASS:3) +run_TestRepeat(AfterTimeout REPEAT AFTER_TIMEOUT:3) diff --git a/Tests/RunCMake/ctest_test/TestRepeatAfterTimeout-stdout.txt b/Tests/RunCMake/ctest_test/TestRepeatAfterTimeout-stdout.txt new file mode 100644 index 0000000..17657c5 --- /dev/null +++ b/Tests/RunCMake/ctest_test/TestRepeatAfterTimeout-stdout.txt @@ -0,0 +1,10 @@ +Test project [^ +]*/Tests/RunCMake/ctest_test/TestRepeatAfterTimeout-build + Start 1: testRepeat +1/1 Test #1: testRepeat .......................\*\*\*Timeout +[0-9.]+ sec + Start 1: testRepeat + Test #1: testRepeat ....................... Passed +[0-9.]+ sec ++ +100% tests passed, 0 tests failed out of 1 ++ +Total Test time \(real\) = +[0-9.]+ sec$ diff --git a/Tests/RunCMake/ctest_test/TestRepeatAfterTimeout.cmake b/Tests/RunCMake/ctest_test/TestRepeatAfterTimeout.cmake new file mode 100644 index 0000000..abde4f0 --- /dev/null +++ b/Tests/RunCMake/ctest_test/TestRepeatAfterTimeout.cmake @@ -0,0 +1,10 @@ +include("${COUNT_FILE}" OPTIONAL) +if(NOT COUNT) + set(COUNT 0) +endif() +math(EXPR COUNT "${COUNT} + 1") +file(WRITE "${COUNT_FILE}" "set(COUNT ${COUNT})\n") +if(NOT COUNT EQUAL 2) + message("this test times out except on the 2nd run") + execute_process(COMMAND ${CMAKE_COMMAND} -E sleep 10) +endif() diff --git a/Tests/RunCMake/ctest_test/TestRepeatBad1-result.txt b/Tests/RunCMake/ctest_test/TestRepeatBad1-result.txt new file mode 100644 index 0000000..b57e2de --- /dev/null +++ b/Tests/RunCMake/ctest_test/TestRepeatBad1-result.txt @@ -0,0 +1 @@ +(-1|255) diff --git a/Tests/RunCMake/ctest_test/TestRepeatBad1-stderr.txt b/Tests/RunCMake/ctest_test/TestRepeatBad1-stderr.txt new file mode 100644 index 0000000..37cffbf --- /dev/null +++ b/Tests/RunCMake/ctest_test/TestRepeatBad1-stderr.txt @@ -0,0 +1 @@ +Repeat option invalid value: UNKNOWN:3 diff --git a/Tests/RunCMake/ctest_test/TestRepeatBad2-result.txt b/Tests/RunCMake/ctest_test/TestRepeatBad2-result.txt new file mode 100644 index 0000000..b57e2de --- /dev/null +++ b/Tests/RunCMake/ctest_test/TestRepeatBad2-result.txt @@ -0,0 +1 @@ +(-1|255) diff --git a/Tests/RunCMake/ctest_test/TestRepeatBad2-stderr.txt b/Tests/RunCMake/ctest_test/TestRepeatBad2-stderr.txt new file mode 100644 index 0000000..ca5cef7 --- /dev/null +++ b/Tests/RunCMake/ctest_test/TestRepeatBad2-stderr.txt @@ -0,0 +1 @@ +Repeat option invalid value: UNTIL_FAIL:-1 diff --git a/Tests/RunCMake/ctest_test/TestRepeatUntilFail-stdout.txt b/Tests/RunCMake/ctest_test/TestRepeatUntilFail-stdout.txt new file mode 100644 index 0000000..5f91a67 --- /dev/null +++ b/Tests/RunCMake/ctest_test/TestRepeatUntilFail-stdout.txt @@ -0,0 +1,13 @@ +Test project [^ +]*/Tests/RunCMake/ctest_test/TestRepeatUntilFail-build + Start 1: testRepeat + Test #1: testRepeat ....................... Passed +[0-9.]+ sec + Start 1: testRepeat + Test #1: testRepeat .......................\*\*\*Failed +[0-9.]+ sec ++ +0% tests passed, 1 tests failed out of 1 ++ +Total Test time \(real\) = +[0-9.]+ sec ++ +The following tests FAILED: +[ ]+1 - testRepeat \(Failed\)$ diff --git a/Tests/RunCMake/ctest_test/TestRepeatUntilFail.cmake b/Tests/RunCMake/ctest_test/TestRepeatUntilFail.cmake new file mode 100644 index 0000000..5eb0d8a --- /dev/null +++ b/Tests/RunCMake/ctest_test/TestRepeatUntilFail.cmake @@ -0,0 +1,9 @@ +include("${COUNT_FILE}" OPTIONAL) +if(NOT COUNT) + set(COUNT 0) +endif() +math(EXPR COUNT "${COUNT} + 1") +file(WRITE "${COUNT_FILE}" "set(COUNT ${COUNT})\n") +if(COUNT EQUAL 2) + message(FATAL_ERROR "this test fails on the 2nd run") +endif() diff --git a/Tests/RunCMake/ctest_test/TestRepeatUntilPass-stdout.txt b/Tests/RunCMake/ctest_test/TestRepeatUntilPass-stdout.txt new file mode 100644 index 0000000..bc6939a --- /dev/null +++ b/Tests/RunCMake/ctest_test/TestRepeatUntilPass-stdout.txt @@ -0,0 +1,10 @@ +Test project [^ +]*/Tests/RunCMake/ctest_test/TestRepeatUntilPass-build + Start 1: testRepeat +1/1 Test #1: testRepeat .......................\*\*\*Failed +[0-9.]+ sec + Start 1: testRepeat + Test #1: testRepeat ....................... Passed +[0-9.]+ sec ++ +100% tests passed, 0 tests failed out of 1 ++ +Total Test time \(real\) = +[0-9.]+ sec$ diff --git a/Tests/RunCMake/ctest_test/TestRepeatUntilPass.cmake b/Tests/RunCMake/ctest_test/TestRepeatUntilPass.cmake new file mode 100644 index 0000000..0662522 --- /dev/null +++ b/Tests/RunCMake/ctest_test/TestRepeatUntilPass.cmake @@ -0,0 +1,9 @@ +include("${COUNT_FILE}" OPTIONAL) +if(NOT COUNT) + set(COUNT 0) +endif() +math(EXPR COUNT "${COUNT} + 1") +file(WRITE "${COUNT_FILE}" "set(COUNT ${COUNT})\n") +if(NOT COUNT EQUAL 2) + message(FATAL_ERROR "this test passes only on the 2nd run") +endif() |