mirror of
https://github.com/Kitware/CMake.git
synced 2025-06-24 05:03:26 +08:00

It was reported in issue #17345 that CTest does not use monotonic time to report test duration. Monotonic clocks are not affected by large NTP adjustments or things like daylight savings time. As CMake 3.10 requires C++11, which introduced std::chrono, this commit moves the time keeping in CTest from cmSystemTools::GetTime() to std::chrono::steady_clock. Fixes: #17345
768 lines
28 KiB
C++
768 lines
28 KiB
C++
/* 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 "cmCTestTestHandler.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 <iomanip>
|
|
#include <sstream>
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
#include <utility>
|
|
|
|
cmCTestRunTest::cmCTestRunTest(cmCTestTestHandler* handler)
|
|
{
|
|
this->CTest = handler->CTest;
|
|
this->TestHandler = handler;
|
|
this->TestProcess = nullptr;
|
|
this->TestResult.ExecutionTime = 0;
|
|
this->TestResult.ReturnValue = 0;
|
|
this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
|
|
this->TestResult.TestCount = 0;
|
|
this->TestResult.Properties = nullptr;
|
|
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()
|
|
{
|
|
}
|
|
|
|
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.count());
|
|
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 "
|
|
<< this->TestProperties->AlternateTimeout
|
|
<< 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
|
|
// is appended to this->CompressedOutput
|
|
void cmCTestRunTest::CompressOutput()
|
|
{
|
|
int ret;
|
|
z_stream strm;
|
|
|
|
unsigned char* in = reinterpret_cast<unsigned char*>(
|
|
const_cast<char*>(this->ProcessOutput.c_str()));
|
|
// zlib makes the guarantee that this is the maximum output size
|
|
int outSize = static_cast<int>(
|
|
static_cast<double>(this->ProcessOutput.size()) * 1.001 + 13.0);
|
|
unsigned char* out = new unsigned char[outSize];
|
|
|
|
strm.zalloc = Z_NULL;
|
|
strm.zfree = Z_NULL;
|
|
strm.opaque = Z_NULL;
|
|
ret = deflateInit(&strm, -1); // default compression level
|
|
if (ret != Z_OK) {
|
|
delete[] out;
|
|
return;
|
|
}
|
|
|
|
strm.avail_in = static_cast<uInt>(this->ProcessOutput.size());
|
|
strm.next_in = in;
|
|
strm.avail_out = outSize;
|
|
strm.next_out = out;
|
|
ret = deflate(&strm, Z_FINISH);
|
|
|
|
if (ret != Z_STREAM_END) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Error during output compression. Sending uncompressed output."
|
|
<< std::endl);
|
|
delete[] out;
|
|
return;
|
|
}
|
|
|
|
(void)deflateEnd(&strm);
|
|
|
|
unsigned char* encoded_buffer =
|
|
new unsigned char[static_cast<int>(outSize * 1.5)];
|
|
|
|
size_t rlen = cmsysBase64_Encode(out, strm.total_out, encoded_buffer, 1);
|
|
|
|
this->CompressedOutput.clear();
|
|
for (size_t i = 0; i < rlen; i++) {
|
|
this->CompressedOutput += encoded_buffer[i];
|
|
}
|
|
|
|
if (strm.total_in) {
|
|
this->CompressionRatio =
|
|
static_cast<double>(strm.total_out) / static_cast<double>(strm.total_in);
|
|
}
|
|
|
|
delete[] encoded_buffer;
|
|
delete[] out;
|
|
}
|
|
|
|
bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started)
|
|
{
|
|
if ((!this->TestHandler->MemCheck &&
|
|
this->CTest->ShouldCompressTestOutput()) ||
|
|
(this->TestHandler->MemCheck &&
|
|
this->CTest->ShouldCompressTestOutput())) {
|
|
this->CompressOutput();
|
|
}
|
|
|
|
this->WriteLogOutputTop(completed, total);
|
|
std::string reason;
|
|
bool passed = true;
|
|
int res =
|
|
started ? this->TestProcess->GetProcessStatus() : cmsysProcess_State_Error;
|
|
int 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.c_str())) {
|
|
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.c_str())) {
|
|
reason = "Error regular expression found in output.";
|
|
reason += " Regex=[";
|
|
reason += pass.second;
|
|
reason += "]";
|
|
forceFail = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (res == cmsysProcess_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;
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, " Passed ");
|
|
} else {
|
|
this->TestResult.Status = cmCTestTestHandler::FAILED;
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Failed " << reason);
|
|
outputTestErrorsToConsole = this->CTest->OutputTestOutputOnTestFailure;
|
|
}
|
|
} else if (res == cmsysProcess_State_Expired) {
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Timeout ");
|
|
this->TestResult.Status = cmCTestTestHandler::TIMEOUT;
|
|
outputTestErrorsToConsole = this->CTest->OutputTestOutputOnTestFailure;
|
|
} else if (res == cmsysProcess_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:
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, "SegFault");
|
|
this->TestResult.Status = cmCTestTestHandler::SEGFAULT;
|
|
break;
|
|
case cmsysProcess_Exception_Illegal:
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, "Illegal");
|
|
this->TestResult.Status = cmCTestTestHandler::ILLEGAL;
|
|
break;
|
|
case cmsysProcess_Exception_Interrupt:
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, "Interrupt");
|
|
this->TestResult.Status = cmCTestTestHandler::INTERRUPT;
|
|
break;
|
|
case cmsysProcess_Exception_Numerical:
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, "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) {
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Not Run (Disabled) ");
|
|
} else // cmsysProcess_State_Error
|
|
{
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Not Run ");
|
|
}
|
|
|
|
passed = this->TestResult.Status == cmCTestTestHandler::COMPLETED;
|
|
char buf[1024];
|
|
sprintf(buf, "%6.2f sec", this->TestProcess->GetTotalTime());
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, buf << "\n");
|
|
|
|
if (outputTestErrorsToConsole) {
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, this->ProcessOutput << std::endl);
|
|
}
|
|
|
|
if (this->TestHandler->LogFile) {
|
|
*this->TestHandler->LogFile << "Test time = " << buf << std::endl;
|
|
}
|
|
|
|
// Set the working directory to the tests directory to process Dart files.
|
|
{
|
|
cmWorkingDirectory workdir(this->TestProperties->Directory);
|
|
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;
|
|
}
|
|
double ttime = this->TestProcess->GetTotalTime();
|
|
int hours = static_cast<int>(ttime / (60 * 60));
|
|
int minutes = static_cast<int>(ttime / 60) % 60;
|
|
int seconds = static_cast<int>(ttime) % 60;
|
|
char buffer[100];
|
|
sprintf(buffer, "%02d:%02d:%02d", hours, minutes, seconds);
|
|
*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) {
|
|
bool compress = !this->TestHandler->MemCheck &&
|
|
this->CompressionRatio < 1 && this->CTest->ShouldCompressTestOutput();
|
|
this->TestResult.Output =
|
|
compress ? this->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);
|
|
}
|
|
delete this->TestProcess;
|
|
return passed || skipped;
|
|
}
|
|
|
|
bool cmCTestRunTest::StartAgain()
|
|
{
|
|
if (!this->RunAgain) {
|
|
return false;
|
|
}
|
|
this->RunAgain = false; // reset
|
|
// change to tests directory
|
|
cmWorkingDirectory workdir(this->TestProperties->Directory);
|
|
this->StartTest(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;
|
|
|
|
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);
|
|
}
|
|
|
|
// Starts the execution of a test. Returns once it has started
|
|
bool cmCTestRunTest::StartTest(size_t total)
|
|
{
|
|
this->TotalNumberOfTests = total; // save for rerun case
|
|
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);
|
|
this->ProcessOutput.clear();
|
|
|
|
// Return immediately if test is disabled
|
|
if (this->TestProperties->Disabled) {
|
|
this->TestResult.Properties = this->TestProperties;
|
|
this->TestResult.ExecutionTime = 0;
|
|
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 = new cmProcess;
|
|
this->TestResult.Output = "Disabled";
|
|
this->TestResult.FullCommandLine.clear();
|
|
return false;
|
|
}
|
|
|
|
this->ComputeArguments();
|
|
std::vector<std::string>& args = this->TestProperties->Args;
|
|
this->TestResult.Properties = this->TestProperties;
|
|
this->TestResult.ExecutionTime = 0;
|
|
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;
|
|
|
|
if (!this->FailedDependencies.empty()) {
|
|
this->TestProcess = new cmProcess;
|
|
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;
|
|
}
|
|
|
|
if (args.size() >= 2 && args[1] == "NOT_AVAILABLE") {
|
|
this->TestProcess = new cmProcess;
|
|
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.c_str())) {
|
|
// Required file was not found
|
|
this->TestProcess = new cmProcess;
|
|
*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 = new cmProcess;
|
|
*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();
|
|
|
|
double timeout = this->ResolveTimeout();
|
|
|
|
if (this->StopTimePassed) {
|
|
return false;
|
|
}
|
|
return this->ForkProcess(timeout, this->TestProperties->ExplicitTimeout,
|
|
&this->TestProperties->Environment);
|
|
}
|
|
|
|
void cmCTestRunTest::ComputeArguments()
|
|
{
|
|
this->Arguments.clear(); // reset becaue 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.c_str());
|
|
|
|
// 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.c_str())) {
|
|
this->TestResult.DartString = this->TestHandler->DartStuff.match(1);
|
|
// keep searching and replacing until none are left
|
|
while (this->TestHandler->DartStuff1.find(this->ProcessOutput.c_str())) {
|
|
// replace the exact match for the string
|
|
cmSystemTools::ReplaceString(
|
|
this->ProcessOutput, this->TestHandler->DartStuff1.match(1).c_str(),
|
|
"");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
double cmCTestRunTest::ResolveTimeout()
|
|
{
|
|
double timeout = this->TestProperties->Timeout;
|
|
|
|
if (this->CTest->GetStopTime().empty()) {
|
|
return timeout;
|
|
}
|
|
struct tm* lctime;
|
|
time_t current_time = time(nullptr);
|
|
lctime = gmtime(¤t_time);
|
|
int gm_hour = lctime->tm_hour;
|
|
time_t gm_time = mktime(lctime);
|
|
lctime = localtime(¤t_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 = curl_getdate(buf, ¤t_time);
|
|
if (stop_time == -1) {
|
|
return timeout;
|
|
}
|
|
|
|
// the stop time refers to the next day
|
|
if (this->CTest->NextDayStopTime) {
|
|
stop_time += 24 * 60 * 60;
|
|
}
|
|
int stop_timeout =
|
|
static_cast<int>(stop_time - current_time) % (24 * 60 * 60);
|
|
this->CTest->LastStopTimeout = stop_timeout;
|
|
|
|
if (stop_timeout <= 0 || 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 0;
|
|
}
|
|
return timeout == 0 ? stop_timeout
|
|
: (timeout < stop_timeout ? timeout : stop_timeout);
|
|
}
|
|
|
|
bool cmCTestRunTest::ForkProcess(double testTimeOut, bool explicitTimeout,
|
|
std::vector<std::string>* environment)
|
|
{
|
|
this->TestProcess = new cmProcess;
|
|
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
|
|
double timeout = this->CTest->GetRemainingTimeAllowed() - 120;
|
|
if (this->CTest->GetTimeOut() > 0 && this->CTest->GetTimeOut() < timeout) {
|
|
timeout = this->CTest->GetTimeOut();
|
|
}
|
|
if (testTimeOut > 0 &&
|
|
testTimeOut < this->CTest->GetRemainingTimeAllowed()) {
|
|
timeout = testTimeOut;
|
|
}
|
|
// always have at least 1 second if we got to here
|
|
if (timeout <= 0) {
|
|
timeout = 1;
|
|
}
|
|
// handle timeout explicitly set to 0
|
|
if (testTimeOut == 0 && explicitTimeout) {
|
|
timeout = 0;
|
|
}
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->Index
|
|
<< ": "
|
|
<< "Test timeout computed to be: " << 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();
|
|
}
|
|
|
|
void cmCTestRunTest::WriteLogOutputTop(size_t completed, size_t total)
|
|
{
|
|
// if this is the last or only run of this test
|
|
// 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) {
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(getNumWidth(total))
|
|
<< completed << "/");
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(getNumWidth(total))
|
|
<< total << " ");
|
|
}
|
|
// if this is one of several runs of a test just print blank space
|
|
// to keep things neat
|
|
else {
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(getNumWidth(total))
|
|
<< " "
|
|
<< " ");
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(getNumWidth(total))
|
|
<< " "
|
|
<< " ");
|
|
}
|
|
|
|
if (this->TestHandler->MemCheck) {
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, "MemCheck");
|
|
} else {
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, "Test");
|
|
}
|
|
|
|
std::ostringstream indexStr;
|
|
indexStr << " #" << this->Index << ":";
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT,
|
|
std::setw(3 + getNumWidth(this->TestHandler->GetMaxIndex()))
|
|
<< indexStr.str());
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, " ");
|
|
const int maxTestNameWidth = this->CTest->GetMaxTestNameWidth();
|
|
std::string outname = this->TestProperties->Name + " ";
|
|
outname.resize(maxTestNameWidth + 4, '.');
|
|
|
|
*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;
|
|
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT, outname.c_str());
|
|
cmCTestLog(this->CTest, DEBUG, "Testing " << this->TestProperties->Name
|
|
<< " ... ");
|
|
}
|