Merge topic 'curl-tls-verify'
[kiteware-cmake.git] / Source / cmCTest.cxx
blob8f2eb850d06bdf88bd885da7de4654ba043200a6
1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmCTest.h"
5 #include <algorithm>
6 #include <cctype>
7 #include <chrono>
8 #include <cstdint>
9 #include <cstdio>
10 #include <cstdlib>
11 #include <cstring>
12 #include <ctime>
13 #include <iostream>
14 #include <map>
15 #include <ratio>
16 #include <sstream>
17 #include <string>
18 #include <utility>
19 #include <vector>
21 #include <cm/memory>
22 #include <cm/optional>
23 #include <cm/string_view>
24 #include <cmext/algorithm>
25 #include <cmext/string_view>
27 #include <cm3p/curl/curl.h>
28 #include <cm3p/uv.h>
29 #include <cm3p/zlib.h>
31 #include "cmsys/Base64.h"
32 #include "cmsys/Directory.hxx"
33 #include "cmsys/FStream.hxx"
34 #include "cmsys/Glob.hxx"
35 #include "cmsys/RegularExpression.hxx"
36 #include "cmsys/SystemInformation.hxx"
37 #if defined(_WIN32)
38 # include <windows.h> // IWYU pragma: keep
39 #else
40 # include <unistd.h> // IWYU pragma: keep
41 #endif
43 #include "cmCMakePresetsGraph.h"
44 #include "cmCTestBuildAndTestHandler.h"
45 #include "cmCTestBuildHandler.h"
46 #include "cmCTestConfigureHandler.h"
47 #include "cmCTestCoverageHandler.h"
48 #include "cmCTestGenericHandler.h"
49 #include "cmCTestMemCheckHandler.h"
50 #include "cmCTestScriptHandler.h"
51 #include "cmCTestStartCommand.h"
52 #include "cmCTestSubmitHandler.h"
53 #include "cmCTestTestHandler.h"
54 #include "cmCTestUpdateHandler.h"
55 #include "cmCTestUploadHandler.h"
56 #include "cmDynamicLoader.h"
57 #include "cmGeneratedFileStream.h"
58 #include "cmGlobalGenerator.h"
59 #include "cmJSONState.h"
60 #include "cmList.h"
61 #include "cmMakefile.h"
62 #include "cmProcessOutput.h"
63 #include "cmState.h"
64 #include "cmStateSnapshot.h"
65 #include "cmStateTypes.h"
66 #include "cmStringAlgorithms.h"
67 #include "cmSystemTools.h"
68 #include "cmUVHandlePtr.h"
69 #include "cmUVProcessChain.h"
70 #include "cmUVStream.h"
71 #include "cmValue.h"
72 #include "cmVersion.h"
73 #include "cmVersionConfig.h"
74 #include "cmXMLWriter.h"
75 #include "cmake.h"
77 #if defined(__BEOS__) || defined(__HAIKU__)
78 # include <be/kernel/OS.h> /* disable_debugger() API. */
79 #endif
81 struct cmCTest::Private
83 /** Representation of one part. */
84 struct PartInfo
86 void SetName(const std::string& name) { this->Name = name; }
87 const std::string& GetName() const { return this->Name; }
89 void Enable() { this->Enabled = true; }
90 explicit operator bool() const { return this->Enabled; }
92 std::vector<std::string> SubmitFiles;
94 private:
95 bool Enabled = false;
96 std::string Name;
99 int RepeatCount = 1; // default to run each test once
100 cmCTest::Repeat RepeatMode = cmCTest::Repeat::Never;
101 std::string ConfigType;
102 std::string ScheduleType;
103 std::chrono::system_clock::time_point StopTime;
104 bool StopOnFailure = false;
105 bool TestProgressOutput = false;
106 bool Verbose = false;
107 bool ExtraVerbose = false;
108 bool ProduceXML = false;
109 bool LabelSummary = true;
110 bool SubprojectSummary = true;
111 bool UseHTTP10 = false;
112 bool PrintLabels = false;
113 bool Failover = false;
115 bool FlushTestProgressLine = false;
117 bool ForceNewCTestProcess = false;
119 bool RunConfigurationScript = false;
121 // these are helper classes
122 cmCTestBuildHandler BuildHandler;
123 cmCTestBuildAndTestHandler BuildAndTestHandler;
124 cmCTestCoverageHandler CoverageHandler;
125 cmCTestScriptHandler ScriptHandler;
126 cmCTestTestHandler TestHandler;
127 cmCTestUpdateHandler UpdateHandler;
128 cmCTestConfigureHandler ConfigureHandler;
129 cmCTestMemCheckHandler MemCheckHandler;
130 cmCTestSubmitHandler SubmitHandler;
131 cmCTestUploadHandler UploadHandler;
133 std::vector<cmCTestGenericHandler*> GetTestingHandlers()
135 return { &this->BuildHandler, &this->BuildAndTestHandler,
136 &this->CoverageHandler, &this->ScriptHandler,
137 &this->TestHandler, &this->UpdateHandler,
138 &this->ConfigureHandler, &this->MemCheckHandler,
139 &this->SubmitHandler, &this->UploadHandler };
142 std::map<std::string, cmCTestGenericHandler*> GetNamedTestingHandlers()
144 return { { "build", &this->BuildHandler },
145 { "buildtest", &this->BuildAndTestHandler },
146 { "coverage", &this->CoverageHandler },
147 { "script", &this->ScriptHandler },
148 { "test", &this->TestHandler },
149 { "update", &this->UpdateHandler },
150 { "configure", &this->ConfigureHandler },
151 { "memcheck", &this->MemCheckHandler },
152 { "submit", &this->SubmitHandler },
153 { "upload", &this->UploadHandler } };
156 bool ShowOnly = false;
157 bool OutputAsJson = false;
158 int OutputAsJsonVersion = 1;
160 // TODO: The ctest configuration should be a hierarchy of
161 // configuration option sources: command-line, script, ini file.
162 // Then the ini file can get re-loaded whenever it changes without
163 // affecting any higher-precedence settings.
164 std::map<std::string, std::string> CTestConfiguration;
165 std::map<std::string, std::string> CTestConfigurationOverwrites;
167 PartInfo Parts[PartCount];
168 std::map<std::string, Part> PartMap;
170 std::string CurrentTag;
171 bool TomorrowTag = false;
173 int TestModel = cmCTest::EXPERIMENTAL;
174 std::string SpecificGroup;
176 cmDuration TimeOut = cmDuration::zero();
178 cmDuration GlobalTimeout = cmDuration::zero();
180 int MaxTestNameWidth = 30;
182 cm::optional<size_t> ParallelLevel = 1;
183 bool ParallelLevelSetInCli = false;
185 unsigned long TestLoad = 0;
187 int CompatibilityMode;
189 // information for the --build-and-test options
190 std::string BinaryDir;
191 std::string TestDir;
193 std::string NotesFiles;
195 bool InteractiveDebugMode = true;
197 bool ShortDateFormat = true;
199 bool CompressXMLFiles = false;
200 bool CompressTestOutput = true;
202 // By default we write output to the process output streams.
203 std::ostream* StreamOut = &std::cout;
204 std::ostream* StreamErr = &std::cerr;
206 bool SuppressUpdatingCTestConfiguration = false;
208 bool Debug = false;
209 bool ShowLineNumbers = false;
210 bool Quiet = false;
212 std::string BuildID;
214 std::vector<std::string> InitialCommandLineArguments;
216 int SubmitIndex = 0;
218 std::unique_ptr<cmGeneratedFileStream> OutputLogFile;
219 int OutputLogFileLastTag = -1;
221 bool OutputTestOutputOnTestFailure = false;
222 bool OutputColorCode = cmCTest::ColoredOutputSupportedByConsole();
224 std::map<std::string, std::string> Definitions;
226 cmCTest::NoTests NoTestsMode = cmCTest::NoTests::Legacy;
227 bool NoTestsModeSetInCli = false;
230 struct tm* cmCTest::GetNightlyTime(std::string const& str, bool tomorrowtag)
232 struct tm* lctime;
233 time_t tctime = time(nullptr);
234 lctime = gmtime(&tctime);
235 char buf[1024];
236 // add todays year day and month to the time in str because
237 // curl_getdate no longer assumes the day is today
238 std::snprintf(buf, sizeof(buf), "%d%02d%02d %s", lctime->tm_year + 1900,
239 lctime->tm_mon + 1, lctime->tm_mday, str.c_str());
240 cmCTestLog(this, OUTPUT,
241 "Determine Nightly Start Time" << std::endl
242 << " Specified time: " << str
243 << std::endl);
244 // Convert the nightly start time to seconds. Since we are
245 // providing only a time and a timezone, the current date of
246 // the local machine is assumed. Consequently, nightlySeconds
247 // is the time at which the nightly dashboard was opened or
248 // will be opened on the date of the current client machine.
249 // As such, this time may be in the past or in the future.
250 time_t ntime = curl_getdate(buf, &tctime);
251 cmCTestLog(this, DEBUG, " Get curl time: " << ntime << std::endl);
252 tctime = time(nullptr);
253 cmCTestLog(this, DEBUG, " Get the current time: " << tctime << std::endl);
255 const int dayLength = 24 * 60 * 60;
256 cmCTestLog(this, DEBUG, "Seconds: " << tctime << std::endl);
257 while (ntime > tctime) {
258 // If nightlySeconds is in the past, this is the current
259 // open dashboard, then return nightlySeconds. If
260 // nightlySeconds is in the future, this is the next
261 // dashboard to be opened, so subtract 24 hours to get the
262 // time of the current open dashboard
263 ntime -= dayLength;
264 cmCTestLog(this, DEBUG, "Pick yesterday" << std::endl);
265 cmCTestLog(this, DEBUG,
266 " Future time, subtract day: " << ntime << std::endl);
268 while (tctime > (ntime + dayLength)) {
269 ntime += dayLength;
270 cmCTestLog(this, DEBUG, " Past time, add day: " << ntime << std::endl);
272 cmCTestLog(this, DEBUG, "nightlySeconds: " << ntime << std::endl);
273 cmCTestLog(this, DEBUG,
274 " Current time: " << tctime << " Nightly time: " << ntime
275 << std::endl);
276 if (tomorrowtag) {
277 cmCTestLog(this, OUTPUT, " Use future tag, Add a day" << std::endl);
278 ntime += dayLength;
280 lctime = gmtime(&ntime);
281 return lctime;
284 bool cmCTest::GetTomorrowTag() const
286 return this->Impl->TomorrowTag;
289 std::string cmCTest::CleanString(const std::string& str,
290 std::string::size_type spos)
292 spos = str.find_first_not_of(" \n\t\r\f\v", spos);
293 std::string::size_type epos = str.find_last_not_of(" \n\t\r\f\v");
294 if (spos == std::string::npos) {
295 return std::string();
297 if (epos != std::string::npos) {
298 epos = epos - spos + 1;
300 return str.substr(spos, epos);
303 std::string cmCTest::CurrentTime()
305 time_t currenttime = time(nullptr);
306 struct tm* t = localtime(&currenttime);
307 // return ::CleanString(ctime(&currenttime));
308 char current_time[1024];
309 if (this->Impl->ShortDateFormat) {
310 strftime(current_time, 1000, "%b %d %H:%M %Z", t);
311 } else {
312 strftime(current_time, 1000, "%a %b %d %H:%M:%S %Z %Y", t);
314 cmCTestLog(this, DEBUG, " Current_Time: " << current_time << std::endl);
315 return cmCTest::CleanString(current_time);
318 std::string cmCTest::GetCostDataFile()
320 std::string fname = this->GetCTestConfiguration("CostDataFile");
321 if (fname.empty()) {
322 fname = this->GetBinaryDir() + "/Testing/Temporary/CTestCostData.txt";
324 return fname;
327 std::string cmCTest::DecodeURL(const std::string& in)
329 std::string out;
330 for (const char* c = in.c_str(); *c; ++c) {
331 if (*c == '%' && isxdigit(*(c + 1)) && isxdigit(*(c + 2))) {
332 char buf[3] = { *(c + 1), *(c + 2), 0 };
333 out.append(1, static_cast<char>(strtoul(buf, nullptr, 16)));
334 c += 2;
335 } else {
336 out.append(1, *c);
339 return out;
342 cmCTest::cmCTest()
343 : Impl(new Private)
345 std::string envValue;
346 if (cmSystemTools::GetEnv("CTEST_OUTPUT_ON_FAILURE", envValue)) {
347 this->Impl->OutputTestOutputOnTestFailure = !cmIsOff(envValue);
349 envValue.clear();
350 if (cmSystemTools::GetEnv("CTEST_PROGRESS_OUTPUT", envValue)) {
351 this->Impl->TestProgressOutput = !cmIsOff(envValue);
354 this->Impl->Parts[PartStart].SetName("Start");
355 this->Impl->Parts[PartUpdate].SetName("Update");
356 this->Impl->Parts[PartConfigure].SetName("Configure");
357 this->Impl->Parts[PartBuild].SetName("Build");
358 this->Impl->Parts[PartTest].SetName("Test");
359 this->Impl->Parts[PartCoverage].SetName("Coverage");
360 this->Impl->Parts[PartMemCheck].SetName("MemCheck");
361 this->Impl->Parts[PartSubmit].SetName("Submit");
362 this->Impl->Parts[PartNotes].SetName("Notes");
363 this->Impl->Parts[PartExtraFiles].SetName("ExtraFiles");
364 this->Impl->Parts[PartUpload].SetName("Upload");
365 this->Impl->Parts[PartDone].SetName("Done");
367 // Fill the part name-to-id map.
368 for (Part p = PartStart; p != PartCount; p = static_cast<Part>(p + 1)) {
369 this->Impl
370 ->PartMap[cmSystemTools::LowerCase(this->Impl->Parts[p].GetName())] = p;
373 for (auto& handler : this->Impl->GetTestingHandlers()) {
374 handler->SetCTestInstance(this);
377 // Make sure we can capture the build tool output.
378 cmSystemTools::EnableVSConsoleOutput();
381 cmCTest::~cmCTest() = default;
383 cm::optional<size_t> cmCTest::GetParallelLevel() const
385 return this->Impl->ParallelLevel;
388 void cmCTest::SetParallelLevel(cm::optional<size_t> level)
390 this->Impl->ParallelLevel = level;
393 unsigned long cmCTest::GetTestLoad() const
395 return this->Impl->TestLoad;
398 void cmCTest::SetTestLoad(unsigned long load)
400 this->Impl->TestLoad = load;
403 bool cmCTest::ShouldCompressTestOutput()
405 return this->Impl->CompressTestOutput;
408 cmCTest::Part cmCTest::GetPartFromName(const std::string& name)
410 // Look up by lower-case to make names case-insensitive.
411 std::string lower_name = cmSystemTools::LowerCase(name);
412 auto const i = this->Impl->PartMap.find(lower_name);
413 if (i != this->Impl->PartMap.end()) {
414 return i->second;
417 // The string does not name a valid part.
418 return PartCount;
421 int cmCTest::Initialize(const std::string& binary_dir,
422 cmCTestStartCommand* command)
424 bool quiet = false;
425 if (command && command->ShouldBeQuiet()) {
426 quiet = true;
429 cmCTestOptionalLog(this, DEBUG, "Here: " << __LINE__ << std::endl, quiet);
430 if (!this->Impl->InteractiveDebugMode) {
431 this->BlockTestErrorDiagnostics();
432 } else {
433 cmSystemTools::PutEnv("CTEST_INTERACTIVE_DEBUG_MODE=1");
436 this->Impl->BinaryDir = binary_dir;
437 cmSystemTools::ConvertToUnixSlashes(this->Impl->BinaryDir);
439 this->UpdateCTestConfiguration();
441 cmCTestOptionalLog(this, DEBUG, "Here: " << __LINE__ << std::endl, quiet);
442 if (this->Impl->ProduceXML) {
443 cmCTestOptionalLog(this, DEBUG, "Here: " << __LINE__ << std::endl, quiet);
444 cmCTestOptionalLog(this, OUTPUT,
445 " Site: "
446 << this->GetCTestConfiguration("Site") << std::endl
447 << " Build name: "
448 << cmCTest::SafeBuildIdField(
449 this->GetCTestConfiguration("BuildName"))
450 << std::endl,
451 quiet);
452 cmCTestOptionalLog(this, DEBUG, "Produce XML is on" << std::endl, quiet);
453 if (this->Impl->TestModel == cmCTest::NIGHTLY &&
454 this->GetCTestConfiguration("NightlyStartTime").empty()) {
455 cmCTestOptionalLog(
456 this, WARNING,
457 "WARNING: No nightly start time found please set in CTestConfig.cmake"
458 " or DartConfig.cmake"
459 << std::endl,
460 quiet);
461 cmCTestOptionalLog(this, DEBUG, "Here: " << __LINE__ << std::endl,
462 quiet);
463 return 0;
467 cmake cm(cmake::RoleScript, cmState::CTest);
468 cm.SetHomeDirectory("");
469 cm.SetHomeOutputDirectory("");
470 cm.GetCurrentSnapshot().SetDefaultDefinitions();
471 cmGlobalGenerator gg(&cm);
472 cmMakefile mf(&gg, cm.GetCurrentSnapshot());
473 if (!this->ReadCustomConfigurationFileTree(this->Impl->BinaryDir, &mf)) {
474 cmCTestOptionalLog(
475 this, DEBUG, "Cannot find custom configuration file tree" << std::endl,
476 quiet);
477 return 0;
480 if (this->Impl->ProduceXML) {
481 // Verify "Testing" directory exists:
483 std::string testingDir = this->Impl->BinaryDir + "/Testing";
484 if (cmSystemTools::FileExists(testingDir)) {
485 if (!cmSystemTools::FileIsDirectory(testingDir)) {
486 cmCTestLog(this, ERROR_MESSAGE,
487 "File " << testingDir
488 << " is in the place of the testing directory"
489 << std::endl);
490 return 0;
492 } else {
493 if (!cmSystemTools::MakeDirectory(testingDir)) {
494 cmCTestLog(this, ERROR_MESSAGE,
495 "Cannot create directory " << testingDir << std::endl);
496 return 0;
500 // Create new "TAG" file or read existing one:
502 bool createNewTag = true;
503 if (command) {
504 createNewTag = command->ShouldCreateNewTag();
507 std::string tagfile = testingDir + "/TAG";
508 cmsys::ifstream tfin(tagfile.c_str());
509 std::string tag;
511 if (createNewTag) {
512 time_t tctime = time(nullptr);
513 if (this->Impl->TomorrowTag) {
514 tctime += (24 * 60 * 60);
516 struct tm* lctime = gmtime(&tctime);
517 if (tfin && cmSystemTools::GetLineFromStream(tfin, tag)) {
518 int year = 0;
519 int mon = 0;
520 int day = 0;
521 int hour = 0;
522 int min = 0;
523 sscanf(tag.c_str(), "%04d%02d%02d-%02d%02d", &year, &mon, &day, &hour,
524 &min);
525 if (year != lctime->tm_year + 1900 || mon != lctime->tm_mon + 1 ||
526 day != lctime->tm_mday) {
527 tag.clear();
529 std::string group;
530 if (cmSystemTools::GetLineFromStream(tfin, group) &&
531 !this->Impl->Parts[PartStart] && !command) {
532 this->Impl->SpecificGroup = group;
534 std::string model;
535 if (cmSystemTools::GetLineFromStream(tfin, model) &&
536 !this->Impl->Parts[PartStart] && !command) {
537 this->Impl->TestModel = GetTestModelFromString(model);
539 tfin.close();
541 if (tag.empty() || command || this->Impl->Parts[PartStart]) {
542 cmCTestOptionalLog(
543 this, DEBUG,
544 "TestModel: " << this->GetTestModelString() << std::endl, quiet);
545 cmCTestOptionalLog(this, DEBUG,
546 "TestModel: " << this->Impl->TestModel << std::endl,
547 quiet);
548 if (this->Impl->TestModel == cmCTest::NIGHTLY) {
549 lctime = this->GetNightlyTime(
550 this->GetCTestConfiguration("NightlyStartTime"),
551 this->Impl->TomorrowTag);
553 char datestring[100];
554 snprintf(datestring, sizeof(datestring), "%04d%02d%02d-%02d%02d",
555 lctime->tm_year + 1900, lctime->tm_mon + 1, lctime->tm_mday,
556 lctime->tm_hour, lctime->tm_min);
557 tag = datestring;
558 cmsys::ofstream ofs(tagfile.c_str());
559 if (ofs) {
560 ofs << tag << std::endl;
561 ofs << this->GetTestModelString() << std::endl;
562 switch (this->Impl->TestModel) {
563 case cmCTest::EXPERIMENTAL:
564 ofs << "Experimental" << std::endl;
565 break;
566 case cmCTest::NIGHTLY:
567 ofs << "Nightly" << std::endl;
568 break;
569 case cmCTest::CONTINUOUS:
570 ofs << "Continuous" << std::endl;
571 break;
574 ofs.close();
575 if (!command) {
576 cmCTestOptionalLog(this, OUTPUT,
577 "Create new tag: " << tag << " - "
578 << this->GetTestModelString()
579 << std::endl,
580 quiet);
583 } else {
584 std::string group;
585 std::string modelStr;
586 int model = cmCTest::UNKNOWN;
588 if (tfin) {
589 cmSystemTools::GetLineFromStream(tfin, tag);
590 cmSystemTools::GetLineFromStream(tfin, group);
591 if (cmSystemTools::GetLineFromStream(tfin, modelStr)) {
592 model = GetTestModelFromString(modelStr);
594 tfin.close();
597 if (tag.empty()) {
598 cmCTestLog(this, ERROR_MESSAGE,
599 "Cannot read existing TAG file in " << testingDir
600 << std::endl);
601 return 0;
604 if (this->Impl->TestModel == cmCTest::UNKNOWN) {
605 if (model == cmCTest::UNKNOWN) {
606 cmCTestLog(this, ERROR_MESSAGE,
607 "TAG file does not contain model and "
608 "no model specified in start command"
609 << std::endl);
610 return 0;
613 this->SetTestModel(model);
616 if (model != this->Impl->TestModel && model != cmCTest::UNKNOWN &&
617 this->Impl->TestModel != cmCTest::UNKNOWN) {
618 cmCTestOptionalLog(this, WARNING,
619 "Model given in TAG does not match "
620 "model given in ctest_start()"
621 << std::endl,
622 quiet);
625 if (!this->Impl->SpecificGroup.empty() &&
626 group != this->Impl->SpecificGroup) {
627 cmCTestOptionalLog(this, WARNING,
628 "Group given in TAG does not match "
629 "group given in ctest_start()"
630 << std::endl,
631 quiet);
632 } else {
633 this->Impl->SpecificGroup = group;
636 cmCTestOptionalLog(this, OUTPUT,
637 " Use existing tag: " << tag << " - "
638 << this->GetTestModelString()
639 << std::endl,
640 quiet);
643 this->Impl->CurrentTag = tag;
646 return 1;
649 bool cmCTest::InitializeFromCommand(cmCTestStartCommand* command)
651 std::string src_dir = this->GetCTestConfiguration("SourceDirectory");
652 std::string bld_dir = this->GetCTestConfiguration("BuildDirectory");
653 this->Impl->BuildID = "";
654 for (Part p = PartStart; p != PartCount; p = static_cast<Part>(p + 1)) {
655 this->Impl->Parts[p].SubmitFiles.clear();
658 cmMakefile* mf = command->GetMakefile();
659 std::string fname;
661 std::string src_dir_fname = cmStrCat(src_dir, "/CTestConfig.cmake");
662 cmSystemTools::ConvertToUnixSlashes(src_dir_fname);
664 std::string bld_dir_fname = cmStrCat(bld_dir, "/CTestConfig.cmake");
665 cmSystemTools::ConvertToUnixSlashes(bld_dir_fname);
667 if (cmSystemTools::FileExists(bld_dir_fname)) {
668 fname = bld_dir_fname;
669 } else if (cmSystemTools::FileExists(src_dir_fname)) {
670 fname = src_dir_fname;
673 if (!fname.empty()) {
674 cmCTestOptionalLog(this, OUTPUT,
675 " Reading ctest configuration file: " << fname
676 << std::endl,
677 command->ShouldBeQuiet());
678 bool readit = mf->ReadDependentFile(fname);
679 if (!readit) {
680 std::string m = cmStrCat("Could not find include file: ", fname);
681 command->SetError(m);
682 return false;
686 this->SetCTestConfigurationFromCMakeVariable(mf, "NightlyStartTime",
687 "CTEST_NIGHTLY_START_TIME",
688 command->ShouldBeQuiet());
689 this->SetCTestConfigurationFromCMakeVariable(mf, "Site", "CTEST_SITE",
690 command->ShouldBeQuiet());
691 this->SetCTestConfigurationFromCMakeVariable(
692 mf, "BuildName", "CTEST_BUILD_NAME", command->ShouldBeQuiet());
694 if (!this->Initialize(bld_dir, command)) {
695 return false;
697 cmCTestOptionalLog(this, OUTPUT,
698 " Use " << this->GetTestModelString() << " tag: "
699 << this->GetCurrentTag() << std::endl,
700 command->ShouldBeQuiet());
701 return true;
704 bool cmCTest::UpdateCTestConfiguration()
706 if (this->Impl->SuppressUpdatingCTestConfiguration) {
707 return true;
709 std::string fileName = this->Impl->BinaryDir + "/CTestConfiguration.ini";
710 if (!cmSystemTools::FileExists(fileName)) {
711 fileName = this->Impl->BinaryDir + "/DartConfiguration.tcl";
713 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
714 "UpdateCTestConfiguration from :" << fileName << "\n");
715 if (!cmSystemTools::FileExists(fileName)) {
716 // No need to exit if we are not producing XML
717 if (this->Impl->ProduceXML) {
718 cmCTestLog(this, WARNING, "Cannot find file: " << fileName << std::endl);
719 return false;
721 } else {
722 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
723 "Parse Config file:" << fileName << "\n");
724 // parse the dart test file
725 cmsys::ifstream fin(fileName.c_str());
727 if (!fin) {
728 return false;
731 char buffer[1024];
732 while (fin) {
733 buffer[0] = 0;
734 fin.getline(buffer, 1023);
735 buffer[1023] = 0;
736 std::string line = cmCTest::CleanString(buffer);
737 if (line.empty()) {
738 continue;
740 while (fin && (line.back() == '\\')) {
741 line.resize(line.size() - 1);
742 buffer[0] = 0;
743 fin.getline(buffer, 1023);
744 buffer[1023] = 0;
745 line += cmCTest::CleanString(buffer);
747 if (line[0] == '#') {
748 continue;
750 std::string::size_type cpos = line.find_first_of(':');
751 if (cpos == std::string::npos) {
752 continue;
754 std::string key = line.substr(0, cpos);
755 std::string value = cmCTest::CleanString(line, cpos + 1);
756 this->Impl->CTestConfiguration[key] = value;
758 fin.close();
760 if (!this->GetCTestConfiguration("BuildDirectory").empty()) {
761 this->Impl->BinaryDir = this->GetCTestConfiguration("BuildDirectory");
762 if (this->Impl->TestDir.empty()) {
763 cmSystemTools::ChangeDirectory(this->Impl->BinaryDir);
766 this->Impl->TimeOut =
767 std::chrono::seconds(atoi(this->GetCTestConfiguration("TimeOut").c_str()));
768 std::string const& testLoad = this->GetCTestConfiguration("TestLoad");
769 if (!testLoad.empty()) {
770 unsigned long load;
771 if (cmStrToULong(testLoad, &load)) {
772 this->SetTestLoad(load);
773 } else {
774 cmCTestLog(this, WARNING,
775 "Invalid value for 'Test Load' : " << testLoad << std::endl);
778 if (this->Impl->ProduceXML) {
779 this->Impl->CompressXMLFiles =
780 cmIsOn(this->GetCTestConfiguration("CompressSubmission"));
782 return true;
785 void cmCTest::BlockTestErrorDiagnostics()
787 cmSystemTools::PutEnv("DART_TEST_FROM_DART=1");
788 cmSystemTools::PutEnv("DASHBOARD_TEST_FROM_CTEST=" CMake_VERSION);
789 #if defined(_WIN32)
790 SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
791 #elif defined(__BEOS__) || defined(__HAIKU__)
792 disable_debugger(1);
793 #endif
796 void cmCTest::SetTestModel(int mode)
798 this->Impl->InteractiveDebugMode = false;
799 this->Impl->TestModel = mode;
802 int cmCTest::GetTestModel() const
804 return this->Impl->TestModel;
807 bool cmCTest::SetTest(const std::string& ttype, bool report)
809 if (cmSystemTools::LowerCase(ttype) == "all") {
810 for (Part p = PartStart; p != PartCount; p = static_cast<Part>(p + 1)) {
811 this->Impl->Parts[p].Enable();
813 return true;
815 Part p = this->GetPartFromName(ttype);
816 if (p != PartCount) {
817 this->Impl->Parts[p].Enable();
818 return true;
820 if (report) {
821 cmCTestLog(this, ERROR_MESSAGE,
822 "Don't know about test \"" << ttype << "\" yet..."
823 << std::endl);
825 return false;
828 void cmCTest::Finalize()
832 bool cmCTest::OpenOutputFile(const std::string& path, const std::string& name,
833 cmGeneratedFileStream& stream, bool compress)
835 std::string testingDir = this->Impl->BinaryDir + "/Testing";
836 if (!path.empty()) {
837 testingDir += "/" + path;
839 if (cmSystemTools::FileExists(testingDir)) {
840 if (!cmSystemTools::FileIsDirectory(testingDir)) {
841 cmCTestLog(this, ERROR_MESSAGE,
842 "File " << testingDir
843 << " is in the place of the testing directory"
844 << std::endl);
845 return false;
847 } else {
848 if (!cmSystemTools::MakeDirectory(testingDir)) {
849 cmCTestLog(this, ERROR_MESSAGE,
850 "Cannot create directory " << testingDir << std::endl);
851 return false;
854 std::string filename = testingDir + "/" + name;
855 stream.SetTempExt("tmp");
856 stream.Open(filename);
857 if (!stream) {
858 cmCTestLog(this, ERROR_MESSAGE,
859 "Problem opening file: " << filename << std::endl);
860 return false;
862 if (compress) {
863 if (this->Impl->CompressXMLFiles) {
864 stream.SetCompression(true);
867 return true;
870 bool cmCTest::AddIfExists(Part part, const std::string& file)
872 if (this->CTestFileExists(file)) {
873 this->AddSubmitFile(part, file);
874 } else {
875 std::string name = cmStrCat(file, ".gz");
876 if (this->CTestFileExists(name)) {
877 this->AddSubmitFile(part, file);
878 } else {
879 return false;
882 return true;
885 bool cmCTest::CTestFileExists(const std::string& filename)
887 std::string testingDir = this->Impl->BinaryDir + "/Testing/" +
888 this->Impl->CurrentTag + "/" + filename;
889 return cmSystemTools::FileExists(testingDir);
892 cmCTestBuildHandler* cmCTest::GetBuildHandler()
894 return &this->Impl->BuildHandler;
897 cmCTestBuildAndTestHandler* cmCTest::GetBuildAndTestHandler()
899 return &this->Impl->BuildAndTestHandler;
902 cmCTestCoverageHandler* cmCTest::GetCoverageHandler()
904 return &this->Impl->CoverageHandler;
907 cmCTestScriptHandler* cmCTest::GetScriptHandler()
909 return &this->Impl->ScriptHandler;
912 cmCTestTestHandler* cmCTest::GetTestHandler()
914 return &this->Impl->TestHandler;
917 cmCTestUpdateHandler* cmCTest::GetUpdateHandler()
919 return &this->Impl->UpdateHandler;
922 cmCTestConfigureHandler* cmCTest::GetConfigureHandler()
924 return &this->Impl->ConfigureHandler;
927 cmCTestMemCheckHandler* cmCTest::GetMemCheckHandler()
929 return &this->Impl->MemCheckHandler;
932 cmCTestSubmitHandler* cmCTest::GetSubmitHandler()
934 return &this->Impl->SubmitHandler;
937 cmCTestUploadHandler* cmCTest::GetUploadHandler()
939 return &this->Impl->UploadHandler;
942 int cmCTest::ProcessSteps()
944 int res = 0;
945 bool notest = true;
946 int update_count = 0;
948 for (Part p = PartStart; notest && p != PartCount;
949 p = static_cast<Part>(p + 1)) {
950 notest = !this->Impl->Parts[p];
952 if (this->Impl->Parts[PartUpdate] &&
953 (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
954 cmCTestUpdateHandler* uphandler = this->GetUpdateHandler();
955 uphandler->SetPersistentOption(
956 "SourceDirectory", this->GetCTestConfiguration("SourceDirectory"));
957 update_count = uphandler->ProcessHandler();
958 if (update_count < 0) {
959 res |= cmCTest::UPDATE_ERRORS;
962 if (this->Impl->TestModel == cmCTest::CONTINUOUS && !update_count) {
963 return 0;
965 if (this->Impl->Parts[PartConfigure] &&
966 (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
967 if (this->GetConfigureHandler()->ProcessHandler() < 0) {
968 res |= cmCTest::CONFIGURE_ERRORS;
971 if (this->Impl->Parts[PartBuild] &&
972 (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
973 this->UpdateCTestConfiguration();
974 if (this->GetBuildHandler()->ProcessHandler() < 0) {
975 res |= cmCTest::BUILD_ERRORS;
978 if ((this->Impl->Parts[PartTest] || notest) &&
979 (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
980 this->UpdateCTestConfiguration();
981 if (this->GetTestHandler()->ProcessHandler() < 0) {
982 res |= cmCTest::TEST_ERRORS;
985 if (this->Impl->Parts[PartCoverage] &&
986 (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
987 this->UpdateCTestConfiguration();
988 if (this->GetCoverageHandler()->ProcessHandler() < 0) {
989 res |= cmCTest::COVERAGE_ERRORS;
992 if (this->Impl->Parts[PartMemCheck] &&
993 (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
994 this->UpdateCTestConfiguration();
995 if (this->GetMemCheckHandler()->ProcessHandler() < 0) {
996 res |= cmCTest::MEMORY_ERRORS;
999 if (!notest) {
1000 std::string notes_dir = this->Impl->BinaryDir + "/Testing/Notes";
1001 if (cmSystemTools::FileIsDirectory(notes_dir)) {
1002 cmsys::Directory d;
1003 d.Load(notes_dir);
1004 unsigned long kk;
1005 for (kk = 0; kk < d.GetNumberOfFiles(); kk++) {
1006 const char* file = d.GetFile(kk);
1007 std::string fullname = notes_dir + "/" + file;
1008 if (cmSystemTools::FileExists(fullname, true)) {
1009 if (!this->Impl->NotesFiles.empty()) {
1010 this->Impl->NotesFiles += ";";
1012 this->Impl->NotesFiles += fullname;
1013 this->Impl->Parts[PartNotes].Enable();
1018 if (this->Impl->Parts[PartNotes]) {
1019 this->UpdateCTestConfiguration();
1020 if (!this->Impl->NotesFiles.empty()) {
1021 this->GenerateNotesFile(this->Impl->NotesFiles);
1024 if (this->Impl->Parts[PartSubmit]) {
1025 this->UpdateCTestConfiguration();
1026 if (this->GetSubmitHandler()->ProcessHandler() < 0) {
1027 res |= cmCTest::SUBMIT_ERRORS;
1030 if (res != 0) {
1031 cmCTestLog(this, ERROR_MESSAGE, "Errors while running CTest" << std::endl);
1032 if (!this->Impl->OutputTestOutputOnTestFailure) {
1033 const std::string lastTestLog =
1034 this->GetBinaryDir() + "/Testing/Temporary/LastTest.log";
1035 cmCTestLog(this, ERROR_MESSAGE,
1036 "Output from these tests are in: " << lastTestLog
1037 << std::endl);
1038 cmCTestLog(this, ERROR_MESSAGE,
1039 "Use \"--rerun-failed --output-on-failure\" to re-run the "
1040 "failed cases verbosely."
1041 << std::endl);
1044 return res;
1047 std::string cmCTest::GetTestModelString()
1049 if (!this->Impl->SpecificGroup.empty()) {
1050 return this->Impl->SpecificGroup;
1052 switch (this->Impl->TestModel) {
1053 case cmCTest::NIGHTLY:
1054 return "Nightly";
1055 case cmCTest::CONTINUOUS:
1056 return "Continuous";
1058 return "Experimental";
1061 int cmCTest::GetTestModelFromString(const std::string& str)
1063 if (str.empty()) {
1064 return cmCTest::EXPERIMENTAL;
1066 std::string rstr = cmSystemTools::LowerCase(str);
1067 if (cmHasLiteralPrefix(rstr, "cont")) {
1068 return cmCTest::CONTINUOUS;
1070 if (cmHasLiteralPrefix(rstr, "nigh")) {
1071 return cmCTest::NIGHTLY;
1073 return cmCTest::EXPERIMENTAL;
1076 bool cmCTest::RunMakeCommand(const std::string& command, std::string& output,
1077 int* retVal, const char* dir, cmDuration timeout,
1078 std::ostream& ofs, Encoding encoding)
1080 // First generate the command and arguments
1081 std::vector<std::string> args = cmSystemTools::ParseArguments(command);
1083 if (args.empty()) {
1084 return false;
1087 output.clear();
1088 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, "Run command:");
1089 for (auto const& arg : args) {
1090 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, " \"" << arg << "\"");
1092 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, std::endl);
1094 // Now create process object
1095 cmUVProcessChainBuilder builder;
1096 builder.AddCommand(args).SetMergedBuiltinStreams();
1097 if (dir) {
1098 builder.SetWorkingDirectory(dir);
1100 auto chain = builder.Start();
1101 cm::uv_pipe_ptr outputStream;
1102 outputStream.init(chain.GetLoop(), 0);
1103 uv_pipe_open(outputStream, chain.OutputStream());
1105 // Initialize tick's
1106 std::string::size_type tick = 0;
1107 std::string::size_type tick_len = 1024;
1108 std::string::size_type tick_line_len = 50;
1110 cmProcessOutput processOutput(encoding);
1111 cmCTestLog(this, HANDLER_PROGRESS_OUTPUT,
1112 " Each . represents " << tick_len
1113 << " bytes of output\n"
1115 << std::flush);
1116 auto outputHandle = cmUVStreamRead(
1117 outputStream,
1118 [this, &processOutput, &output, &tick, &tick_len, &tick_line_len,
1119 &ofs](std::vector<char> data) {
1120 std::string strdata;
1121 processOutput.DecodeText(data.data(), data.size(), strdata);
1122 for (char& cc : strdata) {
1123 if (cc == 0) {
1124 cc = '\n';
1127 output.append(strdata);
1128 while (output.size() > (tick * tick_len)) {
1129 tick++;
1130 cmCTestLog(this, HANDLER_PROGRESS_OUTPUT, "." << std::flush);
1131 if (tick % tick_line_len == 0 && tick > 0) {
1132 cmCTestLog(this, HANDLER_PROGRESS_OUTPUT,
1133 " Size: " << int((double(output.size()) / 1024.0) + 1)
1134 << "K\n " << std::flush);
1137 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, strdata);
1138 if (ofs) {
1139 ofs << strdata;
1142 [this, &processOutput, &output, &ofs]() {
1143 std::string strdata;
1144 processOutput.DecodeText(std::string(), strdata);
1145 if (!strdata.empty()) {
1146 output.append(strdata);
1147 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, strdata);
1148 if (ofs) {
1149 ofs << strdata;
1154 bool finished = chain.Wait(static_cast<uint64_t>(timeout.count() * 1000.0));
1155 cmCTestLog(this, HANDLER_PROGRESS_OUTPUT,
1156 " Size of output: " << int(double(output.size()) / 1024.0) << "K"
1157 << std::endl);
1159 if (finished) {
1160 auto const& status = chain.GetStatus(0);
1161 auto exception = status.GetException();
1162 switch (exception.first) {
1163 case cmUVProcessChain::ExceptionCode::None:
1164 *retVal = static_cast<int>(status.ExitStatus);
1165 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
1166 "Command exited with the value: " << *retVal << std::endl);
1167 break;
1168 case cmUVProcessChain::ExceptionCode::Spawn:
1169 output += "\n*** ERROR executing: ";
1170 output += exception.second;
1171 output += "\n***The build process failed.";
1172 cmCTestLog(this, ERROR_MESSAGE,
1173 "There was an error: " << exception.second << std::endl);
1174 break;
1175 default:
1176 *retVal = static_cast<int>(exception.first);
1177 cmCTestLog(this, WARNING,
1178 "There was an exception: " << *retVal << std::endl);
1179 break;
1181 } else {
1182 cmCTestLog(this, WARNING, "There was a timeout" << std::endl);
1185 return true;
1188 bool cmCTest::RunTest(const std::vector<std::string>& argv,
1189 std::string* output, int* retVal, std::ostream* log,
1190 cmDuration testTimeOut,
1191 std::vector<std::string>* environment, Encoding encoding)
1193 bool modifyEnv = (environment && !environment->empty());
1195 // determine how much time we have
1196 cmDuration timeout = this->GetRemainingTimeAllowed();
1197 if (timeout != cmCTest::MaxDuration()) {
1198 timeout -= std::chrono::minutes(2);
1200 if (this->Impl->TimeOut > cmDuration::zero() &&
1201 this->Impl->TimeOut < timeout) {
1202 timeout = this->Impl->TimeOut;
1204 if (testTimeOut > cmDuration::zero() &&
1205 testTimeOut < this->GetRemainingTimeAllowed()) {
1206 timeout = testTimeOut;
1209 // always have at least 1 second if we got to here
1210 if (timeout <= cmDuration::zero()) {
1211 timeout = std::chrono::seconds(1);
1213 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
1214 "Test timeout computed to be: "
1215 << (timeout == cmCTest::MaxDuration()
1216 ? std::string("infinite")
1217 : std::to_string(cmDurationTo<unsigned int>(timeout)))
1218 << "\n");
1219 if (cmSystemTools::SameFile(argv[0], cmSystemTools::GetCTestCommand()) &&
1220 !this->Impl->ForceNewCTestProcess) {
1221 cmCTest inst;
1222 inst.Impl->ConfigType = this->Impl->ConfigType;
1223 inst.Impl->TimeOut = timeout;
1225 // Capture output of the child ctest.
1226 std::ostringstream oss;
1227 inst.SetStreams(&oss, &oss);
1229 std::vector<std::string> args;
1230 for (auto const& i : argv) {
1231 // make sure we pass the timeout in for any build and test
1232 // invocations. Since --build-generator is required this is a
1233 // good place to check for it, and to add the arguments in
1234 if (i == "--build-generator" && timeout != cmCTest::MaxDuration() &&
1235 timeout > cmDuration::zero()) {
1236 args.emplace_back("--test-timeout");
1237 args.push_back(std::to_string(cmDurationTo<unsigned int>(timeout)));
1239 args.emplace_back(i);
1241 if (log) {
1242 *log << "* Run internal CTest" << std::endl;
1245 std::unique_ptr<cmSystemTools::SaveRestoreEnvironment> saveEnv;
1246 if (modifyEnv) {
1247 saveEnv = cm::make_unique<cmSystemTools::SaveRestoreEnvironment>();
1248 cmSystemTools::AppendEnv(*environment);
1251 *retVal = inst.Run(args, output);
1252 if (output) {
1253 *output += oss.str();
1255 if (log && output) {
1256 *log << *output;
1258 if (output) {
1259 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
1260 "Internal cmCTest object used to run test." << std::endl
1261 << *output
1262 << std::endl);
1265 return true;
1267 std::vector<char> tempOutput;
1268 if (output) {
1269 output->clear();
1272 std::unique_ptr<cmSystemTools::SaveRestoreEnvironment> saveEnv;
1273 if (modifyEnv) {
1274 saveEnv = cm::make_unique<cmSystemTools::SaveRestoreEnvironment>();
1275 cmSystemTools::AppendEnv(*environment);
1278 cmUVProcessChainBuilder builder;
1279 builder.AddCommand(argv).SetMergedBuiltinStreams();
1280 cmCTestLog(this, DEBUG, "Command is: " << argv[0] << std::endl);
1281 auto chain = builder.Start();
1283 cmProcessOutput processOutput(encoding);
1284 cm::uv_pipe_ptr outputStream;
1285 outputStream.init(chain.GetLoop(), 0);
1286 uv_pipe_open(outputStream, chain.OutputStream());
1287 auto outputHandle = cmUVStreamRead(
1288 outputStream,
1289 [this, &processOutput, &output, &tempOutput,
1290 &log](std::vector<char> data) {
1291 std::string strdata;
1292 processOutput.DecodeText(data.data(), data.size(), strdata);
1293 if (output) {
1294 cm::append(tempOutput, data.data(), data.data() + data.size());
1296 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, strdata);
1297 if (log) {
1298 log->write(strdata.c_str(), strdata.size());
1301 [this, &processOutput, &log]() {
1302 std::string strdata;
1303 processOutput.DecodeText(std::string(), strdata);
1304 if (!strdata.empty()) {
1305 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, strdata);
1306 if (log) {
1307 log->write(strdata.c_str(), strdata.size());
1312 bool complete = chain.Wait(static_cast<uint64_t>(timeout.count() * 1000.0));
1313 processOutput.DecodeText(tempOutput, tempOutput);
1314 if (output && tempOutput.begin() != tempOutput.end()) {
1315 output->append(tempOutput.data(), tempOutput.size());
1317 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
1318 "-- Process completed" << std::endl);
1320 bool result = false;
1322 if (complete) {
1323 auto const& status = chain.GetStatus(0);
1324 auto exception = status.GetException();
1325 switch (exception.first) {
1326 case cmUVProcessChain::ExceptionCode::None:
1327 *retVal = static_cast<int>(status.ExitStatus);
1328 if (*retVal != 0 && this->Impl->OutputTestOutputOnTestFailure) {
1329 this->OutputTestErrors(tempOutput);
1331 result = true;
1332 break;
1333 case cmUVProcessChain::ExceptionCode::Spawn: {
1334 std::string outerr =
1335 cmStrCat("\n*** ERROR executing: ", exception.second);
1336 if (output) {
1337 *output += outerr;
1339 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, outerr << std::endl);
1340 } break;
1341 default: {
1342 if (this->Impl->OutputTestOutputOnTestFailure) {
1343 this->OutputTestErrors(tempOutput);
1345 *retVal = status.TermSignal;
1346 std::string outerr =
1347 cmStrCat("\n*** Exception executing: ", exception.second);
1348 if (output) {
1349 *output += outerr;
1351 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, outerr << std::endl);
1352 } break;
1356 return result;
1359 std::string cmCTest::SafeBuildIdField(const std::string& value)
1361 std::string safevalue(value);
1363 if (!safevalue.empty()) {
1364 // Disallow non-filename and non-space whitespace characters.
1365 // If they occur, replace them with ""
1367 const char* disallowed = "\\:*?\"<>|\n\r\t\f\v";
1369 if (safevalue.find_first_of(disallowed) != std::string::npos) {
1370 std::string::size_type i = 0;
1371 std::string::size_type n = strlen(disallowed);
1372 char replace[2];
1373 replace[1] = 0;
1375 for (i = 0; i < n; ++i) {
1376 replace[0] = disallowed[i];
1377 cmSystemTools::ReplaceString(safevalue, replace, "");
1382 if (safevalue.empty()) {
1383 safevalue = "(empty)";
1386 return safevalue;
1389 void cmCTest::StartXML(cmXMLWriter& xml, bool append)
1391 if (this->Impl->CurrentTag.empty()) {
1392 cmCTestLog(this, ERROR_MESSAGE,
1393 "Current Tag empty, this may mean"
1394 " NightlStartTime was not set correctly."
1395 << std::endl);
1396 cmSystemTools::SetFatalErrorOccurred();
1399 // find out about the system
1400 cmsys::SystemInformation info;
1401 info.RunCPUCheck();
1402 info.RunOSCheck();
1403 info.RunMemoryCheck();
1405 std::string buildname =
1406 cmCTest::SafeBuildIdField(this->GetCTestConfiguration("BuildName"));
1407 std::string stamp = cmCTest::SafeBuildIdField(this->Impl->CurrentTag + "-" +
1408 this->GetTestModelString());
1409 std::string site =
1410 cmCTest::SafeBuildIdField(this->GetCTestConfiguration("Site"));
1412 xml.StartDocument();
1413 xml.StartElement("Site");
1414 xml.Attribute("BuildName", buildname);
1415 xml.BreakAttributes();
1416 xml.Attribute("BuildStamp", stamp);
1417 xml.Attribute("Name", site);
1418 xml.Attribute("Generator",
1419 std::string("ctest-") + cmVersion::GetCMakeVersion());
1420 if (append) {
1421 xml.Attribute("Append", "true");
1423 xml.Attribute("CompilerName", this->GetCTestConfiguration("Compiler"));
1424 xml.Attribute("CompilerVersion",
1425 this->GetCTestConfiguration("CompilerVersion"));
1426 xml.Attribute("OSName", info.GetOSName());
1427 xml.Attribute("Hostname", info.GetHostname());
1428 xml.Attribute("OSRelease", info.GetOSRelease());
1429 xml.Attribute("OSVersion", info.GetOSVersion());
1430 xml.Attribute("OSPlatform", info.GetOSPlatform());
1431 xml.Attribute("Is64Bits", info.Is64Bits());
1432 xml.Attribute("VendorString", info.GetVendorString());
1433 xml.Attribute("VendorID", info.GetVendorID());
1434 xml.Attribute("FamilyID", info.GetFamilyID());
1435 xml.Attribute("ModelID", info.GetModelID());
1436 xml.Attribute("ProcessorCacheSize", info.GetProcessorCacheSize());
1437 xml.Attribute("NumberOfLogicalCPU", info.GetNumberOfLogicalCPU());
1438 xml.Attribute("NumberOfPhysicalCPU", info.GetNumberOfPhysicalCPU());
1439 xml.Attribute("TotalVirtualMemory", info.GetTotalVirtualMemory());
1440 xml.Attribute("TotalPhysicalMemory", info.GetTotalPhysicalMemory());
1441 xml.Attribute("LogicalProcessorsPerPhysical",
1442 info.GetLogicalProcessorsPerPhysical());
1443 xml.Attribute("ProcessorClockFrequency", info.GetProcessorClockFrequency());
1445 std::string changeId = this->GetCTestConfiguration("ChangeId");
1446 if (!changeId.empty()) {
1447 xml.Attribute("ChangeId", changeId);
1450 this->AddSiteProperties(xml);
1453 void cmCTest::AddSiteProperties(cmXMLWriter& xml)
1455 cmCTestScriptHandler* ch = this->GetScriptHandler();
1456 cmake* cm = ch->GetCMake();
1457 // if no CMake then this is the old style script and props like
1458 // this will not work anyway.
1459 if (!cm) {
1460 return;
1462 // This code should go when cdash is changed to use labels only
1463 cmValue subproject = cm->GetState()->GetGlobalProperty("SubProject");
1464 if (subproject) {
1465 xml.StartElement("Subproject");
1466 xml.Attribute("name", *subproject);
1467 cmValue labels =
1468 ch->GetCMake()->GetState()->GetGlobalProperty("SubProjectLabels");
1469 if (labels) {
1470 xml.StartElement("Labels");
1471 cmList args{ *labels };
1472 for (std::string const& i : args) {
1473 xml.Element("Label", i);
1475 xml.EndElement();
1477 xml.EndElement();
1480 // This code should stay when cdash only does label based sub-projects
1481 cmValue label = cm->GetState()->GetGlobalProperty("Label");
1482 if (label) {
1483 xml.StartElement("Labels");
1484 xml.Element("Label", *label);
1485 xml.EndElement();
1489 void cmCTest::GenerateSubprojectsOutput(cmXMLWriter& xml)
1491 for (std::string const& subproj : this->GetLabelsForSubprojects()) {
1492 xml.StartElement("Subproject");
1493 xml.Attribute("name", subproj);
1494 xml.Element("Label", subproj);
1495 xml.EndElement(); // Subproject
1499 std::vector<std::string> cmCTest::GetLabelsForSubprojects()
1501 std::string labelsForSubprojects =
1502 this->GetCTestConfiguration("LabelsForSubprojects");
1503 cmList subprojects{ labelsForSubprojects };
1505 // sort the array
1506 std::sort(subprojects.begin(), subprojects.end());
1507 // remove duplicates
1508 auto new_end = std::unique(subprojects.begin(), subprojects.end());
1509 subprojects.erase(new_end, subprojects.end());
1511 return std::move(subprojects.data());
1514 void cmCTest::EndXML(cmXMLWriter& xml)
1516 xml.EndElement(); // Site
1517 xml.EndDocument();
1520 int cmCTest::GenerateCTestNotesOutput(cmXMLWriter& xml,
1521 std::vector<std::string> const& files)
1523 std::string buildname =
1524 cmCTest::SafeBuildIdField(this->GetCTestConfiguration("BuildName"));
1525 xml.StartDocument();
1526 xml.ProcessingInstruction("xml-stylesheet",
1527 "type=\"text/xsl\" "
1528 "href=\"Dart/Source/Server/XSL/Build.xsl "
1529 "<file:///Dart/Source/Server/XSL/Build.xsl> \"");
1530 xml.StartElement("Site");
1531 xml.Attribute("BuildName", buildname);
1532 xml.Attribute("BuildStamp",
1533 this->Impl->CurrentTag + "-" + this->GetTestModelString());
1534 xml.Attribute("Name", this->GetCTestConfiguration("Site"));
1535 xml.Attribute("Generator",
1536 std::string("ctest-") + cmVersion::GetCMakeVersion());
1537 this->AddSiteProperties(xml);
1538 xml.StartElement("Notes");
1540 for (std::string const& file : files) {
1541 cmCTestLog(this, OUTPUT, "\tAdd file: " << file << std::endl);
1542 std::string note_time = this->CurrentTime();
1543 xml.StartElement("Note");
1544 xml.Attribute("Name", file);
1545 xml.Element("Time", std::chrono::system_clock::now());
1546 xml.Element("DateTime", note_time);
1547 xml.StartElement("Text");
1548 cmsys::ifstream ifs(file.c_str());
1549 if (ifs) {
1550 std::string line;
1551 while (cmSystemTools::GetLineFromStream(ifs, line)) {
1552 xml.Content(line);
1553 xml.Content("\n");
1555 ifs.close();
1556 } else {
1557 xml.Content("Problem reading file: " + file + "\n");
1558 cmCTestLog(this, ERROR_MESSAGE,
1559 "Problem reading file: " << file << " while creating notes"
1560 << std::endl);
1562 xml.EndElement(); // Text
1563 xml.EndElement(); // Note
1565 xml.EndElement(); // Notes
1566 xml.EndElement(); // Site
1567 xml.EndDocument();
1568 return 1;
1571 int cmCTest::GenerateNotesFile(std::vector<std::string> const& files)
1573 cmGeneratedFileStream ofs;
1574 if (!this->OpenOutputFile(this->Impl->CurrentTag, "Notes.xml", ofs)) {
1575 cmCTestLog(this, ERROR_MESSAGE, "Cannot open notes file" << std::endl);
1576 return 1;
1578 cmXMLWriter xml(ofs);
1579 this->GenerateCTestNotesOutput(xml, files);
1580 return 0;
1583 int cmCTest::GenerateNotesFile(const std::string& cfiles)
1585 if (cfiles.empty()) {
1586 return 1;
1589 cmCTestLog(this, OUTPUT, "Create notes file" << std::endl);
1591 std::vector<std::string> const files =
1592 cmSystemTools::SplitString(cfiles, ';');
1593 if (files.empty()) {
1594 return 1;
1597 return this->GenerateNotesFile(files);
1600 int cmCTest::GenerateDoneFile()
1602 cmGeneratedFileStream ofs;
1603 if (!this->OpenOutputFile(this->Impl->CurrentTag, "Done.xml", ofs)) {
1604 cmCTestLog(this, ERROR_MESSAGE, "Cannot open done file" << std::endl);
1605 return 1;
1607 cmXMLWriter xml(ofs);
1608 xml.StartDocument();
1609 xml.StartElement("Done");
1610 xml.Element("buildId", this->Impl->BuildID);
1611 xml.Element("time", std::chrono::system_clock::now());
1612 xml.EndElement(); // Done
1613 xml.EndDocument();
1615 return 0;
1618 bool cmCTest::TryToChangeDirectory(std::string const& dir)
1620 cmCTestLog(this, OUTPUT,
1621 "Internal ctest changing into directory: " << dir << std::endl);
1622 cmsys::Status status = cmSystemTools::ChangeDirectory(dir);
1623 if (!status) {
1624 auto msg = "Failed to change working directory to \"" + dir +
1625 "\" : " + status.GetString() + "\n";
1626 cmCTestLog(this, ERROR_MESSAGE, msg);
1627 return false;
1629 return true;
1632 std::string cmCTest::Base64GzipEncodeFile(std::string const& file)
1634 const std::string currDir = cmSystemTools::GetCurrentWorkingDirectory();
1635 std::string parentDir = cmSystemTools::GetParentDirectory(file);
1637 // Temporarily change to the file's directory so the tar gets created
1638 // with a flat directory structure.
1639 if (currDir != parentDir) {
1640 if (!this->TryToChangeDirectory(parentDir)) {
1641 return "";
1645 std::string tarFile = file + "_temp.tar.gz";
1646 std::vector<std::string> files;
1647 files.push_back(file);
1649 if (!cmSystemTools::CreateTar(tarFile, files, {},
1650 cmSystemTools::TarCompressGZip, false)) {
1651 cmCTestLog(this, ERROR_MESSAGE,
1652 "Error creating tar while "
1653 "encoding file: "
1654 << file << std::endl);
1655 return "";
1657 std::string base64 = this->Base64EncodeFile(tarFile);
1658 cmSystemTools::RemoveFile(tarFile);
1660 // Change back to the directory we started in.
1661 if (currDir != parentDir) {
1662 cmSystemTools::ChangeDirectory(currDir);
1665 return base64;
1668 std::string cmCTest::Base64EncodeFile(std::string const& file)
1670 size_t const len = cmSystemTools::FileLength(file);
1671 cmsys::ifstream ifs(file.c_str(),
1672 std::ios::in
1673 #ifdef _WIN32
1674 | std::ios::binary
1675 #endif
1677 std::vector<char> file_buffer(len + 1);
1678 ifs.read(file_buffer.data(), len);
1679 ifs.close();
1681 std::vector<char> encoded_buffer((len * 3) / 2 + 5);
1683 size_t const rlen = cmsysBase64_Encode(
1684 reinterpret_cast<unsigned char*>(file_buffer.data()), len,
1685 reinterpret_cast<unsigned char*>(encoded_buffer.data()), 1);
1687 return std::string(encoded_buffer.data(), rlen);
1690 bool cmCTest::SubmitExtraFiles(std::vector<std::string> const& files)
1692 for (std::string const& file : files) {
1693 if (!cmSystemTools::FileExists(file)) {
1694 cmCTestLog(this, ERROR_MESSAGE,
1695 "Cannot find extra file: " << file << " to submit."
1696 << std::endl);
1697 return false;
1699 this->AddSubmitFile(PartExtraFiles, file);
1701 return true;
1704 bool cmCTest::SubmitExtraFiles(const std::string& cfiles)
1706 if (cfiles.empty()) {
1707 return true;
1710 cmCTestLog(this, OUTPUT, "Submit extra files" << std::endl);
1712 std::vector<std::string> const files =
1713 cmSystemTools::SplitString(cfiles, ';');
1714 if (files.empty()) {
1715 return true;
1718 return this->SubmitExtraFiles(files);
1721 // for a -D argument convert the next argument into
1722 // the proper list of dashboard steps via SetTest
1723 bool cmCTest::AddTestsForDashboardType(std::string& targ)
1725 if (targ == "Experimental") {
1726 this->SetTestModel(cmCTest::EXPERIMENTAL);
1727 this->SetTest("Start");
1728 this->SetTest("Configure");
1729 this->SetTest("Build");
1730 this->SetTest("Test");
1731 this->SetTest("Coverage");
1732 this->SetTest("Submit");
1733 } else if (targ == "ExperimentalStart") {
1734 this->SetTestModel(cmCTest::EXPERIMENTAL);
1735 this->SetTest("Start");
1736 } else if (targ == "ExperimentalUpdate") {
1737 this->SetTestModel(cmCTest::EXPERIMENTAL);
1738 this->SetTest("Update");
1739 } else if (targ == "ExperimentalConfigure") {
1740 this->SetTestModel(cmCTest::EXPERIMENTAL);
1741 this->SetTest("Configure");
1742 } else if (targ == "ExperimentalBuild") {
1743 this->SetTestModel(cmCTest::EXPERIMENTAL);
1744 this->SetTest("Build");
1745 } else if (targ == "ExperimentalTest") {
1746 this->SetTestModel(cmCTest::EXPERIMENTAL);
1747 this->SetTest("Test");
1748 } else if (targ == "ExperimentalMemCheck" || targ == "ExperimentalPurify") {
1749 this->SetTestModel(cmCTest::EXPERIMENTAL);
1750 this->SetTest("MemCheck");
1751 } else if (targ == "ExperimentalCoverage") {
1752 this->SetTestModel(cmCTest::EXPERIMENTAL);
1753 this->SetTest("Coverage");
1754 } else if (targ == "ExperimentalSubmit") {
1755 this->SetTestModel(cmCTest::EXPERIMENTAL);
1756 this->SetTest("Submit");
1757 } else if (targ == "Continuous") {
1758 this->SetTestModel(cmCTest::CONTINUOUS);
1759 this->SetTest("Start");
1760 this->SetTest("Update");
1761 this->SetTest("Configure");
1762 this->SetTest("Build");
1763 this->SetTest("Test");
1764 this->SetTest("Coverage");
1765 this->SetTest("Submit");
1766 } else if (targ == "ContinuousStart") {
1767 this->SetTestModel(cmCTest::CONTINUOUS);
1768 this->SetTest("Start");
1769 } else if (targ == "ContinuousUpdate") {
1770 this->SetTestModel(cmCTest::CONTINUOUS);
1771 this->SetTest("Update");
1772 } else if (targ == "ContinuousConfigure") {
1773 this->SetTestModel(cmCTest::CONTINUOUS);
1774 this->SetTest("Configure");
1775 } else if (targ == "ContinuousBuild") {
1776 this->SetTestModel(cmCTest::CONTINUOUS);
1777 this->SetTest("Build");
1778 } else if (targ == "ContinuousTest") {
1779 this->SetTestModel(cmCTest::CONTINUOUS);
1780 this->SetTest("Test");
1781 } else if (targ == "ContinuousMemCheck" || targ == "ContinuousPurify") {
1782 this->SetTestModel(cmCTest::CONTINUOUS);
1783 this->SetTest("MemCheck");
1784 } else if (targ == "ContinuousCoverage") {
1785 this->SetTestModel(cmCTest::CONTINUOUS);
1786 this->SetTest("Coverage");
1787 } else if (targ == "ContinuousSubmit") {
1788 this->SetTestModel(cmCTest::CONTINUOUS);
1789 this->SetTest("Submit");
1790 } else if (targ == "Nightly") {
1791 this->SetTestModel(cmCTest::NIGHTLY);
1792 this->SetTest("Start");
1793 this->SetTest("Update");
1794 this->SetTest("Configure");
1795 this->SetTest("Build");
1796 this->SetTest("Test");
1797 this->SetTest("Coverage");
1798 this->SetTest("Submit");
1799 } else if (targ == "NightlyStart") {
1800 this->SetTestModel(cmCTest::NIGHTLY);
1801 this->SetTest("Start");
1802 } else if (targ == "NightlyUpdate") {
1803 this->SetTestModel(cmCTest::NIGHTLY);
1804 this->SetTest("Update");
1805 } else if (targ == "NightlyConfigure") {
1806 this->SetTestModel(cmCTest::NIGHTLY);
1807 this->SetTest("Configure");
1808 } else if (targ == "NightlyBuild") {
1809 this->SetTestModel(cmCTest::NIGHTLY);
1810 this->SetTest("Build");
1811 } else if (targ == "NightlyTest") {
1812 this->SetTestModel(cmCTest::NIGHTLY);
1813 this->SetTest("Test");
1814 } else if (targ == "NightlyMemCheck" || targ == "NightlyPurify") {
1815 this->SetTestModel(cmCTest::NIGHTLY);
1816 this->SetTest("MemCheck");
1817 } else if (targ == "NightlyCoverage") {
1818 this->SetTestModel(cmCTest::NIGHTLY);
1819 this->SetTest("Coverage");
1820 } else if (targ == "NightlySubmit") {
1821 this->SetTestModel(cmCTest::NIGHTLY);
1822 this->SetTest("Submit");
1823 } else if (targ == "MemoryCheck") {
1824 this->SetTestModel(cmCTest::EXPERIMENTAL);
1825 this->SetTest("Start");
1826 this->SetTest("Configure");
1827 this->SetTest("Build");
1828 this->SetTest("MemCheck");
1829 this->SetTest("Coverage");
1830 this->SetTest("Submit");
1831 } else if (targ == "NightlyMemoryCheck") {
1832 this->SetTestModel(cmCTest::NIGHTLY);
1833 this->SetTest("Start");
1834 this->SetTest("Update");
1835 this->SetTest("Configure");
1836 this->SetTest("Build");
1837 this->SetTest("MemCheck");
1838 this->SetTest("Coverage");
1839 this->SetTest("Submit");
1840 } else {
1841 return false;
1843 return true;
1846 void cmCTest::ErrorMessageUnknownDashDValue(std::string& val)
1848 cmCTestLog(this, ERROR_MESSAGE,
1849 "CTest -D called with incorrect option: " << val << '\n');
1851 cmCTestLog(this, ERROR_MESSAGE,
1852 "Available options are:\n"
1853 " ctest -D Continuous\n"
1854 " ctest -D Continuous(Start|Update|Configure|Build)\n"
1855 " ctest -D Continuous(Test|Coverage|MemCheck|Submit)\n"
1856 " ctest -D Experimental\n"
1857 " ctest -D Experimental(Start|Update|Configure|Build)\n"
1858 " ctest -D Experimental(Test|Coverage|MemCheck|Submit)\n"
1859 " ctest -D Nightly\n"
1860 " ctest -D Nightly(Start|Update|Configure|Build)\n"
1861 " ctest -D Nightly(Test|Coverage|MemCheck|Submit)\n"
1862 " ctest -D NightlyMemoryCheck\n");
1865 bool cmCTest::CheckArgument(const std::string& arg, cm::string_view varg1,
1866 const char* varg2)
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 cm::string_view noTestsPrefix = "--no-tests=";
1879 if (this->CheckArgument(arg, "-F"_s)) {
1880 this->Impl->Failover = true;
1881 } else if (this->CheckArgument(arg, "-j"_s, "--parallel")) {
1882 cm::optional<size_t> parallelLevel;
1883 // No value or an empty value tells ctest to choose a default.
1884 if (i + 1 < args.size() && !cmHasLiteralPrefix(args[i + 1], "-")) {
1885 ++i;
1886 if (!args[i].empty()) {
1887 // A non-empty value must be a non-negative integer.
1888 unsigned long plevel = 0;
1889 if (!cmStrToULong(args[i], &plevel)) {
1890 errormsg =
1891 cmStrCat("'", arg, "' given invalid value '", args[i], "'");
1892 return false;
1894 parallelLevel = plevel;
1897 this->SetParallelLevel(parallelLevel);
1898 this->Impl->ParallelLevelSetInCli = true;
1899 } else if (cmHasPrefix(arg, "-j")) {
1900 // The value must be a non-negative integer.
1901 unsigned long plevel = 0;
1902 if (!cmStrToULong(arg.substr(2), &plevel)) {
1903 errormsg = cmStrCat("'", arg, "' given invalid value '", args[i], "'");
1904 return false;
1906 this->SetParallelLevel(plevel);
1907 this->Impl->ParallelLevelSetInCli = true;
1910 else if (this->CheckArgument(arg, "--repeat-until-fail"_s)) {
1911 if (i >= args.size() - 1) {
1912 errormsg = "'--repeat-until-fail' requires an argument";
1913 return false;
1915 if (this->Impl->RepeatMode != cmCTest::Repeat::Never) {
1916 errormsg = "At most one '--repeat' option may be used.";
1917 return false;
1919 i++;
1920 long repeat = 1;
1921 if (!cmStrToLong(args[i], &repeat)) {
1922 errormsg = cmStrCat("'--repeat-until-fail' given non-integer value '",
1923 args[i], "'");
1924 return false;
1926 this->Impl->RepeatCount = static_cast<int>(repeat);
1927 if (repeat > 1) {
1928 this->Impl->RepeatMode = cmCTest::Repeat::UntilFail;
1932 else if (this->CheckArgument(arg, "--repeat"_s)) {
1933 if (i >= args.size() - 1) {
1934 errormsg = "'--repeat' requires an argument";
1935 return false;
1937 if (this->Impl->RepeatMode != cmCTest::Repeat::Never) {
1938 errormsg = "At most one '--repeat' option may be used.";
1939 return false;
1941 i++;
1942 cmsys::RegularExpression repeatRegex(
1943 "^(until-fail|until-pass|after-timeout):([0-9]+)$");
1944 if (repeatRegex.find(args[i])) {
1945 std::string const& count = repeatRegex.match(2);
1946 unsigned long n = 1;
1947 cmStrToULong(count, &n); // regex guarantees success
1948 this->Impl->RepeatCount = static_cast<int>(n);
1949 if (this->Impl->RepeatCount > 1) {
1950 std::string const& mode = repeatRegex.match(1);
1951 if (mode == "until-fail") {
1952 this->Impl->RepeatMode = cmCTest::Repeat::UntilFail;
1953 } else if (mode == "until-pass") {
1954 this->Impl->RepeatMode = cmCTest::Repeat::UntilPass;
1955 } else if (mode == "after-timeout") {
1956 this->Impl->RepeatMode = cmCTest::Repeat::AfterTimeout;
1959 } else {
1960 errormsg = cmStrCat("'--repeat' given invalid value '", args[i], "'");
1961 return false;
1965 else if (this->CheckArgument(arg, "--test-load"_s) && i < args.size() - 1) {
1966 i++;
1967 unsigned long load;
1968 if (cmStrToULong(args[i], &load)) {
1969 this->SetTestLoad(load);
1970 } else {
1971 cmCTestLog(this, WARNING,
1972 "Invalid value for 'Test Load' : " << args[i] << '\n');
1976 else if (this->CheckArgument(arg, "--no-compress-output"_s)) {
1977 this->Impl->CompressTestOutput = false;
1980 else if (this->CheckArgument(arg, "--print-labels"_s)) {
1981 this->Impl->PrintLabels = true;
1984 else if (this->CheckArgument(arg, "--http1.0"_s)) {
1985 this->Impl->UseHTTP10 = true;
1988 else if (this->CheckArgument(arg, "--timeout"_s) && i < args.size() - 1) {
1989 i++;
1990 auto timeout = cmDuration(atof(args[i].c_str()));
1991 this->Impl->GlobalTimeout = timeout;
1994 else if (this->CheckArgument(arg, "--stop-time"_s) && i < args.size() - 1) {
1995 i++;
1996 this->SetStopTime(args[i]);
1999 else if (this->CheckArgument(arg, "--stop-on-failure"_s)) {
2000 this->Impl->StopOnFailure = true;
2003 else if (this->CheckArgument(arg, "-C"_s, "--build-config") &&
2004 i < args.size() - 1) {
2005 i++;
2006 this->SetConfigType(args[i]);
2009 else if (this->CheckArgument(arg, "--debug"_s)) {
2010 this->Impl->Debug = true;
2011 this->Impl->ShowLineNumbers = true;
2012 } else if ((this->CheckArgument(arg, "--group"_s) ||
2013 // This is an undocumented / deprecated option.
2014 // "Track" has been renamed to "Group".
2015 this->CheckArgument(arg, "--track"_s)) &&
2016 i < args.size() - 1) {
2017 i++;
2018 this->Impl->SpecificGroup = args[i];
2019 } else if (this->CheckArgument(arg, "--show-line-numbers"_s)) {
2020 this->Impl->ShowLineNumbers = true;
2021 } else if (this->CheckArgument(arg, "--no-label-summary"_s)) {
2022 this->Impl->LabelSummary = false;
2023 } else if (this->CheckArgument(arg, "--no-subproject-summary"_s)) {
2024 this->Impl->SubprojectSummary = false;
2025 } else if (this->CheckArgument(arg, "-Q"_s, "--quiet")) {
2026 this->Impl->Quiet = true;
2027 } else if (this->CheckArgument(arg, "--progress"_s)) {
2028 this->Impl->TestProgressOutput = true;
2029 } else if (this->CheckArgument(arg, "-V"_s, "--verbose")) {
2030 this->Impl->Verbose = true;
2031 } else if (this->CheckArgument(arg, "-VV"_s, "--extra-verbose")) {
2032 this->Impl->ExtraVerbose = true;
2033 this->Impl->Verbose = true;
2034 } else if (this->CheckArgument(arg, "--output-on-failure"_s)) {
2035 this->Impl->OutputTestOutputOnTestFailure = true;
2036 } else if (this->CheckArgument(arg, "--test-output-size-passed"_s) &&
2037 i < args.size() - 1) {
2038 i++;
2039 long outputSize;
2040 if (cmStrToLong(args[i], &outputSize)) {
2041 this->Impl->TestHandler.SetTestOutputSizePassed(
2042 static_cast<int>(outputSize));
2043 } else {
2044 cmCTestLog(this, WARNING,
2045 "Invalid value for '--test-output-size-passed': " << args[i]
2046 << "\n");
2048 } else if (this->CheckArgument(arg, "--test-output-size-failed"_s) &&
2049 i < args.size() - 1) {
2050 i++;
2051 long outputSize;
2052 if (cmStrToLong(args[i], &outputSize)) {
2053 this->Impl->TestHandler.SetTestOutputSizeFailed(
2054 static_cast<int>(outputSize));
2055 } else {
2056 cmCTestLog(this, WARNING,
2057 "Invalid value for '--test-output-size-failed': " << args[i]
2058 << "\n");
2060 } else if (this->CheckArgument(arg, "--test-output-truncation"_s) &&
2061 i < args.size() - 1) {
2062 i++;
2063 if (!this->Impl->TestHandler.SetTestOutputTruncation(args[i])) {
2064 errormsg = "Invalid value for '--test-output-truncation': " + args[i];
2065 return false;
2067 } else if (this->CheckArgument(arg, "-N"_s, "--show-only")) {
2068 this->Impl->ShowOnly = true;
2069 } else if (cmHasLiteralPrefix(arg, "--show-only=")) {
2070 this->Impl->ShowOnly = true;
2072 // Check if a specific format is requested. Defaults to human readable
2073 // text.
2074 std::string argWithFormat = "--show-only=";
2075 std::string format = arg.substr(argWithFormat.length());
2076 if (format == "json-v1") {
2077 // Force quiet mode so the only output is the json object model.
2078 this->Impl->Quiet = true;
2079 this->Impl->OutputAsJson = true;
2080 this->Impl->OutputAsJsonVersion = 1;
2081 } else if (format != "human") {
2082 errormsg = "'--show-only=' given unknown value '" + format + "'";
2083 return false;
2087 else if (this->CheckArgument(arg, "-O"_s, "--output-log") &&
2088 i < args.size() - 1) {
2089 i++;
2090 this->SetOutputLogFileName(args[i]);
2093 else if (this->CheckArgument(arg, "--tomorrow-tag"_s)) {
2094 this->Impl->TomorrowTag = true;
2095 } else if (this->CheckArgument(arg, "--force-new-ctest-process"_s)) {
2096 this->Impl->ForceNewCTestProcess = true;
2097 } else if (this->CheckArgument(arg, "-W"_s, "--max-width") &&
2098 i < args.size() - 1) {
2099 i++;
2100 this->Impl->MaxTestNameWidth = atoi(args[i].c_str());
2101 } else if (this->CheckArgument(arg, "--interactive-debug-mode"_s) &&
2102 i < args.size() - 1) {
2103 i++;
2104 this->Impl->InteractiveDebugMode = cmIsOn(args[i]);
2105 } else if (this->CheckArgument(arg, "--submit-index"_s) &&
2106 i < args.size() - 1) {
2107 i++;
2108 this->Impl->SubmitIndex = atoi(args[i].c_str());
2109 if (this->Impl->SubmitIndex < 0) {
2110 this->Impl->SubmitIndex = 0;
2114 else if (this->CheckArgument(arg, "--overwrite"_s) && i < args.size() - 1) {
2115 i++;
2116 this->AddCTestConfigurationOverwrite(args[i]);
2117 } else if (this->CheckArgument(arg, "-A"_s, "--add-notes") &&
2118 i < args.size() - 1) {
2119 this->Impl->ProduceXML = true;
2120 this->SetTest("Notes");
2121 i++;
2122 this->SetNotesFiles(args[i]);
2123 return true;
2124 } else if (this->CheckArgument(arg, "--test-dir"_s)) {
2125 if (i >= args.size() - 1) {
2126 errormsg = "'--test-dir' requires an argument";
2127 return false;
2129 i++;
2130 this->Impl->TestDir = std::string(args[i]);
2131 } else if (this->CheckArgument(arg, "--output-junit"_s)) {
2132 if (i >= args.size() - 1) {
2133 errormsg = "'--output-junit' requires an argument";
2134 return false;
2136 i++;
2137 this->SetOutputJUnitFileName(std::string(args[i]));
2140 else if (cmHasPrefix(arg, noTestsPrefix)) {
2141 cm::string_view noTestsMode =
2142 cm::string_view(arg).substr(noTestsPrefix.length());
2143 if (noTestsMode == "error") {
2144 this->Impl->NoTestsMode = cmCTest::NoTests::Error;
2145 } else if (noTestsMode != "ignore") {
2146 errormsg =
2147 cmStrCat("'--no-tests=' given unknown value '", noTestsMode, '\'');
2148 return false;
2149 } else {
2150 this->Impl->NoTestsMode = cmCTest::NoTests::Ignore;
2152 this->Impl->NoTestsModeSetInCli = true;
2155 // options that control what tests are run
2156 else if (this->CheckArgument(arg, "-I"_s, "--tests-information") &&
2157 i < args.size() - 1) {
2158 i++;
2159 this->GetTestHandler()->SetPersistentOption("TestsToRunInformation",
2160 args[i]);
2161 this->GetMemCheckHandler()->SetPersistentOption("TestsToRunInformation",
2162 args[i]);
2163 } else if (this->CheckArgument(arg, "-U"_s, "--union")) {
2164 this->GetTestHandler()->SetPersistentOption("UseUnion", "true");
2165 this->GetMemCheckHandler()->SetPersistentOption("UseUnion", "true");
2166 } else if (this->CheckArgument(arg, "-R"_s, "--tests-regex") &&
2167 i < args.size() - 1) {
2168 i++;
2169 this->GetTestHandler()->SetPersistentOption("IncludeRegularExpression",
2170 args[i]);
2171 this->GetMemCheckHandler()->SetPersistentOption("IncludeRegularExpression",
2172 args[i]);
2173 } else if (this->CheckArgument(arg, "-L"_s, "--label-regex") &&
2174 i < args.size() - 1) {
2175 i++;
2176 this->GetTestHandler()->AddPersistentMultiOption("LabelRegularExpression",
2177 args[i]);
2178 this->GetMemCheckHandler()->AddPersistentMultiOption(
2179 "LabelRegularExpression", args[i]);
2180 } else if (this->CheckArgument(arg, "-LE"_s, "--label-exclude") &&
2181 i < args.size() - 1) {
2182 i++;
2183 this->GetTestHandler()->AddPersistentMultiOption(
2184 "ExcludeLabelRegularExpression", args[i]);
2185 this->GetMemCheckHandler()->AddPersistentMultiOption(
2186 "ExcludeLabelRegularExpression", args[i]);
2189 else if (this->CheckArgument(arg, "-E"_s, "--exclude-regex") &&
2190 i < args.size() - 1) {
2191 i++;
2192 this->GetTestHandler()->SetPersistentOption("ExcludeRegularExpression",
2193 args[i]);
2194 this->GetMemCheckHandler()->SetPersistentOption("ExcludeRegularExpression",
2195 args[i]);
2198 else if (this->CheckArgument(arg, "-FA"_s, "--fixture-exclude-any") &&
2199 i < args.size() - 1) {
2200 i++;
2201 this->GetTestHandler()->SetPersistentOption(
2202 "ExcludeFixtureRegularExpression", args[i]);
2203 this->GetMemCheckHandler()->SetPersistentOption(
2204 "ExcludeFixtureRegularExpression", args[i]);
2205 } else if (this->CheckArgument(arg, "-FS"_s, "--fixture-exclude-setup") &&
2206 i < args.size() - 1) {
2207 i++;
2208 this->GetTestHandler()->SetPersistentOption(
2209 "ExcludeFixtureSetupRegularExpression", args[i]);
2210 this->GetMemCheckHandler()->SetPersistentOption(
2211 "ExcludeFixtureSetupRegularExpression", args[i]);
2212 } else if (this->CheckArgument(arg, "-FC"_s, "--fixture-exclude-cleanup") &&
2213 i < args.size() - 1) {
2214 i++;
2215 this->GetTestHandler()->SetPersistentOption(
2216 "ExcludeFixtureCleanupRegularExpression", args[i]);
2217 this->GetMemCheckHandler()->SetPersistentOption(
2218 "ExcludeFixtureCleanupRegularExpression", args[i]);
2221 else if (this->CheckArgument(arg, "--resource-spec-file"_s) &&
2222 i < args.size() - 1) {
2223 i++;
2224 this->GetTestHandler()->SetPersistentOption("ResourceSpecFile", args[i]);
2225 this->GetMemCheckHandler()->SetPersistentOption("ResourceSpecFile",
2226 args[i]);
2229 else if (this->CheckArgument(arg, "--tests-from-file"_s) &&
2230 i < args.size() - 1) {
2231 i++;
2232 this->GetTestHandler()->SetPersistentOption("TestListFile", args[i]);
2233 this->GetMemCheckHandler()->SetPersistentOption("TestListFile", args[i]);
2236 else if (this->CheckArgument(arg, "--exclude-from-file"_s) &&
2237 i < args.size() - 1) {
2238 i++;
2239 this->GetTestHandler()->SetPersistentOption("ExcludeTestListFile",
2240 args[i]);
2241 this->GetMemCheckHandler()->SetPersistentOption("ExcludeTestListFile",
2242 args[i]);
2245 else if (this->CheckArgument(arg, "--rerun-failed"_s)) {
2246 this->GetTestHandler()->SetPersistentOption("RerunFailed", "true");
2247 this->GetMemCheckHandler()->SetPersistentOption("RerunFailed", "true");
2248 } else {
2249 return false;
2251 return true;
2254 #if !defined(_WIN32)
2255 bool cmCTest::ConsoleIsNotDumb()
2257 std::string term_env_variable;
2258 if (cmSystemTools::GetEnv("TERM", term_env_variable)) {
2259 return isatty(1) && term_env_variable != "dumb";
2261 return false;
2263 #endif
2265 bool cmCTest::ProgressOutputSupportedByConsole()
2267 #if defined(_WIN32)
2268 // On Windows we need a console buffer.
2269 void* console = GetStdHandle(STD_OUTPUT_HANDLE);
2270 CONSOLE_SCREEN_BUFFER_INFO csbi;
2271 return GetConsoleScreenBufferInfo(console, &csbi);
2272 #else
2273 // On UNIX we need a non-dumb tty.
2274 return ConsoleIsNotDumb();
2275 #endif
2278 bool cmCTest::ColoredOutputSupportedByConsole()
2280 std::string clicolor_force;
2281 if (cmSystemTools::GetEnv("CLICOLOR_FORCE", clicolor_force) &&
2282 !clicolor_force.empty() && clicolor_force != "0") {
2283 return true;
2285 std::string clicolor;
2286 if (cmSystemTools::GetEnv("CLICOLOR", clicolor) && clicolor == "0") {
2287 return false;
2289 #if defined(_WIN32)
2290 // Not supported on Windows
2291 return false;
2292 #else
2293 // On UNIX we need a non-dumb tty.
2294 return ConsoleIsNotDumb();
2295 #endif
2298 // handle the -S -SR and -SP arguments
2299 bool cmCTest::HandleScriptArguments(size_t& i, std::vector<std::string>& args,
2300 bool& SRArgumentSpecified)
2302 std::string arg = args[i];
2303 if (this->CheckArgument(arg, "-SP"_s, "--script-new-process") &&
2304 i < args.size() - 1) {
2305 this->Impl->RunConfigurationScript = true;
2306 i++;
2307 cmCTestScriptHandler* ch = this->GetScriptHandler();
2308 // -SR is an internal argument, -SP should be ignored when it is passed
2309 if (!SRArgumentSpecified) {
2310 ch->AddConfigurationScript(args[i], false);
2314 else if (this->CheckArgument(arg, "-SR"_s, "--script-run") &&
2315 i < args.size() - 1) {
2316 SRArgumentSpecified = true;
2317 this->Impl->RunConfigurationScript = true;
2318 i++;
2319 cmCTestScriptHandler* ch = this->GetScriptHandler();
2320 ch->AddConfigurationScript(args[i], true);
2323 else if (this->CheckArgument(arg, "-S"_s, "--script") &&
2324 i < args.size() - 1) {
2325 this->Impl->RunConfigurationScript = true;
2326 i++;
2327 cmCTestScriptHandler* ch = this->GetScriptHandler();
2328 // -SR is an internal argument, -S should be ignored when it is passed
2329 if (!SRArgumentSpecified) {
2330 ch->AddConfigurationScript(args[i], true);
2332 } else {
2333 return false;
2335 return true;
2338 bool cmCTest::AddVariableDefinition(const std::string& arg)
2340 std::string name;
2341 std::string value;
2342 cmStateEnums::CacheEntryType type = cmStateEnums::UNINITIALIZED;
2344 if (cmake::ParseCacheEntry(arg, name, value, type)) {
2345 this->Impl->Definitions[name] = value;
2346 return true;
2349 return false;
2352 void cmCTest::SetPersistentOptionIfNotEmpty(const std::string& value,
2353 const std::string& optionName)
2355 if (!value.empty()) {
2356 this->GetTestHandler()->SetPersistentOption(optionName, value);
2357 this->GetMemCheckHandler()->SetPersistentOption(optionName, value);
2361 void cmCTest::AddPersistentMultiOptionIfNotEmpty(const std::string& value,
2362 const std::string& optionName)
2364 if (!value.empty()) {
2365 this->GetTestHandler()->AddPersistentMultiOption(optionName, value);
2366 this->GetMemCheckHandler()->AddPersistentMultiOption(optionName, value);
2370 bool cmCTest::SetArgsFromPreset(const std::string& presetName,
2371 bool listPresets)
2373 const auto workingDirectory = cmSystemTools::GetCurrentWorkingDirectory();
2375 cmCMakePresetsGraph settingsFile;
2376 auto result = settingsFile.ReadProjectPresets(workingDirectory);
2377 if (result != true) {
2378 cmSystemTools::Error(cmStrCat("Could not read presets from ",
2379 workingDirectory, ":",
2380 settingsFile.parseState.GetErrorMessage()));
2381 return false;
2384 if (listPresets) {
2385 settingsFile.PrintTestPresetList();
2386 return true;
2389 auto presetPair = settingsFile.TestPresets.find(presetName);
2390 if (presetPair == settingsFile.TestPresets.end()) {
2391 cmSystemTools::Error(cmStrCat("No such test preset in ", workingDirectory,
2392 ": \"", presetName, '"'));
2393 settingsFile.PrintTestPresetList();
2394 return false;
2397 if (presetPair->second.Unexpanded.Hidden) {
2398 cmSystemTools::Error(cmStrCat("Cannot use hidden test preset in ",
2399 workingDirectory, ": \"", presetName, '"'));
2400 settingsFile.PrintTestPresetList();
2401 return false;
2404 auto const& expandedPreset = presetPair->second.Expanded;
2405 if (!expandedPreset) {
2406 cmSystemTools::Error(cmStrCat("Could not evaluate test preset \"",
2407 presetName, "\": Invalid macro expansion"));
2408 settingsFile.PrintTestPresetList();
2409 return false;
2412 if (!expandedPreset->ConditionResult) {
2413 cmSystemTools::Error(cmStrCat("Cannot use disabled test preset in ",
2414 workingDirectory, ": \"", presetName, '"'));
2415 settingsFile.PrintTestPresetList();
2416 return false;
2419 auto configurePresetPair =
2420 settingsFile.ConfigurePresets.find(expandedPreset->ConfigurePreset);
2421 if (configurePresetPair == settingsFile.ConfigurePresets.end()) {
2422 cmSystemTools::Error(cmStrCat("No such configure preset in ",
2423 workingDirectory, ": \"",
2424 expandedPreset->ConfigurePreset, '"'));
2425 settingsFile.PrintConfigurePresetList();
2426 return false;
2429 if (configurePresetPair->second.Unexpanded.Hidden) {
2430 cmSystemTools::Error(cmStrCat("Cannot use hidden configure preset in ",
2431 workingDirectory, ": \"",
2432 expandedPreset->ConfigurePreset, '"'));
2433 settingsFile.PrintConfigurePresetList();
2434 return false;
2437 auto const& expandedConfigurePreset = configurePresetPair->second.Expanded;
2438 if (!expandedConfigurePreset) {
2439 cmSystemTools::Error(cmStrCat("Could not evaluate configure preset \"",
2440 expandedPreset->ConfigurePreset,
2441 "\": Invalid macro expansion"));
2442 return false;
2445 auto presetEnvironment = expandedPreset->Environment;
2446 for (auto const& var : presetEnvironment) {
2447 if (var.second) {
2448 cmSystemTools::PutEnv(cmStrCat(var.first, '=', *var.second));
2452 if (!expandedPreset->Configuration.empty()) {
2453 this->SetConfigType(expandedPreset->Configuration);
2456 // Set build directory to value specified by the configure preset.
2457 this->AddCTestConfigurationOverwrite(
2458 cmStrCat("BuildDirectory=", expandedConfigurePreset->BinaryDir));
2459 for (const auto& kvp : expandedPreset->OverwriteConfigurationFile) {
2460 this->AddCTestConfigurationOverwrite(kvp);
2463 if (expandedPreset->Output) {
2464 this->Impl->TestProgressOutput =
2465 expandedPreset->Output->ShortProgress.value_or(false);
2467 if (expandedPreset->Output->Verbosity) {
2468 const auto& verbosity = *expandedPreset->Output->Verbosity;
2469 switch (verbosity) {
2470 case cmCMakePresetsGraph::TestPreset::OutputOptions::VerbosityEnum::
2471 Extra:
2472 this->Impl->ExtraVerbose = true;
2473 CM_FALLTHROUGH;
2474 case cmCMakePresetsGraph::TestPreset::OutputOptions::VerbosityEnum::
2475 Verbose:
2476 this->Impl->Verbose = true;
2477 break;
2478 case cmCMakePresetsGraph::TestPreset::OutputOptions::VerbosityEnum::
2479 Default:
2480 default:
2481 // leave default settings
2482 break;
2486 this->Impl->Debug = expandedPreset->Output->Debug.value_or(false);
2487 this->Impl->ShowLineNumbers =
2488 expandedPreset->Output->Debug.value_or(false);
2489 this->Impl->OutputTestOutputOnTestFailure =
2490 expandedPreset->Output->OutputOnFailure.value_or(false);
2491 this->Impl->Quiet = expandedPreset->Output->Quiet.value_or(false);
2493 if (!expandedPreset->Output->OutputLogFile.empty()) {
2494 this->SetOutputLogFileName(expandedPreset->Output->OutputLogFile);
2496 if (!expandedPreset->Output->OutputJUnitFile.empty()) {
2497 this->SetOutputJUnitFileName(expandedPreset->Output->OutputJUnitFile);
2500 this->Impl->LabelSummary =
2501 expandedPreset->Output->LabelSummary.value_or(true);
2502 this->Impl->SubprojectSummary =
2503 expandedPreset->Output->SubprojectSummary.value_or(true);
2505 if (expandedPreset->Output->MaxPassedTestOutputSize) {
2506 this->Impl->TestHandler.SetTestOutputSizePassed(
2507 *expandedPreset->Output->MaxPassedTestOutputSize);
2510 if (expandedPreset->Output->MaxFailedTestOutputSize) {
2511 this->Impl->TestHandler.SetTestOutputSizeFailed(
2512 *expandedPreset->Output->MaxFailedTestOutputSize);
2515 if (expandedPreset->Output->TestOutputTruncation) {
2516 this->Impl->TestHandler.TestOutputTruncation =
2517 *expandedPreset->Output->TestOutputTruncation;
2520 if (expandedPreset->Output->MaxTestNameWidth) {
2521 this->Impl->MaxTestNameWidth = *expandedPreset->Output->MaxTestNameWidth;
2525 if (expandedPreset->Filter) {
2526 if (expandedPreset->Filter->Include) {
2527 this->SetPersistentOptionIfNotEmpty(
2528 expandedPreset->Filter->Include->Name, "IncludeRegularExpression");
2529 this->AddPersistentMultiOptionIfNotEmpty(
2530 expandedPreset->Filter->Include->Label, "LabelRegularExpression");
2532 if (expandedPreset->Filter->Include->Index) {
2533 if (expandedPreset->Filter->Include->Index->IndexFile.empty()) {
2534 const auto& start = expandedPreset->Filter->Include->Index->Start;
2535 const auto& end = expandedPreset->Filter->Include->Index->End;
2536 const auto& stride = expandedPreset->Filter->Include->Index->Stride;
2537 std::string indexOptions;
2538 indexOptions += (start ? std::to_string(*start) : "") + ",";
2539 indexOptions += (end ? std::to_string(*end) : "") + ",";
2540 indexOptions += (stride ? std::to_string(*stride) : "") + ",";
2541 indexOptions +=
2542 cmJoin(expandedPreset->Filter->Include->Index->SpecificTests, ",");
2544 this->SetPersistentOptionIfNotEmpty(indexOptions,
2545 "TestsToRunInformation");
2546 } else {
2547 this->SetPersistentOptionIfNotEmpty(
2548 expandedPreset->Filter->Include->Index->IndexFile,
2549 "TestsToRunInformation");
2553 if (expandedPreset->Filter->Include->UseUnion.value_or(false)) {
2554 this->GetTestHandler()->SetPersistentOption("UseUnion", "true");
2555 this->GetMemCheckHandler()->SetPersistentOption("UseUnion", "true");
2559 if (expandedPreset->Filter->Exclude) {
2560 this->SetPersistentOptionIfNotEmpty(
2561 expandedPreset->Filter->Exclude->Name, "ExcludeRegularExpression");
2562 this->AddPersistentMultiOptionIfNotEmpty(
2563 expandedPreset->Filter->Exclude->Label,
2564 "ExcludeLabelRegularExpression");
2566 if (expandedPreset->Filter->Exclude->Fixtures) {
2567 this->SetPersistentOptionIfNotEmpty(
2568 expandedPreset->Filter->Exclude->Fixtures->Any,
2569 "ExcludeFixtureRegularExpression");
2570 this->SetPersistentOptionIfNotEmpty(
2571 expandedPreset->Filter->Exclude->Fixtures->Setup,
2572 "ExcludeFixtureSetupRegularExpression");
2573 this->SetPersistentOptionIfNotEmpty(
2574 expandedPreset->Filter->Exclude->Fixtures->Cleanup,
2575 "ExcludeFixtureCleanupRegularExpression");
2580 if (expandedPreset->Execution) {
2581 this->Impl->StopOnFailure =
2582 expandedPreset->Execution->StopOnFailure.value_or(false);
2583 this->Impl->Failover =
2584 expandedPreset->Execution->EnableFailover.value_or(false);
2586 if (expandedPreset->Execution->Jobs) {
2587 auto jobs = *expandedPreset->Execution->Jobs;
2588 this->SetParallelLevel(jobs);
2589 this->Impl->ParallelLevelSetInCli = true;
2592 this->SetPersistentOptionIfNotEmpty(
2593 expandedPreset->Execution->ResourceSpecFile, "ResourceSpecFile");
2595 if (expandedPreset->Execution->TestLoad) {
2596 auto testLoad = *expandedPreset->Execution->TestLoad;
2597 this->SetTestLoad(testLoad);
2600 if (expandedPreset->Execution->ShowOnly) {
2601 this->Impl->ShowOnly = true;
2603 switch (*expandedPreset->Execution->ShowOnly) {
2604 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::ShowOnlyEnum::
2605 JsonV1:
2606 this->Impl->Quiet = true;
2607 this->Impl->OutputAsJson = true;
2608 this->Impl->OutputAsJsonVersion = 1;
2609 break;
2610 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::ShowOnlyEnum::
2611 Human:
2612 // intentional fallthrough (human is the default)
2613 default:
2614 break;
2618 if (expandedPreset->Execution->Repeat) {
2619 this->Impl->RepeatCount = expandedPreset->Execution->Repeat->Count;
2620 switch (expandedPreset->Execution->Repeat->Mode) {
2621 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::RepeatOptions::
2622 ModeEnum::UntilFail:
2623 this->Impl->RepeatMode = cmCTest::Repeat::UntilFail;
2624 break;
2625 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::RepeatOptions::
2626 ModeEnum::UntilPass:
2627 this->Impl->RepeatMode = cmCTest::Repeat::UntilPass;
2628 break;
2629 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::RepeatOptions::
2630 ModeEnum::AfterTimeout:
2631 this->Impl->RepeatMode = cmCTest::Repeat::AfterTimeout;
2632 break;
2633 default:
2634 // should never default since mode is required
2635 return false;
2639 if (expandedPreset->Execution->InteractiveDebugging) {
2640 this->Impl->InteractiveDebugMode =
2641 *expandedPreset->Execution->InteractiveDebugging;
2644 if (expandedPreset->Execution->ScheduleRandom.value_or(false)) {
2645 this->Impl->ScheduleType = "Random";
2648 if (expandedPreset->Execution->Timeout) {
2649 this->Impl->GlobalTimeout =
2650 cmDuration(*expandedPreset->Execution->Timeout);
2653 if (expandedPreset->Execution->NoTestsAction) {
2654 switch (*expandedPreset->Execution->NoTestsAction) {
2655 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::
2656 NoTestsActionEnum::Error:
2657 this->Impl->NoTestsMode = cmCTest::NoTests::Error;
2658 break;
2659 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::
2660 NoTestsActionEnum::Ignore:
2661 this->Impl->NoTestsMode = cmCTest::NoTests::Ignore;
2662 break;
2663 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::
2664 NoTestsActionEnum::Default:
2665 break;
2666 default:
2667 // should never default
2668 return false;
2673 return true;
2676 // the main entry point of ctest, called from main
2677 int cmCTest::Run(std::vector<std::string>& args, std::string* output)
2679 const char* ctestExec = "ctest";
2680 bool cmakeAndTest = false;
2681 bool executeTests = true;
2682 bool SRArgumentSpecified = false;
2684 // copy the command line
2685 cm::append(this->Impl->InitialCommandLineArguments, args);
2687 // check if a test preset was specified
2689 bool listPresets =
2690 find(args.begin(), args.end(), "--list-presets") != args.end();
2691 auto it =
2692 std::find_if(args.begin(), args.end(), [](std::string const& arg) -> bool {
2693 return arg == "--preset" || cmHasLiteralPrefix(arg, "--preset=");
2695 if (listPresets || it != args.end()) {
2696 std::string errormsg;
2697 bool success;
2699 if (listPresets) {
2700 // If listing presets we don't need a presetName
2701 success = this->SetArgsFromPreset("", listPresets);
2702 } else {
2703 if (cmHasLiteralPrefix(*it, "--preset=")) {
2704 auto presetName = it->substr(9);
2705 success = this->SetArgsFromPreset(presetName, listPresets);
2706 } else if (++it != args.end()) {
2707 auto presetName = *it;
2708 success = this->SetArgsFromPreset(presetName, listPresets);
2709 } else {
2710 cmSystemTools::Error("'--preset' requires an argument");
2711 success = false;
2715 if (listPresets) {
2716 return success ? 0 : 1;
2719 if (!success) {
2720 return 1;
2724 // process the command line arguments
2725 for (size_t i = 1; i < args.size(); ++i) {
2726 // handle the simple commandline arguments
2727 std::string errormsg;
2728 bool validArg = this->HandleCommandLineArguments(i, args, errormsg);
2729 if (!validArg && !errormsg.empty()) {
2730 cmSystemTools::Error(errormsg);
2731 return 1;
2733 std::string arg = args[i];
2735 // handle the script arguments -S -SR -SP
2736 validArg =
2737 validArg || this->HandleScriptArguments(i, args, SRArgumentSpecified);
2739 // --dashboard: handle a request for a dashboard
2740 if (this->CheckArgument(arg, "-D"_s, "--dashboard") &&
2741 i < args.size() - 1) {
2742 this->Impl->ProduceXML = true;
2743 i++;
2744 std::string targ = args[i];
2745 // AddTestsForDashboard parses the dashboard type and converts it
2746 // into the separate stages
2747 if (!this->AddTestsForDashboardType(targ)) {
2748 if (!this->AddVariableDefinition(targ)) {
2749 this->ErrorMessageUnknownDashDValue(targ);
2750 executeTests = false;
2753 validArg = true;
2756 // If it's not exactly -D, but it starts with -D, then try to parse out
2757 // a variable definition from it, same as CMake does. Unsuccessful
2758 // attempts are simply ignored since previous ctest versions ignore
2759 // this too. (As well as many other unknown command line args.)
2761 if (arg != "-D" && cmHasLiteralPrefix(arg, "-D")) {
2762 std::string input = arg.substr(2);
2763 this->AddVariableDefinition(input);
2764 validArg = true;
2767 // --test-action: calls SetTest(<stage>, /*report=*/ false) to enable
2768 // the corresponding stage
2769 if (!this->HandleTestActionArgument(ctestExec, i, args, validArg)) {
2770 executeTests = false;
2773 // --test-model: what type of test model
2774 if (!this->HandleTestModelArgument(ctestExec, i, args, validArg)) {
2775 executeTests = false;
2778 // --extra-submit
2779 if (this->CheckArgument(arg, "--extra-submit"_s) && i < args.size() - 1) {
2780 this->Impl->ProduceXML = true;
2781 this->SetTest("Submit");
2782 i++;
2783 if (!this->SubmitExtraFiles(args[i])) {
2784 return 0;
2786 validArg = true;
2789 // --build-and-test options
2790 if (this->CheckArgument(arg, "--build-and-test"_s) &&
2791 i < args.size() - 1) {
2792 cmakeAndTest = true;
2793 validArg = true;
2796 // --schedule-random
2797 if (this->CheckArgument(arg, "--schedule-random"_s)) {
2798 this->Impl->ScheduleType = "Random";
2799 validArg = true;
2802 // pass the argument to all the handlers as well, but it may no longer be
2803 // set to what it was originally so I'm not sure this is working as
2804 // intended
2805 for (auto& handler : this->Impl->GetTestingHandlers()) {
2806 if (!handler->ProcessCommandLineArguments(arg, i, args, validArg)) {
2807 cmCTestLog(
2808 this, ERROR_MESSAGE,
2809 "Problem parsing command line arguments within a handler\n");
2810 return 0;
2814 if (!validArg && cmHasLiteralPrefix(arg, "-") &&
2815 !cmHasLiteralPrefix(arg, "--preset")) {
2816 cmSystemTools::Error(cmStrCat("Unknown argument: ", arg));
2817 cmSystemTools::Error("Run 'ctest --help' for all supported options.");
2818 return 1;
2820 } // the close of the for argument loop
2822 // handle CTEST_PARALLEL_LEVEL environment variable
2823 if (!this->Impl->ParallelLevelSetInCli) {
2824 if (cm::optional<std::string> parallelEnv =
2825 cmSystemTools::GetEnvVar("CTEST_PARALLEL_LEVEL")) {
2826 if (parallelEnv->empty() ||
2827 parallelEnv->find_first_not_of(" \t") == std::string::npos) {
2828 // An empty value tells ctest to choose a default.
2829 this->SetParallelLevel(cm::nullopt);
2830 } else {
2831 // A non-empty value must be a non-negative integer.
2832 // Otherwise, ignore it.
2833 unsigned long plevel = 0;
2834 if (cmStrToULong(*parallelEnv, &plevel)) {
2835 this->SetParallelLevel(plevel);
2841 // handle CTEST_NO_TESTS_ACTION environment variable
2842 if (!this->Impl->NoTestsModeSetInCli) {
2843 std::string action;
2844 if (cmSystemTools::GetEnv("CTEST_NO_TESTS_ACTION", action) &&
2845 !action.empty()) {
2846 if (action == "error"_s) {
2847 this->Impl->NoTestsMode = cmCTest::NoTests::Error;
2848 } else if (action == "ignore"_s) {
2849 this->Impl->NoTestsMode = cmCTest::NoTests::Ignore;
2850 } else {
2851 cmCTestLog(this, ERROR_MESSAGE,
2852 "Unknown value for CTEST_NO_TESTS_ACTION: '" << action
2853 << '\'');
2854 return 1;
2859 // TestProgressOutput only supported if console supports it and not logging
2860 // to a file
2861 this->Impl->TestProgressOutput = this->Impl->TestProgressOutput &&
2862 !this->Impl->OutputLogFile && this->ProgressOutputSupportedByConsole();
2863 #ifdef _WIN32
2864 if (this->Impl->TestProgressOutput) {
2865 // Disable output line buffering so we can print content without
2866 // a newline.
2867 std::setvbuf(stdout, nullptr, _IONBF, 0);
2869 #endif
2871 // now what should cmake do? if --build-and-test was specified then
2872 // we run the build and test handler and return
2873 if (cmakeAndTest) {
2874 return this->RunCMakeAndTest(output);
2877 if (executeTests) {
2878 return this->ExecuteTests();
2881 return 1;
2884 bool cmCTest::HandleTestActionArgument(const char* ctestExec, size_t& i,
2885 const std::vector<std::string>& args,
2886 bool& validArg)
2888 bool success = true;
2889 std::string const& arg = args[i];
2890 if (this->CheckArgument(arg, "-T"_s, "--test-action") &&
2891 (i < args.size() - 1)) {
2892 validArg = true;
2893 this->Impl->ProduceXML = true;
2894 i++;
2895 if (!this->SetTest(args[i], false)) {
2896 success = false;
2897 cmCTestLog(this, ERROR_MESSAGE,
2898 "CTest -T called with incorrect option: " << args[i] << '\n');
2899 /* clang-format off */
2900 cmCTestLog(this, ERROR_MESSAGE,
2901 "Available options are:\n"
2902 " " << ctestExec << " -T all\n"
2903 " " << ctestExec << " -T start\n"
2904 " " << ctestExec << " -T update\n"
2905 " " << ctestExec << " -T configure\n"
2906 " " << ctestExec << " -T build\n"
2907 " " << ctestExec << " -T test\n"
2908 " " << ctestExec << " -T coverage\n"
2909 " " << ctestExec << " -T memcheck\n"
2910 " " << ctestExec << " -T notes\n"
2911 " " << ctestExec << " -T submit\n");
2912 /* clang-format on */
2915 return success;
2918 bool cmCTest::HandleTestModelArgument(const char* ctestExec, size_t& i,
2919 const std::vector<std::string>& args,
2920 bool& validArg)
2922 bool success = true;
2923 std::string const& arg = args[i];
2924 if (this->CheckArgument(arg, "-M"_s, "--test-model") &&
2925 (i < args.size() - 1)) {
2926 validArg = true;
2927 i++;
2928 std::string const& str = args[i];
2929 if (cmSystemTools::LowerCase(str) == "nightly"_s) {
2930 this->SetTestModel(cmCTest::NIGHTLY);
2931 } else if (cmSystemTools::LowerCase(str) == "continuous"_s) {
2932 this->SetTestModel(cmCTest::CONTINUOUS);
2933 } else if (cmSystemTools::LowerCase(str) == "experimental"_s) {
2934 this->SetTestModel(cmCTest::EXPERIMENTAL);
2935 } else {
2936 success = false;
2937 cmCTestLog(this, ERROR_MESSAGE,
2938 "CTest -M called with incorrect option: " << str << '\n');
2939 /* clang-format off */
2940 cmCTestLog(this, ERROR_MESSAGE,
2941 "Available options are:\n"
2942 " " << ctestExec << " -M Continuous\n"
2943 " " << ctestExec << " -M Experimental\n"
2944 " " << ctestExec << " -M Nightly\n");
2945 /* clang-format on */
2948 return success;
2951 int cmCTest::ExecuteTests()
2953 int res;
2954 // call process directory
2955 if (this->Impl->RunConfigurationScript) {
2956 if (this->Impl->ExtraVerbose) {
2957 cmCTestLog(this, OUTPUT, "* Extra verbosity turned on" << std::endl);
2959 for (auto& handler : this->Impl->GetTestingHandlers()) {
2960 handler->SetVerbose(this->Impl->ExtraVerbose);
2961 handler->SetSubmitIndex(this->Impl->SubmitIndex);
2963 this->GetScriptHandler()->SetVerbose(this->Impl->Verbose);
2964 res = this->GetScriptHandler()->ProcessHandler();
2965 if (res != 0) {
2966 cmCTestLog(this, DEBUG,
2967 "running script failing returning: " << res << std::endl);
2970 } else {
2971 // What is this? -V seems to be the same as -VV,
2972 // and Verbose is always on in this case
2973 this->Impl->ExtraVerbose = this->Impl->Verbose;
2974 this->Impl->Verbose = true;
2975 for (auto& handler : this->Impl->GetTestingHandlers()) {
2976 handler->SetVerbose(this->Impl->Verbose);
2977 handler->SetSubmitIndex(this->Impl->SubmitIndex);
2980 const std::string currDir = cmSystemTools::GetCurrentWorkingDirectory();
2981 std::string workDir = currDir;
2982 if (!this->Impl->TestDir.empty()) {
2983 workDir = cmSystemTools::CollapseFullPath(this->Impl->TestDir);
2986 if (currDir != workDir) {
2987 if (!this->TryToChangeDirectory(workDir)) {
2988 return 1;
2992 if (!this->Initialize(workDir, nullptr)) {
2993 res = 12;
2994 cmCTestLog(this, ERROR_MESSAGE,
2995 "Problem initializing the dashboard." << std::endl);
2996 } else {
2997 res = this->ProcessSteps();
2999 this->Finalize();
3001 if (currDir != workDir) {
3002 cmSystemTools::ChangeDirectory(currDir);
3005 if (res != 0) {
3006 cmCTestLog(this, DEBUG,
3007 "Running a test(s) failed returning : " << res << std::endl);
3009 return res;
3012 int cmCTest::RunCMakeAndTest(std::string* output)
3014 this->Impl->Verbose = true;
3015 cmCTestBuildAndTestHandler* handler = this->GetBuildAndTestHandler();
3016 int retv = handler->ProcessHandler();
3017 *output = handler->GetOutput();
3018 #ifndef CMAKE_BOOTSTRAP
3019 cmDynamicLoader::FlushCache();
3020 #endif
3021 if (retv != 0) {
3022 cmCTestLog(this, DEBUG,
3023 "build and test failing returning: " << retv << std::endl);
3025 return retv;
3028 void cmCTest::SetNotesFiles(const std::string& notes)
3030 this->Impl->NotesFiles = notes;
3033 bool cmCTest::GetStopOnFailure() const
3035 return this->Impl->StopOnFailure;
3038 void cmCTest::SetStopOnFailure(bool stop)
3040 this->Impl->StopOnFailure = stop;
3043 std::chrono::system_clock::time_point cmCTest::GetStopTime() const
3045 return this->Impl->StopTime;
3048 void cmCTest::SetStopTime(std::string const& time_str)
3051 struct tm* lctime;
3052 time_t current_time = time(nullptr);
3053 lctime = gmtime(&current_time);
3054 int gm_hour = lctime->tm_hour;
3055 time_t gm_time = mktime(lctime);
3056 lctime = localtime(&current_time);
3057 int local_hour = lctime->tm_hour;
3059 int tzone_offset = local_hour - gm_hour;
3060 if (gm_time > current_time && gm_hour < local_hour) {
3061 // this means gm_time is on the next day
3062 tzone_offset -= 24;
3063 } else if (gm_time < current_time && gm_hour > local_hour) {
3064 // this means gm_time is on the previous day
3065 tzone_offset += 24;
3068 tzone_offset *= 100;
3069 char buf[1024];
3070 snprintf(buf, sizeof(buf), "%d%02d%02d %s %+05i", lctime->tm_year + 1900,
3071 lctime->tm_mon + 1, lctime->tm_mday, time_str.c_str(),
3072 tzone_offset);
3074 time_t stop_time = curl_getdate(buf, &current_time);
3075 if (stop_time == -1) {
3076 this->Impl->StopTime = std::chrono::system_clock::time_point();
3077 return;
3079 this->Impl->StopTime = std::chrono::system_clock::from_time_t(stop_time);
3081 if (stop_time < current_time) {
3082 this->Impl->StopTime += std::chrono::hours(24);
3086 std::string cmCTest::GetScheduleType() const
3088 return this->Impl->ScheduleType;
3091 void cmCTest::SetScheduleType(std::string const& type)
3093 this->Impl->ScheduleType = type;
3096 int cmCTest::ReadCustomConfigurationFileTree(const std::string& dir,
3097 cmMakefile* mf)
3099 bool found = false;
3100 cmCTestLog(this, DEBUG,
3101 "* Read custom CTest configuration directory: " << dir
3102 << std::endl);
3104 std::string fname = cmStrCat(dir, "/CTestCustom.cmake");
3105 cmCTestLog(this, DEBUG, "* Check for file: " << fname << std::endl);
3106 if (cmSystemTools::FileExists(fname)) {
3107 cmCTestLog(this, DEBUG,
3108 "* Read custom CTest configuration file: " << fname
3109 << std::endl);
3110 bool erroroc = cmSystemTools::GetErrorOccurredFlag();
3111 cmSystemTools::ResetErrorOccurredFlag();
3113 if (!mf->ReadListFile(fname) || cmSystemTools::GetErrorOccurredFlag()) {
3114 cmCTestLog(this, ERROR_MESSAGE,
3115 "Problem reading custom configuration: " << fname
3116 << std::endl);
3118 found = true;
3119 if (erroroc) {
3120 cmSystemTools::SetErrorOccurred();
3124 std::string rexpr = cmStrCat(dir, "/CTestCustom.ctest");
3125 cmCTestLog(this, DEBUG, "* Check for file: " << rexpr << std::endl);
3126 if (!found && cmSystemTools::FileExists(rexpr)) {
3127 cmsys::Glob gl;
3128 gl.RecurseOn();
3129 gl.FindFiles(rexpr);
3130 std::vector<std::string>& files = gl.GetFiles();
3131 for (const std::string& file : files) {
3132 cmCTestLog(this, DEBUG,
3133 "* Read custom CTest configuration file: " << file
3134 << std::endl);
3135 if (!mf->ReadListFile(file) || cmSystemTools::GetErrorOccurredFlag()) {
3136 cmCTestLog(this, ERROR_MESSAGE,
3137 "Problem reading custom configuration: " << file
3138 << std::endl);
3141 found = true;
3144 if (found) {
3145 for (auto& handler : this->Impl->GetNamedTestingHandlers()) {
3146 cmCTestLog(this, DEBUG,
3147 "* Read custom CTest configuration vectors for handler: "
3148 << handler.first << " (" << handler.second << ")"
3149 << std::endl);
3150 handler.second->PopulateCustomVectors(mf);
3154 return 1;
3157 void cmCTest::PopulateCustomVector(cmMakefile* mf, const std::string& def,
3158 std::vector<std::string>& vec)
3160 cmValue dval = mf->GetDefinition(def);
3161 if (!dval) {
3162 return;
3164 cmCTestLog(this, DEBUG, "PopulateCustomVector: " << def << std::endl);
3166 cmList::assign(vec, *dval);
3168 for (std::string const& it : vec) {
3169 cmCTestLog(this, DEBUG, " -- " << it << std::endl);
3173 void cmCTest::PopulateCustomInteger(cmMakefile* mf, const std::string& def,
3174 int& val)
3176 cmValue dval = mf->GetDefinition(def);
3177 if (!dval) {
3178 return;
3180 val = atoi(dval->c_str());
3183 std::string cmCTest::GetShortPathToFile(const std::string& cfname)
3185 const std::string& sourceDir = cmSystemTools::CollapseFullPath(
3186 this->GetCTestConfiguration("SourceDirectory"));
3187 const std::string& buildDir = cmSystemTools::CollapseFullPath(
3188 this->GetCTestConfiguration("BuildDirectory"));
3189 std::string fname = cmSystemTools::CollapseFullPath(cfname);
3191 // Find relative paths to both directories
3192 std::string srcRelpath = cmSystemTools::RelativePath(sourceDir, fname);
3193 std::string bldRelpath = cmSystemTools::RelativePath(buildDir, fname);
3195 // If any contains "." it is not parent directory
3196 bool inSrc = srcRelpath.find("..") == std::string::npos;
3197 bool inBld = bldRelpath.find("..") == std::string::npos;
3198 // TODO: Handle files with .. in their name
3200 std::string* res = nullptr;
3202 if (inSrc && inBld) {
3203 // If both have relative path with no dots, pick the shorter one
3204 if (srcRelpath.size() < bldRelpath.size()) {
3205 res = &srcRelpath;
3206 } else {
3207 res = &bldRelpath;
3209 } else if (inSrc) {
3210 res = &srcRelpath;
3211 } else if (inBld) {
3212 res = &bldRelpath;
3215 std::string path;
3217 if (!res) {
3218 path = fname;
3219 } else {
3220 cmSystemTools::ConvertToUnixSlashes(*res);
3222 path = "./" + *res;
3223 if (path.back() == '/') {
3224 path.resize(path.size() - 1);
3228 cmsys::SystemTools::ReplaceString(path, ":", "_");
3229 cmsys::SystemTools::ReplaceString(path, " ", "_");
3230 return path;
3233 std::string cmCTest::GetCTestConfiguration(const std::string& name)
3235 if (this->Impl->CTestConfigurationOverwrites.find(name) !=
3236 this->Impl->CTestConfigurationOverwrites.end()) {
3237 return this->Impl->CTestConfigurationOverwrites[name];
3239 return this->Impl->CTestConfiguration[name];
3242 void cmCTest::EmptyCTestConfiguration()
3244 this->Impl->CTestConfiguration.clear();
3247 void cmCTest::SetCTestConfiguration(const char* name, const std::string& value,
3248 bool suppress)
3250 cmCTestOptionalLog(this, HANDLER_VERBOSE_OUTPUT,
3251 "SetCTestConfiguration:" << name << ":" << value << "\n",
3252 suppress);
3254 if (!name) {
3255 return;
3257 if (value.empty()) {
3258 this->Impl->CTestConfiguration.erase(name);
3259 return;
3261 this->Impl->CTestConfiguration[name] = value;
3264 std::string cmCTest::GetSubmitURL()
3266 std::string url = this->GetCTestConfiguration("SubmitURL");
3267 if (url.empty()) {
3268 std::string method = this->GetCTestConfiguration("DropMethod");
3269 std::string user = this->GetCTestConfiguration("DropSiteUser");
3270 std::string password = this->GetCTestConfiguration("DropSitePassword");
3271 std::string site = this->GetCTestConfiguration("DropSite");
3272 std::string location = this->GetCTestConfiguration("DropLocation");
3274 url = cmStrCat(method.empty() ? "http" : method, "://"_s);
3275 if (!user.empty()) {
3276 url += user;
3277 if (!password.empty()) {
3278 url += ':';
3279 url += password;
3281 url += '@';
3283 url += site;
3284 url += location;
3286 return url;
3289 std::string cmCTest::GetCurrentTag()
3291 return this->Impl->CurrentTag;
3294 std::string cmCTest::GetBinaryDir()
3296 return this->Impl->BinaryDir;
3299 std::string const& cmCTest::GetConfigType()
3301 return this->Impl->ConfigType;
3304 cmDuration cmCTest::GetTimeOut() const
3306 return this->Impl->TimeOut;
3309 void cmCTest::SetTimeOut(cmDuration t)
3311 this->Impl->TimeOut = t;
3314 cmDuration cmCTest::GetGlobalTimeout() const
3316 return this->Impl->GlobalTimeout;
3319 bool cmCTest::GetShowOnly()
3321 return this->Impl->ShowOnly;
3324 bool cmCTest::GetOutputAsJson()
3326 return this->Impl->OutputAsJson;
3329 int cmCTest::GetOutputAsJsonVersion()
3331 return this->Impl->OutputAsJsonVersion;
3334 bool cmCTest::ShouldUseHTTP10() const
3336 return this->Impl->UseHTTP10;
3339 bool cmCTest::ShouldPrintLabels() const
3341 return this->Impl->PrintLabels;
3344 int cmCTest::GetMaxTestNameWidth() const
3346 return this->Impl->MaxTestNameWidth;
3349 void cmCTest::SetMaxTestNameWidth(int w)
3351 this->Impl->MaxTestNameWidth = w;
3354 void cmCTest::SetProduceXML(bool v)
3356 this->Impl->ProduceXML = v;
3359 bool cmCTest::GetProduceXML()
3361 return this->Impl->ProduceXML;
3364 std::vector<std::string>& cmCTest::GetInitialCommandLineArguments()
3366 return this->Impl->InitialCommandLineArguments;
3369 const char* cmCTest::GetSpecificGroup()
3371 if (this->Impl->SpecificGroup.empty()) {
3372 return nullptr;
3374 return this->Impl->SpecificGroup.c_str();
3377 void cmCTest::SetSpecificGroup(const char* group)
3379 if (!group) {
3380 this->Impl->SpecificGroup.clear();
3381 return;
3383 this->Impl->SpecificGroup = group;
3386 void cmCTest::SetFailover(bool failover)
3388 this->Impl->Failover = failover;
3391 bool cmCTest::GetFailover() const
3393 return this->Impl->Failover;
3396 bool cmCTest::GetTestProgressOutput() const
3398 return this->Impl->TestProgressOutput && !GetExtraVerbose();
3401 bool cmCTest::GetVerbose() const
3403 return this->Impl->Verbose;
3406 bool cmCTest::GetExtraVerbose() const
3408 return this->Impl->ExtraVerbose;
3411 void cmCTest::SetStreams(std::ostream* out, std::ostream* err)
3413 this->Impl->StreamOut = out;
3414 this->Impl->StreamErr = err;
3417 bool cmCTest::GetLabelSummary() const
3419 return this->Impl->LabelSummary;
3422 bool cmCTest::GetSubprojectSummary() const
3424 return this->Impl->SubprojectSummary;
3427 bool cmCTest::GetOutputTestOutputOnTestFailure() const
3429 return this->Impl->OutputTestOutputOnTestFailure;
3432 const std::map<std::string, std::string>& cmCTest::GetDefinitions() const
3434 return this->Impl->Definitions;
3437 int cmCTest::GetRepeatCount() const
3439 return this->Impl->RepeatCount;
3442 cmCTest::Repeat cmCTest::GetRepeatMode() const
3444 return this->Impl->RepeatMode;
3447 cmCTest::NoTests cmCTest::GetNoTestsMode() const
3449 return this->Impl->NoTestsMode;
3452 void cmCTest::SetBuildID(const std::string& id)
3454 this->Impl->BuildID = id;
3457 std::string cmCTest::GetBuildID() const
3459 return this->Impl->BuildID;
3462 void cmCTest::AddSubmitFile(Part part, const std::string& name)
3464 this->Impl->Parts[part].SubmitFiles.emplace_back(name);
3467 std::vector<std::string> const& cmCTest::GetSubmitFiles(Part part) const
3469 return this->Impl->Parts[part].SubmitFiles;
3472 void cmCTest::ClearSubmitFiles(Part part)
3474 this->Impl->Parts[part].SubmitFiles.clear();
3477 void cmCTest::SetSuppressUpdatingCTestConfiguration(bool val)
3479 this->Impl->SuppressUpdatingCTestConfiguration = val;
3482 void cmCTest::AddCTestConfigurationOverwrite(const std::string& overStr)
3484 size_t epos = overStr.find('=');
3485 if (epos == std::string::npos) {
3486 cmCTestLog(this, ERROR_MESSAGE,
3487 "CTest configuration overwrite specified in the wrong format.\n"
3488 "Valid format is: --overwrite key=value\n"
3489 "The specified was: --overwrite "
3490 << overStr << '\n');
3491 return;
3493 std::string key = overStr.substr(0, epos);
3494 std::string value = overStr.substr(epos + 1);
3495 this->Impl->CTestConfigurationOverwrites[key] = value;
3498 void cmCTest::SetConfigType(const std::string& ct)
3500 this->Impl->ConfigType = ct;
3501 cmSystemTools::ReplaceString(this->Impl->ConfigType, ".\\", "");
3502 std::string confTypeEnv = "CMAKE_CONFIG_TYPE=" + this->Impl->ConfigType;
3503 cmSystemTools::PutEnv(confTypeEnv);
3506 bool cmCTest::SetCTestConfigurationFromCMakeVariable(
3507 cmMakefile* mf, const char* dconfig, const std::string& cmake_var,
3508 bool suppress)
3510 cmValue ctvar = mf->GetDefinition(cmake_var);
3511 if (!ctvar) {
3512 return false;
3514 cmCTestOptionalLog(this, HANDLER_VERBOSE_OUTPUT,
3515 "SetCTestConfigurationFromCMakeVariable:"
3516 << dconfig << ":" << cmake_var << std::endl,
3517 suppress);
3518 this->SetCTestConfiguration(dconfig, *ctvar, suppress);
3519 return true;
3522 bool cmCTest::RunCommand(std::vector<std::string> const& args,
3523 std::string* stdOut, std::string* stdErr, int* retVal,
3524 const char* dir, cmDuration timeout,
3525 Encoding encoding)
3527 std::vector<const char*> argv;
3528 argv.reserve(args.size() + 1);
3529 for (std::string const& a : args) {
3530 argv.push_back(a.c_str());
3532 argv.push_back(nullptr);
3534 stdOut->clear();
3535 stdErr->clear();
3537 cmUVProcessChainBuilder builder;
3538 builder.AddCommand(args)
3539 .SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT)
3540 .SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR);
3541 if (dir) {
3542 builder.SetWorkingDirectory(dir);
3544 auto chain = builder.Start();
3546 cm::uv_timer_ptr timer;
3547 bool timedOut = false;
3548 if (timeout.count()) {
3549 timer.init(chain.GetLoop(), &timedOut);
3550 timer.start(
3551 [](uv_timer_t* t) {
3552 auto* timedOutPtr = static_cast<bool*>(t->data);
3553 *timedOutPtr = true;
3555 static_cast<uint64_t>(timeout.count() * 1000.0), 0);
3558 std::vector<char> tempOutput;
3559 bool outFinished = false;
3560 cm::uv_pipe_ptr outStream;
3561 std::vector<char> tempError;
3562 bool errFinished = false;
3563 cm::uv_pipe_ptr errStream;
3564 cmProcessOutput processOutput(encoding);
3565 auto startRead = [this, &chain, &processOutput](
3566 cm::uv_pipe_ptr& pipe, int stream,
3567 std::vector<char>& temp,
3568 bool& finished) -> std::unique_ptr<cmUVStreamReadHandle> {
3569 pipe.init(chain.GetLoop(), 0);
3570 uv_pipe_open(pipe, stream);
3571 return cmUVStreamRead(
3572 pipe,
3573 [this, &temp, &processOutput](std::vector<char> data) {
3574 cm::append(temp, data);
3575 if (this->Impl->ExtraVerbose) {
3576 std::string strdata;
3577 processOutput.DecodeText(data.data(), data.size(), strdata);
3578 cmSystemTools::Stdout(strdata);
3581 [&finished]() { finished = true; });
3583 auto outputHandle =
3584 startRead(outStream, chain.OutputStream(), tempOutput, outFinished);
3585 auto errorHandle =
3586 startRead(errStream, chain.ErrorStream(), tempError, errFinished);
3587 while (!timedOut && !(outFinished && errFinished)) {
3588 uv_run(&chain.GetLoop(), UV_RUN_ONCE);
3590 if (this->Impl->ExtraVerbose) {
3591 std::string strdata;
3592 processOutput.DecodeText(std::string(), strdata);
3593 if (!strdata.empty()) {
3594 cmSystemTools::Stdout(strdata);
3598 while (!timedOut && !chain.Finished()) {
3599 uv_run(&chain.GetLoop(), UV_RUN_ONCE);
3601 if (!tempOutput.empty()) {
3602 processOutput.DecodeText(tempOutput, tempOutput);
3603 stdOut->append(tempOutput.data(), tempOutput.size());
3605 if (!tempError.empty()) {
3606 processOutput.DecodeText(tempError, tempError);
3607 stdErr->append(tempError.data(), tempError.size());
3610 bool result = true;
3611 if (timedOut) {
3612 const char* error_str = "Process terminated due to timeout\n";
3613 cmCTestLog(this, ERROR_MESSAGE, error_str << std::endl);
3614 stdErr->append(error_str, strlen(error_str));
3615 result = false;
3616 } else {
3617 auto const& status = chain.GetStatus(0);
3618 auto exception = status.GetException();
3619 switch (exception.first) {
3620 case cmUVProcessChain::ExceptionCode::None:
3621 if (retVal) {
3622 *retVal = static_cast<int>(status.ExitStatus);
3623 } else {
3624 if (status.ExitStatus != 0) {
3625 result = false;
3628 break;
3629 default: {
3630 cmCTestLog(this, ERROR_MESSAGE, exception.second << std::endl);
3631 stdErr->append(exception.second);
3632 result = false;
3633 } break;
3637 return result;
3640 void cmCTest::SetOutputLogFileName(const std::string& name)
3642 if (!name.empty()) {
3643 this->Impl->OutputLogFile = cm::make_unique<cmGeneratedFileStream>(name);
3644 } else {
3645 this->Impl->OutputLogFile.reset();
3649 void cmCTest::SetOutputJUnitFileName(const std::string& name)
3651 this->Impl->TestHandler.SetJUnitXMLFileName(name);
3652 // Turn test output compression off.
3653 // This makes it easier to include test output in the resulting
3654 // JUnit XML report.
3655 this->Impl->CompressTestOutput = false;
3658 static const char* cmCTestStringLogType[] = { "DEBUG",
3659 "OUTPUT",
3660 "HANDLER_OUTPUT",
3661 "HANDLER_PROGRESS_OUTPUT",
3662 "HANDLER_TEST_PROGRESS_OUTPUT",
3663 "HANDLER_VERBOSE_OUTPUT",
3664 "WARNING",
3665 "ERROR_MESSAGE",
3666 nullptr };
3668 #define cmCTestLogOutputFileLine(stream) \
3669 do { \
3670 if (this->Impl->ShowLineNumbers) { \
3671 (stream) << std::endl << file << ":" << line << " "; \
3673 } while (false)
3675 void cmCTest::Log(int logType, const char* file, int line, const char* msg,
3676 bool suppress)
3678 if (!msg || !*msg) {
3679 return;
3681 if (suppress && logType != cmCTest::ERROR_MESSAGE) {
3682 return;
3684 if (logType == cmCTest::HANDLER_PROGRESS_OUTPUT &&
3685 (this->Impl->Debug || this->Impl->ExtraVerbose)) {
3686 return;
3688 if (this->Impl->OutputLogFile) {
3689 bool display = true;
3690 if (logType == cmCTest::DEBUG && !this->Impl->Debug) {
3691 display = false;
3693 if (logType == cmCTest::HANDLER_VERBOSE_OUTPUT && !this->Impl->Debug &&
3694 !this->Impl->ExtraVerbose) {
3695 display = false;
3697 if (display) {
3698 cmCTestLogOutputFileLine(*this->Impl->OutputLogFile);
3699 if (logType != this->Impl->OutputLogFileLastTag) {
3700 *this->Impl->OutputLogFile << "[";
3701 if (logType >= OTHER || logType < 0) {
3702 *this->Impl->OutputLogFile << "OTHER";
3703 } else {
3704 *this->Impl->OutputLogFile << cmCTestStringLogType[logType];
3706 *this->Impl->OutputLogFile << "] " << std::endl;
3708 *this->Impl->OutputLogFile << msg << std::flush;
3709 if (logType != this->Impl->OutputLogFileLastTag) {
3710 *this->Impl->OutputLogFile << std::endl;
3711 this->Impl->OutputLogFileLastTag = logType;
3715 if (!this->Impl->Quiet) {
3716 std::ostream& out = *this->Impl->StreamOut;
3717 std::ostream& err = *this->Impl->StreamErr;
3719 if (logType == HANDLER_TEST_PROGRESS_OUTPUT) {
3720 if (this->Impl->TestProgressOutput) {
3721 cmCTestLogOutputFileLine(out);
3722 if (this->Impl->FlushTestProgressLine) {
3723 printf("\r");
3724 this->Impl->FlushTestProgressLine = false;
3725 out.flush();
3728 std::string msg_str{ msg };
3729 auto const lineBreakIt = msg_str.find('\n');
3730 if (lineBreakIt != std::string::npos) {
3731 this->Impl->FlushTestProgressLine = true;
3732 msg_str.erase(std::remove(msg_str.begin(), msg_str.end(), '\n'),
3733 msg_str.end());
3736 out << msg_str;
3737 #ifndef _WIN32
3738 printf("\x1B[K"); // move caret to end
3739 #endif
3740 out.flush();
3741 return;
3743 logType = HANDLER_OUTPUT;
3746 switch (logType) {
3747 case DEBUG:
3748 if (this->Impl->Debug) {
3749 cmCTestLogOutputFileLine(out);
3750 out << msg;
3751 out.flush();
3753 break;
3754 case OUTPUT:
3755 case HANDLER_OUTPUT:
3756 if (this->Impl->Debug || this->Impl->Verbose) {
3757 cmCTestLogOutputFileLine(out);
3758 out << msg;
3759 out.flush();
3761 break;
3762 case HANDLER_VERBOSE_OUTPUT:
3763 if (this->Impl->Debug || this->Impl->ExtraVerbose) {
3764 cmCTestLogOutputFileLine(out);
3765 out << msg;
3766 out.flush();
3768 break;
3769 case WARNING:
3770 cmCTestLogOutputFileLine(err);
3771 err << msg;
3772 err.flush();
3773 break;
3774 case ERROR_MESSAGE:
3775 cmCTestLogOutputFileLine(err);
3776 err << msg;
3777 err.flush();
3778 cmSystemTools::SetErrorOccurred();
3779 break;
3780 default:
3781 cmCTestLogOutputFileLine(out);
3782 out << msg;
3783 out.flush();
3788 std::string cmCTest::GetColorCode(Color color) const
3790 if (this->Impl->OutputColorCode) {
3791 return "\033[0;" + std::to_string(static_cast<int>(color)) + "m";
3794 return "";
3797 cmDuration cmCTest::GetRemainingTimeAllowed()
3799 return this->GetScriptHandler()->GetRemainingTimeAllowed();
3802 cmDuration cmCTest::MaxDuration()
3804 return cmDuration(1.0e7);
3807 void cmCTest::SetRunCurrentScript(bool value)
3809 this->GetScriptHandler()->SetRunCurrentScript(value);
3812 void cmCTest::OutputTestErrors(std::vector<char> const& process_output)
3814 std::string test_outputs("\n*** Test Failed:\n");
3815 if (!process_output.empty()) {
3816 test_outputs.append(process_output.data(), process_output.size());
3818 cmCTestLog(this, HANDLER_OUTPUT, test_outputs << std::endl);
3821 bool cmCTest::CompressString(std::string& str)
3823 int ret;
3824 z_stream strm;
3826 strm.zalloc = Z_NULL;
3827 strm.zfree = Z_NULL;
3828 strm.opaque = Z_NULL;
3829 ret = deflateInit(&strm, -1); // default compression level
3830 if (ret != Z_OK) {
3831 return false;
3834 unsigned char* in =
3835 reinterpret_cast<unsigned char*>(const_cast<char*>(str.c_str()));
3836 // zlib makes the guarantee that this is the maximum output size
3837 int outSize =
3838 static_cast<int>(static_cast<double>(str.size()) * 1.001 + 13.0);
3839 std::vector<unsigned char> out(outSize);
3841 strm.avail_in = static_cast<uInt>(str.size());
3842 strm.next_in = in;
3843 strm.avail_out = outSize;
3844 strm.next_out = out.data();
3845 ret = deflate(&strm, Z_FINISH);
3847 if (ret != Z_STREAM_END) {
3848 cmCTestLog(this, ERROR_MESSAGE,
3849 "Error during gzip compression." << std::endl);
3850 return false;
3853 (void)deflateEnd(&strm);
3855 // Now base64 encode the resulting binary string
3856 std::vector<unsigned char> base64EncodedBuffer((outSize * 3) / 2);
3858 size_t rlen = cmsysBase64_Encode(out.data(), strm.total_out,
3859 base64EncodedBuffer.data(), 1);
3861 str.assign(reinterpret_cast<char*>(base64EncodedBuffer.data()), rlen);
3863 return true;