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
This commit is contained in:
Brad King
2023-05-11 13:32:15 -04:00
parent e38c05688e
commit 54c5654f7d
34 changed files with 433 additions and 1 deletions

View File

@@ -522,6 +522,8 @@ Properties on Tests
/prop_test/SKIP_RETURN_CODE /prop_test/SKIP_RETURN_CODE
/prop_test/TIMEOUT /prop_test/TIMEOUT
/prop_test/TIMEOUT_AFTER_MATCH /prop_test/TIMEOUT_AFTER_MATCH
/prop_test/TIMEOUT_SIGNAL_GRACE_PERIOD
/prop_test/TIMEOUT_SIGNAL_NAME
/prop_test/WILL_FAIL /prop_test/WILL_FAIL
/prop_test/WORKING_DIRECTORY /prop_test/WORKING_DIRECTORY

View File

@@ -10,3 +10,6 @@ setting takes precedence over :variable:`CTEST_TEST_TIMEOUT`.
An explicit ``0`` value means the test has no timeout, except as An explicit ``0`` value means the test has no timeout, except as
necessary to honor :option:`ctest --stop-time`. necessary to honor :option:`ctest --stop-time`.
See also :prop_test:`TIMEOUT_AFTER_MATCH` and
:prop_test:`TIMEOUT_SIGNAL_NAME`.

View File

@@ -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``. :prop_test:`RESOURCE_LOCK` instead of ``TIMEOUT_AFTER_MATCH``.
This property should be used when only the test itself can determine This property should be used when only the test itself can determine
when its required resources are available. when its required resources are available.
See also :prop_test:`TIMEOUT_SIGNAL_NAME`.

View File

@@ -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.

View File

@@ -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`.

View File

@@ -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.

View File

@@ -181,6 +181,11 @@ cmCTestRunTest::EndTestResult cmCTestRunTest::EndTest(size_t completed,
} }
} else if (res == cmProcess::State::Expired) { } else if (res == cmProcess::State::Expired) {
outputStream << "***Timeout "; outputStream << "***Timeout ";
if (this->TestProperties->TimeoutSignal &&
this->TestProcess->GetTerminationStyle() ==
cmProcess::Termination::Custom) {
outputStream << "(" << this->TestProperties->TimeoutSignal->Name << ") ";
}
this->TestResult.Status = cmCTestTestHandler::TIMEOUT; this->TestResult.Status = cmCTestTestHandler::TIMEOUT;
outputTestErrorsToConsole = outputTestErrorsToConsole =
this->CTest->GetOutputTestOutputOnTestFailure(); this->CTest->GetOutputTestOutputOnTestFailure();
@@ -540,6 +545,19 @@ bool cmCTestRunTest::StartTest(size_t completed, size_t total)
this->TestResult.Name = this->TestProperties->Name; this->TestResult.Name = this->TestProperties->Name;
this->TestResult.Path = this->TestProperties->Directory; 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 // Return immediately if test is disabled
if (this->TestProperties->Disabled) { if (this->TestProperties->Disabled) {
this->TestResult.CompletionStatus = "Disabled"; this->TestResult.CompletionStatus = "Disabled";

View File

@@ -17,6 +17,10 @@
#include <sstream> #include <sstream>
#include <utility> #include <utility>
#ifndef _WIN32
# include <csignal>
#endif
#include <cm/memory> #include <cm/memory>
#include <cm/string_view> #include <cm/string_view>
#include <cmext/algorithm> #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( bool cmCTestTestHandler::SetTestsProperties(
const std::vector<std::string>& args) const std::vector<std::string>& args)
{ {
@@ -2247,6 +2261,53 @@ bool cmCTestTestHandler::SetTestsProperties(
rt.FixturesRequired.insert(lval.begin(), lval.end()); rt.FixturesRequired.insert(lval.begin(), lval.end());
} else if (key == "TIMEOUT"_s) { } else if (key == "TIMEOUT"_s) {
rt.Timeout = cmDuration(atof(val.c_str())); 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) { } else if (key == "COST"_s) {
rt.Cost = static_cast<float>(atof(val.c_str())); rt.Cost = static_cast<float>(atof(val.c_str()));
} else if (key == "REQUIRED_FILES"_s) { } else if (key == "REQUIRED_FILES"_s) {

View File

@@ -15,6 +15,7 @@
#include <vector> #include <vector>
#include <cm/optional> #include <cm/optional>
#include <cm/string_view>
#include "cmsys/RegularExpression.hxx" #include "cmsys/RegularExpression.hxx"
@@ -119,8 +120,16 @@ public:
bool operator!=(const cmCTestTestResourceRequirement& other) const; bool operator!=(const cmCTestTestResourceRequirement& other) const;
}; };
struct Signal
{
int Number = 0;
std::string Name;
};
struct cmCTestTestProperties struct cmCTestTestProperties
{ {
void AppendError(cm::string_view err);
cm::optional<std::string> Error;
std::string Name; std::string Name;
std::string Directory; std::string Directory;
std::vector<std::string> Args; std::vector<std::string> Args;
@@ -144,6 +153,8 @@ public:
int PreviousRuns = 0; int PreviousRuns = 0;
bool RunSerial = false; bool RunSerial = false;
cm::optional<cmDuration> Timeout; cm::optional<cmDuration> Timeout;
cm::optional<Signal> TimeoutSignal;
cm::optional<cmDuration> TimeoutGracePeriod;
cmDuration AlternateTimeout; cmDuration AlternateTimeout;
int Index = 0; int Index = 0;
// Requested number of process slots // Requested number of process slots

View File

@@ -13,6 +13,7 @@
#include "cmCTest.h" #include "cmCTest.h"
#include "cmCTestRunTest.h" #include "cmCTestRunTest.h"
#include "cmCTestTestHandler.h"
#include "cmGetPipes.h" #include "cmGetPipes.h"
#include "cmStringAlgorithms.h" #include "cmStringAlgorithms.h"
#if defined(_WIN32) #if defined(_WIN32)
@@ -274,7 +275,29 @@ void cmProcess::OnTimeoutCB(uv_timer_t* timer)
void cmProcess::OnTimeout() void cmProcess::OnTimeout()
{ {
bool const wasExecuting = this->ProcessState == cmProcess::State::Executing;
this->ProcessState = cmProcess::State::Expired; 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; bool const was_still_reading = !this->ReadHandleClosed;
if (!this->ReadHandleClosed) { if (!this->ReadHandleClosed) {
this->ReadHandleClosed = true; this->ReadHandleClosed = true;

View File

@@ -85,6 +85,14 @@ public:
return std::move(this->Runner); return std::move(this->Runner);
} }
enum class Termination
{
Normal,
Custom,
Forced,
};
Termination GetTerminationStyle() const { return this->TerminationStyle; }
private: private:
cm::optional<cmDuration> Timeout; cm::optional<cmDuration> Timeout;
TimeoutReason TimeoutReason_ = TimeoutReason::Normal; TimeoutReason TimeoutReason_ = TimeoutReason::Normal;
@@ -137,4 +145,5 @@ private:
std::vector<const char*> ProcessArgs; std::vector<const char*> ProcessArgs;
int Id; int Id;
int64_t ExitValue; int64_t ExitValue;
Termination TerminationStyle = Termination::Normal;
}; };

View File

@@ -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") run_cmake_command(output-junit ${CMAKE_CTEST_COMMAND} --output-junit "${RunCMake_TEST_BINARY_DIR}/junit.xml")
endfunction() endfunction()
run_output_junit() 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()

View File

@@ -0,0 +1 @@
[^0]

View File

@@ -0,0 +1 @@
Errors while running CTest

View File

@@ -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

View File

@@ -0,0 +1 @@
[^0]

View File

@@ -0,0 +1 @@
Errors while running CTest

View File

@@ -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

View File

@@ -2,6 +2,10 @@ cmake_minimum_required(VERSION 3.16)
project(CTestTest@CASE_NAME@ C) project(CTestTest@CASE_NAME@ C)
include(CTest) 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) add_executable(TestTimeout TestTimeout.c)
if(NOT DEFINED TIMEOUT) if(NOT DEFINED TIMEOUT)

View File

@@ -13,12 +13,68 @@ endfunction()
run_ctest_timeout(Basic) 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 [[ string(CONCAT CASE_CMAKELISTS_SUFFIX_CODE [[
target_compile_definitions(TestTimeout PRIVATE FORK) target_compile_definitions(TestTimeout PRIVATE FORK)
]]) ]])
run_ctest_timeout(Fork) run_ctest_timeout(Fork)
unset(CASE_CMAKELISTS_SUFFIX_CODE) 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() endif()
block() block()

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -1,3 +1,8 @@
#if !defined(_WIN32) && !defined(__APPLE__) && !defined(__OpenBSD__)
/* NOLINTNEXTLINE(bugprone-reserved-identifier) */
# define _XOPEN_SOURCE 600
#endif
#if defined(_WIN32) #if defined(_WIN32)
# include <windows.h> # include <windows.h>
#else #else
@@ -7,6 +12,19 @@
#include <stdio.h> #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) int main(void)
{ {
#ifdef FORK #ifdef FORK
@@ -16,10 +34,39 @@ int main(void)
} }
#endif #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) #if defined(_WIN32)
Sleep((TIMEOUT + 4) * 1000); 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 #else
sleep((TIMEOUT + 4)); sleep((TIMEOUT + 4));
#endif #endif
#ifdef SIGNAL
if (signal_count > 0) {
printf("SIGUSR1: count=%u\n", signal_count);
fflush(stdout);
}
#endif
return 0; return 0;
} }

View File

@@ -102,6 +102,7 @@
{ symbol: [ "std::enable_if<true, std::chrono::duration<long, std::ratio<1, 1000> > >::type", private, "\"cmConfigure.h\"", public ] }, { 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: [ "__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<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<Defer &>::type", private, "\"cmConfigure.h\"", public ] },
{ symbol: [ "std::remove_reference<dap::StoppedEvent &>::type", private, "\"cmConfigure.h\"", public ] }, { symbol: [ "std::remove_reference<dap::StoppedEvent &>::type", private, "\"cmConfigure.h\"", public ] },