1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
20 #include <cm/optional>
21 #include <cm/string_view>
22 #include <cmext/algorithm>
23 #include <cmext/string_view>
25 #include <cm3p/curl/curl.h>
26 #include <cm3p/zlib.h>
28 #include "cmsys/Base64.h"
29 #include "cmsys/Directory.hxx"
30 #include "cmsys/FStream.hxx"
31 #include "cmsys/Glob.hxx"
32 #include "cmsys/Process.h"
33 #include "cmsys/RegularExpression.hxx"
34 #include "cmsys/SystemInformation.hxx"
36 # include <windows.h> // IWYU pragma: keep
38 # include <unistd.h> // IWYU pragma: keep
41 #include "cmCMakePresetsGraph.h"
42 #include "cmCTestBuildAndTestHandler.h"
43 #include "cmCTestBuildHandler.h"
44 #include "cmCTestConfigureHandler.h"
45 #include "cmCTestCoverageHandler.h"
46 #include "cmCTestGenericHandler.h"
47 #include "cmCTestMemCheckHandler.h"
48 #include "cmCTestScriptHandler.h"
49 #include "cmCTestStartCommand.h"
50 #include "cmCTestSubmitHandler.h"
51 #include "cmCTestTestHandler.h"
52 #include "cmCTestUpdateHandler.h"
53 #include "cmCTestUploadHandler.h"
54 #include "cmDynamicLoader.h"
55 #include "cmGeneratedFileStream.h"
56 #include "cmGlobalGenerator.h"
57 #include "cmMakefile.h"
58 #include "cmProcessOutput.h"
60 #include "cmStateSnapshot.h"
61 #include "cmStateTypes.h"
62 #include "cmStringAlgorithms.h"
63 #include "cmSystemTools.h"
65 #include "cmVersion.h"
66 #include "cmVersionConfig.h"
67 #include "cmXMLWriter.h"
70 #if defined(__BEOS__) || defined(__HAIKU__)
71 # include <be/kernel/OS.h> /* disable_debugger() API. */
74 struct cmCTest::Private
76 /** Representation of one part. */
79 void SetName(const std::string& name) { this->Name = name; }
80 const std::string& GetName() const { return this->Name; }
82 void Enable() { this->Enabled = true; }
83 explicit operator bool() const { return this->Enabled; }
85 std::vector<std::string> SubmitFiles;
92 int RepeatCount = 1; // default to run each test once
93 cmCTest::Repeat RepeatMode = cmCTest::Repeat::Never;
94 std::string ConfigType;
95 std::string ScheduleType;
96 std::chrono::system_clock::time_point StopTime;
97 bool StopOnFailure = false;
98 bool TestProgressOutput = false;
100 bool ExtraVerbose = false;
101 bool ProduceXML = false;
102 bool LabelSummary = true;
103 bool SubprojectSummary = true;
104 bool UseHTTP10 = false;
105 bool PrintLabels = false;
106 bool Failover = false;
108 bool FlushTestProgressLine = false;
110 bool ForceNewCTestProcess = false;
112 bool RunConfigurationScript = false;
114 // these are helper classes
115 cmCTestBuildHandler BuildHandler;
116 cmCTestBuildAndTestHandler BuildAndTestHandler;
117 cmCTestCoverageHandler CoverageHandler;
118 cmCTestScriptHandler ScriptHandler;
119 cmCTestTestHandler TestHandler;
120 cmCTestUpdateHandler UpdateHandler;
121 cmCTestConfigureHandler ConfigureHandler;
122 cmCTestMemCheckHandler MemCheckHandler;
123 cmCTestSubmitHandler SubmitHandler;
124 cmCTestUploadHandler UploadHandler;
126 std::vector<cmCTestGenericHandler*> GetTestingHandlers()
128 return { &this->BuildHandler, &this->BuildAndTestHandler,
129 &this->CoverageHandler, &this->ScriptHandler,
130 &this->TestHandler, &this->UpdateHandler,
131 &this->ConfigureHandler, &this->MemCheckHandler,
132 &this->SubmitHandler, &this->UploadHandler };
135 std::map<std::string, cmCTestGenericHandler*> GetNamedTestingHandlers()
137 return { { "build", &this->BuildHandler },
138 { "buildtest", &this->BuildAndTestHandler },
139 { "coverage", &this->CoverageHandler },
140 { "script", &this->ScriptHandler },
141 { "test", &this->TestHandler },
142 { "update", &this->UpdateHandler },
143 { "configure", &this->ConfigureHandler },
144 { "memcheck", &this->MemCheckHandler },
145 { "submit", &this->SubmitHandler },
146 { "upload", &this->UploadHandler } };
149 bool ShowOnly = false;
150 bool OutputAsJson = false;
151 int OutputAsJsonVersion = 1;
153 // TODO: The ctest configuration should be a hierarchy of
154 // configuration option sources: command-line, script, ini file.
155 // Then the ini file can get re-loaded whenever it changes without
156 // affecting any higher-precedence settings.
157 std::map<std::string, std::string> CTestConfiguration;
158 std::map<std::string, std::string> CTestConfigurationOverwrites;
160 PartInfo Parts[PartCount];
161 std::map<std::string, Part> PartMap;
163 std::string CurrentTag;
164 bool TomorrowTag = false;
166 int TestModel = cmCTest::EXPERIMENTAL;
167 std::string SpecificGroup;
169 cmDuration TimeOut = cmDuration::zero();
171 cmDuration GlobalTimeout = cmDuration::zero();
173 int MaxTestNameWidth = 30;
175 int ParallelLevel = 1;
176 bool ParallelLevelSetInCli = false;
178 unsigned long TestLoad = 0;
180 int CompatibilityMode;
182 // information for the --build-and-test options
183 std::string BinaryDir;
186 std::string NotesFiles;
188 bool InteractiveDebugMode = true;
190 bool ShortDateFormat = true;
192 bool CompressXMLFiles = false;
193 bool CompressTestOutput = true;
195 // By default we write output to the process output streams.
196 std::ostream* StreamOut = &std::cout;
197 std::ostream* StreamErr = &std::cerr;
199 bool SuppressUpdatingCTestConfiguration = false;
202 bool ShowLineNumbers = false;
207 std::vector<std::string> InitialCommandLineArguments;
211 std::unique_ptr<cmGeneratedFileStream> OutputLogFile;
212 int OutputLogFileLastTag = -1;
214 bool OutputTestOutputOnTestFailure = false;
215 bool OutputColorCode = cmCTest::ColoredOutputSupportedByConsole();
217 std::map<std::string, std::string> Definitions;
219 cmCTest::NoTests NoTestsMode = cmCTest::NoTests::Legacy;
222 struct tm* cmCTest::GetNightlyTime(std::string const& str, bool tomorrowtag)
225 time_t tctime = time(nullptr);
226 lctime = gmtime(&tctime);
228 // add todays year day and month to the time in str because
229 // curl_getdate no longer assumes the day is today
230 snprintf(buf, sizeof(buf), "%d%02d%02d %s", lctime->tm_year + 1900,
231 lctime->tm_mon + 1, lctime->tm_mday, str.c_str());
232 cmCTestLog(this, OUTPUT,
233 "Determine Nightly Start Time" << std::endl
234 << " Specified time: " << str
236 // Convert the nightly start time to seconds. Since we are
237 // providing only a time and a timezone, the current date of
238 // the local machine is assumed. Consequently, nightlySeconds
239 // is the time at which the nightly dashboard was opened or
240 // will be opened on the date of the current client machine.
241 // As such, this time may be in the past or in the future.
242 time_t ntime = curl_getdate(buf, &tctime);
243 cmCTestLog(this, DEBUG, " Get curl time: " << ntime << std::endl);
244 tctime = time(nullptr);
245 cmCTestLog(this, DEBUG, " Get the current time: " << tctime << std::endl);
247 const int dayLength = 24 * 60 * 60;
248 cmCTestLog(this, DEBUG, "Seconds: " << tctime << std::endl);
249 while (ntime > tctime) {
250 // If nightlySeconds is in the past, this is the current
251 // open dashboard, then return nightlySeconds. If
252 // nightlySeconds is in the future, this is the next
253 // dashboard to be opened, so subtract 24 hours to get the
254 // time of the current open dashboard
256 cmCTestLog(this, DEBUG, "Pick yesterday" << std::endl);
257 cmCTestLog(this, DEBUG,
258 " Future time, subtract day: " << ntime << std::endl);
260 while (tctime > (ntime + dayLength)) {
262 cmCTestLog(this, DEBUG, " Past time, add day: " << ntime << std::endl);
264 cmCTestLog(this, DEBUG, "nightlySeconds: " << ntime << std::endl);
265 cmCTestLog(this, DEBUG,
266 " Current time: " << tctime << " Nightly time: " << ntime
269 cmCTestLog(this, OUTPUT, " Use future tag, Add a day" << std::endl);
272 lctime = gmtime(&ntime);
276 bool cmCTest::GetTomorrowTag() const
278 return this->Impl->TomorrowTag;
281 std::string cmCTest::CleanString(const std::string& str,
282 std::string::size_type spos)
284 spos = str.find_first_not_of(" \n\t\r\f\v", spos);
285 std::string::size_type epos = str.find_last_not_of(" \n\t\r\f\v");
286 if (spos == std::string::npos) {
287 return std::string();
289 if (epos != std::string::npos) {
290 epos = epos - spos + 1;
292 return str.substr(spos, epos);
295 std::string cmCTest::CurrentTime()
297 time_t currenttime = time(nullptr);
298 struct tm* t = localtime(¤ttime);
299 // return ::CleanString(ctime(¤ttime));
300 char current_time[1024];
301 if (this->Impl->ShortDateFormat) {
302 strftime(current_time, 1000, "%b %d %H:%M %Z", t);
304 strftime(current_time, 1000, "%a %b %d %H:%M:%S %Z %Y", t);
306 cmCTestLog(this, DEBUG, " Current_Time: " << current_time << std::endl);
307 return cmCTest::CleanString(current_time);
310 std::string cmCTest::GetCostDataFile()
312 std::string fname = this->GetCTestConfiguration("CostDataFile");
314 fname = this->GetBinaryDir() + "/Testing/Temporary/CTestCostData.txt";
319 std::string cmCTest::DecodeURL(const std::string& in)
322 for (const char* c = in.c_str(); *c; ++c) {
323 if (*c == '%' && isxdigit(*(c + 1)) && isxdigit(*(c + 2))) {
324 char buf[3] = { *(c + 1), *(c + 2), 0 };
325 out.append(1, static_cast<char>(strtoul(buf, nullptr, 16)));
337 std::string envValue;
338 if (cmSystemTools::GetEnv("CTEST_OUTPUT_ON_FAILURE", envValue)) {
339 this->Impl->OutputTestOutputOnTestFailure = !cmIsOff(envValue);
342 if (cmSystemTools::GetEnv("CTEST_PROGRESS_OUTPUT", envValue)) {
343 this->Impl->TestProgressOutput = !cmIsOff(envValue);
346 this->Impl->Parts[PartStart].SetName("Start");
347 this->Impl->Parts[PartUpdate].SetName("Update");
348 this->Impl->Parts[PartConfigure].SetName("Configure");
349 this->Impl->Parts[PartBuild].SetName("Build");
350 this->Impl->Parts[PartTest].SetName("Test");
351 this->Impl->Parts[PartCoverage].SetName("Coverage");
352 this->Impl->Parts[PartMemCheck].SetName("MemCheck");
353 this->Impl->Parts[PartSubmit].SetName("Submit");
354 this->Impl->Parts[PartNotes].SetName("Notes");
355 this->Impl->Parts[PartExtraFiles].SetName("ExtraFiles");
356 this->Impl->Parts[PartUpload].SetName("Upload");
357 this->Impl->Parts[PartDone].SetName("Done");
359 // Fill the part name-to-id map.
360 for (Part p = PartStart; p != PartCount; p = static_cast<Part>(p + 1)) {
362 ->PartMap[cmSystemTools::LowerCase(this->Impl->Parts[p].GetName())] = p;
365 for (auto& handler : this->Impl->GetTestingHandlers()) {
366 handler->SetCTestInstance(this);
369 // Make sure we can capture the build tool output.
370 cmSystemTools::EnableVSConsoleOutput();
373 cmCTest::~cmCTest() = default;
375 int cmCTest::GetParallelLevel() const
377 return this->Impl->ParallelLevel;
380 void cmCTest::SetParallelLevel(int level)
382 this->Impl->ParallelLevel = level < 1 ? 1 : level;
385 unsigned long cmCTest::GetTestLoad() const
387 return this->Impl->TestLoad;
390 void cmCTest::SetTestLoad(unsigned long load)
392 this->Impl->TestLoad = load;
395 bool cmCTest::ShouldCompressTestOutput()
397 return this->Impl->CompressTestOutput;
400 cmCTest::Part cmCTest::GetPartFromName(const std::string& name)
402 // Look up by lower-case to make names case-insensitive.
403 std::string lower_name = cmSystemTools::LowerCase(name);
404 auto const i = this->Impl->PartMap.find(lower_name);
405 if (i != this->Impl->PartMap.end()) {
409 // The string does not name a valid part.
413 int cmCTest::Initialize(const char* binary_dir, cmCTestStartCommand* command)
416 if (command && command->ShouldBeQuiet()) {
420 cmCTestOptionalLog(this, DEBUG, "Here: " << __LINE__ << std::endl, quiet);
421 if (!this->Impl->InteractiveDebugMode) {
422 this->BlockTestErrorDiagnostics();
424 cmSystemTools::PutEnv("CTEST_INTERACTIVE_DEBUG_MODE=1");
427 this->Impl->BinaryDir = binary_dir;
428 cmSystemTools::ConvertToUnixSlashes(this->Impl->BinaryDir);
430 this->UpdateCTestConfiguration();
432 cmCTestOptionalLog(this, DEBUG, "Here: " << __LINE__ << std::endl, quiet);
433 if (this->Impl->ProduceXML) {
434 cmCTestOptionalLog(this, DEBUG, "Here: " << __LINE__ << std::endl, quiet);
435 cmCTestOptionalLog(this, OUTPUT,
437 << this->GetCTestConfiguration("Site") << std::endl
439 << cmCTest::SafeBuildIdField(
440 this->GetCTestConfiguration("BuildName"))
443 cmCTestOptionalLog(this, DEBUG, "Produce XML is on" << std::endl, quiet);
444 if (this->Impl->TestModel == cmCTest::NIGHTLY &&
445 this->GetCTestConfiguration("NightlyStartTime").empty()) {
448 "WARNING: No nightly start time found please set in CTestConfig.cmake"
449 " or DartConfig.cmake"
452 cmCTestOptionalLog(this, DEBUG, "Here: " << __LINE__ << std::endl,
458 cmake cm(cmake::RoleScript, cmState::CTest);
459 cm.SetHomeDirectory("");
460 cm.SetHomeOutputDirectory("");
461 cm.GetCurrentSnapshot().SetDefaultDefinitions();
462 cmGlobalGenerator gg(&cm);
463 cmMakefile mf(&gg, cm.GetCurrentSnapshot());
464 if (!this->ReadCustomConfigurationFileTree(this->Impl->BinaryDir, &mf)) {
466 this, DEBUG, "Cannot find custom configuration file tree" << std::endl,
471 if (this->Impl->ProduceXML) {
472 // Verify "Testing" directory exists:
474 std::string testingDir = this->Impl->BinaryDir + "/Testing";
475 if (cmSystemTools::FileExists(testingDir)) {
476 if (!cmSystemTools::FileIsDirectory(testingDir)) {
477 cmCTestLog(this, ERROR_MESSAGE,
478 "File " << testingDir
479 << " is in the place of the testing directory"
484 if (!cmSystemTools::MakeDirectory(testingDir)) {
485 cmCTestLog(this, ERROR_MESSAGE,
486 "Cannot create directory " << testingDir << std::endl);
491 // Create new "TAG" file or read existing one:
493 bool createNewTag = true;
495 createNewTag = command->ShouldCreateNewTag();
498 std::string tagfile = testingDir + "/TAG";
499 cmsys::ifstream tfin(tagfile.c_str());
503 time_t tctime = time(nullptr);
504 if (this->Impl->TomorrowTag) {
505 tctime += (24 * 60 * 60);
507 struct tm* lctime = gmtime(&tctime);
508 if (tfin && cmSystemTools::GetLineFromStream(tfin, tag)) {
514 sscanf(tag.c_str(), "%04d%02d%02d-%02d%02d", &year, &mon, &day, &hour,
516 if (year != lctime->tm_year + 1900 || mon != lctime->tm_mon + 1 ||
517 day != lctime->tm_mday) {
521 if (cmSystemTools::GetLineFromStream(tfin, group) &&
522 !this->Impl->Parts[PartStart] && !command) {
523 this->Impl->SpecificGroup = group;
526 if (cmSystemTools::GetLineFromStream(tfin, model) &&
527 !this->Impl->Parts[PartStart] && !command) {
528 this->Impl->TestModel = GetTestModelFromString(model);
532 if (tag.empty() || (nullptr != command) ||
533 this->Impl->Parts[PartStart]) {
536 "TestModel: " << this->GetTestModelString() << std::endl, quiet);
537 cmCTestOptionalLog(this, DEBUG,
538 "TestModel: " << this->Impl->TestModel << std::endl,
540 if (this->Impl->TestModel == cmCTest::NIGHTLY) {
541 lctime = this->GetNightlyTime(
542 this->GetCTestConfiguration("NightlyStartTime"),
543 this->Impl->TomorrowTag);
545 char datestring[100];
546 snprintf(datestring, sizeof(datestring), "%04d%02d%02d-%02d%02d",
547 lctime->tm_year + 1900, lctime->tm_mon + 1, lctime->tm_mday,
548 lctime->tm_hour, lctime->tm_min);
550 cmsys::ofstream ofs(tagfile.c_str());
552 ofs << tag << std::endl;
553 ofs << this->GetTestModelString() << std::endl;
554 switch (this->Impl->TestModel) {
555 case cmCTest::EXPERIMENTAL:
556 ofs << "Experimental" << std::endl;
558 case cmCTest::NIGHTLY:
559 ofs << "Nightly" << std::endl;
561 case cmCTest::CONTINUOUS:
562 ofs << "Continuous" << std::endl;
567 if (nullptr == command) {
568 cmCTestOptionalLog(this, OUTPUT,
569 "Create new tag: " << tag << " - "
570 << this->GetTestModelString()
577 std::string modelStr;
578 int model = cmCTest::UNKNOWN;
581 cmSystemTools::GetLineFromStream(tfin, tag);
582 cmSystemTools::GetLineFromStream(tfin, group);
583 if (cmSystemTools::GetLineFromStream(tfin, modelStr)) {
584 model = GetTestModelFromString(modelStr);
590 cmCTestLog(this, ERROR_MESSAGE,
591 "Cannot read existing TAG file in " << testingDir
596 if (this->Impl->TestModel == cmCTest::UNKNOWN) {
597 if (model == cmCTest::UNKNOWN) {
598 cmCTestLog(this, ERROR_MESSAGE,
599 "TAG file does not contain model and "
600 "no model specified in start command"
605 this->SetTestModel(model);
608 if (model != this->Impl->TestModel && model != cmCTest::UNKNOWN &&
609 this->Impl->TestModel != cmCTest::UNKNOWN) {
610 cmCTestOptionalLog(this, WARNING,
611 "Model given in TAG does not match "
612 "model given in ctest_start()"
617 if (!this->Impl->SpecificGroup.empty() &&
618 group != this->Impl->SpecificGroup) {
619 cmCTestOptionalLog(this, WARNING,
620 "Group given in TAG does not match "
621 "group given in ctest_start()"
625 this->Impl->SpecificGroup = group;
628 cmCTestOptionalLog(this, OUTPUT,
629 " Use existing tag: " << tag << " - "
630 << this->GetTestModelString()
635 this->Impl->CurrentTag = tag;
641 bool cmCTest::InitializeFromCommand(cmCTestStartCommand* command)
643 std::string src_dir = this->GetCTestConfiguration("SourceDirectory");
644 std::string bld_dir = this->GetCTestConfiguration("BuildDirectory");
645 this->Impl->BuildID = "";
646 for (Part p = PartStart; p != PartCount; p = static_cast<Part>(p + 1)) {
647 this->Impl->Parts[p].SubmitFiles.clear();
650 cmMakefile* mf = command->GetMakefile();
653 std::string src_dir_fname = cmStrCat(src_dir, "/CTestConfig.cmake");
654 cmSystemTools::ConvertToUnixSlashes(src_dir_fname);
656 std::string bld_dir_fname = cmStrCat(bld_dir, "/CTestConfig.cmake");
657 cmSystemTools::ConvertToUnixSlashes(bld_dir_fname);
659 if (cmSystemTools::FileExists(bld_dir_fname)) {
660 fname = bld_dir_fname;
661 } else if (cmSystemTools::FileExists(src_dir_fname)) {
662 fname = src_dir_fname;
665 if (!fname.empty()) {
666 cmCTestOptionalLog(this, OUTPUT,
667 " Reading ctest configuration file: " << fname
669 command->ShouldBeQuiet());
670 bool readit = mf->ReadDependentFile(fname);
672 std::string m = cmStrCat("Could not find include file: ", fname);
673 command->SetError(m);
678 this->SetCTestConfigurationFromCMakeVariable(mf, "NightlyStartTime",
679 "CTEST_NIGHTLY_START_TIME",
680 command->ShouldBeQuiet());
681 this->SetCTestConfigurationFromCMakeVariable(mf, "Site", "CTEST_SITE",
682 command->ShouldBeQuiet());
683 this->SetCTestConfigurationFromCMakeVariable(
684 mf, "BuildName", "CTEST_BUILD_NAME", command->ShouldBeQuiet());
686 if (!this->Initialize(bld_dir.c_str(), command)) {
689 cmCTestOptionalLog(this, OUTPUT,
690 " Use " << this->GetTestModelString() << " tag: "
691 << this->GetCurrentTag() << std::endl,
692 command->ShouldBeQuiet());
696 bool cmCTest::UpdateCTestConfiguration()
698 if (this->Impl->SuppressUpdatingCTestConfiguration) {
701 std::string fileName = this->Impl->BinaryDir + "/CTestConfiguration.ini";
702 if (!cmSystemTools::FileExists(fileName)) {
703 fileName = this->Impl->BinaryDir + "/DartConfiguration.tcl";
705 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
706 "UpdateCTestConfiguration from :" << fileName << "\n");
707 if (!cmSystemTools::FileExists(fileName)) {
708 // No need to exit if we are not producing XML
709 if (this->Impl->ProduceXML) {
710 cmCTestLog(this, WARNING, "Cannot find file: " << fileName << std::endl);
714 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
715 "Parse Config file:" << fileName << "\n");
716 // parse the dart test file
717 cmsys::ifstream fin(fileName.c_str());
726 fin.getline(buffer, 1023);
728 std::string line = cmCTest::CleanString(buffer);
732 while (fin && (line.back() == '\\')) {
733 line.resize(line.size() - 1);
735 fin.getline(buffer, 1023);
737 line += cmCTest::CleanString(buffer);
739 if (line[0] == '#') {
742 std::string::size_type cpos = line.find_first_of(':');
743 if (cpos == std::string::npos) {
746 std::string key = line.substr(0, cpos);
747 std::string value = cmCTest::CleanString(line, cpos + 1);
748 this->Impl->CTestConfiguration[key] = value;
752 if (!this->GetCTestConfiguration("BuildDirectory").empty()) {
753 this->Impl->BinaryDir = this->GetCTestConfiguration("BuildDirectory");
754 cmSystemTools::ChangeDirectory(this->Impl->BinaryDir);
756 this->Impl->TimeOut =
757 std::chrono::seconds(atoi(this->GetCTestConfiguration("TimeOut").c_str()));
758 std::string const& testLoad = this->GetCTestConfiguration("TestLoad");
759 if (!testLoad.empty()) {
761 if (cmStrToULong(testLoad, &load)) {
762 this->SetTestLoad(load);
764 cmCTestLog(this, WARNING,
765 "Invalid value for 'Test Load' : " << testLoad << std::endl);
768 if (this->Impl->ProduceXML) {
769 this->Impl->CompressXMLFiles =
770 cmIsOn(this->GetCTestConfiguration("CompressSubmission"));
775 void cmCTest::BlockTestErrorDiagnostics()
777 cmSystemTools::PutEnv("DART_TEST_FROM_DART=1");
778 cmSystemTools::PutEnv("DASHBOARD_TEST_FROM_CTEST=" CMake_VERSION);
780 SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
781 #elif defined(__BEOS__) || defined(__HAIKU__)
786 void cmCTest::SetTestModel(int mode)
788 this->Impl->InteractiveDebugMode = false;
789 this->Impl->TestModel = mode;
792 int cmCTest::GetTestModel() const
794 return this->Impl->TestModel;
797 bool cmCTest::SetTest(const std::string& ttype, bool report)
799 if (cmSystemTools::LowerCase(ttype) == "all") {
800 for (Part p = PartStart; p != PartCount; p = static_cast<Part>(p + 1)) {
801 this->Impl->Parts[p].Enable();
805 Part p = this->GetPartFromName(ttype);
806 if (p != PartCount) {
807 this->Impl->Parts[p].Enable();
811 cmCTestLog(this, ERROR_MESSAGE,
812 "Don't know about test \"" << ttype << "\" yet..."
818 void cmCTest::Finalize()
822 bool cmCTest::OpenOutputFile(const std::string& path, const std::string& name,
823 cmGeneratedFileStream& stream, bool compress)
825 std::string testingDir = this->Impl->BinaryDir + "/Testing";
827 testingDir += "/" + path;
829 if (cmSystemTools::FileExists(testingDir)) {
830 if (!cmSystemTools::FileIsDirectory(testingDir)) {
831 cmCTestLog(this, ERROR_MESSAGE,
832 "File " << testingDir
833 << " is in the place of the testing directory"
838 if (!cmSystemTools::MakeDirectory(testingDir)) {
839 cmCTestLog(this, ERROR_MESSAGE,
840 "Cannot create directory " << testingDir << std::endl);
844 std::string filename = testingDir + "/" + name;
845 stream.SetTempExt("tmp");
846 stream.Open(filename);
848 cmCTestLog(this, ERROR_MESSAGE,
849 "Problem opening file: " << filename << std::endl);
853 if (this->Impl->CompressXMLFiles) {
854 stream.SetCompression(true);
860 bool cmCTest::AddIfExists(Part part, const std::string& file)
862 if (this->CTestFileExists(file)) {
863 this->AddSubmitFile(part, file);
865 std::string name = cmStrCat(file, ".gz");
866 if (this->CTestFileExists(name)) {
867 this->AddSubmitFile(part, file);
875 bool cmCTest::CTestFileExists(const std::string& filename)
877 std::string testingDir = this->Impl->BinaryDir + "/Testing/" +
878 this->Impl->CurrentTag + "/" + filename;
879 return cmSystemTools::FileExists(testingDir);
882 cmCTestBuildHandler* cmCTest::GetBuildHandler()
884 return &this->Impl->BuildHandler;
887 cmCTestBuildAndTestHandler* cmCTest::GetBuildAndTestHandler()
889 return &this->Impl->BuildAndTestHandler;
892 cmCTestCoverageHandler* cmCTest::GetCoverageHandler()
894 return &this->Impl->CoverageHandler;
897 cmCTestScriptHandler* cmCTest::GetScriptHandler()
899 return &this->Impl->ScriptHandler;
902 cmCTestTestHandler* cmCTest::GetTestHandler()
904 return &this->Impl->TestHandler;
907 cmCTestUpdateHandler* cmCTest::GetUpdateHandler()
909 return &this->Impl->UpdateHandler;
912 cmCTestConfigureHandler* cmCTest::GetConfigureHandler()
914 return &this->Impl->ConfigureHandler;
917 cmCTestMemCheckHandler* cmCTest::GetMemCheckHandler()
919 return &this->Impl->MemCheckHandler;
922 cmCTestSubmitHandler* cmCTest::GetSubmitHandler()
924 return &this->Impl->SubmitHandler;
927 cmCTestUploadHandler* cmCTest::GetUploadHandler()
929 return &this->Impl->UploadHandler;
932 int cmCTest::ProcessSteps()
936 int update_count = 0;
938 for (Part p = PartStart; notest && p != PartCount;
939 p = static_cast<Part>(p + 1)) {
940 notest = !this->Impl->Parts[p];
942 if (this->Impl->Parts[PartUpdate] &&
943 (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
944 cmCTestUpdateHandler* uphandler = this->GetUpdateHandler();
945 uphandler->SetPersistentOption(
947 this->GetCTestConfiguration("SourceDirectory").c_str());
948 update_count = uphandler->ProcessHandler();
949 if (update_count < 0) {
950 res |= cmCTest::UPDATE_ERRORS;
953 if (this->Impl->TestModel == cmCTest::CONTINUOUS && !update_count) {
956 if (this->Impl->Parts[PartConfigure] &&
957 (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
958 if (this->GetConfigureHandler()->ProcessHandler() < 0) {
959 res |= cmCTest::CONFIGURE_ERRORS;
962 if (this->Impl->Parts[PartBuild] &&
963 (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
964 this->UpdateCTestConfiguration();
965 if (this->GetBuildHandler()->ProcessHandler() < 0) {
966 res |= cmCTest::BUILD_ERRORS;
969 if ((this->Impl->Parts[PartTest] || notest) &&
970 (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
971 this->UpdateCTestConfiguration();
972 if (this->GetTestHandler()->ProcessHandler() < 0) {
973 res |= cmCTest::TEST_ERRORS;
976 if (this->Impl->Parts[PartCoverage] &&
977 (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
978 this->UpdateCTestConfiguration();
979 if (this->GetCoverageHandler()->ProcessHandler() < 0) {
980 res |= cmCTest::COVERAGE_ERRORS;
983 if (this->Impl->Parts[PartMemCheck] &&
984 (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
985 this->UpdateCTestConfiguration();
986 if (this->GetMemCheckHandler()->ProcessHandler() < 0) {
987 res |= cmCTest::MEMORY_ERRORS;
991 std::string notes_dir = this->Impl->BinaryDir + "/Testing/Notes";
992 if (cmSystemTools::FileIsDirectory(notes_dir)) {
996 for (kk = 0; kk < d.GetNumberOfFiles(); kk++) {
997 const char* file = d.GetFile(kk);
998 std::string fullname = notes_dir + "/" + file;
999 if (cmSystemTools::FileExists(fullname) &&
1000 !cmSystemTools::FileIsDirectory(fullname)) {
1001 if (!this->Impl->NotesFiles.empty()) {
1002 this->Impl->NotesFiles += ";";
1004 this->Impl->NotesFiles += fullname;
1005 this->Impl->Parts[PartNotes].Enable();
1010 if (this->Impl->Parts[PartNotes]) {
1011 this->UpdateCTestConfiguration();
1012 if (!this->Impl->NotesFiles.empty()) {
1013 this->GenerateNotesFile(this->Impl->NotesFiles);
1016 if (this->Impl->Parts[PartSubmit]) {
1017 this->UpdateCTestConfiguration();
1018 if (this->GetSubmitHandler()->ProcessHandler() < 0) {
1019 res |= cmCTest::SUBMIT_ERRORS;
1023 cmCTestLog(this, ERROR_MESSAGE, "Errors while running CTest" << std::endl);
1024 if (!this->Impl->OutputTestOutputOnTestFailure) {
1025 const std::string lastTestLog =
1026 this->GetBinaryDir() + "/Testing/Temporary/LastTest.log";
1027 cmCTestLog(this, ERROR_MESSAGE,
1028 "Output from these tests are in: " << lastTestLog
1030 cmCTestLog(this, ERROR_MESSAGE,
1031 "Use \"--rerun-failed --output-on-failure\" to re-run the "
1032 "failed cases verbosely."
1039 std::string cmCTest::GetTestModelString()
1041 if (!this->Impl->SpecificGroup.empty()) {
1042 return this->Impl->SpecificGroup;
1044 switch (this->Impl->TestModel) {
1045 case cmCTest::NIGHTLY:
1047 case cmCTest::CONTINUOUS:
1048 return "Continuous";
1050 return "Experimental";
1053 int cmCTest::GetTestModelFromString(const std::string& str)
1056 return cmCTest::EXPERIMENTAL;
1058 std::string rstr = cmSystemTools::LowerCase(str);
1059 if (cmHasLiteralPrefix(rstr, "cont")) {
1060 return cmCTest::CONTINUOUS;
1062 if (cmHasLiteralPrefix(rstr, "nigh")) {
1063 return cmCTest::NIGHTLY;
1065 return cmCTest::EXPERIMENTAL;
1068 //######################################################################
1069 //######################################################################
1070 //######################################################################
1071 //######################################################################
1073 int cmCTest::RunMakeCommand(const std::string& command, std::string& output,
1074 int* retVal, const char* dir, cmDuration timeout,
1075 std::ostream& ofs, Encoding encoding)
1077 // First generate the command and arguments
1078 std::vector<std::string> args = cmSystemTools::ParseArguments(command);
1084 std::vector<const char*> argv;
1085 argv.reserve(args.size() + 1);
1086 for (std::string const& a : args) {
1087 argv.push_back(a.c_str());
1089 argv.push_back(nullptr);
1092 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, "Run command:");
1093 for (char const* arg : argv) {
1097 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, " \"" << arg << "\"");
1099 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, std::endl);
1101 // Now create process object
1102 cmsysProcess* cp = cmsysProcess_New();
1103 cmsysProcess_SetCommand(cp, argv.data());
1104 cmsysProcess_SetWorkingDirectory(cp, dir);
1105 cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
1106 cmsysProcess_SetTimeout(cp, timeout.count());
1107 cmsysProcess_Execute(cp);
1109 // Initialize tick's
1110 std::string::size_type tick = 0;
1111 std::string::size_type tick_len = 1024;
1112 std::string::size_type tick_line_len = 50;
1116 cmProcessOutput processOutput(encoding);
1117 std::string strdata;
1118 cmCTestLog(this, HANDLER_PROGRESS_OUTPUT,
1119 " Each . represents " << tick_len
1120 << " bytes of output\n"
1123 while (cmsysProcess_WaitForData(cp, &data, &length, nullptr)) {
1124 processOutput.DecodeText(data, length, strdata);
1125 for (char& cc : strdata) {
1130 output.append(strdata);
1131 while (output.size() > (tick * tick_len)) {
1133 cmCTestLog(this, HANDLER_PROGRESS_OUTPUT, "." << std::flush);
1134 if (tick % tick_line_len == 0 && tick > 0) {
1135 cmCTestLog(this, HANDLER_PROGRESS_OUTPUT,
1136 " Size: " << int((double(output.size()) / 1024.0) + 1)
1137 << "K\n " << std::flush);
1140 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
1141 cmCTestLogWrite(strdata.c_str(), strdata.size()));
1143 ofs << cmCTestLogWrite(strdata.c_str(), strdata.size());
1146 processOutput.DecodeText(std::string(), strdata);
1147 if (!strdata.empty()) {
1148 output.append(strdata);
1149 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
1150 cmCTestLogWrite(strdata.c_str(), strdata.size()));
1152 ofs << cmCTestLogWrite(strdata.c_str(), strdata.size());
1155 cmCTestLog(this, HANDLER_PROGRESS_OUTPUT,
1156 " Size of output: " << int(double(output.size()) / 1024.0) << "K"
1159 cmsysProcess_WaitForExit(cp, nullptr);
1161 int result = cmsysProcess_GetState(cp);
1163 if (result == cmsysProcess_State_Exited) {
1164 *retVal = cmsysProcess_GetExitValue(cp);
1165 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
1166 "Command exited with the value: " << *retVal << std::endl);
1167 } else if (result == cmsysProcess_State_Exception) {
1168 *retVal = cmsysProcess_GetExitException(cp);
1169 cmCTestLog(this, WARNING,
1170 "There was an exception: " << *retVal << std::endl);
1171 } else if (result == cmsysProcess_State_Expired) {
1172 cmCTestLog(this, WARNING, "There was a timeout" << std::endl);
1173 } else if (result == cmsysProcess_State_Error) {
1174 output += "\n*** ERROR executing: ";
1175 output += cmsysProcess_GetErrorString(cp);
1176 output += "\n***The build process failed.";
1177 cmCTestLog(this, ERROR_MESSAGE,
1178 "There was an error: " << cmsysProcess_GetErrorString(cp)
1182 cmsysProcess_Delete(cp);
1187 //######################################################################
1188 //######################################################################
1189 //######################################################################
1190 //######################################################################
1192 int cmCTest::RunTest(std::vector<const char*> argv, std::string* output,
1193 int* retVal, std::ostream* log, cmDuration testTimeOut,
1194 std::vector<std::string>* environment, Encoding encoding)
1196 bool modifyEnv = (environment && !environment->empty());
1198 // determine how much time we have
1199 cmDuration timeout = this->GetRemainingTimeAllowed();
1200 if (timeout != cmCTest::MaxDuration()) {
1201 timeout -= std::chrono::minutes(2);
1203 if (this->Impl->TimeOut > cmDuration::zero() &&
1204 this->Impl->TimeOut < timeout) {
1205 timeout = this->Impl->TimeOut;
1207 if (testTimeOut > cmDuration::zero() &&
1208 testTimeOut < this->GetRemainingTimeAllowed()) {
1209 timeout = testTimeOut;
1212 // always have at least 1 second if we got to here
1213 if (timeout <= cmDuration::zero()) {
1214 timeout = std::chrono::seconds(1);
1216 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
1217 "Test timeout computed to be: "
1218 << (timeout == cmCTest::MaxDuration()
1219 ? std::string("infinite")
1220 : std::to_string(cmDurationTo<unsigned int>(timeout)))
1222 if (cmSystemTools::SameFile(argv[0], cmSystemTools::GetCTestCommand()) &&
1223 !this->Impl->ForceNewCTestProcess) {
1225 inst.Impl->ConfigType = this->Impl->ConfigType;
1226 inst.Impl->TimeOut = timeout;
1228 // Capture output of the child ctest.
1229 std::ostringstream oss;
1230 inst.SetStreams(&oss, &oss);
1232 std::vector<std::string> args;
1233 for (char const* i : argv) {
1235 // make sure we pass the timeout in for any build and test
1236 // invocations. Since --build-generator is required this is a
1237 // good place to check for it, and to add the arguments in
1238 if (strcmp(i, "--build-generator") == 0 &&
1239 timeout != cmCTest::MaxDuration() &&
1240 timeout > cmDuration::zero()) {
1241 args.emplace_back("--test-timeout");
1242 args.push_back(std::to_string(cmDurationTo<unsigned int>(timeout)));
1244 args.emplace_back(i);
1248 *log << "* Run internal CTest" << std::endl;
1251 std::unique_ptr<cmSystemTools::SaveRestoreEnvironment> saveEnv;
1253 saveEnv = cm::make_unique<cmSystemTools::SaveRestoreEnvironment>();
1254 cmSystemTools::AppendEnv(*environment);
1257 *retVal = inst.Run(args, output);
1259 *output += oss.str();
1261 if (log && output) {
1265 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
1266 "Internal cmCTest object used to run test." << std::endl
1271 return cmsysProcess_State_Exited;
1273 std::vector<char> tempOutput;
1278 std::unique_ptr<cmSystemTools::SaveRestoreEnvironment> saveEnv;
1280 saveEnv = cm::make_unique<cmSystemTools::SaveRestoreEnvironment>();
1281 cmSystemTools::AppendEnv(*environment);
1284 cmsysProcess* cp = cmsysProcess_New();
1285 cmsysProcess_SetCommand(cp, argv.data());
1286 cmCTestLog(this, DEBUG, "Command is: " << argv[0] << std::endl);
1287 if (cmSystemTools::GetRunCommandHideConsole()) {
1288 cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
1291 cmsysProcess_SetTimeout(cp, timeout.count());
1292 cmsysProcess_Execute(cp);
1296 cmProcessOutput processOutput(encoding);
1297 std::string strdata;
1298 while (cmsysProcess_WaitForData(cp, &data, &length, nullptr)) {
1299 processOutput.DecodeText(data, length, strdata);
1301 cm::append(tempOutput, data, data + length);
1303 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
1304 cmCTestLogWrite(strdata.c_str(), strdata.size()));
1306 log->write(strdata.c_str(), strdata.size());
1309 processOutput.DecodeText(std::string(), strdata);
1310 if (!strdata.empty()) {
1311 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
1312 cmCTestLogWrite(strdata.c_str(), strdata.size()));
1314 log->write(strdata.c_str(), strdata.size());
1318 cmsysProcess_WaitForExit(cp, nullptr);
1319 processOutput.DecodeText(tempOutput, tempOutput);
1320 if (output && tempOutput.begin() != tempOutput.end()) {
1321 output->append(tempOutput.data(), tempOutput.size());
1323 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
1324 "-- Process completed" << std::endl);
1326 int result = cmsysProcess_GetState(cp);
1328 if (result == cmsysProcess_State_Exited) {
1329 *retVal = cmsysProcess_GetExitValue(cp);
1330 if (*retVal != 0 && this->Impl->OutputTestOutputOnTestFailure) {
1331 this->OutputTestErrors(tempOutput);
1333 } else if (result == cmsysProcess_State_Exception) {
1334 if (this->Impl->OutputTestOutputOnTestFailure) {
1335 this->OutputTestErrors(tempOutput);
1337 *retVal = cmsysProcess_GetExitException(cp);
1338 std::string outerr = cmStrCat("\n*** Exception executing: ",
1339 cmsysProcess_GetExceptionString(cp));
1343 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, outerr << std::endl);
1344 } else if (result == cmsysProcess_State_Error) {
1345 std::string outerr =
1346 cmStrCat("\n*** ERROR executing: ", cmsysProcess_GetErrorString(cp));
1350 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, outerr << std::endl);
1352 cmsysProcess_Delete(cp);
1357 std::string cmCTest::SafeBuildIdField(const std::string& value)
1359 std::string safevalue(value);
1361 if (!safevalue.empty()) {
1362 // Disallow non-filename and non-space whitespace characters.
1363 // If they occur, replace them with ""
1365 const char* disallowed = "\\:*?\"<>|\n\r\t\f\v";
1367 if (safevalue.find_first_of(disallowed) != std::string::npos) {
1368 std::string::size_type i = 0;
1369 std::string::size_type n = strlen(disallowed);
1373 for (i = 0; i < n; ++i) {
1374 replace[0] = disallowed[i];
1375 cmSystemTools::ReplaceString(safevalue, replace, "");
1380 if (safevalue.empty()) {
1381 safevalue = "(empty)";
1387 void cmCTest::StartXML(cmXMLWriter& xml, bool append)
1389 if (this->Impl->CurrentTag.empty()) {
1390 cmCTestLog(this, ERROR_MESSAGE,
1391 "Current Tag empty, this may mean"
1392 " NightlStartTime was not set correctly."
1394 cmSystemTools::SetFatalErrorOccurred();
1397 // find out about the system
1398 cmsys::SystemInformation info;
1401 info.RunMemoryCheck();
1403 std::string buildname =
1404 cmCTest::SafeBuildIdField(this->GetCTestConfiguration("BuildName"));
1405 std::string stamp = cmCTest::SafeBuildIdField(this->Impl->CurrentTag + "-" +
1406 this->GetTestModelString());
1408 cmCTest::SafeBuildIdField(this->GetCTestConfiguration("Site"));
1410 xml.StartDocument();
1411 xml.StartElement("Site");
1412 xml.Attribute("BuildName", buildname);
1413 xml.BreakAttributes();
1414 xml.Attribute("BuildStamp", stamp);
1415 xml.Attribute("Name", site);
1416 xml.Attribute("Generator",
1417 std::string("ctest-") + cmVersion::GetCMakeVersion());
1419 xml.Attribute("Append", "true");
1421 xml.Attribute("CompilerName", this->GetCTestConfiguration("Compiler"));
1422 xml.Attribute("CompilerVersion",
1423 this->GetCTestConfiguration("CompilerVersion"));
1424 xml.Attribute("OSName", info.GetOSName());
1425 xml.Attribute("Hostname", info.GetHostname());
1426 xml.Attribute("OSRelease", info.GetOSRelease());
1427 xml.Attribute("OSVersion", info.GetOSVersion());
1428 xml.Attribute("OSPlatform", info.GetOSPlatform());
1429 xml.Attribute("Is64Bits", info.Is64Bits());
1430 xml.Attribute("VendorString", info.GetVendorString());
1431 xml.Attribute("VendorID", info.GetVendorID());
1432 xml.Attribute("FamilyID", info.GetFamilyID());
1433 xml.Attribute("ModelID", info.GetModelID());
1434 xml.Attribute("ProcessorCacheSize", info.GetProcessorCacheSize());
1435 xml.Attribute("NumberOfLogicalCPU", info.GetNumberOfLogicalCPU());
1436 xml.Attribute("NumberOfPhysicalCPU", info.GetNumberOfPhysicalCPU());
1437 xml.Attribute("TotalVirtualMemory", info.GetTotalVirtualMemory());
1438 xml.Attribute("TotalPhysicalMemory", info.GetTotalPhysicalMemory());
1439 xml.Attribute("LogicalProcessorsPerPhysical",
1440 info.GetLogicalProcessorsPerPhysical());
1441 xml.Attribute("ProcessorClockFrequency", info.GetProcessorClockFrequency());
1443 std::string changeId = this->GetCTestConfiguration("ChangeId");
1444 if (!changeId.empty()) {
1445 xml.Attribute("ChangeId", changeId);
1448 this->AddSiteProperties(xml);
1451 void cmCTest::AddSiteProperties(cmXMLWriter& xml)
1453 cmCTestScriptHandler* ch = this->GetScriptHandler();
1454 cmake* cm = ch->GetCMake();
1455 // if no CMake then this is the old style script and props like
1456 // this will not work anyway.
1460 // This code should go when cdash is changed to use labels only
1461 cmValue subproject = cm->GetState()->GetGlobalProperty("SubProject");
1463 xml.StartElement("Subproject");
1464 xml.Attribute("name", *subproject);
1466 ch->GetCMake()->GetState()->GetGlobalProperty("SubProjectLabels");
1468 xml.StartElement("Labels");
1469 std::vector<std::string> args = cmExpandedList(*labels);
1470 for (std::string const& i : args) {
1471 xml.Element("Label", i);
1478 // This code should stay when cdash only does label based sub-projects
1479 cmValue label = cm->GetState()->GetGlobalProperty("Label");
1481 xml.StartElement("Labels");
1482 xml.Element("Label", *label);
1487 void cmCTest::GenerateSubprojectsOutput(cmXMLWriter& xml)
1489 for (std::string const& subproj : this->GetLabelsForSubprojects()) {
1490 xml.StartElement("Subproject");
1491 xml.Attribute("name", subproj);
1492 xml.Element("Label", subproj);
1493 xml.EndElement(); // Subproject
1497 std::vector<std::string> cmCTest::GetLabelsForSubprojects()
1499 std::string labelsForSubprojects =
1500 this->GetCTestConfiguration("LabelsForSubprojects");
1501 std::vector<std::string> subprojects = cmExpandedList(labelsForSubprojects);
1504 std::sort(subprojects.begin(), subprojects.end());
1505 // remove duplicates
1506 auto new_end = std::unique(subprojects.begin(), subprojects.end());
1507 subprojects.erase(new_end, subprojects.end());
1512 void cmCTest::EndXML(cmXMLWriter& xml)
1514 xml.EndElement(); // Site
1518 int cmCTest::GenerateCTestNotesOutput(cmXMLWriter& xml,
1519 std::vector<std::string> const& files)
1521 std::string buildname =
1522 cmCTest::SafeBuildIdField(this->GetCTestConfiguration("BuildName"));
1523 xml.StartDocument();
1524 xml.ProcessingInstruction("xml-stylesheet",
1525 "type=\"text/xsl\" "
1526 "href=\"Dart/Source/Server/XSL/Build.xsl "
1527 "<file:///Dart/Source/Server/XSL/Build.xsl> \"");
1528 xml.StartElement("Site");
1529 xml.Attribute("BuildName", buildname);
1530 xml.Attribute("BuildStamp",
1531 this->Impl->CurrentTag + "-" + this->GetTestModelString());
1532 xml.Attribute("Name", this->GetCTestConfiguration("Site"));
1533 xml.Attribute("Generator",
1534 std::string("ctest-") + cmVersion::GetCMakeVersion());
1535 this->AddSiteProperties(xml);
1536 xml.StartElement("Notes");
1538 for (std::string const& file : files) {
1539 cmCTestLog(this, OUTPUT, "\tAdd file: " << file << std::endl);
1540 std::string note_time = this->CurrentTime();
1541 xml.StartElement("Note");
1542 xml.Attribute("Name", file);
1543 xml.Element("Time", std::chrono::system_clock::now());
1544 xml.Element("DateTime", note_time);
1545 xml.StartElement("Text");
1546 cmsys::ifstream ifs(file.c_str());
1549 while (cmSystemTools::GetLineFromStream(ifs, line)) {
1555 xml.Content("Problem reading file: " + file + "\n");
1556 cmCTestLog(this, ERROR_MESSAGE,
1557 "Problem reading file: " << file << " while creating notes"
1560 xml.EndElement(); // Text
1561 xml.EndElement(); // Note
1563 xml.EndElement(); // Notes
1564 xml.EndElement(); // Site
1569 int cmCTest::GenerateNotesFile(std::vector<std::string> const& files)
1571 cmGeneratedFileStream ofs;
1572 if (!this->OpenOutputFile(this->Impl->CurrentTag, "Notes.xml", ofs)) {
1573 cmCTestLog(this, ERROR_MESSAGE, "Cannot open notes file" << std::endl);
1576 cmXMLWriter xml(ofs);
1577 this->GenerateCTestNotesOutput(xml, files);
1581 int cmCTest::GenerateNotesFile(const std::string& cfiles)
1583 if (cfiles.empty()) {
1587 cmCTestLog(this, OUTPUT, "Create notes file" << std::endl);
1589 std::vector<std::string> const files =
1590 cmSystemTools::SplitString(cfiles, ';');
1591 if (files.empty()) {
1595 return this->GenerateNotesFile(files);
1598 int cmCTest::GenerateDoneFile()
1600 cmGeneratedFileStream ofs;
1601 if (!this->OpenOutputFile(this->Impl->CurrentTag, "Done.xml", ofs)) {
1602 cmCTestLog(this, ERROR_MESSAGE, "Cannot open done file" << std::endl);
1605 cmXMLWriter xml(ofs);
1606 xml.StartDocument();
1607 xml.StartElement("Done");
1608 xml.Element("buildId", this->Impl->BuildID);
1609 xml.Element("time", std::chrono::system_clock::now());
1610 xml.EndElement(); // Done
1616 bool cmCTest::TryToChangeDirectory(std::string const& dir)
1618 cmCTestLog(this, OUTPUT,
1619 "Internal ctest changing into directory: " << dir << std::endl);
1620 cmsys::Status status = cmSystemTools::ChangeDirectory(dir);
1622 auto msg = "Failed to change working directory to \"" + dir +
1623 "\" : " + status.GetString() + "\n";
1624 cmCTestLog(this, ERROR_MESSAGE, msg);
1630 std::string cmCTest::Base64GzipEncodeFile(std::string const& file)
1632 const std::string currDir = cmSystemTools::GetCurrentWorkingDirectory();
1633 std::string parentDir = cmSystemTools::GetParentDirectory(file);
1635 // Temporarily change to the file's directory so the tar gets created
1636 // with a flat directory structure.
1637 if (currDir != parentDir) {
1638 if (!this->TryToChangeDirectory(parentDir)) {
1643 std::string tarFile = file + "_temp.tar.gz";
1644 std::vector<std::string> files;
1645 files.push_back(file);
1647 if (!cmSystemTools::CreateTar(tarFile, files, cmSystemTools::TarCompressGZip,
1649 cmCTestLog(this, ERROR_MESSAGE,
1650 "Error creating tar while "
1652 << file << std::endl);
1655 std::string base64 = this->Base64EncodeFile(tarFile);
1656 cmSystemTools::RemoveFile(tarFile);
1658 // Change back to the directory we started in.
1659 if (currDir != parentDir) {
1660 cmSystemTools::ChangeDirectory(currDir);
1666 std::string cmCTest::Base64EncodeFile(std::string const& file)
1668 size_t const len = cmSystemTools::FileLength(file);
1669 cmsys::ifstream ifs(file.c_str(),
1675 std::vector<char> file_buffer(len + 1);
1676 ifs.read(file_buffer.data(), len);
1679 std::vector<char> encoded_buffer((len * 3) / 2 + 5);
1681 size_t const rlen = cmsysBase64_Encode(
1682 reinterpret_cast<unsigned char*>(file_buffer.data()), len,
1683 reinterpret_cast<unsigned char*>(encoded_buffer.data()), 1);
1685 return std::string(encoded_buffer.data(), rlen);
1688 bool cmCTest::SubmitExtraFiles(std::vector<std::string> const& files)
1690 for (std::string const& file : files) {
1691 if (!cmSystemTools::FileExists(file)) {
1692 cmCTestLog(this, ERROR_MESSAGE,
1693 "Cannot find extra file: " << file << " to submit."
1697 this->AddSubmitFile(PartExtraFiles, file);
1702 bool cmCTest::SubmitExtraFiles(const std::string& cfiles)
1704 if (cfiles.empty()) {
1708 cmCTestLog(this, OUTPUT, "Submit extra files" << std::endl);
1710 std::vector<std::string> const files =
1711 cmSystemTools::SplitString(cfiles, ';');
1712 if (files.empty()) {
1716 return this->SubmitExtraFiles(files);
1719 // for a -D argument convert the next argument into
1720 // the proper list of dashboard steps via SetTest
1721 bool cmCTest::AddTestsForDashboardType(std::string& targ)
1723 if (targ == "Experimental") {
1724 this->SetTestModel(cmCTest::EXPERIMENTAL);
1725 this->SetTest("Start");
1726 this->SetTest("Configure");
1727 this->SetTest("Build");
1728 this->SetTest("Test");
1729 this->SetTest("Coverage");
1730 this->SetTest("Submit");
1731 } else if (targ == "ExperimentalStart") {
1732 this->SetTestModel(cmCTest::EXPERIMENTAL);
1733 this->SetTest("Start");
1734 } else if (targ == "ExperimentalUpdate") {
1735 this->SetTestModel(cmCTest::EXPERIMENTAL);
1736 this->SetTest("Update");
1737 } else if (targ == "ExperimentalConfigure") {
1738 this->SetTestModel(cmCTest::EXPERIMENTAL);
1739 this->SetTest("Configure");
1740 } else if (targ == "ExperimentalBuild") {
1741 this->SetTestModel(cmCTest::EXPERIMENTAL);
1742 this->SetTest("Build");
1743 } else if (targ == "ExperimentalTest") {
1744 this->SetTestModel(cmCTest::EXPERIMENTAL);
1745 this->SetTest("Test");
1746 } else if (targ == "ExperimentalMemCheck" || targ == "ExperimentalPurify") {
1747 this->SetTestModel(cmCTest::EXPERIMENTAL);
1748 this->SetTest("MemCheck");
1749 } else if (targ == "ExperimentalCoverage") {
1750 this->SetTestModel(cmCTest::EXPERIMENTAL);
1751 this->SetTest("Coverage");
1752 } else if (targ == "ExperimentalSubmit") {
1753 this->SetTestModel(cmCTest::EXPERIMENTAL);
1754 this->SetTest("Submit");
1755 } else if (targ == "Continuous") {
1756 this->SetTestModel(cmCTest::CONTINUOUS);
1757 this->SetTest("Start");
1758 this->SetTest("Update");
1759 this->SetTest("Configure");
1760 this->SetTest("Build");
1761 this->SetTest("Test");
1762 this->SetTest("Coverage");
1763 this->SetTest("Submit");
1764 } else if (targ == "ContinuousStart") {
1765 this->SetTestModel(cmCTest::CONTINUOUS);
1766 this->SetTest("Start");
1767 } else if (targ == "ContinuousUpdate") {
1768 this->SetTestModel(cmCTest::CONTINUOUS);
1769 this->SetTest("Update");
1770 } else if (targ == "ContinuousConfigure") {
1771 this->SetTestModel(cmCTest::CONTINUOUS);
1772 this->SetTest("Configure");
1773 } else if (targ == "ContinuousBuild") {
1774 this->SetTestModel(cmCTest::CONTINUOUS);
1775 this->SetTest("Build");
1776 } else if (targ == "ContinuousTest") {
1777 this->SetTestModel(cmCTest::CONTINUOUS);
1778 this->SetTest("Test");
1779 } else if (targ == "ContinuousMemCheck" || targ == "ContinuousPurify") {
1780 this->SetTestModel(cmCTest::CONTINUOUS);
1781 this->SetTest("MemCheck");
1782 } else if (targ == "ContinuousCoverage") {
1783 this->SetTestModel(cmCTest::CONTINUOUS);
1784 this->SetTest("Coverage");
1785 } else if (targ == "ContinuousSubmit") {
1786 this->SetTestModel(cmCTest::CONTINUOUS);
1787 this->SetTest("Submit");
1788 } else if (targ == "Nightly") {
1789 this->SetTestModel(cmCTest::NIGHTLY);
1790 this->SetTest("Start");
1791 this->SetTest("Update");
1792 this->SetTest("Configure");
1793 this->SetTest("Build");
1794 this->SetTest("Test");
1795 this->SetTest("Coverage");
1796 this->SetTest("Submit");
1797 } else if (targ == "NightlyStart") {
1798 this->SetTestModel(cmCTest::NIGHTLY);
1799 this->SetTest("Start");
1800 } else if (targ == "NightlyUpdate") {
1801 this->SetTestModel(cmCTest::NIGHTLY);
1802 this->SetTest("Update");
1803 } else if (targ == "NightlyConfigure") {
1804 this->SetTestModel(cmCTest::NIGHTLY);
1805 this->SetTest("Configure");
1806 } else if (targ == "NightlyBuild") {
1807 this->SetTestModel(cmCTest::NIGHTLY);
1808 this->SetTest("Build");
1809 } else if (targ == "NightlyTest") {
1810 this->SetTestModel(cmCTest::NIGHTLY);
1811 this->SetTest("Test");
1812 } else if (targ == "NightlyMemCheck" || targ == "NightlyPurify") {
1813 this->SetTestModel(cmCTest::NIGHTLY);
1814 this->SetTest("MemCheck");
1815 } else if (targ == "NightlyCoverage") {
1816 this->SetTestModel(cmCTest::NIGHTLY);
1817 this->SetTest("Coverage");
1818 } else if (targ == "NightlySubmit") {
1819 this->SetTestModel(cmCTest::NIGHTLY);
1820 this->SetTest("Submit");
1821 } else if (targ == "MemoryCheck") {
1822 this->SetTestModel(cmCTest::EXPERIMENTAL);
1823 this->SetTest("Start");
1824 this->SetTest("Configure");
1825 this->SetTest("Build");
1826 this->SetTest("MemCheck");
1827 this->SetTest("Coverage");
1828 this->SetTest("Submit");
1829 } else if (targ == "NightlyMemoryCheck") {
1830 this->SetTestModel(cmCTest::NIGHTLY);
1831 this->SetTest("Start");
1832 this->SetTest("Update");
1833 this->SetTest("Configure");
1834 this->SetTest("Build");
1835 this->SetTest("MemCheck");
1836 this->SetTest("Coverage");
1837 this->SetTest("Submit");
1844 void cmCTest::ErrorMessageUnknownDashDValue(std::string& val)
1846 cmCTestLog(this, ERROR_MESSAGE,
1847 "CTest -D called with incorrect option: " << val << std::endl);
1850 this, ERROR_MESSAGE,
1851 "Available options are:"
1853 << " ctest -D Continuous" << std::endl
1854 << " ctest -D Continuous(Start|Update|Configure|Build)" << std::endl
1855 << " ctest -D Continuous(Test|Coverage|MemCheck|Submit)" << std::endl
1856 << " ctest -D Experimental" << std::endl
1857 << " ctest -D Experimental(Start|Update|Configure|Build)" << std::endl
1858 << " ctest -D Experimental(Test|Coverage|MemCheck|Submit)" << std::endl
1859 << " ctest -D Nightly" << std::endl
1860 << " ctest -D Nightly(Start|Update|Configure|Build)" << std::endl
1861 << " ctest -D Nightly(Test|Coverage|MemCheck|Submit)" << std::endl
1862 << " ctest -D NightlyMemoryCheck" << std::endl);
1865 bool cmCTest::CheckArgument(const std::string& arg, cm::string_view varg1,
1868 return (arg == varg1) || (varg2 && arg == varg2);
1871 // Processes one command line argument (and its arguments if any)
1872 // for many simple options and then returns
1873 bool cmCTest::HandleCommandLineArguments(size_t& i,
1874 std::vector<std::string>& args,
1875 std::string& errormsg)
1877 std::string arg = args[i];
1878 if (this->CheckArgument(arg, "-F"_s)) {
1879 this->Impl->Failover = true;
1880 } else if (this->CheckArgument(arg, "-j"_s, "--parallel") &&
1881 i < args.size() - 1) {
1883 int plevel = atoi(args[i].c_str());
1884 this->SetParallelLevel(plevel);
1885 this->Impl->ParallelLevelSetInCli = true;
1886 } else if (cmHasPrefix(arg, "-j")) {
1887 int plevel = atoi(arg.substr(2).c_str());
1888 this->SetParallelLevel(plevel);
1889 this->Impl->ParallelLevelSetInCli = true;
1892 else if (this->CheckArgument(arg, "--repeat-until-fail"_s)) {
1893 if (i >= args.size() - 1) {
1894 errormsg = "'--repeat-until-fail' requires an argument";
1897 if (this->Impl->RepeatMode != cmCTest::Repeat::Never) {
1898 errormsg = "At most one '--repeat' option may be used.";
1903 if (!cmStrToLong(args[i], &repeat)) {
1904 errormsg = cmStrCat("'--repeat-until-fail' given non-integer value '",
1908 this->Impl->RepeatCount = static_cast<int>(repeat);
1910 this->Impl->RepeatMode = cmCTest::Repeat::UntilFail;
1914 else if (this->CheckArgument(arg, "--repeat"_s)) {
1915 if (i >= args.size() - 1) {
1916 errormsg = "'--repeat' requires an argument";
1919 if (this->Impl->RepeatMode != cmCTest::Repeat::Never) {
1920 errormsg = "At most one '--repeat' option may be used.";
1924 cmsys::RegularExpression repeatRegex(
1925 "^(until-fail|until-pass|after-timeout):([0-9]+)$");
1926 if (repeatRegex.find(args[i])) {
1927 std::string const& count = repeatRegex.match(2);
1928 unsigned long n = 1;
1929 cmStrToULong(count, &n); // regex guarantees success
1930 this->Impl->RepeatCount = static_cast<int>(n);
1931 if (this->Impl->RepeatCount > 1) {
1932 std::string const& mode = repeatRegex.match(1);
1933 if (mode == "until-fail") {
1934 this->Impl->RepeatMode = cmCTest::Repeat::UntilFail;
1935 } else if (mode == "until-pass") {
1936 this->Impl->RepeatMode = cmCTest::Repeat::UntilPass;
1937 } else if (mode == "after-timeout") {
1938 this->Impl->RepeatMode = cmCTest::Repeat::AfterTimeout;
1942 errormsg = cmStrCat("'--repeat' given invalid value '", args[i], "'");
1947 else if (this->CheckArgument(arg, "--test-load"_s) && i < args.size() - 1) {
1950 if (cmStrToULong(args[i], &load)) {
1951 this->SetTestLoad(load);
1953 cmCTestLog(this, WARNING,
1954 "Invalid value for 'Test Load' : " << args[i] << std::endl);
1958 else if (this->CheckArgument(arg, "--no-compress-output"_s)) {
1959 this->Impl->CompressTestOutput = false;
1962 else if (this->CheckArgument(arg, "--print-labels"_s)) {
1963 this->Impl->PrintLabels = true;
1966 else if (this->CheckArgument(arg, "--http1.0"_s)) {
1967 this->Impl->UseHTTP10 = true;
1970 else if (this->CheckArgument(arg, "--timeout"_s) && i < args.size() - 1) {
1972 auto timeout = cmDuration(atof(args[i].c_str()));
1973 this->Impl->GlobalTimeout = timeout;
1976 else if (this->CheckArgument(arg, "--stop-time"_s) && i < args.size() - 1) {
1978 this->SetStopTime(args[i]);
1981 else if (this->CheckArgument(arg, "--stop-on-failure"_s)) {
1982 this->Impl->StopOnFailure = true;
1985 else if (this->CheckArgument(arg, "-C"_s, "--build-config") &&
1986 i < args.size() - 1) {
1988 this->SetConfigType(args[i]);
1991 else if (this->CheckArgument(arg, "--debug"_s)) {
1992 this->Impl->Debug = true;
1993 this->Impl->ShowLineNumbers = true;
1994 } else if ((this->CheckArgument(arg, "--group"_s) ||
1995 // This is an undocumented / deprecated option.
1996 // "Track" has been renamed to "Group".
1997 this->CheckArgument(arg, "--track"_s)) &&
1998 i < args.size() - 1) {
2000 this->Impl->SpecificGroup = args[i];
2001 } else if (this->CheckArgument(arg, "--show-line-numbers"_s)) {
2002 this->Impl->ShowLineNumbers = true;
2003 } else if (this->CheckArgument(arg, "--no-label-summary"_s)) {
2004 this->Impl->LabelSummary = false;
2005 } else if (this->CheckArgument(arg, "--no-subproject-summary"_s)) {
2006 this->Impl->SubprojectSummary = false;
2007 } else if (this->CheckArgument(arg, "-Q"_s, "--quiet")) {
2008 this->Impl->Quiet = true;
2009 } else if (this->CheckArgument(arg, "--progress"_s)) {
2010 this->Impl->TestProgressOutput = true;
2011 } else if (this->CheckArgument(arg, "-V"_s, "--verbose")) {
2012 this->Impl->Verbose = true;
2013 } else if (this->CheckArgument(arg, "-VV"_s, "--extra-verbose")) {
2014 this->Impl->ExtraVerbose = true;
2015 this->Impl->Verbose = true;
2016 } else if (this->CheckArgument(arg, "--output-on-failure"_s)) {
2017 this->Impl->OutputTestOutputOnTestFailure = true;
2018 } else if (this->CheckArgument(arg, "--test-output-size-passed"_s) &&
2019 i < args.size() - 1) {
2022 if (cmStrToLong(args[i], &outputSize)) {
2023 this->Impl->TestHandler.SetTestOutputSizePassed(
2024 static_cast<int>(outputSize));
2026 cmCTestLog(this, WARNING,
2027 "Invalid value for '--test-output-size-passed': " << args[i]
2030 } else if (this->CheckArgument(arg, "--test-output-size-failed"_s) &&
2031 i < args.size() - 1) {
2034 if (cmStrToLong(args[i], &outputSize)) {
2035 this->Impl->TestHandler.SetTestOutputSizeFailed(
2036 static_cast<int>(outputSize));
2038 cmCTestLog(this, WARNING,
2039 "Invalid value for '--test-output-size-failed': " << args[i]
2042 } else if (this->CheckArgument(arg, "--test-output-truncation"_s) &&
2043 i < args.size() - 1) {
2045 if (!this->Impl->TestHandler.SetTestOutputTruncation(args[i])) {
2046 errormsg = "Invalid value for '--test-output-truncation': " + args[i];
2049 } else if (this->CheckArgument(arg, "-N"_s, "--show-only")) {
2050 this->Impl->ShowOnly = true;
2051 } else if (cmHasLiteralPrefix(arg, "--show-only=")) {
2052 this->Impl->ShowOnly = true;
2054 // Check if a specific format is requested. Defaults to human readable
2056 std::string argWithFormat = "--show-only=";
2057 std::string format = arg.substr(argWithFormat.length());
2058 if (format == "json-v1") {
2059 // Force quiet mode so the only output is the json object model.
2060 this->Impl->Quiet = true;
2061 this->Impl->OutputAsJson = true;
2062 this->Impl->OutputAsJsonVersion = 1;
2063 } else if (format != "human") {
2064 errormsg = "'--show-only=' given unknown value '" + format + "'";
2069 else if (this->CheckArgument(arg, "-O"_s, "--output-log") &&
2070 i < args.size() - 1) {
2072 this->SetOutputLogFileName(args[i]);
2075 else if (this->CheckArgument(arg, "--tomorrow-tag"_s)) {
2076 this->Impl->TomorrowTag = true;
2077 } else if (this->CheckArgument(arg, "--force-new-ctest-process"_s)) {
2078 this->Impl->ForceNewCTestProcess = true;
2079 } else if (this->CheckArgument(arg, "-W"_s, "--max-width") &&
2080 i < args.size() - 1) {
2082 this->Impl->MaxTestNameWidth = atoi(args[i].c_str());
2083 } else if (this->CheckArgument(arg, "--interactive-debug-mode"_s) &&
2084 i < args.size() - 1) {
2086 this->Impl->InteractiveDebugMode = cmIsOn(args[i]);
2087 } else if (this->CheckArgument(arg, "--submit-index"_s) &&
2088 i < args.size() - 1) {
2090 this->Impl->SubmitIndex = atoi(args[i].c_str());
2091 if (this->Impl->SubmitIndex < 0) {
2092 this->Impl->SubmitIndex = 0;
2096 else if (this->CheckArgument(arg, "--overwrite"_s) && i < args.size() - 1) {
2098 this->AddCTestConfigurationOverwrite(args[i]);
2099 } else if (this->CheckArgument(arg, "-A"_s, "--add-notes") &&
2100 i < args.size() - 1) {
2101 this->Impl->ProduceXML = true;
2102 this->SetTest("Notes");
2104 this->SetNotesFiles(args[i]);
2106 } else if (this->CheckArgument(arg, "--test-dir"_s)) {
2107 if (i >= args.size() - 1) {
2108 errormsg = "'--test-dir' requires an argument";
2112 this->Impl->TestDir = std::string(args[i]);
2113 } else if (this->CheckArgument(arg, "--output-junit"_s)) {
2114 if (i >= args.size() - 1) {
2115 errormsg = "'--output-junit' requires an argument";
2119 this->SetOutputJUnitFileName(std::string(args[i]));
2122 cm::string_view noTestsPrefix = "--no-tests=";
2123 if (cmHasPrefix(arg, noTestsPrefix)) {
2124 cm::string_view noTestsMode =
2125 cm::string_view(arg).substr(noTestsPrefix.length());
2126 if (noTestsMode == "error") {
2127 this->Impl->NoTestsMode = cmCTest::NoTests::Error;
2128 } else if (noTestsMode != "ignore") {
2130 cmStrCat("'--no-tests=' given unknown value '", noTestsMode, '\'');
2133 this->Impl->NoTestsMode = cmCTest::NoTests::Ignore;
2137 // options that control what tests are run
2138 else if (this->CheckArgument(arg, "-I"_s, "--tests-information") &&
2139 i < args.size() - 1) {
2141 this->GetTestHandler()->SetPersistentOption("TestsToRunInformation",
2143 this->GetMemCheckHandler()->SetPersistentOption("TestsToRunInformation",
2145 } else if (this->CheckArgument(arg, "-U"_s, "--union")) {
2146 this->GetTestHandler()->SetPersistentOption("UseUnion", "true");
2147 this->GetMemCheckHandler()->SetPersistentOption("UseUnion", "true");
2148 } else if (this->CheckArgument(arg, "-R"_s, "--tests-regex") &&
2149 i < args.size() - 1) {
2151 this->GetTestHandler()->SetPersistentOption("IncludeRegularExpression",
2153 this->GetMemCheckHandler()->SetPersistentOption("IncludeRegularExpression",
2155 } else if (this->CheckArgument(arg, "-L"_s, "--label-regex") &&
2156 i < args.size() - 1) {
2158 this->GetTestHandler()->AddPersistentMultiOption("LabelRegularExpression",
2160 this->GetMemCheckHandler()->AddPersistentMultiOption(
2161 "LabelRegularExpression", args[i]);
2162 } else if (this->CheckArgument(arg, "-LE"_s, "--label-exclude") &&
2163 i < args.size() - 1) {
2165 this->GetTestHandler()->AddPersistentMultiOption(
2166 "ExcludeLabelRegularExpression", args[i]);
2167 this->GetMemCheckHandler()->AddPersistentMultiOption(
2168 "ExcludeLabelRegularExpression", args[i]);
2171 else if (this->CheckArgument(arg, "-E"_s, "--exclude-regex") &&
2172 i < args.size() - 1) {
2174 this->GetTestHandler()->SetPersistentOption("ExcludeRegularExpression",
2176 this->GetMemCheckHandler()->SetPersistentOption("ExcludeRegularExpression",
2180 else if (this->CheckArgument(arg, "-FA"_s, "--fixture-exclude-any") &&
2181 i < args.size() - 1) {
2183 this->GetTestHandler()->SetPersistentOption(
2184 "ExcludeFixtureRegularExpression", args[i].c_str());
2185 this->GetMemCheckHandler()->SetPersistentOption(
2186 "ExcludeFixtureRegularExpression", args[i].c_str());
2187 } else if (this->CheckArgument(arg, "-FS"_s, "--fixture-exclude-setup") &&
2188 i < args.size() - 1) {
2190 this->GetTestHandler()->SetPersistentOption(
2191 "ExcludeFixtureSetupRegularExpression", args[i].c_str());
2192 this->GetMemCheckHandler()->SetPersistentOption(
2193 "ExcludeFixtureSetupRegularExpression", args[i].c_str());
2194 } else if (this->CheckArgument(arg, "-FC"_s, "--fixture-exclude-cleanup") &&
2195 i < args.size() - 1) {
2197 this->GetTestHandler()->SetPersistentOption(
2198 "ExcludeFixtureCleanupRegularExpression", args[i].c_str());
2199 this->GetMemCheckHandler()->SetPersistentOption(
2200 "ExcludeFixtureCleanupRegularExpression", args[i].c_str());
2203 else if (this->CheckArgument(arg, "--resource-spec-file"_s) &&
2204 i < args.size() - 1) {
2206 this->GetTestHandler()->SetPersistentOption("ResourceSpecFile",
2208 this->GetMemCheckHandler()->SetPersistentOption("ResourceSpecFile",
2212 else if (this->CheckArgument(arg, "--rerun-failed"_s)) {
2213 this->GetTestHandler()->SetPersistentOption("RerunFailed", "true");
2214 this->GetMemCheckHandler()->SetPersistentOption("RerunFailed", "true");
2219 #if !defined(_WIN32)
2220 bool cmCTest::ConsoleIsNotDumb()
2222 std::string term_env_variable;
2223 if (cmSystemTools::GetEnv("TERM", term_env_variable)) {
2224 return isatty(1) && term_env_variable != "dumb";
2230 bool cmCTest::ProgressOutputSupportedByConsole()
2233 // On Windows we need a console buffer.
2234 void* console = GetStdHandle(STD_OUTPUT_HANDLE);
2235 CONSOLE_SCREEN_BUFFER_INFO csbi;
2236 return GetConsoleScreenBufferInfo(console, &csbi);
2238 // On UNIX we need a non-dumb tty.
2239 return ConsoleIsNotDumb();
2243 bool cmCTest::ColoredOutputSupportedByConsole()
2246 // Not supported on Windows
2249 // On UNIX we need a non-dumb tty.
2250 std::string clicolor_force;
2251 if (cmSystemTools::GetEnv("CLICOLOR_FORCE", clicolor_force) &&
2252 !clicolor_force.empty() && clicolor_force != "0") {
2255 std::string clicolor;
2256 if (cmSystemTools::GetEnv("CLICOLOR", clicolor) && clicolor == "0") {
2259 return ConsoleIsNotDumb();
2263 // handle the -S -SR and -SP arguments
2264 void cmCTest::HandleScriptArguments(size_t& i, std::vector<std::string>& args,
2265 bool& SRArgumentSpecified)
2267 std::string arg = args[i];
2268 if (this->CheckArgument(arg, "-SP"_s, "--script-new-process") &&
2269 i < args.size() - 1) {
2270 this->Impl->RunConfigurationScript = true;
2272 cmCTestScriptHandler* ch = this->GetScriptHandler();
2273 // -SR is an internal argument, -SP should be ignored when it is passed
2274 if (!SRArgumentSpecified) {
2275 ch->AddConfigurationScript(args[i], false);
2279 if (this->CheckArgument(arg, "-SR"_s, "--script-run") &&
2280 i < args.size() - 1) {
2281 SRArgumentSpecified = true;
2282 this->Impl->RunConfigurationScript = true;
2284 cmCTestScriptHandler* ch = this->GetScriptHandler();
2285 ch->AddConfigurationScript(args[i], true);
2288 if (this->CheckArgument(arg, "-S"_s, "--script") && i < args.size() - 1) {
2289 this->Impl->RunConfigurationScript = true;
2291 cmCTestScriptHandler* ch = this->GetScriptHandler();
2292 // -SR is an internal argument, -S should be ignored when it is passed
2293 if (!SRArgumentSpecified) {
2294 ch->AddConfigurationScript(args[i], true);
2299 bool cmCTest::AddVariableDefinition(const std::string& arg)
2303 cmStateEnums::CacheEntryType type = cmStateEnums::UNINITIALIZED;
2305 if (cmake::ParseCacheEntry(arg, name, value, type)) {
2306 this->Impl->Definitions[name] = value;
2313 void cmCTest::SetPersistentOptionIfNotEmpty(const std::string& value,
2314 const std::string& optionName)
2316 if (!value.empty()) {
2317 this->GetTestHandler()->SetPersistentOption(optionName, value.c_str());
2318 this->GetMemCheckHandler()->SetPersistentOption(optionName, value.c_str());
2322 void cmCTest::AddPersistentMultiOptionIfNotEmpty(const std::string& value,
2323 const std::string& optionName)
2325 if (!value.empty()) {
2326 this->GetTestHandler()->AddPersistentMultiOption(optionName, value);
2327 this->GetMemCheckHandler()->AddPersistentMultiOption(optionName, value);
2331 bool cmCTest::SetArgsFromPreset(const std::string& presetName,
2334 const auto workingDirectory = cmSystemTools::GetCurrentWorkingDirectory();
2336 cmCMakePresetsGraph settingsFile;
2337 auto result = settingsFile.ReadProjectPresets(workingDirectory);
2338 if (result != cmCMakePresetsGraph::ReadFileResult::READ_OK) {
2339 cmSystemTools::Error(
2340 cmStrCat("Could not read presets from ", workingDirectory, ": ",
2341 cmCMakePresetsGraph::ResultToString(result)));
2346 settingsFile.PrintTestPresetList();
2350 auto presetPair = settingsFile.TestPresets.find(presetName);
2351 if (presetPair == settingsFile.TestPresets.end()) {
2352 cmSystemTools::Error(cmStrCat("No such test preset in ", workingDirectory,
2353 ": \"", presetName, '"'));
2354 settingsFile.PrintTestPresetList();
2358 if (presetPair->second.Unexpanded.Hidden) {
2359 cmSystemTools::Error(cmStrCat("Cannot use hidden test preset in ",
2360 workingDirectory, ": \"", presetName, '"'));
2361 settingsFile.PrintTestPresetList();
2365 auto const& expandedPreset = presetPair->second.Expanded;
2366 if (!expandedPreset) {
2367 cmSystemTools::Error(cmStrCat("Could not evaluate test preset \"",
2368 presetName, "\": Invalid macro expansion"));
2369 settingsFile.PrintTestPresetList();
2373 if (!expandedPreset->ConditionResult) {
2374 cmSystemTools::Error(cmStrCat("Cannot use disabled test preset in ",
2375 workingDirectory, ": \"", presetName, '"'));
2376 settingsFile.PrintTestPresetList();
2380 auto configurePresetPair =
2381 settingsFile.ConfigurePresets.find(expandedPreset->ConfigurePreset);
2382 if (configurePresetPair == settingsFile.ConfigurePresets.end()) {
2383 cmSystemTools::Error(cmStrCat("No such configure preset in ",
2384 workingDirectory, ": \"",
2385 expandedPreset->ConfigurePreset, '"'));
2386 settingsFile.PrintConfigurePresetList();
2390 if (configurePresetPair->second.Unexpanded.Hidden) {
2391 cmSystemTools::Error(cmStrCat("Cannot use hidden configure preset in ",
2392 workingDirectory, ": \"",
2393 expandedPreset->ConfigurePreset, '"'));
2394 settingsFile.PrintConfigurePresetList();
2398 auto const& expandedConfigurePreset = configurePresetPair->second.Expanded;
2399 if (!expandedConfigurePreset) {
2400 cmSystemTools::Error(cmStrCat("Could not evaluate configure preset \"",
2401 expandedPreset->ConfigurePreset,
2402 "\": Invalid macro expansion"));
2406 auto presetEnvironment = expandedPreset->Environment;
2407 for (auto const& var : presetEnvironment) {
2409 cmSystemTools::PutEnv(cmStrCat(var.first, '=', *var.second));
2413 if (!expandedPreset->Configuration.empty()) {
2414 this->SetConfigType(expandedPreset->Configuration);
2417 // Set build directory to value specified by the configure preset.
2418 this->AddCTestConfigurationOverwrite(
2419 cmStrCat("BuildDirectory=", expandedConfigurePreset->BinaryDir));
2420 for (const auto& kvp : expandedPreset->OverwriteConfigurationFile) {
2421 this->AddCTestConfigurationOverwrite(kvp);
2424 if (expandedPreset->Output) {
2425 this->Impl->TestProgressOutput =
2426 expandedPreset->Output->ShortProgress.value_or(false);
2428 if (expandedPreset->Output->Verbosity) {
2429 const auto& verbosity = *expandedPreset->Output->Verbosity;
2430 switch (verbosity) {
2431 case cmCMakePresetsGraph::TestPreset::OutputOptions::VerbosityEnum::
2433 this->Impl->ExtraVerbose = true;
2435 case cmCMakePresetsGraph::TestPreset::OutputOptions::VerbosityEnum::
2437 this->Impl->Verbose = true;
2439 case cmCMakePresetsGraph::TestPreset::OutputOptions::VerbosityEnum::
2442 // leave default settings
2447 this->Impl->Debug = expandedPreset->Output->Debug.value_or(false);
2448 this->Impl->ShowLineNumbers =
2449 expandedPreset->Output->Debug.value_or(false);
2450 this->Impl->OutputTestOutputOnTestFailure =
2451 expandedPreset->Output->OutputOnFailure.value_or(false);
2452 this->Impl->Quiet = expandedPreset->Output->Quiet.value_or(false);
2454 if (!expandedPreset->Output->OutputLogFile.empty()) {
2455 this->SetOutputLogFileName(expandedPreset->Output->OutputLogFile);
2457 if (!expandedPreset->Output->OutputJUnitFile.empty()) {
2458 this->SetOutputJUnitFileName(expandedPreset->Output->OutputJUnitFile);
2461 this->Impl->LabelSummary =
2462 expandedPreset->Output->LabelSummary.value_or(true);
2463 this->Impl->SubprojectSummary =
2464 expandedPreset->Output->SubprojectSummary.value_or(true);
2466 if (expandedPreset->Output->MaxPassedTestOutputSize) {
2467 this->Impl->TestHandler.SetTestOutputSizePassed(
2468 *expandedPreset->Output->MaxPassedTestOutputSize);
2471 if (expandedPreset->Output->MaxFailedTestOutputSize) {
2472 this->Impl->TestHandler.SetTestOutputSizeFailed(
2473 *expandedPreset->Output->MaxFailedTestOutputSize);
2476 if (expandedPreset->Output->TestOutputTruncation) {
2477 this->Impl->TestHandler.TestOutputTruncation =
2478 *expandedPreset->Output->TestOutputTruncation;
2481 if (expandedPreset->Output->MaxTestNameWidth) {
2482 this->Impl->MaxTestNameWidth = *expandedPreset->Output->MaxTestNameWidth;
2486 if (expandedPreset->Filter) {
2487 if (expandedPreset->Filter->Include) {
2488 this->SetPersistentOptionIfNotEmpty(
2489 expandedPreset->Filter->Include->Name, "IncludeRegularExpression");
2490 this->AddPersistentMultiOptionIfNotEmpty(
2491 expandedPreset->Filter->Include->Label, "LabelRegularExpression");
2493 if (expandedPreset->Filter->Include->Index) {
2494 if (expandedPreset->Filter->Include->Index->IndexFile.empty()) {
2495 const auto& start = expandedPreset->Filter->Include->Index->Start;
2496 const auto& end = expandedPreset->Filter->Include->Index->End;
2497 const auto& stride = expandedPreset->Filter->Include->Index->Stride;
2498 std::string indexOptions;
2499 indexOptions += (start ? std::to_string(*start) : "") + ",";
2500 indexOptions += (end ? std::to_string(*end) : "") + ",";
2501 indexOptions += (stride ? std::to_string(*stride) : "") + ",";
2503 cmJoin(expandedPreset->Filter->Include->Index->SpecificTests, ",");
2505 this->SetPersistentOptionIfNotEmpty(indexOptions,
2506 "TestsToRunInformation");
2508 this->SetPersistentOptionIfNotEmpty(
2509 expandedPreset->Filter->Include->Index->IndexFile,
2510 "TestsToRunInformation");
2514 if (expandedPreset->Filter->Include->UseUnion.value_or(false)) {
2515 this->GetTestHandler()->SetPersistentOption("UseUnion", "true");
2516 this->GetMemCheckHandler()->SetPersistentOption("UseUnion", "true");
2520 if (expandedPreset->Filter->Exclude) {
2521 this->SetPersistentOptionIfNotEmpty(
2522 expandedPreset->Filter->Exclude->Name, "ExcludeRegularExpression");
2523 this->AddPersistentMultiOptionIfNotEmpty(
2524 expandedPreset->Filter->Exclude->Label,
2525 "ExcludeLabelRegularExpression");
2527 if (expandedPreset->Filter->Exclude->Fixtures) {
2528 this->SetPersistentOptionIfNotEmpty(
2529 expandedPreset->Filter->Exclude->Fixtures->Any,
2530 "ExcludeFixtureRegularExpression");
2531 this->SetPersistentOptionIfNotEmpty(
2532 expandedPreset->Filter->Exclude->Fixtures->Setup,
2533 "ExcludeFixtureSetupRegularExpression");
2534 this->SetPersistentOptionIfNotEmpty(
2535 expandedPreset->Filter->Exclude->Fixtures->Cleanup,
2536 "ExcludeFixtureCleanupRegularExpression");
2541 if (expandedPreset->Execution) {
2542 this->Impl->StopOnFailure =
2543 expandedPreset->Execution->StopOnFailure.value_or(false);
2544 this->Impl->Failover =
2545 expandedPreset->Execution->EnableFailover.value_or(false);
2547 if (expandedPreset->Execution->Jobs) {
2548 auto jobs = *expandedPreset->Execution->Jobs;
2549 this->SetParallelLevel(jobs);
2550 this->Impl->ParallelLevelSetInCli = true;
2553 this->SetPersistentOptionIfNotEmpty(
2554 expandedPreset->Execution->ResourceSpecFile, "ResourceSpecFile");
2556 if (expandedPreset->Execution->TestLoad) {
2557 auto testLoad = *expandedPreset->Execution->TestLoad;
2558 this->SetTestLoad(testLoad);
2561 if (expandedPreset->Execution->ShowOnly) {
2562 this->Impl->ShowOnly = true;
2564 switch (*expandedPreset->Execution->ShowOnly) {
2565 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::ShowOnlyEnum::
2567 this->Impl->Quiet = true;
2568 this->Impl->OutputAsJson = true;
2569 this->Impl->OutputAsJsonVersion = 1;
2571 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::ShowOnlyEnum::
2573 // intentional fallthrough (human is the default)
2579 if (expandedPreset->Execution->Repeat) {
2580 this->Impl->RepeatCount = expandedPreset->Execution->Repeat->Count;
2581 switch (expandedPreset->Execution->Repeat->Mode) {
2582 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::RepeatOptions::
2583 ModeEnum::UntilFail:
2584 this->Impl->RepeatMode = cmCTest::Repeat::UntilFail;
2586 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::RepeatOptions::
2587 ModeEnum::UntilPass:
2588 this->Impl->RepeatMode = cmCTest::Repeat::UntilPass;
2590 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::RepeatOptions::
2591 ModeEnum::AfterTimeout:
2592 this->Impl->RepeatMode = cmCTest::Repeat::AfterTimeout;
2595 // should never default since mode is required
2600 if (expandedPreset->Execution->InteractiveDebugging) {
2601 this->Impl->InteractiveDebugMode =
2602 *expandedPreset->Execution->InteractiveDebugging;
2605 if (expandedPreset->Execution->ScheduleRandom.value_or(false)) {
2606 this->Impl->ScheduleType = "Random";
2609 if (expandedPreset->Execution->Timeout) {
2610 this->Impl->GlobalTimeout =
2611 cmDuration(*expandedPreset->Execution->Timeout);
2614 if (expandedPreset->Execution->NoTestsAction) {
2615 switch (*expandedPreset->Execution->NoTestsAction) {
2616 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::
2617 NoTestsActionEnum::Error:
2618 this->Impl->NoTestsMode = cmCTest::NoTests::Error;
2620 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::
2621 NoTestsActionEnum::Ignore:
2622 this->Impl->NoTestsMode = cmCTest::NoTests::Ignore;
2624 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::
2625 NoTestsActionEnum::Default:
2628 // should never default
2637 // the main entry point of ctest, called from main
2638 int cmCTest::Run(std::vector<std::string>& args, std::string* output)
2640 const char* ctestExec = "ctest";
2641 bool cmakeAndTest = false;
2642 bool executeTests = true;
2643 bool SRArgumentSpecified = false;
2645 // copy the command line
2646 cm::append(this->Impl->InitialCommandLineArguments, args);
2648 // check if a test preset was specified
2651 find(args.begin(), args.end(), "--list-presets") != args.end();
2653 std::find_if(args.begin(), args.end(), [](std::string const& arg) -> bool {
2654 return arg == "--preset" || cmHasLiteralPrefix(arg, "--preset=");
2656 if (listPresets || it != args.end()) {
2657 std::string errormsg;
2661 // If listing presets we don't need a presetName
2662 success = this->SetArgsFromPreset("", listPresets);
2664 if (cmHasLiteralPrefix(*it, "--preset=")) {
2665 auto presetName = it->substr(9);
2666 success = this->SetArgsFromPreset(presetName, listPresets);
2667 } else if (++it != args.end()) {
2668 auto presetName = *it;
2669 success = this->SetArgsFromPreset(presetName, listPresets);
2671 cmSystemTools::Error("'--preset' requires an argument");
2677 return success ? 0 : 1;
2685 // process the command line arguments
2686 for (size_t i = 1; i < args.size(); ++i) {
2687 // handle the simple commandline arguments
2688 std::string errormsg;
2689 if (!this->HandleCommandLineArguments(i, args, errormsg)) {
2690 cmSystemTools::Error(errormsg);
2694 // handle the script arguments -S -SR -SP
2695 this->HandleScriptArguments(i, args, SRArgumentSpecified);
2697 // --dashboard: handle a request for a dashboard
2698 std::string arg = args[i];
2699 if (this->CheckArgument(arg, "-D"_s, "--dashboard") &&
2700 i < args.size() - 1) {
2701 this->Impl->ProduceXML = true;
2703 std::string targ = args[i];
2704 // AddTestsForDashboard parses the dashboard type and converts it
2705 // into the separate stages
2706 if (!this->AddTestsForDashboardType(targ)) {
2707 if (!this->AddVariableDefinition(targ)) {
2708 this->ErrorMessageUnknownDashDValue(targ);
2709 executeTests = false;
2714 // If it's not exactly -D, but it starts with -D, then try to parse out
2715 // a variable definition from it, same as CMake does. Unsuccessful
2716 // attempts are simply ignored since previous ctest versions ignore
2717 // this too. (As well as many other unknown command line args.)
2719 if (arg != "-D" && cmHasLiteralPrefix(arg, "-D")) {
2720 std::string input = arg.substr(2);
2721 this->AddVariableDefinition(input);
2724 // --test-action: calls SetTest(<stage>, /*report=*/ false) to enable
2725 // the corresponding stage
2726 if (!this->HandleTestActionArgument(ctestExec, i, args)) {
2727 executeTests = false;
2730 // --test-model: what type of test model
2731 if (!this->HandleTestModelArgument(ctestExec, i, args)) {
2732 executeTests = false;
2736 if (this->CheckArgument(arg, "--extra-submit"_s) && i < args.size() - 1) {
2737 this->Impl->ProduceXML = true;
2738 this->SetTest("Submit");
2740 if (!this->SubmitExtraFiles(args[i])) {
2745 // --build-and-test options
2746 if (this->CheckArgument(arg, "--build-and-test"_s) &&
2747 i < args.size() - 1) {
2748 cmakeAndTest = true;
2751 // --schedule-random
2752 if (this->CheckArgument(arg, "--schedule-random"_s)) {
2753 this->Impl->ScheduleType = "Random";
2756 // pass the argument to all the handlers as well, but it may no longer be
2757 // set to what it was originally so I'm not sure this is working as
2759 for (auto& handler : this->Impl->GetTestingHandlers()) {
2760 if (!handler->ProcessCommandLineArguments(arg, i, args)) {
2761 cmCTestLog(this, ERROR_MESSAGE,
2762 "Problem parsing command line arguments within a handler");
2766 } // the close of the for argument loop
2768 // handle CTEST_PARALLEL_LEVEL environment variable
2769 if (!this->Impl->ParallelLevelSetInCli) {
2770 std::string parallel;
2771 if (cmSystemTools::GetEnv("CTEST_PARALLEL_LEVEL", parallel)) {
2772 int plevel = atoi(parallel.c_str());
2773 this->SetParallelLevel(plevel);
2777 // TestProgressOutput only supported if console supports it and not logging
2779 this->Impl->TestProgressOutput = this->Impl->TestProgressOutput &&
2780 !this->Impl->OutputLogFile && this->ProgressOutputSupportedByConsole();
2782 if (this->Impl->TestProgressOutput) {
2783 // Disable output line buffering so we can print content without
2785 std::setvbuf(stdout, nullptr, _IONBF, 0);
2789 // now what should cmake do? if --build-and-test was specified then
2790 // we run the build and test handler and return
2792 return this->RunCMakeAndTest(output);
2796 return this->ExecuteTests();
2802 bool cmCTest::HandleTestActionArgument(const char* ctestExec, size_t& i,
2803 const std::vector<std::string>& args)
2805 bool success = true;
2806 std::string const& arg = args[i];
2807 if (this->CheckArgument(arg, "-T"_s, "--test-action") &&
2808 (i < args.size() - 1)) {
2809 this->Impl->ProduceXML = true;
2811 if (!this->SetTest(args[i], false)) {
2813 cmCTestLog(this, ERROR_MESSAGE,
2814 "CTest -T called with incorrect option: " << args[i]
2816 cmCTestLog(this, ERROR_MESSAGE,
2817 "Available options are:"
2819 << " " << ctestExec << " -T all" << std::endl
2820 << " " << ctestExec << " -T start" << std::endl
2821 << " " << ctestExec << " -T update" << std::endl
2822 << " " << ctestExec << " -T configure" << std::endl
2823 << " " << ctestExec << " -T build" << std::endl
2824 << " " << ctestExec << " -T test" << std::endl
2825 << " " << ctestExec << " -T coverage" << std::endl
2826 << " " << ctestExec << " -T memcheck" << std::endl
2827 << " " << ctestExec << " -T notes" << std::endl
2828 << " " << ctestExec << " -T submit" << std::endl);
2834 bool cmCTest::HandleTestModelArgument(const char* ctestExec, size_t& i,
2835 const std::vector<std::string>& args)
2837 bool success = true;
2838 std::string const& arg = args[i];
2839 if (this->CheckArgument(arg, "-M"_s, "--test-model") &&
2840 (i < args.size() - 1)) {
2842 std::string const& str = args[i];
2843 if (cmSystemTools::LowerCase(str) == "nightly"_s) {
2844 this->SetTestModel(cmCTest::NIGHTLY);
2845 } else if (cmSystemTools::LowerCase(str) == "continuous"_s) {
2846 this->SetTestModel(cmCTest::CONTINUOUS);
2847 } else if (cmSystemTools::LowerCase(str) == "experimental"_s) {
2848 this->SetTestModel(cmCTest::EXPERIMENTAL);
2851 cmCTestLog(this, ERROR_MESSAGE,
2852 "CTest -M called with incorrect option: " << str
2854 cmCTestLog(this, ERROR_MESSAGE,
2855 "Available options are:"
2857 << " " << ctestExec << " -M Continuous" << std::endl
2858 << " " << ctestExec << " -M Experimental" << std::endl
2859 << " " << ctestExec << " -M Nightly" << std::endl);
2865 int cmCTest::ExecuteTests()
2868 // call process directory
2869 if (this->Impl->RunConfigurationScript) {
2870 if (this->Impl->ExtraVerbose) {
2871 cmCTestLog(this, OUTPUT, "* Extra verbosity turned on" << std::endl);
2873 for (auto& handler : this->Impl->GetTestingHandlers()) {
2874 handler->SetVerbose(this->Impl->ExtraVerbose);
2875 handler->SetSubmitIndex(this->Impl->SubmitIndex);
2877 this->GetScriptHandler()->SetVerbose(this->Impl->Verbose);
2878 res = this->GetScriptHandler()->ProcessHandler();
2880 cmCTestLog(this, DEBUG,
2881 "running script failing returning: " << res << std::endl);
2885 // What is this? -V seems to be the same as -VV,
2886 // and Verbose is always on in this case
2887 this->Impl->ExtraVerbose = this->Impl->Verbose;
2888 this->Impl->Verbose = true;
2889 for (auto& handler : this->Impl->GetTestingHandlers()) {
2890 handler->SetVerbose(this->Impl->Verbose);
2891 handler->SetSubmitIndex(this->Impl->SubmitIndex);
2894 const std::string currDir = cmSystemTools::GetCurrentWorkingDirectory();
2895 std::string workDir = currDir;
2896 if (!this->Impl->TestDir.empty()) {
2897 workDir = cmSystemTools::CollapseFullPath(this->Impl->TestDir);
2900 if (currDir != workDir) {
2901 if (!this->TryToChangeDirectory(workDir)) {
2906 if (!this->Initialize(workDir.c_str(), nullptr)) {
2908 cmCTestLog(this, ERROR_MESSAGE,
2909 "Problem initializing the dashboard." << std::endl);
2911 res = this->ProcessSteps();
2915 if (currDir != workDir) {
2916 cmSystemTools::ChangeDirectory(currDir);
2920 cmCTestLog(this, DEBUG,
2921 "Running a test(s) failed returning : " << res << std::endl);
2926 int cmCTest::RunCMakeAndTest(std::string* output)
2928 this->Impl->Verbose = true;
2929 cmCTestBuildAndTestHandler* handler = this->GetBuildAndTestHandler();
2930 int retv = handler->ProcessHandler();
2931 *output = handler->GetOutput();
2932 #ifndef CMAKE_BOOTSTRAP
2933 cmDynamicLoader::FlushCache();
2936 cmCTestLog(this, DEBUG,
2937 "build and test failing returning: " << retv << std::endl);
2942 void cmCTest::SetNotesFiles(const std::string& notes)
2944 this->Impl->NotesFiles = notes;
2947 bool cmCTest::GetStopOnFailure() const
2949 return this->Impl->StopOnFailure;
2952 void cmCTest::SetStopOnFailure(bool stop)
2954 this->Impl->StopOnFailure = stop;
2957 std::chrono::system_clock::time_point cmCTest::GetStopTime() const
2959 return this->Impl->StopTime;
2962 void cmCTest::SetStopTime(std::string const& time_str)
2966 time_t current_time = time(nullptr);
2967 lctime = gmtime(¤t_time);
2968 int gm_hour = lctime->tm_hour;
2969 time_t gm_time = mktime(lctime);
2970 lctime = localtime(¤t_time);
2971 int local_hour = lctime->tm_hour;
2973 int tzone_offset = local_hour - gm_hour;
2974 if (gm_time > current_time && gm_hour < local_hour) {
2975 // this means gm_time is on the next day
2977 } else if (gm_time < current_time && gm_hour > local_hour) {
2978 // this means gm_time is on the previous day
2982 tzone_offset *= 100;
2984 snprintf(buf, sizeof(buf), "%d%02d%02d %s %+05i", lctime->tm_year + 1900,
2985 lctime->tm_mon + 1, lctime->tm_mday, time_str.c_str(),
2988 time_t stop_time = curl_getdate(buf, ¤t_time);
2989 if (stop_time == -1) {
2990 this->Impl->StopTime = std::chrono::system_clock::time_point();
2993 this->Impl->StopTime = std::chrono::system_clock::from_time_t(stop_time);
2995 if (stop_time < current_time) {
2996 this->Impl->StopTime += std::chrono::hours(24);
3000 std::string cmCTest::GetScheduleType() const
3002 return this->Impl->ScheduleType;
3005 void cmCTest::SetScheduleType(std::string const& type)
3007 this->Impl->ScheduleType = type;
3010 int cmCTest::ReadCustomConfigurationFileTree(const std::string& dir,
3014 cmCTestLog(this, DEBUG,
3015 "* Read custom CTest configuration directory: " << dir
3018 std::string fname = cmStrCat(dir, "/CTestCustom.cmake");
3019 cmCTestLog(this, DEBUG, "* Check for file: " << fname << std::endl);
3020 if (cmSystemTools::FileExists(fname)) {
3021 cmCTestLog(this, DEBUG,
3022 "* Read custom CTest configuration file: " << fname
3024 bool erroroc = cmSystemTools::GetErrorOccurredFlag();
3025 cmSystemTools::ResetErrorOccurredFlag();
3027 if (!mf->ReadListFile(fname) || cmSystemTools::GetErrorOccurredFlag()) {
3028 cmCTestLog(this, ERROR_MESSAGE,
3029 "Problem reading custom configuration: " << fname
3034 cmSystemTools::SetErrorOccurred();
3038 std::string rexpr = cmStrCat(dir, "/CTestCustom.ctest");
3039 cmCTestLog(this, DEBUG, "* Check for file: " << rexpr << std::endl);
3040 if (!found && cmSystemTools::FileExists(rexpr)) {
3043 gl.FindFiles(rexpr);
3044 std::vector<std::string>& files = gl.GetFiles();
3045 for (const std::string& file : files) {
3046 cmCTestLog(this, DEBUG,
3047 "* Read custom CTest configuration file: " << file
3049 if (!mf->ReadListFile(file) || cmSystemTools::GetErrorOccurredFlag()) {
3050 cmCTestLog(this, ERROR_MESSAGE,
3051 "Problem reading custom configuration: " << file
3059 for (auto& handler : this->Impl->GetNamedTestingHandlers()) {
3060 cmCTestLog(this, DEBUG,
3061 "* Read custom CTest configuration vectors for handler: "
3062 << handler.first << " (" << handler.second << ")"
3064 handler.second->PopulateCustomVectors(mf);
3071 void cmCTest::PopulateCustomVector(cmMakefile* mf, const std::string& def,
3072 std::vector<std::string>& vec)
3074 cmValue dval = mf->GetDefinition(def);
3078 cmCTestLog(this, DEBUG, "PopulateCustomVector: " << def << std::endl);
3081 cmExpandList(*dval, vec);
3083 for (std::string const& it : vec) {
3084 cmCTestLog(this, DEBUG, " -- " << it << std::endl);
3088 void cmCTest::PopulateCustomInteger(cmMakefile* mf, const std::string& def,
3091 cmValue dval = mf->GetDefinition(def);
3095 val = atoi(dval->c_str());
3098 std::string cmCTest::GetShortPathToFile(const std::string& cfname)
3100 const std::string& sourceDir = cmSystemTools::CollapseFullPath(
3101 this->GetCTestConfiguration("SourceDirectory"));
3102 const std::string& buildDir = cmSystemTools::CollapseFullPath(
3103 this->GetCTestConfiguration("BuildDirectory"));
3104 std::string fname = cmSystemTools::CollapseFullPath(cfname);
3106 // Find relative paths to both directories
3107 std::string srcRelpath = cmSystemTools::RelativePath(sourceDir, fname);
3108 std::string bldRelpath = cmSystemTools::RelativePath(buildDir, fname);
3110 // If any contains "." it is not parent directory
3111 bool inSrc = srcRelpath.find("..") == std::string::npos;
3112 bool inBld = bldRelpath.find("..") == std::string::npos;
3113 // TODO: Handle files with .. in their name
3115 std::string* res = nullptr;
3117 if (inSrc && inBld) {
3118 // If both have relative path with no dots, pick the shorter one
3119 if (srcRelpath.size() < bldRelpath.size()) {
3135 cmSystemTools::ConvertToUnixSlashes(*res);
3138 if (path.back() == '/') {
3139 path.resize(path.size() - 1);
3143 cmsys::SystemTools::ReplaceString(path, ":", "_");
3144 cmsys::SystemTools::ReplaceString(path, " ", "_");
3148 std::string cmCTest::GetCTestConfiguration(const std::string& name)
3150 if (this->Impl->CTestConfigurationOverwrites.find(name) !=
3151 this->Impl->CTestConfigurationOverwrites.end()) {
3152 return this->Impl->CTestConfigurationOverwrites[name];
3154 return this->Impl->CTestConfiguration[name];
3157 void cmCTest::EmptyCTestConfiguration()
3159 this->Impl->CTestConfiguration.clear();
3162 void cmCTest::SetCTestConfiguration(const char* name, const std::string& value,
3165 cmCTestOptionalLog(this, HANDLER_VERBOSE_OUTPUT,
3166 "SetCTestConfiguration:" << name << ":" << value << "\n",
3172 if (value.empty()) {
3173 this->Impl->CTestConfiguration.erase(name);
3176 this->Impl->CTestConfiguration[name] = value;
3179 std::string cmCTest::GetSubmitURL()
3181 std::string url = this->GetCTestConfiguration("SubmitURL");
3183 std::string method = this->GetCTestConfiguration("DropMethod");
3184 std::string user = this->GetCTestConfiguration("DropSiteUser");
3185 std::string password = this->GetCTestConfiguration("DropSitePassword");
3186 std::string site = this->GetCTestConfiguration("DropSite");
3187 std::string location = this->GetCTestConfiguration("DropLocation");
3189 url = cmStrCat(method.empty() ? "http" : method, "://"_s);
3190 if (!user.empty()) {
3192 if (!password.empty()) {
3204 std::string cmCTest::GetCurrentTag()
3206 return this->Impl->CurrentTag;
3209 std::string cmCTest::GetBinaryDir()
3211 return this->Impl->BinaryDir;
3214 std::string const& cmCTest::GetConfigType()
3216 return this->Impl->ConfigType;
3219 cmDuration cmCTest::GetTimeOut() const
3221 return this->Impl->TimeOut;
3224 void cmCTest::SetTimeOut(cmDuration t)
3226 this->Impl->TimeOut = t;
3229 cmDuration cmCTest::GetGlobalTimeout() const
3231 return this->Impl->GlobalTimeout;
3234 bool cmCTest::GetShowOnly()
3236 return this->Impl->ShowOnly;
3239 bool cmCTest::GetOutputAsJson()
3241 return this->Impl->OutputAsJson;
3244 int cmCTest::GetOutputAsJsonVersion()
3246 return this->Impl->OutputAsJsonVersion;
3249 bool cmCTest::ShouldUseHTTP10() const
3251 return this->Impl->UseHTTP10;
3254 bool cmCTest::ShouldPrintLabels() const
3256 return this->Impl->PrintLabels;
3259 int cmCTest::GetMaxTestNameWidth() const
3261 return this->Impl->MaxTestNameWidth;
3264 void cmCTest::SetMaxTestNameWidth(int w)
3266 this->Impl->MaxTestNameWidth = w;
3269 void cmCTest::SetProduceXML(bool v)
3271 this->Impl->ProduceXML = v;
3274 bool cmCTest::GetProduceXML()
3276 return this->Impl->ProduceXML;
3279 std::vector<std::string>& cmCTest::GetInitialCommandLineArguments()
3281 return this->Impl->InitialCommandLineArguments;
3284 const char* cmCTest::GetSpecificGroup()
3286 if (this->Impl->SpecificGroup.empty()) {
3289 return this->Impl->SpecificGroup.c_str();
3292 void cmCTest::SetSpecificGroup(const char* group)
3295 this->Impl->SpecificGroup.clear();
3298 this->Impl->SpecificGroup = group;
3301 void cmCTest::SetFailover(bool failover)
3303 this->Impl->Failover = failover;
3306 bool cmCTest::GetFailover() const
3308 return this->Impl->Failover;
3311 bool cmCTest::GetTestProgressOutput() const
3313 return this->Impl->TestProgressOutput && !GetExtraVerbose();
3316 bool cmCTest::GetVerbose() const
3318 return this->Impl->Verbose;
3321 bool cmCTest::GetExtraVerbose() const
3323 return this->Impl->ExtraVerbose;
3326 void cmCTest::SetStreams(std::ostream* out, std::ostream* err)
3328 this->Impl->StreamOut = out;
3329 this->Impl->StreamErr = err;
3332 bool cmCTest::GetLabelSummary() const
3334 return this->Impl->LabelSummary;
3337 bool cmCTest::GetSubprojectSummary() const
3339 return this->Impl->SubprojectSummary;
3342 bool cmCTest::GetOutputTestOutputOnTestFailure() const
3344 return this->Impl->OutputTestOutputOnTestFailure;
3347 const std::map<std::string, std::string>& cmCTest::GetDefinitions() const
3349 return this->Impl->Definitions;
3352 int cmCTest::GetRepeatCount() const
3354 return this->Impl->RepeatCount;
3357 cmCTest::Repeat cmCTest::GetRepeatMode() const
3359 return this->Impl->RepeatMode;
3362 cmCTest::NoTests cmCTest::GetNoTestsMode() const
3364 return this->Impl->NoTestsMode;
3367 void cmCTest::SetBuildID(const std::string& id)
3369 this->Impl->BuildID = id;
3372 std::string cmCTest::GetBuildID() const
3374 return this->Impl->BuildID;
3377 void cmCTest::AddSubmitFile(Part part, const std::string& name)
3379 this->Impl->Parts[part].SubmitFiles.emplace_back(name);
3382 std::vector<std::string> const& cmCTest::GetSubmitFiles(Part part) const
3384 return this->Impl->Parts[part].SubmitFiles;
3387 void cmCTest::ClearSubmitFiles(Part part)
3389 this->Impl->Parts[part].SubmitFiles.clear();
3392 void cmCTest::SetSuppressUpdatingCTestConfiguration(bool val)
3394 this->Impl->SuppressUpdatingCTestConfiguration = val;
3397 void cmCTest::AddCTestConfigurationOverwrite(const std::string& overStr)
3399 size_t epos = overStr.find('=');
3400 if (epos == std::string::npos) {
3401 cmCTestLog(this, ERROR_MESSAGE,
3402 "CTest configuration overwrite specified in the wrong format."
3404 << "Valid format is: --overwrite key=value" << std::endl
3405 << "The specified was: --overwrite " << overStr << std::endl);
3408 std::string key = overStr.substr(0, epos);
3409 std::string value = overStr.substr(epos + 1);
3410 this->Impl->CTestConfigurationOverwrites[key] = value;
3413 void cmCTest::SetConfigType(const std::string& ct)
3415 this->Impl->ConfigType = ct;
3416 cmSystemTools::ReplaceString(this->Impl->ConfigType, ".\\", "");
3417 std::string confTypeEnv = "CMAKE_CONFIG_TYPE=" + this->Impl->ConfigType;
3418 cmSystemTools::PutEnv(confTypeEnv);
3421 bool cmCTest::SetCTestConfigurationFromCMakeVariable(
3422 cmMakefile* mf, const char* dconfig, const std::string& cmake_var,
3425 cmValue ctvar = mf->GetDefinition(cmake_var);
3429 cmCTestOptionalLog(this, HANDLER_VERBOSE_OUTPUT,
3430 "SetCTestConfigurationFromCMakeVariable:"
3431 << dconfig << ":" << cmake_var << std::endl,
3433 this->SetCTestConfiguration(dconfig, *ctvar, suppress);
3437 bool cmCTest::RunCommand(std::vector<std::string> const& args,
3438 std::string* stdOut, std::string* stdErr, int* retVal,
3439 const char* dir, cmDuration timeout,
3442 std::vector<const char*> argv;
3443 argv.reserve(args.size() + 1);
3444 for (std::string const& a : args) {
3445 argv.push_back(a.c_str());
3447 argv.push_back(nullptr);
3452 cmsysProcess* cp = cmsysProcess_New();
3453 cmsysProcess_SetCommand(cp, argv.data());
3454 cmsysProcess_SetWorkingDirectory(cp, dir);
3455 if (cmSystemTools::GetRunCommandHideConsole()) {
3456 cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
3458 cmsysProcess_SetTimeout(cp, timeout.count());
3459 cmsysProcess_Execute(cp);
3461 std::vector<char> tempOutput;
3462 std::vector<char> tempError;
3465 cmProcessOutput processOutput(encoding);
3466 std::string strdata;
3470 res = cmsysProcess_WaitForData(cp, &data, &length, nullptr);
3472 case cmsysProcess_Pipe_STDOUT:
3473 cm::append(tempOutput, data, data + length);
3475 case cmsysProcess_Pipe_STDERR:
3476 cm::append(tempError, data, data + length);
3481 if ((res == cmsysProcess_Pipe_STDOUT || res == cmsysProcess_Pipe_STDERR) &&
3482 this->Impl->ExtraVerbose) {
3483 processOutput.DecodeText(data, length, strdata);
3484 cmSystemTools::Stdout(strdata);
3487 if (this->Impl->ExtraVerbose) {
3488 processOutput.DecodeText(std::string(), strdata);
3489 if (!strdata.empty()) {
3490 cmSystemTools::Stdout(strdata);
3494 cmsysProcess_WaitForExit(cp, nullptr);
3495 if (!tempOutput.empty()) {
3496 processOutput.DecodeText(tempOutput, tempOutput);
3497 stdOut->append(tempOutput.data(), tempOutput.size());
3499 if (!tempError.empty()) {
3500 processOutput.DecodeText(tempError, tempError);
3501 stdErr->append(tempError.data(), tempError.size());
3505 if (cmsysProcess_GetState(cp) == cmsysProcess_State_Exited) {
3507 *retVal = cmsysProcess_GetExitValue(cp);
3509 if (cmsysProcess_GetExitValue(cp) != 0) {
3513 } else if (cmsysProcess_GetState(cp) == cmsysProcess_State_Exception) {
3514 const char* exception_str = cmsysProcess_GetExceptionString(cp);
3515 cmCTestLog(this, ERROR_MESSAGE, exception_str << std::endl);
3516 stdErr->append(exception_str, strlen(exception_str));
3518 } else if (cmsysProcess_GetState(cp) == cmsysProcess_State_Error) {
3519 const char* error_str = cmsysProcess_GetErrorString(cp);
3520 cmCTestLog(this, ERROR_MESSAGE, error_str << std::endl);
3521 stdErr->append(error_str, strlen(error_str));
3523 } else if (cmsysProcess_GetState(cp) == cmsysProcess_State_Expired) {
3524 const char* error_str = "Process terminated due to timeout\n";
3525 cmCTestLog(this, ERROR_MESSAGE, error_str << std::endl);
3526 stdErr->append(error_str, strlen(error_str));
3530 cmsysProcess_Delete(cp);
3534 void cmCTest::SetOutputLogFileName(const std::string& name)
3536 if (!name.empty()) {
3537 this->Impl->OutputLogFile = cm::make_unique<cmGeneratedFileStream>(name);
3539 this->Impl->OutputLogFile.reset();
3543 void cmCTest::SetOutputJUnitFileName(const std::string& name)
3545 this->Impl->TestHandler.SetJUnitXMLFileName(name);
3546 // Turn test output compression off.
3547 // This makes it easier to include test output in the resulting
3548 // JUnit XML report.
3549 this->Impl->CompressTestOutput = false;
3552 static const char* cmCTestStringLogType[] = { "DEBUG",
3555 "HANDLER_PROGRESS_OUTPUT",
3556 "HANDLER_TEST_PROGRESS_OUTPUT",
3557 "HANDLER_VERBOSE_OUTPUT",
3562 #define cmCTestLogOutputFileLine(stream) \
3564 if (this->Impl->ShowLineNumbers) { \
3565 (stream) << std::endl << file << ":" << line << " "; \
3569 void cmCTest::Log(int logType, const char* file, int line, const char* msg,
3572 if (!msg || !*msg) {
3575 if (suppress && logType != cmCTest::ERROR_MESSAGE) {
3578 if (logType == cmCTest::HANDLER_PROGRESS_OUTPUT &&
3579 (this->Impl->Debug || this->Impl->ExtraVerbose)) {
3582 if (this->Impl->OutputLogFile) {
3583 bool display = true;
3584 if (logType == cmCTest::DEBUG && !this->Impl->Debug) {
3587 if (logType == cmCTest::HANDLER_VERBOSE_OUTPUT && !this->Impl->Debug &&
3588 !this->Impl->ExtraVerbose) {
3592 cmCTestLogOutputFileLine(*this->Impl->OutputLogFile);
3593 if (logType != this->Impl->OutputLogFileLastTag) {
3594 *this->Impl->OutputLogFile << "[";
3595 if (logType >= OTHER || logType < 0) {
3596 *this->Impl->OutputLogFile << "OTHER";
3598 *this->Impl->OutputLogFile << cmCTestStringLogType[logType];
3600 *this->Impl->OutputLogFile << "] " << std::endl;
3602 *this->Impl->OutputLogFile << msg << std::flush;
3603 if (logType != this->Impl->OutputLogFileLastTag) {
3604 *this->Impl->OutputLogFile << std::endl;
3605 this->Impl->OutputLogFileLastTag = logType;
3609 if (!this->Impl->Quiet) {
3610 std::ostream& out = *this->Impl->StreamOut;
3611 std::ostream& err = *this->Impl->StreamErr;
3613 if (logType == HANDLER_TEST_PROGRESS_OUTPUT) {
3614 if (this->Impl->TestProgressOutput) {
3615 cmCTestLogOutputFileLine(out);
3616 if (this->Impl->FlushTestProgressLine) {
3618 this->Impl->FlushTestProgressLine = false;
3622 std::string msg_str{ msg };
3623 auto const lineBreakIt = msg_str.find('\n');
3624 if (lineBreakIt != std::string::npos) {
3625 this->Impl->FlushTestProgressLine = true;
3626 msg_str.erase(std::remove(msg_str.begin(), msg_str.end(), '\n'),
3632 printf("\x1B[K"); // move caret to end
3637 logType = HANDLER_OUTPUT;
3642 if (this->Impl->Debug) {
3643 cmCTestLogOutputFileLine(out);
3649 case HANDLER_OUTPUT:
3650 if (this->Impl->Debug || this->Impl->Verbose) {
3651 cmCTestLogOutputFileLine(out);
3656 case HANDLER_VERBOSE_OUTPUT:
3657 if (this->Impl->Debug || this->Impl->ExtraVerbose) {
3658 cmCTestLogOutputFileLine(out);
3664 cmCTestLogOutputFileLine(err);
3669 cmCTestLogOutputFileLine(err);
3672 cmSystemTools::SetErrorOccurred();
3675 cmCTestLogOutputFileLine(out);
3682 std::string cmCTest::GetColorCode(Color color) const
3684 if (this->Impl->OutputColorCode) {
3686 // Not supported on Windows
3687 static_cast<void>(color);
3689 return "\033[0;" + std::to_string(static_cast<int>(color)) + "m";
3696 cmDuration cmCTest::GetRemainingTimeAllowed()
3698 return this->GetScriptHandler()->GetRemainingTimeAllowed();
3701 cmDuration cmCTest::MaxDuration()
3703 return cmDuration(1.0e7);
3706 void cmCTest::SetRunCurrentScript(bool value)
3708 this->GetScriptHandler()->SetRunCurrentScript(value);
3711 void cmCTest::OutputTestErrors(std::vector<char> const& process_output)
3713 std::string test_outputs("\n*** Test Failed:\n");
3714 if (!process_output.empty()) {
3715 test_outputs.append(process_output.data(), process_output.size());
3717 cmCTestLog(this, HANDLER_OUTPUT, test_outputs << std::endl);
3720 bool cmCTest::CompressString(std::string& str)
3725 strm.zalloc = Z_NULL;
3726 strm.zfree = Z_NULL;
3727 strm.opaque = Z_NULL;
3728 ret = deflateInit(&strm, -1); // default compression level
3734 reinterpret_cast<unsigned char*>(const_cast<char*>(str.c_str()));
3735 // zlib makes the guarantee that this is the maximum output size
3737 static_cast<int>(static_cast<double>(str.size()) * 1.001 + 13.0);
3738 std::vector<unsigned char> out(outSize);
3740 strm.avail_in = static_cast<uInt>(str.size());
3742 strm.avail_out = outSize;
3743 strm.next_out = out.data();
3744 ret = deflate(&strm, Z_FINISH);
3746 if (ret != Z_STREAM_END) {
3747 cmCTestLog(this, ERROR_MESSAGE,
3748 "Error during gzip compression." << std::endl);
3752 (void)deflateEnd(&strm);
3754 // Now base64 encode the resulting binary string
3755 std::vector<unsigned char> base64EncodedBuffer((outSize * 3) / 2);
3757 size_t rlen = cmsysBase64_Encode(out.data(), strm.total_out,
3758 base64EncodedBuffer.data(), 1);
3760 str.assign(reinterpret_cast<char*>(base64EncodedBuffer.data()), rlen);