cmCTestSubmitHandler.cxx 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945
  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 "cmCTestSubmitHandler.h"
  4. #include <chrono>
  5. #include <cstdio>
  6. #include <cstdlib>
  7. #include <sstream>
  8. #include <cm/iomanip>
  9. #include <cm/optional>
  10. #include <cm/string_view>
  11. #include <cmext/algorithm>
  12. #include <cm3p/curl/curl.h>
  13. #include <cm3p/json/reader.h>
  14. #include <cm3p/json/value.h>
  15. #include "cmAlgorithms.h"
  16. #include "cmCTest.h"
  17. #include "cmCTestCurl.h"
  18. #include "cmCTestScriptHandler.h"
  19. #include "cmCryptoHash.h"
  20. #include "cmCurl.h"
  21. #include "cmDuration.h"
  22. #include "cmGeneratedFileStream.h"
  23. #include "cmState.h"
  24. #include "cmStringAlgorithms.h"
  25. #include "cmSystemTools.h"
  26. #include "cmValue.h"
  27. #include "cmXMLParser.h"
  28. #include "cmake.h"
  29. #define SUBMIT_TIMEOUT_IN_SECONDS_DEFAULT 120
  30. using cmCTestSubmitHandlerVectorOfChar = std::vector<char>;
  31. class cmCTestSubmitHandler::ResponseParser : public cmXMLParser
  32. {
  33. public:
  34. enum StatusType
  35. {
  36. STATUS_OK,
  37. STATUS_WARNING,
  38. STATUS_ERROR
  39. };
  40. StatusType Status = STATUS_OK;
  41. std::string Filename;
  42. std::string MD5;
  43. std::string Message;
  44. std::string BuildID;
  45. private:
  46. std::vector<char> CurrentValue;
  47. std::string GetCurrentValue()
  48. {
  49. std::string val;
  50. if (!this->CurrentValue.empty()) {
  51. val.assign(this->CurrentValue.data(), this->CurrentValue.size());
  52. }
  53. return val;
  54. }
  55. void StartElement(const std::string& /*name*/,
  56. const char** /*atts*/) override
  57. {
  58. this->CurrentValue.clear();
  59. }
  60. void CharacterDataHandler(const char* data, int length) override
  61. {
  62. cm::append(this->CurrentValue, data, data + length);
  63. }
  64. void EndElement(const std::string& name) override
  65. {
  66. if (name == "status") {
  67. std::string status = cmSystemTools::UpperCase(this->GetCurrentValue());
  68. if (status == "OK" || status == "SUCCESS") {
  69. this->Status = STATUS_OK;
  70. } else if (status == "WARNING") {
  71. this->Status = STATUS_WARNING;
  72. } else {
  73. this->Status = STATUS_ERROR;
  74. }
  75. } else if (name == "filename") {
  76. this->Filename = this->GetCurrentValue();
  77. } else if (name == "md5") {
  78. this->MD5 = this->GetCurrentValue();
  79. } else if (name == "message") {
  80. this->Message = this->GetCurrentValue();
  81. } else if (name == "buildId") {
  82. this->BuildID = this->GetCurrentValue();
  83. }
  84. }
  85. };
  86. static size_t cmCTestSubmitHandlerWriteMemoryCallback(void* ptr, size_t size,
  87. size_t nmemb, void* data)
  88. {
  89. int realsize = static_cast<int>(size * nmemb);
  90. const char* chPtr = static_cast<char*>(ptr);
  91. cm::append(*static_cast<cmCTestSubmitHandlerVectorOfChar*>(data), chPtr,
  92. chPtr + realsize);
  93. return realsize;
  94. }
  95. static size_t cmCTestSubmitHandlerCurlDebugCallback(CURL* /*unused*/,
  96. curl_infotype /*unused*/,
  97. char* chPtr, size_t size,
  98. void* data)
  99. {
  100. cm::append(*static_cast<cmCTestSubmitHandlerVectorOfChar*>(data), chPtr,
  101. chPtr + size);
  102. return 0;
  103. }
  104. cmCTestSubmitHandler::cmCTestSubmitHandler()
  105. {
  106. this->Initialize();
  107. }
  108. void cmCTestSubmitHandler::Initialize()
  109. {
  110. // We submit all available parts by default.
  111. for (cmCTest::Part p = cmCTest::PartStart; p != cmCTest::PartCount;
  112. p = static_cast<cmCTest::Part>(p + 1)) {
  113. this->SubmitPart[p] = true;
  114. }
  115. this->HasWarnings = false;
  116. this->HasErrors = false;
  117. this->Superclass::Initialize();
  118. this->HTTPProxy.clear();
  119. this->HTTPProxyType = 0;
  120. this->HTTPProxyAuth.clear();
  121. this->LogFile = nullptr;
  122. this->Files.clear();
  123. }
  124. int cmCTestSubmitHandler::ProcessCommandLineArguments(
  125. const std::string& currentArg, size_t& idx,
  126. const std::vector<std::string>& allArgs, bool& validArg)
  127. {
  128. if (cmHasLiteralPrefix(currentArg, "--http-header") &&
  129. idx < allArgs.size() - 1) {
  130. ++idx;
  131. this->HttpHeaders.push_back(allArgs[idx]);
  132. this->CommandLineHttpHeaders.push_back(allArgs[idx]);
  133. validArg = true;
  134. }
  135. return 1;
  136. }
  137. bool cmCTestSubmitHandler::SubmitUsingHTTP(
  138. const std::string& localprefix, const std::vector<std::string>& files,
  139. const std::string& remoteprefix, const std::string& url)
  140. {
  141. CURL* curl;
  142. FILE* ftpfile;
  143. char error_buffer[1024];
  144. // Set Content-Type to satisfy fussy modsecurity rules.
  145. struct curl_slist* headers =
  146. ::curl_slist_append(nullptr, "Content-Type: text/xml");
  147. // Add any additional headers that the user specified.
  148. for (std::string const& h : this->HttpHeaders) {
  149. cmCTestOptionalLog(this->CTest, DEBUG,
  150. " Add HTTP Header: \"" << h << "\"" << std::endl,
  151. this->Quiet);
  152. headers = ::curl_slist_append(headers, h.c_str());
  153. }
  154. cmCurlInitOnce();
  155. /* In windows, this will init the winsock stuff */
  156. ::curl_global_init(CURL_GLOBAL_ALL);
  157. cmCTestCurlOpts curlOpts(this->CTest);
  158. for (std::string const& file : files) {
  159. /* get a curl handle */
  160. curl = cm_curl_easy_init();
  161. if (curl) {
  162. cmCurlSetCAInfo(curl);
  163. if (curlOpts.TLSVersionOpt) {
  164. cm::optional<std::string> tlsVersionStr =
  165. cmCurlPrintTLSVersion(*curlOpts.TLSVersionOpt);
  166. cmCTestOptionalLog(
  167. this->CTest, HANDLER_VERBOSE_OUTPUT,
  168. " Set CURLOPT_SSLVERSION to "
  169. << (tlsVersionStr ? *tlsVersionStr : "unknown value") << "\n",
  170. this->Quiet);
  171. curl_easy_setopt(curl, CURLOPT_SSLVERSION, *curlOpts.TLSVersionOpt);
  172. }
  173. if (curlOpts.TLSVerifyOpt) {
  174. cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
  175. " Set CURLOPT_SSL_VERIFYPEER to "
  176. << (*curlOpts.TLSVerifyOpt ? "on" : "off")
  177. << "\n",
  178. this->Quiet);
  179. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER,
  180. *curlOpts.TLSVerifyOpt ? 1 : 0);
  181. }
  182. if (curlOpts.VerifyHostOff) {
  183. cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
  184. " Set CURLOPT_SSL_VERIFYHOST to off\n",
  185. this->Quiet);
  186. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
  187. }
  188. // Using proxy
  189. if (this->HTTPProxyType > 0) {
  190. curl_easy_setopt(curl, CURLOPT_PROXY, this->HTTPProxy.c_str());
  191. switch (this->HTTPProxyType) {
  192. case 2:
  193. curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
  194. break;
  195. case 3:
  196. curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
  197. break;
  198. default:
  199. curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
  200. if (!this->HTTPProxyAuth.empty()) {
  201. curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD,
  202. this->HTTPProxyAuth.c_str());
  203. }
  204. }
  205. }
  206. if (this->CTest->ShouldUseHTTP10()) {
  207. curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
  208. }
  209. /* enable uploading */
  210. curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
  211. // if there is little to no activity for too long stop submitting
  212. ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
  213. auto submitInactivityTimeout = this->GetSubmitInactivityTimeout();
  214. if (submitInactivityTimeout != 0) {
  215. ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME,
  216. submitInactivityTimeout);
  217. }
  218. ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
  219. ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
  220. std::string local_file = file;
  221. bool initialize_cdash_buildid = false;
  222. if (!cmSystemTools::FileExists(local_file)) {
  223. local_file = cmStrCat(localprefix, "/", file);
  224. // If this file exists within the local Testing directory we assume
  225. // that it will be associated with the current build in CDash.
  226. initialize_cdash_buildid = true;
  227. }
  228. std::string remote_file =
  229. remoteprefix + cmSystemTools::GetFilenameName(file);
  230. *this->LogFile << "\tUpload file: " << local_file << " to "
  231. << remote_file << std::endl;
  232. std::string ofile = cmSystemTools::EncodeURL(remote_file);
  233. std::string upload_as =
  234. cmStrCat(url, ((url.find('?') == std::string::npos) ? '?' : '&'),
  235. "FileName=", ofile);
  236. if (initialize_cdash_buildid) {
  237. // Provide extra arguments to CDash so that it can initialize and
  238. // return a buildid.
  239. cmCTestCurl ctest_curl(this->CTest);
  240. upload_as += "&build=";
  241. upload_as +=
  242. ctest_curl.Escape(this->CTest->GetCTestConfiguration("BuildName"));
  243. upload_as += "&site=";
  244. upload_as +=
  245. ctest_curl.Escape(this->CTest->GetCTestConfiguration("Site"));
  246. upload_as += "&stamp=";
  247. upload_as += ctest_curl.Escape(this->CTest->GetCurrentTag());
  248. upload_as += "-";
  249. upload_as += ctest_curl.Escape(this->CTest->GetTestModelString());
  250. cmCTestScriptHandler* ch = this->CTest->GetScriptHandler();
  251. cmake* cm = ch->GetCMake();
  252. if (cm) {
  253. cmValue subproject = cm->GetState()->GetGlobalProperty("SubProject");
  254. if (subproject) {
  255. upload_as += "&subproject=";
  256. upload_as += ctest_curl.Escape(*subproject);
  257. }
  258. }
  259. }
  260. // Generate Done.xml right before it is submitted.
  261. // The reason for this is two-fold:
  262. // 1) It must be generated after some other part has been submitted
  263. // so we have a buildId to refer to in its contents.
  264. // 2) By generating Done.xml here its timestamp will be as late as
  265. // possible. This gives us a more accurate record of how long the
  266. // entire build took to complete.
  267. if (file == "Done.xml") {
  268. this->CTest->GenerateDoneFile();
  269. }
  270. upload_as += "&MD5=";
  271. if (this->GetOption("InternalTest").IsOn()) {
  272. upload_as += "ffffffffffffffffffffffffffffffff";
  273. } else {
  274. cmCryptoHash hasher(cmCryptoHash::AlgoMD5);
  275. upload_as += hasher.HashFile(local_file);
  276. }
  277. if (!cmSystemTools::FileExists(local_file)) {
  278. cmCTestLog(this->CTest, ERROR_MESSAGE,
  279. " Cannot find file: " << local_file << std::endl);
  280. ::curl_easy_cleanup(curl);
  281. ::curl_slist_free_all(headers);
  282. ::curl_global_cleanup();
  283. return false;
  284. }
  285. unsigned long filelen = cmSystemTools::FileLength(local_file);
  286. ftpfile = cmsys::SystemTools::Fopen(local_file, "rb");
  287. cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
  288. " Upload file: " << local_file << " to "
  289. << upload_as << " Size: "
  290. << filelen << std::endl,
  291. this->Quiet);
  292. // specify target
  293. ::curl_easy_setopt(curl, CURLOPT_URL, upload_as.c_str());
  294. // follow redirects
  295. ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
  296. // CURLAUTH_BASIC is default, and here we allow additional methods,
  297. // including more secure ones
  298. ::curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
  299. // now specify which file to upload
  300. ::curl_easy_setopt(curl, CURLOPT_INFILE, ftpfile);
  301. // and give the size of the upload (optional)
  302. ::curl_easy_setopt(curl, CURLOPT_INFILESIZE, static_cast<long>(filelen));
  303. // and give curl the buffer for errors
  304. ::curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error_buffer);
  305. // specify handler for output
  306. ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
  307. cmCTestSubmitHandlerWriteMemoryCallback);
  308. ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION,
  309. cmCTestSubmitHandlerCurlDebugCallback);
  310. /* we pass our 'chunk' struct to the callback function */
  311. cmCTestSubmitHandlerVectorOfChar chunk;
  312. cmCTestSubmitHandlerVectorOfChar chunkDebug;
  313. ::curl_easy_setopt(curl, CURLOPT_FILE, &chunk);
  314. ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug);
  315. // Now run off and do what you've been told!
  316. ::curl_easy_perform(curl);
  317. if (!chunk.empty()) {
  318. cmCTestOptionalLog(this->CTest, DEBUG,
  319. "CURL output: ["
  320. << cm::string_view(chunk.data(), chunk.size())
  321. << "]" << std::endl,
  322. this->Quiet);
  323. this->ParseResponse(chunk);
  324. }
  325. if (!chunkDebug.empty()) {
  326. cmCTestOptionalLog(
  327. this->CTest, DEBUG,
  328. "CURL debug output: ["
  329. << cm::string_view(chunkDebug.data(), chunkDebug.size()) << "]"
  330. << std::endl,
  331. this->Quiet);
  332. }
  333. // If curl failed for any reason, or checksum fails, wait and retry
  334. //
  335. long response_code;
  336. curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
  337. bool successful_submission = response_code == 200;
  338. if (!successful_submission || this->HasErrors) {
  339. std::string retryDelay = *this->GetOption("RetryDelay");
  340. std::string retryCount = *this->GetOption("RetryCount");
  341. auto delay = cmDuration(
  342. retryDelay.empty()
  343. ? atoi(this->CTest->GetCTestConfiguration("CTestSubmitRetryDelay")
  344. .c_str())
  345. : atoi(retryDelay.c_str()));
  346. int count = retryCount.empty()
  347. ? atoi(this->CTest->GetCTestConfiguration("CTestSubmitRetryCount")
  348. .c_str())
  349. : atoi(retryCount.c_str());
  350. for (int i = 0; i < count; i++) {
  351. cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
  352. " Submit failed, waiting " << delay.count()
  353. << " seconds...\n",
  354. this->Quiet);
  355. auto stop = std::chrono::steady_clock::now() + delay;
  356. while (std::chrono::steady_clock::now() < stop) {
  357. cmSystemTools::Delay(100);
  358. }
  359. cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
  360. " Retry submission: Attempt "
  361. << (i + 1) << " of " << count << std::endl,
  362. this->Quiet);
  363. ::fclose(ftpfile);
  364. ftpfile = cmsys::SystemTools::Fopen(local_file, "rb");
  365. ::curl_easy_setopt(curl, CURLOPT_INFILE, ftpfile);
  366. chunk.clear();
  367. chunkDebug.clear();
  368. this->HasErrors = false;
  369. ::curl_easy_perform(curl);
  370. if (!chunk.empty()) {
  371. cmCTestOptionalLog(this->CTest, DEBUG,
  372. "CURL output: ["
  373. << cm::string_view(chunk.data(), chunk.size())
  374. << "]" << std::endl,
  375. this->Quiet);
  376. this->ParseResponse(chunk);
  377. }
  378. curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
  379. if (response_code == 200 && !this->HasErrors) {
  380. successful_submission = true;
  381. break;
  382. }
  383. }
  384. }
  385. fclose(ftpfile);
  386. if (!successful_submission) {
  387. cmCTestLog(this->CTest, ERROR_MESSAGE,
  388. " Error when uploading file: " << local_file
  389. << std::endl);
  390. cmCTestLog(this->CTest, ERROR_MESSAGE,
  391. " Error message was: " << error_buffer << std::endl);
  392. *this->LogFile << " Error when uploading file: " << local_file
  393. << std::endl
  394. << " Error message was: " << error_buffer
  395. << std::endl;
  396. // avoid deref of begin for zero size array
  397. if (!chunk.empty()) {
  398. *this->LogFile << " Curl output was: "
  399. << cm::string_view(chunk.data(), chunk.size())
  400. << std::endl;
  401. cmCTestLog(this->CTest, ERROR_MESSAGE,
  402. "CURL output: ["
  403. << cm::string_view(chunk.data(), chunk.size()) << "]"
  404. << std::endl);
  405. }
  406. ::curl_easy_cleanup(curl);
  407. ::curl_slist_free_all(headers);
  408. ::curl_global_cleanup();
  409. return false;
  410. }
  411. // always cleanup
  412. ::curl_easy_cleanup(curl);
  413. cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
  414. " Uploaded: " + local_file << std::endl,
  415. this->Quiet);
  416. }
  417. }
  418. ::curl_slist_free_all(headers);
  419. ::curl_global_cleanup();
  420. return true;
  421. }
  422. void cmCTestSubmitHandler::ParseResponse(
  423. cmCTestSubmitHandlerVectorOfChar chunk)
  424. {
  425. std::string output;
  426. output.append(chunk.begin(), chunk.end());
  427. if (output.find("<cdash") != std::string::npos) {
  428. ResponseParser parser;
  429. parser.Parse(output.c_str());
  430. if (parser.Status != ResponseParser::STATUS_OK) {
  431. this->HasErrors = true;
  432. cmCTestLog(this->CTest, HANDLER_OUTPUT,
  433. " Submission failed: " << parser.Message << std::endl);
  434. return;
  435. }
  436. this->CTest->SetBuildID(parser.BuildID);
  437. }
  438. output = cmSystemTools::UpperCase(output);
  439. if (output.find("WARNING") != std::string::npos) {
  440. this->HasWarnings = true;
  441. }
  442. if (output.find("ERROR") != std::string::npos) {
  443. this->HasErrors = true;
  444. }
  445. if (this->HasWarnings || this->HasErrors) {
  446. cmCTestLog(this->CTest, HANDLER_OUTPUT,
  447. " Server Response:\n"
  448. << cm::string_view(chunk.data(), chunk.size()) << "\n");
  449. }
  450. }
  451. int cmCTestSubmitHandler::HandleCDashUploadFile(std::string const& file,
  452. std::string const& typeString)
  453. {
  454. if (file.empty()) {
  455. cmCTestLog(this->CTest, ERROR_MESSAGE, "Upload file not specified\n");
  456. return -1;
  457. }
  458. if (!cmSystemTools::FileExists(file)) {
  459. cmCTestLog(this->CTest, ERROR_MESSAGE,
  460. "Upload file not found: '" << file << "'\n");
  461. return -1;
  462. }
  463. cmCTestCurl curl(this->CTest);
  464. curl.SetQuiet(this->Quiet);
  465. auto submitInactivityTimeout = this->GetSubmitInactivityTimeout();
  466. if (submitInactivityTimeout != 0) {
  467. curl.SetTimeOutSeconds(submitInactivityTimeout);
  468. }
  469. curl.SetHttpHeaders(this->HttpHeaders);
  470. std::string url = this->CTest->GetSubmitURL();
  471. if (!cmHasLiteralPrefix(url, "http://") &&
  472. !cmHasLiteralPrefix(url, "https://")) {
  473. cmCTestLog(this->CTest, ERROR_MESSAGE,
  474. "Only http and https are supported for CDASH_UPLOAD\n");
  475. return -1;
  476. }
  477. std::string fields;
  478. std::string::size_type pos = url.find('?');
  479. if (pos != std::string::npos) {
  480. fields = url.substr(pos + 1);
  481. url.erase(pos);
  482. }
  483. bool internalTest = this->GetOption("InternalTest").IsOn();
  484. // Get RETRY_COUNT and RETRY_DELAY values if they were set.
  485. std::string retryDelayString = *this->GetOption("RetryDelay");
  486. std::string retryCountString = *this->GetOption("RetryCount");
  487. auto retryDelay = std::chrono::seconds(0);
  488. if (!retryDelayString.empty()) {
  489. unsigned long retryDelayValue = 0;
  490. if (!cmStrToULong(retryDelayString, &retryDelayValue)) {
  491. cmCTestLog(this->CTest, WARNING,
  492. "Invalid value for 'RETRY_DELAY' : " << retryDelayString
  493. << std::endl);
  494. } else {
  495. retryDelay = std::chrono::seconds(retryDelayValue);
  496. }
  497. }
  498. unsigned long retryCount = 0;
  499. if (!retryCountString.empty()) {
  500. if (!cmStrToULong(retryCountString, &retryCount)) {
  501. cmCTestLog(this->CTest, WARNING,
  502. "Invalid value for 'RETRY_DELAY' : " << retryCountString
  503. << std::endl);
  504. }
  505. }
  506. cmCryptoHash hasher(cmCryptoHash::AlgoMD5);
  507. std::string md5sum = hasher.HashFile(file);
  508. // 1. request the buildid and check to see if the file
  509. // has already been uploaded
  510. // TODO I added support for subproject. You would need to add
  511. // a "&subproject=subprojectname" to the first POST.
  512. cmCTestScriptHandler* ch = this->CTest->GetScriptHandler();
  513. cmake* cm = ch->GetCMake();
  514. cmValue subproject = cm->GetState()->GetGlobalProperty("SubProject");
  515. // TODO: Encode values for a URL instead of trusting caller.
  516. std::ostringstream str;
  517. if (subproject) {
  518. str << "subproject=" << curl.Escape(*subproject) << "&";
  519. }
  520. auto timeNow =
  521. std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
  522. str << "stamp=" << curl.Escape(this->CTest->GetCurrentTag()) << "-"
  523. << curl.Escape(this->CTest->GetTestModelString()) << "&"
  524. << "model=" << curl.Escape(this->CTest->GetTestModelString()) << "&"
  525. << "build="
  526. << curl.Escape(this->CTest->GetCTestConfiguration("BuildName")) << "&"
  527. << "site=" << curl.Escape(this->CTest->GetCTestConfiguration("Site"))
  528. << "&"
  529. << "group=" << curl.Escape(this->CTest->GetTestModelString())
  530. << "&"
  531. // For now, we send both "track" and "group" to CDash in case we're
  532. // submitting to an older instance that still expects the prior
  533. // terminology.
  534. << "track=" << curl.Escape(this->CTest->GetTestModelString()) << "&"
  535. << "starttime=" << timeNow << "&"
  536. << "endtime=" << timeNow << "&"
  537. << "datafilesmd5[0]=" << md5sum << "&"
  538. << "type=" << curl.Escape(typeString);
  539. if (!fields.empty()) {
  540. fields += '&';
  541. }
  542. fields += str.str();
  543. cmCTestOptionalLog(this->CTest, DEBUG,
  544. "fields: " << fields << "\nurl:" << url
  545. << "\nfile: " << file << "\n",
  546. this->Quiet);
  547. std::string response;
  548. bool requestSucceeded = curl.HttpRequest(url, fields, response);
  549. if (!internalTest && !requestSucceeded) {
  550. // If request failed, wait and retry.
  551. for (unsigned long i = 0; i < retryCount; i++) {
  552. cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
  553. " Request failed, waiting " << retryDelay.count()
  554. << " seconds...\n",
  555. this->Quiet);
  556. auto stop = std::chrono::steady_clock::now() + retryDelay;
  557. while (std::chrono::steady_clock::now() < stop) {
  558. cmSystemTools::Delay(100);
  559. }
  560. cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
  561. " Retry request: Attempt "
  562. << (i + 1) << " of " << retryCount << std::endl,
  563. this->Quiet);
  564. requestSucceeded = curl.HttpRequest(url, fields, response);
  565. if (requestSucceeded) {
  566. break;
  567. }
  568. }
  569. }
  570. if (!internalTest && !requestSucceeded) {
  571. cmCTestLog(this->CTest, ERROR_MESSAGE,
  572. "Error in HttpRequest\n"
  573. << response);
  574. return -1;
  575. }
  576. cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
  577. "Request upload response: [" << response << "]\n",
  578. this->Quiet);
  579. Json::Value json;
  580. Json::Reader reader;
  581. if (!internalTest && !reader.parse(response, json)) {
  582. cmCTestLog(this->CTest, ERROR_MESSAGE,
  583. "error parsing json string ["
  584. << response << "]\n"
  585. << reader.getFormattedErrorMessages() << "\n");
  586. return -1;
  587. }
  588. if (!internalTest && json["status"].asInt() != 0) {
  589. cmCTestLog(this->CTest, ERROR_MESSAGE,
  590. "Bad status returned from CDash: " << json["status"].asInt());
  591. return -1;
  592. }
  593. if (!internalTest) {
  594. if (json["datafilesmd5"].isArray()) {
  595. int datares = json["datafilesmd5"][0].asInt();
  596. if (datares == 1) {
  597. cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
  598. "File already exists on CDash, skip upload "
  599. << file << "\n",
  600. this->Quiet);
  601. return 0;
  602. }
  603. } else {
  604. cmCTestLog(this->CTest, ERROR_MESSAGE,
  605. "bad datafilesmd5 value in response " << response << "\n");
  606. return -1;
  607. }
  608. }
  609. std::string upload_as = cmSystemTools::GetFilenameName(file);
  610. std::ostringstream fstr;
  611. fstr << "type=" << curl.Escape(typeString) << "&"
  612. << "md5=" << md5sum << "&"
  613. << "filename=" << curl.Escape(upload_as) << "&"
  614. << "buildid=" << json["buildid"].asString();
  615. bool uploadSucceeded = false;
  616. if (!internalTest) {
  617. uploadSucceeded = curl.UploadFile(file, url, fstr.str(), response);
  618. }
  619. if (!uploadSucceeded) {
  620. // If upload failed, wait and retry.
  621. for (unsigned long i = 0; i < retryCount; i++) {
  622. cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
  623. " Upload failed, waiting " << retryDelay.count()
  624. << " seconds...\n",
  625. this->Quiet);
  626. auto stop = std::chrono::steady_clock::now() + retryDelay;
  627. while (std::chrono::steady_clock::now() < stop) {
  628. cmSystemTools::Delay(100);
  629. }
  630. cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
  631. " Retry upload: Attempt "
  632. << (i + 1) << " of " << retryCount << std::endl,
  633. this->Quiet);
  634. if (!internalTest) {
  635. uploadSucceeded = curl.UploadFile(file, url, fstr.str(), response);
  636. }
  637. if (uploadSucceeded) {
  638. break;
  639. }
  640. }
  641. }
  642. if (!uploadSucceeded) {
  643. cmCTestLog(this->CTest, ERROR_MESSAGE,
  644. "error uploading to CDash. " << file << " " << url << " "
  645. << fstr.str());
  646. return -1;
  647. }
  648. if (!reader.parse(response, json)) {
  649. cmCTestLog(this->CTest, ERROR_MESSAGE,
  650. "error parsing json string ["
  651. << response << "]\n"
  652. << reader.getFormattedErrorMessages() << "\n");
  653. return -1;
  654. }
  655. cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
  656. "Upload file response: [" << response << "]\n",
  657. this->Quiet);
  658. return 0;
  659. }
  660. int cmCTestSubmitHandler::ProcessHandler()
  661. {
  662. cmValue cdashUploadFile = this->GetOption("CDashUploadFile");
  663. cmValue cdashUploadType = this->GetOption("CDashUploadType");
  664. if (cdashUploadFile && cdashUploadType) {
  665. return this->HandleCDashUploadFile(*cdashUploadFile, *cdashUploadType);
  666. }
  667. const std::string& buildDirectory =
  668. this->CTest->GetCTestConfiguration("BuildDirectory");
  669. if (buildDirectory.empty()) {
  670. cmCTestLog(this->CTest, ERROR_MESSAGE,
  671. "Cannot find BuildDirectory key in the DartConfiguration.tcl"
  672. << std::endl);
  673. return -1;
  674. }
  675. if (char const* proxy = getenv("HTTP_PROXY")) {
  676. this->HTTPProxyType = 1;
  677. this->HTTPProxy = proxy;
  678. if (getenv("HTTP_PROXY_PORT")) {
  679. this->HTTPProxy += ":";
  680. this->HTTPProxy += getenv("HTTP_PROXY_PORT");
  681. }
  682. if (char const* proxy_type = getenv("HTTP_PROXY_TYPE")) {
  683. std::string type = proxy_type;
  684. // HTTP/SOCKS4/SOCKS5
  685. if (type == "HTTP") {
  686. this->HTTPProxyType = 1;
  687. } else if (type == "SOCKS4") {
  688. this->HTTPProxyType = 2;
  689. } else if (type == "SOCKS5") {
  690. this->HTTPProxyType = 3;
  691. }
  692. }
  693. if (getenv("HTTP_PROXY_USER")) {
  694. this->HTTPProxyAuth = getenv("HTTP_PROXY_USER");
  695. }
  696. if (getenv("HTTP_PROXY_PASSWD")) {
  697. this->HTTPProxyAuth += ":";
  698. this->HTTPProxyAuth += getenv("HTTP_PROXY_PASSWD");
  699. }
  700. }
  701. if (!this->HTTPProxy.empty()) {
  702. cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
  703. " Use HTTP Proxy: " << this->HTTPProxy << std::endl,
  704. this->Quiet);
  705. }
  706. cmGeneratedFileStream ofs;
  707. this->StartLogFile("Submit", ofs);
  708. std::vector<std::string> files;
  709. std::string prefix = this->GetSubmitResultsPrefix();
  710. if (!this->Files.empty()) {
  711. // Submit the explicitly selected files:
  712. cm::append(files, this->Files);
  713. }
  714. // Add to the list of files to submit from any selected, existing parts:
  715. //
  716. // TODO:
  717. // Check if test is enabled
  718. this->CTest->AddIfExists(cmCTest::PartUpdate, "Update.xml");
  719. this->CTest->AddIfExists(cmCTest::PartConfigure, "Configure.xml");
  720. this->CTest->AddIfExists(cmCTest::PartBuild, "Build.xml");
  721. this->CTest->AddIfExists(cmCTest::PartTest, "Test.xml");
  722. if (this->CTest->AddIfExists(cmCTest::PartCoverage, "Coverage.xml")) {
  723. std::vector<std::string> gfiles;
  724. std::string gpath =
  725. buildDirectory + "/Testing/" + this->CTest->GetCurrentTag();
  726. std::string::size_type glen = gpath.size() + 1;
  727. gpath = gpath + "/CoverageLog*";
  728. cmCTestOptionalLog(this->CTest, DEBUG,
  729. "Globbing for: " << gpath << std::endl, this->Quiet);
  730. if (cmSystemTools::SimpleGlob(gpath, gfiles, 1)) {
  731. for (std::string& gfile : gfiles) {
  732. gfile = gfile.substr(glen);
  733. cmCTestOptionalLog(this->CTest, DEBUG,
  734. "Glob file: " << gfile << std::endl, this->Quiet);
  735. this->CTest->AddSubmitFile(cmCTest::PartCoverage, gfile);
  736. }
  737. } else {
  738. cmCTestLog(this->CTest, ERROR_MESSAGE, "Problem globbing" << std::endl);
  739. }
  740. }
  741. this->CTest->AddIfExists(cmCTest::PartMemCheck, "DynamicAnalysis.xml");
  742. this->CTest->AddIfExists(cmCTest::PartMemCheck, "DynamicAnalysis-Test.xml");
  743. this->CTest->AddIfExists(cmCTest::PartMemCheck, "Purify.xml");
  744. this->CTest->AddIfExists(cmCTest::PartNotes, "Notes.xml");
  745. this->CTest->AddIfExists(cmCTest::PartUpload, "Upload.xml");
  746. // Query parts for files to submit.
  747. for (cmCTest::Part p = cmCTest::PartStart; p != cmCTest::PartCount;
  748. p = static_cast<cmCTest::Part>(p + 1)) {
  749. // Skip parts we are not submitting.
  750. if (!this->SubmitPart[p]) {
  751. continue;
  752. }
  753. // Submit files from this part.
  754. cm::append(files, this->CTest->GetSubmitFiles(p));
  755. }
  756. // Make sure files are unique, but preserve order.
  757. {
  758. // This endPos intermediate is needed to work around non-conformant C++11
  759. // standard libraries that have erase(iterator,iterator) instead of
  760. // erase(const_iterator,const_iterator).
  761. size_t endPos = cmRemoveDuplicates(files) - files.cbegin();
  762. files.erase(files.begin() + endPos, files.end());
  763. }
  764. // Submit Done.xml last
  765. if (this->SubmitPart[cmCTest::PartDone]) {
  766. files.emplace_back("Done.xml");
  767. }
  768. if (ofs) {
  769. ofs << "Upload files:" << std::endl;
  770. int cnt = 0;
  771. for (std::string const& file : files) {
  772. ofs << cnt << "\t" << file << std::endl;
  773. cnt++;
  774. }
  775. }
  776. cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "Submit files\n",
  777. this->Quiet);
  778. const char* specificGroup = this->CTest->GetSpecificGroup();
  779. if (specificGroup) {
  780. cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
  781. " Send to group: " << specificGroup << std::endl,
  782. this->Quiet);
  783. }
  784. this->SetLogFile(&ofs);
  785. std::string url = this->CTest->GetSubmitURL();
  786. cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
  787. " SubmitURL: " << url << '\n', this->Quiet);
  788. if (!this->SubmitUsingHTTP(buildDirectory + "/Testing/" +
  789. this->CTest->GetCurrentTag(),
  790. files, prefix, url)) {
  791. cmCTestLog(this->CTest, ERROR_MESSAGE,
  792. " Problems when submitting via HTTP\n");
  793. ofs << " Problems when submitting via HTTP\n";
  794. return -1;
  795. }
  796. if (this->HasErrors) {
  797. cmCTestLog(this->CTest, HANDLER_OUTPUT,
  798. " Errors occurred during submission.\n");
  799. ofs << " Errors occurred during submission.\n";
  800. } else {
  801. cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
  802. " Submission successful"
  803. << (this->HasWarnings ? ", with warnings." : "")
  804. << std::endl,
  805. this->Quiet);
  806. ofs << " Submission successful"
  807. << (this->HasWarnings ? ", with warnings." : "") << std::endl;
  808. }
  809. return 0;
  810. }
  811. std::string cmCTestSubmitHandler::GetSubmitResultsPrefix()
  812. {
  813. std::string buildname =
  814. cmCTest::SafeBuildIdField(this->CTest->GetCTestConfiguration("BuildName"));
  815. std::string name = this->CTest->GetCTestConfiguration("Site") + "___" +
  816. buildname + "___" + this->CTest->GetCurrentTag() + "-" +
  817. this->CTest->GetTestModelString() + "___XML___";
  818. return name;
  819. }
  820. void cmCTestSubmitHandler::SelectParts(std::set<cmCTest::Part> const& parts)
  821. {
  822. // Check whether each part is selected.
  823. for (cmCTest::Part p = cmCTest::PartStart; p != cmCTest::PartCount;
  824. p = static_cast<cmCTest::Part>(p + 1)) {
  825. this->SubmitPart[p] = parts.find(p) != parts.end();
  826. }
  827. }
  828. int cmCTestSubmitHandler::GetSubmitInactivityTimeout()
  829. {
  830. int submitInactivityTimeout = SUBMIT_TIMEOUT_IN_SECONDS_DEFAULT;
  831. std::string const& timeoutStr =
  832. this->CTest->GetCTestConfiguration("SubmitInactivityTimeout");
  833. if (!timeoutStr.empty()) {
  834. unsigned long timeout;
  835. if (cmStrToULong(timeoutStr, &timeout)) {
  836. submitInactivityTimeout = static_cast<int>(timeout);
  837. } else {
  838. cmCTestLog(this->CTest, ERROR_MESSAGE,
  839. "SubmitInactivityTimeout is invalid: "
  840. << cm::quoted(timeoutStr) << "."
  841. << " Using a default value of "
  842. << SUBMIT_TIMEOUT_IN_SECONDS_DEFAULT << "." << std::endl);
  843. }
  844. }
  845. return submitInactivityTimeout;
  846. }
  847. void cmCTestSubmitHandler::SelectFiles(std::set<std::string> const& files)
  848. {
  849. this->Files.insert(files.begin(), files.end());
  850. }