summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Source/CTest/cmCTestMultiProcessHandler.cxx173
-rw-r--r--Source/CTest/cmCTestMultiProcessHandler.h12
-rw-r--r--Source/CTest/cmCTestRunTest.cxx201
-rw-r--r--Source/CTest/cmCTestRunTest.h30
-rw-r--r--Source/CTest/cmProcess.cxx708
-rw-r--r--Source/CTest/cmProcess.h79
-rw-r--r--Source/cmCTest.cxx72
-rw-r--r--Source/cmCTest.h12
-rw-r--r--Utilities/cmlibuv/src/unix/signal.c7
9 files changed, 882 insertions, 412 deletions
diff --git a/Source/CTest/cmCTestMultiProcessHandler.cxx b/Source/CTest/cmCTestMultiProcessHandler.cxx
index ae07feb..53c47a2 100644
--- a/Source/CTest/cmCTestMultiProcessHandler.cxx
+++ b/Source/CTest/cmCTestMultiProcessHandler.cxx
@@ -9,10 +9,14 @@
#include "cmSystemTools.h"
#include "cmWorkingDirectory.h"
+#include "cm_uv.h"
+
#include "cmsys/FStream.hxx"
#include "cmsys/String.hxx"
#include "cmsys/SystemInformation.hxx"
+
#include <algorithm>
+#include <chrono>
#include <iomanip>
#include <list>
#include <math.h>
@@ -21,6 +25,43 @@
#include <stdlib.h>
#include <utility>
+#if defined(CMAKE_USE_SYSTEM_LIBUV) && !defined(_WIN32) && \
+ UV_VERSION_MAJOR == 1 && UV_VERSION_MINOR < 19
+#define CMAKE_UV_SIGNAL_HACK
+/*
+ libuv does not use SA_RESTART on its signal handler, but C++ streams
+ depend on it for reliable i/o operations. This RAII helper convinces
+ libuv to install its handler, and then revises the handler to add the
+ SA_RESTART flag. We use a distinct uv loop that never runs to avoid
+ ever really getting a callback. libuv may fill the hack loop's signal
+ pipe and then stop writing, but that won't break any real loops.
+ */
+class cmUVSignalHackRAII
+{
+ uv_loop_t HackLoop;
+ cm::uv_signal_ptr HackSignal;
+ static void HackCB(uv_signal_t*, int) {}
+public:
+ cmUVSignalHackRAII()
+ {
+ uv_loop_init(&this->HackLoop);
+ this->HackSignal.init(this->HackLoop);
+ this->HackSignal.start(HackCB, SIGCHLD);
+ struct sigaction hack_sa;
+ sigaction(SIGCHLD, NULL, &hack_sa);
+ if (!(hack_sa.sa_flags & SA_RESTART)) {
+ hack_sa.sa_flags |= SA_RESTART;
+ sigaction(SIGCHLD, &hack_sa, NULL);
+ }
+ }
+ ~cmUVSignalHackRAII()
+ {
+ this->HackSignal.stop();
+ uv_loop_close(&this->HackLoop);
+ }
+};
+#endif
+
class TestComparator
{
public:
@@ -95,24 +136,32 @@ void cmCTestMultiProcessHandler::RunTests()
if (this->HasCycles) {
return;
}
+#ifdef CMAKE_UV_SIGNAL_HACK
+ cmUVSignalHackRAII hackRAII;
+#endif
this->TestHandler->SetMaxIndex(this->FindMaxIndex());
+
+ uv_loop_init(&this->Loop);
this->StartNextTests();
- while (!this->Tests.empty()) {
- if (this->StopTimePassed) {
- return;
- }
- this->CheckOutput();
- this->StartNextTests();
- }
- // let all running tests finish
- while (this->CheckOutput()) {
- }
+ uv_run(&this->Loop, UV_RUN_DEFAULT);
+ uv_loop_close(&this->Loop);
+
this->MarkFinished();
this->UpdateCostData();
}
-void cmCTestMultiProcessHandler::StartTestProcess(int test)
+bool cmCTestMultiProcessHandler::StartTestProcess(int test)
{
+ std::chrono::system_clock::time_point stop_time = this->CTest->GetStopTime();
+ if (stop_time != std::chrono::system_clock::time_point() &&
+ stop_time <= std::chrono::system_clock::now()) {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "The stop time has been passed. "
+ "Stopping all tests."
+ << std::endl);
+ this->StopTimePassed = true;
+ return false;
+ }
+
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
"test " << test << "\n", this->Quiet);
this->TestRunningMap[test] = true; // mark the test as running
@@ -120,7 +169,7 @@ void cmCTestMultiProcessHandler::StartTestProcess(int test)
this->EraseTest(test);
this->RunningCount += GetProcessorsUsed(test);
- cmCTestRunTest* testRun = new cmCTestRunTest(this->TestHandler);
+ cmCTestRunTest* testRun = new cmCTestRunTest(*this);
if (this->CTest->GetRepeatUntilFail()) {
testRun->SetRunUntilFailOn();
testRun->SetNumberOfRuns(this->CTest->GetTestRepeat());
@@ -143,28 +192,11 @@ void cmCTestMultiProcessHandler::StartTestProcess(int test)
this->LockResources(test);
if (testRun->StartTest(this->Total)) {
- this->RunningTests.insert(testRun);
- } else if (testRun->IsStopTimePassed()) {
- this->StopTimePassed = true;
- delete testRun;
- return;
- } else {
-
- for (auto& j : this->Tests) {
- j.second.erase(test);
- }
-
- this->UnlockResources(test);
- this->Completed++;
- this->TestFinishMap[test] = true;
- this->TestRunningMap[test] = false;
- this->RunningCount -= GetProcessorsUsed(test);
- testRun->EndTest(this->Completed, this->Total, false);
- if (!this->Properties[test]->Disabled) {
- this->Failed->push_back(this->Properties[test]->Name);
- }
- delete testRun;
+ return true;
}
+
+ this->FinishTestProcess(testRun, false);
+ return false;
}
void cmCTestMultiProcessHandler::LockResources(int index)
@@ -222,8 +254,7 @@ bool cmCTestMultiProcessHandler::StartTest(int test)
// if there are no depends left then run this test
if (this->Tests[test].empty()) {
- this->StartTestProcess(test);
- return true;
+ return this->StartTestProcess(test);
}
// This test was not able to start because it is waiting
// on depends to run
@@ -233,6 +264,11 @@ bool cmCTestMultiProcessHandler::StartTest(int test)
void cmCTestMultiProcessHandler::StartNextTests()
{
size_t numToStart = 0;
+
+ if (this->Tests.empty()) {
+ return;
+ }
+
if (this->RunningCount < this->ParallelLevel) {
numToStart = this->ParallelLevel - this->RunningCount;
}
@@ -365,45 +401,42 @@ void cmCTestMultiProcessHandler::StartNextTests()
}
}
-bool cmCTestMultiProcessHandler::CheckOutput()
+void cmCTestMultiProcessHandler::FinishTestProcess(cmCTestRunTest* runner,
+ bool started)
{
- // no more output we are done
- if (this->RunningTests.empty()) {
- return false;
- }
- std::vector<cmCTestRunTest*> finished;
- std::string out, err;
- for (cmCTestRunTest* p : this->RunningTests) {
- if (!p->CheckOutput()) {
- finished.push_back(p);
- }
- }
- for (cmCTestRunTest* p : finished) {
- this->Completed++;
- int test = p->GetIndex();
+ this->Completed++;
+
+ int test = runner->GetIndex();
+ auto properties = runner->GetTestProperties();
- bool testResult = p->EndTest(this->Completed, this->Total, true);
- if (p->StartAgain()) {
+ bool testResult = runner->EndTest(this->Completed, this->Total, started);
+ if (started) {
+ if (runner->StartAgain()) {
this->Completed--; // remove the completed test because run again
- continue;
- }
- if (testResult) {
- this->Passed->push_back(p->GetTestProperties()->Name);
- } else {
- this->Failed->push_back(p->GetTestProperties()->Name);
- }
- for (auto& t : this->Tests) {
- t.second.erase(test);
+ return;
}
- this->TestFinishMap[test] = true;
- this->TestRunningMap[test] = false;
- this->RunningTests.erase(p);
- this->WriteCheckpoint(test);
- this->UnlockResources(test);
- this->RunningCount -= GetProcessorsUsed(test);
- delete p;
}
- return true;
+
+ if (testResult) {
+ this->Passed->push_back(properties->Name);
+ } else if (!properties->Disabled) {
+ this->Failed->push_back(properties->Name);
+ }
+
+ for (auto& t : this->Tests) {
+ t.second.erase(test);
+ }
+
+ this->TestFinishMap[test] = true;
+ this->TestRunningMap[test] = false;
+ this->WriteCheckpoint(test);
+ this->UnlockResources(test);
+ this->RunningCount -= GetProcessorsUsed(test);
+
+ delete runner;
+ if (started) {
+ this->StartNextTests();
+ }
}
void cmCTestMultiProcessHandler::UpdateCostData()
@@ -670,7 +703,7 @@ void cmCTestMultiProcessHandler::PrintTestList()
cmWorkingDirectory workdir(p.Directory);
- cmCTestRunTest testRun(this->TestHandler);
+ cmCTestRunTest testRun(*this);
testRun.SetIndex(p.Index);
testRun.SetTestProperties(&p);
testRun.ComputeArguments(); // logs the command in verbose mode
diff --git a/Source/CTest/cmCTestMultiProcessHandler.h b/Source/CTest/cmCTestMultiProcessHandler.h
index dccc2c8..7837ff9 100644
--- a/Source/CTest/cmCTestMultiProcessHandler.h
+++ b/Source/CTest/cmCTestMultiProcessHandler.h
@@ -12,6 +12,8 @@
#include <string>
#include <vector>
+#include "cm_uv.h"
+
class cmCTest;
class cmCTestRunTest;
@@ -23,6 +25,7 @@ class cmCTestRunTest;
class cmCTestMultiProcessHandler
{
friend class TestComparator;
+ friend class cmCTestRunTest;
public:
struct TestSet : public std::set<int>
@@ -75,7 +78,7 @@ protected:
// Start the next test or tests as many as are allowed by
// ParallelLevel
void StartNextTests();
- void StartTestProcess(int test);
+ bool StartTestProcess(int test);
bool StartTest(int test);
// Mark the checkpoint for the given test
void WriteCheckpoint(int index);
@@ -95,9 +98,8 @@ protected:
// Removes the checkpoint file
void MarkFinished();
void EraseTest(int index);
- // Return true if there are still tests running
- // check all running processes for output and exit case
- bool CheckOutput();
+ void FinishTestProcess(cmCTestRunTest* runner, bool started);
+
void RemoveTest(int index);
// Check if we need to resume an interrupted test set
void CheckResume();
@@ -130,7 +132,7 @@ protected:
std::vector<cmCTestTestHandler::cmCTestTestResult>* TestResults;
size_t ParallelLevel; // max number of process that can be run at once
unsigned long TestLoad;
- std::set<cmCTestRunTest*> RunningTests; // current running tests
+ uv_loop_t Loop;
cmCTestTestHandler* TestHandler;
cmCTest* CTest;
bool HasCycles;
diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx
index dbdefae..baf894e 100644
--- a/Source/CTest/cmCTestRunTest.cxx
+++ b/Source/CTest/cmCTestRunTest.cxx
@@ -4,28 +4,27 @@
#include "cmCTest.h"
#include "cmCTestMemCheckHandler.h"
-#include "cmCTestTestHandler.h"
+#include "cmCTestMultiProcessHandler.h"
#include "cmProcess.h"
#include "cmSystemTools.h"
#include "cmWorkingDirectory.h"
-#include "cm_curl.h"
#include "cm_zlib.h"
#include "cmsys/Base64.h"
-#include "cmsys/Process.h"
#include "cmsys/RegularExpression.hxx"
#include <chrono>
+#include <cmAlgorithms.h>
#include <iomanip>
+#include <ratio>
#include <sstream>
#include <stdio.h>
-#include <time.h>
#include <utility>
-cmCTestRunTest::cmCTestRunTest(cmCTestTestHandler* handler)
+cmCTestRunTest::cmCTestRunTest(cmCTestMultiProcessHandler& multiHandler)
+ : MultiTestHandler(multiHandler)
{
- this->CTest = handler->CTest;
- this->TestHandler = handler;
- this->TestProcess = nullptr;
+ this->CTest = multiHandler.CTest;
+ this->TestHandler = multiHandler.TestHandler;
this->TestResult.ExecutionTime = std::chrono::duration<double>::zero();
this->TestResult.ReturnValue = 0;
this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
@@ -34,60 +33,37 @@ cmCTestRunTest::cmCTestRunTest(cmCTestTestHandler* handler)
this->ProcessOutput.clear();
this->CompressedOutput.clear();
this->CompressionRatio = 2;
- this->StopTimePassed = false;
this->NumberOfRunsLeft = 1; // default to 1 run of the test
this->RunUntilFail = false; // default to run the test once
this->RunAgain = false; // default to not having to run again
}
-cmCTestRunTest::~cmCTestRunTest()
+void cmCTestRunTest::CheckOutput(std::string const& line)
{
-}
-
-bool cmCTestRunTest::CheckOutput()
-{
- // Read lines for up to 0.1 seconds of total time.
- std::chrono::duration<double> timeout = std::chrono::milliseconds(100);
- auto timeEnd = std::chrono::steady_clock::now() + timeout;
- std::string line;
- while ((timeout = timeEnd - std::chrono::steady_clock::now(),
- timeout > std::chrono::seconds(0))) {
- int p = this->TestProcess->GetNextOutputLine(line, timeout);
- if (p == cmsysProcess_Pipe_None) {
- // Process has terminated and all output read.
- return false;
- }
- if (p == cmsysProcess_Pipe_STDOUT) {
- // Store this line of output.
- cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->GetIndex()
- << ": " << line << std::endl);
- this->ProcessOutput += line;
- this->ProcessOutput += "\n";
-
- // Check for TIMEOUT_AFTER_MATCH property.
- if (!this->TestProperties->TimeoutRegularExpressions.empty()) {
- for (auto& reg : this->TestProperties->TimeoutRegularExpressions) {
- if (reg.first.find(this->ProcessOutput.c_str())) {
- cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->GetIndex()
- << ": "
- << "Test timeout changed to "
- << std::chrono::duration_cast<std::chrono::seconds>(
- this->TestProperties->AlternateTimeout)
- .count()
- << std::endl);
- this->TestProcess->ResetStartTime();
- this->TestProcess->ChangeTimeout(
- this->TestProperties->AlternateTimeout);
- this->TestProperties->TimeoutRegularExpressions.clear();
- break;
- }
- }
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->GetIndex()
+ << ": " << line << std::endl);
+ this->ProcessOutput += line;
+ this->ProcessOutput += "\n";
+
+ // Check for TIMEOUT_AFTER_MATCH property.
+ if (!this->TestProperties->TimeoutRegularExpressions.empty()) {
+ for (auto& reg : this->TestProperties->TimeoutRegularExpressions) {
+ if (reg.first.find(this->ProcessOutput.c_str())) {
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->GetIndex()
+ << ": "
+ << "Test timeout changed to "
+ << std::chrono::duration_cast<std::chrono::seconds>(
+ this->TestProperties->AlternateTimeout)
+ .count()
+ << std::endl);
+ this->TestProcess->ResetStartTime();
+ this->TestProcess->ChangeTimeout(
+ this->TestProperties->AlternateTimeout);
+ this->TestProperties->TimeoutRegularExpressions.clear();
+ break;
}
- } else { // if(p == cmsysProcess_Pipe_Timeout)
- break;
}
}
- return true;
}
// Streamed compression of test output. The compressed data
@@ -160,8 +136,8 @@ bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started)
this->WriteLogOutputTop(completed, total);
std::string reason;
bool passed = true;
- int res =
- started ? this->TestProcess->GetProcessStatus() : cmsysProcess_State_Error;
+ cmProcess::State res =
+ started ? this->TestProcess->GetProcessStatus() : cmProcess::State::Error;
int retVal = this->TestProcess->GetExitValue();
bool forceFail = false;
bool skipped = false;
@@ -200,7 +176,7 @@ bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started)
}
}
}
- if (res == cmsysProcess_State_Exited) {
+ if (res == cmProcess::State::Exited) {
bool success = !forceFail &&
(retVal == 0 ||
!this->TestProperties->RequiredRegularExpressions.empty());
@@ -221,29 +197,29 @@ bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started)
cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Failed " << reason);
outputTestErrorsToConsole = this->CTest->OutputTestOutputOnTestFailure;
}
- } else if (res == cmsysProcess_State_Expired) {
+ } else if (res == cmProcess::State::Expired) {
cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Timeout ");
this->TestResult.Status = cmCTestTestHandler::TIMEOUT;
outputTestErrorsToConsole = this->CTest->OutputTestOutputOnTestFailure;
- } else if (res == cmsysProcess_State_Exception) {
+ } else if (res == cmProcess::State::Exception) {
outputTestErrorsToConsole = this->CTest->OutputTestOutputOnTestFailure;
cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Exception: ");
this->TestResult.ExceptionStatus =
this->TestProcess->GetExitExceptionString();
switch (this->TestProcess->GetExitException()) {
- case cmsysProcess_Exception_Fault:
+ case cmProcess::Exception::Fault:
cmCTestLog(this->CTest, HANDLER_OUTPUT, "SegFault");
this->TestResult.Status = cmCTestTestHandler::SEGFAULT;
break;
- case cmsysProcess_Exception_Illegal:
+ case cmProcess::Exception::Illegal:
cmCTestLog(this->CTest, HANDLER_OUTPUT, "Illegal");
this->TestResult.Status = cmCTestTestHandler::ILLEGAL;
break;
- case cmsysProcess_Exception_Interrupt:
+ case cmProcess::Exception::Interrupt:
cmCTestLog(this->CTest, HANDLER_OUTPUT, "Interrupt");
this->TestResult.Status = cmCTestTestHandler::INTERRUPT;
break;
- case cmsysProcess_Exception_Numerical:
+ case cmProcess::Exception::Numerical:
cmCTestLog(this->CTest, HANDLER_OUTPUT, "Numerical");
this->TestResult.Status = cmCTestTestHandler::NUMERICAL;
break;
@@ -254,7 +230,7 @@ bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started)
}
} else if ("Disabled" == this->TestResult.CompletionStatus) {
cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Not Run (Disabled) ");
- } else // cmsysProcess_State_Error
+ } else // cmProcess::State::Error
{
cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Not Run ");
}
@@ -350,7 +326,7 @@ bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started)
if (!this->NeedsToRerun()) {
this->TestHandler->TestResults.push_back(this->TestResult);
}
- delete this->TestProcess;
+ this->TestProcess.reset();
return passed || skipped;
}
@@ -432,7 +408,7 @@ bool cmCTestRunTest::StartTest(size_t total)
this->TestResult.TestCount = this->TestProperties->Index;
this->TestResult.Name = this->TestProperties->Name;
this->TestResult.Path = this->TestProperties->Directory;
- this->TestProcess = new cmProcess;
+ this->TestProcess = cm::make_unique<cmProcess>(*this);
this->TestResult.Output = "Disabled";
this->TestResult.FullCommandLine.clear();
return false;
@@ -453,7 +429,7 @@ bool cmCTestRunTest::StartTest(size_t total)
// its arguments are irrelevant. This matters for the case where a fixture
// dependency might be creating the executable we want to run.
if (!this->FailedDependencies.empty()) {
- this->TestProcess = new cmProcess;
+ this->TestProcess = cm::make_unique<cmProcess>(*this);
std::string msg = "Failed test dependencies:";
for (std::string const& failedDep : this->FailedDependencies) {
msg += " " + failedDep;
@@ -470,7 +446,7 @@ bool cmCTestRunTest::StartTest(size_t total)
this->ComputeArguments();
std::vector<std::string>& args = this->TestProperties->Args;
if (args.size() >= 2 && args[1] == "NOT_AVAILABLE") {
- this->TestProcess = new cmProcess;
+ this->TestProcess = cm::make_unique<cmProcess>(*this);
std::string msg;
if (this->CTest->GetConfigType().empty()) {
msg = "Test not available without configuration.";
@@ -493,7 +469,7 @@ bool cmCTestRunTest::StartTest(size_t total)
for (std::string const& file : this->TestProperties->RequiredFiles) {
if (!cmSystemTools::FileExists(file.c_str())) {
// Required file was not found
- this->TestProcess = new cmProcess;
+ this->TestProcess = cm::make_unique<cmProcess>(*this);
*this->TestHandler->LogFile << "Unable to find required file: " << file
<< std::endl;
cmCTestLog(this->CTest, ERROR_MESSAGE,
@@ -509,7 +485,7 @@ bool cmCTestRunTest::StartTest(size_t total)
if (this->ActualCommand.empty()) {
// if the command was not found create a TestResult object
// that has that information
- this->TestProcess = new cmProcess;
+ this->TestProcess = cm::make_unique<cmProcess>(*this);
*this->TestHandler->LogFile << "Unable to find executable: " << args[1]
<< std::endl;
cmCTestLog(this->CTest, ERROR_MESSAGE,
@@ -522,11 +498,22 @@ bool cmCTestRunTest::StartTest(size_t total)
}
this->StartTime = this->CTest->CurrentTime();
- auto timeout = this->ResolveTimeout();
+ auto timeout = this->TestProperties->Timeout;
- if (this->StopTimePassed) {
- return false;
+ std::chrono::system_clock::time_point stop_time = this->CTest->GetStopTime();
+ if (stop_time != std::chrono::system_clock::time_point()) {
+ std::chrono::duration<double> stop_timeout =
+ (stop_time - std::chrono::system_clock::now()) % std::chrono::hours(24);
+
+ if (stop_timeout <= std::chrono::duration<double>::zero()) {
+ stop_timeout = std::chrono::duration<double>::zero();
+ }
+ if (timeout == std::chrono::duration<double>::zero() ||
+ stop_timeout < timeout) {
+ timeout = stop_timeout;
+ }
}
+
return this->ForkProcess(timeout, this->TestProperties->ExplicitTimeout,
&this->TestProperties->Environment);
}
@@ -603,72 +590,11 @@ void cmCTestRunTest::DartProcessing()
}
}
-std::chrono::duration<double> cmCTestRunTest::ResolveTimeout()
-{
- auto timeout = this->TestProperties->Timeout;
-
- if (this->CTest->GetStopTime().empty()) {
- return timeout;
- }
- struct tm* lctime;
- time_t current_time = time(nullptr);
- lctime = gmtime(&current_time);
- int gm_hour = lctime->tm_hour;
- time_t gm_time = mktime(lctime);
- lctime = localtime(&current_time);
- int local_hour = lctime->tm_hour;
-
- int tzone_offset = local_hour - gm_hour;
- if (gm_time > current_time && gm_hour < local_hour) {
- // this means gm_time is on the next day
- tzone_offset -= 24;
- } else if (gm_time < current_time && gm_hour > local_hour) {
- // this means gm_time is on the previous day
- tzone_offset += 24;
- }
-
- tzone_offset *= 100;
- char buf[1024];
- // add todays year day and month to the time in str because
- // curl_getdate no longer assumes the day is today
- sprintf(buf, "%d%02d%02d %s %+05i", lctime->tm_year + 1900,
- lctime->tm_mon + 1, lctime->tm_mday,
- this->CTest->GetStopTime().c_str(), tzone_offset);
-
- time_t stop_time_t = curl_getdate(buf, &current_time);
- if (stop_time_t == -1) {
- return timeout;
- }
-
- auto stop_time = std::chrono::system_clock::from_time_t(stop_time_t);
-
- // the stop time refers to the next day
- if (this->CTest->NextDayStopTime) {
- stop_time += std::chrono::hours(24);
- }
- auto stop_timeout =
- (stop_time - std::chrono::system_clock::from_time_t(current_time)) %
- std::chrono::hours(24);
- this->CTest->LastStopTimeout = stop_timeout;
-
- if (stop_timeout <= std::chrono::duration<double>::zero() ||
- stop_timeout > this->CTest->LastStopTimeout) {
- cmCTestLog(this->CTest, ERROR_MESSAGE, "The stop time has been passed. "
- "Stopping all tests."
- << std::endl);
- this->StopTimePassed = true;
- return std::chrono::duration<double>::zero();
- }
- return timeout == std::chrono::duration<double>::zero()
- ? stop_timeout
- : (timeout < stop_timeout ? timeout : stop_timeout);
-}
-
bool cmCTestRunTest::ForkProcess(std::chrono::duration<double> testTimeOut,
bool explicitTimeout,
std::vector<std::string>* environment)
{
- this->TestProcess = new cmProcess;
+ this->TestProcess = cm::make_unique<cmProcess>(*this);
this->TestProcess->SetId(this->Index);
this->TestProcess->SetWorkingDirectory(
this->TestProperties->Directory.c_str());
@@ -720,7 +646,7 @@ bool cmCTestRunTest::ForkProcess(std::chrono::duration<double> testTimeOut,
cmSystemTools::AppendEnv(*environment);
}
- return this->TestProcess->StartProcess();
+ return this->TestProcess->StartProcess(this->MultiTestHandler.Loop);
}
void cmCTestRunTest::WriteLogOutputTop(size_t completed, size_t total)
@@ -794,3 +720,8 @@ void cmCTestRunTest::WriteLogOutputTop(size_t completed, size_t total)
cmCTestLog(this->CTest, DEBUG, "Testing " << this->TestProperties->Name
<< " ... ");
}
+
+void cmCTestRunTest::FinalizeTest()
+{
+ this->MultiTestHandler.FinishTestProcess(this, true);
+}
diff --git a/Source/CTest/cmCTestRunTest.h b/Source/CTest/cmCTestRunTest.h
index cd380ca..fbc202f 100644
--- a/Source/CTest/cmCTestRunTest.h
+++ b/Source/CTest/cmCTestRunTest.h
@@ -12,9 +12,10 @@
#include <vector>
#include "cmCTestTestHandler.h"
+#include "cmProcess.h" // IWYU pragma: keep (for unique_ptr)
class cmCTest;
-class cmProcess;
+class cmCTestMultiProcessHandler;
/** \class cmRunTest
* \brief represents a single test to be run
@@ -24,8 +25,9 @@ class cmProcess;
class cmCTestRunTest
{
public:
- cmCTestRunTest(cmCTestTestHandler* handler);
- ~cmCTestRunTest();
+ explicit cmCTestRunTest(cmCTestMultiProcessHandler& multiHandler);
+
+ ~cmCTestRunTest() = default;
void SetNumberOfRuns(int n) { this->NumberOfRunsLeft = n; }
void SetRunUntilFailOn() { this->RunUntilFail = true; }
@@ -50,15 +52,13 @@ public:
std::string GetProcessOutput() { return this->ProcessOutput; }
- bool IsStopTimePassed() { return this->StopTimePassed; }
-
cmCTestTestHandler::cmCTestTestResult GetTestResults()
{
return this->TestResult;
}
// Read and store output. Returns true if it must be called again.
- bool CheckOutput();
+ void CheckOutput(std::string const& line);
// Compresses the output, writing to CompressedOutput
void CompressOutput();
@@ -74,12 +74,14 @@ public:
bool StartAgain();
+ cmCTest* GetCTest() const { return this->CTest; }
+
+ void FinalizeTest();
+
private:
bool NeedsToRerun();
void DartProcessing();
void ExeNotFound(std::string exe);
- // Figures out a final timeout which is min(STOP_TIME, NOW+TIMEOUT)
- std::chrono::duration<double> ResolveTimeout();
bool ForkProcess(std::chrono::duration<double> testTimeOut,
bool explicitTimeout,
std::vector<std::string>* environment);
@@ -91,26 +93,18 @@ private:
// Pointer back to the "parent"; the handler that invoked this test run
cmCTestTestHandler* TestHandler;
cmCTest* CTest;
- cmProcess* TestProcess;
- // If the executable to run is ctest, don't create a new process;
- // just instantiate a new cmTest. (Can be disabled for a single test
- // if this option is set to false.)
- // bool OptimizeForCTest;
-
- bool UsePrefixCommand;
- std::string PrefixCommand;
-
+ std::unique_ptr<cmProcess> TestProcess;
std::string ProcessOutput;
std::string CompressedOutput;
double CompressionRatio;
// The test results
cmCTestTestHandler::cmCTestTestResult TestResult;
+ cmCTestMultiProcessHandler& MultiTestHandler;
int Index;
std::set<std::string> FailedDependencies;
std::string StartTime;
std::string ActualCommand;
std::vector<std::string> Arguments;
- bool StopTimePassed;
bool RunUntilFail;
int NumberOfRunsLeft;
bool RunAgain;
diff --git a/Source/CTest/cmProcess.cxx b/Source/CTest/cmProcess.cxx
index 0db66c3..c8806a7 100644
--- a/Source/CTest/cmProcess.cxx
+++ b/Source/CTest/cmProcess.cxx
@@ -2,12 +2,65 @@
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmProcess.h"
+#include "cmCTest.h"
+#include "cmCTestRunTest.h"
+#include "cmCTestTestHandler.h"
#include "cmProcessOutput.h"
+#include "cmsys/Process.h"
+
+#include <algorithm>
+#include <fcntl.h>
#include <iostream>
+#include <signal.h>
+#include <stdint.h>
+#include <string>
+#if !defined(_WIN32)
+#include <unistd.h>
+#endif
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+#include <io.h>
+
+static int cmProcessGetPipes(int* fds)
+{
+ SECURITY_ATTRIBUTES attr;
+ HANDLE readh, writeh;
+ attr.nLength = sizeof(attr);
+ attr.lpSecurityDescriptor = nullptr;
+ attr.bInheritHandle = FALSE;
+ if (!CreatePipe(&readh, &writeh, &attr, 0))
+ return uv_translate_sys_error(GetLastError());
+ fds[0] = _open_osfhandle((intptr_t)readh, 0);
+ fds[1] = _open_osfhandle((intptr_t)writeh, 0);
+ if (fds[0] == -1 || fds[1] == -1) {
+ CloseHandle(readh);
+ CloseHandle(writeh);
+ return uv_translate_sys_error(GetLastError());
+ }
+ return 0;
+}
+#else
+#include <errno.h>
-cmProcess::cmProcess()
+static int cmProcessGetPipes(int* fds)
+{
+ if (pipe(fds) == -1) {
+ return uv_translate_sys_error(errno);
+ }
+
+ if (fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 ||
+ fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) {
+ close(fds[0]);
+ close(fds[1]);
+ return uv_translate_sys_error(errno);
+ }
+ return 0;
+}
+#endif
+
+cmProcess::cmProcess(cmCTestRunTest& runner)
+ : Runner(runner)
{
- this->Process = nullptr;
this->Timeout = std::chrono::duration<double>::zero();
this->TotalTime = std::chrono::duration<double>::zero();
this->ExitValue = 0;
@@ -17,8 +70,8 @@ cmProcess::cmProcess()
cmProcess::~cmProcess()
{
- cmsysProcess_Delete(this->Process);
}
+
void cmProcess::SetCommand(const char* command)
{
this->Command = command;
@@ -29,8 +82,9 @@ void cmProcess::SetCommandArguments(std::vector<std::string> const& args)
this->Arguments = args;
}
-bool cmProcess::StartProcess()
+bool cmProcess::StartProcess(uv_loop_t& loop)
{
+ this->ProcessState = cmProcess::State::Error;
if (this->Command.empty()) {
return false;
}
@@ -43,17 +97,83 @@ bool cmProcess::StartProcess()
this->ProcessArgs.push_back(arg.c_str());
}
this->ProcessArgs.push_back(nullptr); // null terminate the list
- this->Process = cmsysProcess_New();
- cmsysProcess_SetCommand(this->Process, &*this->ProcessArgs.begin());
- if (!this->WorkingDirectory.empty()) {
- cmsysProcess_SetWorkingDirectory(this->Process,
- this->WorkingDirectory.c_str());
+
+ cm::uv_timer_ptr timer;
+ int status = timer.init(loop, this);
+ if (status != 0) {
+ cmCTestLog(this->Runner.GetCTest(), ERROR_MESSAGE,
+ "Error initializing timer: " << uv_strerror(status)
+ << std::endl);
+ return false;
+ }
+
+ cm::uv_pipe_ptr pipe_writer;
+ cm::uv_pipe_ptr pipe_reader;
+
+ pipe_writer.init(loop, 0);
+ pipe_reader.init(loop, 0, this);
+
+ int fds[2] = { -1, -1 };
+ status = cmProcessGetPipes(fds);
+ if (status != 0) {
+ cmCTestLog(this->Runner.GetCTest(), ERROR_MESSAGE,
+ "Error initializing pipe: " << uv_strerror(status)
+ << std::endl);
+ return false;
+ }
+
+ uv_pipe_open(pipe_reader, fds[0]);
+ uv_pipe_open(pipe_writer, fds[1]);
+
+ uv_stdio_container_t stdio[3];
+ stdio[0].flags = UV_IGNORE;
+ stdio[1].flags = UV_INHERIT_STREAM;
+ stdio[1].data.stream = pipe_writer;
+ stdio[2] = stdio[1];
+
+ uv_process_options_t options = uv_process_options_t();
+ options.file = this->Command.data();
+ options.args = const_cast<char**>(this->ProcessArgs.data());
+ options.stdio_count = 3; // in, out and err
+ options.exit_cb = &cmProcess::OnExitCB;
+ options.stdio = stdio;
+
+ status =
+ uv_read_start(pipe_reader, &cmProcess::OnAllocateCB, &cmProcess::OnReadCB);
+
+ if (status != 0) {
+ cmCTestLog(this->Runner.GetCTest(), ERROR_MESSAGE,
+ "Error starting read events: " << uv_strerror(status)
+ << std::endl);
+ return false;
+ }
+
+ status = this->Process.spawn(loop, options, this);
+ if (status != 0) {
+ cmCTestLog(this->Runner.GetCTest(), ERROR_MESSAGE, "Process not started\n "
+ << this->Command << "\n[" << uv_strerror(status) << "]\n");
+ return false;
+ }
+
+ this->PipeReader = std::move(pipe_reader);
+ this->Timer = std::move(timer);
+
+ this->StartTimer();
+
+ this->ProcessState = cmProcess::State::Executing;
+ return true;
+}
+
+void cmProcess::StartTimer()
+{
+ auto properties = this->Runner.GetTestProperties();
+ auto msec =
+ std::chrono::duration_cast<std::chrono::milliseconds>(this->Timeout);
+
+ if (msec != std::chrono::milliseconds(0) || !properties->ExplicitTimeout) {
+ this->Timer.start(&cmProcess::OnTimeoutCB,
+ static_cast<uint64_t>(msec.count()), 0);
}
- cmsysProcess_SetTimeout(this->Process, this->Timeout.count());
- cmsysProcess_SetOption(this->Process, cmsysProcess_Option_MergeOutput, 1);
- cmsysProcess_Execute(this->Process);
- return (cmsysProcess_GetState(this->Process) ==
- cmsysProcess_State_Executing);
}
bool cmProcess::Buffer::GetLine(std::string& line)
@@ -100,51 +220,121 @@ bool cmProcess::Buffer::GetLast(std::string& line)
return false;
}
-int cmProcess::GetNextOutputLine(std::string& line,
- std::chrono::duration<double> timeout)
+void cmProcess::OnReadCB(uv_stream_t* stream, ssize_t nread,
+ const uv_buf_t* buf)
{
- cmProcessOutput processOutput(cmProcessOutput::UTF8);
- std::string strdata;
- double waitTimeout = timeout.count();
- for (;;) {
- // Look for lines already buffered.
- if (this->Output.GetLine(line)) {
- return cmsysProcess_Pipe_STDOUT;
- }
+ auto self = static_cast<cmProcess*>(stream->data);
+ self->OnRead(nread, buf);
+}
- // Check for more data from the process.
- char* data;
- int length;
- int p =
- cmsysProcess_WaitForData(this->Process, &data, &length, &waitTimeout);
- if (p == cmsysProcess_Pipe_Timeout) {
- return cmsysProcess_Pipe_Timeout;
- }
- if (p == cmsysProcess_Pipe_STDOUT) {
- processOutput.DecodeText(data, length, strdata);
- this->Output.insert(this->Output.end(), strdata.begin(), strdata.end());
- } else { // p == cmsysProcess_Pipe_None
- // The process will provide no more data.
- break;
+void cmProcess::OnRead(ssize_t nread, const uv_buf_t* buf)
+{
+ std::string line;
+ if (nread > 0) {
+ std::string strdata;
+ cmProcessOutput processOutput(cmProcessOutput::UTF8,
+ static_cast<unsigned int>(buf->len));
+ processOutput.DecodeText(buf->base, static_cast<size_t>(nread), strdata);
+ this->Output.insert(this->Output.end(), strdata.begin(), strdata.end());
+
+ while (this->Output.GetLine(line)) {
+ this->Runner.CheckOutput(line);
+ line.clear();
}
+
+ return;
}
- processOutput.DecodeText(std::string(), strdata);
- if (!strdata.empty()) {
- this->Output.insert(this->Output.end(), strdata.begin(), strdata.end());
+
+ // The process will provide no more data.
+ if (nread != UV_EOF) {
+ auto error = static_cast<int>(nread);
+ cmCTestLog(this->Runner.GetCTest(), ERROR_MESSAGE,
+ "Error reading stream: " << uv_strerror(error) << std::endl);
}
// Look for partial last lines.
if (this->Output.GetLast(line)) {
- return cmsysProcess_Pipe_STDOUT;
+ this->Runner.CheckOutput(line);
+ }
+
+ this->ReadHandleClosed = true;
+ if (this->ProcessHandleClosed) {
+ uv_timer_stop(this->Timer);
+ this->Runner.FinalizeTest();
+ }
+}
+
+void cmProcess::OnAllocateCB(uv_handle_t* handle, size_t suggested_size,
+ uv_buf_t* buf)
+{
+ auto self = static_cast<cmProcess*>(handle->data);
+ self->OnAllocate(suggested_size, buf);
+}
+
+void cmProcess::OnAllocate(size_t suggested_size, uv_buf_t* buf)
+{
+ if (this->Buf.size() < suggested_size) {
+ this->Buf.resize(suggested_size);
+ }
+
+ *buf =
+ uv_buf_init(this->Buf.data(), static_cast<unsigned int>(this->Buf.size()));
+}
+
+void cmProcess::OnTimeoutCB(uv_timer_t* timer)
+{
+ auto self = static_cast<cmProcess*>(timer->data);
+ self->OnTimeout();
+}
+
+void cmProcess::OnTimeout()
+{
+ if (this->ProcessState != cmProcess::State::Executing) {
+ return;
+ }
+ this->ProcessState = cmProcess::State::Expired;
+ bool const was_still_reading = !this->ReadHandleClosed;
+ if (!this->ReadHandleClosed) {
+ this->ReadHandleClosed = true;
+ this->PipeReader.reset();
+ }
+ if (!this->ProcessHandleClosed) {
+ // Kill the child and let our on-exit handler finish the test.
+ cmsysProcess_KillPID(static_cast<unsigned long>(this->Process->pid));
+ } else if (was_still_reading) {
+ // Our on-exit handler already ran but did not finish the test
+ // because we were still reading output. We've just dropped
+ // our read handler, so we need to finish the test now.
+ this->Runner.FinalizeTest();
}
+}
- // No more data. Wait for process exit.
- if (!cmsysProcess_WaitForExit(this->Process, &waitTimeout)) {
- return cmsysProcess_Pipe_Timeout;
+void cmProcess::OnExitCB(uv_process_t* process, int64_t exit_status,
+ int term_signal)
+{
+ auto self = static_cast<cmProcess*>(process->data);
+ self->OnExit(exit_status, term_signal);
+}
+
+void cmProcess::OnExit(int64_t exit_status, int term_signal)
+{
+ if (this->ProcessState != cmProcess::State::Expired) {
+ if (
+#if defined(_WIN32)
+ ((DWORD)exit_status & 0xF0000000) == 0xC0000000
+#else
+ term_signal != 0
+#endif
+ ) {
+ this->ProcessState = cmProcess::State::Exception;
+ } else {
+ this->ProcessState = cmProcess::State::Exited;
+ }
}
// Record exit information.
- this->ExitValue = cmsysProcess_GetExitValue(this->Process);
+ this->ExitValue = static_cast<int>(exit_status);
+ this->Signal = term_signal;
this->TotalTime = std::chrono::steady_clock::now() - this->StartTime;
// Because of a processor clock scew the runtime may become slightly
// negative. If someone changed the system clock while the process was
@@ -153,95 +343,373 @@ int cmProcess::GetNextOutputLine(std::string& line,
if (this->TotalTime <= std::chrono::duration<double>::zero()) {
this->TotalTime = std::chrono::duration<double>::zero();
}
- // std::cerr << "Time to run: " << this->TotalTime << "\n";
- return cmsysProcess_Pipe_None;
-}
-
-// return the process status
-int cmProcess::GetProcessStatus()
-{
- if (!this->Process) {
- return cmsysProcess_State_Exited;
- }
- return cmsysProcess_GetState(this->Process);
-}
-
-int cmProcess::ReportStatus()
-{
- int result = 1;
- switch (cmsysProcess_GetState(this->Process)) {
- case cmsysProcess_State_Starting: {
- std::cerr << "cmProcess: Never started " << this->Command
- << " process.\n";
- } break;
- case cmsysProcess_State_Error: {
- std::cerr << "cmProcess: Error executing " << this->Command
- << " process: " << cmsysProcess_GetErrorString(this->Process)
- << "\n";
- } break;
- case cmsysProcess_State_Exception: {
- std::cerr << "cmProcess: " << this->Command
- << " process exited with an exception: ";
- switch (cmsysProcess_GetExitException(this->Process)) {
- case cmsysProcess_Exception_None: {
- std::cerr << "None";
- } break;
- case cmsysProcess_Exception_Fault: {
- std::cerr << "Segmentation fault";
- } break;
- case cmsysProcess_Exception_Illegal: {
- std::cerr << "Illegal instruction";
- } break;
- case cmsysProcess_Exception_Interrupt: {
- std::cerr << "Interrupted by user";
- } break;
- case cmsysProcess_Exception_Numerical: {
- std::cerr << "Numerical exception";
- } break;
- case cmsysProcess_Exception_Other: {
- std::cerr << "Unknown";
- } break;
- }
- std::cerr << "\n";
- } break;
- case cmsysProcess_State_Executing: {
- std::cerr << "cmProcess: Never terminated " << this->Command
- << " process.\n";
- } break;
- case cmsysProcess_State_Exited: {
- result = cmsysProcess_GetExitValue(this->Process);
- std::cerr << "cmProcess: " << this->Command
- << " process exited with code " << result << "\n";
- } break;
- case cmsysProcess_State_Expired: {
- std::cerr << "cmProcess: killed " << this->Command
- << " process due to timeout.\n";
- } break;
- case cmsysProcess_State_Killed: {
- std::cerr << "cmProcess: killed " << this->Command << " process.\n";
- } break;
- }
- return result;
+
+ this->ProcessHandleClosed = true;
+ if (this->ReadHandleClosed) {
+ uv_timer_stop(this->Timer);
+ this->Runner.FinalizeTest();
+ }
+}
+
+cmProcess::State cmProcess::GetProcessStatus()
+{
+ return this->ProcessState;
}
void cmProcess::ChangeTimeout(std::chrono::duration<double> t)
{
this->Timeout = t;
- cmsysProcess_SetTimeout(this->Process, this->Timeout.count());
+ this->StartTimer();
}
void cmProcess::ResetStartTime()
{
- cmsysProcess_ResetStartTime(this->Process);
this->StartTime = std::chrono::steady_clock::now();
}
-int cmProcess::GetExitException()
+cmProcess::Exception cmProcess::GetExitException()
{
- return cmsysProcess_GetExitException(this->Process);
+ auto exception = Exception::None;
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ auto exit_code = (DWORD) this->ExitValue;
+ if ((exit_code & 0xF0000000) != 0xC0000000) {
+ return exception;
+ }
+
+ if (exit_code) {
+ switch (exit_code) {
+ case STATUS_DATATYPE_MISALIGNMENT:
+ case STATUS_ACCESS_VIOLATION:
+ case STATUS_IN_PAGE_ERROR:
+ case STATUS_INVALID_HANDLE:
+ case STATUS_NONCONTINUABLE_EXCEPTION:
+ case STATUS_INVALID_DISPOSITION:
+ case STATUS_ARRAY_BOUNDS_EXCEEDED:
+ case STATUS_STACK_OVERFLOW:
+ exception = Exception::Fault;
+ break;
+ case STATUS_FLOAT_DENORMAL_OPERAND:
+ case STATUS_FLOAT_DIVIDE_BY_ZERO:
+ case STATUS_FLOAT_INEXACT_RESULT:
+ case STATUS_FLOAT_INVALID_OPERATION:
+ case STATUS_FLOAT_OVERFLOW:
+ case STATUS_FLOAT_STACK_CHECK:
+ case STATUS_FLOAT_UNDERFLOW:
+#ifdef STATUS_FLOAT_MULTIPLE_FAULTS
+ case STATUS_FLOAT_MULTIPLE_FAULTS:
+#endif
+#ifdef STATUS_FLOAT_MULTIPLE_TRAPS
+ case STATUS_FLOAT_MULTIPLE_TRAPS:
+#endif
+ case STATUS_INTEGER_DIVIDE_BY_ZERO:
+ case STATUS_INTEGER_OVERFLOW:
+ exception = Exception::Numerical;
+ break;
+ case STATUS_CONTROL_C_EXIT:
+ exception = Exception::Interrupt;
+ break;
+ case STATUS_ILLEGAL_INSTRUCTION:
+ case STATUS_PRIVILEGED_INSTRUCTION:
+ exception = Exception::Illegal;
+ break;
+ default:
+ exception = Exception::Other;
+ }
+ }
+#else
+ if (this->Signal) {
+ switch (this->Signal) {
+ case SIGSEGV:
+ exception = Exception::Fault;
+ break;
+ case SIGFPE:
+ exception = Exception::Numerical;
+ break;
+ case SIGINT:
+ exception = Exception::Interrupt;
+ break;
+ case SIGILL:
+ exception = Exception::Illegal;
+ break;
+ default:
+ exception = Exception::Other;
+ }
+ }
+#endif
+ return exception;
}
std::string cmProcess::GetExitExceptionString()
{
- return cmsysProcess_GetExceptionString(this->Process);
+ std::string exception_str;
+#if defined(_WIN32)
+ switch (this->ExitValue) {
+ case STATUS_CONTROL_C_EXIT:
+ exception_str = "User interrupt";
+ break;
+ case STATUS_FLOAT_DENORMAL_OPERAND:
+ exception_str = "Floating-point exception (denormal operand)";
+ break;
+ case STATUS_FLOAT_DIVIDE_BY_ZERO:
+ exception_str = "Divide-by-zero";
+ break;
+ case STATUS_FLOAT_INEXACT_RESULT:
+ exception_str = "Floating-point exception (inexact result)";
+ break;
+ case STATUS_FLOAT_INVALID_OPERATION:
+ exception_str = "Invalid floating-point operation";
+ break;
+ case STATUS_FLOAT_OVERFLOW:
+ exception_str = "Floating-point overflow";
+ break;
+ case STATUS_FLOAT_STACK_CHECK:
+ exception_str = "Floating-point stack check failed";
+ break;
+ case STATUS_FLOAT_UNDERFLOW:
+ exception_str = "Floating-point underflow";
+ break;
+#ifdef STATUS_FLOAT_MULTIPLE_FAULTS
+ case STATUS_FLOAT_MULTIPLE_FAULTS:
+ exception_str = "Floating-point exception (multiple faults)";
+ break;
+#endif
+#ifdef STATUS_FLOAT_MULTIPLE_TRAPS
+ case STATUS_FLOAT_MULTIPLE_TRAPS:
+ exception_str = "Floating-point exception (multiple traps)";
+ break;
+#endif
+ case STATUS_INTEGER_DIVIDE_BY_ZERO:
+ exception_str = "Integer divide-by-zero";
+ break;
+ case STATUS_INTEGER_OVERFLOW:
+ exception_str = "Integer overflow";
+ break;
+
+ case STATUS_DATATYPE_MISALIGNMENT:
+ exception_str = "Datatype misalignment";
+ break;
+ case STATUS_ACCESS_VIOLATION:
+ exception_str = "Access violation";
+ break;
+ case STATUS_IN_PAGE_ERROR:
+ exception_str = "In-page error";
+ break;
+ case STATUS_INVALID_HANDLE:
+ exception_str = "Invalid handle";
+ break;
+ case STATUS_NONCONTINUABLE_EXCEPTION:
+ exception_str = "Noncontinuable exception";
+ break;
+ case STATUS_INVALID_DISPOSITION:
+ exception_str = "Invalid disposition";
+ break;
+ case STATUS_ARRAY_BOUNDS_EXCEEDED:
+ exception_str = "Array bounds exceeded";
+ break;
+ case STATUS_STACK_OVERFLOW:
+ exception_str = "Stack overflow";
+ break;
+
+ case STATUS_ILLEGAL_INSTRUCTION:
+ exception_str = "Illegal instruction";
+ break;
+ case STATUS_PRIVILEGED_INSTRUCTION:
+ exception_str = "Privileged instruction";
+ break;
+ case STATUS_NO_MEMORY:
+ default:
+ char buf[1024];
+ _snprintf(buf, 1024, "Exit code 0x%x\n", this->ExitValue);
+ exception_str.assign(buf);
+ }
+#else
+ switch (this->Signal) {
+#ifdef SIGSEGV
+ case SIGSEGV:
+ exception_str = "Segmentation fault";
+ break;
+#endif
+#ifdef SIGBUS
+#if !defined(SIGSEGV) || SIGBUS != SIGSEGV
+ case SIGBUS:
+ exception_str = "Bus error";
+ break;
+#endif
+#endif
+#ifdef SIGFPE
+ case SIGFPE:
+ exception_str = "Floating-point exception";
+ break;
+#endif
+#ifdef SIGILL
+ case SIGILL:
+ exception_str = "Illegal instruction";
+ break;
+#endif
+#ifdef SIGINT
+ case SIGINT:
+ exception_str = "User interrupt";
+ break;
+#endif
+#ifdef SIGABRT
+ case SIGABRT:
+ exception_str = "Child aborted";
+ break;
+#endif
+#ifdef SIGKILL
+ case SIGKILL:
+ exception_str = "Child killed";
+ break;
+#endif
+#ifdef SIGTERM
+ case SIGTERM:
+ exception_str = "Child terminated";
+ break;
+#endif
+#ifdef SIGHUP
+ case SIGHUP:
+ exception_str = "SIGHUP";
+ break;
+#endif
+#ifdef SIGQUIT
+ case SIGQUIT:
+ exception_str = "SIGQUIT";
+ break;
+#endif
+#ifdef SIGTRAP
+ case SIGTRAP:
+ exception_str = "SIGTRAP";
+ break;
+#endif
+#ifdef SIGIOT
+#if !defined(SIGABRT) || SIGIOT != SIGABRT
+ case SIGIOT:
+ exception_str = "SIGIOT";
+ break;
+#endif
+#endif
+#ifdef SIGUSR1
+ case SIGUSR1:
+ exception_str = "SIGUSR1";
+ break;
+#endif
+#ifdef SIGUSR2
+ case SIGUSR2:
+ exception_str = "SIGUSR2";
+ break;
+#endif
+#ifdef SIGPIPE
+ case SIGPIPE:
+ exception_str = "SIGPIPE";
+ break;
+#endif
+#ifdef SIGALRM
+ case SIGALRM:
+ exception_str = "SIGALRM";
+ break;
+#endif
+#ifdef SIGSTKFLT
+ case SIGSTKFLT:
+ exception_str = "SIGSTKFLT";
+ break;
+#endif
+#ifdef SIGCHLD
+ case SIGCHLD:
+ exception_str = "SIGCHLD";
+ break;
+#elif defined(SIGCLD)
+ case SIGCLD:
+ exception_str = "SIGCLD";
+ break;
+#endif
+#ifdef SIGCONT
+ case SIGCONT:
+ exception_str = "SIGCONT";
+ break;
+#endif
+#ifdef SIGSTOP
+ case SIGSTOP:
+ exception_str = "SIGSTOP";
+ break;
+#endif
+#ifdef SIGTSTP
+ case SIGTSTP:
+ exception_str = "SIGTSTP";
+ break;
+#endif
+#ifdef SIGTTIN
+ case SIGTTIN:
+ exception_str = "SIGTTIN";
+ break;
+#endif
+#ifdef SIGTTOU
+ case SIGTTOU:
+ exception_str = "SIGTTOU";
+ break;
+#endif
+#ifdef SIGURG
+ case SIGURG:
+ exception_str = "SIGURG";
+ break;
+#endif
+#ifdef SIGXCPU
+ case SIGXCPU:
+ exception_str = "SIGXCPU";
+ break;
+#endif
+#ifdef SIGXFSZ
+ case SIGXFSZ:
+ exception_str = "SIGXFSZ";
+ break;
+#endif
+#ifdef SIGVTALRM
+ case SIGVTALRM:
+ exception_str = "SIGVTALRM";
+ break;
+#endif
+#ifdef SIGPROF
+ case SIGPROF:
+ exception_str = "SIGPROF";
+ break;
+#endif
+#ifdef SIGWINCH
+ case SIGWINCH:
+ exception_str = "SIGWINCH";
+ break;
+#endif
+#ifdef SIGPOLL
+ case SIGPOLL:
+ exception_str = "SIGPOLL";
+ break;
+#endif
+#ifdef SIGIO
+#if !defined(SIGPOLL) || SIGIO != SIGPOLL
+ case SIGIO:
+ exception_str = "SIGIO";
+ break;
+#endif
+#endif
+#ifdef SIGPWR
+ case SIGPWR:
+ exception_str = "SIGPWR";
+ break;
+#endif
+#ifdef SIGSYS
+ case SIGSYS:
+ exception_str = "SIGSYS";
+ break;
+#endif
+#ifdef SIGUNUSED
+#if !defined(SIGSYS) || SIGUNUSED != SIGSYS
+ case SIGUNUSED:
+ exception_str = "SIGUNUSED";
+ break;
+#endif
+#endif
+ default:
+ exception_str = "Signal ";
+ exception_str += std::to_string(this->Signal);
+ }
+#endif
+ return exception_str;
}
diff --git a/Source/CTest/cmProcess.h b/Source/CTest/cmProcess.h
index f3b0bd7..9250896 100644
--- a/Source/CTest/cmProcess.h
+++ b/Source/CTest/cmProcess.h
@@ -5,11 +5,17 @@
#include "cmConfigure.h" // IWYU pragma: keep
-#include "cmsys/Process.h"
+#include "cmUVHandlePtr.h"
+#include "cm_uv.h"
+
#include <chrono>
+#include <stddef.h>
#include <string>
+#include <sys/types.h>
#include <vector>
+class cmCTestRunTest;
+
/** \class cmProcess
* \brief run a process with c++
*
@@ -18,7 +24,7 @@
class cmProcess
{
public:
- cmProcess();
+ explicit cmProcess(cmCTestRunTest& runner);
~cmProcess();
const char* GetCommand() { return this->Command.c_str(); }
void SetCommand(const char* command);
@@ -28,33 +34,70 @@ public:
void ChangeTimeout(std::chrono::duration<double> t);
void ResetStartTime();
// Return true if the process starts
- bool StartProcess();
+ bool StartProcess(uv_loop_t& loop);
+
+ enum class State
+ {
+ Starting,
+ Error,
+ Exception,
+ Executing,
+ Exited,
+ Expired,
+ Killed,
+ Disowned
+ };
- // return the process status
- int GetProcessStatus();
- // Report the status of the program
- int ReportStatus();
+ State GetProcessStatus();
int GetId() { return this->Id; }
void SetId(int id) { this->Id = id; }
int GetExitValue() { return this->ExitValue; }
std::chrono::duration<double> GetTotalTime() { return this->TotalTime; }
- int GetExitException();
+
+ enum class Exception
+ {
+ None,
+ Fault,
+ Illegal,
+ Interrupt,
+ Numerical,
+ Other
+ };
+
+ Exception GetExitException();
std::string GetExitExceptionString();
- /**
- * Read one line of output but block for no more than timeout.
- * Returns:
- * cmsysProcess_Pipe_None = Process terminated and all output read
- * cmsysProcess_Pipe_STDOUT = Line came from stdout or stderr
- * cmsysProcess_Pipe_Timeout = Timeout expired while waiting
- */
- int GetNextOutputLine(std::string& line,
- std::chrono::duration<double> timeout);
private:
std::chrono::duration<double> Timeout;
std::chrono::steady_clock::time_point StartTime;
std::chrono::duration<double> TotalTime;
- cmsysProcess* Process;
+ bool ReadHandleClosed = false;
+ bool ProcessHandleClosed = false;
+
+ cm::uv_process_ptr Process;
+ cm::uv_pipe_ptr PipeReader;
+ cm::uv_timer_ptr Timer;
+ std::vector<char> Buf;
+
+ cmCTestRunTest& Runner;
+ int Signal = 0;
+ cmProcess::State ProcessState = cmProcess::State::Starting;
+
+ static void OnExitCB(uv_process_t* process, int64_t exit_status,
+ int term_signal);
+ static void OnTimeoutCB(uv_timer_t* timer);
+ static void OnReadCB(uv_stream_t* stream, ssize_t nread,
+ const uv_buf_t* buf);
+ static void OnAllocateCB(uv_handle_t* handle, size_t suggested_size,
+ uv_buf_t* buf);
+
+ void OnExit(int64_t exit_status, int term_signal);
+ void OnTimeout();
+ void OnRead(ssize_t nread, const uv_buf_t* buf);
+ void OnAllocate(size_t suggested_size, uv_buf_t* buf);
+
+ void StartTimer();
+
class Buffer : public std::vector<char>
{
// Half-open index range of partial line already scanned.
diff --git a/Source/cmCTest.cxx b/Source/cmCTest.cxx
index 26e1dcb..fd7c5e8 100644
--- a/Source/cmCTest.cxx
+++ b/Source/cmCTest.cxx
@@ -279,11 +279,8 @@ cmCTest::cmCTest()
this->InteractiveDebugMode = true;
this->TimeOut = std::chrono::duration<double>::zero();
this->GlobalTimeout = std::chrono::duration<double>::zero();
- this->LastStopTimeout = std::chrono::hours(24);
this->CompressXMLFiles = false;
this->ScheduleType.clear();
- this->StopTime.clear();
- this->NextDayStopTime = false;
this->OutputLogFile = nullptr;
this->OutputLogFileLastTag = -1;
this->SuppressUpdatingCTestConfiguration = false;
@@ -2269,10 +2266,41 @@ void cmCTest::SetNotesFiles(const char* notes)
this->NotesFiles = notes;
}
-void cmCTest::SetStopTime(std::string const& time)
+void cmCTest::SetStopTime(std::string const& time_str)
{
- this->StopTime = time;
- this->DetermineNextDayStop();
+
+ struct tm* lctime;
+ time_t current_time = time(nullptr);
+ lctime = gmtime(&current_time);
+ int gm_hour = lctime->tm_hour;
+ time_t gm_time = mktime(lctime);
+ lctime = localtime(&current_time);
+ int local_hour = lctime->tm_hour;
+
+ int tzone_offset = local_hour - gm_hour;
+ if (gm_time > current_time && gm_hour < local_hour) {
+ // this means gm_time is on the next day
+ tzone_offset -= 24;
+ } else if (gm_time < current_time && gm_hour > local_hour) {
+ // this means gm_time is on the previous day
+ tzone_offset += 24;
+ }
+
+ tzone_offset *= 100;
+ char buf[1024];
+ sprintf(buf, "%d%02d%02d %s %+05i", lctime->tm_year + 1900,
+ lctime->tm_mon + 1, lctime->tm_mday, time_str.c_str(), tzone_offset);
+
+ time_t stop_time = curl_getdate(buf, &current_time);
+ if (stop_time == -1) {
+ this->StopTime = std::chrono::system_clock::time_point();
+ return;
+ }
+ this->StopTime = std::chrono::system_clock::from_time_t(stop_time);
+
+ if (stop_time < current_time) {
+ this->StopTime += std::chrono::hours(24);
+ }
}
int cmCTest::ReadCustomConfigurationFileTree(const char* dir, cmMakefile* mf)
@@ -2430,38 +2458,6 @@ void cmCTest::EmptyCTestConfiguration()
this->CTestConfiguration.clear();
}
-void cmCTest::DetermineNextDayStop()
-{
- struct tm* lctime;
- time_t current_time = time(nullptr);
- lctime = gmtime(&current_time);
- int gm_hour = lctime->tm_hour;
- time_t gm_time = mktime(lctime);
- lctime = localtime(&current_time);
- int local_hour = lctime->tm_hour;
-
- int tzone_offset = local_hour - gm_hour;
- if (gm_time > current_time && gm_hour < local_hour) {
- // this means gm_time is on the next day
- tzone_offset -= 24;
- } else if (gm_time < current_time && gm_hour > local_hour) {
- // this means gm_time is on the previous day
- tzone_offset += 24;
- }
-
- tzone_offset *= 100;
- char buf[1024];
- sprintf(buf, "%d%02d%02d %s %+05i", lctime->tm_year + 1900,
- lctime->tm_mon + 1, lctime->tm_mday, this->StopTime.c_str(),
- tzone_offset);
-
- time_t stop_time = curl_getdate(buf, &current_time);
-
- if (stop_time < current_time) {
- this->NextDayStopTime = true;
- }
-}
-
void cmCTest::SetCTestConfiguration(const char* name, const char* value,
bool suppress)
{
diff --git a/Source/cmCTest.h b/Source/cmCTest.h
index 23d71cb..61487f1 100644
--- a/Source/cmCTest.h
+++ b/Source/cmCTest.h
@@ -226,7 +226,10 @@ public:
bool ShouldCompressTestOutput();
bool CompressString(std::string& str);
- std::string GetStopTime() { return this->StopTime; }
+ std::chrono::system_clock::time_point GetStopTime()
+ {
+ return this->StopTime;
+ }
void SetStopTime(std::string const& time);
/** Used for parallel ctest job scheduling */
@@ -464,8 +467,7 @@ private:
bool RepeatUntilFail;
std::string ConfigType;
std::string ScheduleType;
- std::string StopTime;
- bool NextDayStopTime;
+ std::chrono::system_clock::time_point StopTime;
bool Verbose;
bool ExtraVerbose;
bool ProduceXML;
@@ -481,8 +483,6 @@ private:
int GenerateNotesFile(const char* files);
- void DetermineNextDayStop();
-
// these are helper classes
typedef std::map<std::string, cmCTestGenericHandler*> t_TestingHandlers;
t_TestingHandlers TestingHandlers;
@@ -512,8 +512,6 @@ private:
std::chrono::duration<double> GlobalTimeout;
- std::chrono::duration<double> LastStopTimeout;
-
int MaxTestNameWidth;
int ParallelLevel;
diff --git a/Utilities/cmlibuv/src/unix/signal.c b/Utilities/cmlibuv/src/unix/signal.c
index cb09ead..3759778 100644
--- a/Utilities/cmlibuv/src/unix/signal.c
+++ b/Utilities/cmlibuv/src/unix/signal.c
@@ -28,6 +28,9 @@
#include <string.h>
#include <unistd.h>
+#ifndef SA_RESTART
+# define SA_RESTART 0
+#endif
typedef struct {
uv_signal_t* handle;
@@ -216,7 +219,9 @@ static int uv__signal_register_handler(int signum, int oneshot) {
if (sigfillset(&sa.sa_mask))
abort();
sa.sa_handler = uv__signal_handler;
- sa.sa_flags = oneshot ? SA_RESETHAND : 0;
+ sa.sa_flags = SA_RESTART;
+ if (oneshot)
+ sa.sa_flags |= SA_RESETHAND;
/* XXX save old action so we can restore it later on? */
if (sigaction(signum, &sa, NULL))