|
|
|
/* 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 <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.
|
|
|
|
double timeout = 0.1;
|
|
|
|
double timeEnd = cmSystemTools::GetTime() + timeout;
|
|
|
|
std::string line;
|
|
|
|
while ((timeout = timeEnd - cmSystemTools::GetTime(), timeout > 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 "
|
|
|
|
<< 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
|
|
|
|
<< " ... ");
|
|
|
|
}
|