1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
22 #include <cm/optional>
23 #include <cm/string_view>
24 #include <cmext/algorithm>
25 #include <cmext/string_view>
27 #include <cm3p/curl/curl.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"
38 # include <windows.h> // IWYU pragma: keep
40 # include <unistd.h> // IWYU pragma: keep
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"
61 #include "cmMakefile.h"
62 #include "cmProcessOutput.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"
72 #include "cmVersion.h"
73 #include "cmVersionConfig.h"
74 #include "cmXMLWriter.h"
77 #if defined(__BEOS__) || defined(__HAIKU__)
78 # include <be/kernel/OS.h> /* disable_debugger() API. */
81 struct cmCTest::Private
83 /** Representation of one part. */
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
;
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
;
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;
209 bool ShowLineNumbers
= false;
214 std::vector
<std::string
> InitialCommandLineArguments
;
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
)
233 time_t tctime
= time(nullptr);
234 lctime
= gmtime(&tctime
);
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
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
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
)) {
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
277 cmCTestLog(this, OUTPUT
, " Use future tag, Add a day" << std::endl
);
280 lctime
= gmtime(&ntime
);
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(¤ttime
);
307 // return ::CleanString(ctime(¤ttime));
308 char current_time
[1024];
309 if (this->Impl
->ShortDateFormat
) {
310 strftime(current_time
, 1000, "%b %d %H:%M %Z", t
);
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");
322 fname
= this->GetBinaryDir() + "/Testing/Temporary/CTestCostData.txt";
327 std::string
cmCTest::DecodeURL(const std::string
& in
)
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)));
345 std::string envValue
;
346 if (cmSystemTools::GetEnv("CTEST_OUTPUT_ON_FAILURE", envValue
)) {
347 this->Impl
->OutputTestOutputOnTestFailure
= !cmIsOff(envValue
);
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)) {
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()) {
417 // The string does not name a valid part.
421 int cmCTest::Initialize(const std::string
& binary_dir
,
422 cmCTestStartCommand
* command
)
425 if (command
&& command
->ShouldBeQuiet()) {
429 cmCTestOptionalLog(this, DEBUG
, "Here: " << __LINE__
<< std::endl
, quiet
);
430 if (!this->Impl
->InteractiveDebugMode
) {
431 this->BlockTestErrorDiagnostics();
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
,
446 << this->GetCTestConfiguration("Site") << std::endl
448 << cmCTest::SafeBuildIdField(
449 this->GetCTestConfiguration("BuildName"))
452 cmCTestOptionalLog(this, DEBUG
, "Produce XML is on" << std::endl
, quiet
);
453 if (this->Impl
->TestModel
== cmCTest::NIGHTLY
&&
454 this->GetCTestConfiguration("NightlyStartTime").empty()) {
457 "WARNING: No nightly start time found please set in CTestConfig.cmake"
458 " or DartConfig.cmake"
461 cmCTestOptionalLog(this, DEBUG
, "Here: " << __LINE__
<< std::endl
,
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
)) {
475 this, DEBUG
, "Cannot find custom configuration file tree" << std::endl
,
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"
493 if (!cmSystemTools::MakeDirectory(testingDir
)) {
494 cmCTestLog(this, ERROR_MESSAGE
,
495 "Cannot create directory " << testingDir
<< std::endl
);
500 // Create new "TAG" file or read existing one:
502 bool createNewTag
= true;
504 createNewTag
= command
->ShouldCreateNewTag();
507 std::string tagfile
= testingDir
+ "/TAG";
508 cmsys::ifstream
tfin(tagfile
.c_str());
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
)) {
523 sscanf(tag
.c_str(), "%04d%02d%02d-%02d%02d", &year
, &mon
, &day
, &hour
,
525 if (year
!= lctime
->tm_year
+ 1900 || mon
!= lctime
->tm_mon
+ 1 ||
526 day
!= lctime
->tm_mday
) {
530 if (cmSystemTools::GetLineFromStream(tfin
, group
) &&
531 !this->Impl
->Parts
[PartStart
] && !command
) {
532 this->Impl
->SpecificGroup
= group
;
535 if (cmSystemTools::GetLineFromStream(tfin
, model
) &&
536 !this->Impl
->Parts
[PartStart
] && !command
) {
537 this->Impl
->TestModel
= GetTestModelFromString(model
);
541 if (tag
.empty() || command
|| this->Impl
->Parts
[PartStart
]) {
544 "TestModel: " << this->GetTestModelString() << std::endl
, quiet
);
545 cmCTestOptionalLog(this, DEBUG
,
546 "TestModel: " << this->Impl
->TestModel
<< std::endl
,
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
);
558 cmsys::ofstream
ofs(tagfile
.c_str());
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
;
566 case cmCTest::NIGHTLY
:
567 ofs
<< "Nightly" << std::endl
;
569 case cmCTest::CONTINUOUS
:
570 ofs
<< "Continuous" << std::endl
;
576 cmCTestOptionalLog(this, OUTPUT
,
577 "Create new tag: " << tag
<< " - "
578 << this->GetTestModelString()
585 std::string modelStr
;
586 int model
= cmCTest::UNKNOWN
;
589 cmSystemTools::GetLineFromStream(tfin
, tag
);
590 cmSystemTools::GetLineFromStream(tfin
, group
);
591 if (cmSystemTools::GetLineFromStream(tfin
, modelStr
)) {
592 model
= GetTestModelFromString(modelStr
);
598 cmCTestLog(this, ERROR_MESSAGE
,
599 "Cannot read existing TAG file in " << testingDir
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"
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()"
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()"
633 this->Impl
->SpecificGroup
= group
;
636 cmCTestOptionalLog(this, OUTPUT
,
637 " Use existing tag: " << tag
<< " - "
638 << this->GetTestModelString()
643 this->Impl
->CurrentTag
= tag
;
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();
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
677 command
->ShouldBeQuiet());
678 bool readit
= mf
->ReadDependentFile(fname
);
680 std::string m
= cmStrCat("Could not find include file: ", fname
);
681 command
->SetError(m
);
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
)) {
697 cmCTestOptionalLog(this, OUTPUT
,
698 " Use " << this->GetTestModelString() << " tag: "
699 << this->GetCurrentTag() << std::endl
,
700 command
->ShouldBeQuiet());
704 bool cmCTest::UpdateCTestConfiguration()
706 if (this->Impl
->SuppressUpdatingCTestConfiguration
) {
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
);
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());
734 fin
.getline(buffer
, 1023);
736 std::string line
= cmCTest::CleanString(buffer
);
740 while (fin
&& (line
.back() == '\\')) {
741 line
.resize(line
.size() - 1);
743 fin
.getline(buffer
, 1023);
745 line
+= cmCTest::CleanString(buffer
);
747 if (line
[0] == '#') {
750 std::string::size_type cpos
= line
.find_first_of(':');
751 if (cpos
== std::string::npos
) {
754 std::string key
= line
.substr(0, cpos
);
755 std::string value
= cmCTest::CleanString(line
, cpos
+ 1);
756 this->Impl
->CTestConfiguration
[key
] = value
;
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()) {
771 if (cmStrToULong(testLoad
, &load
)) {
772 this->SetTestLoad(load
);
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"));
785 void cmCTest::BlockTestErrorDiagnostics()
787 cmSystemTools::PutEnv("DART_TEST_FROM_DART=1");
788 cmSystemTools::PutEnv("DASHBOARD_TEST_FROM_CTEST=" CMake_VERSION
);
790 SetErrorMode(SEM_FAILCRITICALERRORS
| SEM_NOGPFAULTERRORBOX
);
791 #elif defined(__BEOS__) || defined(__HAIKU__)
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();
815 Part p
= this->GetPartFromName(ttype
);
816 if (p
!= PartCount
) {
817 this->Impl
->Parts
[p
].Enable();
821 cmCTestLog(this, ERROR_MESSAGE
,
822 "Don't know about test \"" << ttype
<< "\" yet..."
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";
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"
848 if (!cmSystemTools::MakeDirectory(testingDir
)) {
849 cmCTestLog(this, ERROR_MESSAGE
,
850 "Cannot create directory " << testingDir
<< std::endl
);
854 std::string filename
= testingDir
+ "/" + name
;
855 stream
.SetTempExt("tmp");
856 stream
.Open(filename
);
858 cmCTestLog(this, ERROR_MESSAGE
,
859 "Problem opening file: " << filename
<< std::endl
);
863 if (this->Impl
->CompressXMLFiles
) {
864 stream
.SetCompression(true);
870 bool cmCTest::AddIfExists(Part part
, const std::string
& file
)
872 if (this->CTestFileExists(file
)) {
873 this->AddSubmitFile(part
, file
);
875 std::string name
= cmStrCat(file
, ".gz");
876 if (this->CTestFileExists(name
)) {
877 this->AddSubmitFile(part
, file
);
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()
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
) {
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
;
1000 std::string notes_dir
= this->Impl
->BinaryDir
+ "/Testing/Notes";
1001 if (cmSystemTools::FileIsDirectory(notes_dir
)) {
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
;
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
1038 cmCTestLog(this, ERROR_MESSAGE
,
1039 "Use \"--rerun-failed --output-on-failure\" to re-run the "
1040 "failed cases verbosely."
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
:
1055 case cmCTest::CONTINUOUS
:
1056 return "Continuous";
1058 return "Experimental";
1061 int cmCTest::GetTestModelFromString(const std::string
& str
)
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
);
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();
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"
1116 auto outputHandle
= cmUVStreamRead(
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
) {
1127 output
.append(strdata
);
1128 while (output
.size() > (tick
* tick_len
)) {
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
);
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
);
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"
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
);
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
);
1176 *retVal
= static_cast<int>(exception
.first
);
1177 cmCTestLog(this, WARNING
,
1178 "There was an exception: " << *retVal
<< std::endl
);
1182 cmCTestLog(this, WARNING
, "There was a timeout" << std::endl
);
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
)))
1219 if (cmSystemTools::SameFile(argv
[0], cmSystemTools::GetCTestCommand()) &&
1220 !this->Impl
->ForceNewCTestProcess
) {
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
);
1242 *log
<< "* Run internal CTest" << std::endl
;
1245 std::unique_ptr
<cmSystemTools::SaveRestoreEnvironment
> saveEnv
;
1247 saveEnv
= cm::make_unique
<cmSystemTools::SaveRestoreEnvironment
>();
1248 cmSystemTools::AppendEnv(*environment
);
1251 *retVal
= inst
.Run(args
, output
);
1253 *output
+= oss
.str();
1255 if (log
&& output
) {
1259 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT
,
1260 "Internal cmCTest object used to run test." << std::endl
1267 std::vector
<char> tempOutput
;
1272 std::unique_ptr
<cmSystemTools::SaveRestoreEnvironment
> saveEnv
;
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(
1289 [this, &processOutput
, &output
, &tempOutput
,
1290 &log
](std::vector
<char> data
) {
1291 std::string strdata
;
1292 processOutput
.DecodeText(data
.data(), data
.size(), strdata
);
1294 cm::append(tempOutput
, data
.data(), data
.data() + data
.size());
1296 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT
, strdata
);
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
);
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;
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
);
1333 case cmUVProcessChain::ExceptionCode::Spawn
: {
1334 std::string outerr
=
1335 cmStrCat("\n*** ERROR executing: ", exception
.second
);
1339 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT
, outerr
<< std::endl
);
1342 if (this->Impl
->OutputTestOutputOnTestFailure
) {
1343 this->OutputTestErrors(tempOutput
);
1345 *retVal
= status
.TermSignal
;
1346 std::string outerr
=
1347 cmStrCat("\n*** Exception executing: ", exception
.second
);
1351 cmCTestLog(this, HANDLER_VERBOSE_OUTPUT
, outerr
<< std::endl
);
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
);
1375 for (i
= 0; i
< n
; ++i
) {
1376 replace
[0] = disallowed
[i
];
1377 cmSystemTools::ReplaceString(safevalue
, replace
, "");
1382 if (safevalue
.empty()) {
1383 safevalue
= "(empty)";
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."
1396 cmSystemTools::SetFatalErrorOccurred();
1399 // find out about the system
1400 cmsys::SystemInformation info
;
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());
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());
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.
1462 // This code should go when cdash is changed to use labels only
1463 cmValue subproject
= cm
->GetState()->GetGlobalProperty("SubProject");
1465 xml
.StartElement("Subproject");
1466 xml
.Attribute("name", *subproject
);
1468 ch
->GetCMake()->GetState()->GetGlobalProperty("SubProjectLabels");
1470 xml
.StartElement("Labels");
1471 cmList args
{ *labels
};
1472 for (std::string
const& i
: args
) {
1473 xml
.Element("Label", i
);
1480 // This code should stay when cdash only does label based sub-projects
1481 cmValue label
= cm
->GetState()->GetGlobalProperty("Label");
1483 xml
.StartElement("Labels");
1484 xml
.Element("Label", *label
);
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
};
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
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());
1551 while (cmSystemTools::GetLineFromStream(ifs
, line
)) {
1557 xml
.Content("Problem reading file: " + file
+ "\n");
1558 cmCTestLog(this, ERROR_MESSAGE
,
1559 "Problem reading file: " << file
<< " while creating notes"
1562 xml
.EndElement(); // Text
1563 xml
.EndElement(); // Note
1565 xml
.EndElement(); // Notes
1566 xml
.EndElement(); // Site
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
);
1578 cmXMLWriter
xml(ofs
);
1579 this->GenerateCTestNotesOutput(xml
, files
);
1583 int cmCTest::GenerateNotesFile(const std::string
& cfiles
)
1585 if (cfiles
.empty()) {
1589 cmCTestLog(this, OUTPUT
, "Create notes file" << std::endl
);
1591 std::vector
<std::string
> const files
=
1592 cmSystemTools::SplitString(cfiles
, ';');
1593 if (files
.empty()) {
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
);
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
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
);
1624 auto msg
= "Failed to change working directory to \"" + dir
+
1625 "\" : " + status
.GetString() + "\n";
1626 cmCTestLog(this, ERROR_MESSAGE
, msg
);
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
)) {
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 "
1654 << file
<< std::endl
);
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
);
1668 std::string
cmCTest::Base64EncodeFile(std::string
const& file
)
1670 size_t const len
= cmSystemTools::FileLength(file
);
1671 cmsys::ifstream
ifs(file
.c_str(),
1677 std::vector
<char> file_buffer(len
+ 1);
1678 ifs
.read(file_buffer
.data(), len
);
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."
1699 this->AddSubmitFile(PartExtraFiles
, file
);
1704 bool cmCTest::SubmitExtraFiles(const std::string
& cfiles
)
1706 if (cfiles
.empty()) {
1710 cmCTestLog(this, OUTPUT
, "Submit extra files" << std::endl
);
1712 std::vector
<std::string
> const files
=
1713 cmSystemTools::SplitString(cfiles
, ';');
1714 if (files
.empty()) {
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");
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
,
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], "-")) {
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
)) {
1891 cmStrCat("'", arg
, "' given invalid value '", args
[i
], "'");
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
], "'");
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";
1915 if (this->Impl
->RepeatMode
!= cmCTest::Repeat::Never
) {
1916 errormsg
= "At most one '--repeat' option may be used.";
1921 if (!cmStrToLong(args
[i
], &repeat
)) {
1922 errormsg
= cmStrCat("'--repeat-until-fail' given non-integer value '",
1926 this->Impl
->RepeatCount
= static_cast<int>(repeat
);
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";
1937 if (this->Impl
->RepeatMode
!= cmCTest::Repeat::Never
) {
1938 errormsg
= "At most one '--repeat' option may be used.";
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
;
1960 errormsg
= cmStrCat("'--repeat' given invalid value '", args
[i
], "'");
1965 else if (this->CheckArgument(arg
, "--test-load"_s
) && i
< args
.size() - 1) {
1968 if (cmStrToULong(args
[i
], &load
)) {
1969 this->SetTestLoad(load
);
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) {
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) {
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) {
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) {
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) {
2040 if (cmStrToLong(args
[i
], &outputSize
)) {
2041 this->Impl
->TestHandler
.SetTestOutputSizePassed(
2042 static_cast<int>(outputSize
));
2044 cmCTestLog(this, WARNING
,
2045 "Invalid value for '--test-output-size-passed': " << args
[i
]
2048 } else if (this->CheckArgument(arg
, "--test-output-size-failed"_s
) &&
2049 i
< args
.size() - 1) {
2052 if (cmStrToLong(args
[i
], &outputSize
)) {
2053 this->Impl
->TestHandler
.SetTestOutputSizeFailed(
2054 static_cast<int>(outputSize
));
2056 cmCTestLog(this, WARNING
,
2057 "Invalid value for '--test-output-size-failed': " << args
[i
]
2060 } else if (this->CheckArgument(arg
, "--test-output-truncation"_s
) &&
2061 i
< args
.size() - 1) {
2063 if (!this->Impl
->TestHandler
.SetTestOutputTruncation(args
[i
])) {
2064 errormsg
= "Invalid value for '--test-output-truncation': " + args
[i
];
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
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
+ "'";
2087 else if (this->CheckArgument(arg
, "-O"_s
, "--output-log") &&
2088 i
< args
.size() - 1) {
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) {
2100 this->Impl
->MaxTestNameWidth
= atoi(args
[i
].c_str());
2101 } else if (this->CheckArgument(arg
, "--interactive-debug-mode"_s
) &&
2102 i
< args
.size() - 1) {
2104 this->Impl
->InteractiveDebugMode
= cmIsOn(args
[i
]);
2105 } else if (this->CheckArgument(arg
, "--submit-index"_s
) &&
2106 i
< args
.size() - 1) {
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) {
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");
2122 this->SetNotesFiles(args
[i
]);
2124 } else if (this->CheckArgument(arg
, "--test-dir"_s
)) {
2125 if (i
>= args
.size() - 1) {
2126 errormsg
= "'--test-dir' requires an argument";
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";
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") {
2147 cmStrCat("'--no-tests=' given unknown value '", noTestsMode
, '\'');
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) {
2159 this->GetTestHandler()->SetPersistentOption("TestsToRunInformation",
2161 this->GetMemCheckHandler()->SetPersistentOption("TestsToRunInformation",
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) {
2169 this->GetTestHandler()->SetPersistentOption("IncludeRegularExpression",
2171 this->GetMemCheckHandler()->SetPersistentOption("IncludeRegularExpression",
2173 } else if (this->CheckArgument(arg
, "-L"_s
, "--label-regex") &&
2174 i
< args
.size() - 1) {
2176 this->GetTestHandler()->AddPersistentMultiOption("LabelRegularExpression",
2178 this->GetMemCheckHandler()->AddPersistentMultiOption(
2179 "LabelRegularExpression", args
[i
]);
2180 } else if (this->CheckArgument(arg
, "-LE"_s
, "--label-exclude") &&
2181 i
< args
.size() - 1) {
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) {
2192 this->GetTestHandler()->SetPersistentOption("ExcludeRegularExpression",
2194 this->GetMemCheckHandler()->SetPersistentOption("ExcludeRegularExpression",
2198 else if (this->CheckArgument(arg
, "-FA"_s
, "--fixture-exclude-any") &&
2199 i
< args
.size() - 1) {
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) {
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) {
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) {
2224 this->GetTestHandler()->SetPersistentOption("ResourceSpecFile", args
[i
]);
2225 this->GetMemCheckHandler()->SetPersistentOption("ResourceSpecFile",
2229 else if (this->CheckArgument(arg
, "--tests-from-file"_s
) &&
2230 i
< args
.size() - 1) {
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) {
2239 this->GetTestHandler()->SetPersistentOption("ExcludeTestListFile",
2241 this->GetMemCheckHandler()->SetPersistentOption("ExcludeTestListFile",
2245 else if (this->CheckArgument(arg
, "--rerun-failed"_s
)) {
2246 this->GetTestHandler()->SetPersistentOption("RerunFailed", "true");
2247 this->GetMemCheckHandler()->SetPersistentOption("RerunFailed", "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";
2265 bool cmCTest::ProgressOutputSupportedByConsole()
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
);
2273 // On UNIX we need a non-dumb tty.
2274 return ConsoleIsNotDumb();
2278 bool cmCTest::ColoredOutputSupportedByConsole()
2280 std::string clicolor_force
;
2281 if (cmSystemTools::GetEnv("CLICOLOR_FORCE", clicolor_force
) &&
2282 !clicolor_force
.empty() && clicolor_force
!= "0") {
2285 std::string clicolor
;
2286 if (cmSystemTools::GetEnv("CLICOLOR", clicolor
) && clicolor
== "0") {
2290 // Not supported on Windows
2293 // On UNIX we need a non-dumb tty.
2294 return ConsoleIsNotDumb();
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;
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;
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;
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);
2338 bool cmCTest::AddVariableDefinition(const std::string
& arg
)
2342 cmStateEnums::CacheEntryType type
= cmStateEnums::UNINITIALIZED
;
2344 if (cmake::ParseCacheEntry(arg
, name
, value
, type
)) {
2345 this->Impl
->Definitions
[name
] = value
;
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
,
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()));
2385 settingsFile
.PrintTestPresetList();
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();
2397 if (presetPair
->second
.Unexpanded
.Hidden
) {
2398 cmSystemTools::Error(cmStrCat("Cannot use hidden test preset in ",
2399 workingDirectory
, ": \"", presetName
, '"'));
2400 settingsFile
.PrintTestPresetList();
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();
2412 if (!expandedPreset
->ConditionResult
) {
2413 cmSystemTools::Error(cmStrCat("Cannot use disabled test preset in ",
2414 workingDirectory
, ": \"", presetName
, '"'));
2415 settingsFile
.PrintTestPresetList();
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();
2429 if (configurePresetPair
->second
.Unexpanded
.Hidden
) {
2430 cmSystemTools::Error(cmStrCat("Cannot use hidden configure preset in ",
2431 workingDirectory
, ": \"",
2432 expandedPreset
->ConfigurePreset
, '"'));
2433 settingsFile
.PrintConfigurePresetList();
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"));
2445 auto presetEnvironment
= expandedPreset
->Environment
;
2446 for (auto const& var
: presetEnvironment
) {
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::
2472 this->Impl
->ExtraVerbose
= true;
2474 case cmCMakePresetsGraph::TestPreset::OutputOptions::VerbosityEnum::
2476 this->Impl
->Verbose
= true;
2478 case cmCMakePresetsGraph::TestPreset::OutputOptions::VerbosityEnum::
2481 // leave default settings
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
) : "") + ",";
2542 cmJoin(expandedPreset
->Filter
->Include
->Index
->SpecificTests
, ",");
2544 this->SetPersistentOptionIfNotEmpty(indexOptions
,
2545 "TestsToRunInformation");
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::
2606 this->Impl
->Quiet
= true;
2607 this->Impl
->OutputAsJson
= true;
2608 this->Impl
->OutputAsJsonVersion
= 1;
2610 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::ShowOnlyEnum::
2612 // intentional fallthrough (human is the default)
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
;
2625 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::RepeatOptions::
2626 ModeEnum::UntilPass
:
2627 this->Impl
->RepeatMode
= cmCTest::Repeat::UntilPass
;
2629 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::RepeatOptions::
2630 ModeEnum::AfterTimeout
:
2631 this->Impl
->RepeatMode
= cmCTest::Repeat::AfterTimeout
;
2634 // should never default since mode is required
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
;
2659 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::
2660 NoTestsActionEnum::Ignore
:
2661 this->Impl
->NoTestsMode
= cmCTest::NoTests::Ignore
;
2663 case cmCMakePresetsGraph::TestPreset::ExecutionOptions::
2664 NoTestsActionEnum::Default
:
2667 // should never default
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
2690 find(args
.begin(), args
.end(), "--list-presets") != args
.end();
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
;
2700 // If listing presets we don't need a presetName
2701 success
= this->SetArgsFromPreset("", listPresets
);
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
);
2710 cmSystemTools::Error("'--preset' requires an argument");
2716 return success
? 0 : 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
);
2733 std::string arg
= args
[i
];
2735 // handle the script arguments -S -SR -SP
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;
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;
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
);
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;
2779 if (this->CheckArgument(arg
, "--extra-submit"_s
) && i
< args
.size() - 1) {
2780 this->Impl
->ProduceXML
= true;
2781 this->SetTest("Submit");
2783 if (!this->SubmitExtraFiles(args
[i
])) {
2789 // --build-and-test options
2790 if (this->CheckArgument(arg
, "--build-and-test"_s
) &&
2791 i
< args
.size() - 1) {
2792 cmakeAndTest
= true;
2796 // --schedule-random
2797 if (this->CheckArgument(arg
, "--schedule-random"_s
)) {
2798 this->Impl
->ScheduleType
= "Random";
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
2805 for (auto& handler
: this->Impl
->GetTestingHandlers()) {
2806 if (!handler
->ProcessCommandLineArguments(arg
, i
, args
, validArg
)) {
2808 this, ERROR_MESSAGE
,
2809 "Problem parsing command line arguments within a handler\n");
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.");
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
);
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
) {
2844 if (cmSystemTools::GetEnv("CTEST_NO_TESTS_ACTION", action
) &&
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
;
2851 cmCTestLog(this, ERROR_MESSAGE
,
2852 "Unknown value for CTEST_NO_TESTS_ACTION: '" << action
2859 // TestProgressOutput only supported if console supports it and not logging
2861 this->Impl
->TestProgressOutput
= this->Impl
->TestProgressOutput
&&
2862 !this->Impl
->OutputLogFile
&& this->ProgressOutputSupportedByConsole();
2864 if (this->Impl
->TestProgressOutput
) {
2865 // Disable output line buffering so we can print content without
2867 std::setvbuf(stdout
, nullptr, _IONBF
, 0);
2871 // now what should cmake do? if --build-and-test was specified then
2872 // we run the build and test handler and return
2874 return this->RunCMakeAndTest(output
);
2878 return this->ExecuteTests();
2884 bool cmCTest::HandleTestActionArgument(const char* ctestExec
, size_t& i
,
2885 const std::vector
<std::string
>& args
,
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)) {
2893 this->Impl
->ProduceXML
= true;
2895 if (!this->SetTest(args
[i
], 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 */
2918 bool cmCTest::HandleTestModelArgument(const char* ctestExec
, size_t& i
,
2919 const std::vector
<std::string
>& args
,
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)) {
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
);
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 */
2951 int cmCTest::ExecuteTests()
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();
2966 cmCTestLog(this, DEBUG
,
2967 "running script failing returning: " << res
<< std::endl
);
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
)) {
2992 if (!this->Initialize(workDir
, nullptr)) {
2994 cmCTestLog(this, ERROR_MESSAGE
,
2995 "Problem initializing the dashboard." << std::endl
);
2997 res
= this->ProcessSteps();
3001 if (currDir
!= workDir
) {
3002 cmSystemTools::ChangeDirectory(currDir
);
3006 cmCTestLog(this, DEBUG
,
3007 "Running a test(s) failed returning : " << res
<< std::endl
);
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();
3022 cmCTestLog(this, DEBUG
,
3023 "build and test failing returning: " << retv
<< std::endl
);
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
)
3052 time_t current_time
= time(nullptr);
3053 lctime
= gmtime(¤t_time
);
3054 int gm_hour
= lctime
->tm_hour
;
3055 time_t gm_time
= mktime(lctime
);
3056 lctime
= localtime(¤t_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
3063 } else if (gm_time
< current_time
&& gm_hour
> local_hour
) {
3064 // this means gm_time is on the previous day
3068 tzone_offset
*= 100;
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(),
3074 time_t stop_time
= curl_getdate(buf
, ¤t_time
);
3075 if (stop_time
== -1) {
3076 this->Impl
->StopTime
= std::chrono::system_clock::time_point();
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
,
3100 cmCTestLog(this, DEBUG
,
3101 "* Read custom CTest configuration directory: " << dir
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
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
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
)) {
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
3135 if (!mf
->ReadListFile(file
) || cmSystemTools::GetErrorOccurredFlag()) {
3136 cmCTestLog(this, ERROR_MESSAGE
,
3137 "Problem reading custom configuration: " << file
3145 for (auto& handler
: this->Impl
->GetNamedTestingHandlers()) {
3146 cmCTestLog(this, DEBUG
,
3147 "* Read custom CTest configuration vectors for handler: "
3148 << handler
.first
<< " (" << handler
.second
<< ")"
3150 handler
.second
->PopulateCustomVectors(mf
);
3157 void cmCTest::PopulateCustomVector(cmMakefile
* mf
, const std::string
& def
,
3158 std::vector
<std::string
>& vec
)
3160 cmValue dval
= mf
->GetDefinition(def
);
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
,
3176 cmValue dval
= mf
->GetDefinition(def
);
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()) {
3220 cmSystemTools::ConvertToUnixSlashes(*res
);
3223 if (path
.back() == '/') {
3224 path
.resize(path
.size() - 1);
3228 cmsys::SystemTools::ReplaceString(path
, ":", "_");
3229 cmsys::SystemTools::ReplaceString(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
,
3250 cmCTestOptionalLog(this, HANDLER_VERBOSE_OUTPUT
,
3251 "SetCTestConfiguration:" << name
<< ":" << value
<< "\n",
3257 if (value
.empty()) {
3258 this->Impl
->CTestConfiguration
.erase(name
);
3261 this->Impl
->CTestConfiguration
[name
] = value
;
3264 std::string
cmCTest::GetSubmitURL()
3266 std::string url
= this->GetCTestConfiguration("SubmitURL");
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()) {
3277 if (!password
.empty()) {
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()) {
3374 return this->Impl
->SpecificGroup
.c_str();
3377 void cmCTest::SetSpecificGroup(const char* group
)
3380 this->Impl
->SpecificGroup
.clear();
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');
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
,
3510 cmValue ctvar
= mf
->GetDefinition(cmake_var
);
3514 cmCTestOptionalLog(this, HANDLER_VERBOSE_OUTPUT
,
3515 "SetCTestConfigurationFromCMakeVariable:"
3516 << dconfig
<< ":" << cmake_var
<< std::endl
,
3518 this->SetCTestConfiguration(dconfig
, *ctvar
, suppress
);
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
,
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);
3537 cmUVProcessChainBuilder builder
;
3538 builder
.AddCommand(args
)
3539 .SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT
)
3540 .SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR
);
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
);
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(
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; });
3584 startRead(outStream
, chain
.OutputStream(), tempOutput
, outFinished
);
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());
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
));
3617 auto const& status
= chain
.GetStatus(0);
3618 auto exception
= status
.GetException();
3619 switch (exception
.first
) {
3620 case cmUVProcessChain::ExceptionCode::None
:
3622 *retVal
= static_cast<int>(status
.ExitStatus
);
3624 if (status
.ExitStatus
!= 0) {
3630 cmCTestLog(this, ERROR_MESSAGE
, exception
.second
<< std::endl
);
3631 stdErr
->append(exception
.second
);
3640 void cmCTest::SetOutputLogFileName(const std::string
& name
)
3642 if (!name
.empty()) {
3643 this->Impl
->OutputLogFile
= cm::make_unique
<cmGeneratedFileStream
>(name
);
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",
3661 "HANDLER_PROGRESS_OUTPUT",
3662 "HANDLER_TEST_PROGRESS_OUTPUT",
3663 "HANDLER_VERBOSE_OUTPUT",
3668 #define cmCTestLogOutputFileLine(stream) \
3670 if (this->Impl->ShowLineNumbers) { \
3671 (stream) << std::endl << file << ":" << line << " "; \
3675 void cmCTest::Log(int logType
, const char* file
, int line
, const char* msg
,
3678 if (!msg
|| !*msg
) {
3681 if (suppress
&& logType
!= cmCTest::ERROR_MESSAGE
) {
3684 if (logType
== cmCTest::HANDLER_PROGRESS_OUTPUT
&&
3685 (this->Impl
->Debug
|| this->Impl
->ExtraVerbose
)) {
3688 if (this->Impl
->OutputLogFile
) {
3689 bool display
= true;
3690 if (logType
== cmCTest::DEBUG
&& !this->Impl
->Debug
) {
3693 if (logType
== cmCTest::HANDLER_VERBOSE_OUTPUT
&& !this->Impl
->Debug
&&
3694 !this->Impl
->ExtraVerbose
) {
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";
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
) {
3724 this->Impl
->FlushTestProgressLine
= false;
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'),
3738 printf("\x1B[K"); // move caret to end
3743 logType
= HANDLER_OUTPUT
;
3748 if (this->Impl
->Debug
) {
3749 cmCTestLogOutputFileLine(out
);
3755 case HANDLER_OUTPUT
:
3756 if (this->Impl
->Debug
|| this->Impl
->Verbose
) {
3757 cmCTestLogOutputFileLine(out
);
3762 case HANDLER_VERBOSE_OUTPUT
:
3763 if (this->Impl
->Debug
|| this->Impl
->ExtraVerbose
) {
3764 cmCTestLogOutputFileLine(out
);
3770 cmCTestLogOutputFileLine(err
);
3775 cmCTestLogOutputFileLine(err
);
3778 cmSystemTools::SetErrorOccurred();
3781 cmCTestLogOutputFileLine(out
);
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";
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
)
3826 strm
.zalloc
= Z_NULL
;
3827 strm
.zfree
= Z_NULL
;
3828 strm
.opaque
= Z_NULL
;
3829 ret
= deflateInit(&strm
, -1); // default compression level
3835 reinterpret_cast<unsigned char*>(const_cast<char*>(str
.c_str()));
3836 // zlib makes the guarantee that this is the maximum output size
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());
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
);
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
);