cmCTestHandlerCommand.cxx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  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. #include "cmCTest.h"
  5. #include "cmCTestGenericHandler.h"
  6. #include "cmExecutionStatus.h"
  7. #include "cmMakefile.h"
  8. #include "cmMessageType.h"
  9. #include "cmSystemTools.h"
  10. #include "cmWorkingDirectory.h"
  11. #include <cstring>
  12. #include <sstream>
  13. #include <stdlib.h>
  14. cmCTestHandlerCommand::cmCTestHandlerCommand()
  15. {
  16. const size_t INIT_SIZE = 100;
  17. size_t cc;
  18. this->Arguments.reserve(INIT_SIZE);
  19. for (cc = 0; cc < INIT_SIZE; ++cc) {
  20. this->Arguments.push_back(nullptr);
  21. }
  22. this->Arguments[ct_RETURN_VALUE] = "RETURN_VALUE";
  23. this->Arguments[ct_CAPTURE_CMAKE_ERROR] = "CAPTURE_CMAKE_ERROR";
  24. this->Arguments[ct_SOURCE] = "SOURCE";
  25. this->Arguments[ct_BUILD] = "BUILD";
  26. this->Arguments[ct_SUBMIT_INDEX] = "SUBMIT_INDEX";
  27. this->Last = ct_LAST;
  28. this->AppendXML = false;
  29. this->Quiet = false;
  30. }
  31. namespace {
  32. // class to save and restore the error state for ctest_* commands
  33. // if a ctest_* command has a CAPTURE_CMAKE_ERROR then put the error
  34. // state into there and restore the system wide error to what
  35. // it was before the command ran
  36. class SaveRestoreErrorState
  37. {
  38. public:
  39. SaveRestoreErrorState()
  40. {
  41. this->InitialErrorState = cmSystemTools::GetErrorOccuredFlag();
  42. cmSystemTools::ResetErrorOccuredFlag(); // rest the error state
  43. this->CaptureCMakeErrorValue = false;
  44. }
  45. // if the function has a CAPTURE_CMAKE_ERROR then we should restore
  46. // the error state to what it was before the function was run
  47. // if not then let the error state be what it is
  48. void CaptureCMakeError() { this->CaptureCMakeErrorValue = true; }
  49. ~SaveRestoreErrorState()
  50. {
  51. // if we are not saving the return value then make sure
  52. // if it was in error it goes back to being in error
  53. // otherwise leave it be what it is
  54. if (!this->CaptureCMakeErrorValue) {
  55. if (this->InitialErrorState) {
  56. cmSystemTools::SetErrorOccured();
  57. }
  58. return;
  59. }
  60. // if we have saved the error in a return variable
  61. // then put things back exactly like they were
  62. bool currentState = cmSystemTools::GetErrorOccuredFlag();
  63. // if the state changed during this command we need
  64. // to handle it, if not then nothing needs to be done
  65. if (currentState != this->InitialErrorState) {
  66. // restore the initial error state
  67. if (this->InitialErrorState) {
  68. cmSystemTools::SetErrorOccured();
  69. } else {
  70. cmSystemTools::ResetErrorOccuredFlag();
  71. }
  72. }
  73. }
  74. SaveRestoreErrorState(const SaveRestoreErrorState&) = delete;
  75. SaveRestoreErrorState& operator=(const SaveRestoreErrorState&) = delete;
  76. private:
  77. bool InitialErrorState;
  78. bool CaptureCMakeErrorValue;
  79. };
  80. }
  81. bool cmCTestHandlerCommand::InitialPass(std::vector<std::string> const& args,
  82. cmExecutionStatus& status)
  83. {
  84. // save error state and restore it if needed
  85. SaveRestoreErrorState errorState;
  86. // Allocate space for argument values.
  87. this->Values.clear();
  88. this->Values.resize(this->Last, nullptr);
  89. // Process input arguments.
  90. this->ArgumentDoing = ArgumentDoingNone;
  91. // look at all arguments and do not short circuit on the first
  92. // bad one so that CAPTURE_CMAKE_ERROR can override setting the
  93. // global error state
  94. bool foundBadArgument = false;
  95. for (std::string const& arg : args) {
  96. // Check this argument.
  97. if (!this->CheckArgumentKeyword(arg) && !this->CheckArgumentValue(arg)) {
  98. std::ostringstream e;
  99. e << "called with unknown argument \"" << arg << "\".";
  100. this->SetError(e.str());
  101. foundBadArgument = true;
  102. }
  103. // note bad argument
  104. if (this->ArgumentDoing == ArgumentDoingError) {
  105. foundBadArgument = true;
  106. }
  107. }
  108. bool captureCMakeError = (this->Values[ct_CAPTURE_CMAKE_ERROR] &&
  109. *this->Values[ct_CAPTURE_CMAKE_ERROR]);
  110. // now that arguments are parsed check to see if there is a
  111. // CAPTURE_CMAKE_ERROR specified let the errorState object know.
  112. if (captureCMakeError) {
  113. errorState.CaptureCMakeError();
  114. }
  115. // if we found a bad argument then exit before running command
  116. if (foundBadArgument) {
  117. // store the cmake error
  118. if (captureCMakeError) {
  119. this->Makefile->AddDefinition(this->Values[ct_CAPTURE_CMAKE_ERROR],
  120. "-1");
  121. std::string const err = this->GetName() + " " + status.GetError();
  122. if (!cmSystemTools::FindLastString(err.c_str(), "unknown error.")) {
  123. cmCTestLog(this->CTest, ERROR_MESSAGE, err << " error from command\n");
  124. }
  125. // return success because failure is recorded in CAPTURE_CMAKE_ERROR
  126. return true;
  127. }
  128. // return failure because of bad argument
  129. return false;
  130. }
  131. // Set the config type of this ctest to the current value of the
  132. // CTEST_CONFIGURATION_TYPE script variable if it is defined.
  133. // The current script value trumps the -C argument on the command
  134. // line.
  135. const char* ctestConfigType =
  136. this->Makefile->GetDefinition("CTEST_CONFIGURATION_TYPE");
  137. if (ctestConfigType) {
  138. this->CTest->SetConfigType(ctestConfigType);
  139. }
  140. if (this->Values[ct_BUILD]) {
  141. this->CTest->SetCTestConfiguration(
  142. "BuildDirectory",
  143. cmSystemTools::CollapseFullPath(this->Values[ct_BUILD]).c_str(),
  144. this->Quiet);
  145. } else {
  146. std::string const& bdir =
  147. this->Makefile->GetSafeDefinition("CTEST_BINARY_DIRECTORY");
  148. if (!bdir.empty()) {
  149. this->CTest->SetCTestConfiguration(
  150. "BuildDirectory", cmSystemTools::CollapseFullPath(bdir).c_str(),
  151. this->Quiet);
  152. } else {
  153. cmCTestLog(this->CTest, ERROR_MESSAGE,
  154. "CTEST_BINARY_DIRECTORY not set" << std::endl;);
  155. }
  156. }
  157. if (this->Values[ct_SOURCE]) {
  158. cmCTestLog(this->CTest, DEBUG,
  159. "Set source directory to: " << this->Values[ct_SOURCE]
  160. << std::endl);
  161. this->CTest->SetCTestConfiguration(
  162. "SourceDirectory",
  163. cmSystemTools::CollapseFullPath(this->Values[ct_SOURCE]).c_str(),
  164. this->Quiet);
  165. } else {
  166. this->CTest->SetCTestConfiguration(
  167. "SourceDirectory",
  168. cmSystemTools::CollapseFullPath(
  169. this->Makefile->GetSafeDefinition("CTEST_SOURCE_DIRECTORY"))
  170. .c_str(),
  171. this->Quiet);
  172. }
  173. if (const char* changeId =
  174. this->Makefile->GetDefinition("CTEST_CHANGE_ID")) {
  175. this->CTest->SetCTestConfiguration("ChangeId", changeId, this->Quiet);
  176. }
  177. cmCTestLog(this->CTest, DEBUG, "Initialize handler" << std::endl;);
  178. cmCTestGenericHandler* handler = this->InitializeHandler();
  179. if (!handler) {
  180. cmCTestLog(this->CTest, ERROR_MESSAGE,
  181. "Cannot instantiate test handler " << this->GetName()
  182. << std::endl);
  183. if (captureCMakeError) {
  184. this->Makefile->AddDefinition(this->Values[ct_CAPTURE_CMAKE_ERROR],
  185. "-1");
  186. std::string const& err = status.GetError();
  187. if (!cmSystemTools::FindLastString(err.c_str(), "unknown error.")) {
  188. cmCTestLog(this->CTest, ERROR_MESSAGE, err << " error from command\n");
  189. }
  190. return true;
  191. }
  192. return false;
  193. }
  194. handler->SetAppendXML(this->AppendXML);
  195. handler->PopulateCustomVectors(this->Makefile);
  196. if (this->Values[ct_SUBMIT_INDEX]) {
  197. handler->SetSubmitIndex(atoi(this->Values[ct_SUBMIT_INDEX]));
  198. }
  199. cmWorkingDirectory workdir(
  200. this->CTest->GetCTestConfiguration("BuildDirectory"));
  201. if (workdir.Failed()) {
  202. this->SetError("failed to change directory to " +
  203. this->CTest->GetCTestConfiguration("BuildDirectory") +
  204. " : " + std::strerror(workdir.GetLastResult()));
  205. if (captureCMakeError) {
  206. this->Makefile->AddDefinition(this->Values[ct_CAPTURE_CMAKE_ERROR],
  207. "-1");
  208. cmCTestLog(this->CTest, ERROR_MESSAGE,
  209. this->GetName() << " " << status.GetError() << "\n");
  210. // return success because failure is recorded in CAPTURE_CMAKE_ERROR
  211. return true;
  212. }
  213. return false;
  214. }
  215. int res = handler->ProcessHandler();
  216. if (this->Values[ct_RETURN_VALUE] && *this->Values[ct_RETURN_VALUE]) {
  217. std::ostringstream str;
  218. str << res;
  219. this->Makefile->AddDefinition(this->Values[ct_RETURN_VALUE], str.str());
  220. }
  221. this->ProcessAdditionalValues(handler);
  222. // log the error message if there was an error
  223. if (captureCMakeError) {
  224. const char* returnString = "0";
  225. if (cmSystemTools::GetErrorOccuredFlag()) {
  226. returnString = "-1";
  227. std::string const& err = status.GetError();
  228. // print out the error if it is not "unknown error" which means
  229. // there was no message
  230. if (!cmSystemTools::FindLastString(err.c_str(), "unknown error.")) {
  231. cmCTestLog(this->CTest, ERROR_MESSAGE, err);
  232. }
  233. }
  234. // store the captured cmake error state 0 or -1
  235. this->Makefile->AddDefinition(this->Values[ct_CAPTURE_CMAKE_ERROR],
  236. returnString);
  237. }
  238. return true;
  239. }
  240. void cmCTestHandlerCommand::ProcessAdditionalValues(cmCTestGenericHandler*)
  241. {
  242. }
  243. bool cmCTestHandlerCommand::CheckArgumentKeyword(std::string const& arg)
  244. {
  245. // Look for non-value arguments common to all commands.
  246. if (arg == "APPEND") {
  247. this->ArgumentDoing = ArgumentDoingNone;
  248. this->AppendXML = true;
  249. return true;
  250. }
  251. if (arg == "QUIET") {
  252. this->ArgumentDoing = ArgumentDoingNone;
  253. this->Quiet = true;
  254. return true;
  255. }
  256. // Check for a keyword in our argument/value table.
  257. for (unsigned int k = 0; k < this->Arguments.size(); ++k) {
  258. if (this->Arguments[k] && arg == this->Arguments[k]) {
  259. this->ArgumentDoing = ArgumentDoingKeyword;
  260. this->ArgumentIndex = k;
  261. return true;
  262. }
  263. }
  264. return false;
  265. }
  266. bool cmCTestHandlerCommand::CheckArgumentValue(std::string const& arg)
  267. {
  268. if (this->ArgumentDoing == ArgumentDoingKeyword) {
  269. this->ArgumentDoing = ArgumentDoingNone;
  270. unsigned int k = this->ArgumentIndex;
  271. if (this->Values[k]) {
  272. std::ostringstream e;
  273. e << "Called with more than one value for " << this->Arguments[k];
  274. this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
  275. this->ArgumentDoing = ArgumentDoingError;
  276. return true;
  277. }
  278. this->Values[k] = arg.c_str();
  279. cmCTestLog(this->CTest, DEBUG,
  280. "Set " << this->Arguments[k] << " to " << arg << "\n");
  281. return true;
  282. }
  283. return false;
  284. }