summaryrefslogtreecommitdiffstats
path: root/Source/CTest/cmCTestRunTest.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'Source/CTest/cmCTestRunTest.cxx')
-rw-r--r--Source/CTest/cmCTestRunTest.cxx760
1 files changed, 760 insertions, 0 deletions
diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx
new file mode 100644
index 0000000..41d02c5
--- /dev/null
+++ b/Source/CTest/cmCTestRunTest.cxx
@@ -0,0 +1,760 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmCTestRunTest.h"
+
+#include "cmCTest.h"
+#include "cmCTestMemCheckHandler.h"
+#include "cmCTestMultiProcessHandler.h"
+#include "cmProcess.h"
+#include "cmSystemTools.h"
+#include "cmWorkingDirectory.h"
+
+#include "cmsys/RegularExpression.hxx"
+#include <chrono>
+#include <cmAlgorithms.h>
+#include <cstdint>
+#include <cstring>
+#include <iomanip>
+#include <ratio>
+#include <sstream>
+#include <stdio.h>
+#include <utility>
+
+cmCTestRunTest::cmCTestRunTest(cmCTestMultiProcessHandler& multiHandler)
+ : MultiTestHandler(multiHandler)
+{
+ this->CTest = multiHandler.CTest;
+ this->TestHandler = multiHandler.TestHandler;
+ this->TestResult.ExecutionTime = cmDuration::zero();
+ this->TestResult.ReturnValue = 0;
+ this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
+ this->TestResult.TestCount = 0;
+ this->TestResult.Properties = nullptr;
+ 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
+}
+
+void cmCTestRunTest::CheckOutput(std::string const& line)
+{
+ 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)) {
+ 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;
+ }
+ }
+ }
+}
+
+bool 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 skipped = false;
+ bool outputTestErrorsToConsole = false;
+ if (!this->TestProperties->RequiredRegularExpressions.empty() &&
+ this->FailedDependencies.empty()) {
+ bool found = false;
+ for (auto& pass : this->TestProperties->RequiredRegularExpressions) {
+ if (pass.first.find(this->ProcessOutput)) {
+ found = true;
+ reason = "Required regular expression found.";
+ break;
+ }
+ }
+ if (!found) {
+ reason = "Required regular expression not found.";
+ forceFail = true;
+ }
+ reason += "Regex=[";
+ for (auto& pass : this->TestProperties->RequiredRegularExpressions) {
+ reason += pass.second;
+ reason += "\n";
+ }
+ reason += "]";
+ }
+ if (!this->TestProperties->ErrorRegularExpressions.empty() &&
+ this->FailedDependencies.empty()) {
+ for (auto& pass : this->TestProperties->ErrorRegularExpressions) {
+ if (pass.first.find(this->ProcessOutput)) {
+ reason = "Error regular expression found in output.";
+ reason += " Regex=[";
+ reason += pass.second;
+ reason += "]";
+ forceFail = true;
+ break;
+ }
+ }
+ }
+ std::ostringstream outputStream;
+ if (res == cmProcess::State::Exited) {
+ bool success = !forceFail &&
+ (retVal == 0 ||
+ !this->TestProperties->RequiredRegularExpressions.empty());
+ if (this->TestProperties->SkipReturnCode >= 0 &&
+ this->TestProperties->SkipReturnCode == retVal) {
+ this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
+ std::ostringstream s;
+ s << "SKIP_RETURN_CODE=" << this->TestProperties->SkipReturnCode;
+ this->TestResult.CompletionStatus = s.str();
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Skipped ");
+ skipped = true;
+ } else if ((success && !this->TestProperties->WillFail) ||
+ (!success && this->TestProperties->WillFail)) {
+ this->TestResult.Status = cmCTestTestHandler::COMPLETED;
+ outputStream << " Passed ";
+ } else {
+ this->TestResult.Status = cmCTestTestHandler::FAILED;
+ outputStream << "***Failed " << reason;
+ outputTestErrorsToConsole = this->CTest->OutputTestOutputOnTestFailure;
+ }
+ } else if (res == cmProcess::State::Expired) {
+ outputStream << "***Timeout ";
+ this->TestResult.Status = cmCTestTestHandler::TIMEOUT;
+ outputTestErrorsToConsole = this->CTest->OutputTestOutputOnTestFailure;
+ } else if (res == cmProcess::State::Exception) {
+ outputTestErrorsToConsole = this->CTest->OutputTestOutputOnTestFailure;
+ outputStream << "***Exception: ";
+ this->TestResult.ExceptionStatus =
+ this->TestProcess->GetExitExceptionString();
+ switch (this->TestProcess->GetExitException()) {
+ case cmProcess::Exception::Fault:
+ outputStream << "SegFault";
+ this->TestResult.Status = cmCTestTestHandler::SEGFAULT;
+ break;
+ case cmProcess::Exception::Illegal:
+ outputStream << "Illegal";
+ this->TestResult.Status = cmCTestTestHandler::ILLEGAL;
+ break;
+ case cmProcess::Exception::Interrupt:
+ outputStream << "Interrupt";
+ this->TestResult.Status = cmCTestTestHandler::INTERRUPT;
+ break;
+ case cmProcess::Exception::Numerical:
+ outputStream << "Numerical";
+ this->TestResult.Status = cmCTestTestHandler::NUMERICAL;
+ break;
+ default:
+ cmCTestLog(this->CTest, HANDLER_OUTPUT,
+ this->TestResult.ExceptionStatus);
+ this->TestResult.Status = cmCTestTestHandler::OTHER_FAULT;
+ }
+ } else if ("Disabled" == this->TestResult.CompletionStatus) {
+ outputStream << "***Not Run (Disabled) ";
+ } else // cmProcess::State::Error
+ {
+ outputStream << "***Not Run ";
+ }
+
+ passed = this->TestResult.Status == cmCTestTestHandler::COMPLETED;
+ char buf[1024];
+ sprintf(buf, "%6.2f sec", this->TestProcess->GetTotalTime().count());
+ outputStream << buf << "\n";
+
+ if (this->CTest->GetTestProgressOutput()) {
+ if (!passed) {
+ // If the test did not pass, reprint test name and error
+ std::string output = GetTestPrefix(completed, total);
+ std::string testName = this->TestProperties->Name;
+ const int maxTestNameWidth = this->CTest->GetMaxTestNameWidth();
+ testName.resize(maxTestNameWidth + 4, '.');
+
+ output += testName;
+ output += outputStream.str();
+ outputStream.str("");
+ outputStream.clear();
+ outputStream << output;
+ cmCTestLog(this->CTest, HANDLER_TEST_PROGRESS_OUTPUT, "\n"); // flush
+ }
+ if (completed == total) {
+ std::string testName =
+ GetTestPrefix(completed, total) + this->TestProperties->Name + "\n";
+ cmCTestLog(this->CTest, HANDLER_TEST_PROGRESS_OUTPUT, testName);
+ }
+ }
+ if (!this->CTest->GetTestProgressOutput() || !passed) {
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, outputStream.str());
+ }
+
+ if (outputTestErrorsToConsole) {
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, this->ProcessOutput << std::endl);
+ }
+
+ if (this->TestHandler->LogFile) {
+ *this->TestHandler->LogFile << "Test time = " << buf << std::endl;
+ }
+
+ this->DartProcessing();
+
+ // if this is doing MemCheck then all the output needs to be put into
+ // Output since that is what is parsed by cmCTestMemCheckHandler
+ if (!this->TestHandler->MemCheck && started) {
+ this->TestHandler->CleanTestOutput(
+ this->ProcessOutput,
+ static_cast<size_t>(
+ this->TestResult.Status == cmCTestTestHandler::COMPLETED
+ ? this->TestHandler->CustomMaximumPassedTestOutputSize
+ : this->TestHandler->CustomMaximumFailedTestOutputSize));
+ }
+ this->TestResult.Reason = reason;
+ if (this->TestHandler->LogFile) {
+ bool pass = true;
+ const char* reasonType = "Test Pass Reason";
+ if (this->TestResult.Status != cmCTestTestHandler::COMPLETED &&
+ this->TestResult.Status != cmCTestTestHandler::NOT_RUN) {
+ reasonType = "Test Fail Reason";
+ pass = false;
+ }
+ auto ttime = this->TestProcess->GetTotalTime();
+ auto hours = std::chrono::duration_cast<std::chrono::hours>(ttime);
+ ttime -= hours;
+ auto minutes = std::chrono::duration_cast<std::chrono::minutes>(ttime);
+ ttime -= minutes;
+ auto seconds = std::chrono::duration_cast<std::chrono::seconds>(ttime);
+ char buffer[100];
+ sprintf(buffer, "%02d:%02d:%02d", static_cast<unsigned>(hours.count()),
+ static_cast<unsigned>(minutes.count()),
+ static_cast<unsigned>(seconds.count()));
+ *this->TestHandler->LogFile
+ << "----------------------------------------------------------"
+ << std::endl;
+ if (!this->TestResult.Reason.empty()) {
+ *this->TestHandler->LogFile << reasonType << ":\n"
+ << this->TestResult.Reason << "\n";
+ } else {
+ if (pass) {
+ *this->TestHandler->LogFile << "Test Passed.\n";
+ } else {
+ *this->TestHandler->LogFile << "Test Failed.\n";
+ }
+ }
+ *this->TestHandler->LogFile
+ << "\"" << this->TestProperties->Name
+ << "\" end time: " << this->CTest->CurrentTime() << std::endl
+ << "\"" << this->TestProperties->Name << "\" time elapsed: " << buffer
+ << std::endl
+ << "----------------------------------------------------------"
+ << std::endl
+ << std::endl;
+ }
+ // if the test actually started and ran
+ // record the results in TestResult
+ if (started) {
+ std::string compressedOutput;
+ if (!this->TestHandler->MemCheck &&
+ this->CTest->ShouldCompressTestOutput()) {
+ std::string str = this->ProcessOutput;
+ if (this->CTest->CompressString(str)) {
+ compressedOutput = std::move(str);
+ }
+ }
+ bool compress = !compressedOutput.empty() &&
+ compressedOutput.length() < this->ProcessOutput.length();
+ this->TestResult.Output =
+ compress ? compressedOutput : this->ProcessOutput;
+ this->TestResult.CompressOutput = compress;
+ this->TestResult.ReturnValue = this->TestProcess->GetExitValue();
+ if (!skipped) {
+ this->TestResult.CompletionStatus = "Completed";
+ }
+ this->TestResult.ExecutionTime = this->TestProcess->GetTotalTime();
+ this->MemCheckPostProcess();
+ this->ComputeWeightedCost();
+ }
+ // If the test does not need to rerun push the current TestResult onto the
+ // TestHandler vector
+ if (!this->NeedsToRerun()) {
+ this->TestHandler->TestResults.push_back(this->TestResult);
+ }
+ this->TestProcess.reset();
+ return passed || skipped;
+}
+
+bool cmCTestRunTest::StartAgain(size_t completed)
+{
+ if (!this->RunAgain) {
+ return false;
+ }
+ this->RunAgain = false; // reset
+ // change to tests directory
+ cmWorkingDirectory workdir(this->TestProperties->Directory);
+ if (workdir.Failed()) {
+ this->StartFailure("Failed to change working directory to " +
+ this->TestProperties->Directory + " : " +
+ std::strerror(workdir.GetLastResult()));
+ return true;
+ }
+
+ this->StartTest(completed, this->TotalNumberOfTests);
+ return true;
+}
+
+bool cmCTestRunTest::NeedsToRerun()
+{
+ this->NumberOfRunsLeft--;
+ if (this->NumberOfRunsLeft == 0) {
+ return false;
+ }
+ // if number of runs left is not 0, and we are running until
+ // we find a failed test, then return true so the test can be
+ // restarted
+ if (this->RunUntilFail &&
+ this->TestResult.Status == cmCTestTestHandler::COMPLETED) {
+ this->RunAgain = true;
+ return true;
+ }
+ return false;
+}
+void cmCTestRunTest::ComputeWeightedCost()
+{
+ double prev = static_cast<double>(this->TestProperties->PreviousRuns);
+ double avgcost = static_cast<double>(this->TestProperties->Cost);
+ double current = this->TestResult.ExecutionTime.count();
+
+ if (this->TestResult.Status == cmCTestTestHandler::COMPLETED) {
+ this->TestProperties->Cost =
+ static_cast<float>(((prev * avgcost) + current) / (prev + 1.0));
+ this->TestProperties->PreviousRuns++;
+ }
+}
+
+void cmCTestRunTest::MemCheckPostProcess()
+{
+ if (!this->TestHandler->MemCheck) {
+ return;
+ }
+ cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ this->Index << ": process test output now: "
+ << this->TestProperties->Name << " "
+ << this->TestResult.Name << std::endl,
+ this->TestHandler->GetQuiet());
+ cmCTestMemCheckHandler* handler =
+ static_cast<cmCTestMemCheckHandler*>(this->TestHandler);
+ handler->PostProcessTest(this->TestResult, this->Index);
+}
+
+void cmCTestRunTest::StartFailure(std::string const& output)
+{
+ // Still need to log the Start message so the test summary records our
+ // attempt to start this test
+ if (!this->CTest->GetTestProgressOutput()) {
+ cmCTestLog(this->CTest, HANDLER_OUTPUT,
+ std::setw(2 * getNumWidth(this->TotalNumberOfTests) + 8)
+ << "Start "
+ << std::setw(getNumWidth(this->TestHandler->GetMaxIndex()))
+ << this->TestProperties->Index << ": "
+ << this->TestProperties->Name << std::endl);
+ }
+
+ this->ProcessOutput.clear();
+ if (!output.empty()) {
+ *this->TestHandler->LogFile << output << std::endl;
+ cmCTestLog(this->CTest, ERROR_MESSAGE, output << std::endl);
+ }
+
+ this->TestResult.Properties = this->TestProperties;
+ this->TestResult.ExecutionTime = cmDuration::zero();
+ this->TestResult.CompressOutput = false;
+ this->TestResult.ReturnValue = -1;
+ this->TestResult.CompletionStatus = "Failed to start";
+ this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
+ this->TestResult.TestCount = this->TestProperties->Index;
+ this->TestResult.Name = this->TestProperties->Name;
+ this->TestResult.Path = this->TestProperties->Directory;
+ this->TestResult.Output = output;
+ this->TestResult.FullCommandLine.clear();
+ this->TestProcess = cm::make_unique<cmProcess>(*this);
+}
+
+std::string cmCTestRunTest::GetTestPrefix(size_t completed, size_t total) const
+{
+ std::ostringstream outputStream;
+ outputStream << std::setw(getNumWidth(total)) << completed << "/";
+ outputStream << std::setw(getNumWidth(total)) << total << " ";
+
+ if (this->TestHandler->MemCheck) {
+ outputStream << "MemCheck";
+ } else {
+ outputStream << "Test";
+ }
+
+ std::ostringstream indexStr;
+ indexStr << " #" << this->Index << ":";
+ outputStream << std::setw(3 + getNumWidth(this->TestHandler->GetMaxIndex()))
+ << indexStr.str();
+ outputStream << " ";
+
+ return outputStream.str();
+}
+
+// Starts the execution of a test. Returns once it has started
+bool cmCTestRunTest::StartTest(size_t completed, size_t total)
+{
+ this->TotalNumberOfTests = total; // save for rerun case
+ if (!this->CTest->GetTestProgressOutput()) {
+ cmCTestLog(this->CTest, HANDLER_OUTPUT,
+ std::setw(2 * getNumWidth(total) + 8)
+ << "Start "
+ << std::setw(getNumWidth(this->TestHandler->GetMaxIndex()))
+ << this->TestProperties->Index << ": "
+ << this->TestProperties->Name << std::endl);
+ } else {
+ std::string testName =
+ GetTestPrefix(completed, total) + this->TestProperties->Name + "\n";
+ cmCTestLog(this->CTest, HANDLER_TEST_PROGRESS_OUTPUT, testName);
+ }
+
+ this->ProcessOutput.clear();
+
+ // Return immediately if test is disabled
+ if (this->TestProperties->Disabled) {
+ this->TestResult.Properties = this->TestProperties;
+ this->TestResult.ExecutionTime = cmDuration::zero();
+ this->TestResult.CompressOutput = false;
+ this->TestResult.ReturnValue = -1;
+ this->TestResult.CompletionStatus = "Disabled";
+ this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
+ this->TestResult.TestCount = this->TestProperties->Index;
+ this->TestResult.Name = this->TestProperties->Name;
+ this->TestResult.Path = this->TestProperties->Directory;
+ this->TestProcess = cm::make_unique<cmProcess>(*this);
+ this->TestResult.Output = "Disabled";
+ this->TestResult.FullCommandLine.clear();
+ return false;
+ }
+
+ this->TestResult.Properties = this->TestProperties;
+ this->TestResult.ExecutionTime = cmDuration::zero();
+ this->TestResult.CompressOutput = false;
+ this->TestResult.ReturnValue = -1;
+ this->TestResult.CompletionStatus = "Failed to start";
+ this->TestResult.Status = cmCTestTestHandler::BAD_COMMAND;
+ this->TestResult.TestCount = this->TestProperties->Index;
+ this->TestResult.Name = this->TestProperties->Name;
+ this->TestResult.Path = this->TestProperties->Directory;
+
+ // Check for failed fixture dependencies before we even look at the command
+ // arguments because if we are not going to run the test, the command and
+ // 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 = cm::make_unique<cmProcess>(*this);
+ std::string msg = "Failed test dependencies:";
+ for (std::string const& failedDep : this->FailedDependencies) {
+ msg += " " + failedDep;
+ }
+ *this->TestHandler->LogFile << msg << std::endl;
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, msg << std::endl);
+ this->TestResult.Output = msg;
+ this->TestResult.FullCommandLine.clear();
+ this->TestResult.CompletionStatus = "Fixture dependency failed";
+ this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
+ return false;
+ }
+
+ this->ComputeArguments();
+ std::vector<std::string>& args = this->TestProperties->Args;
+ if (args.size() >= 2 && args[1] == "NOT_AVAILABLE") {
+ this->TestProcess = cm::make_unique<cmProcess>(*this);
+ std::string msg;
+ if (this->CTest->GetConfigType().empty()) {
+ msg = "Test not available without configuration.";
+ msg += " (Missing \"-C <config>\"?)";
+ } else {
+ msg = "Test not available in configuration \"";
+ msg += this->CTest->GetConfigType();
+ msg += "\".";
+ }
+ *this->TestHandler->LogFile << msg << std::endl;
+ cmCTestLog(this->CTest, ERROR_MESSAGE, msg << std::endl);
+ this->TestResult.Output = msg;
+ this->TestResult.FullCommandLine.clear();
+ this->TestResult.CompletionStatus = "Missing Configuration";
+ this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
+ return false;
+ }
+
+ // Check if all required files exist
+ for (std::string const& file : this->TestProperties->RequiredFiles) {
+ if (!cmSystemTools::FileExists(file)) {
+ // Required file was not found
+ this->TestProcess = cm::make_unique<cmProcess>(*this);
+ *this->TestHandler->LogFile << "Unable to find required file: " << file
+ << std::endl;
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Unable to find required file: " << file << std::endl);
+ this->TestResult.Output = "Unable to find required file: " + file;
+ this->TestResult.FullCommandLine.clear();
+ this->TestResult.CompletionStatus = "Required Files Missing";
+ this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
+ return false;
+ }
+ }
+ // log and return if we did not find the executable
+ if (this->ActualCommand.empty()) {
+ // if the command was not found create a TestResult object
+ // that has that information
+ this->TestProcess = cm::make_unique<cmProcess>(*this);
+ *this->TestHandler->LogFile << "Unable to find executable: " << args[1]
+ << std::endl;
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Unable to find executable: " << args[1] << std::endl);
+ this->TestResult.Output = "Unable to find executable: " + args[1];
+ this->TestResult.FullCommandLine.clear();
+ this->TestResult.CompletionStatus = "Unable to find executable";
+ this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
+ return false;
+ }
+ this->StartTime = this->CTest->CurrentTime();
+
+ auto timeout = this->TestProperties->Timeout;
+
+ this->TimeoutIsForStopTime = 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) {
+ this->TimeoutIsForStopTime = true;
+ timeout = stop_timeout;
+ }
+ }
+
+ return this->ForkProcess(timeout, this->TestProperties->ExplicitTimeout,
+ &this->TestProperties->Environment,
+ &this->TestProperties->Affinity);
+}
+
+void cmCTestRunTest::ComputeArguments()
+{
+ this->Arguments.clear(); // reset because this might be a rerun
+ std::vector<std::string>::const_iterator j =
+ this->TestProperties->Args.begin();
+ ++j; // skip test name
+ // find the test executable
+ if (this->TestHandler->MemCheck) {
+ cmCTestMemCheckHandler* handler =
+ static_cast<cmCTestMemCheckHandler*>(this->TestHandler);
+ this->ActualCommand = handler->MemoryTester;
+ this->TestProperties->Args[1] = this->TestHandler->FindTheExecutable(
+ this->TestProperties->Args[1].c_str());
+ } else {
+ this->ActualCommand = this->TestHandler->FindTheExecutable(
+ this->TestProperties->Args[1].c_str());
+ ++j; // skip the executable (it will be actualCommand)
+ }
+ std::string testCommand =
+ cmSystemTools::ConvertToOutputPath(this->ActualCommand);
+
+ // Prepends memcheck args to our command string
+ this->TestHandler->GenerateTestCommand(this->Arguments, this->Index);
+ for (std::string const& arg : this->Arguments) {
+ testCommand += " \"";
+ testCommand += arg;
+ testCommand += "\"";
+ }
+
+ for (; j != this->TestProperties->Args.end(); ++j) {
+ testCommand += " \"";
+ testCommand += *j;
+ testCommand += "\"";
+ this->Arguments.push_back(*j);
+ }
+ this->TestResult.FullCommandLine = testCommand;
+
+ // Print the test command in verbose mode
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ std::endl
+ << this->Index << ": "
+ << (this->TestHandler->MemCheck ? "MemCheck" : "Test")
+ << " command: " << testCommand << std::endl);
+
+ // Print any test-specific env vars in verbose mode
+ if (!this->TestProperties->Environment.empty()) {
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ this->Index << ": "
+ << "Environment variables: " << std::endl);
+ }
+ for (std::string const& env : this->TestProperties->Environment) {
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ this->Index << ": " << env << std::endl);
+ }
+}
+
+void cmCTestRunTest::DartProcessing()
+{
+ if (!this->ProcessOutput.empty() &&
+ this->ProcessOutput.find("<DartMeasurement") != std::string::npos) {
+ if (this->TestHandler->DartStuff.find(this->ProcessOutput)) {
+ this->TestResult.DartString = this->TestHandler->DartStuff.match(1);
+ // keep searching and replacing until none are left
+ while (this->TestHandler->DartStuff1.find(this->ProcessOutput)) {
+ // replace the exact match for the string
+ cmSystemTools::ReplaceString(
+ this->ProcessOutput, this->TestHandler->DartStuff1.match(1).c_str(),
+ "");
+ }
+ }
+ }
+}
+
+bool cmCTestRunTest::ForkProcess(cmDuration testTimeOut, bool explicitTimeout,
+ std::vector<std::string>* environment,
+ std::vector<size_t>* affinity)
+{
+ this->TestProcess = cm::make_unique<cmProcess>(*this);
+ this->TestProcess->SetId(this->Index);
+ this->TestProcess->SetWorkingDirectory(
+ this->TestProperties->Directory.c_str());
+ this->TestProcess->SetCommand(this->ActualCommand.c_str());
+ this->TestProcess->SetCommandArguments(this->Arguments);
+
+ // determine how much time we have
+ cmDuration timeout = this->CTest->GetRemainingTimeAllowed();
+ if (timeout != cmCTest::MaxDuration()) {
+ timeout -= std::chrono::minutes(2);
+ }
+ if (this->CTest->GetTimeOut() > cmDuration::zero() &&
+ this->CTest->GetTimeOut() < timeout) {
+ timeout = this->CTest->GetTimeOut();
+ }
+ if (testTimeOut > cmDuration::zero() &&
+ testTimeOut < this->CTest->GetRemainingTimeAllowed()) {
+ timeout = testTimeOut;
+ }
+ // always have at least 1 second if we got to here
+ if (timeout <= cmDuration::zero()) {
+ timeout = std::chrono::seconds(1);
+ }
+ // handle timeout explicitly set to 0
+ if (testTimeOut == cmDuration::zero() && explicitTimeout) {
+ timeout = cmDuration::zero();
+ }
+ cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ this->Index << ": "
+ << "Test timeout computed to be: "
+ << cmDurationTo<unsigned int>(timeout)
+ << "\n",
+ this->TestHandler->GetQuiet());
+
+ this->TestProcess->SetTimeout(timeout);
+
+#ifdef CMAKE_BUILD_WITH_CMAKE
+ cmSystemTools::SaveRestoreEnvironment sre;
+#endif
+
+ if (environment && !environment->empty()) {
+ cmSystemTools::AppendEnv(*environment);
+ }
+
+ return this->TestProcess->StartProcess(this->MultiTestHandler.Loop,
+ affinity);
+}
+
+void cmCTestRunTest::WriteLogOutputTop(size_t completed, size_t total)
+{
+ std::ostringstream outputStream;
+
+ // If this is the last or only run of this test, or progress output is
+ // requested, then print out completed / total.
+ // Only issue is if a test fails and we are running until fail
+ // then it will never print out the completed / total, same would
+ // got for run until pass. Trick is when this is called we don't
+ // yet know if we are passing or failing.
+ if (this->NumberOfRunsLeft == 1 || this->CTest->GetTestProgressOutput()) {
+ outputStream << std::setw(getNumWidth(total)) << completed << "/";
+ outputStream << std::setw(getNumWidth(total)) << total << " ";
+ }
+ // if this is one of several runs of a test just print blank space
+ // to keep things neat
+ else {
+ outputStream << std::setw(getNumWidth(total)) << " ";
+ outputStream << std::setw(getNumWidth(total)) << " ";
+ }
+
+ if (this->TestHandler->MemCheck) {
+ outputStream << "MemCheck";
+ } else {
+ outputStream << "Test";
+ }
+
+ std::ostringstream indexStr;
+ indexStr << " #" << this->Index << ":";
+ outputStream << std::setw(3 + getNumWidth(this->TestHandler->GetMaxIndex()))
+ << indexStr.str();
+ outputStream << " ";
+
+ const int maxTestNameWidth = this->CTest->GetMaxTestNameWidth();
+ std::string outname = this->TestProperties->Name + " ";
+ outname.resize(maxTestNameWidth + 4, '.');
+ outputStream << outname;
+
+ *this->TestHandler->LogFile << this->TestProperties->Index << "/"
+ << this->TestHandler->TotalNumberOfTests
+ << " Testing: " << this->TestProperties->Name
+ << std::endl;
+ *this->TestHandler->LogFile << this->TestProperties->Index << "/"
+ << this->TestHandler->TotalNumberOfTests
+ << " Test: " << this->TestProperties->Name
+ << std::endl;
+ *this->TestHandler->LogFile << "Command: \"" << this->ActualCommand << "\"";
+
+ for (std::string const& arg : this->Arguments) {
+ *this->TestHandler->LogFile << " \"" << arg << "\"";
+ }
+ *this->TestHandler->LogFile
+ << std::endl
+ << "Directory: " << this->TestProperties->Directory << std::endl
+ << "\"" << this->TestProperties->Name
+ << "\" start time: " << this->StartTime << std::endl;
+
+ *this->TestHandler->LogFile
+ << "Output:" << std::endl
+ << "----------------------------------------------------------"
+ << std::endl;
+ *this->TestHandler->LogFile << this->ProcessOutput << "<end of output>"
+ << std::endl;
+
+ if (!this->CTest->GetTestProgressOutput()) {
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, outputStream.str());
+ }
+
+ cmCTestLog(this->CTest, DEBUG,
+ "Testing " << this->TestProperties->Name << " ... ");
+}
+
+void cmCTestRunTest::FinalizeTest()
+{
+ this->MultiTestHandler.FinishTestProcess(this, true);
+}