1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmCTestHandlerCommand.h"
10 #include <cm/string_view>
11 #include <cmext/string_view>
14 #include "cmCTestGenericHandler.h"
15 #include "cmExecutionStatus.h"
16 #include "cmMakefile.h"
17 #include "cmMessageType.h"
18 #include "cmStringAlgorithms.h"
19 #include "cmSystemTools.h"
21 #include "cmWorkingDirectory.h"
24 // class to save and restore the error state for ctest_* commands
25 // if a ctest_* command has a CAPTURE_CMAKE_ERROR then put the error
26 // state into there and restore the system wide error to what
27 // it was before the command ran
28 class SaveRestoreErrorState
31 SaveRestoreErrorState()
33 this->InitialErrorState = cmSystemTools::GetErrorOccurredFlag();
34 cmSystemTools::ResetErrorOccurredFlag(); // rest the error state
35 this->CaptureCMakeErrorValue = false;
37 // if the function has a CAPTURE_CMAKE_ERROR then we should restore
38 // the error state to what it was before the function was run
39 // if not then let the error state be what it is
40 void CaptureCMakeError() { this->CaptureCMakeErrorValue = true; }
41 ~SaveRestoreErrorState()
43 // if we are not saving the return value then make sure
44 // if it was in error it goes back to being in error
45 // otherwise leave it be what it is
46 if (!this->CaptureCMakeErrorValue) {
47 if (this->InitialErrorState) {
48 cmSystemTools::SetErrorOccurred();
52 // if we have saved the error in a return variable
53 // then put things back exactly like they were
54 bool currentState = cmSystemTools::GetErrorOccurredFlag();
55 // if the state changed during this command we need
56 // to handle it, if not then nothing needs to be done
57 if (currentState != this->InitialErrorState) {
58 // restore the initial error state
59 if (this->InitialErrorState) {
60 cmSystemTools::SetErrorOccurred();
62 cmSystemTools::ResetErrorOccurredFlag();
66 SaveRestoreErrorState(const SaveRestoreErrorState&) = delete;
67 SaveRestoreErrorState& operator=(const SaveRestoreErrorState&) = delete;
70 bool InitialErrorState;
71 bool CaptureCMakeErrorValue;
75 bool cmCTestHandlerCommand::InitialPass(std::vector<std::string> const& args,
76 cmExecutionStatus& status)
78 // save error state and restore it if needed
79 SaveRestoreErrorState errorState;
80 // Allocate space for argument values.
81 this->BindArguments();
83 // Process input arguments.
84 std::vector<std::string> unparsedArguments;
85 this->Parse(args, &unparsedArguments);
86 this->CheckArguments();
88 std::sort(this->ParsedKeywords.begin(), this->ParsedKeywords.end());
89 auto it = std::adjacent_find(this->ParsedKeywords.begin(),
90 this->ParsedKeywords.end());
91 if (it != this->ParsedKeywords.end()) {
92 this->Makefile->IssueMessage(
93 MessageType::FATAL_ERROR,
94 cmStrCat("Called with more than one value for ", *it));
97 bool const foundBadArgument = !unparsedArguments.empty();
98 if (foundBadArgument) {
99 this->SetError(cmStrCat("called with unknown argument \"",
100 unparsedArguments.front(), "\"."));
102 bool const captureCMakeError = !this->CaptureCMakeError.empty();
103 // now that arguments are parsed check to see if there is a
104 // CAPTURE_CMAKE_ERROR specified let the errorState object know.
105 if (captureCMakeError) {
106 errorState.CaptureCMakeError();
108 // if we found a bad argument then exit before running command
109 if (foundBadArgument) {
110 // store the cmake error
111 if (captureCMakeError) {
112 this->Makefile->AddDefinition(this->CaptureCMakeError, "-1");
113 std::string const err = this->GetName() + " " + status.GetError();
114 if (!cmSystemTools::FindLastString(err.c_str(), "unknown error.")) {
115 cmCTestLog(this->CTest, ERROR_MESSAGE, err << " error from command\n");
117 // return success because failure is recorded in CAPTURE_CMAKE_ERROR
120 // return failure because of bad argument
124 // Set the config type of this ctest to the current value of the
125 // CTEST_CONFIGURATION_TYPE script variable if it is defined.
126 // The current script value trumps the -C argument on the command
128 cmValue ctestConfigType =
129 this->Makefile->GetDefinition("CTEST_CONFIGURATION_TYPE");
130 if (ctestConfigType) {
131 this->CTest->SetConfigType(*ctestConfigType);
134 if (!this->Build.empty()) {
135 this->CTest->SetCTestConfiguration(
136 "BuildDirectory", cmSystemTools::CollapseFullPath(this->Build),
139 std::string const& bdir =
140 this->Makefile->GetSafeDefinition("CTEST_BINARY_DIRECTORY");
142 this->CTest->SetCTestConfiguration(
143 "BuildDirectory", cmSystemTools::CollapseFullPath(bdir), this->Quiet);
145 cmCTestLog(this->CTest, ERROR_MESSAGE,
146 "CTEST_BINARY_DIRECTORY not set" << std::endl;);
149 if (!this->Source.empty()) {
150 cmCTestLog(this->CTest, DEBUG,
151 "Set source directory to: " << this->Source << std::endl);
152 this->CTest->SetCTestConfiguration(
153 "SourceDirectory", cmSystemTools::CollapseFullPath(this->Source),
156 this->CTest->SetCTestConfiguration(
158 cmSystemTools::CollapseFullPath(
159 this->Makefile->GetSafeDefinition("CTEST_SOURCE_DIRECTORY")),
163 if (cmValue changeId = this->Makefile->GetDefinition("CTEST_CHANGE_ID")) {
164 this->CTest->SetCTestConfiguration("ChangeId", *changeId, this->Quiet);
167 cmCTestLog(this->CTest, DEBUG, "Initialize handler" << std::endl;);
168 cmCTestGenericHandler* handler = this->InitializeHandler();
170 cmCTestLog(this->CTest, ERROR_MESSAGE,
171 "Cannot instantiate test handler " << this->GetName()
173 if (captureCMakeError) {
174 this->Makefile->AddDefinition(this->CaptureCMakeError, "-1");
175 std::string const& err = status.GetError();
176 if (!cmSystemTools::FindLastString(err.c_str(), "unknown error.")) {
177 cmCTestLog(this->CTest, ERROR_MESSAGE, err << " error from command\n");
184 handler->SetAppendXML(this->Append);
186 handler->PopulateCustomVectors(this->Makefile);
187 if (!this->SubmitIndex.empty()) {
188 handler->SetSubmitIndex(atoi(this->SubmitIndex.c_str()));
190 cmWorkingDirectory workdir(
191 this->CTest->GetCTestConfiguration("BuildDirectory"));
192 if (workdir.Failed()) {
193 this->SetError("failed to change directory to " +
194 this->CTest->GetCTestConfiguration("BuildDirectory") +
195 " : " + std::strerror(workdir.GetLastResult()));
196 if (captureCMakeError) {
197 this->Makefile->AddDefinition(this->CaptureCMakeError, "-1");
198 cmCTestLog(this->CTest, ERROR_MESSAGE,
199 this->GetName() << " " << status.GetError() << "\n");
200 // return success because failure is recorded in CAPTURE_CMAKE_ERROR
206 int res = handler->ProcessHandler();
207 if (!this->ReturnValue.empty()) {
208 this->Makefile->AddDefinition(this->ReturnValue, std::to_string(res));
210 this->ProcessAdditionalValues(handler);
211 // log the error message if there was an error
212 if (captureCMakeError) {
213 const char* returnString = "0";
214 if (cmSystemTools::GetErrorOccurredFlag()) {
216 std::string const& err = status.GetError();
217 // print out the error if it is not "unknown error" which means
218 // there was no message
219 if (!cmSystemTools::FindLastString(err.c_str(), "unknown error.")) {
220 cmCTestLog(this->CTest, ERROR_MESSAGE, err);
223 // store the captured cmake error state 0 or -1
224 this->Makefile->AddDefinition(this->CaptureCMakeError, returnString);
229 void cmCTestHandlerCommand::ProcessAdditionalValues(cmCTestGenericHandler*)
233 void cmCTestHandlerCommand::BindArguments()
235 this->BindParsedKeywords(this->ParsedKeywords);
236 this->Bind("APPEND"_s, this->Append);
237 this->Bind("QUIET"_s, this->Quiet);
238 this->Bind("RETURN_VALUE"_s, this->ReturnValue);
239 this->Bind("CAPTURE_CMAKE_ERROR"_s, this->CaptureCMakeError);
240 this->Bind("SOURCE"_s, this->Source);
241 this->Bind("BUILD"_s, this->Build);
242 this->Bind("SUBMIT_INDEX"_s, this->SubmitIndex);
245 void cmCTestHandlerCommand::CheckArguments()