Imported Upstream version 3.25.0
[platform/upstream/cmake.git] / Source / CTest / cmCTestHandlerCommand.cxx
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"
4
5 #include <algorithm>
6 #include <cstdlib>
7 #include <cstring>
8 #include <sstream>
9
10 #include <cm/string_view>
11 #include <cmext/string_view>
12
13 #include "cmCTest.h"
14 #include "cmCTestGenericHandler.h"
15 #include "cmExecutionStatus.h"
16 #include "cmMakefile.h"
17 #include "cmMessageType.h"
18 #include "cmStringAlgorithms.h"
19 #include "cmSystemTools.h"
20 #include "cmValue.h"
21 #include "cmWorkingDirectory.h"
22
23 namespace {
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
29 {
30 public:
31   SaveRestoreErrorState()
32   {
33     this->InitialErrorState = cmSystemTools::GetErrorOccurredFlag();
34     cmSystemTools::ResetErrorOccurredFlag(); // rest the error state
35     this->CaptureCMakeErrorValue = false;
36   }
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()
42   {
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();
49       }
50       return;
51     }
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();
61       } else {
62         cmSystemTools::ResetErrorOccurredFlag();
63       }
64     }
65   }
66   SaveRestoreErrorState(const SaveRestoreErrorState&) = delete;
67   SaveRestoreErrorState& operator=(const SaveRestoreErrorState&) = delete;
68
69 private:
70   bool InitialErrorState;
71   bool CaptureCMakeErrorValue;
72 };
73 }
74
75 bool cmCTestHandlerCommand::InitialPass(std::vector<std::string> const& args,
76                                         cmExecutionStatus& status)
77 {
78   // save error state and restore it if needed
79   SaveRestoreErrorState errorState;
80   // Allocate space for argument values.
81   this->BindArguments();
82
83   // Process input arguments.
84   std::vector<std::string> unparsedArguments;
85   this->Parse(args, &unparsedArguments);
86   this->CheckArguments();
87
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));
95   }
96
97   bool const foundBadArgument = !unparsedArguments.empty();
98   if (foundBadArgument) {
99     this->SetError(cmStrCat("called with unknown argument \"",
100                             unparsedArguments.front(), "\"."));
101   }
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();
107   }
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");
116       }
117       // return success because failure is recorded in CAPTURE_CMAKE_ERROR
118       return true;
119     }
120     // return failure because of bad argument
121     return false;
122   }
123
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
127   // line.
128   cmValue ctestConfigType =
129     this->Makefile->GetDefinition("CTEST_CONFIGURATION_TYPE");
130   if (ctestConfigType) {
131     this->CTest->SetConfigType(*ctestConfigType);
132   }
133
134   if (!this->Build.empty()) {
135     this->CTest->SetCTestConfiguration(
136       "BuildDirectory", cmSystemTools::CollapseFullPath(this->Build),
137       this->Quiet);
138   } else {
139     std::string const& bdir =
140       this->Makefile->GetSafeDefinition("CTEST_BINARY_DIRECTORY");
141     if (!bdir.empty()) {
142       this->CTest->SetCTestConfiguration(
143         "BuildDirectory", cmSystemTools::CollapseFullPath(bdir), this->Quiet);
144     } else {
145       cmCTestLog(this->CTest, ERROR_MESSAGE,
146                  "CTEST_BINARY_DIRECTORY not set" << std::endl;);
147     }
148   }
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),
154       this->Quiet);
155   } else {
156     this->CTest->SetCTestConfiguration(
157       "SourceDirectory",
158       cmSystemTools::CollapseFullPath(
159         this->Makefile->GetSafeDefinition("CTEST_SOURCE_DIRECTORY")),
160       this->Quiet);
161   }
162
163   if (cmValue changeId = this->Makefile->GetDefinition("CTEST_CHANGE_ID")) {
164     this->CTest->SetCTestConfiguration("ChangeId", *changeId, this->Quiet);
165   }
166
167   cmCTestLog(this->CTest, DEBUG, "Initialize handler" << std::endl;);
168   cmCTestGenericHandler* handler = this->InitializeHandler();
169   if (!handler) {
170     cmCTestLog(this->CTest, ERROR_MESSAGE,
171                "Cannot instantiate test handler " << this->GetName()
172                                                   << std::endl);
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");
178       }
179       return true;
180     }
181     return false;
182   }
183
184   handler->SetAppendXML(this->Append);
185
186   handler->PopulateCustomVectors(this->Makefile);
187   if (!this->SubmitIndex.empty()) {
188     handler->SetSubmitIndex(atoi(this->SubmitIndex.c_str()));
189   }
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
201       return true;
202     }
203     return false;
204   }
205
206   int res = handler->ProcessHandler();
207   if (!this->ReturnValue.empty()) {
208     this->Makefile->AddDefinition(this->ReturnValue, std::to_string(res));
209   }
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()) {
215       returnString = "-1";
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);
221       }
222     }
223     // store the captured cmake error state 0 or -1
224     this->Makefile->AddDefinition(this->CaptureCMakeError, returnString);
225   }
226   return true;
227 }
228
229 void cmCTestHandlerCommand::ProcessAdditionalValues(cmCTestGenericHandler*)
230 {
231 }
232
233 void cmCTestHandlerCommand::BindArguments()
234 {
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);
243 }
244
245 void cmCTestHandlerCommand::CheckArguments()
246 {
247 }