1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmCTestRunTest.h"
7 #include <cstddef> // IWYU pragma: keep
18 #include <cm/optional>
19 #include <cm/string_view>
20 #include <cmext/string_view>
22 #include "cmsys/RegularExpression.hxx"
25 #include "cmCTestMemCheckHandler.h"
26 #include "cmCTestMultiProcessHandler.h"
27 #include "cmProcess.h"
28 #include "cmStringAlgorithms.h"
29 #include "cmSystemTools.h"
30 #include "cmWorkingDirectory.h"
32 cmCTestRunTest::cmCTestRunTest(cmCTestMultiProcessHandler& multiHandler)
33 : MultiTestHandler(multiHandler)
35 this->CTest = multiHandler.CTest;
36 this->TestHandler = multiHandler.TestHandler;
37 this->TestResult.ExecutionTime = cmDuration::zero();
38 this->TestResult.ReturnValue = 0;
39 this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
40 this->TestResult.TestCount = 0;
41 this->TestResult.Properties = nullptr;
44 void cmCTestRunTest::CheckOutput(std::string const& line)
46 cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
47 this->GetIndex() << ": " << line << std::endl);
49 // Check for special CTest XML tags in this line of output.
50 // If any are found, this line is excluded from ProcessOutput.
51 if (!line.empty() && line.find("<CTest") != std::string::npos) {
52 bool ctest_tag_found = false;
53 if (this->TestHandler->CustomCompletionStatusRegex.find(line)) {
54 ctest_tag_found = true;
55 this->TestResult.CustomCompletionStatus =
56 this->TestHandler->CustomCompletionStatusRegex.match(1);
57 cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
58 this->GetIndex() << ": "
59 << "Test Details changed to '"
60 << this->TestResult.CustomCompletionStatus
62 } else if (this->TestHandler->CustomLabelRegex.find(line)) {
63 ctest_tag_found = true;
64 auto label = this->TestHandler->CustomLabelRegex.match(1);
65 auto& labels = this->TestProperties->Labels;
66 if (std::find(labels.begin(), labels.end(), label) == labels.end()) {
67 labels.push_back(label);
68 std::sort(labels.begin(), labels.end());
69 cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
72 << "Test Label added: '" << label << "'" << std::endl);
75 if (ctest_tag_found) {
80 this->ProcessOutput += line;
81 this->ProcessOutput += "\n";
83 // Check for TIMEOUT_AFTER_MATCH property.
84 if (!this->TestProperties->TimeoutRegularExpressions.empty()) {
85 for (auto& reg : this->TestProperties->TimeoutRegularExpressions) {
86 if (reg.first.find(this->ProcessOutput)) {
87 cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
90 << "Test timeout changed to "
91 << std::chrono::duration_cast<std::chrono::seconds>(
92 this->TestProperties->AlternateTimeout)
95 this->TestProcess->ResetStartTime();
96 this->TestProcess->ChangeTimeout(
97 this->TestProperties->AlternateTimeout);
98 this->TestProperties->TimeoutRegularExpressions.clear();
105 bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started)
107 this->WriteLogOutputTop(completed, total);
110 cmProcess::State res =
111 started ? this->TestProcess->GetProcessStatus() : cmProcess::State::Error;
112 if (res != cmProcess::State::Expired) {
113 this->TimeoutIsForStopTime = false;
115 std::int64_t retVal = this->TestProcess->GetExitValue();
116 bool forceFail = false;
117 bool forceSkip = false;
118 bool skipped = false;
119 bool outputTestErrorsToConsole = false;
120 if (!this->TestProperties->RequiredRegularExpressions.empty() &&
121 this->FailedDependencies.empty()) {
123 for (auto& pass : this->TestProperties->RequiredRegularExpressions) {
124 if (pass.first.find(this->ProcessOutput)) {
126 reason = cmStrCat("Required regular expression found. Regex=[",
132 reason = "Required regular expression not found. Regex=[";
133 for (auto& pass : this->TestProperties->RequiredRegularExpressions) {
134 reason += pass.second;
141 if (!this->TestProperties->ErrorRegularExpressions.empty() &&
142 this->FailedDependencies.empty()) {
143 for (auto& fail : this->TestProperties->ErrorRegularExpressions) {
144 if (fail.first.find(this->ProcessOutput)) {
145 reason = cmStrCat("Error regular expression found in output. Regex=[",
152 if (!this->TestProperties->SkipRegularExpressions.empty() &&
153 this->FailedDependencies.empty()) {
154 for (auto& skip : this->TestProperties->SkipRegularExpressions) {
155 if (skip.first.find(this->ProcessOutput)) {
156 reason = cmStrCat("Skip regular expression found in output. Regex=[",
163 std::ostringstream outputStream;
164 if (res == cmProcess::State::Exited) {
165 bool success = !forceFail &&
167 !this->TestProperties->RequiredRegularExpressions.empty());
168 if ((this->TestProperties->SkipReturnCode >= 0 &&
169 this->TestProperties->SkipReturnCode == retVal) ||
171 this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
172 std::ostringstream s;
174 s << "SKIP_REGULAR_EXPRESSION_MATCHED";
176 s << "SKIP_RETURN_CODE=" << this->TestProperties->SkipReturnCode;
178 this->TestResult.CompletionStatus = s.str();
179 outputStream << "***Skipped ";
181 } else if (success != this->TestProperties->WillFail) {
182 this->TestResult.Status = cmCTestTestHandler::COMPLETED;
183 outputStream << " Passed ";
185 this->TestResult.Status = cmCTestTestHandler::FAILED;
186 outputStream << "***Failed " << reason;
187 outputTestErrorsToConsole =
188 this->CTest->GetOutputTestOutputOnTestFailure();
190 } else if (res == cmProcess::State::Expired) {
191 outputStream << "***Timeout ";
192 this->TestResult.Status = cmCTestTestHandler::TIMEOUT;
193 outputTestErrorsToConsole =
194 this->CTest->GetOutputTestOutputOnTestFailure();
195 } else if (res == cmProcess::State::Exception) {
196 outputTestErrorsToConsole =
197 this->CTest->GetOutputTestOutputOnTestFailure();
198 outputStream << "***Exception: ";
199 this->TestResult.ExceptionStatus =
200 this->TestProcess->GetExitExceptionString();
201 switch (this->TestProcess->GetExitException()) {
202 case cmProcess::Exception::Fault:
203 outputStream << "SegFault";
204 this->TestResult.Status = cmCTestTestHandler::SEGFAULT;
206 case cmProcess::Exception::Illegal:
207 outputStream << "Illegal";
208 this->TestResult.Status = cmCTestTestHandler::ILLEGAL;
210 case cmProcess::Exception::Interrupt:
211 outputStream << "Interrupt";
212 this->TestResult.Status = cmCTestTestHandler::INTERRUPT;
214 case cmProcess::Exception::Numerical:
215 outputStream << "Numerical";
216 this->TestResult.Status = cmCTestTestHandler::NUMERICAL;
219 cmCTestLog(this->CTest, HANDLER_OUTPUT,
220 this->TestResult.ExceptionStatus);
221 this->TestResult.Status = cmCTestTestHandler::OTHER_FAULT;
223 } else if ("Disabled" == this->TestResult.CompletionStatus) {
224 outputStream << "***Not Run (Disabled) ";
225 } else // cmProcess::State::Error
227 outputStream << "***Not Run ";
230 passed = this->TestResult.Status == cmCTestTestHandler::COMPLETED;
232 snprintf(buf, sizeof(buf), "%6.2f sec",
233 this->TestProcess->GetTotalTime().count());
234 outputStream << buf << "\n";
236 bool passedOrSkipped = passed || skipped;
237 if (this->CTest->GetTestProgressOutput()) {
238 if (!passedOrSkipped) {
239 // If the test did not pass, reprint test name and error
240 std::string output = this->GetTestPrefix(completed, total);
241 std::string testName = this->TestProperties->Name;
242 const int maxTestNameWidth = this->CTest->GetMaxTestNameWidth();
243 testName.resize(maxTestNameWidth + 4, '.');
246 output += outputStream.str();
247 outputStream.str("");
248 outputStream.clear();
249 outputStream << output;
250 cmCTestLog(this->CTest, HANDLER_TEST_PROGRESS_OUTPUT, "\n"); // flush
252 if (completed == total) {
253 std::string testName = this->GetTestPrefix(completed, total) +
254 this->TestProperties->Name + "\n";
255 cmCTestLog(this->CTest, HANDLER_TEST_PROGRESS_OUTPUT, testName);
258 if (!this->CTest->GetTestProgressOutput() || !passedOrSkipped) {
259 cmCTestLog(this->CTest, HANDLER_OUTPUT, outputStream.str());
262 if (outputTestErrorsToConsole) {
263 cmCTestLog(this->CTest, HANDLER_OUTPUT, this->ProcessOutput << std::endl);
266 if (this->TestHandler->LogFile) {
267 *this->TestHandler->LogFile << "Test time = " << buf << std::endl;
270 this->ParseOutputForMeasurements();
272 // if this is doing MemCheck then all the output needs to be put into
273 // Output since that is what is parsed by cmCTestMemCheckHandler
274 if (!this->TestHandler->MemCheck && started) {
275 this->TestHandler->CleanTestOutput(
278 this->TestResult.Status == cmCTestTestHandler::COMPLETED
279 ? this->TestHandler->CustomMaximumPassedTestOutputSize
280 : this->TestHandler->CustomMaximumFailedTestOutputSize),
281 this->TestHandler->TestOutputTruncation);
283 this->TestResult.Reason = reason;
284 if (this->TestHandler->LogFile) {
286 const char* reasonType = "Test Pass Reason";
287 if (this->TestResult.Status != cmCTestTestHandler::COMPLETED &&
288 this->TestResult.Status != cmCTestTestHandler::NOT_RUN) {
289 reasonType = "Test Fail Reason";
292 auto ttime = this->TestProcess->GetTotalTime();
293 auto hours = std::chrono::duration_cast<std::chrono::hours>(ttime);
295 auto minutes = std::chrono::duration_cast<std::chrono::minutes>(ttime);
297 auto seconds = std::chrono::duration_cast<std::chrono::seconds>(ttime);
299 snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d",
300 static_cast<unsigned>(hours.count()),
301 static_cast<unsigned>(minutes.count()),
302 static_cast<unsigned>(seconds.count()));
303 *this->TestHandler->LogFile
304 << "----------------------------------------------------------"
306 if (!this->TestResult.Reason.empty()) {
307 *this->TestHandler->LogFile << reasonType << ":\n"
308 << this->TestResult.Reason << "\n";
311 *this->TestHandler->LogFile << "Test Passed.\n";
313 *this->TestHandler->LogFile << "Test Failed.\n";
316 *this->TestHandler->LogFile
317 << "\"" << this->TestProperties->Name
318 << "\" end time: " << this->CTest->CurrentTime() << std::endl
319 << "\"" << this->TestProperties->Name << "\" time elapsed: " << buffer
321 << "----------------------------------------------------------"
325 // if the test actually started and ran
326 // record the results in TestResult
328 std::string compressedOutput;
329 if (!this->TestHandler->MemCheck &&
330 this->CTest->ShouldCompressTestOutput()) {
331 std::string str = this->ProcessOutput;
332 if (this->CTest->CompressString(str)) {
333 compressedOutput = std::move(str);
336 bool compress = !compressedOutput.empty() &&
337 compressedOutput.length() < this->ProcessOutput.length();
338 this->TestResult.Output =
339 compress ? compressedOutput : this->ProcessOutput;
340 this->TestResult.CompressOutput = compress;
341 this->TestResult.ReturnValue = this->TestProcess->GetExitValue();
343 this->TestResult.CompletionStatus = "Completed";
345 this->TestResult.ExecutionTime = this->TestProcess->GetTotalTime();
346 this->MemCheckPostProcess();
347 this->ComputeWeightedCost();
349 // If the test does not need to rerun push the current TestResult onto the
350 // TestHandler vector
351 if (!this->NeedsToRepeat()) {
352 this->TestHandler->TestResults.push_back(this->TestResult);
354 this->TestProcess.reset();
355 return passed || skipped;
358 bool cmCTestRunTest::StartAgain(std::unique_ptr<cmCTestRunTest> runner,
361 auto* testRun = runner.get();
363 if (!testRun->RunAgain) {
366 testRun->RunAgain = false; // reset
367 testRun->TestProcess = cm::make_unique<cmProcess>(std::move(runner));
368 // change to tests directory
369 cmWorkingDirectory workdir(testRun->TestProperties->Directory);
370 if (workdir.Failed()) {
371 testRun->StartFailure("Failed to change working directory to " +
372 testRun->TestProperties->Directory + " : " +
373 std::strerror(workdir.GetLastResult()),
374 "Failed to change working directory");
378 testRun->StartTest(completed, testRun->TotalNumberOfTests);
382 bool cmCTestRunTest::NeedsToRepeat()
384 this->NumberOfRunsLeft--;
385 if (this->NumberOfRunsLeft == 0) {
388 // If a test is marked as NOT_RUN it will not be repeated
389 // no matter the repeat settings, so just record it as-is.
390 if (this->TestResult.Status == cmCTestTestHandler::NOT_RUN) {
393 // if number of runs left is not 0, and we are running until
394 // we find a failed (or passed) test, then return true so the test can be
396 if ((this->RepeatMode == cmCTest::Repeat::UntilFail &&
397 this->TestResult.Status == cmCTestTestHandler::COMPLETED) ||
398 (this->RepeatMode == cmCTest::Repeat::UntilPass &&
399 this->TestResult.Status != cmCTestTestHandler::COMPLETED) ||
400 (this->RepeatMode == cmCTest::Repeat::AfterTimeout &&
401 this->TestResult.Status == cmCTestTestHandler::TIMEOUT)) {
402 this->RunAgain = true;
407 void cmCTestRunTest::ComputeWeightedCost()
409 double prev = static_cast<double>(this->TestProperties->PreviousRuns);
410 double avgcost = static_cast<double>(this->TestProperties->Cost);
411 double current = this->TestResult.ExecutionTime.count();
413 if (this->TestResult.Status == cmCTestTestHandler::COMPLETED) {
414 this->TestProperties->Cost =
415 static_cast<float>(((prev * avgcost) + current) / (prev + 1.0));
416 this->TestProperties->PreviousRuns++;
420 void cmCTestRunTest::MemCheckPostProcess()
422 if (!this->TestHandler->MemCheck) {
425 cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
426 this->Index << ": process test output now: "
427 << this->TestProperties->Name << " "
428 << this->TestResult.Name << std::endl,
429 this->TestHandler->GetQuiet());
430 cmCTestMemCheckHandler* handler =
431 static_cast<cmCTestMemCheckHandler*>(this->TestHandler);
432 handler->PostProcessTest(this->TestResult, this->Index);
435 void cmCTestRunTest::StartFailure(std::unique_ptr<cmCTestRunTest> runner,
436 std::string const& output,
437 std::string const& detail)
439 auto* testRun = runner.get();
441 testRun->TestProcess = cm::make_unique<cmProcess>(std::move(runner));
442 testRun->StartFailure(output, detail);
444 testRun->FinalizeTest(false);
447 void cmCTestRunTest::StartFailure(std::string const& output,
448 std::string const& detail)
450 // Still need to log the Start message so the test summary records our
451 // attempt to start this test
452 if (!this->CTest->GetTestProgressOutput()) {
453 cmCTestLog(this->CTest, HANDLER_OUTPUT,
454 std::setw(2 * getNumWidth(this->TotalNumberOfTests) + 8)
456 << std::setw(getNumWidth(this->TestHandler->GetMaxIndex()))
457 << this->TestProperties->Index << ": "
458 << this->TestProperties->Name << std::endl);
461 this->ProcessOutput.clear();
462 if (!output.empty()) {
463 *this->TestHandler->LogFile << output << std::endl;
464 cmCTestLog(this->CTest, ERROR_MESSAGE, output << std::endl);
467 this->TestResult.Properties = this->TestProperties;
468 this->TestResult.ExecutionTime = cmDuration::zero();
469 this->TestResult.CompressOutput = false;
470 this->TestResult.ReturnValue = -1;
471 this->TestResult.CompletionStatus = detail;
472 this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
473 this->TestResult.TestCount = this->TestProperties->Index;
474 this->TestResult.Name = this->TestProperties->Name;
475 this->TestResult.Path = this->TestProperties->Directory;
476 this->TestResult.Output = output;
477 this->TestResult.FullCommandLine.clear();
478 this->TestResult.Environment.clear();
481 std::string cmCTestRunTest::GetTestPrefix(size_t completed, size_t total) const
483 std::ostringstream outputStream;
484 outputStream << std::setw(getNumWidth(total)) << completed << "/";
485 outputStream << std::setw(getNumWidth(total)) << total << " ";
487 if (this->TestHandler->MemCheck) {
488 outputStream << "MemCheck";
490 outputStream << "Test";
493 std::ostringstream indexStr;
494 indexStr << " #" << this->Index << ":";
495 outputStream << std::setw(3 + getNumWidth(this->TestHandler->GetMaxIndex()))
499 return outputStream.str();
502 bool cmCTestRunTest::StartTest(std::unique_ptr<cmCTestRunTest> runner,
503 size_t completed, size_t total)
505 auto* testRun = runner.get();
507 testRun->TestProcess = cm::make_unique<cmProcess>(std::move(runner));
509 if (!testRun->StartTest(completed, total)) {
510 testRun->FinalizeTest(false);
517 // Starts the execution of a test. Returns once it has started
518 bool cmCTestRunTest::StartTest(size_t completed, size_t total)
520 this->TotalNumberOfTests = total; // save for rerun case
521 if (!this->CTest->GetTestProgressOutput()) {
522 cmCTestLog(this->CTest, HANDLER_OUTPUT,
523 std::setw(2 * getNumWidth(total) + 8)
525 << std::setw(getNumWidth(this->TestHandler->GetMaxIndex()))
526 << this->TestProperties->Index << ": "
527 << this->TestProperties->Name << std::endl);
529 std::string testName = this->GetTestPrefix(completed, total) +
530 this->TestProperties->Name + "\n";
531 cmCTestLog(this->CTest, HANDLER_TEST_PROGRESS_OUTPUT, testName);
534 this->ProcessOutput.clear();
536 this->TestResult.Properties = this->TestProperties;
537 this->TestResult.ExecutionTime = cmDuration::zero();
538 this->TestResult.CompressOutput = false;
539 this->TestResult.ReturnValue = -1;
540 this->TestResult.TestCount = this->TestProperties->Index;
541 this->TestResult.Name = this->TestProperties->Name;
542 this->TestResult.Path = this->TestProperties->Directory;
544 // Return immediately if test is disabled
545 if (this->TestProperties->Disabled) {
546 this->TestResult.CompletionStatus = "Disabled";
547 this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
548 this->TestResult.Output = "Disabled";
549 this->TestResult.FullCommandLine.clear();
550 this->TestResult.Environment.clear();
554 this->TestResult.CompletionStatus = "Failed to start";
555 this->TestResult.Status = cmCTestTestHandler::BAD_COMMAND;
557 // Check for failed fixture dependencies before we even look at the command
558 // arguments because if we are not going to run the test, the command and
559 // its arguments are irrelevant. This matters for the case where a fixture
560 // dependency might be creating the executable we want to run.
561 if (!this->FailedDependencies.empty()) {
562 std::string msg = "Failed test dependencies:";
563 for (std::string const& failedDep : this->FailedDependencies) {
564 msg += " " + failedDep;
566 *this->TestHandler->LogFile << msg << std::endl;
567 cmCTestLog(this->CTest, HANDLER_OUTPUT, msg << std::endl);
568 this->TestResult.Output = msg;
569 this->TestResult.FullCommandLine.clear();
570 this->TestResult.Environment.clear();
571 this->TestResult.CompletionStatus = "Fixture dependency failed";
572 this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
576 this->ComputeArguments();
577 std::vector<std::string>& args = this->TestProperties->Args;
578 if (args.size() >= 2 && args[1] == "NOT_AVAILABLE") {
580 if (this->CTest->GetConfigType().empty()) {
581 msg = "Test not available without configuration. (Missing \"-C "
584 msg = cmStrCat("Test not available in configuration \"",
585 this->CTest->GetConfigType(), "\".");
587 *this->TestHandler->LogFile << msg << std::endl;
588 cmCTestLog(this->CTest, ERROR_MESSAGE, msg << std::endl);
589 this->TestResult.Output = msg;
590 this->TestResult.FullCommandLine.clear();
591 this->TestResult.Environment.clear();
592 this->TestResult.CompletionStatus = "Missing Configuration";
593 this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
597 // Check if all required files exist
598 for (std::string const& file : this->TestProperties->RequiredFiles) {
599 if (!cmSystemTools::FileExists(file)) {
600 // Required file was not found
601 *this->TestHandler->LogFile << "Unable to find required file: " << file
603 cmCTestLog(this->CTest, ERROR_MESSAGE,
604 "Unable to find required file: " << file << std::endl);
605 this->TestResult.Output = "Unable to find required file: " + file;
606 this->TestResult.FullCommandLine.clear();
607 this->TestResult.Environment.clear();
608 this->TestResult.CompletionStatus = "Required Files Missing";
609 this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
613 // log and return if we did not find the executable
614 if (this->ActualCommand.empty()) {
615 // if the command was not found create a TestResult object
616 // that has that information
617 *this->TestHandler->LogFile << "Unable to find executable: " << args[1]
619 cmCTestLog(this->CTest, ERROR_MESSAGE,
620 "Unable to find executable: " << args[1] << std::endl);
621 this->TestResult.Output = "Unable to find executable: " + args[1];
622 this->TestResult.FullCommandLine.clear();
623 this->TestResult.Environment.clear();
624 this->TestResult.CompletionStatus = "Unable to find executable";
625 this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
628 this->StartTime = this->CTest->CurrentTime();
630 auto timeout = this->TestProperties->Timeout;
632 this->TimeoutIsForStopTime = false;
633 std::chrono::system_clock::time_point stop_time = this->CTest->GetStopTime();
634 if (stop_time != std::chrono::system_clock::time_point()) {
635 std::chrono::duration<double> stop_timeout =
636 (stop_time - std::chrono::system_clock::now()) % std::chrono::hours(24);
638 if (stop_timeout <= std::chrono::duration<double>::zero()) {
639 stop_timeout = std::chrono::duration<double>::zero();
641 if (timeout == std::chrono::duration<double>::zero() ||
642 stop_timeout < timeout) {
643 this->TimeoutIsForStopTime = true;
644 timeout = stop_timeout;
648 return this->ForkProcess(timeout, this->TestProperties->ExplicitTimeout,
649 &this->TestProperties->Environment,
650 &this->TestProperties->EnvironmentModification,
651 &this->TestProperties->Affinity);
654 void cmCTestRunTest::ComputeArguments()
656 this->Arguments.clear(); // reset because this might be a rerun
657 auto j = this->TestProperties->Args.begin();
658 ++j; // skip test name
659 // find the test executable
660 if (this->TestHandler->MemCheck) {
661 cmCTestMemCheckHandler* handler =
662 static_cast<cmCTestMemCheckHandler*>(this->TestHandler);
663 this->ActualCommand = handler->MemoryTester;
664 this->TestProperties->Args[1] =
665 this->TestHandler->FindTheExecutable(this->TestProperties->Args[1]);
667 this->ActualCommand =
668 this->TestHandler->FindTheExecutable(this->TestProperties->Args[1]);
669 ++j; // skip the executable (it will be actualCommand)
671 std::string testCommand =
672 cmSystemTools::ConvertToOutputPath(this->ActualCommand);
674 // Prepends memcheck args to our command string
675 this->TestHandler->GenerateTestCommand(this->Arguments, this->Index);
676 for (std::string const& arg : this->Arguments) {
677 testCommand += " \"";
682 for (; j != this->TestProperties->Args.end(); ++j) {
683 testCommand += " \"";
686 this->Arguments.push_back(*j);
688 this->TestResult.FullCommandLine = testCommand;
690 // Print the test command in verbose mode
691 cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
693 << this->Index << ": "
694 << (this->TestHandler->MemCheck ? "MemCheck" : "Test")
695 << " command: " << testCommand << std::endl);
697 // Print any test-specific env vars in verbose mode
698 if (!this->TestProperties->Directory.empty()) {
699 cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
701 << "Working Directory: "
702 << this->TestProperties->Directory << std::endl);
705 // Print any test-specific env vars in verbose mode
706 if (!this->TestProperties->Environment.empty()) {
707 cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
709 << "Environment variables: " << std::endl);
711 for (std::string const& env : this->TestProperties->Environment) {
712 cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
713 this->Index << ": " << env << std::endl);
715 if (!this->TestProperties->EnvironmentModification.empty()) {
716 cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
718 << "Environment variable modifications: "
721 for (std::string const& envmod :
722 this->TestProperties->EnvironmentModification) {
723 cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
724 this->Index << ": " << envmod << std::endl);
728 void cmCTestRunTest::ParseOutputForMeasurements()
730 if (!this->ProcessOutput.empty() &&
731 (this->ProcessOutput.find("<DartMeasurement") != std::string::npos ||
732 this->ProcessOutput.find("<CTestMeasurement") != std::string::npos)) {
733 if (this->TestHandler->AllTestMeasurementsRegex.find(
734 this->ProcessOutput)) {
735 this->TestResult.TestMeasurementsOutput =
736 this->TestHandler->AllTestMeasurementsRegex.match(1);
737 // keep searching and replacing until none are left
738 while (this->TestHandler->SingleTestMeasurementRegex.find(
739 this->ProcessOutput)) {
740 // replace the exact match for the string
741 cmSystemTools::ReplaceString(
743 this->TestHandler->SingleTestMeasurementRegex.match(1).c_str(), "");
749 bool cmCTestRunTest::ForkProcess(
750 cmDuration testTimeOut, bool explicitTimeout,
751 std::vector<std::string>* environment,
752 std::vector<std::string>* environment_modification,
753 std::vector<size_t>* affinity)
755 this->TestProcess->SetId(this->Index);
756 this->TestProcess->SetWorkingDirectory(this->TestProperties->Directory);
757 this->TestProcess->SetCommand(this->ActualCommand);
758 this->TestProcess->SetCommandArguments(this->Arguments);
760 // determine how much time we have
761 cmDuration timeout = this->CTest->GetRemainingTimeAllowed();
762 if (timeout != cmCTest::MaxDuration()) {
763 timeout -= std::chrono::minutes(2);
765 if (this->CTest->GetTimeOut() > cmDuration::zero() &&
766 this->CTest->GetTimeOut() < timeout) {
767 timeout = this->CTest->GetTimeOut();
769 if (testTimeOut > cmDuration::zero() &&
770 testTimeOut < this->CTest->GetRemainingTimeAllowed()) {
771 timeout = testTimeOut;
773 // always have at least 1 second if we got to here
774 if (timeout <= cmDuration::zero()) {
775 timeout = std::chrono::seconds(1);
777 // handle timeout explicitly set to 0
778 if (testTimeOut == cmDuration::zero() && explicitTimeout) {
779 timeout = cmDuration::zero();
781 cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
783 << "Test timeout computed to be: "
784 << cmDurationTo<unsigned int>(timeout)
786 this->TestHandler->GetQuiet());
788 this->TestProcess->SetTimeout(timeout);
790 #ifndef CMAKE_BOOTSTRAP
791 cmSystemTools::SaveRestoreEnvironment sre;
794 std::ostringstream envMeasurement;
795 if (environment && !environment->empty()) {
796 // Environment modification works on the assumption that the environment is
797 // actually modified here. If another strategy is used, there will need to
798 // be updates below in `apply_diff`.
799 cmSystemTools::AppendEnv(*environment);
800 for (auto const& var : *environment) {
801 envMeasurement << var << std::endl;
805 if (environment_modification && !environment_modification->empty()) {
806 std::map<std::string, cm::optional<std::string>> env_application;
815 [&env_application](const std::string& name,
816 std::function<void(std::string&)> const& apply) {
817 cm::optional<std::string> old_value = env_application[name];
822 // This only works because the environment is actually modified above
823 // (`AppendEnv`). If CTest ever just creates an environment block
824 // directly, that block will need to be queried for the subprocess'
826 const char* curval = cmSystemTools::GetEnv(name);
832 env_application[name] = output;
835 bool err_occurred = false;
837 for (auto const& envmod : *environment_modification) {
839 auto const eq_loc = envmod.find_first_of('=');
840 if (eq_loc == std::string::npos) {
841 cmCTestLog(this->CTest, ERROR_MESSAGE,
842 "Error: Missing `=` after the variable name in: "
843 << envmod << std::endl);
847 auto const name = envmod.substr(0, eq_loc);
849 // Split value on `:`
850 auto const op_value_start = eq_loc + 1;
851 auto const colon_loc = envmod.find_first_of(':', op_value_start);
852 if (colon_loc == std::string::npos) {
853 cmCTestLog(this->CTest, ERROR_MESSAGE,
854 "Error: Missing `:` after the operation in: " << envmod
860 envmod.substr(op_value_start, colon_loc - op_value_start);
862 auto const value_start = colon_loc + 1;
863 auto const value = envmod.substr(value_start);
865 // Determine what to do with the operation.
866 if (op == "reset"_s) {
867 auto entry = env_application.find(name);
868 if (entry != env_application.end()) {
869 env_application.erase(entry);
871 } else if (op == "set"_s) {
872 env_application[name] = value;
873 } else if (op == "unset"_s) {
874 env_application[name] = {};
875 } else if (op == "string_append"_s) {
876 apply_diff(name, [&value](std::string& output) { output += value; });
877 } else if (op == "string_prepend"_s) {
879 [&value](std::string& output) { output.insert(0, value); });
880 } else if (op == "path_list_append"_s) {
881 apply_diff(name, [&value, path_sep](std::string& output) {
882 if (!output.empty()) {
887 } else if (op == "path_list_prepend"_s) {
888 apply_diff(name, [&value, path_sep](std::string& output) {
889 if (!output.empty()) {
890 output.insert(output.begin(), path_sep);
892 output.insert(0, value);
894 } else if (op == "cmake_list_append"_s) {
895 apply_diff(name, [&value](std::string& output) {
896 if (!output.empty()) {
901 } else if (op == "cmake_list_prepend"_s) {
902 apply_diff(name, [&value](std::string& output) {
903 if (!output.empty()) {
904 output.insert(output.begin(), ';');
906 output.insert(0, value);
909 cmCTestLog(this->CTest, ERROR_MESSAGE,
910 "Error: Unrecognized environment manipulation argument: "
921 for (auto const& env_apply : env_application) {
922 if (env_apply.second) {
923 auto const env_update =
924 cmStrCat(env_apply.first, '=', *env_apply.second);
925 cmSystemTools::PutEnv(env_update);
926 envMeasurement << env_update << std::endl;
928 cmSystemTools::UnsetEnv(env_apply.first.c_str());
929 // Signify that this variable is being actively unset
930 envMeasurement << "#" << env_apply.first << "=" << std::endl;
935 if (this->UseAllocatedResources) {
936 std::vector<std::string> envLog;
937 this->SetupResourcesEnvironment(&envLog);
938 for (auto const& var : envLog) {
939 envMeasurement << var << std::endl;
942 cmSystemTools::UnsetEnv("CTEST_RESOURCE_GROUP_COUNT");
943 // Signify that this variable is being actively unset
944 envMeasurement << "#CTEST_RESOURCE_GROUP_COUNT=" << std::endl;
947 this->TestResult.Environment = envMeasurement.str();
948 // Remove last newline
949 this->TestResult.Environment.erase(this->TestResult.Environment.length() -
952 return this->TestProcess->StartProcess(this->MultiTestHandler.Loop,
956 void cmCTestRunTest::SetupResourcesEnvironment(std::vector<std::string>* log)
958 std::string processCount = "CTEST_RESOURCE_GROUP_COUNT=";
959 processCount += std::to_string(this->AllocatedResources.size());
960 cmSystemTools::PutEnv(processCount);
962 log->push_back(processCount);
966 for (auto const& process : this->AllocatedResources) {
967 std::string prefix = "CTEST_RESOURCE_GROUP_";
968 prefix += std::to_string(i);
969 std::string resourceList = prefix + '=';
971 bool firstType = true;
972 for (auto const& it : process) {
977 auto resourceType = it.first;
978 resourceList += resourceType;
979 std::string var = prefix + cmSystemTools::UpperCase(resourceType) + '=';
980 bool firstName = true;
981 for (auto const& it2 : it.second) {
986 var += "id:" + it2.Id + ",slots:" + std::to_string(it2.Slots);
988 cmSystemTools::PutEnv(var);
993 cmSystemTools::PutEnv(resourceList);
995 log->push_back(resourceList);
1001 void cmCTestRunTest::WriteLogOutputTop(size_t completed, size_t total)
1003 std::ostringstream outputStream;
1005 // If this is the last or only run of this test, or progress output is
1006 // requested, then print out completed / total.
1007 // Only issue is if a test fails and we are running until fail
1008 // then it will never print out the completed / total, same would
1009 // got for run until pass. Trick is when this is called we don't
1010 // yet know if we are passing or failing.
1011 bool const progressOnLast =
1012 (this->RepeatMode != cmCTest::Repeat::UntilPass &&
1013 this->RepeatMode != cmCTest::Repeat::AfterTimeout);
1014 if ((progressOnLast && this->NumberOfRunsLeft == 1) ||
1015 (!progressOnLast && this->NumberOfRunsLeft == this->NumberOfRunsTotal) ||
1016 this->CTest->GetTestProgressOutput()) {
1017 outputStream << std::setw(getNumWidth(total)) << completed << "/";
1018 outputStream << std::setw(getNumWidth(total)) << total << " ";
1020 // if this is one of several runs of a test just print blank space
1021 // to keep things neat
1023 outputStream << std::setw(getNumWidth(total)) << " ";
1024 outputStream << std::setw(getNumWidth(total)) << " ";
1027 if (this->TestHandler->MemCheck) {
1028 outputStream << "MemCheck";
1030 outputStream << "Test";
1033 std::ostringstream indexStr;
1034 indexStr << " #" << this->Index << ":";
1035 outputStream << std::setw(3 + getNumWidth(this->TestHandler->GetMaxIndex()))
1037 outputStream << " ";
1039 const int maxTestNameWidth = this->CTest->GetMaxTestNameWidth();
1040 std::string outname = this->TestProperties->Name + " ";
1041 outname.resize(maxTestNameWidth + 4, '.');
1042 outputStream << outname;
1044 *this->TestHandler->LogFile << this->TestProperties->Index << "/"
1045 << this->TestHandler->TotalNumberOfTests
1046 << " Testing: " << this->TestProperties->Name
1048 *this->TestHandler->LogFile << this->TestProperties->Index << "/"
1049 << this->TestHandler->TotalNumberOfTests
1050 << " Test: " << this->TestProperties->Name
1052 *this->TestHandler->LogFile << "Command: \"" << this->ActualCommand << "\"";
1054 for (std::string const& arg : this->Arguments) {
1055 *this->TestHandler->LogFile << " \"" << arg << "\"";
1057 *this->TestHandler->LogFile
1059 << "Directory: " << this->TestProperties->Directory << std::endl
1060 << "\"" << this->TestProperties->Name
1061 << "\" start time: " << this->StartTime << std::endl;
1063 *this->TestHandler->LogFile
1064 << "Output:" << std::endl
1065 << "----------------------------------------------------------"
1067 *this->TestHandler->LogFile << this->ProcessOutput << "<end of output>"
1070 if (!this->CTest->GetTestProgressOutput()) {
1071 cmCTestLog(this->CTest, HANDLER_OUTPUT, outputStream.str());
1074 cmCTestLog(this->CTest, DEBUG,
1075 "Testing " << this->TestProperties->Name << " ... ");
1078 void cmCTestRunTest::FinalizeTest(bool started)
1080 this->MultiTestHandler.FinishTestProcess(this->TestProcess->GetRunner(),