cmCTestBuildAndTest.cxx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file LICENSE.rst or https://cmake.org/licensing for details. */
  3. #include "cmCTestBuildAndTest.h"
  4. #include <chrono>
  5. #include <cstdint>
  6. #include <iostream>
  7. #include <ratio>
  8. #include <utility>
  9. #include <cm3p/uv.h>
  10. #include "cmBuildOptions.h"
  11. #include "cmCTest.h"
  12. #include "cmCTestTestHandler.h"
  13. #include "cmGlobalGenerator.h"
  14. #include "cmMakefile.h"
  15. #include "cmProcessOutput.h"
  16. #include "cmState.h"
  17. #include "cmStringAlgorithms.h"
  18. #include "cmSystemTools.h"
  19. #include "cmUVHandlePtr.h"
  20. #include "cmUVProcessChain.h"
  21. #include "cmUVStream.h"
  22. #include "cmWorkingDirectory.h"
  23. #include "cmake.h"
  24. struct cmMessageMetadata;
  25. cmCTestBuildAndTest::cmCTestBuildAndTest(cmCTest* ctest)
  26. : CTest(ctest)
  27. {
  28. }
  29. bool cmCTestBuildAndTest::RunCMake(cmake* cm)
  30. {
  31. std::vector<std::string> args;
  32. args.push_back(cmSystemTools::GetCMakeCommand());
  33. args.push_back(this->SourceDir);
  34. if (!this->BuildGenerator.empty()) {
  35. args.push_back("-G" + this->BuildGenerator);
  36. }
  37. if (!this->BuildGeneratorPlatform.empty()) {
  38. args.push_back("-A" + this->BuildGeneratorPlatform);
  39. }
  40. if (!this->BuildGeneratorToolset.empty()) {
  41. args.push_back("-T" + this->BuildGeneratorToolset);
  42. }
  43. char const* config = nullptr;
  44. if (!this->CTest->GetConfigType().empty()) {
  45. config = this->CTest->GetConfigType().c_str();
  46. }
  47. if (config) {
  48. args.push_back("-DCMAKE_BUILD_TYPE:STRING=" + std::string(config));
  49. }
  50. if (!this->BuildMakeProgram.empty() &&
  51. (this->BuildGenerator.find("Make") != std::string::npos ||
  52. this->BuildGenerator.find("Ninja") != std::string::npos ||
  53. this->BuildGenerator.find("FASTBuild") != std::string::npos)) {
  54. args.push_back("-DCMAKE_MAKE_PROGRAM:FILEPATH=" + this->BuildMakeProgram);
  55. }
  56. for (std::string const& opt : this->BuildOptions) {
  57. args.push_back(opt);
  58. }
  59. std::cout << "======== CMake output ======\n";
  60. if (cm->Run(args) != 0) {
  61. std::cout << "======== End CMake output ======\n"
  62. "Error: cmake execution failed\n";
  63. return false;
  64. }
  65. // do another config?
  66. if (this->BuildTwoConfig) {
  67. if (cm->Run(args) != 0) {
  68. std::cout << "======== End CMake output ======\n"
  69. "Error: cmake execution failed\n";
  70. return false;
  71. }
  72. }
  73. std::cout << "======== End CMake output ======\n";
  74. return true;
  75. }
  76. bool cmCTestBuildAndTest::RunTest(std::vector<std::string> const& argv,
  77. int* retVal, cmDuration timeout)
  78. {
  79. cmUVProcessChainBuilder builder;
  80. builder.AddCommand(argv).SetMergedBuiltinStreams();
  81. auto chain = builder.Start();
  82. cmProcessOutput processOutput(cmProcessOutput::Auto);
  83. cm::uv_pipe_ptr outputStream;
  84. outputStream.init(chain.GetLoop(), 0);
  85. uv_pipe_open(outputStream, chain.OutputStream());
  86. auto outputHandle = cmUVStreamRead(
  87. outputStream,
  88. [&processOutput](std::vector<char> data) {
  89. std::string decoded;
  90. processOutput.DecodeText(data.data(), data.size(), decoded);
  91. std::cout << decoded << std::flush;
  92. },
  93. []() {});
  94. bool complete = chain.Wait(static_cast<uint64_t>(timeout.count() * 1000.0));
  95. bool result = false;
  96. if (complete) {
  97. auto const& status = chain.GetStatus(0);
  98. auto exception = status.GetException();
  99. switch (exception.first) {
  100. case cmUVProcessChain::ExceptionCode::None:
  101. *retVal = static_cast<int>(status.ExitStatus);
  102. result = true;
  103. break;
  104. case cmUVProcessChain::ExceptionCode::Spawn: {
  105. std::cout << "\n*** ERROR executing: " << exception.second;
  106. } break;
  107. default: {
  108. *retVal = status.TermSignal;
  109. std::cout << "\n*** Exception executing: " << exception.second;
  110. } break;
  111. }
  112. }
  113. return result;
  114. }
  115. class cmCTestBuildAndTestCaptureRAII
  116. {
  117. cmake& CM;
  118. public:
  119. cmCTestBuildAndTestCaptureRAII(cmake& cm)
  120. : CM(cm)
  121. {
  122. cmSystemTools::SetMessageCallback(
  123. [](std::string const& msg, cmMessageMetadata const& /* unused */) {
  124. std::cout << msg << std::endl;
  125. });
  126. cmSystemTools::SetStdoutCallback(
  127. [](std::string const& m) { std::cout << m << std::flush; });
  128. cmSystemTools::SetStderrCallback(
  129. [](std::string const& m) { std::cout << m << std::flush; });
  130. this->CM.SetProgressCallback([](std::string const& msg, float prog) {
  131. if (prog < 0) {
  132. std::cout << msg << std::endl;
  133. }
  134. });
  135. }
  136. ~cmCTestBuildAndTestCaptureRAII()
  137. {
  138. this->CM.SetProgressCallback(nullptr);
  139. cmSystemTools::SetStderrCallback(nullptr);
  140. cmSystemTools::SetStdoutCallback(nullptr);
  141. cmSystemTools::SetMessageCallback(nullptr);
  142. }
  143. cmCTestBuildAndTestCaptureRAII(cmCTestBuildAndTestCaptureRAII const&) =
  144. delete;
  145. cmCTestBuildAndTestCaptureRAII& operator=(
  146. cmCTestBuildAndTestCaptureRAII const&) = delete;
  147. };
  148. int cmCTestBuildAndTest::Run()
  149. {
  150. // if the generator and make program are not specified then it is an error
  151. if (this->BuildGenerator.empty()) {
  152. std::cout << "--build-and-test requires that the generator "
  153. "be provided using the --build-generator "
  154. "command line option.\n";
  155. return 1;
  156. }
  157. cmake cm(cmake::RoleProject, cmState::Project);
  158. cmCTestBuildAndTestCaptureRAII captureRAII(cm);
  159. static_cast<void>(captureRAII);
  160. if (this->CTest->GetConfigType().empty() && !this->ConfigSample.empty()) {
  161. // use the config sample to set the ConfigType
  162. std::string fullPath;
  163. std::string resultingConfig;
  164. std::vector<std::string> extraPaths;
  165. std::vector<std::string> failed;
  166. fullPath = cmCTestTestHandler::FindExecutable(
  167. this->CTest, this->ConfigSample, resultingConfig, extraPaths, failed);
  168. if (!fullPath.empty() && !resultingConfig.empty()) {
  169. this->CTest->SetConfigType(resultingConfig);
  170. }
  171. std::cout << "Using config sample with results: " << fullPath << " and "
  172. << resultingConfig << std::endl;
  173. }
  174. // we need to honor the timeout specified, the timeout include cmake, build
  175. // and test time
  176. auto clock_start = std::chrono::steady_clock::now();
  177. // make sure the binary dir is there
  178. std::cout << "Internal cmake changing into directory: " << this->BinaryDir
  179. << std::endl;
  180. if (!cmSystemTools::FileIsDirectory(this->BinaryDir)) {
  181. cmSystemTools::MakeDirectory(this->BinaryDir);
  182. }
  183. cmWorkingDirectory workdir(this->BinaryDir);
  184. if (workdir.Failed()) {
  185. std::cout << workdir.GetError() << '\n';
  186. return 1;
  187. }
  188. if (this->BuildNoCMake) {
  189. // Make the generator available for the Build call below.
  190. cm.SetGlobalGenerator(cm.CreateGlobalGenerator(this->BuildGenerator));
  191. if (!this->BuildGeneratorPlatform.empty()) {
  192. cmMakefile mf(cm.GetGlobalGenerator(), cm.GetCurrentSnapshot());
  193. if (!cm.GetGlobalGenerator()->SetGeneratorPlatform(
  194. this->BuildGeneratorPlatform, &mf)) {
  195. return 1;
  196. }
  197. }
  198. // Load the cache to make CMAKE_MAKE_PROGRAM available.
  199. cm.LoadCache(this->BinaryDir);
  200. } else {
  201. // do the cmake step, no timeout here since it is not a sub process
  202. if (!this->RunCMake(&cm)) {
  203. return 1;
  204. }
  205. }
  206. // do the build
  207. if (this->BuildTargets.empty()) {
  208. this->BuildTargets.emplace_back();
  209. }
  210. for (std::string const& tar : this->BuildTargets) {
  211. cmDuration remainingTime = std::chrono::seconds(0);
  212. if (this->Timeout > cmDuration::zero()) {
  213. remainingTime =
  214. this->Timeout - (std::chrono::steady_clock::now() - clock_start);
  215. if (remainingTime <= std::chrono::seconds(0)) {
  216. std::cout << "--build-and-test timeout exceeded. ";
  217. return 1;
  218. }
  219. }
  220. char const* config = nullptr;
  221. if (!this->CTest->GetConfigType().empty()) {
  222. config = this->CTest->GetConfigType().c_str();
  223. }
  224. if (!config) {
  225. config = "Debug";
  226. }
  227. cmBuildOptions buildOptions(!this->BuildNoClean, false,
  228. PackageResolveMode::Disable);
  229. int retVal = cm.GetGlobalGenerator()->Build(
  230. cmake::NO_BUILD_PARALLEL_LEVEL, this->SourceDir, this->BinaryDir,
  231. this->BuildProject, { tar }, std::cout, this->BuildMakeProgram, config,
  232. buildOptions, false, remainingTime, cmSystemTools::OUTPUT_PASSTHROUGH);
  233. // if the build failed then return
  234. if (retVal) {
  235. return 1;
  236. }
  237. }
  238. // if no test was specified then we are done
  239. if (this->TestCommand.empty()) {
  240. return 0;
  241. }
  242. // now run the compiled test if we can find it
  243. // store the final location in fullPath
  244. std::string fullPath;
  245. std::string resultingConfig;
  246. std::vector<std::string> extraPaths;
  247. // if this->ExecutableDirectory is set try that as well
  248. if (!this->ExecutableDirectory.empty()) {
  249. std::string tempPath =
  250. cmStrCat(this->ExecutableDirectory, '/', this->TestCommand);
  251. extraPaths.push_back(tempPath);
  252. }
  253. std::vector<std::string> failed;
  254. fullPath = cmCTestTestHandler::FindExecutable(
  255. this->CTest, this->TestCommand, resultingConfig, extraPaths, failed);
  256. if (!cmSystemTools::FileExists(fullPath)) {
  257. std::cout
  258. << "Could not find path to executable, perhaps it was not built: "
  259. << this->TestCommand << "\n"
  260. << "tried to find it in these places:\n"
  261. << fullPath << '\n';
  262. for (std::string const& fail : failed) {
  263. std::cout << fail << '\n';
  264. }
  265. return 1;
  266. }
  267. std::vector<std::string> testCommand;
  268. testCommand.push_back(fullPath);
  269. for (std::string const& testCommandArg : this->TestCommandArgs) {
  270. testCommand.push_back(testCommandArg);
  271. }
  272. int retval = 0;
  273. // run the test from the this->BuildRunDir if set
  274. if (!this->BuildRunDir.empty()) {
  275. std::cout << "Run test in directory: " << this->BuildRunDir << '\n';
  276. if (!workdir.SetDirectory(this->BuildRunDir)) {
  277. std::cout << workdir.GetError() << '\n';
  278. return 1;
  279. }
  280. }
  281. std::cout << "Running test command: \"" << fullPath << '"';
  282. for (std::string const& testCommandArg : this->TestCommandArgs) {
  283. std::cout << " \"" << testCommandArg << '"';
  284. }
  285. std::cout << '\n';
  286. // how much time is remaining
  287. cmDuration remainingTime = std::chrono::seconds(0);
  288. if (this->Timeout > cmDuration::zero()) {
  289. remainingTime =
  290. this->Timeout - (std::chrono::steady_clock::now() - clock_start);
  291. if (remainingTime <= std::chrono::seconds(0)) {
  292. std::cout << "--build-and-test timeout exceeded. ";
  293. return 1;
  294. }
  295. }
  296. bool runTestRes = this->RunTest(testCommand, &retval, remainingTime);
  297. if (!runTestRes || retval != 0) {
  298. std::cout << "\nTest command failed: " << testCommand[0] << '\n';
  299. retval = 1;
  300. }
  301. return retval;
  302. }