CMake/Source/cmCTest.cxx
Stefan Dinkelacker a39d4139d0 Add --no-tests=<[error|ignore]> option to CTest
If no tests were found, the default behavior of CTest is to always log an
error message but to return an error code in script mode only. This option
unifies the behavior of CTest by either returning an error code if no tests
were found or by ignoring it.

Signed-off-by: Stefan Dinkelacker <s.dinkelacker@dkfz-heidelberg.de>
2020-01-31 18:17:13 +01:00

3310 lines
101 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmCTest.h"
#include <algorithm>
#include <cctype>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "cmsys/Base64.h"
#include "cmsys/Directory.hxx"
#include "cmsys/FStream.hxx"
#include "cmsys/Glob.hxx"
#include "cmsys/Process.h"
#include "cmsys/RegularExpression.hxx"
#include "cmsys/SystemInformation.hxx"
#include "cm_curl.h"
#include "cm_zlib.h"
#if defined(_WIN32)
# include <windows.h> // IWYU pragma: keep
#else
# include <unistd.h> // IWYU pragma: keep
#endif
#include <cm/memory>
#include <cmext/algorithm>
#include "cmCTestBuildAndTestHandler.h"
#include "cmCTestBuildHandler.h"
#include "cmCTestConfigureHandler.h"
#include "cmCTestCoverageHandler.h"
#include "cmCTestGenericHandler.h"
#include "cmCTestMemCheckHandler.h"
#include "cmCTestScriptHandler.h"
#include "cmCTestStartCommand.h"
#include "cmCTestSubmitHandler.h"
#include "cmCTestTestHandler.h"
#include "cmCTestUpdateHandler.h"
#include "cmCTestUploadHandler.h"
#include "cmDynamicLoader.h"
#include "cmGeneratedFileStream.h"
#include "cmGlobalGenerator.h"
#include "cmMakefile.h"
#include "cmProcessOutput.h"
#include "cmState.h"
#include "cmStateSnapshot.h"
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmVersion.h"
#include "cmVersionConfig.h"
#include "cmXMLWriter.h"
#include "cmake.h"
#if defined(__BEOS__) || defined(__HAIKU__)
# include <be/kernel/OS.h> /* disable_debugger() API. */
#endif
struct cmCTest::Private
{
/** Representation of one part. */
struct PartInfo
{
void SetName(const std::string& name) { this->Name = name; }
const std::string& GetName() const { return this->Name; }
void Enable() { this->Enabled = true; }
explicit operator bool() const { return this->Enabled; }
std::vector<std::string> SubmitFiles;
private:
bool Enabled = false;
std::string Name;
};
int RepeatCount = 1; // default to run each test once
cmCTest::Repeat RepeatMode = cmCTest::Repeat::Never;
std::string ConfigType;
std::string ScheduleType;
std::chrono::system_clock::time_point StopTime;
bool TestProgressOutput = false;
bool Verbose = false;
bool ExtraVerbose = false;
bool ProduceXML = false;
bool LabelSummary = true;
bool SubprojectSummary = true;
bool UseHTTP10 = false;
bool PrintLabels = false;
bool Failover = false;
bool FlushTestProgressLine = false;
bool ForceNewCTestProcess = false;
bool RunConfigurationScript = false;
// these are helper classes
cmCTestBuildHandler BuildHandler;
cmCTestBuildAndTestHandler BuildAndTestHandler;
cmCTestCoverageHandler CoverageHandler;
cmCTestScriptHandler ScriptHandler;
cmCTestTestHandler TestHandler;
cmCTestUpdateHandler UpdateHandler;
cmCTestConfigureHandler ConfigureHandler;
cmCTestMemCheckHandler MemCheckHandler;
cmCTestSubmitHandler SubmitHandler;
cmCTestUploadHandler UploadHandler;
std::vector<cmCTestGenericHandler*> GetTestingHandlers()
{
return { &this->BuildHandler, &this->BuildAndTestHandler,
&this->CoverageHandler, &this->ScriptHandler,
&this->TestHandler, &this->UpdateHandler,
&this->ConfigureHandler, &this->MemCheckHandler,
&this->SubmitHandler, &this->UploadHandler };
}
std::map<std::string, cmCTestGenericHandler*> GetNamedTestingHandlers()
{
return { { "build", &this->BuildHandler },
{ "buildtest", &this->BuildAndTestHandler },
{ "coverage", &this->CoverageHandler },
{ "script", &this->ScriptHandler },
{ "test", &this->TestHandler },
{ "update", &this->UpdateHandler },
{ "configure", &this->ConfigureHandler },
{ "memcheck", &this->MemCheckHandler },
{ "submit", &this->SubmitHandler },
{ "upload", &this->UploadHandler } };
}
bool ShowOnly = false;
bool OutputAsJson = false;
int OutputAsJsonVersion = 1;
// TODO: The ctest configuration should be a hierarchy of
// configuration option sources: command-line, script, ini file.
// Then the ini file can get re-loaded whenever it changes without
// affecting any higher-precedence settings.
std::map<std::string, std::string> CTestConfiguration;
std::map<std::string, std::string> CTestConfigurationOverwrites;
PartInfo Parts[PartCount];
std::map<std::string, Part> PartMap;
std::string CurrentTag;
bool TomorrowTag = false;
int TestModel = cmCTest::EXPERIMENTAL;
std::string SpecificGroup;
cmDuration TimeOut = cmDuration::zero();
cmDuration GlobalTimeout = cmDuration::zero();
int MaxTestNameWidth = 30;
int ParallelLevel = 1;
bool ParallelLevelSetInCli = false;
unsigned long TestLoad = 0;
int CompatibilityMode;
// information for the --build-and-test options
std::string BinaryDir;
std::string NotesFiles;
bool InteractiveDebugMode = true;
bool ShortDateFormat = true;
bool CompressXMLFiles = false;
bool CompressTestOutput = true;
// By default we write output to the process output streams.
std::ostream* StreamOut = &std::cout;
std::ostream* StreamErr = &std::cerr;
bool SuppressUpdatingCTestConfiguration = false;
bool Debug = false;
bool ShowLineNumbers = false;
bool Quiet = false;
std::string BuildID;
std::vector<std::string> InitialCommandLineArguments;
int SubmitIndex = 0;
cmGeneratedFileStream* OutputLogFile = nullptr;
int OutputLogFileLastTag = -1;
bool OutputTestOutputOnTestFailure = false;
bool OutputColorCode = cmCTest::ColoredOutputSupportedByConsole();
std::map<std::string, std::string> Definitions;
cmCTest::NoTests NoTestsMode = cmCTest::NoTests::Legacy;
};
struct tm* cmCTest::GetNightlyTime(std::string const& str, bool tomorrowtag)
{
struct tm* lctime;
time_t tctime = time(nullptr);
lctime = gmtime(&tctime);
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", lctime->tm_year + 1900, lctime->tm_mon + 1,
lctime->tm_mday, str.c_str());
cmCTestLog(this, OUTPUT,
"Determine Nightly Start Time" << std::endl
<< " Specified time: " << str
<< std::endl);
// Convert the nightly start time to seconds. Since we are
// providing only a time and a timezone, the current date of
// the local machine is assumed. Consequently, nightlySeconds
// is the time at which the nightly dashboard was opened or
// will be opened on the date of the current client machine.
// As such, this time may be in the past or in the future.
time_t ntime = curl_getdate(buf, &tctime);
cmCTestLog(this, DEBUG, " Get curl time: " << ntime << std::endl);
tctime = time(nullptr);
cmCTestLog(this, DEBUG, " Get the current time: " << tctime << std::endl);
const int dayLength = 24 * 60 * 60;
cmCTestLog(this, DEBUG, "Seconds: " << tctime << std::endl);
while (ntime > tctime) {
// If nightlySeconds is in the past, this is the current
// open dashboard, then return nightlySeconds. If
// nightlySeconds is in the future, this is the next
// dashboard to be opened, so subtract 24 hours to get the
// time of the current open dashboard
ntime -= dayLength;
cmCTestLog(this, DEBUG, "Pick yesterday" << std::endl);
cmCTestLog(this, DEBUG,
" Future time, subtract day: " << ntime << std::endl);
}
while (tctime > (ntime + dayLength)) {
ntime += dayLength;
cmCTestLog(this, DEBUG, " Past time, add day: " << ntime << std::endl);
}
cmCTestLog(this, DEBUG, "nightlySeconds: " << ntime << std::endl);
cmCTestLog(this, DEBUG,
" Current time: " << tctime << " Nightly time: " << ntime
<< std::endl);
if (tomorrowtag) {
cmCTestLog(this, OUTPUT, " Use future tag, Add a day" << std::endl);
ntime += dayLength;
}
lctime = gmtime(&ntime);
return lctime;
}
bool cmCTest::GetTomorrowTag() const
{
return this->Impl->TomorrowTag;
}
std::string cmCTest::CleanString(const std::string& str)
{
std::string::size_type spos = str.find_first_not_of(" \n\t\r\f\v");
std::string::size_type epos = str.find_last_not_of(" \n\t\r\f\v");
if (spos == std::string::npos) {
return std::string();
}
if (epos != std::string::npos) {
epos = epos - spos + 1;
}
return str.substr(spos, epos);
}
std::string cmCTest::CurrentTime()
{
time_t currenttime = time(nullptr);
struct tm* t = localtime(&currenttime);
// return ::CleanString(ctime(&currenttime));
char current_time[1024];
if (this->Impl->ShortDateFormat) {
strftime(current_time, 1000, "%b %d %H:%M %Z", t);
} else {
strftime(current_time, 1000, "%a %b %d %H:%M:%S %Z %Y", t);
}
cmCTestLog(this, DEBUG, " Current_Time: " << current_time << std::endl);
return cmCTest::CleanString(current_time);
}
std::string cmCTest::GetCostDataFile()
{
std::string fname = this->GetCTestConfiguration("CostDataFile");
if (fname.empty()) {
fname = this->GetBinaryDir() + "/Testing/Temporary/CTestCostData.txt";
}
return fname;
}
std::string cmCTest::DecodeURL(const std::string& in)
{
std::string out;
for (const char* c = in.c_str(); *c; ++c) {
if (*c == '%' && isxdigit(*(c + 1)) && isxdigit(*(c + 2))) {
char buf[3] = { *(c + 1), *(c + 2), 0 };
out.append(1, char(strtoul(buf, nullptr, 16)));
c += 2;
} else {
out.append(1, *c);
}
}
return out;
}
cmCTest::cmCTest()
: Impl(new Private)
{
std::string envValue;
if (cmSystemTools::GetEnv("CTEST_OUTPUT_ON_FAILURE", envValue)) {
this->Impl->OutputTestOutputOnTestFailure = !cmIsOff(envValue);
}
envValue.clear();
if (cmSystemTools::GetEnv("CTEST_PROGRESS_OUTPUT", envValue)) {
this->Impl->TestProgressOutput = !cmIsOff(envValue);
}
this->Impl->Parts[PartStart].SetName("Start");
this->Impl->Parts[PartUpdate].SetName("Update");
this->Impl->Parts[PartConfigure].SetName("Configure");
this->Impl->Parts[PartBuild].SetName("Build");
this->Impl->Parts[PartTest].SetName("Test");
this->Impl->Parts[PartCoverage].SetName("Coverage");
this->Impl->Parts[PartMemCheck].SetName("MemCheck");
this->Impl->Parts[PartSubmit].SetName("Submit");
this->Impl->Parts[PartNotes].SetName("Notes");
this->Impl->Parts[PartExtraFiles].SetName("ExtraFiles");
this->Impl->Parts[PartUpload].SetName("Upload");
this->Impl->Parts[PartDone].SetName("Done");
// Fill the part name-to-id map.
for (Part p = PartStart; p != PartCount; p = Part(p + 1)) {
this->Impl
->PartMap[cmSystemTools::LowerCase(this->Impl->Parts[p].GetName())] = p;
}
for (auto& handler : this->Impl->GetTestingHandlers()) {
handler->SetCTestInstance(this);
}
// Make sure we can capture the build tool output.
cmSystemTools::EnableVSConsoleOutput();
}
cmCTest::~cmCTest()
{
delete this->Impl->OutputLogFile;
}
int cmCTest::GetParallelLevel() const
{
return this->Impl->ParallelLevel;
}
void cmCTest::SetParallelLevel(int level)
{
this->Impl->ParallelLevel = level < 1 ? 1 : level;
}
unsigned long cmCTest::GetTestLoad() const
{
return this->Impl->TestLoad;
}
void cmCTest::SetTestLoad(unsigned long load)
{
this->Impl->TestLoad = load;
}
bool cmCTest::ShouldCompressTestOutput()
{
return this->Impl->CompressTestOutput;
}
cmCTest::Part cmCTest::GetPartFromName(const char* name)
{
// Look up by lower-case to make names case-insensitive.
std::string lower_name = cmSystemTools::LowerCase(name);
auto const i = this->Impl->PartMap.find(lower_name);
if (i != this->Impl->PartMap.end()) {
return i->second;
}
// The string does not name a valid part.
return PartCount;
}
int cmCTest::Initialize(const char* binary_dir, cmCTestStartCommand* command)
{
bool quiet = false;
if (command && command->ShouldBeQuiet()) {
quiet = true;
}
cmCTestOptionalLog(this, DEBUG, "Here: " << __LINE__ << std::endl, quiet);
if (!this->Impl->InteractiveDebugMode) {
this->BlockTestErrorDiagnostics();
} else {
cmSystemTools::PutEnv("CTEST_INTERACTIVE_DEBUG_MODE=1");
}
this->Impl->BinaryDir = binary_dir;
cmSystemTools::ConvertToUnixSlashes(this->Impl->BinaryDir);
this->UpdateCTestConfiguration();
cmCTestOptionalLog(this, DEBUG, "Here: " << __LINE__ << std::endl, quiet);
if (this->Impl->ProduceXML) {
cmCTestOptionalLog(this, DEBUG, "Here: " << __LINE__ << std::endl, quiet);
cmCTestOptionalLog(this, OUTPUT,
" Site: "
<< this->GetCTestConfiguration("Site") << std::endl
<< " Build name: "
<< cmCTest::SafeBuildIdField(
this->GetCTestConfiguration("BuildName"))
<< std::endl,
quiet);
cmCTestOptionalLog(this, DEBUG, "Produce XML is on" << std::endl, quiet);
if (this->Impl->TestModel == cmCTest::NIGHTLY &&
this->GetCTestConfiguration("NightlyStartTime").empty()) {
cmCTestOptionalLog(
this, WARNING,
"WARNING: No nightly start time found please set in CTestConfig.cmake"
" or DartConfig.cmake"
<< std::endl,
quiet);
cmCTestOptionalLog(this, DEBUG, "Here: " << __LINE__ << std::endl,
quiet);
return 0;
}
}
cmake cm(cmake::RoleScript, cmState::CTest);
cm.SetHomeDirectory("");
cm.SetHomeOutputDirectory("");
cm.GetCurrentSnapshot().SetDefaultDefinitions();
cmGlobalGenerator gg(&cm);
cmMakefile mf(&gg, cm.GetCurrentSnapshot());
if (!this->ReadCustomConfigurationFileTree(this->Impl->BinaryDir.c_str(),
&mf)) {
cmCTestOptionalLog(
this, DEBUG, "Cannot find custom configuration file tree" << std::endl,
quiet);
return 0;
}
if (this->Impl->ProduceXML) {
// Verify "Testing" directory exists:
//
std::string testingDir = this->Impl->BinaryDir + "/Testing";
if (cmSystemTools::FileExists(testingDir)) {
if (!cmSystemTools::FileIsDirectory(testingDir)) {
cmCTestLog(this, ERROR_MESSAGE,
"File " << testingDir
<< " is in the place of the testing directory"
<< std::endl);
return 0;
}
} else {
if (!cmSystemTools::MakeDirectory(testingDir)) {
cmCTestLog(this, ERROR_MESSAGE,
"Cannot create directory " << testingDir << std::endl);
return 0;
}
}
// Create new "TAG" file or read existing one:
//
bool createNewTag = true;
if (command) {
createNewTag = command->ShouldCreateNewTag();
}
std::string tagfile = testingDir + "/TAG";
cmsys::ifstream tfin(tagfile.c_str());
std::string tag;
if (createNewTag) {
time_t tctime = time(nullptr);
if (this->Impl->TomorrowTag) {
tctime += (24 * 60 * 60);
}
struct tm* lctime = gmtime(&tctime);
if (tfin && cmSystemTools::GetLineFromStream(tfin, tag)) {
int year = 0;
int mon = 0;
int day = 0;
int hour = 0;
int min = 0;
sscanf(tag.c_str(), "%04d%02d%02d-%02d%02d", &year, &mon, &day, &hour,
&min);
if (year != lctime->tm_year + 1900 || mon != lctime->tm_mon + 1 ||
day != lctime->tm_mday) {
tag.clear();
}
std::string group;
if (cmSystemTools::GetLineFromStream(tfin, group) &&
!this->Impl->Parts[PartStart] && !command) {
this->Impl->SpecificGroup = group;
}
std::string model;
if (cmSystemTools::GetLineFromStream(tfin, model) &&
!this->Impl->Parts[PartStart] && !command) {
this->Impl->TestModel = GetTestModelFromString(model.c_str());
}
tfin.close();
}
if (tag.empty() || (nullptr != command) ||
this->Impl->Parts[PartStart]) {
cmCTestOptionalLog(
this, DEBUG,
"TestModel: " << this->GetTestModelString() << std::endl, quiet);
cmCTestOptionalLog(this, DEBUG,
"TestModel: " << this->Impl->TestModel << std::endl,
quiet);
if (this->Impl->TestModel == cmCTest::NIGHTLY) {
lctime = this->GetNightlyTime(
this->GetCTestConfiguration("NightlyStartTime"),
this->Impl->TomorrowTag);
}
char datestring[100];
sprintf(datestring, "%04d%02d%02d-%02d%02d", lctime->tm_year + 1900,
lctime->tm_mon + 1, lctime->tm_mday, lctime->tm_hour,
lctime->tm_min);
tag = datestring;
cmsys::ofstream ofs(tagfile.c_str());
if (ofs) {
ofs << tag << std::endl;
ofs << this->GetTestModelString() << std::endl;
switch (this->Impl->TestModel) {
case cmCTest::EXPERIMENTAL:
ofs << "Experimental" << std::endl;
break;
case cmCTest::NIGHTLY:
ofs << "Nightly" << std::endl;
break;
case cmCTest::CONTINUOUS:
ofs << "Continuous" << std::endl;
break;
}
}
ofs.close();
if (nullptr == command) {
cmCTestOptionalLog(this, OUTPUT,
"Create new tag: " << tag << " - "
<< this->GetTestModelString()
<< std::endl,
quiet);
}
}
} else {
std::string group;
std::string modelStr;
int model = cmCTest::UNKNOWN;
if (tfin) {
cmSystemTools::GetLineFromStream(tfin, tag);
cmSystemTools::GetLineFromStream(tfin, group);
if (cmSystemTools::GetLineFromStream(tfin, modelStr)) {
model = GetTestModelFromString(modelStr.c_str());
}
tfin.close();
}
if (tag.empty()) {
cmCTestLog(this, ERROR_MESSAGE,
"Cannot read existing TAG file in " << testingDir
<< std::endl);
return 0;
}
if (this->Impl->TestModel == cmCTest::UNKNOWN) {
if (model == cmCTest::UNKNOWN) {
cmCTestLog(this, ERROR_MESSAGE,
"TAG file does not contain model and "
"no model specified in start command"
<< std::endl);
return 0;
}
this->SetTestModel(model);
}
if (model != this->Impl->TestModel && model != cmCTest::UNKNOWN &&
this->Impl->TestModel != cmCTest::UNKNOWN) {
cmCTestOptionalLog(this, WARNING,
"Model given in TAG does not match "
"model given in ctest_start()"
<< std::endl,
quiet);
}
if (!this->Impl->SpecificGroup.empty() &&
group != this->Impl->SpecificGroup) {
cmCTestOptionalLog(this, WARNING,
"Group given in TAG does not match "
"group given in ctest_start()"
<< std::endl,
quiet);
} else {
this->Impl->SpecificGroup = group;
}
cmCTestOptionalLog(this, OUTPUT,
" Use existing tag: " << tag << " - "
<< this->GetTestModelString()
<< std::endl,
quiet);
}
this->Impl->CurrentTag = tag;
}
return 1;
}
bool cmCTest::InitializeFromCommand(cmCTestStartCommand* command)
{
std::string src_dir = this->GetCTestConfiguration("SourceDirectory");
std::string bld_dir = this->GetCTestConfiguration("BuildDirectory");
this->Impl->BuildID = "";
for (Part p = PartStart; p != PartCount; p = Part(p + 1)) {
this->Impl->Parts[p].SubmitFiles.clear();
}
cmMakefile* mf = command->GetMakefile();
std::string fname;
std::string src_dir_fname = cmStrCat(src_dir, "/CTestConfig.cmake");
cmSystemTools::ConvertToUnixSlashes(src_dir_fname);
std::string bld_dir_fname = cmStrCat(bld_dir, "/CTestConfig.cmake");
cmSystemTools::ConvertToUnixSlashes(bld_dir_fname);
if (cmSystemTools::FileExists(bld_dir_fname)) {
fname = bld_dir_fname;
} else if (cmSystemTools::FileExists(src_dir_fname)) {
fname = src_dir_fname;
}
if (!fname.empty()) {
cmCTestOptionalLog(this, OUTPUT,
" Reading ctest configuration file: " << fname
<< std::endl,
command->ShouldBeQuiet());
bool readit = mf->ReadDependentFile(fname);
if (!readit) {
std::string m = cmStrCat("Could not find include file: ", fname);
command->SetError(m);
return false;
}
}
this->SetCTestConfigurationFromCMakeVariable(mf, "NightlyStartTime",
"CTEST_NIGHTLY_START_TIME",
command->ShouldBeQuiet());
this->SetCTestConfigurationFromCMakeVariable(mf, "Site", "CTEST_SITE",
command->ShouldBeQuiet());
this->SetCTestConfigurationFromCMakeVariable(
mf, "BuildName", "CTEST_BUILD_NAME", command->ShouldBeQuiet());
if (!this->Initialize(bld_dir.c_str(), command)) {
return false;
}
cmCTestOptionalLog(this, OUTPUT,
" Use " << this->GetTestModelString() << " tag: "
<< this->GetCurrentTag() << std::endl,
command->ShouldBeQuiet());
return true;
}
bool cmCTest::UpdateCTestConfiguration()
{
if (this->Impl->SuppressUpdatingCTestConfiguration) {
return true;
}
std::string fileName = this->Impl->BinaryDir + "/CTestConfiguration.ini";
if (!cmSystemTools::FileExists(fileName)) {
fileName = this->Impl->BinaryDir + "/DartConfiguration.tcl";
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
"UpdateCTestConfiguration from :" << fileName << "\n");
if (!cmSystemTools::FileExists(fileName)) {
// No need to exit if we are not producing XML
if (this->Impl->ProduceXML) {
cmCTestLog(this, ERROR_MESSAGE,
"Cannot find file: " << fileName << std::endl);
return false;
}
} else {
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
"Parse Config file:" << fileName << "\n");
// parse the dart test file
cmsys::ifstream fin(fileName.c_str());
if (!fin) {
return false;
}
char buffer[1024];
while (fin) {
buffer[0] = 0;
fin.getline(buffer, 1023);
buffer[1023] = 0;
std::string line = cmCTest::CleanString(buffer);
if (line.empty()) {
continue;
}
while (fin && (line.back() == '\\')) {
line = line.substr(0, line.size() - 1);
buffer[0] = 0;
fin.getline(buffer, 1023);
buffer[1023] = 0;
line += cmCTest::CleanString(buffer);
}
if (line[0] == '#') {
continue;
}
std::string::size_type cpos = line.find_first_of(':');
if (cpos == std::string::npos) {
continue;
}
std::string key = line.substr(0, cpos);
std::string value = cmCTest::CleanString(line.substr(cpos + 1));
this->Impl->CTestConfiguration[key] = value;
}
fin.close();
}
if (!this->GetCTestConfiguration("BuildDirectory").empty()) {
this->Impl->BinaryDir = this->GetCTestConfiguration("BuildDirectory");
cmSystemTools::ChangeDirectory(this->Impl->BinaryDir);
}
this->Impl->TimeOut =
std::chrono::seconds(atoi(this->GetCTestConfiguration("TimeOut").c_str()));
std::string const& testLoad = this->GetCTestConfiguration("TestLoad");
if (!testLoad.empty()) {
unsigned long load;
if (cmStrToULong(testLoad, &load)) {
this->SetTestLoad(load);
} else {
cmCTestLog(this, WARNING,
"Invalid value for 'Test Load' : " << testLoad << std::endl);
}
}
if (this->Impl->ProduceXML) {
this->Impl->CompressXMLFiles =
cmIsOn(this->GetCTestConfiguration("CompressSubmission"));
}
return true;
}
void cmCTest::BlockTestErrorDiagnostics()
{
cmSystemTools::PutEnv("DART_TEST_FROM_DART=1");
cmSystemTools::PutEnv("DASHBOARD_TEST_FROM_CTEST=" CMake_VERSION);
#if defined(_WIN32)
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
#elif defined(__BEOS__) || defined(__HAIKU__)
disable_debugger(1);
#endif
}
void cmCTest::SetTestModel(int mode)
{
this->Impl->InteractiveDebugMode = false;
this->Impl->TestModel = mode;
}
int cmCTest::GetTestModel() const
{
return this->Impl->TestModel;
}
bool cmCTest::SetTest(const char* ttype, bool report)
{
if (cmSystemTools::LowerCase(ttype) == "all") {
for (Part p = PartStart; p != PartCount; p = Part(p + 1)) {
this->Impl->Parts[p].Enable();
}
return true;
}
Part p = this->GetPartFromName(ttype);
if (p != PartCount) {
this->Impl->Parts[p].Enable();
return true;
}
if (report) {
cmCTestLog(this, ERROR_MESSAGE,
"Don't know about test \"" << ttype << "\" yet..."
<< std::endl);
}
return false;
}
void cmCTest::Finalize()
{
}
bool cmCTest::OpenOutputFile(const std::string& path, const std::string& name,
cmGeneratedFileStream& stream, bool compress)
{
std::string testingDir = this->Impl->BinaryDir + "/Testing";
if (!path.empty()) {
testingDir += "/" + path;
}
if (cmSystemTools::FileExists(testingDir)) {
if (!cmSystemTools::FileIsDirectory(testingDir)) {
cmCTestLog(this, ERROR_MESSAGE,
"File " << testingDir
<< " is in the place of the testing directory"
<< std::endl);
return false;
}
} else {
if (!cmSystemTools::MakeDirectory(testingDir)) {
cmCTestLog(this, ERROR_MESSAGE,
"Cannot create directory " << testingDir << std::endl);
return false;
}
}
std::string filename = testingDir + "/" + name;
stream.Open(filename);
if (!stream) {
cmCTestLog(this, ERROR_MESSAGE,
"Problem opening file: " << filename << std::endl);
return false;
}
if (compress) {
if (this->Impl->CompressXMLFiles) {
stream.SetCompression(true);
}
}
return true;
}
bool cmCTest::AddIfExists(Part part, const char* file)
{
if (this->CTestFileExists(file)) {
this->AddSubmitFile(part, file);
} else {
std::string name = cmStrCat(file, ".gz");
if (this->CTestFileExists(name)) {
this->AddSubmitFile(part, file);
} else {
return false;
}
}
return true;
}
bool cmCTest::CTestFileExists(const std::string& filename)
{
std::string testingDir = this->Impl->BinaryDir + "/Testing/" +
this->Impl->CurrentTag + "/" + filename;
return cmSystemTools::FileExists(testingDir);
}
cmCTestBuildHandler* cmCTest::GetBuildHandler()
{
return &this->Impl->BuildHandler;
}
cmCTestBuildAndTestHandler* cmCTest::GetBuildAndTestHandler()
{
return &this->Impl->BuildAndTestHandler;
}
cmCTestCoverageHandler* cmCTest::GetCoverageHandler()
{
return &this->Impl->CoverageHandler;
}
cmCTestScriptHandler* cmCTest::GetScriptHandler()
{
return &this->Impl->ScriptHandler;
}
cmCTestTestHandler* cmCTest::GetTestHandler()
{
return &this->Impl->TestHandler;
}
cmCTestUpdateHandler* cmCTest::GetUpdateHandler()
{
return &this->Impl->UpdateHandler;
}
cmCTestConfigureHandler* cmCTest::GetConfigureHandler()
{
return &this->Impl->ConfigureHandler;
}
cmCTestMemCheckHandler* cmCTest::GetMemCheckHandler()
{
return &this->Impl->MemCheckHandler;
}
cmCTestSubmitHandler* cmCTest::GetSubmitHandler()
{
return &this->Impl->SubmitHandler;
}
cmCTestUploadHandler* cmCTest::GetUploadHandler()
{
return &this->Impl->UploadHandler;
}
int cmCTest::ProcessSteps()
{
int res = 0;
bool notest = true;
int update_count = 0;
for (Part p = PartStart; notest && p != PartCount; p = Part(p + 1)) {
notest = !this->Impl->Parts[p];
}
if (this->Impl->Parts[PartUpdate] &&
(this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
cmCTestUpdateHandler* uphandler = this->GetUpdateHandler();
uphandler->SetPersistentOption(
"SourceDirectory",
this->GetCTestConfiguration("SourceDirectory").c_str());
update_count = uphandler->ProcessHandler();
if (update_count < 0) {
res |= cmCTest::UPDATE_ERRORS;
}
}
if (this->Impl->TestModel == cmCTest::CONTINUOUS && !update_count) {
return 0;
}
if (this->Impl->Parts[PartConfigure] &&
(this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
if (this->GetConfigureHandler()->ProcessHandler() < 0) {
res |= cmCTest::CONFIGURE_ERRORS;
}
}
if (this->Impl->Parts[PartBuild] &&
(this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
this->UpdateCTestConfiguration();
if (this->GetBuildHandler()->ProcessHandler() < 0) {
res |= cmCTest::BUILD_ERRORS;
}
}
if ((this->Impl->Parts[PartTest] || notest) &&
(this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
this->UpdateCTestConfiguration();
if (this->GetTestHandler()->ProcessHandler() < 0) {
res |= cmCTest::TEST_ERRORS;
}
}
if (this->Impl->Parts[PartCoverage] &&
(this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
this->UpdateCTestConfiguration();
if (this->GetCoverageHandler()->ProcessHandler() < 0) {
res |= cmCTest::COVERAGE_ERRORS;
}
}
if (this->Impl->Parts[PartMemCheck] &&
(this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
this->UpdateCTestConfiguration();
if (this->GetMemCheckHandler()->ProcessHandler() < 0) {
res |= cmCTest::MEMORY_ERRORS;
}
}
if (!notest) {
std::string notes_dir = this->Impl->BinaryDir + "/Testing/Notes";
if (cmSystemTools::FileIsDirectory(notes_dir)) {
cmsys::Directory d;
d.Load(notes_dir);
unsigned long kk;
for (kk = 0; kk < d.GetNumberOfFiles(); kk++) {
const char* file = d.GetFile(kk);
std::string fullname = notes_dir + "/" + file;
if (cmSystemTools::FileExists(fullname) &&
!cmSystemTools::FileIsDirectory(fullname)) {
if (!this->Impl->NotesFiles.empty()) {
this->Impl->NotesFiles += ";";
}
this->Impl->NotesFiles += fullname;
this->Impl->Parts[PartNotes].Enable();
}
}
}
}
if (this->Impl->Parts[PartNotes]) {
this->UpdateCTestConfiguration();
if (!this->Impl->NotesFiles.empty()) {
this->GenerateNotesFile(this->Impl->NotesFiles.c_str());
}
}
if (this->Impl->Parts[PartSubmit]) {
this->UpdateCTestConfiguration();
if (this->GetSubmitHandler()->ProcessHandler() < 0) {
res |= cmCTest::SUBMIT_ERRORS;
}
}
if (res != 0) {
cmCTestLog(this, ERROR_MESSAGE, "Errors while running CTest" << std::endl);
}
return res;
}
std::string cmCTest::GetTestModelString()
{
if (!this->Impl->SpecificGroup.empty()) {
return this->Impl->SpecificGroup;
}
switch (this->Impl->TestModel) {
case cmCTest::NIGHTLY:
return "Nightly";
case cmCTest::CONTINUOUS:
return "Continuous";
}
return "Experimental";
}
int cmCTest::GetTestModelFromString(const char* str)
{
if (!str) {
return cmCTest::EXPERIMENTAL;
}
std::string rstr = cmSystemTools::LowerCase(str);
if (cmHasLiteralPrefix(rstr, "cont")) {
return cmCTest::CONTINUOUS;
}
if (cmHasLiteralPrefix(rstr, "nigh")) {
return cmCTest::NIGHTLY;
}
return cmCTest::EXPERIMENTAL;
}
//######################################################################
//######################################################################
//######################################################################
//######################################################################
int cmCTest::RunMakeCommand(const std::string& command, std::string& output,
int* retVal, const char* dir, cmDuration timeout,
std::ostream& ofs, Encoding encoding)
{
// First generate the command and arguments
std::vector<std::string> args = cmSystemTools::ParseArguments(command);
if (args.empty()) {
return false;
}
std::vector<const char*> argv;
argv.reserve(args.size() + 1);
for (std::string const& a : args) {
argv.push_back(a.c_str());
}
argv.push_back(nullptr);
output.clear();
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, "Run command:");
for (char const* arg : argv) {
if (!arg) {
break;
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, " \"" << arg << "\"");
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, std::endl);
// Now create process object
cmsysProcess* cp = cmsysProcess_New();
cmsysProcess_SetCommand(cp, argv.data());
cmsysProcess_SetWorkingDirectory(cp, dir);
cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
cmsysProcess_SetTimeout(cp, timeout.count());
cmsysProcess_Execute(cp);
// Initialize tick's
std::string::size_type tick = 0;
std::string::size_type tick_len = 1024;
std::string::size_type tick_line_len = 50;
char* data;
int length;
cmProcessOutput processOutput(encoding);
std::string strdata;
cmCTestLog(this, HANDLER_PROGRESS_OUTPUT,
" Each . represents " << tick_len
<< " bytes of output\n"
" "
<< std::flush);
while (cmsysProcess_WaitForData(cp, &data, &length, nullptr)) {
processOutput.DecodeText(data, length, strdata);
for (char& cc : strdata) {
if (cc == 0) {
cc = '\n';
}
}
output.append(strdata);
while (output.size() > (tick * tick_len)) {
tick++;
cmCTestLog(this, HANDLER_PROGRESS_OUTPUT, "." << std::flush);
if (tick % tick_line_len == 0 && tick > 0) {
cmCTestLog(this, HANDLER_PROGRESS_OUTPUT,
" Size: " << int((double(output.size()) / 1024.0) + 1)
<< "K\n " << std::flush);
}
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
cmCTestLogWrite(strdata.c_str(), strdata.size()));
if (ofs) {
ofs << cmCTestLogWrite(strdata.c_str(), strdata.size());
}
}
processOutput.DecodeText(std::string(), strdata);
if (!strdata.empty()) {
output.append(strdata);
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
cmCTestLogWrite(strdata.c_str(), strdata.size()));
if (ofs) {
ofs << cmCTestLogWrite(strdata.c_str(), strdata.size());
}
}
cmCTestLog(this, HANDLER_PROGRESS_OUTPUT,
" Size of output: " << int(double(output.size()) / 1024.0) << "K"
<< std::endl);
cmsysProcess_WaitForExit(cp, nullptr);
int result = cmsysProcess_GetState(cp);
if (result == cmsysProcess_State_Exited) {
*retVal = cmsysProcess_GetExitValue(cp);
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
"Command exited with the value: " << *retVal << std::endl);
} else if (result == cmsysProcess_State_Exception) {
*retVal = cmsysProcess_GetExitException(cp);
cmCTestLog(this, WARNING,
"There was an exception: " << *retVal << std::endl);
} else if (result == cmsysProcess_State_Expired) {
cmCTestLog(this, WARNING, "There was a timeout" << std::endl);
} else if (result == cmsysProcess_State_Error) {
output += "\n*** ERROR executing: ";
output += cmsysProcess_GetErrorString(cp);
output += "\n***The build process failed.";
cmCTestLog(this, ERROR_MESSAGE,
"There was an error: " << cmsysProcess_GetErrorString(cp)
<< std::endl);
}
cmsysProcess_Delete(cp);
return result;
}
//######################################################################
//######################################################################
//######################################################################
//######################################################################
int cmCTest::RunTest(std::vector<const char*> argv, std::string* output,
int* retVal, std::ostream* log, cmDuration testTimeOut,
std::vector<std::string>* environment, Encoding encoding)
{
bool modifyEnv = (environment && !environment->empty());
// determine how much time we have
cmDuration timeout = this->GetRemainingTimeAllowed();
if (timeout != cmCTest::MaxDuration()) {
timeout -= std::chrono::minutes(2);
}
if (this->Impl->TimeOut > cmDuration::zero() &&
this->Impl->TimeOut < timeout) {
timeout = this->Impl->TimeOut;
}
if (testTimeOut > cmDuration::zero() &&
testTimeOut < this->GetRemainingTimeAllowed()) {
timeout = testTimeOut;
}
// always have at least 1 second if we got to here
if (timeout <= cmDuration::zero()) {
timeout = std::chrono::seconds(1);
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
"Test timeout computed to be: "
<< (timeout == cmCTest::MaxDuration()
? std::string("infinite")
: std::to_string(cmDurationTo<unsigned int>(timeout)))
<< "\n");
if (cmSystemTools::SameFile(argv[0], cmSystemTools::GetCTestCommand()) &&
!this->Impl->ForceNewCTestProcess) {
cmCTest inst;
inst.Impl->ConfigType = this->Impl->ConfigType;
inst.Impl->TimeOut = timeout;
// Capture output of the child ctest.
std::ostringstream oss;
inst.SetStreams(&oss, &oss);
std::vector<std::string> args;
for (char const* i : argv) {
if (i) {
// make sure we pass the timeout in for any build and test
// invocations. Since --build-generator is required this is a
// good place to check for it, and to add the arguments in
if (strcmp(i, "--build-generator") == 0 &&
timeout != cmCTest::MaxDuration() &&
timeout > cmDuration::zero()) {
args.emplace_back("--test-timeout");
args.push_back(std::to_string(cmDurationTo<unsigned int>(timeout)));
}
args.emplace_back(i);
}
}
if (log) {
*log << "* Run internal CTest" << std::endl;
}
std::unique_ptr<cmSystemTools::SaveRestoreEnvironment> saveEnv;
if (modifyEnv) {
saveEnv = cm::make_unique<cmSystemTools::SaveRestoreEnvironment>();
cmSystemTools::AppendEnv(*environment);
}
*retVal = inst.Run(args, output);
if (output) {
*output += oss.str();
}
if (log && output) {
*log << *output;
}
if (output) {
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
"Internal cmCTest object used to run test." << std::endl
<< *output
<< std::endl);
}
return cmsysProcess_State_Exited;
}
std::vector<char> tempOutput;
if (output) {
output->clear();
}
std::unique_ptr<cmSystemTools::SaveRestoreEnvironment> saveEnv;
if (modifyEnv) {
saveEnv = cm::make_unique<cmSystemTools::SaveRestoreEnvironment>();
cmSystemTools::AppendEnv(*environment);
}
cmsysProcess* cp = cmsysProcess_New();
cmsysProcess_SetCommand(cp, argv.data());
cmCTestLog(this, DEBUG, "Command is: " << argv[0] << std::endl);
if (cmSystemTools::GetRunCommandHideConsole()) {
cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
}
cmsysProcess_SetTimeout(cp, timeout.count());
cmsysProcess_Execute(cp);
char* data;
int length;
cmProcessOutput processOutput(encoding);
std::string strdata;
while (cmsysProcess_WaitForData(cp, &data, &length, nullptr)) {
processOutput.DecodeText(data, length, strdata);
if (output) {
cm::append(tempOutput, data, data + length);
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
cmCTestLogWrite(strdata.c_str(), strdata.size()));
if (log) {
log->write(strdata.c_str(), strdata.size());
}
}
processOutput.DecodeText(std::string(), strdata);
if (!strdata.empty()) {
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
cmCTestLogWrite(strdata.c_str(), strdata.size()));
if (log) {
log->write(strdata.c_str(), strdata.size());
}
}
cmsysProcess_WaitForExit(cp, nullptr);
processOutput.DecodeText(tempOutput, tempOutput);
if (output && tempOutput.begin() != tempOutput.end()) {
output->append(tempOutput.data(), tempOutput.size());
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
"-- Process completed" << std::endl);
int result = cmsysProcess_GetState(cp);
if (result == cmsysProcess_State_Exited) {
*retVal = cmsysProcess_GetExitValue(cp);
if (*retVal != 0 && this->Impl->OutputTestOutputOnTestFailure) {
OutputTestErrors(tempOutput);
}
} else if (result == cmsysProcess_State_Exception) {
if (this->Impl->OutputTestOutputOnTestFailure) {
OutputTestErrors(tempOutput);
}
*retVal = cmsysProcess_GetExitException(cp);
std::string outerr = cmStrCat("\n*** Exception executing: ",
cmsysProcess_GetExceptionString(cp));
if (output) {
*output += outerr;
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, outerr << std::endl);
} else if (result == cmsysProcess_State_Error) {
std::string outerr =
cmStrCat("\n*** ERROR executing: ", cmsysProcess_GetErrorString(cp));
if (output) {
*output += outerr;
}
cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, outerr << std::endl);
}
cmsysProcess_Delete(cp);
return result;
}
std::string cmCTest::SafeBuildIdField(const std::string& value)
{
std::string safevalue(value);
if (!safevalue.empty()) {
// Disallow non-filename and non-space whitespace characters.
// If they occur, replace them with ""
//
const char* disallowed = "\\:*?\"<>|\n\r\t\f\v";
if (safevalue.find_first_of(disallowed) != std::string::npos) {
std::string::size_type i = 0;
std::string::size_type n = strlen(disallowed);
char replace[2];
replace[1] = 0;
for (i = 0; i < n; ++i) {
replace[0] = disallowed[i];
cmSystemTools::ReplaceString(safevalue, replace, "");
}
}
}
if (safevalue.empty()) {
safevalue = "(empty)";
}
return safevalue;
}
void cmCTest::StartXML(cmXMLWriter& xml, bool append)
{
if (this->Impl->CurrentTag.empty()) {
cmCTestLog(this, ERROR_MESSAGE,
"Current Tag empty, this may mean"
" NightlStartTime was not set correctly."
<< std::endl);
cmSystemTools::SetFatalErrorOccured();
}
// find out about the system
cmsys::SystemInformation info;
info.RunCPUCheck();
info.RunOSCheck();
info.RunMemoryCheck();
std::string buildname =
cmCTest::SafeBuildIdField(this->GetCTestConfiguration("BuildName"));
std::string stamp = cmCTest::SafeBuildIdField(this->Impl->CurrentTag + "-" +
this->GetTestModelString());
std::string site =
cmCTest::SafeBuildIdField(this->GetCTestConfiguration("Site"));
xml.StartDocument();
xml.StartElement("Site");
xml.Attribute("BuildName", buildname);
xml.BreakAttributes();
xml.Attribute("BuildStamp", stamp);
xml.Attribute("Name", site);
xml.Attribute("Generator",
std::string("ctest-") + cmVersion::GetCMakeVersion());
if (append) {
xml.Attribute("Append", "true");
}
xml.Attribute("CompilerName", this->GetCTestConfiguration("Compiler"));
xml.Attribute("CompilerVersion",
this->GetCTestConfiguration("CompilerVersion"));
xml.Attribute("OSName", info.GetOSName());
xml.Attribute("Hostname", info.GetHostname());
xml.Attribute("OSRelease", info.GetOSRelease());
xml.Attribute("OSVersion", info.GetOSVersion());
xml.Attribute("OSPlatform", info.GetOSPlatform());
xml.Attribute("Is64Bits", info.Is64Bits());
xml.Attribute("VendorString", info.GetVendorString());
xml.Attribute("VendorID", info.GetVendorID());
xml.Attribute("FamilyID", info.GetFamilyID());
xml.Attribute("ModelID", info.GetModelID());
xml.Attribute("ProcessorCacheSize", info.GetProcessorCacheSize());
xml.Attribute("NumberOfLogicalCPU", info.GetNumberOfLogicalCPU());
xml.Attribute("NumberOfPhysicalCPU", info.GetNumberOfPhysicalCPU());
xml.Attribute("TotalVirtualMemory", info.GetTotalVirtualMemory());
xml.Attribute("TotalPhysicalMemory", info.GetTotalPhysicalMemory());
xml.Attribute("LogicalProcessorsPerPhysical",
info.GetLogicalProcessorsPerPhysical());
xml.Attribute("ProcessorClockFrequency", info.GetProcessorClockFrequency());
std::string changeId = this->GetCTestConfiguration("ChangeId");
if (!changeId.empty()) {
xml.Attribute("ChangeId", changeId);
}
this->AddSiteProperties(xml);
}
void cmCTest::AddSiteProperties(cmXMLWriter& xml)
{
cmCTestScriptHandler* ch = this->GetScriptHandler();
cmake* cm = ch->GetCMake();
// if no CMake then this is the old style script and props like
// this will not work anyway.
if (!cm) {
return;
}
// This code should go when cdash is changed to use labels only
const char* subproject = cm->GetState()->GetGlobalProperty("SubProject");
if (subproject) {
xml.StartElement("Subproject");
xml.Attribute("name", subproject);
const char* labels =
ch->GetCMake()->GetState()->GetGlobalProperty("SubProjectLabels");
if (labels) {
xml.StartElement("Labels");
std::string l = labels;
std::vector<std::string> args = cmExpandedList(l);
for (std::string const& i : args) {
xml.Element("Label", i);
}
xml.EndElement();
}
xml.EndElement();
}
// This code should stay when cdash only does label based sub-projects
const char* label = cm->GetState()->GetGlobalProperty("Label");
if (label) {
xml.StartElement("Labels");
xml.Element("Label", label);
xml.EndElement();
}
}
void cmCTest::GenerateSubprojectsOutput(cmXMLWriter& xml)
{
for (std::string const& subproj : this->GetLabelsForSubprojects()) {
xml.StartElement("Subproject");
xml.Attribute("name", subproj);
xml.Element("Label", subproj);
xml.EndElement(); // Subproject
}
}
std::vector<std::string> cmCTest::GetLabelsForSubprojects()
{
std::string labelsForSubprojects =
this->GetCTestConfiguration("LabelsForSubprojects");
std::vector<std::string> subprojects = cmExpandedList(labelsForSubprojects);
// sort the array
std::sort(subprojects.begin(), subprojects.end());
// remove duplicates
auto new_end = std::unique(subprojects.begin(), subprojects.end());
subprojects.erase(new_end, subprojects.end());
return subprojects;
}
void cmCTest::EndXML(cmXMLWriter& xml)
{
xml.EndElement(); // Site
xml.EndDocument();
}
int cmCTest::GenerateCTestNotesOutput(cmXMLWriter& xml,
std::vector<std::string> const& files)
{
std::string buildname =
cmCTest::SafeBuildIdField(this->GetCTestConfiguration("BuildName"));
xml.StartDocument();
xml.ProcessingInstruction("xml-stylesheet",
"type=\"text/xsl\" "
"href=\"Dart/Source/Server/XSL/Build.xsl "
"<file:///Dart/Source/Server/XSL/Build.xsl> \"");
xml.StartElement("Site");
xml.Attribute("BuildName", buildname);
xml.Attribute("BuildStamp",
this->Impl->CurrentTag + "-" + this->GetTestModelString());
xml.Attribute("Name", this->GetCTestConfiguration("Site"));
xml.Attribute("Generator",
std::string("ctest-") + cmVersion::GetCMakeVersion());
this->AddSiteProperties(xml);
xml.StartElement("Notes");
for (std::string const& file : files) {
cmCTestLog(this, OUTPUT, "\tAdd file: " << file << std::endl);
std::string note_time = this->CurrentTime();
xml.StartElement("Note");
xml.Attribute("Name", file);
xml.Element("Time", std::chrono::system_clock::now());
xml.Element("DateTime", note_time);
xml.StartElement("Text");
cmsys::ifstream ifs(file.c_str());
if (ifs) {
std::string line;
while (cmSystemTools::GetLineFromStream(ifs, line)) {
xml.Content(line);
xml.Content("\n");
}
ifs.close();
} else {
xml.Content("Problem reading file: " + file + "\n");
cmCTestLog(this, ERROR_MESSAGE,
"Problem reading file: " << file << " while creating notes"
<< std::endl);
}
xml.EndElement(); // Text
xml.EndElement(); // Note
}
xml.EndElement(); // Notes
xml.EndElement(); // Site
xml.EndDocument();
return 1;
}
int cmCTest::GenerateNotesFile(std::vector<std::string> const& files)
{
cmGeneratedFileStream ofs;
if (!this->OpenOutputFile(this->Impl->CurrentTag, "Notes.xml", ofs)) {
cmCTestLog(this, ERROR_MESSAGE, "Cannot open notes file" << std::endl);
return 1;
}
cmXMLWriter xml(ofs);
this->GenerateCTestNotesOutput(xml, files);
return 0;
}
int cmCTest::GenerateNotesFile(const char* cfiles)
{
if (!cfiles) {
return 1;
}
cmCTestLog(this, OUTPUT, "Create notes file" << std::endl);
std::vector<std::string> const files =
cmSystemTools::SplitString(cfiles, ';');
if (files.empty()) {
return 1;
}
return this->GenerateNotesFile(files);
}
int cmCTest::GenerateDoneFile()
{
cmGeneratedFileStream ofs;
if (!this->OpenOutputFile(this->Impl->CurrentTag, "Done.xml", ofs)) {
cmCTestLog(this, ERROR_MESSAGE, "Cannot open done file" << std::endl);
return 1;
}
cmXMLWriter xml(ofs);
xml.StartDocument();
xml.StartElement("Done");
xml.Element("buildId", this->Impl->BuildID);
xml.Element("time", std::chrono::system_clock::now());
xml.EndElement(); // Done
xml.EndDocument();
return 0;
}
std::string cmCTest::Base64GzipEncodeFile(std::string const& file)
{
std::string tarFile = file + "_temp.tar.gz";
std::vector<std::string> files;
files.push_back(file);
if (!cmSystemTools::CreateTar(tarFile, files, cmSystemTools::TarCompressGZip,
false)) {
cmCTestLog(this, ERROR_MESSAGE,
"Error creating tar while "
"encoding file: "
<< file << std::endl);
return "";
}
std::string base64 = this->Base64EncodeFile(tarFile);
cmSystemTools::RemoveFile(tarFile);
return base64;
}
std::string cmCTest::Base64EncodeFile(std::string const& file)
{
size_t const len = cmSystemTools::FileLength(file);
cmsys::ifstream ifs(file.c_str(),
std::ios::in
#ifdef _WIN32
| std::ios::binary
#endif
);
std::vector<char> file_buffer(len + 1);
ifs.read(&file_buffer[0], len);
ifs.close();
std::vector<char> encoded_buffer((len * 3) / 2 + 5);
size_t const rlen = cmsysBase64_Encode(
reinterpret_cast<unsigned char*>(&file_buffer[0]), len,
reinterpret_cast<unsigned char*>(&encoded_buffer[0]), 1);
return std::string(&encoded_buffer[0], rlen);
}
bool cmCTest::SubmitExtraFiles(std::vector<std::string> const& files)
{
for (std::string const& file : files) {
if (!cmSystemTools::FileExists(file)) {
cmCTestLog(this, ERROR_MESSAGE,
"Cannot find extra file: " << file << " to submit."
<< std::endl;);
return false;
}
this->AddSubmitFile(PartExtraFiles, file.c_str());
}
return true;
}
bool cmCTest::SubmitExtraFiles(const char* cfiles)
{
if (!cfiles) {
return true;
}
cmCTestLog(this, OUTPUT, "Submit extra files" << std::endl);
std::vector<std::string> const files =
cmSystemTools::SplitString(cfiles, ';');
if (files.empty()) {
return true;
}
return this->SubmitExtraFiles(files);
}
// for a -D argument convert the next argument into
// the proper list of dashboard steps via SetTest
bool cmCTest::AddTestsForDashboardType(std::string& targ)
{
if (targ == "Experimental") {
this->SetTestModel(cmCTest::EXPERIMENTAL);
this->SetTest("Start");
this->SetTest("Configure");
this->SetTest("Build");
this->SetTest("Test");
this->SetTest("Coverage");
this->SetTest("Submit");
} else if (targ == "ExperimentalStart") {
this->SetTestModel(cmCTest::EXPERIMENTAL);
this->SetTest("Start");
} else if (targ == "ExperimentalUpdate") {
this->SetTestModel(cmCTest::EXPERIMENTAL);
this->SetTest("Update");
} else if (targ == "ExperimentalConfigure") {
this->SetTestModel(cmCTest::EXPERIMENTAL);
this->SetTest("Configure");
} else if (targ == "ExperimentalBuild") {
this->SetTestModel(cmCTest::EXPERIMENTAL);
this->SetTest("Build");
} else if (targ == "ExperimentalTest") {
this->SetTestModel(cmCTest::EXPERIMENTAL);
this->SetTest("Test");
} else if (targ == "ExperimentalMemCheck" || targ == "ExperimentalPurify") {
this->SetTestModel(cmCTest::EXPERIMENTAL);
this->SetTest("MemCheck");
} else if (targ == "ExperimentalCoverage") {
this->SetTestModel(cmCTest::EXPERIMENTAL);
this->SetTest("Coverage");
} else if (targ == "ExperimentalSubmit") {
this->SetTestModel(cmCTest::EXPERIMENTAL);
this->SetTest("Submit");
} else if (targ == "Continuous") {
this->SetTestModel(cmCTest::CONTINUOUS);
this->SetTest("Start");
this->SetTest("Update");
this->SetTest("Configure");
this->SetTest("Build");
this->SetTest("Test");
this->SetTest("Coverage");
this->SetTest("Submit");
} else if (targ == "ContinuousStart") {
this->SetTestModel(cmCTest::CONTINUOUS);
this->SetTest("Start");
} else if (targ == "ContinuousUpdate") {
this->SetTestModel(cmCTest::CONTINUOUS);
this->SetTest("Update");
} else if (targ == "ContinuousConfigure") {
this->SetTestModel(cmCTest::CONTINUOUS);
this->SetTest("Configure");
} else if (targ == "ContinuousBuild") {
this->SetTestModel(cmCTest::CONTINUOUS);
this->SetTest("Build");
} else if (targ == "ContinuousTest") {
this->SetTestModel(cmCTest::CONTINUOUS);
this->SetTest("Test");
} else if (targ == "ContinuousMemCheck" || targ == "ContinuousPurify") {
this->SetTestModel(cmCTest::CONTINUOUS);
this->SetTest("MemCheck");
} else if (targ == "ContinuousCoverage") {
this->SetTestModel(cmCTest::CONTINUOUS);
this->SetTest("Coverage");
} else if (targ == "ContinuousSubmit") {
this->SetTestModel(cmCTest::CONTINUOUS);
this->SetTest("Submit");
} else if (targ == "Nightly") {
this->SetTestModel(cmCTest::NIGHTLY);
this->SetTest("Start");
this->SetTest("Update");
this->SetTest("Configure");
this->SetTest("Build");
this->SetTest("Test");
this->SetTest("Coverage");
this->SetTest("Submit");
} else if (targ == "NightlyStart") {
this->SetTestModel(cmCTest::NIGHTLY);
this->SetTest("Start");
} else if (targ == "NightlyUpdate") {
this->SetTestModel(cmCTest::NIGHTLY);
this->SetTest("Update");
} else if (targ == "NightlyConfigure") {
this->SetTestModel(cmCTest::NIGHTLY);
this->SetTest("Configure");
} else if (targ == "NightlyBuild") {
this->SetTestModel(cmCTest::NIGHTLY);
this->SetTest("Build");
} else if (targ == "NightlyTest") {
this->SetTestModel(cmCTest::NIGHTLY);
this->SetTest("Test");
} else if (targ == "NightlyMemCheck" || targ == "NightlyPurify") {
this->SetTestModel(cmCTest::NIGHTLY);
this->SetTest("MemCheck");
} else if (targ == "NightlyCoverage") {
this->SetTestModel(cmCTest::NIGHTLY);
this->SetTest("Coverage");
} else if (targ == "NightlySubmit") {
this->SetTestModel(cmCTest::NIGHTLY);
this->SetTest("Submit");
} else if (targ == "MemoryCheck") {
this->SetTestModel(cmCTest::EXPERIMENTAL);
this->SetTest("Start");
this->SetTest("Configure");
this->SetTest("Build");
this->SetTest("MemCheck");
this->SetTest("Coverage");
this->SetTest("Submit");
} else if (targ == "NightlyMemoryCheck") {
this->SetTestModel(cmCTest::NIGHTLY);
this->SetTest("Start");
this->SetTest("Update");
this->SetTest("Configure");
this->SetTest("Build");
this->SetTest("MemCheck");
this->SetTest("Coverage");
this->SetTest("Submit");
} else {
return false;
}
return true;
}
void cmCTest::ErrorMessageUnknownDashDValue(std::string& val)
{
cmCTestLog(this, ERROR_MESSAGE,
"CTest -D called with incorrect option: " << val << std::endl);
cmCTestLog(
this, ERROR_MESSAGE,
"Available options are:"
<< std::endl
<< " ctest -D Continuous" << std::endl
<< " ctest -D Continuous(Start|Update|Configure|Build)" << std::endl
<< " ctest -D Continuous(Test|Coverage|MemCheck|Submit)" << std::endl
<< " ctest -D Experimental" << std::endl
<< " ctest -D Experimental(Start|Update|Configure|Build)" << std::endl
<< " ctest -D Experimental(Test|Coverage|MemCheck|Submit)" << std::endl
<< " ctest -D Nightly" << std::endl
<< " ctest -D Nightly(Start|Update|Configure|Build)" << std::endl
<< " ctest -D Nightly(Test|Coverage|MemCheck|Submit)" << std::endl
<< " ctest -D NightlyMemoryCheck" << std::endl);
}
bool cmCTest::CheckArgument(const std::string& arg, const char* varg1,
const char* varg2)
{
return (varg1 && arg == varg1) || (varg2 && arg == varg2);
}
// Processes one command line argument (and its arguments if any)
// for many simple options and then returns
bool cmCTest::HandleCommandLineArguments(size_t& i,
std::vector<std::string>& args,
std::string& errormsg)
{
std::string arg = args[i];
if (this->CheckArgument(arg, "-F")) {
this->Impl->Failover = true;
}
if (this->CheckArgument(arg, "-j", "--parallel") && i < args.size() - 1) {
i++;
int plevel = atoi(args[i].c_str());
this->SetParallelLevel(plevel);
this->Impl->ParallelLevelSetInCli = true;
} else if (arg.find("-j") == 0) {
int plevel = atoi(arg.substr(2).c_str());
this->SetParallelLevel(plevel);
this->Impl->ParallelLevelSetInCli = true;
}
if (this->CheckArgument(arg, "--repeat-until-fail")) {
if (i >= args.size() - 1) {
errormsg = "'--repeat-until-fail' requires an argument";
return false;
}
if (this->Impl->RepeatMode != cmCTest::Repeat::Never) {
errormsg = "At most one '--repeat' option may be used.";
return false;
}
i++;
long repeat = 1;
if (!cmStrToLong(args[i], &repeat)) {
errormsg =
"'--repeat-until-fail' given non-integer value '" + args[i] + "'";
return false;
}
this->Impl->RepeatCount = static_cast<int>(repeat);
if (repeat > 1) {
this->Impl->RepeatMode = cmCTest::Repeat::UntilFail;
}
}
if (this->CheckArgument(arg, "--repeat")) {
if (i >= args.size() - 1) {
errormsg = "'--repeat' requires an argument";
return false;
}
if (this->Impl->RepeatMode != cmCTest::Repeat::Never) {
errormsg = "At most one '--repeat' option may be used.";
return false;
}
i++;
cmsys::RegularExpression repeatRegex(
"^(until-fail|until-pass|after-timeout):([0-9]+)$");
if (repeatRegex.find(args[i])) {
std::string const& count = repeatRegex.match(2);
unsigned long n = 1;
cmStrToULong(count, &n); // regex guarantees success
this->Impl->RepeatCount = static_cast<int>(n);
if (this->Impl->RepeatCount > 1) {
std::string const& mode = repeatRegex.match(1);
if (mode == "until-fail") {
this->Impl->RepeatMode = cmCTest::Repeat::UntilFail;
} else if (mode == "until-pass") {
this->Impl->RepeatMode = cmCTest::Repeat::UntilPass;
} else if (mode == "after-timeout") {
this->Impl->RepeatMode = cmCTest::Repeat::AfterTimeout;
}
}
} else {
errormsg = "'--repeat' given invalid value '" + args[i] + "'";
return false;
}
}
if (this->CheckArgument(arg, "--test-load") && i < args.size() - 1) {
i++;
unsigned long load;
if (cmStrToULong(args[i], &load)) {
this->SetTestLoad(load);
} else {
cmCTestLog(this, WARNING,
"Invalid value for 'Test Load' : " << args[i] << std::endl);
}
}
if (this->CheckArgument(arg, "--no-compress-output")) {
this->Impl->CompressTestOutput = false;
}
if (this->CheckArgument(arg, "--print-labels")) {
this->Impl->PrintLabels = true;
}
if (this->CheckArgument(arg, "--http1.0")) {
this->Impl->UseHTTP10 = true;
}
if (this->CheckArgument(arg, "--timeout") && i < args.size() - 1) {
i++;
auto timeout = cmDuration(atof(args[i].c_str()));
this->Impl->GlobalTimeout = timeout;
}
if (this->CheckArgument(arg, "--stop-time") && i < args.size() - 1) {
i++;
this->SetStopTime(args[i]);
}
if (this->CheckArgument(arg, "-C", "--build-config") &&
i < args.size() - 1) {
i++;
this->SetConfigType(args[i].c_str());
}
if (this->CheckArgument(arg, "--debug")) {
this->Impl->Debug = true;
this->Impl->ShowLineNumbers = true;
}
if (this->CheckArgument(arg, "--group") && i < args.size() - 1) {
i++;
this->Impl->SpecificGroup = args[i];
}
// This is an undocumented / deprecated option.
// "Track" has been renamed to "Group".
if (this->CheckArgument(arg, "--track") && i < args.size() - 1) {
i++;
this->Impl->SpecificGroup = args[i];
}
if (this->CheckArgument(arg, "--show-line-numbers")) {
this->Impl->ShowLineNumbers = true;
}
if (this->CheckArgument(arg, "--no-label-summary")) {
this->Impl->LabelSummary = false;
}
if (this->CheckArgument(arg, "--no-subproject-summary")) {
this->Impl->SubprojectSummary = false;
}
if (this->CheckArgument(arg, "-Q", "--quiet")) {
this->Impl->Quiet = true;
}
if (this->CheckArgument(arg, "--progress")) {
this->Impl->TestProgressOutput = true;
}
if (this->CheckArgument(arg, "-V", "--verbose")) {
this->Impl->Verbose = true;
}
if (this->CheckArgument(arg, "-VV", "--extra-verbose")) {
this->Impl->ExtraVerbose = true;
this->Impl->Verbose = true;
}
if (this->CheckArgument(arg, "--output-on-failure")) {
this->Impl->OutputTestOutputOnTestFailure = true;
}
if (this->CheckArgument(arg, "--test-output-size-passed") &&
i < args.size() - 1) {
i++;
long outputSize;
if (cmStrToLong(args[i], &outputSize)) {
this->Impl->TestHandler.SetTestOutputSizePassed(int(outputSize));
} else {
cmCTestLog(this, WARNING,
"Invalid value for '--test-output-size-passed': " << args[i]
<< "\n");
}
}
if (this->CheckArgument(arg, "--test-output-size-failed") &&
i < args.size() - 1) {
i++;
long outputSize;
if (cmStrToLong(args[i], &outputSize)) {
this->Impl->TestHandler.SetTestOutputSizeFailed(int(outputSize));
} else {
cmCTestLog(this, WARNING,
"Invalid value for '--test-output-size-failed': " << args[i]
<< "\n");
}
}
if (this->CheckArgument(arg, "-N", "--show-only")) {
this->Impl->ShowOnly = true;
}
if (cmHasLiteralPrefix(arg, "--show-only=")) {
this->Impl->ShowOnly = true;
// Check if a specific format is requested. Defaults to human readable
// text.
std::string argWithFormat = "--show-only=";
std::string format = arg.substr(argWithFormat.length());
if (format == "json-v1") {
// Force quiet mode so the only output is the json object model.
this->Impl->Quiet = true;
this->Impl->OutputAsJson = true;
this->Impl->OutputAsJsonVersion = 1;
} else if (format != "human") {
errormsg = "'--show-only=' given unknown value '" + format + "'";
return false;
}
}
if (this->CheckArgument(arg, "-O", "--output-log") && i < args.size() - 1) {
i++;
this->SetOutputLogFileName(args[i].c_str());
}
if (this->CheckArgument(arg, "--tomorrow-tag")) {
this->Impl->TomorrowTag = true;
}
if (this->CheckArgument(arg, "--force-new-ctest-process")) {
this->Impl->ForceNewCTestProcess = true;
}
if (this->CheckArgument(arg, "-W", "--max-width") && i < args.size() - 1) {
i++;
this->Impl->MaxTestNameWidth = atoi(args[i].c_str());
}
if (this->CheckArgument(arg, "--interactive-debug-mode") &&
i < args.size() - 1) {
i++;
this->Impl->InteractiveDebugMode = cmIsOn(args[i]);
}
if (this->CheckArgument(arg, "--submit-index") && i < args.size() - 1) {
i++;
this->Impl->SubmitIndex = atoi(args[i].c_str());
if (this->Impl->SubmitIndex < 0) {
this->Impl->SubmitIndex = 0;
}
}
if (this->CheckArgument(arg, "--overwrite") && i < args.size() - 1) {
i++;
this->AddCTestConfigurationOverwrite(args[i]);
}
if (this->CheckArgument(arg, "-A", "--add-notes") && i < args.size() - 1) {
this->Impl->ProduceXML = true;
this->SetTest("Notes");
i++;
this->SetNotesFiles(args[i].c_str());
}
const std::string noTestsPrefix = "--no-tests=";
if (cmHasPrefix(arg, noTestsPrefix)) {
const std::string noTestsMode = arg.substr(noTestsPrefix.length());
if (noTestsMode == "error") {
this->Impl->NoTestsMode = cmCTest::NoTests::Error;
} else if (noTestsMode != "ignore") {
errormsg = "'--no-tests=' given unknown value '" + noTestsMode + "'";
return false;
} else {
this->Impl->NoTestsMode = cmCTest::NoTests::Ignore;
}
}
// options that control what tests are run
if (this->CheckArgument(arg, "-I", "--tests-information") &&
i < args.size() - 1) {
i++;
this->GetTestHandler()->SetPersistentOption("TestsToRunInformation",
args[i].c_str());
this->GetMemCheckHandler()->SetPersistentOption("TestsToRunInformation",
args[i].c_str());
}
if (this->CheckArgument(arg, "-U", "--union")) {
this->GetTestHandler()->SetPersistentOption("UseUnion", "true");
this->GetMemCheckHandler()->SetPersistentOption("UseUnion", "true");
}
if (this->CheckArgument(arg, "-R", "--tests-regex") && i < args.size() - 1) {
i++;
this->GetTestHandler()->SetPersistentOption("IncludeRegularExpression",
args[i].c_str());
this->GetMemCheckHandler()->SetPersistentOption("IncludeRegularExpression",
args[i].c_str());
}
if (this->CheckArgument(arg, "-L", "--label-regex") && i < args.size() - 1) {
i++;
this->GetTestHandler()->SetPersistentOption("LabelRegularExpression",
args[i].c_str());
this->GetMemCheckHandler()->SetPersistentOption("LabelRegularExpression",
args[i].c_str());
}
if (this->CheckArgument(arg, "-LE", "--label-exclude") &&
i < args.size() - 1) {
i++;
this->GetTestHandler()->SetPersistentOption(
"ExcludeLabelRegularExpression", args[i].c_str());
this->GetMemCheckHandler()->SetPersistentOption(
"ExcludeLabelRegularExpression", args[i].c_str());
}
if (this->CheckArgument(arg, "-E", "--exclude-regex") &&
i < args.size() - 1) {
i++;
this->GetTestHandler()->SetPersistentOption("ExcludeRegularExpression",
args[i].c_str());
this->GetMemCheckHandler()->SetPersistentOption("ExcludeRegularExpression",
args[i].c_str());
}
if (this->CheckArgument(arg, "-FA", "--fixture-exclude-any") &&
i < args.size() - 1) {
i++;
this->GetTestHandler()->SetPersistentOption(
"ExcludeFixtureRegularExpression", args[i].c_str());
this->GetMemCheckHandler()->SetPersistentOption(
"ExcludeFixtureRegularExpression", args[i].c_str());
}
if (this->CheckArgument(arg, "-FS", "--fixture-exclude-setup") &&
i < args.size() - 1) {
i++;
this->GetTestHandler()->SetPersistentOption(
"ExcludeFixtureSetupRegularExpression", args[i].c_str());
this->GetMemCheckHandler()->SetPersistentOption(
"ExcludeFixtureSetupRegularExpression", args[i].c_str());
}
if (this->CheckArgument(arg, "-FC", "--fixture-exclude-cleanup") &&
i < args.size() - 1) {
i++;
this->GetTestHandler()->SetPersistentOption(
"ExcludeFixtureCleanupRegularExpression", args[i].c_str());
this->GetMemCheckHandler()->SetPersistentOption(
"ExcludeFixtureCleanupRegularExpression", args[i].c_str());
}
if (this->CheckArgument(arg, "--resource-spec-file") &&
i < args.size() - 1) {
i++;
this->GetTestHandler()->SetPersistentOption("ResourceSpecFile",
args[i].c_str());
this->GetMemCheckHandler()->SetPersistentOption("ResourceSpecFile",
args[i].c_str());
}
if (this->CheckArgument(arg, "--rerun-failed")) {
this->GetTestHandler()->SetPersistentOption("RerunFailed", "true");
this->GetMemCheckHandler()->SetPersistentOption("RerunFailed", "true");
}
return true;
}
#if !defined(_WIN32)
bool cmCTest::ConsoleIsNotDumb()
{
std::string term_env_variable;
if (cmSystemTools::GetEnv("TERM", term_env_variable)) {
return isatty(1) && term_env_variable != "dumb";
}
return false;
}
#endif
bool cmCTest::ProgressOutputSupportedByConsole()
{
#if defined(_WIN32)
// On Windows we need a console buffer.
void* console = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO csbi;
return GetConsoleScreenBufferInfo(console, &csbi);
#else
// On UNIX we need a non-dumb tty.
return ConsoleIsNotDumb();
#endif
}
bool cmCTest::ColoredOutputSupportedByConsole()
{
#if defined(_WIN32)
// Not supported on Windows
return false;
#else
// On UNIX we need a non-dumb tty.
std::string clicolor_force;
if (cmSystemTools::GetEnv("CLICOLOR_FORCE", clicolor_force) &&
!clicolor_force.empty() && clicolor_force != "0") {
return true;
}
return ConsoleIsNotDumb();
#endif
}
// handle the -S -SR and -SP arguments
void cmCTest::HandleScriptArguments(size_t& i, std::vector<std::string>& args,
bool& SRArgumentSpecified)
{
std::string arg = args[i];
if (this->CheckArgument(arg, "-SP", "--script-new-process") &&
i < args.size() - 1) {
this->Impl->RunConfigurationScript = true;
i++;
cmCTestScriptHandler* ch = this->GetScriptHandler();
// -SR is an internal argument, -SP should be ignored when it is passed
if (!SRArgumentSpecified) {
ch->AddConfigurationScript(args[i].c_str(), false);
}
}
if (this->CheckArgument(arg, "-SR", "--script-run") && i < args.size() - 1) {
SRArgumentSpecified = true;
this->Impl->RunConfigurationScript = true;
i++;
cmCTestScriptHandler* ch = this->GetScriptHandler();
ch->AddConfigurationScript(args[i].c_str(), true);
}
if (this->CheckArgument(arg, "-S", "--script") && i < args.size() - 1) {
this->Impl->RunConfigurationScript = true;
i++;
cmCTestScriptHandler* ch = this->GetScriptHandler();
// -SR is an internal argument, -S should be ignored when it is passed
if (!SRArgumentSpecified) {
ch->AddConfigurationScript(args[i].c_str(), true);
}
}
}
bool cmCTest::AddVariableDefinition(const std::string& arg)
{
std::string name;
std::string value;
cmStateEnums::CacheEntryType type = cmStateEnums::UNINITIALIZED;
if (cmake::ParseCacheEntry(arg, name, value, type)) {
this->Impl->Definitions[name] = value;
return true;
}
return false;
}
// the main entry point of ctest, called from main
int cmCTest::Run(std::vector<std::string>& args, std::string* output)
{
const char* ctestExec = "ctest";
bool cmakeAndTest = false;
bool executeTests = true;
bool SRArgumentSpecified = false;
// copy the command line
cm::append(this->Impl->InitialCommandLineArguments, args);
// process the command line arguments
for (size_t i = 1; i < args.size(); ++i) {
// handle the simple commandline arguments
std::string errormsg;
if (!this->HandleCommandLineArguments(i, args, errormsg)) {
cmSystemTools::Error(errormsg);
return 1;
}
// handle the script arguments -S -SR -SP
this->HandleScriptArguments(i, args, SRArgumentSpecified);
// --dashboard: handle a request for a dashboard
std::string arg = args[i];
if (this->CheckArgument(arg, "-D", "--dashboard") && i < args.size() - 1) {
this->Impl->ProduceXML = true;
i++;
std::string targ = args[i];
// AddTestsForDashboard parses the dashboard type and converts it
// into the separate stages
if (!this->AddTestsForDashboardType(targ)) {
if (!this->AddVariableDefinition(targ)) {
this->ErrorMessageUnknownDashDValue(targ);
executeTests = false;
}
}
}
// If it's not exactly -D, but it starts with -D, then try to parse out
// a variable definition from it, same as CMake does. Unsuccessful
// attempts are simply ignored since previous ctest versions ignore
// this too. (As well as many other unknown command line args.)
//
if (arg != "-D" && cmHasLiteralPrefix(arg, "-D")) {
std::string input = arg.substr(2);
this->AddVariableDefinition(input);
}
// --test-action: calls SetTest(<stage>, /*report=*/ false) to enable
// the corresponding stage
if (!this->HandleTestActionArgument(ctestExec, i, args)) {
executeTests = false;
}
// --test-model: what type of test model
if (!this->HandleTestModelArgument(ctestExec, i, args)) {
executeTests = false;
}
// --extra-submit
if (this->CheckArgument(arg, "--extra-submit") && i < args.size() - 1) {
this->Impl->ProduceXML = true;
this->SetTest("Submit");
i++;
if (!this->SubmitExtraFiles(args[i].c_str())) {
return 0;
}
}
// --build-and-test options
if (this->CheckArgument(arg, "--build-and-test") && i < args.size() - 1) {
cmakeAndTest = true;
}
// --schedule-random
if (this->CheckArgument(arg, "--schedule-random")) {
this->Impl->ScheduleType = "Random";
}
// pass the argument to all the handlers as well, but i may no longer be
// set to what it was originally so I'm not sure this is working as
// intended
for (auto& handler : this->Impl->GetTestingHandlers()) {
if (!handler->ProcessCommandLineArguments(arg, i, args)) {
cmCTestLog(this, ERROR_MESSAGE,
"Problem parsing command line arguments within a handler");
return 0;
}
}
} // the close of the for argument loop
// handle CTEST_PARALLEL_LEVEL environment variable
if (!this->Impl->ParallelLevelSetInCli) {
std::string parallel;
if (cmSystemTools::GetEnv("CTEST_PARALLEL_LEVEL", parallel)) {
int plevel = atoi(parallel.c_str());
this->SetParallelLevel(plevel);
}
}
// TestProgressOutput only supported if console supports it and not logging
// to a file
this->Impl->TestProgressOutput = this->Impl->TestProgressOutput &&
!this->Impl->OutputLogFile && this->ProgressOutputSupportedByConsole();
#ifdef _WIN32
if (this->Impl->TestProgressOutput) {
// Disable output line buffering so we can print content without
// a newline.
std::setvbuf(stdout, nullptr, _IONBF, 0);
}
#endif
// now what should cmake do? if --build-and-test was specified then
// we run the build and test handler and return
if (cmakeAndTest) {
return this->RunCMakeAndTest(output);
}
if (executeTests) {
return this->ExecuteTests();
}
return 1;
}
bool cmCTest::HandleTestActionArgument(const char* ctestExec, size_t& i,
const std::vector<std::string>& args)
{
bool success = true;
std::string arg = args[i];
if (this->CheckArgument(arg, "-T", "--test-action") &&
(i < args.size() - 1)) {
this->Impl->ProduceXML = true;
i++;
if (!this->SetTest(args[i].c_str(), false)) {
success = false;
cmCTestLog(this, ERROR_MESSAGE,
"CTest -T called with incorrect option: " << args[i]
<< std::endl);
cmCTestLog(this, ERROR_MESSAGE,
"Available options are:"
<< std::endl
<< " " << ctestExec << " -T all" << std::endl
<< " " << ctestExec << " -T start" << std::endl
<< " " << ctestExec << " -T update" << std::endl
<< " " << ctestExec << " -T configure" << std::endl
<< " " << ctestExec << " -T build" << std::endl
<< " " << ctestExec << " -T test" << std::endl
<< " " << ctestExec << " -T coverage" << std::endl
<< " " << ctestExec << " -T memcheck" << std::endl
<< " " << ctestExec << " -T notes" << std::endl
<< " " << ctestExec << " -T submit" << std::endl);
}
}
return success;
}
bool cmCTest::HandleTestModelArgument(const char* ctestExec, size_t& i,
const std::vector<std::string>& args)
{
bool success = true;
std::string arg = args[i];
if (this->CheckArgument(arg, "-M", "--test-model") &&
(i < args.size() - 1)) {
i++;
std::string const& str = args[i];
if (cmSystemTools::LowerCase(str) == "nightly") {
this->SetTestModel(cmCTest::NIGHTLY);
} else if (cmSystemTools::LowerCase(str) == "continuous") {
this->SetTestModel(cmCTest::CONTINUOUS);
} else if (cmSystemTools::LowerCase(str) == "experimental") {
this->SetTestModel(cmCTest::EXPERIMENTAL);
} else {
success = false;
cmCTestLog(this, ERROR_MESSAGE,
"CTest -M called with incorrect option: " << str
<< std::endl);
cmCTestLog(this, ERROR_MESSAGE,
"Available options are:"
<< std::endl
<< " " << ctestExec << " -M Continuous" << std::endl
<< " " << ctestExec << " -M Experimental" << std::endl
<< " " << ctestExec << " -M Nightly" << std::endl);
}
}
return success;
}
int cmCTest::ExecuteTests()
{
int res;
// call process directory
if (this->Impl->RunConfigurationScript) {
if (this->Impl->ExtraVerbose) {
cmCTestLog(this, OUTPUT, "* Extra verbosity turned on" << std::endl);
}
for (auto& handler : this->Impl->GetTestingHandlers()) {
handler->SetVerbose(this->Impl->ExtraVerbose);
handler->SetSubmitIndex(this->Impl->SubmitIndex);
}
this->GetScriptHandler()->SetVerbose(this->Impl->Verbose);
res = this->GetScriptHandler()->ProcessHandler();
if (res != 0) {
cmCTestLog(this, DEBUG,
"running script failing returning: " << res << std::endl);
}
} else {
// What is this? -V seems to be the same as -VV,
// and Verbose is always on in this case
this->Impl->ExtraVerbose = this->Impl->Verbose;
this->Impl->Verbose = true;
for (auto& handler : this->Impl->GetTestingHandlers()) {
handler->SetVerbose(this->Impl->Verbose);
handler->SetSubmitIndex(this->Impl->SubmitIndex);
}
std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
if (!this->Initialize(cwd.c_str(), nullptr)) {
res = 12;
cmCTestLog(this, ERROR_MESSAGE,
"Problem initializing the dashboard." << std::endl);
} else {
res = this->ProcessSteps();
}
this->Finalize();
}
if (res != 0) {
cmCTestLog(this, DEBUG,
"Running a test(s) failed returning : " << res << std::endl);
}
return res;
}
int cmCTest::RunCMakeAndTest(std::string* output)
{
this->Impl->Verbose = true;
cmCTestBuildAndTestHandler* handler = this->GetBuildAndTestHandler();
int retv = handler->ProcessHandler();
*output = handler->GetOutput();
#ifndef CMAKE_BOOTSTRAP
cmDynamicLoader::FlushCache();
#endif
if (retv != 0) {
cmCTestLog(this, DEBUG,
"build and test failing returning: " << retv << std::endl);
}
return retv;
}
void cmCTest::SetNotesFiles(const char* notes)
{
if (!notes) {
return;
}
this->Impl->NotesFiles = notes;
}
std::chrono::system_clock::time_point cmCTest::GetStopTime() const
{
return this->Impl->StopTime;
}
void cmCTest::SetStopTime(std::string const& time_str)
{
struct tm* lctime;
time_t current_time = time(nullptr);
lctime = gmtime(&current_time);
int gm_hour = lctime->tm_hour;
time_t gm_time = mktime(lctime);
lctime = localtime(&current_time);
int local_hour = lctime->tm_hour;
int tzone_offset = local_hour - gm_hour;
if (gm_time > current_time && gm_hour < local_hour) {
// this means gm_time is on the next day
tzone_offset -= 24;
} else if (gm_time < current_time && gm_hour > local_hour) {
// this means gm_time is on the previous day
tzone_offset += 24;
}
tzone_offset *= 100;
char buf[1024];
sprintf(buf, "%d%02d%02d %s %+05i", lctime->tm_year + 1900,
lctime->tm_mon + 1, lctime->tm_mday, time_str.c_str(), tzone_offset);
time_t stop_time = curl_getdate(buf, &current_time);
if (stop_time == -1) {
this->Impl->StopTime = std::chrono::system_clock::time_point();
return;
}
this->Impl->StopTime = std::chrono::system_clock::from_time_t(stop_time);
if (stop_time < current_time) {
this->Impl->StopTime += std::chrono::hours(24);
}
}
std::string cmCTest::GetScheduleType() const
{
return this->Impl->ScheduleType;
}
void cmCTest::SetScheduleType(std::string const& type)
{
this->Impl->ScheduleType = type;
}
int cmCTest::ReadCustomConfigurationFileTree(const char* dir, cmMakefile* mf)
{
bool found = false;
cmCTestLog(this, DEBUG,
"* Read custom CTest configuration directory: " << dir
<< std::endl);
std::string fname = cmStrCat(dir, "/CTestCustom.cmake");
cmCTestLog(this, DEBUG, "* Check for file: " << fname << std::endl);
if (cmSystemTools::FileExists(fname)) {
cmCTestLog(this, DEBUG,
"* Read custom CTest configuration file: " << fname
<< std::endl);
bool erroroc = cmSystemTools::GetErrorOccuredFlag();
cmSystemTools::ResetErrorOccuredFlag();
if (!mf->ReadListFile(fname) || cmSystemTools::GetErrorOccuredFlag()) {
cmCTestLog(this, ERROR_MESSAGE,
"Problem reading custom configuration: " << fname
<< std::endl);
}
found = true;
if (erroroc) {
cmSystemTools::SetErrorOccured();
}
}
std::string rexpr = cmStrCat(dir, "/CTestCustom.ctest");
cmCTestLog(this, DEBUG, "* Check for file: " << rexpr << std::endl);
if (!found && cmSystemTools::FileExists(rexpr)) {
cmsys::Glob gl;
gl.RecurseOn();
gl.FindFiles(rexpr);
std::vector<std::string>& files = gl.GetFiles();
for (const std::string& file : files) {
cmCTestLog(this, DEBUG,
"* Read custom CTest configuration file: " << file
<< std::endl);
if (!mf->ReadListFile(file) || cmSystemTools::GetErrorOccuredFlag()) {
cmCTestLog(this, ERROR_MESSAGE,
"Problem reading custom configuration: " << file
<< std::endl);
}
}
found = true;
}
if (found) {
for (auto& handler : this->Impl->GetNamedTestingHandlers()) {
cmCTestLog(this, DEBUG,
"* Read custom CTest configuration vectors for handler: "
<< handler.first << " (" << handler.second << ")"
<< std::endl);
handler.second->PopulateCustomVectors(mf);
}
}
return 1;
}
void cmCTest::PopulateCustomVector(cmMakefile* mf, const std::string& def,
std::vector<std::string>& vec)
{
const char* dval = mf->GetDefinition(def);
if (!dval) {
return;
}
cmCTestLog(this, DEBUG, "PopulateCustomVector: " << def << std::endl);
vec.clear();
cmExpandList(dval, vec);
for (std::string const& it : vec) {
cmCTestLog(this, DEBUG, " -- " << it << std::endl);
}
}
void cmCTest::PopulateCustomInteger(cmMakefile* mf, const std::string& def,
int& val)
{
const char* dval = mf->GetDefinition(def);
if (!dval) {
return;
}
val = atoi(dval);
}
std::string cmCTest::GetShortPathToFile(const char* cfname)
{
const std::string& sourceDir = cmSystemTools::CollapseFullPath(
this->GetCTestConfiguration("SourceDirectory"));
const std::string& buildDir = cmSystemTools::CollapseFullPath(
this->GetCTestConfiguration("BuildDirectory"));
std::string fname = cmSystemTools::CollapseFullPath(cfname);
// Find relative paths to both directories
std::string srcRelpath = cmSystemTools::RelativePath(sourceDir, fname);
std::string bldRelpath = cmSystemTools::RelativePath(buildDir, fname);
// If any contains "." it is not parent directory
bool inSrc = srcRelpath.find("..") == std::string::npos;
bool inBld = bldRelpath.find("..") == std::string::npos;
// TODO: Handle files with .. in their name
std::string* res = nullptr;
if (inSrc && inBld) {
// If both have relative path with no dots, pick the shorter one
if (srcRelpath.size() < bldRelpath.size()) {
res = &srcRelpath;
} else {
res = &bldRelpath;
}
} else if (inSrc) {
res = &srcRelpath;
} else if (inBld) {
res = &bldRelpath;
}
std::string path;
if (!res) {
path = fname;
} else {
cmSystemTools::ConvertToUnixSlashes(*res);
path = "./" + *res;
if (path.back() == '/') {
path = path.substr(0, path.size() - 1);
}
}
cmsys::SystemTools::ReplaceString(path, ":", "_");
cmsys::SystemTools::ReplaceString(path, " ", "_");
return path;
}
std::string cmCTest::GetCTestConfiguration(const std::string& name)
{
if (this->Impl->CTestConfigurationOverwrites.find(name) !=
this->Impl->CTestConfigurationOverwrites.end()) {
return this->Impl->CTestConfigurationOverwrites[name];
}
return this->Impl->CTestConfiguration[name];
}
void cmCTest::EmptyCTestConfiguration()
{
this->Impl->CTestConfiguration.clear();
}
void cmCTest::SetCTestConfiguration(const char* name, const char* value,
bool suppress)
{
cmCTestOptionalLog(this, HANDLER_VERBOSE_OUTPUT,
"SetCTestConfiguration:"
<< name << ":" << (value ? value : "(null)") << "\n",
suppress);
if (!name) {
return;
}
if (!value) {
this->Impl->CTestConfiguration.erase(name);
return;
}
this->Impl->CTestConfiguration[name] = value;
}
std::string cmCTest::GetSubmitURL()
{
std::string url = this->GetCTestConfiguration("SubmitURL");
if (url.empty()) {
std::string method = this->GetCTestConfiguration("DropMethod");
std::string user = this->GetCTestConfiguration("DropSiteUser");
std::string password = this->GetCTestConfiguration("DropSitePassword");
std::string site = this->GetCTestConfiguration("DropSite");
std::string location = this->GetCTestConfiguration("DropLocation");
url = cmStrCat(method.empty() ? "http" : method, "://");
if (!user.empty()) {
url += user;
if (!password.empty()) {
url += ':';
url += password;
}
url += '@';
}
url += site;
url += location;
}
return url;
}
std::string cmCTest::GetCurrentTag()
{
return this->Impl->CurrentTag;
}
std::string cmCTest::GetBinaryDir()
{
return this->Impl->BinaryDir;
}
std::string const& cmCTest::GetConfigType()
{
return this->Impl->ConfigType;
}
cmDuration cmCTest::GetTimeOut() const
{
return this->Impl->TimeOut;
}
void cmCTest::SetTimeOut(cmDuration t)
{
this->Impl->TimeOut = t;
}
cmDuration cmCTest::GetGlobalTimeout() const
{
return this->Impl->GlobalTimeout;
}
bool cmCTest::GetShowOnly()
{
return this->Impl->ShowOnly;
}
bool cmCTest::GetOutputAsJson()
{
return this->Impl->OutputAsJson;
}
int cmCTest::GetOutputAsJsonVersion()
{
return this->Impl->OutputAsJsonVersion;
}
bool cmCTest::ShouldUseHTTP10() const
{
return this->Impl->UseHTTP10;
}
bool cmCTest::ShouldPrintLabels() const
{
return this->Impl->PrintLabels;
}
int cmCTest::GetMaxTestNameWidth() const
{
return this->Impl->MaxTestNameWidth;
}
void cmCTest::SetMaxTestNameWidth(int w)
{
this->Impl->MaxTestNameWidth = w;
}
void cmCTest::SetProduceXML(bool v)
{
this->Impl->ProduceXML = v;
}
bool cmCTest::GetProduceXML()
{
return this->Impl->ProduceXML;
}
std::vector<std::string>& cmCTest::GetInitialCommandLineArguments()
{
return this->Impl->InitialCommandLineArguments;
}
const char* cmCTest::GetSpecificGroup()
{
if (this->Impl->SpecificGroup.empty()) {
return nullptr;
}
return this->Impl->SpecificGroup.c_str();
}
void cmCTest::SetSpecificGroup(const char* group)
{
if (!group) {
this->Impl->SpecificGroup.clear();
return;
}
this->Impl->SpecificGroup = group;
}
void cmCTest::SetFailover(bool failover)
{
this->Impl->Failover = failover;
}
bool cmCTest::GetFailover() const
{
return this->Impl->Failover;
}
bool cmCTest::GetTestProgressOutput() const
{
return this->Impl->TestProgressOutput;
}
bool cmCTest::GetVerbose() const
{
return this->Impl->Verbose;
}
bool cmCTest::GetExtraVerbose() const
{
return this->Impl->ExtraVerbose;
}
void cmCTest::SetStreams(std::ostream* out, std::ostream* err)
{
this->Impl->StreamOut = out;
this->Impl->StreamErr = err;
}
bool cmCTest::GetLabelSummary() const
{
return this->Impl->LabelSummary;
}
bool cmCTest::GetSubprojectSummary() const
{
return this->Impl->SubprojectSummary;
}
bool cmCTest::GetOutputTestOutputOnTestFailure() const
{
return this->Impl->OutputTestOutputOnTestFailure;
}
const std::map<std::string, std::string>& cmCTest::GetDefinitions() const
{
return this->Impl->Definitions;
}
int cmCTest::GetRepeatCount() const
{
return this->Impl->RepeatCount;
}
cmCTest::Repeat cmCTest::GetRepeatMode() const
{
return this->Impl->RepeatMode;
}
cmCTest::NoTests cmCTest::GetNoTestsMode() const
{
return this->Impl->NoTestsMode;
}
void cmCTest::SetBuildID(const std::string& id)
{
this->Impl->BuildID = id;
}
std::string cmCTest::GetBuildID() const
{
return this->Impl->BuildID;
}
void cmCTest::AddSubmitFile(Part part, const char* name)
{
this->Impl->Parts[part].SubmitFiles.emplace_back(name);
}
std::vector<std::string> const& cmCTest::GetSubmitFiles(Part part) const
{
return this->Impl->Parts[part].SubmitFiles;
}
void cmCTest::ClearSubmitFiles(Part part)
{
this->Impl->Parts[part].SubmitFiles.clear();
}
void cmCTest::SetSuppressUpdatingCTestConfiguration(bool val)
{
this->Impl->SuppressUpdatingCTestConfiguration = val;
}
void cmCTest::AddCTestConfigurationOverwrite(const std::string& overStr)
{
size_t epos = overStr.find('=');
if (epos == std::string::npos) {
cmCTestLog(this, ERROR_MESSAGE,
"CTest configuration overwrite specified in the wrong format."
<< std::endl
<< "Valid format is: --overwrite key=value" << std::endl
<< "The specified was: --overwrite " << overStr << std::endl);
return;
}
std::string key = overStr.substr(0, epos);
std::string value = overStr.substr(epos + 1);
this->Impl->CTestConfigurationOverwrites[key] = value;
}
void cmCTest::SetConfigType(const char* ct)
{
this->Impl->ConfigType = ct ? ct : "";
cmSystemTools::ReplaceString(this->Impl->ConfigType, ".\\", "");
std::string confTypeEnv = "CMAKE_CONFIG_TYPE=" + this->Impl->ConfigType;
cmSystemTools::PutEnv(confTypeEnv);
}
bool cmCTest::SetCTestConfigurationFromCMakeVariable(
cmMakefile* mf, const char* dconfig, const std::string& cmake_var,
bool suppress)
{
const char* ctvar;
ctvar = mf->GetDefinition(cmake_var);
if (!ctvar) {
return false;
}
cmCTestOptionalLog(this, HANDLER_VERBOSE_OUTPUT,
"SetCTestConfigurationFromCMakeVariable:"
<< dconfig << ":" << cmake_var << std::endl,
suppress);
this->SetCTestConfiguration(dconfig, ctvar, suppress);
return true;
}
bool cmCTest::RunCommand(std::vector<std::string> const& args,
std::string* stdOut, std::string* stdErr, int* retVal,
const char* dir, cmDuration timeout,
Encoding encoding)
{
std::vector<const char*> argv;
argv.reserve(args.size() + 1);
for (std::string const& a : args) {
argv.push_back(a.c_str());
}
argv.push_back(nullptr);
stdOut->clear();
stdErr->clear();
cmsysProcess* cp = cmsysProcess_New();
cmsysProcess_SetCommand(cp, argv.data());
cmsysProcess_SetWorkingDirectory(cp, dir);
if (cmSystemTools::GetRunCommandHideConsole()) {
cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
}
cmsysProcess_SetTimeout(cp, timeout.count());
cmsysProcess_Execute(cp);
std::vector<char> tempOutput;
std::vector<char> tempError;
char* data;
int length;
cmProcessOutput processOutput(encoding);
std::string strdata;
int res;
bool done = false;
while (!done) {
res = cmsysProcess_WaitForData(cp, &data, &length, nullptr);
switch (res) {
case cmsysProcess_Pipe_STDOUT:
cm::append(tempOutput, data, data + length);
break;
case cmsysProcess_Pipe_STDERR:
cm::append(tempError, data, data + length);
break;
default:
done = true;
}
if ((res == cmsysProcess_Pipe_STDOUT || res == cmsysProcess_Pipe_STDERR) &&
this->Impl->ExtraVerbose) {
processOutput.DecodeText(data, length, strdata);
cmSystemTools::Stdout(strdata);
}
}
if (this->Impl->ExtraVerbose) {
processOutput.DecodeText(std::string(), strdata);
if (!strdata.empty()) {
cmSystemTools::Stdout(strdata);
}
}
cmsysProcess_WaitForExit(cp, nullptr);
if (!tempOutput.empty()) {
processOutput.DecodeText(tempOutput, tempOutput);
stdOut->append(tempOutput.data(), tempOutput.size());
}
if (!tempError.empty()) {
processOutput.DecodeText(tempError, tempError);
stdErr->append(tempError.data(), tempError.size());
}
bool result = true;
if (cmsysProcess_GetState(cp) == cmsysProcess_State_Exited) {
if (retVal) {
*retVal = cmsysProcess_GetExitValue(cp);
} else {
if (cmsysProcess_GetExitValue(cp) != 0) {
result = false;
}
}
} else if (cmsysProcess_GetState(cp) == cmsysProcess_State_Exception) {
const char* exception_str = cmsysProcess_GetExceptionString(cp);
cmCTestLog(this, ERROR_MESSAGE, exception_str << std::endl);
stdErr->append(exception_str, strlen(exception_str));
result = false;
} else if (cmsysProcess_GetState(cp) == cmsysProcess_State_Error) {
const char* error_str = cmsysProcess_GetErrorString(cp);
cmCTestLog(this, ERROR_MESSAGE, error_str << std::endl);
stdErr->append(error_str, strlen(error_str));
result = false;
} else if (cmsysProcess_GetState(cp) == cmsysProcess_State_Expired) {
const char* error_str = "Process terminated due to timeout\n";
cmCTestLog(this, ERROR_MESSAGE, error_str << std::endl);
stdErr->append(error_str, strlen(error_str));
result = false;
}
cmsysProcess_Delete(cp);
return result;
}
void cmCTest::SetOutputLogFileName(const char* name)
{
if (this->Impl->OutputLogFile) {
delete this->Impl->OutputLogFile;
this->Impl->OutputLogFile = nullptr;
}
if (name) {
this->Impl->OutputLogFile = new cmGeneratedFileStream(name);
}
}
static const char* cmCTestStringLogType[] = { "DEBUG",
"OUTPUT",
"HANDLER_OUTPUT",
"HANDLER_PROGRESS_OUTPUT",
"HANDLER_TEST_PROGRESS_OUTPUT",
"HANDLER_VERBOSE_OUTPUT",
"WARNING",
"ERROR_MESSAGE",
nullptr };
#define cmCTestLogOutputFileLine(stream) \
do { \
if (this->Impl->ShowLineNumbers) { \
(stream) << std::endl << file << ":" << line << " "; \
} \
} while (false)
void cmCTest::Log(int logType, const char* file, int line, const char* msg,
bool suppress)
{
if (!msg || !*msg) {
return;
}
if (suppress && logType != cmCTest::ERROR_MESSAGE) {
return;
}
if (logType == cmCTest::HANDLER_PROGRESS_OUTPUT &&
(this->Impl->Debug || this->Impl->ExtraVerbose)) {
return;
}
if (this->Impl->OutputLogFile) {
bool display = true;
if (logType == cmCTest::DEBUG && !this->Impl->Debug) {
display = false;
}
if (logType == cmCTest::HANDLER_VERBOSE_OUTPUT && !this->Impl->Debug &&
!this->Impl->ExtraVerbose) {
display = false;
}
if (display) {
cmCTestLogOutputFileLine(*this->Impl->OutputLogFile);
if (logType != this->Impl->OutputLogFileLastTag) {
*this->Impl->OutputLogFile << "[";
if (logType >= OTHER || logType < 0) {
*this->Impl->OutputLogFile << "OTHER";
} else {
*this->Impl->OutputLogFile << cmCTestStringLogType[logType];
}
*this->Impl->OutputLogFile << "] " << std::endl;
}
*this->Impl->OutputLogFile << msg << std::flush;
if (logType != this->Impl->OutputLogFileLastTag) {
*this->Impl->OutputLogFile << std::endl;
this->Impl->OutputLogFileLastTag = logType;
}
}
}
if (!this->Impl->Quiet) {
std::ostream& out = *this->Impl->StreamOut;
std::ostream& err = *this->Impl->StreamErr;
if (logType == HANDLER_TEST_PROGRESS_OUTPUT) {
if (this->Impl->TestProgressOutput) {
cmCTestLogOutputFileLine(out);
if (this->Impl->FlushTestProgressLine) {
printf("\r");
this->Impl->FlushTestProgressLine = false;
out.flush();
}
std::string msg_str{ msg };
auto const lineBreakIt = msg_str.find('\n');
if (lineBreakIt != std::string::npos) {
this->Impl->FlushTestProgressLine = true;
msg_str.erase(std::remove(msg_str.begin(), msg_str.end(), '\n'),
msg_str.end());
}
out << msg_str;
#ifndef _WIN32
printf("\x1B[K"); // move caret to end
#endif
out.flush();
return;
}
logType = HANDLER_OUTPUT;
}
switch (logType) {
case DEBUG:
if (this->Impl->Debug) {
cmCTestLogOutputFileLine(out);
out << msg;
out.flush();
}
break;
case OUTPUT:
case HANDLER_OUTPUT:
if (this->Impl->Debug || this->Impl->Verbose) {
cmCTestLogOutputFileLine(out);
out << msg;
out.flush();
}
break;
case HANDLER_VERBOSE_OUTPUT:
if (this->Impl->Debug || this->Impl->ExtraVerbose) {
cmCTestLogOutputFileLine(out);
out << msg;
out.flush();
}
break;
case WARNING:
cmCTestLogOutputFileLine(err);
err << msg;
err.flush();
break;
case ERROR_MESSAGE:
cmCTestLogOutputFileLine(err);
err << msg;
err.flush();
cmSystemTools::SetErrorOccured();
break;
default:
cmCTestLogOutputFileLine(out);
out << msg;
out.flush();
}
}
}
std::string cmCTest::GetColorCode(Color color) const
{
if (this->Impl->OutputColorCode) {
#if defined(_WIN32)
// Not supported on Windows
static_cast<void>(color);
#else
return "\033[0;" + std::to_string(static_cast<int>(color)) + "m";
#endif
}
return "";
}
cmDuration cmCTest::GetRemainingTimeAllowed()
{
return this->GetScriptHandler()->GetRemainingTimeAllowed();
}
cmDuration cmCTest::MaxDuration()
{
return cmDuration(1.0e7);
}
void cmCTest::SetRunCurrentScript(bool value)
{
this->GetScriptHandler()->SetRunCurrentScript(value);
}
void cmCTest::OutputTestErrors(std::vector<char> const& process_output)
{
std::string test_outputs("\n*** Test Failed:\n");
if (!process_output.empty()) {
test_outputs.append(process_output.data(), process_output.size());
}
cmCTestLog(this, HANDLER_OUTPUT, test_outputs << std::endl);
}
bool cmCTest::CompressString(std::string& str)
{
int ret;
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
ret = deflateInit(&strm, -1); // default compression level
if (ret != Z_OK) {
return false;
}
unsigned char* in =
reinterpret_cast<unsigned char*>(const_cast<char*>(str.c_str()));
// zlib makes the guarantee that this is the maximum output size
int outSize =
static_cast<int>(static_cast<double>(str.size()) * 1.001 + 13.0);
std::vector<unsigned char> out(outSize);
strm.avail_in = static_cast<uInt>(str.size());
strm.next_in = in;
strm.avail_out = outSize;
strm.next_out = &out[0];
ret = deflate(&strm, Z_FINISH);
if (ret != Z_STREAM_END) {
cmCTestLog(this, ERROR_MESSAGE,
"Error during gzip compression." << std::endl);
return false;
}
(void)deflateEnd(&strm);
// Now base64 encode the resulting binary string
std::vector<unsigned char> base64EncodedBuffer((outSize * 3) / 2);
size_t rlen =
cmsysBase64_Encode(&out[0], strm.total_out, &base64EncodedBuffer[0], 1);
str.assign(reinterpret_cast<char*>(&base64EncodedBuffer[0]), rlen);
return true;
}