From 54c5654f7d530ea363b3fcc02f2176d79342c07b Mon Sep 17 00:00:00 2001 From: Brad King Date: Thu, 11 May 2023 13:32:15 -0400 Subject: ctest: Optionally terminate tests with a custom signal on timeout CTest normally terminates test processes on timeout using `SIGKILL`. Offer tests a chance to exit gracefully, on platforms supporting POSIX signals, by setting `TIMEOUT_SIGNAL_{NAME,GRACE_PERIOD}` properties. Fixes: #17288 --- Help/manual/cmake-properties.7.rst | 2 + Help/prop_test/TIMEOUT.rst | 3 ++ Help/prop_test/TIMEOUT_AFTER_MATCH.rst | 2 + Help/prop_test/TIMEOUT_SIGNAL_GRACE_PERIOD.rst | 14 +++++ Help/prop_test/TIMEOUT_SIGNAL_NAME.rst | 41 +++++++++++++++ Help/release/dev/ctest-timeout-signal.rst | 7 +++ Source/CTest/cmCTestRunTest.cxx | 18 +++++++ Source/CTest/cmCTestTestHandler.cxx | 61 ++++++++++++++++++++++ Source/CTest/cmCTestTestHandler.h | 11 ++++ Source/CTest/cmProcess.cxx | 23 ++++++++ Source/CTest/cmProcess.h | 9 ++++ Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake | 29 ++++++++++ .../CTestCommandLine/TimeoutSignalBad-result.txt | 1 + .../CTestCommandLine/TimeoutSignalBad-stderr.txt | 1 + .../CTestCommandLine/TimeoutSignalBad-stdout.txt | 5 ++ .../TimeoutSignalWindows-result.txt | 1 + .../TimeoutSignalWindows-stderr.txt | 1 + .../TimeoutSignalWindows-stdout.txt | 4 ++ Tests/RunCMake/CTestTimeout/CMakeLists.txt.in | 4 ++ Tests/RunCMake/CTestTimeout/RunCMakeTest.cmake | 58 +++++++++++++++++++- Tests/RunCMake/CTestTimeout/Signal-check.cmake | 11 ++++ Tests/RunCMake/CTestTimeout/Signal-stdout.txt | 6 +++ .../CTestTimeout/SignalGraceHigh-check.cmake | 11 ++++ .../CTestTimeout/SignalGraceHigh-stdout.txt | 3 ++ .../CTestTimeout/SignalGraceLow-check.cmake | 11 ++++ .../CTestTimeout/SignalGraceLow-stdout.txt | 3 ++ .../RunCMake/CTestTimeout/SignalIgnore-check.cmake | 11 ++++ .../RunCMake/CTestTimeout/SignalIgnore-stdout.txt | 6 +++ .../CTestTimeout/SignalUnknown-check.cmake | 11 ++++ .../RunCMake/CTestTimeout/SignalUnknown-stdout.txt | 3 ++ .../CTestTimeout/SignalWindows-check.cmake | 11 ++++ .../RunCMake/CTestTimeout/SignalWindows-stdout.txt | 4 ++ Tests/RunCMake/CTestTimeout/TestTimeout.c | 47 +++++++++++++++++ Utilities/IWYU/mapping.imp | 1 + 34 files changed, 433 insertions(+), 1 deletion(-) create mode 100644 Help/prop_test/TIMEOUT_SIGNAL_GRACE_PERIOD.rst create mode 100644 Help/prop_test/TIMEOUT_SIGNAL_NAME.rst create mode 100644 Help/release/dev/ctest-timeout-signal.rst create mode 100644 Tests/RunCMake/CTestCommandLine/TimeoutSignalBad-result.txt create mode 100644 Tests/RunCMake/CTestCommandLine/TimeoutSignalBad-stderr.txt create mode 100644 Tests/RunCMake/CTestCommandLine/TimeoutSignalBad-stdout.txt create mode 100644 Tests/RunCMake/CTestCommandLine/TimeoutSignalWindows-result.txt create mode 100644 Tests/RunCMake/CTestCommandLine/TimeoutSignalWindows-stderr.txt create mode 100644 Tests/RunCMake/CTestCommandLine/TimeoutSignalWindows-stdout.txt create mode 100644 Tests/RunCMake/CTestTimeout/Signal-check.cmake create mode 100644 Tests/RunCMake/CTestTimeout/Signal-stdout.txt create mode 100644 Tests/RunCMake/CTestTimeout/SignalGraceHigh-check.cmake create mode 100644 Tests/RunCMake/CTestTimeout/SignalGraceHigh-stdout.txt create mode 100644 Tests/RunCMake/CTestTimeout/SignalGraceLow-check.cmake create mode 100644 Tests/RunCMake/CTestTimeout/SignalGraceLow-stdout.txt create mode 100644 Tests/RunCMake/CTestTimeout/SignalIgnore-check.cmake create mode 100644 Tests/RunCMake/CTestTimeout/SignalIgnore-stdout.txt create mode 100644 Tests/RunCMake/CTestTimeout/SignalUnknown-check.cmake create mode 100644 Tests/RunCMake/CTestTimeout/SignalUnknown-stdout.txt create mode 100644 Tests/RunCMake/CTestTimeout/SignalWindows-check.cmake create mode 100644 Tests/RunCMake/CTestTimeout/SignalWindows-stdout.txt diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst index b2a27a0..4e2e7d5 100644 --- a/Help/manual/cmake-properties.7.rst +++ b/Help/manual/cmake-properties.7.rst @@ -522,6 +522,8 @@ Properties on Tests /prop_test/SKIP_RETURN_CODE /prop_test/TIMEOUT /prop_test/TIMEOUT_AFTER_MATCH + /prop_test/TIMEOUT_SIGNAL_GRACE_PERIOD + /prop_test/TIMEOUT_SIGNAL_NAME /prop_test/WILL_FAIL /prop_test/WORKING_DIRECTORY diff --git a/Help/prop_test/TIMEOUT.rst b/Help/prop_test/TIMEOUT.rst index 385539b..175c0fb 100644 --- a/Help/prop_test/TIMEOUT.rst +++ b/Help/prop_test/TIMEOUT.rst @@ -10,3 +10,6 @@ setting takes precedence over :variable:`CTEST_TEST_TIMEOUT`. An explicit ``0`` value means the test has no timeout, except as necessary to honor :option:`ctest --stop-time`. + +See also :prop_test:`TIMEOUT_AFTER_MATCH` and +:prop_test:`TIMEOUT_SIGNAL_NAME`. diff --git a/Help/prop_test/TIMEOUT_AFTER_MATCH.rst b/Help/prop_test/TIMEOUT_AFTER_MATCH.rst index aa17590..edee2ef 100644 --- a/Help/prop_test/TIMEOUT_AFTER_MATCH.rst +++ b/Help/prop_test/TIMEOUT_AFTER_MATCH.rst @@ -39,3 +39,5 @@ If the required resource can be controlled by CTest you should use :prop_test:`RESOURCE_LOCK` instead of ``TIMEOUT_AFTER_MATCH``. This property should be used when only the test itself can determine when its required resources are available. + +See also :prop_test:`TIMEOUT_SIGNAL_NAME`. diff --git a/Help/prop_test/TIMEOUT_SIGNAL_GRACE_PERIOD.rst b/Help/prop_test/TIMEOUT_SIGNAL_GRACE_PERIOD.rst new file mode 100644 index 0000000..858be5d --- /dev/null +++ b/Help/prop_test/TIMEOUT_SIGNAL_GRACE_PERIOD.rst @@ -0,0 +1,14 @@ +TIMEOUT_SIGNAL_GRACE_PERIOD +--------------------------- + +.. versionadded:: 3.27 + +If the :prop_test:`TIMEOUT_SIGNAL_NAME` test property is set, this property +specifies the number of seconds to wait for a test process to terminate after +sending the custom signal. Otherwise, this property has no meaning. + +The grace period may be any real value greater than ``0.0``, but not greater +than ``60.0``. If this property is not set, the default is ``1.0`` second. + +This is available only on platforms supporting POSIX signals. +It is not available on Windows. diff --git a/Help/prop_test/TIMEOUT_SIGNAL_NAME.rst b/Help/prop_test/TIMEOUT_SIGNAL_NAME.rst new file mode 100644 index 0000000..6294d27 --- /dev/null +++ b/Help/prop_test/TIMEOUT_SIGNAL_NAME.rst @@ -0,0 +1,41 @@ +TIMEOUT_SIGNAL_NAME +------------------- + +.. versionadded:: 3.27 + +Specify a custom signal to send to a test process when its timeout is reached. +This is available only on platforms supporting POSIX signals. +It is not available on Windows. + +The name must be one of the following: + + ``SIGINT`` + Interrupt. + + ``SIGQUIT`` + Quit. + + ``SIGTERM`` + Terminate. + + ``SIGUSR1`` + User defined signal 1. + + ``SIGUSR2`` + User defined signal 2. + +The custom signal is sent to the test process to give it a chance +to exit gracefully during a grace period: + +* If the test process created any children, it is responsible for + terminating them too. + +* The grace period length is determined by the + :prop_test:`TIMEOUT_SIGNAL_GRACE_PERIOD` test property. + +* If the test process does not terminate before the grace period ends, + :manual:`ctest(1)` will force termination of its entire process tree + via ``SIGSTOP`` and ``SIGKILL``. + +See also :variable:`CTEST_TEST_TIMEOUT`, +:prop_test:`TIMEOUT`, and :prop_test:`TIMEOUT_AFTER_MATCH`. diff --git a/Help/release/dev/ctest-timeout-signal.rst b/Help/release/dev/ctest-timeout-signal.rst new file mode 100644 index 0000000..f182429 --- /dev/null +++ b/Help/release/dev/ctest-timeout-signal.rst @@ -0,0 +1,7 @@ +ctest-timeout-signal +-------------------- + +* The :prop_test:`TIMEOUT_SIGNAL_NAME` and + :prop_test:`TIMEOUT_SIGNAL_GRACE_PERIOD` test properties were added + to specify a POSIX signal to send to a test process when its timeout + is reached. diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx index 2859b82..9b62183 100644 --- a/Source/CTest/cmCTestRunTest.cxx +++ b/Source/CTest/cmCTestRunTest.cxx @@ -181,6 +181,11 @@ cmCTestRunTest::EndTestResult cmCTestRunTest::EndTest(size_t completed, } } else if (res == cmProcess::State::Expired) { outputStream << "***Timeout "; + if (this->TestProperties->TimeoutSignal && + this->TestProcess->GetTerminationStyle() == + cmProcess::Termination::Custom) { + outputStream << "(" << this->TestProperties->TimeoutSignal->Name << ") "; + } this->TestResult.Status = cmCTestTestHandler::TIMEOUT; outputTestErrorsToConsole = this->CTest->GetOutputTestOutputOnTestFailure(); @@ -540,6 +545,19 @@ bool cmCTestRunTest::StartTest(size_t completed, size_t total) this->TestResult.Name = this->TestProperties->Name; this->TestResult.Path = this->TestProperties->Directory; + // Reject invalid test properties. + if (this->TestProperties->Error) { + std::string const& msg = *this->TestProperties->Error; + *this->TestHandler->LogFile << msg << std::endl; + cmCTestLog(this->CTest, HANDLER_OUTPUT, msg << std::endl); + this->TestResult.CompletionStatus = "Invalid Test Properties"; + this->TestResult.Status = cmCTestTestHandler::NOT_RUN; + this->TestResult.Output = msg; + this->TestResult.FullCommandLine.clear(); + this->TestResult.Environment.clear(); + return false; + } + // Return immediately if test is disabled if (this->TestProperties->Disabled) { this->TestResult.CompletionStatus = "Disabled"; diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx index 7764f2b..6b02a5e 100644 --- a/Source/CTest/cmCTestTestHandler.cxx +++ b/Source/CTest/cmCTestTestHandler.cxx @@ -17,6 +17,10 @@ #include #include +#ifndef _WIN32 +# include +#endif + #include #include #include @@ -2171,6 +2175,16 @@ void cmCTestTestHandler::CleanTestOutput(std::string& output, size_t length, } } +void cmCTestTestHandler::cmCTestTestProperties::AppendError( + cm::string_view err) +{ + if (this->Error) { + *this->Error = cmStrCat(*this->Error, '\n', err); + } else { + this->Error = err; + } +} + bool cmCTestTestHandler::SetTestsProperties( const std::vector& args) { @@ -2247,6 +2261,53 @@ bool cmCTestTestHandler::SetTestsProperties( rt.FixturesRequired.insert(lval.begin(), lval.end()); } else if (key == "TIMEOUT"_s) { rt.Timeout = cmDuration(atof(val.c_str())); + } else if (key == "TIMEOUT_SIGNAL_NAME"_s) { +#ifdef _WIN32 + rt.AppendError("TIMEOUT_SIGNAL_NAME is not supported on Windows."); +#else + std::string const& signalName = val; + Signal s; + if (signalName == "SIGINT"_s) { + s.Number = SIGINT; + } else if (signalName == "SIGQUIT"_s) { + s.Number = SIGQUIT; + } else if (signalName == "SIGTERM"_s) { + s.Number = SIGTERM; + } else if (signalName == "SIGUSR1"_s) { + s.Number = SIGUSR1; + } else if (signalName == "SIGUSR2"_s) { + s.Number = SIGUSR2; + } + if (s.Number) { + s.Name = signalName; + rt.TimeoutSignal = std::move(s); + } else { + rt.AppendError(cmStrCat("TIMEOUT_SIGNAL_NAME \"", signalName, + "\" not supported on this platform.")); + } +#endif + } else if (key == "TIMEOUT_SIGNAL_GRACE_PERIOD"_s) { +#ifdef _WIN32 + rt.AppendError( + "TIMEOUT_SIGNAL_GRACE_PERIOD is not supported on Windows."); +#else + std::string const& gracePeriod = val; + static cmDuration minGracePeriod{ 0 }; + static cmDuration maxGracePeriod{ 60 }; + cmDuration gp = cmDuration(atof(gracePeriod.c_str())); + if (gp <= minGracePeriod) { + rt.AppendError(cmStrCat("TIMEOUT_SIGNAL_GRACE_PERIOD \"", + gracePeriod, "\" is not greater than \"", + minGracePeriod.count(), "\" seconds.")); + } else if (gp > maxGracePeriod) { + rt.AppendError(cmStrCat("TIMEOUT_SIGNAL_GRACE_PERIOD \"", + gracePeriod, + "\" is not less than the maximum of \"", + maxGracePeriod.count(), "\" seconds.")); + } else { + rt.TimeoutGracePeriod = gp; + } +#endif } else if (key == "COST"_s) { rt.Cost = static_cast(atof(val.c_str())); } else if (key == "REQUIRED_FILES"_s) { diff --git a/Source/CTest/cmCTestTestHandler.h b/Source/CTest/cmCTestTestHandler.h index b6bfde1..315a5b7 100644 --- a/Source/CTest/cmCTestTestHandler.h +++ b/Source/CTest/cmCTestTestHandler.h @@ -15,6 +15,7 @@ #include #include +#include #include "cmsys/RegularExpression.hxx" @@ -119,8 +120,16 @@ public: bool operator!=(const cmCTestTestResourceRequirement& other) const; }; + struct Signal + { + int Number = 0; + std::string Name; + }; + struct cmCTestTestProperties { + void AppendError(cm::string_view err); + cm::optional Error; std::string Name; std::string Directory; std::vector Args; @@ -144,6 +153,8 @@ public: int PreviousRuns = 0; bool RunSerial = false; cm::optional Timeout; + cm::optional TimeoutSignal; + cm::optional TimeoutGracePeriod; cmDuration AlternateTimeout; int Index = 0; // Requested number of process slots diff --git a/Source/CTest/cmProcess.cxx b/Source/CTest/cmProcess.cxx index 780d626..269b92c 100644 --- a/Source/CTest/cmProcess.cxx +++ b/Source/CTest/cmProcess.cxx @@ -13,6 +13,7 @@ #include "cmCTest.h" #include "cmCTestRunTest.h" +#include "cmCTestTestHandler.h" #include "cmGetPipes.h" #include "cmStringAlgorithms.h" #if defined(_WIN32) @@ -274,7 +275,29 @@ void cmProcess::OnTimeoutCB(uv_timer_t* timer) void cmProcess::OnTimeout() { + bool const wasExecuting = this->ProcessState == cmProcess::State::Executing; this->ProcessState = cmProcess::State::Expired; + + // If the test process is still executing normally, and we timed out because + // the test timeout was reached, send the custom timeout signal, if any. + if (wasExecuting && this->TimeoutReason_ == TimeoutReason::Normal) { + cmCTestTestHandler::cmCTestTestProperties* p = + this->Runner->GetTestProperties(); + if (p->TimeoutSignal) { + this->TerminationStyle = Termination::Custom; + uv_process_kill(this->Process, p->TimeoutSignal->Number); + if (p->TimeoutGracePeriod) { + this->Timeout = *p->TimeoutGracePeriod; + } else { + static const cmDuration defaultGracePeriod{ 1.0 }; + this->Timeout = defaultGracePeriod; + } + this->StartTimer(); + return; + } + } + + this->TerminationStyle = Termination::Forced; bool const was_still_reading = !this->ReadHandleClosed; if (!this->ReadHandleClosed) { this->ReadHandleClosed = true; diff --git a/Source/CTest/cmProcess.h b/Source/CTest/cmProcess.h index c80922d..dc755eb 100644 --- a/Source/CTest/cmProcess.h +++ b/Source/CTest/cmProcess.h @@ -85,6 +85,14 @@ public: return std::move(this->Runner); } + enum class Termination + { + Normal, + Custom, + Forced, + }; + Termination GetTerminationStyle() const { return this->TerminationStyle; } + private: cm::optional Timeout; TimeoutReason TimeoutReason_ = TimeoutReason::Normal; @@ -137,4 +145,5 @@ private: std::vector ProcessArgs; int Id; int64_t ExitValue; + Termination TerminationStyle = Termination::Normal; }; diff --git a/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake b/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake index bda260a..c90d543 100644 --- a/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake +++ b/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake @@ -479,3 +479,32 @@ set_tests_properties(test5 PROPERTIES SKIP_REGULAR_EXPRESSION \"please skip\") run_cmake_command(output-junit ${CMAKE_CTEST_COMMAND} --output-junit "${RunCMake_TEST_BINARY_DIR}/junit.xml") endfunction() run_output_junit() + +if(WIN32) + block() + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/TimeoutSignalWindows) + 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" " +add_test(test1 \"${CMAKE_COMMAND}\" -E true) +set_tests_properties(test1 PROPERTIES TIMEOUT_SIGNAL_NAME SIGUSR1) +set_tests_properties(test1 PROPERTIES TIMEOUT_SIGNAL_GRACE_PERIOD 1) +") + run_cmake_command(TimeoutSignalWindows ${CMAKE_CTEST_COMMAND}) + endblock() +else() + block() + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/TimeoutSignalBad) + 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" " +add_test(test1 \"${CMAKE_COMMAND}\" -E true) +set_tests_properties(test1 PROPERTIES TIMEOUT_SIGNAL_NAME NOTASIG) +set_tests_properties(test1 PROPERTIES TIMEOUT_SIGNAL_GRACE_PERIOD 0) +set_tests_properties(test1 PROPERTIES TIMEOUT_SIGNAL_GRACE_PERIOD 1000) +") + run_cmake_command(TimeoutSignalBad ${CMAKE_CTEST_COMMAND}) + endblock() +endif() diff --git a/Tests/RunCMake/CTestCommandLine/TimeoutSignalBad-result.txt b/Tests/RunCMake/CTestCommandLine/TimeoutSignalBad-result.txt new file mode 100644 index 0000000..d197c91 --- /dev/null +++ b/Tests/RunCMake/CTestCommandLine/TimeoutSignalBad-result.txt @@ -0,0 +1 @@ +[^0] diff --git a/Tests/RunCMake/CTestCommandLine/TimeoutSignalBad-stderr.txt b/Tests/RunCMake/CTestCommandLine/TimeoutSignalBad-stderr.txt new file mode 100644 index 0000000..ba4235d --- /dev/null +++ b/Tests/RunCMake/CTestCommandLine/TimeoutSignalBad-stderr.txt @@ -0,0 +1 @@ +Errors while running CTest diff --git a/Tests/RunCMake/CTestCommandLine/TimeoutSignalBad-stdout.txt b/Tests/RunCMake/CTestCommandLine/TimeoutSignalBad-stdout.txt new file mode 100644 index 0000000..e848dcf --- /dev/null +++ b/Tests/RunCMake/CTestCommandLine/TimeoutSignalBad-stdout.txt @@ -0,0 +1,5 @@ + Start 1: test1 +TIMEOUT_SIGNAL_NAME "NOTASIG" not supported on this platform\. +TIMEOUT_SIGNAL_GRACE_PERIOD "0" is not greater than "0" seconds\. +TIMEOUT_SIGNAL_GRACE_PERIOD "1000" is not less than the maximum of "60" seconds\. +1/1 Test #1: test1 ............................\*\*\*Not Run +[0-9.]+ sec diff --git a/Tests/RunCMake/CTestCommandLine/TimeoutSignalWindows-result.txt b/Tests/RunCMake/CTestCommandLine/TimeoutSignalWindows-result.txt new file mode 100644 index 0000000..d197c91 --- /dev/null +++ b/Tests/RunCMake/CTestCommandLine/TimeoutSignalWindows-result.txt @@ -0,0 +1 @@ +[^0] diff --git a/Tests/RunCMake/CTestCommandLine/TimeoutSignalWindows-stderr.txt b/Tests/RunCMake/CTestCommandLine/TimeoutSignalWindows-stderr.txt new file mode 100644 index 0000000..ba4235d --- /dev/null +++ b/Tests/RunCMake/CTestCommandLine/TimeoutSignalWindows-stderr.txt @@ -0,0 +1 @@ +Errors while running CTest diff --git a/Tests/RunCMake/CTestCommandLine/TimeoutSignalWindows-stdout.txt b/Tests/RunCMake/CTestCommandLine/TimeoutSignalWindows-stdout.txt new file mode 100644 index 0000000..295ace5 --- /dev/null +++ b/Tests/RunCMake/CTestCommandLine/TimeoutSignalWindows-stdout.txt @@ -0,0 +1,4 @@ + Start 1: test1 +TIMEOUT_SIGNAL_NAME is not supported on Windows\. +TIMEOUT_SIGNAL_GRACE_PERIOD is not supported on Windows\. +1/1 Test #1: test1 ............................\*\*\*Not Run +[0-9.]+ sec diff --git a/Tests/RunCMake/CTestTimeout/CMakeLists.txt.in b/Tests/RunCMake/CTestTimeout/CMakeLists.txt.in index ee3323c..1045d1a 100644 --- a/Tests/RunCMake/CTestTimeout/CMakeLists.txt.in +++ b/Tests/RunCMake/CTestTimeout/CMakeLists.txt.in @@ -2,6 +2,10 @@ cmake_minimum_required(VERSION 3.16) project(CTestTest@CASE_NAME@ C) include(CTest) +if(CMAKE_C_COMPILER_ID STREQUAL "SunPro" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 5.14) + set(CMAKE_C_STANDARD 99) +endif() + add_executable(TestTimeout TestTimeout.c) if(NOT DEFINED TIMEOUT) diff --git a/Tests/RunCMake/CTestTimeout/RunCMakeTest.cmake b/Tests/RunCMake/CTestTimeout/RunCMakeTest.cmake index a4080e3..6caeef1 100644 --- a/Tests/RunCMake/CTestTimeout/RunCMakeTest.cmake +++ b/Tests/RunCMake/CTestTimeout/RunCMakeTest.cmake @@ -13,12 +13,68 @@ endfunction() run_ctest_timeout(Basic) -if(UNIX) +if(WIN32) + string(CONCAT CASE_CMAKELISTS_SUFFIX_CODE [[ + set_tests_properties(TestTimeout PROPERTIES + TIMEOUT_SIGNAL_NAME SIGUSR1 + TIMEOUT_SIGNAL_GRACE_PERIOD 1.2 + ) +]]) + run_ctest_timeout(SignalWindows) + unset(CASE_CMAKELISTS_SUFFIX_CODE) + +else() string(CONCAT CASE_CMAKELISTS_SUFFIX_CODE [[ target_compile_definitions(TestTimeout PRIVATE FORK) ]]) run_ctest_timeout(Fork) unset(CASE_CMAKELISTS_SUFFIX_CODE) + + string(CONCAT CASE_CMAKELISTS_SUFFIX_CODE [[ + target_compile_definitions(TestTimeout PRIVATE SIGNAL) + set_tests_properties(TestTimeout PROPERTIES + TIMEOUT_SIGNAL_NAME SIGUSR1 + TIMEOUT_SIGNAL_GRACE_PERIOD 1.2 + ) +]]) + run_ctest_timeout(Signal) + unset(CASE_CMAKELISTS_SUFFIX_CODE) + + string(CONCAT CASE_CMAKELISTS_SUFFIX_CODE [[ + target_compile_definitions(TestTimeout PRIVATE SIGNAL SIGNAL_IGNORE=1) + set_tests_properties(TestTimeout PROPERTIES + TIMEOUT_SIGNAL_NAME SIGUSR1 + # Use default TIMEOUT_SIGNAL_GRACE_PERIOD of 1. + ) +]]) + run_ctest_timeout(SignalIgnore) + unset(CASE_CMAKELISTS_SUFFIX_CODE) + + string(CONCAT CASE_CMAKELISTS_SUFFIX_CODE [[ + set_tests_properties(TestTimeout PROPERTIES + TIMEOUT_SIGNAL_NAME NOTASIG + ) +]]) + run_ctest_timeout(SignalUnknown) + unset(CASE_CMAKELISTS_SUFFIX_CODE) + + string(CONCAT CASE_CMAKELISTS_SUFFIX_CODE [[ + set_tests_properties(TestTimeout PROPERTIES + TIMEOUT_SIGNAL_NAME SIGUSR1 + TIMEOUT_SIGNAL_GRACE_PERIOD -1 + ) +]]) + run_ctest_timeout(SignalGraceLow) + unset(CASE_CMAKELISTS_SUFFIX_CODE) + + string(CONCAT CASE_CMAKELISTS_SUFFIX_CODE [[ + set_tests_properties(TestTimeout PROPERTIES + TIMEOUT_SIGNAL_NAME SIGUSR1 + TIMEOUT_SIGNAL_GRACE_PERIOD 1000 + ) +]]) + run_ctest_timeout(SignalGraceHigh) + unset(CASE_CMAKELISTS_SUFFIX_CODE) endif() block() diff --git a/Tests/RunCMake/CTestTimeout/Signal-check.cmake b/Tests/RunCMake/CTestTimeout/Signal-check.cmake new file mode 100644 index 0000000..bee5ac7 --- /dev/null +++ b/Tests/RunCMake/CTestTimeout/Signal-check.cmake @@ -0,0 +1,11 @@ +file(GLOB test_xml "${RunCMake_TEST_BINARY_DIR}/Testing/*/Test.xml") +if(NOT test_xml) + set(RunCMake_TEST_FAILED "Test.xml not found.") + return() +endif() + +file(READ "${test_xml}" test_xml_content) +if(NOT test_xml_content MATCHES "SIGUSR1") + set(RunCMake_TEST_FAILED "Test output does not mention SIGUSR1.") + return() +endif() diff --git a/Tests/RunCMake/CTestTimeout/Signal-stdout.txt b/Tests/RunCMake/CTestTimeout/Signal-stdout.txt new file mode 100644 index 0000000..6cb5d0c --- /dev/null +++ b/Tests/RunCMake/CTestTimeout/Signal-stdout.txt @@ -0,0 +1,6 @@ +Test project [^ +]*/Tests/RunCMake/CTestTimeout/Signal-build + Start 1: TestTimeout +1/1 Test #1: TestTimeout ......................\*\*\*Timeout \(SIGUSR1\) +[1-9][0-9.]* sec ++ +0% tests passed, 1 tests failed out of 1 diff --git a/Tests/RunCMake/CTestTimeout/SignalGraceHigh-check.cmake b/Tests/RunCMake/CTestTimeout/SignalGraceHigh-check.cmake new file mode 100644 index 0000000..96dabc8 --- /dev/null +++ b/Tests/RunCMake/CTestTimeout/SignalGraceHigh-check.cmake @@ -0,0 +1,11 @@ +file(GLOB test_xml "${RunCMake_TEST_BINARY_DIR}/Testing/*/Test.xml") +if(NOT test_xml) + set(RunCMake_TEST_FAILED "Test.xml not found.") + return() +endif() + +file(READ "${test_xml}" test_xml_content) +if(NOT test_xml_content MATCHES "TIMEOUT_SIGNAL_GRACE_PERIOD \"1000\" is not less than the maximum of \"60\" seconds\\.") + set(RunCMake_TEST_FAILED "Test output does not have expected error message.") + return() +endif() diff --git a/Tests/RunCMake/CTestTimeout/SignalGraceHigh-stdout.txt b/Tests/RunCMake/CTestTimeout/SignalGraceHigh-stdout.txt new file mode 100644 index 0000000..fc965c0 --- /dev/null +++ b/Tests/RunCMake/CTestTimeout/SignalGraceHigh-stdout.txt @@ -0,0 +1,3 @@ + Start 1: TestTimeout +TIMEOUT_SIGNAL_GRACE_PERIOD "1000" is not less than the maximum of "60" seconds\. +1/1 Test #1: TestTimeout ......................\*\*\*Not Run +[0-9.]+ sec diff --git a/Tests/RunCMake/CTestTimeout/SignalGraceLow-check.cmake b/Tests/RunCMake/CTestTimeout/SignalGraceLow-check.cmake new file mode 100644 index 0000000..c4b01e5 --- /dev/null +++ b/Tests/RunCMake/CTestTimeout/SignalGraceLow-check.cmake @@ -0,0 +1,11 @@ +file(GLOB test_xml "${RunCMake_TEST_BINARY_DIR}/Testing/*/Test.xml") +if(NOT test_xml) + set(RunCMake_TEST_FAILED "Test.xml not found.") + return() +endif() + +file(READ "${test_xml}" test_xml_content) +if(NOT test_xml_content MATCHES "TIMEOUT_SIGNAL_GRACE_PERIOD \"-1\" is not greater than \"0\" seconds\\.") + set(RunCMake_TEST_FAILED "Test output does not have expected error message.") + return() +endif() diff --git a/Tests/RunCMake/CTestTimeout/SignalGraceLow-stdout.txt b/Tests/RunCMake/CTestTimeout/SignalGraceLow-stdout.txt new file mode 100644 index 0000000..097cab6 --- /dev/null +++ b/Tests/RunCMake/CTestTimeout/SignalGraceLow-stdout.txt @@ -0,0 +1,3 @@ + Start 1: TestTimeout +TIMEOUT_SIGNAL_GRACE_PERIOD "-1" is not greater than "0" seconds. +1/1 Test #1: TestTimeout ......................\*\*\*Not Run +[0-9.]+ sec diff --git a/Tests/RunCMake/CTestTimeout/SignalIgnore-check.cmake b/Tests/RunCMake/CTestTimeout/SignalIgnore-check.cmake new file mode 100644 index 0000000..e408764 --- /dev/null +++ b/Tests/RunCMake/CTestTimeout/SignalIgnore-check.cmake @@ -0,0 +1,11 @@ +file(GLOB test_xml "${RunCMake_TEST_BINARY_DIR}/Testing/*/Test.xml") +if(NOT test_xml) + set(RunCMake_TEST_FAILED "Test.xml not found.") + return() +endif() + +file(READ "${test_xml}" test_xml_content) +if(NOT test_xml_content MATCHES "EINTR") + set(RunCMake_TEST_FAILED "Test output does not mention EINTR.") + return() +endif() diff --git a/Tests/RunCMake/CTestTimeout/SignalIgnore-stdout.txt b/Tests/RunCMake/CTestTimeout/SignalIgnore-stdout.txt new file mode 100644 index 0000000..de97df4 --- /dev/null +++ b/Tests/RunCMake/CTestTimeout/SignalIgnore-stdout.txt @@ -0,0 +1,6 @@ +Test project [^ +]*/Tests/RunCMake/CTestTimeout/SignalIgnore-build + Start 1: TestTimeout +1/1 Test #1: TestTimeout ......................\*\*\*Timeout +[1-9][0-9.]* sec ++ +0% tests passed, 1 tests failed out of 1 diff --git a/Tests/RunCMake/CTestTimeout/SignalUnknown-check.cmake b/Tests/RunCMake/CTestTimeout/SignalUnknown-check.cmake new file mode 100644 index 0000000..d024934 --- /dev/null +++ b/Tests/RunCMake/CTestTimeout/SignalUnknown-check.cmake @@ -0,0 +1,11 @@ +file(GLOB test_xml "${RunCMake_TEST_BINARY_DIR}/Testing/*/Test.xml") +if(NOT test_xml) + set(RunCMake_TEST_FAILED "Test.xml not found.") + return() +endif() + +file(READ "${test_xml}" test_xml_content) +if(NOT test_xml_content MATCHES "TIMEOUT_SIGNAL_NAME \"NOTASIG\" not supported on this platform\\.") + set(RunCMake_TEST_FAILED "Test output does not have expected error message.") + return() +endif() diff --git a/Tests/RunCMake/CTestTimeout/SignalUnknown-stdout.txt b/Tests/RunCMake/CTestTimeout/SignalUnknown-stdout.txt new file mode 100644 index 0000000..33a133f --- /dev/null +++ b/Tests/RunCMake/CTestTimeout/SignalUnknown-stdout.txt @@ -0,0 +1,3 @@ + Start 1: TestTimeout +TIMEOUT_SIGNAL_NAME "NOTASIG" not supported on this platform\. +1/1 Test #1: TestTimeout ......................\*\*\*Not Run +[0-9.]+ sec diff --git a/Tests/RunCMake/CTestTimeout/SignalWindows-check.cmake b/Tests/RunCMake/CTestTimeout/SignalWindows-check.cmake new file mode 100644 index 0000000..1bf7f8a --- /dev/null +++ b/Tests/RunCMake/CTestTimeout/SignalWindows-check.cmake @@ -0,0 +1,11 @@ +file(GLOB test_xml "${RunCMake_TEST_BINARY_DIR}/Testing/*/Test.xml") +if(NOT test_xml) + set(RunCMake_TEST_FAILED "Test.xml not found.") + return() +endif() + +file(READ "${test_xml}" test_xml_content) +if(NOT test_xml_content MATCHES "TIMEOUT_SIGNAL_NAME is not supported on Windows\\.") + set(RunCMake_TEST_FAILED "Test output does not have expected error message.") + return() +endif() diff --git a/Tests/RunCMake/CTestTimeout/SignalWindows-stdout.txt b/Tests/RunCMake/CTestTimeout/SignalWindows-stdout.txt new file mode 100644 index 0000000..6911015 --- /dev/null +++ b/Tests/RunCMake/CTestTimeout/SignalWindows-stdout.txt @@ -0,0 +1,4 @@ + Start 1: TestTimeout +TIMEOUT_SIGNAL_GRACE_PERIOD is not supported on Windows\. +TIMEOUT_SIGNAL_NAME is not supported on Windows\. +1/1 Test #1: TestTimeout ......................\*\*\*Not Run +[0-9.]+ sec diff --git a/Tests/RunCMake/CTestTimeout/TestTimeout.c b/Tests/RunCMake/CTestTimeout/TestTimeout.c index 0d534fc..425548c 100644 --- a/Tests/RunCMake/CTestTimeout/TestTimeout.c +++ b/Tests/RunCMake/CTestTimeout/TestTimeout.c @@ -1,3 +1,8 @@ +#if !defined(_WIN32) && !defined(__APPLE__) && !defined(__OpenBSD__) +/* NOLINTNEXTLINE(bugprone-reserved-identifier) */ +# define _XOPEN_SOURCE 600 +#endif + #if defined(_WIN32) # include #else @@ -7,6 +12,19 @@ #include +#ifdef SIGNAL +# include +# include +# include + +static unsigned int signal_count; +static void signal_handler(int signum) +{ + (void)signum; + ++signal_count; +} +#endif + int main(void) { #ifdef FORK @@ -16,10 +34,39 @@ int main(void) } #endif +#ifdef SIGNAL + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = signal_handler; + while ((sigaction(SIGUSR1, &sa, NULL) < 0) && (errno == EINTR)) + ; +#endif + #if defined(_WIN32) Sleep((TIMEOUT + 4) * 1000); +#elif defined(SIGNAL_IGNORE) +# if defined(__CYGWIN__) || defined(__sun__) +# define ERRNO_IS_EINTR (errno == EINTR || errno == 0) +# else +# define ERRNO_IS_EINTR (errno == EINTR) +# endif + { + unsigned int timeLeft = (TIMEOUT + 4 + SIGNAL_IGNORE); + while ((timeLeft = sleep(timeLeft), timeLeft > 0 && ERRNO_IS_EINTR)) { + printf("EINTR: timeLeft=%u\n", timeLeft); + fflush(stdout); + } + } #else sleep((TIMEOUT + 4)); #endif + +#ifdef SIGNAL + if (signal_count > 0) { + printf("SIGUSR1: count=%u\n", signal_count); + fflush(stdout); + } +#endif + return 0; } diff --git a/Utilities/IWYU/mapping.imp b/Utilities/IWYU/mapping.imp index fba4c60..0f03c33 100644 --- a/Utilities/IWYU/mapping.imp +++ b/Utilities/IWYU/mapping.imp @@ -102,6 +102,7 @@ { symbol: [ "std::enable_if > >::type", private, "\"cmConfigure.h\"", public ] }, { symbol: [ "__gnu_cxx::__enable_if::__type", private, "\"cmConfigure.h\"", public ] }, { symbol: [ "std::remove_reference, std::allocator > &>::type", private, "\"cmConfigure.h\"", public ] }, + { symbol: [ "std::remove_reference::type", private, "\"cmConfigure.h\"", public ] }, { symbol: [ "std::remove_reference::type", private, "\"cmConfigure.h\"", public ] }, { symbol: [ "std::remove_reference::type", private, "\"cmConfigure.h\"", public ] }, -- cgit v0.12