cmCTestSubmitHandler.cxx 33 KB

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