From c6940b0dccef0bd29587b15604fdbe0ec95b1133 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Mon, 22 Dec 2025 13:34:31 -0500 Subject: cmProcess: explicitly track the StopTimeout When a test can have its timeout reset, the stop time still needs to be considered when setting the new timeout. Track it explicitly. --- Source/CTest/cmCTestRunTest.cxx | 1 + Source/CTest/cmProcess.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx index 24ee6eb..eff891a 100644 --- a/Source/CTest/cmCTestRunTest.cxx +++ b/Source/CTest/cmCTestRunTest.cxx @@ -816,6 +816,7 @@ bool cmCTestRunTest::ForkProcess() if (timeUntilStop < timeRemaining) { timeRemaining = timeUntilStop; } + this->TestProcess->SetStopTimeout(timeUntilStop); } // Enforce remaining time even over explicit TIMEOUT=0. diff --git a/Source/CTest/cmProcess.h b/Source/CTest/cmProcess.h index 2489ba5..d3df8d3 100644 --- a/Source/CTest/cmProcess.h +++ b/Source/CTest/cmProcess.h @@ -37,6 +37,7 @@ public: void SetCommand(std::string const& command); void SetCommandArguments(std::vector const& arg); void SetWorkingDirectory(std::string const& dir); + void SetStopTimeout(cmDuration t) { this->StopTimeout = t; } void SetTimeout(cmDuration t) { this->Timeout = t; } void ChangeTimeout(cmDuration t); void ResetStartTime(); @@ -104,6 +105,7 @@ public: Termination GetTerminationStyle() const { return this->TerminationStyle; } private: + cm::optional StopTimeout; cm::optional Timeout; TimeoutReason TimeoutReason_ = TimeoutReason::Normal; std::chrono::steady_clock::time_point StartTime; -- cgit v0.12 From af7427675ad11e4c51e56399f315a236ca7b57ea Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Mon, 22 Dec 2025 13:36:53 -0500 Subject: cmProcess: compute the timeout when needed When a timeout is updated during runtime (e.g., via `TIMEOUT_AFTER_MATCH`), the actual timeout needs recomputed based on consideration of `StopTimeout` as well. Instead of using `Timeout` directly, add a `GetComputedTimeout` method which also retrieves the timeout reason based on which timeout is selected. --- Source/CTest/cmCTestRunTest.cxx | 14 ++++++++++---- Source/CTest/cmProcess.cxx | 25 ++++++++++++++++++++++--- Source/CTest/cmProcess.h | 7 ++++++- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx index eff891a..de8651c 100644 --- a/Source/CTest/cmCTestRunTest.cxx +++ b/Source/CTest/cmCTestRunTest.cxx @@ -813,9 +813,6 @@ bool cmCTestRunTest::ForkProcess() if (stop_time != std::chrono::system_clock::time_point()) { cmDuration timeUntilStop = (stop_time - std::chrono::system_clock::now()) % std::chrono::hours(24); - if (timeUntilStop < timeRemaining) { - timeRemaining = timeUntilStop; - } this->TestProcess->SetStopTimeout(timeUntilStop); } @@ -825,7 +822,16 @@ bool cmCTestRunTest::ForkProcess() } if (!timeout || timeRemaining < *timeout) { timeout = timeRemaining; - this->TestProcess->SetTimeoutReason(cmProcess::TimeoutReason::StopTime); + } + + // Inform the test process of its normal timeout + if (timeout) { + this->TestProcess->SetTimeout(*timeout); + } + + // Ask the test process which timeout is in effect. + if (auto ctimeout = this->TestProcess->GetComputedTimeout()) { + timeout = ctimeout->Duration; } if (timeout) { diff --git a/Source/CTest/cmProcess.cxx b/Source/CTest/cmProcess.cxx index 9002d2e..9bf81f8 100644 --- a/Source/CTest/cmProcess.cxx +++ b/Source/CTest/cmProcess.cxx @@ -158,9 +158,10 @@ bool cmProcess::StartProcess(uv_loop_t& loop, std::vector* affinity) void cmProcess::StartTimer() { - if (this->Timeout) { - auto msec = - std::chrono::duration_cast(*this->Timeout); + if (auto ctimeout = this->GetComputedTimeout()) { + this->TimeoutReason_ = ctimeout->Reason; + auto msec = std::chrono::duration_cast( + ctimeout->Duration); this->Timer.start(&cmProcess::OnTimeoutCB, static_cast(msec.count()), 0, cm::uv_update_time::no); @@ -374,6 +375,24 @@ cmProcess::State cmProcess::GetProcessStatus() return this->ProcessState; } +cm::optional cmProcess::GetComputedTimeout() const +{ + if (this->StopTimeout && this->Timeout) { + if (*this->StopTimeout < *this->Timeout) { + return ComputedTimeout{ TimeoutReason::StopTime, *this->StopTimeout }; + } + return ComputedTimeout{ TimeoutReason::Normal, *this->Timeout }; + } + if (this->StopTimeout) { + return ComputedTimeout{ TimeoutReason::StopTime, *this->StopTimeout }; + } + if (this->Timeout) { + return ComputedTimeout{ TimeoutReason::Normal, *this->Timeout }; + } + + return cm::nullopt; +} + void cmProcess::ChangeTimeout(cmDuration t) { this->Timeout = t; diff --git a/Source/CTest/cmProcess.h b/Source/CTest/cmProcess.h index d3df8d3..a4704b4 100644 --- a/Source/CTest/cmProcess.h +++ b/Source/CTest/cmProcess.h @@ -49,8 +49,13 @@ public: Normal, StopTime, }; - void SetTimeoutReason(TimeoutReason r) { this->TimeoutReason_ = r; } TimeoutReason GetTimeoutReason() const { return this->TimeoutReason_; } + struct ComputedTimeout + { + TimeoutReason Reason; + cmDuration Duration; + }; + cm::optional GetComputedTimeout() const; enum class State { -- cgit v0.12 From 68beb2e514ca89fe0a3531b20d472cea7353497f Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Mon, 22 Dec 2025 15:01:13 -0500 Subject: Tests/CTestTimeoutAfterMatch: add case for stop time bug When there is no timeout, a stop time reason was made for any timeout, including a case where a `TIMEOUT_AFTER_MATCH` would reset the timeout. Add a test case. --- .../CTestTimeoutAfterMatch/RunCMakeTest.cmake | 22 ++++++++++++++++++++++ .../ShouldTimeoutNoBaseTimeout-test-result.txt | 1 + .../ShouldTimeoutNoBaseTimeout-test-stderr.txt | 3 +++ .../ShouldTimeoutNoBaseTimeout-test-stdout.txt | 1 + .../ShouldTimeoutNoBaseTimeout/CMakeLists.txt | 5 +++++ 5 files changed, 32 insertions(+) create mode 100644 Tests/RunCMake/CTestTimeoutAfterMatch/ShouldTimeoutNoBaseTimeout-test-result.txt create mode 100644 Tests/RunCMake/CTestTimeoutAfterMatch/ShouldTimeoutNoBaseTimeout-test-stderr.txt create mode 100644 Tests/RunCMake/CTestTimeoutAfterMatch/ShouldTimeoutNoBaseTimeout-test-stdout.txt create mode 100644 Tests/RunCMake/CTestTimeoutAfterMatch/ShouldTimeoutNoBaseTimeout/CMakeLists.txt diff --git a/Tests/RunCMake/CTestTimeoutAfterMatch/RunCMakeTest.cmake b/Tests/RunCMake/CTestTimeoutAfterMatch/RunCMakeTest.cmake index ee4db83..2dad5f0 100644 --- a/Tests/RunCMake/CTestTimeoutAfterMatch/RunCMakeTest.cmake +++ b/Tests/RunCMake/CTestTimeoutAfterMatch/RunCMakeTest.cmake @@ -1,3 +1,4 @@ +include(RunCMake) include(RunCTest) function(run_ctest_TimeoutAfterMatch CASE_NAME) @@ -9,3 +10,24 @@ run_ctest_TimeoutAfterMatch(MissingArg1 "\"-Darg2=Test started\"") run_ctest_TimeoutAfterMatch(MissingArg2 "\"-Darg1=2\"") run_ctest_TimeoutAfterMatch(ShouldTimeout "\"-Darg1=1\" \"-Darg2=Test started\"") run_ctest_TimeoutAfterMatch(ShouldPass "\"-Darg1=15\" \"-Darg2=Test started\"") + +function(run_ctest_cli_TimeoutAfterMatch CASE_NAME) + set(test_name "${CASE_NAME}") + + set(RunCMake_TEST_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/${test_name}") + set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/${test_name}-build") + + if (RunCMake_GENERATOR_IS_MULTI_CONFIG) + set(RunCMake_TEST_OPTIONS -DCMAKE_CONFIGURATION_TYPES=Debug) + else () + set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Debug) + endif () + list(APPEND RunCMake_TEST_OPTIONS + ${ARGN}) + run_cmake("${test_name}") + set(RunCMake_TEST_NO_CLEAN 1) + run_cmake_command("${test_name}-build" "${CMAKE_COMMAND}" --build . --config Debug) + run_cmake_command("${test_name}-test" "${CMAKE_CTEST_COMMAND}" -C Debug -VV) +endfunction() + +run_ctest_cli_TimeoutAfterMatch(ShouldTimeoutNoBaseTimeout "-Dno_timeout=1" "-Darg1=1" "-Darg2=Test started") diff --git a/Tests/RunCMake/CTestTimeoutAfterMatch/ShouldTimeoutNoBaseTimeout-test-result.txt b/Tests/RunCMake/CTestTimeoutAfterMatch/ShouldTimeoutNoBaseTimeout-test-result.txt new file mode 100644 index 0000000..45a4fb7 --- /dev/null +++ b/Tests/RunCMake/CTestTimeoutAfterMatch/ShouldTimeoutNoBaseTimeout-test-result.txt @@ -0,0 +1 @@ +8 diff --git a/Tests/RunCMake/CTestTimeoutAfterMatch/ShouldTimeoutNoBaseTimeout-test-stderr.txt b/Tests/RunCMake/CTestTimeoutAfterMatch/ShouldTimeoutNoBaseTimeout-test-stderr.txt new file mode 100644 index 0000000..0452e31 --- /dev/null +++ b/Tests/RunCMake/CTestTimeoutAfterMatch/ShouldTimeoutNoBaseTimeout-test-stderr.txt @@ -0,0 +1,3 @@ +^Errors while running CTest +Output from these tests are in: .*/Tests/RunCMake/CTestTimeoutAfterMatch/ShouldTimeoutNoBaseTimeout-build/Testing/Temporary/LastTest\.log +Use "--rerun-failed --output-on-failure" to re-run the failed cases verbosely\.$ diff --git a/Tests/RunCMake/CTestTimeoutAfterMatch/ShouldTimeoutNoBaseTimeout-test-stdout.txt b/Tests/RunCMake/CTestTimeoutAfterMatch/ShouldTimeoutNoBaseTimeout-test-stdout.txt new file mode 100644 index 0000000..1b38331 --- /dev/null +++ b/Tests/RunCMake/CTestTimeoutAfterMatch/ShouldTimeoutNoBaseTimeout-test-stdout.txt @@ -0,0 +1 @@ +1/1 Test #1: SleepFor1Second \.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\*\*\*Timeout +[0-9\.]+ sec diff --git a/Tests/RunCMake/CTestTimeoutAfterMatch/ShouldTimeoutNoBaseTimeout/CMakeLists.txt b/Tests/RunCMake/CTestTimeoutAfterMatch/ShouldTimeoutNoBaseTimeout/CMakeLists.txt new file mode 100644 index 0000000..456c092 --- /dev/null +++ b/Tests/RunCMake/CTestTimeoutAfterMatch/ShouldTimeoutNoBaseTimeout/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.10) +project(TimeoutAfterMatch NONE) +enable_testing() +add_test(NAME SleepFor1Second COMMAND "${CMAKE_COMMAND}" -P ${CMAKE_SOURCE_DIR}/../SleepFor1Second.cmake) +set_property(TEST SleepFor1Second PROPERTY TIMEOUT_AFTER_MATCH "${arg1}" "${arg2}") -- cgit v0.12