cmCTestSubmitHandler.cxx 33 KB

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