summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrad King <brad.king@kitware.com>2023-06-05 10:33:25 (GMT)
committerKitware Robot <kwrobot@kitware.com>2023-06-05 10:33:36 (GMT)
commit7838ae4fd844ca2bf7abb3541879098ace18a947 (patch)
tree07a258c165073bce3cf214877fc68430f4791a00
parent8f8a5e56f46ab211694986f4349d0c0f78a6c6c5 (diff)
parent54c5654f7d530ea363b3fcc02f2176d79342c07b (diff)
downloadCMake-7838ae4fd844ca2bf7abb3541879098ace18a947.zip
CMake-7838ae4fd844ca2bf7abb3541879098ace18a947.tar.gz
CMake-7838ae4fd844ca2bf7abb3541879098ace18a947.tar.bz2
Merge topic 'ctest-timeout-signal'
54c5654f7d ctest: Optionally terminate tests with a custom signal on timeout e38c05688e CTest/cmProcess: Adopt field tracking reason for the process timeout 25c1468314 cmCTestTestHandler: Remove outdated comment 41e8507ab7 IWYU: Add mapping for bits/chrono.h to chrono Acked-by: Kitware Robot <kwrobot@kitware.com> Merge-request: !8472
-rw-r--r--Help/manual/cmake-properties.7.rst2
-rw-r--r--Help/prop_test/TIMEOUT.rst3
-rw-r--r--Help/prop_test/TIMEOUT_AFTER_MATCH.rst2
-rw-r--r--Help/prop_test/TIMEOUT_SIGNAL_GRACE_PERIOD.rst14
-rw-r--r--Help/prop_test/TIMEOUT_SIGNAL_NAME.rst41
-rw-r--r--Help/release/dev/ctest-timeout-signal.rst7
-rw-r--r--Source/CTest/cmCTestMultiProcessHandler.cxx7
-rw-r--r--Source/CTest/cmCTestRunTest.cxx36
-rw-r--r--Source/CTest/cmCTestRunTest.h11
-rw-r--r--Source/CTest/cmCTestTestHandler.cxx61
-rw-r--r--Source/CTest/cmCTestTestHandler.h15
-rw-r--r--Source/CTest/cmProcess.cxx23
-rw-r--r--Source/CTest/cmProcess.h18
-rw-r--r--Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake29
-rw-r--r--Tests/RunCMake/CTestCommandLine/TimeoutSignalBad-result.txt1
-rw-r--r--Tests/RunCMake/CTestCommandLine/TimeoutSignalBad-stderr.txt1
-rw-r--r--Tests/RunCMake/CTestCommandLine/TimeoutSignalBad-stdout.txt5
-rw-r--r--Tests/RunCMake/CTestCommandLine/TimeoutSignalWindows-result.txt1
-rw-r--r--Tests/RunCMake/CTestCommandLine/TimeoutSignalWindows-stderr.txt1
-rw-r--r--Tests/RunCMake/CTestCommandLine/TimeoutSignalWindows-stdout.txt4
-rw-r--r--Tests/RunCMake/CTestTimeout/CMakeLists.txt.in4
-rw-r--r--Tests/RunCMake/CTestTimeout/RunCMakeTest.cmake58
-rw-r--r--Tests/RunCMake/CTestTimeout/Signal-check.cmake11
-rw-r--r--Tests/RunCMake/CTestTimeout/Signal-stdout.txt6
-rw-r--r--Tests/RunCMake/CTestTimeout/SignalGraceHigh-check.cmake11
-rw-r--r--Tests/RunCMake/CTestTimeout/SignalGraceHigh-stdout.txt3
-rw-r--r--Tests/RunCMake/CTestTimeout/SignalGraceLow-check.cmake11
-rw-r--r--Tests/RunCMake/CTestTimeout/SignalGraceLow-stdout.txt3
-rw-r--r--Tests/RunCMake/CTestTimeout/SignalIgnore-check.cmake11
-rw-r--r--Tests/RunCMake/CTestTimeout/SignalIgnore-stdout.txt6
-rw-r--r--Tests/RunCMake/CTestTimeout/SignalUnknown-check.cmake11
-rw-r--r--Tests/RunCMake/CTestTimeout/SignalUnknown-stdout.txt3
-rw-r--r--Tests/RunCMake/CTestTimeout/SignalWindows-check.cmake11
-rw-r--r--Tests/RunCMake/CTestTimeout/SignalWindows-stdout.txt4
-rw-r--r--Tests/RunCMake/CTestTimeout/TestTimeout.c47
-rw-r--r--Utilities/IWYU/mapping.imp2
36 files changed, 466 insertions, 18 deletions
diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst
index e17d472..4df0547 100644
--- a/Help/manual/cmake-properties.7.rst
+++ b/Help/manual/cmake-properties.7.rst
@@ -516,6 +516,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/cmCTestMultiProcessHandler.cxx b/Source/CTest/cmCTestMultiProcessHandler.cxx
index 44eccb2..7d22a87 100644
--- a/Source/CTest/cmCTestMultiProcessHandler.cxx
+++ b/Source/CTest/cmCTestMultiProcessHandler.cxx
@@ -634,8 +634,9 @@ void cmCTestMultiProcessHandler::FinishTestProcess(
int test = runner->GetIndex();
auto* properties = runner->GetTestProperties();
- bool testResult = runner->EndTest(this->Completed, this->Total, started);
- if (runner->TimedOutForStopTime()) {
+ cmCTestRunTest::EndTestResult testResult =
+ runner->EndTest(this->Completed, this->Total, started);
+ if (testResult.StopTimePassed) {
this->SetStopTimePassed();
}
if (started) {
@@ -646,7 +647,7 @@ void cmCTestMultiProcessHandler::FinishTestProcess(
}
}
- if (testResult) {
+ if (testResult.Passed) {
this->Passed->push_back(properties->Name);
} else if (!properties->Disabled) {
this->Failed->push_back(properties->Name);
diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx
index cd2b230..9b62183 100644
--- a/Source/CTest/cmCTestRunTest.cxx
+++ b/Source/CTest/cmCTestRunTest.cxx
@@ -95,16 +95,15 @@ void cmCTestRunTest::CheckOutput(std::string const& line)
}
}
-bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started)
+cmCTestRunTest::EndTestResult cmCTestRunTest::EndTest(size_t completed,
+ size_t total,
+ bool started)
{
this->WriteLogOutputTop(completed, total);
std::string reason;
bool passed = true;
cmProcess::State res =
started ? this->TestProcess->GetProcessStatus() : cmProcess::State::Error;
- if (res != cmProcess::State::Expired) {
- this->TimeoutIsForStopTime = false;
- }
std::int64_t retVal = this->TestProcess->GetExitValue();
bool forceFail = false;
bool forceSkip = false;
@@ -182,6 +181,11 @@ bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started)
}
} 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();
@@ -344,8 +348,15 @@ bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started)
if (!this->NeedsToRepeat()) {
this->TestHandler->TestResults.push_back(this->TestResult);
}
+ cmCTestRunTest::EndTestResult testResult;
+ testResult.Passed = passed || skipped;
+ if (res == cmProcess::State::Expired &&
+ this->TestProcess->GetTimeoutReason() ==
+ cmProcess::TimeoutReason::StopTime) {
+ testResult.StopTimePassed = true;
+ }
this->TestProcess.reset();
- return passed || skipped;
+ return testResult;
}
bool cmCTestRunTest::StartAgain(std::unique_ptr<cmCTestRunTest> runner,
@@ -534,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";
@@ -772,8 +796,8 @@ bool cmCTestRunTest::ForkProcess()
timeRemaining = cmDuration::zero();
}
if (!timeout || timeRemaining < *timeout) {
- this->TimeoutIsForStopTime = true;
timeout = timeRemaining;
+ this->TestProcess->SetTimeoutReason(cmProcess::TimeoutReason::StopTime);
}
if (timeout) {
diff --git a/Source/CTest/cmCTestRunTest.h b/Source/CTest/cmCTestRunTest.h
index 6a507f4..fed7296 100644
--- a/Source/CTest/cmCTestRunTest.h
+++ b/Source/CTest/cmCTestRunTest.h
@@ -71,10 +71,16 @@ public:
std::string const& output,
std::string const& detail);
+ struct EndTestResult
+ {
+ bool Passed = false;
+ bool StopTimePassed = false;
+ };
+
// launch the test process, return whether it started correctly
bool StartTest(size_t completed, size_t total);
// capture and report the test results
- bool EndTest(size_t completed, size_t total, bool started);
+ EndTestResult EndTest(size_t completed, size_t total, bool started);
// Called by ctest -N to log the command string
void ComputeArguments();
@@ -90,8 +96,6 @@ public:
void FinalizeTest(bool started = true);
- bool TimedOutForStopTime() const { return this->TimeoutIsForStopTime; }
-
void SetUseAllocatedResources(bool use)
{
this->UseAllocatedResources = use;
@@ -120,7 +124,6 @@ private:
std::string GetTestPrefix(size_t completed, size_t total) const;
cmCTestTestHandler::cmCTestTestProperties* TestProperties;
- bool TimeoutIsForStopTime = false;
// Pointer back to the "parent"; the handler that invoked this test run
cmCTestTestHandler* TestHandler;
cmCTest* CTest;
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 <sstream>
#include <utility>
+#ifndef _WIN32
+# include <csignal>
+#endif
+
#include <cm/memory>
#include <cm/string_view>
#include <cmext/algorithm>
@@ -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<std::string>& 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<float>(atof(val.c_str()));
} else if (key == "REQUIRED_FILES"_s) {
diff --git a/Source/CTest/cmCTestTestHandler.h b/Source/CTest/cmCTestTestHandler.h
index b7c0faf..315a5b7 100644
--- a/Source/CTest/cmCTestTestHandler.h
+++ b/Source/CTest/cmCTestTestHandler.h
@@ -15,6 +15,7 @@
#include <vector>
#include <cm/optional>
+#include <cm/string_view>
#include "cmsys/RegularExpression.hxx"
@@ -119,12 +120,16 @@ public:
bool operator!=(const cmCTestTestResourceRequirement& other) const;
};
- // NOTE: This struct is Saved/Restored
- // in cmCTestTestHandler, if you add to this class
- // then you must add the new members to that code or
- // ctest -j N will break for that feature
+ struct Signal
+ {
+ int Number = 0;
+ std::string Name;
+ };
+
struct cmCTestTestProperties
{
+ void AppendError(cm::string_view err);
+ cm::optional<std::string> Error;
std::string Name;
std::string Directory;
std::vector<std::string> Args;
@@ -148,6 +153,8 @@ public:
int PreviousRuns = 0;
bool RunSerial = false;
cm::optional<cmDuration> Timeout;
+ cm::optional<Signal> TimeoutSignal;
+ cm::optional<cmDuration> 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 1578687..dc755eb 100644
--- a/Source/CTest/cmProcess.h
+++ b/Source/CTest/cmProcess.h
@@ -41,6 +41,14 @@ public:
// Return true if the process starts
bool StartProcess(uv_loop_t& loop, std::vector<size_t>* affinity);
+ enum class TimeoutReason
+ {
+ Normal,
+ StopTime,
+ };
+ void SetTimeoutReason(TimeoutReason r) { this->TimeoutReason_ = r; }
+ TimeoutReason GetTimeoutReason() const { return this->TimeoutReason_; }
+
enum class State
{
Starting,
@@ -77,8 +85,17 @@ public:
return std::move(this->Runner);
}
+ enum class Termination
+ {
+ Normal,
+ Custom,
+ Forced,
+ };
+ Termination GetTerminationStyle() const { return this->TerminationStyle; }
+
private:
cm::optional<cmDuration> Timeout;
+ TimeoutReason TimeoutReason_ = TimeoutReason::Normal;
std::chrono::steady_clock::time_point StartTime;
cmDuration TotalTime;
bool ReadHandleClosed = false;
@@ -128,4 +145,5 @@ private:
std::vector<const char*> 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 <windows.h>
#else
@@ -7,6 +12,19 @@
#include <stdio.h>
+#ifdef SIGNAL
+# include <errno.h>
+# include <signal.h>
+# include <string.h>
+
+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 6c12ada..0f03c33 100644
--- a/Utilities/IWYU/mapping.imp
+++ b/Utilities/IWYU/mapping.imp
@@ -22,6 +22,7 @@
# HACK: check whether this can be removed with next iwyu release.
{ include: [ "<bits/cxxabi_forced.h>", private, "<ctime>", public ] },
+ { include: [ "<bits/chrono.h>", private, "<chrono>", public ] },
{ include: [ "<bits/exception.h>", private, "<exception>", public ] },
{ include: [ "<bits/shared_ptr.h>", private, "<memory>", public ] },
{ include: [ "<bits/std_function.h>", private, "<functional>", public ] },
@@ -101,6 +102,7 @@
{ symbol: [ "std::enable_if<true, std::chrono::duration<long, std::ratio<1, 1000> > >::type", private, "\"cmConfigure.h\"", public ] },
{ symbol: [ "__gnu_cxx::__enable_if<true, bool>::__type", private, "\"cmConfigure.h\"", public ] },
{ symbol: [ "std::remove_reference<std::basic_string<char, std::char_traits<char>, std::allocator<char> > &>::type", private, "\"cmConfigure.h\"", public ] },
+ { symbol: [ "std::remove_reference<cmCTestTestHandler::Signal &>::type", private, "\"cmConfigure.h\"", public ] },
{ symbol: [ "std::remove_reference<Defer &>::type", private, "\"cmConfigure.h\"", public ] },
{ symbol: [ "std::remove_reference<dap::StoppedEvent &>::type", private, "\"cmConfigure.h\"", public ] },