/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestSubmitHandler.h" #include "cm_curl.h" #include "cm_jsoncpp_reader.h" #include "cm_jsoncpp_value.h" #include #include #include #include #include "cmAlgorithms.h" #include "cmCTest.h" #include "cmCTestCurl.h" #include "cmCTestScriptHandler.h" #include "cmCryptoHash.h" #include "cmCurl.h" #include "cmDuration.h" #include "cmGeneratedFileStream.h" #include "cmState.h" #include "cmSystemTools.h" #include "cmXMLParser.h" #include "cmake.h" #define SUBMIT_TIMEOUT_IN_SECONDS_DEFAULT 120 typedef std::vector cmCTestSubmitHandlerVectorOfChar; class cmCTestSubmitHandler::ResponseParser : public cmXMLParser { public: ResponseParser() { this->Status = STATUS_OK; } ~ResponseParser() override {} public: enum StatusType { STATUS_OK, STATUS_WARNING, STATUS_ERROR }; StatusType Status; std::string Filename; std::string MD5; std::string Message; std::string BuildID; private: std::vector CurrentValue; std::string GetCurrentValue() { std::string val; if (!this->CurrentValue.empty()) { val.assign(&this->CurrentValue[0], this->CurrentValue.size()); } return val; } void StartElement(const std::string& /*name*/, const char** /*atts*/) override { this->CurrentValue.clear(); } void CharacterDataHandler(const char* data, int length) override { this->CurrentValue.insert(this->CurrentValue.end(), data, data + length); } void EndElement(const std::string& name) override { if (name == "status") { std::string status = cmSystemTools::UpperCase(this->GetCurrentValue()); if (status == "OK" || status == "SUCCESS") { this->Status = STATUS_OK; } else if (status == "WARNING") { this->Status = STATUS_WARNING; } else { this->Status = STATUS_ERROR; } } else if (name == "filename") { this->Filename = this->GetCurrentValue(); } else if (name == "md5") { this->MD5 = this->GetCurrentValue(); } else if (name == "message") { this->Message = this->GetCurrentValue(); } else if (name == "buildId") { this->BuildID = this->GetCurrentValue(); } } }; static size_t cmCTestSubmitHandlerWriteMemoryCallback(void* ptr, size_t size, size_t nmemb, void* data) { int realsize = static_cast(size * nmemb); cmCTestSubmitHandlerVectorOfChar* vec = static_cast(data); const char* chPtr = static_cast(ptr); vec->insert(vec->end(), chPtr, chPtr + realsize); return realsize; } static size_t cmCTestSubmitHandlerCurlDebugCallback(CURL* /*unused*/, curl_infotype /*unused*/, char* chPtr, size_t size, void* data) { cmCTestSubmitHandlerVectorOfChar* vec = static_cast(data); vec->insert(vec->end(), chPtr, chPtr + size); return size; } cmCTestSubmitHandler::cmCTestSubmitHandler() { this->Initialize(); } void cmCTestSubmitHandler::Initialize() { // We submit all available parts by default. for (cmCTest::Part p = cmCTest::PartStart; p != cmCTest::PartCount; p = cmCTest::Part(p + 1)) { this->SubmitPart[p] = true; } this->HasWarnings = false; this->HasErrors = false; this->Superclass::Initialize(); this->HTTPProxy.clear(); this->HTTPProxyType = 0; this->HTTPProxyAuth.clear(); this->LogFile = nullptr; this->Files.clear(); } bool cmCTestSubmitHandler::SubmitUsingHTTP( const std::string& localprefix, const std::vector& files, const std::string& remoteprefix, const std::string& url) { CURL* curl; CURLcode res; FILE* ftpfile; char error_buffer[1024]; // Set Content-Type to satisfy fussy modsecurity rules. struct curl_slist* headers = ::curl_slist_append(nullptr, "Content-Type: text/xml"); // Add any additional headers that the user specified. for (std::string const& h : this->HttpHeaders) { cmCTestOptionalLog(this->CTest, DEBUG, " Add HTTP Header: \"" << h << "\"" << std::endl, this->Quiet); headers = ::curl_slist_append(headers, h.c_str()); } /* In windows, this will init the winsock stuff */ ::curl_global_init(CURL_GLOBAL_ALL); std::string curlopt(this->CTest->GetCTestConfiguration("CurlOptions")); std::vector args; cmSystemTools::ExpandListArgument(curlopt, args); bool verifyPeerOff = false; bool verifyHostOff = false; for (std::string const& arg : args) { if (arg == "CURLOPT_SSL_VERIFYPEER_OFF") { verifyPeerOff = true; } if (arg == "CURLOPT_SSL_VERIFYHOST_OFF") { verifyHostOff = true; } } for (std::string const& file : files) { /* get a curl handle */ curl = curl_easy_init(); if (curl) { cmCurlSetCAInfo(curl); if (verifyPeerOff) { cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " Set CURLOPT_SSL_VERIFYPEER to off\n", this->Quiet); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); } if (verifyHostOff) { cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " Set CURLOPT_SSL_VERIFYHOST to off\n", this->Quiet); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); } // Using proxy if (this->HTTPProxyType > 0) { curl_easy_setopt(curl, CURLOPT_PROXY, this->HTTPProxy.c_str()); switch (this->HTTPProxyType) { case 2: curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4); break; case 3: curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); break; default: curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); if (!this->HTTPProxyAuth.empty()) { curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, this->HTTPProxyAuth.c_str()); } } } if (this->CTest->ShouldUseHTTP10()) { curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); } // enable HTTP ERROR parsing curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); /* enable uploading */ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); // if there is little to no activity for too long stop submitting ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1); ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, SUBMIT_TIMEOUT_IN_SECONDS_DEFAULT); /* HTTP PUT please */ ::curl_easy_setopt(curl, CURLOPT_PUT, 1); ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); std::string local_file = file; bool initialize_cdash_buildid = false; if (!cmSystemTools::FileExists(local_file)) { local_file = localprefix + "/" + file; // If this file exists within the local Testing directory we assume // that it will be associated with the current build in CDash. initialize_cdash_buildid = true; } std::string remote_file = remoteprefix + cmSystemTools::GetFilenameName(file); *this->LogFile << "\tUpload file: " << local_file << " to " << remote_file << std::endl; std::string ofile = cmSystemTools::EncodeURL(remote_file); std::string upload_as = url + ((url.find('?') == std::string::npos) ? '?' : '&') + "FileName=" + ofile; if (initialize_cdash_buildid) { // Provide extra arguments to CDash so that it can initialize and // return a buildid. cmCTestCurl ctest_curl(this->CTest); upload_as += "&build="; upload_as += ctest_curl.Escape(this->CTest->GetCTestConfiguration("BuildName")); upload_as += "&site="; upload_as += ctest_curl.Escape(this->CTest->GetCTestConfiguration("Site")); upload_as += "&stamp="; upload_as += ctest_curl.Escape(this->CTest->GetCurrentTag()); upload_as += "-"; upload_as += ctest_curl.Escape(this->CTest->GetTestModelString()); cmCTestScriptHandler* ch = static_cast( this->CTest->GetHandler("script")); cmake* cm = ch->GetCMake(); if (cm) { const char* subproject = cm->GetState()->GetGlobalProperty("SubProject"); if (subproject) { upload_as += "&subproject="; upload_as += ctest_curl.Escape(subproject); } } } upload_as += "&MD5="; if (cmSystemTools::IsOn(this->GetOption("InternalTest"))) { upload_as += "bad_md5sum"; } else { upload_as += cmSystemTools::ComputeFileHash(local_file, cmCryptoHash::AlgoMD5); } // Generate Done.xml right before it is submitted. // The reason for this is two-fold: // 1) It must be generated after some other part has been submitted // so we have a buildId to refer to in its contents. // 2) By generating Done.xml here its timestamp will be as late as // possible. This gives us a more accurate record of how long the // entire build took to complete. if (file == "Done.xml") { this->CTest->GenerateDoneFile(); } if (!cmSystemTools::FileExists(local_file)) { cmCTestLog(this->CTest, ERROR_MESSAGE, " Cannot find file: " << local_file << std::endl); ::curl_easy_cleanup(curl); ::curl_slist_free_all(headers); ::curl_global_cleanup(); return false; } unsigned long filelen = cmSystemTools::FileLength(local_file); ftpfile = cmsys::SystemTools::Fopen(local_file, "rb"); cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " Upload file: " << local_file << " to " << upload_as << " Size: " << filelen << std::endl, this->Quiet); // specify target ::curl_easy_setopt(curl, CURLOPT_URL, upload_as.c_str()); // CURLAUTH_BASIC is default, and here we allow additional methods, // including more secure ones ::curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); // now specify which file to upload ::curl_easy_setopt(curl, CURLOPT_INFILE, ftpfile); // and give the size of the upload (optional) ::curl_easy_setopt(curl, CURLOPT_INFILESIZE, static_cast(filelen)); // and give curl the buffer for errors ::curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error_buffer); // specify handler for output ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmCTestSubmitHandlerWriteMemoryCallback); ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, cmCTestSubmitHandlerCurlDebugCallback); /* we pass our 'chunk' struct to the callback function */ cmCTestSubmitHandlerVectorOfChar chunk; cmCTestSubmitHandlerVectorOfChar chunkDebug; ::curl_easy_setopt(curl, CURLOPT_FILE, &chunk); ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug); // Now run off and do what you've been told! res = ::curl_easy_perform(curl); if (!chunk.empty()) { cmCTestOptionalLog(this->CTest, DEBUG, "CURL output: [" << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "]" << std::endl, this->Quiet); this->ParseResponse(chunk); } if (!chunkDebug.empty()) { cmCTestOptionalLog( this->CTest, DEBUG, "CURL debug output: [" << cmCTestLogWrite(&*chunkDebug.begin(), chunkDebug.size()) << "]" << std::endl, this->Quiet); } // If curl failed for any reason, or checksum fails, wait and retry // if (res != CURLE_OK || this->HasErrors) { std::string retryDelay = this->GetOption("RetryDelay") == nullptr ? "" : this->GetOption("RetryDelay"); std::string retryCount = this->GetOption("RetryCount") == nullptr ? "" : this->GetOption("RetryCount"); auto delay = cmDuration( retryDelay.empty() ? atoi(this->CTest->GetCTestConfiguration("CTestSubmitRetryDelay") .c_str()) : atoi(retryDelay.c_str())); int count = retryCount.empty() ? atoi(this->CTest->GetCTestConfiguration("CTestSubmitRetryCount") .c_str()) : atoi(retryCount.c_str()); for (int i = 0; i < count; i++) { cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " Submit failed, waiting " << delay.count() << " seconds...\n", this->Quiet); auto stop = std::chrono::steady_clock::now() + delay; while (std::chrono::steady_clock::now() < stop) { cmSystemTools::Delay(100); } cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " Retry submission: Attempt " << (i + 1) << " of " << count << std::endl, this->Quiet); ::fclose(ftpfile); ftpfile = cmsys::SystemTools::Fopen(local_file, "rb"); ::curl_easy_setopt(curl, CURLOPT_INFILE, ftpfile); chunk.clear(); chunkDebug.clear(); this->HasErrors = false; res = ::curl_easy_perform(curl); if (!chunk.empty()) { cmCTestOptionalLog( this->CTest, DEBUG, "CURL output: [" << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "]" << std::endl, this->Quiet); this->ParseResponse(chunk); } if (res == CURLE_OK && !this->HasErrors) { break; } } } fclose(ftpfile); if (res) { cmCTestLog(this->CTest, ERROR_MESSAGE, " Error when uploading file: " << local_file << std::endl); cmCTestLog(this->CTest, ERROR_MESSAGE, " Error message was: " << error_buffer << std::endl); *this->LogFile << " Error when uploading file: " << local_file << std::endl << " Error message was: " << error_buffer << std::endl; // avoid deref of begin for zero size array if (!chunk.empty()) { *this->LogFile << " Curl output was: " << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << std::endl; cmCTestLog(this->CTest, ERROR_MESSAGE, "CURL output: [" << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "]" << std::endl); } ::curl_easy_cleanup(curl); ::curl_slist_free_all(headers); ::curl_global_cleanup(); return false; } // always cleanup ::curl_easy_cleanup(curl); cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " Uploaded: " + local_file << std::endl, this->Quiet); } } ::curl_slist_free_all(headers); ::curl_global_cleanup(); return true; } void cmCTestSubmitHandler::ParseResponse( cmCTestSubmitHandlerVectorOfChar chunk) { std::string output; output.append(chunk.begin(), chunk.end()); if (output.find("HasErrors = true; cmCTestLog(this->CTest, HANDLER_OUTPUT, " Submission failed: " << parser.Message << std::endl); return; } this->CTest->SetBuildID(parser.BuildID); } output = cmSystemTools::UpperCase(output); if (output.find("WARNING") != std::string::npos) { this->HasWarnings = true; } if (output.find("ERROR") != std::string::npos) { this->HasErrors = true; } if (this->HasWarnings || this->HasErrors) { cmCTestLog(this->CTest, HANDLER_OUTPUT, " Server Response:\n" << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "\n"); } } int cmCTestSubmitHandler::HandleCDashUploadFile(std::string const& file, std::string const& typeString) { if (file.empty()) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Upload file not specified\n"); return -1; } if (!cmSystemTools::FileExists(file)) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Upload file not found: '" << file << "'\n"); return -1; } cmCTestCurl curl(this->CTest); curl.SetQuiet(this->Quiet); std::string curlopt(this->CTest->GetCTestConfiguration("CurlOptions")); std::vector args; cmSystemTools::ExpandListArgument(curlopt, args); curl.SetCurlOptions(args); curl.SetTimeOutSeconds(SUBMIT_TIMEOUT_IN_SECONDS_DEFAULT); curl.SetHttpHeaders(this->HttpHeaders); std::string url = this->CTest->GetSubmitURL(); std::string fields; std::string::size_type pos = url.find('?'); if (pos != std::string::npos) { fields = url.substr(pos + 1); url = url.substr(0, pos); } if (!cmHasLiteralPrefix(url, "http://") && !cmHasLiteralPrefix(url, "https://")) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Only http and https are supported for CDASH_UPLOAD\n"); return -1; } bool internalTest = cmSystemTools::IsOn(this->GetOption("InternalTest")); // Get RETRY_COUNT and RETRY_DELAY values if they were set. std::string retryDelayString = this->GetOption("RetryDelay") == nullptr ? "" : this->GetOption("RetryDelay"); std::string retryCountString = this->GetOption("RetryCount") == nullptr ? "" : this->GetOption("RetryCount"); auto retryDelay = std::chrono::seconds(0); if (!retryDelayString.empty()) { unsigned long retryDelayValue = 0; if (!cmSystemTools::StringToULong(retryDelayString.c_str(), &retryDelayValue)) { cmCTestLog(this->CTest, WARNING, "Invalid value for 'RETRY_DELAY' : " << retryDelayString << std::endl); } else { retryDelay = std::chrono::seconds(retryDelayValue); } } unsigned long retryCount = 0; if (!retryCountString.empty()) { if (!cmSystemTools::StringToULong(retryCountString.c_str(), &retryCount)) { cmCTestLog(this->CTest, WARNING, "Invalid value for 'RETRY_DELAY' : " << retryCountString << std::endl); } } std::string md5sum = cmSystemTools::ComputeFileHash(file, cmCryptoHash::AlgoMD5); // 1. request the buildid and check to see if the file // has already been uploaded // TODO I added support for subproject. You would need to add // a "&subproject=subprojectname" to the first POST. cmCTestScriptHandler* ch = static_cast(this->CTest->GetHandler("script")); cmake* cm = ch->GetCMake(); const char* subproject = cm->GetState()->GetGlobalProperty("SubProject"); // TODO: Encode values for a URL instead of trusting caller. std::ostringstream str; if (subproject) { str << "subproject=" << curl.Escape(subproject) << "&"; } auto timeNow = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); str << "stamp=" << curl.Escape(this->CTest->GetCurrentTag()) << "-" << curl.Escape(this->CTest->GetTestModelString()) << "&" << "model=" << curl.Escape(this->CTest->GetTestModelString()) << "&" << "build=" << curl.Escape(this->CTest->GetCTestConfiguration("BuildName")) << "&" << "site=" << curl.Escape(this->CTest->GetCTestConfiguration("Site")) << "&" << "track=" << curl.Escape(this->CTest->GetTestModelString()) << "&" << "starttime=" << timeNow << "&" << "endtime=" << timeNow << "&" << "datafilesmd5[0]=" << md5sum << "&" << "type=" << curl.Escape(typeString); if (!fields.empty()) { fields += '&'; } fields += str.str(); cmCTestOptionalLog(this->CTest, DEBUG, "fields: " << fields << "\nurl:" << url << "\nfile: " << file << "\n", this->Quiet); std::string response; bool requestSucceeded = curl.HttpRequest(url, fields, response); if (!internalTest && !requestSucceeded) { // If request failed, wait and retry. for (unsigned long i = 0; i < retryCount; i++) { cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " Request failed, waiting " << retryDelay.count() << " seconds...\n", this->Quiet); auto stop = std::chrono::steady_clock::now() + retryDelay; while (std::chrono::steady_clock::now() < stop) { cmSystemTools::Delay(100); } cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " Retry request: Attempt " << (i + 1) << " of " << retryCount << std::endl, this->Quiet); requestSucceeded = curl.HttpRequest(url, fields, response); if (requestSucceeded) { break; } } } if (!internalTest && !requestSucceeded) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Error in HttpRequest\n" << response); return -1; } cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Request upload response: [" << response << "]\n", this->Quiet); Json::Value json; Json::Reader reader; if (!internalTest && !reader.parse(response, json)) { cmCTestLog(this->CTest, ERROR_MESSAGE, "error parsing json string [" << response << "]\n" << reader.getFormattedErrorMessages() << "\n"); return -1; } if (!internalTest && json["status"].asInt() != 0) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Bad status returned from CDash: " << json["status"].asInt()); return -1; } if (!internalTest) { if (json["datafilesmd5"].isArray()) { int datares = json["datafilesmd5"][0].asInt(); if (datares == 1) { cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "File already exists on CDash, skip upload " << file << "\n", this->Quiet); return 0; } } else { cmCTestLog(this->CTest, ERROR_MESSAGE, "bad datafilesmd5 value in response " << response << "\n"); return -1; } } std::string upload_as = cmSystemTools::GetFilenameName(file); std::ostringstream fstr; fstr << "type=" << curl.Escape(typeString) << "&" << "md5=" << md5sum << "&" << "filename=" << curl.Escape(upload_as) << "&" << "buildid=" << json["buildid"].asString(); bool uploadSucceeded = false; if (!internalTest) { uploadSucceeded = curl.UploadFile(file, url, fstr.str(), response); } if (!uploadSucceeded) { // If upload failed, wait and retry. for (unsigned long i = 0; i < retryCount; i++) { cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " Upload failed, waiting " << retryDelay.count() << " seconds...\n", this->Quiet); auto stop = std::chrono::steady_clock::now() + retryDelay; while (std::chrono::steady_clock::now() < stop) { cmSystemTools::Delay(100); } cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " Retry upload: Attempt " << (i + 1) << " of " << retryCount << std::endl, this->Quiet); if (!internalTest) { uploadSucceeded = curl.UploadFile(file, url, fstr.str(), response); } if (uploadSucceeded) { break; } } } if (!uploadSucceeded) { cmCTestLog(this->CTest, ERROR_MESSAGE, "error uploading to CDash. " << file << " " << url << " " << fstr.str()); return -1; } if (!reader.parse(response, json)) { cmCTestLog(this->CTest, ERROR_MESSAGE, "error parsing json string [" << response << "]\n" << reader.getFormattedErrorMessages() << "\n"); return -1; } cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Upload file response: [" << response << "]\n", this->Quiet); return 0; } int cmCTestSubmitHandler::ProcessHandler() { const char* cdashUploadFile = this->GetOption("CDashUploadFile"); const char* cdashUploadType = this->GetOption("CDashUploadType"); if (cdashUploadFile && cdashUploadType) { return this->HandleCDashUploadFile(cdashUploadFile, cdashUploadType); } const std::string& buildDirectory = this->CTest->GetCTestConfiguration("BuildDirectory"); if (buildDirectory.empty()) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot find BuildDirectory key in the DartConfiguration.tcl" << std::endl); return -1; } if (getenv("HTTP_PROXY")) { this->HTTPProxyType = 1; this->HTTPProxy = getenv("HTTP_PROXY"); if (getenv("HTTP_PROXY_PORT")) { this->HTTPProxy += ":"; this->HTTPProxy += getenv("HTTP_PROXY_PORT"); } if (getenv("HTTP_PROXY_TYPE")) { std::string type = getenv("HTTP_PROXY_TYPE"); // HTTP/SOCKS4/SOCKS5 if (type == "HTTP") { this->HTTPProxyType = 1; } else if (type == "SOCKS4") { this->HTTPProxyType = 2; } else if (type == "SOCKS5") { this->HTTPProxyType = 3; } } if (getenv("HTTP_PROXY_USER")) { this->HTTPProxyAuth = getenv("HTTP_PROXY_USER"); } if (getenv("HTTP_PROXY_PASSWD")) { this->HTTPProxyAuth += ":"; this->HTTPProxyAuth += getenv("HTTP_PROXY_PASSWD"); } } if (!this->HTTPProxy.empty()) { cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " Use HTTP Proxy: " << this->HTTPProxy << std::endl, this->Quiet); } cmGeneratedFileStream ofs; this->StartLogFile("Submit", ofs); std::vector files; std::string prefix = this->GetSubmitResultsPrefix(); if (!this->Files.empty()) { // Submit the explicitly selected files: // files.insert(files.end(), this->Files.begin(), this->Files.end()); } // Add to the list of files to submit from any selected, existing parts: // // TODO: // Check if test is enabled this->CTest->AddIfExists(cmCTest::PartUpdate, "Update.xml"); this->CTest->AddIfExists(cmCTest::PartConfigure, "Configure.xml"); this->CTest->AddIfExists(cmCTest::PartBuild, "Build.xml"); this->CTest->AddIfExists(cmCTest::PartTest, "Test.xml"); if (this->CTest->AddIfExists(cmCTest::PartCoverage, "Coverage.xml")) { std::vector gfiles; std::string gpath = buildDirectory + "/Testing/" + this->CTest->GetCurrentTag(); std::string::size_type glen = gpath.size() + 1; gpath = gpath + "/CoverageLog*"; cmCTestOptionalLog(this->CTest, DEBUG, "Globbing for: " << gpath << std::endl, this->Quiet); if (cmSystemTools::SimpleGlob(gpath, gfiles, 1)) { for (std::string& gfile : gfiles) { gfile = gfile.substr(glen); cmCTestOptionalLog(this->CTest, DEBUG, "Glob file: " << gfile << std::endl, this->Quiet); this->CTest->AddSubmitFile(cmCTest::PartCoverage, gfile.c_str()); } } else { cmCTestLog(this->CTest, ERROR_MESSAGE, "Problem globbing" << std::endl); } } this->CTest->AddIfExists(cmCTest::PartMemCheck, "DynamicAnalysis.xml"); this->CTest->AddIfExists(cmCTest::PartMemCheck, "Purify.xml"); this->CTest->AddIfExists(cmCTest::PartNotes, "Notes.xml"); this->CTest->AddIfExists(cmCTest::PartUpload, "Upload.xml"); // Query parts for files to submit. for (cmCTest::Part p = cmCTest::PartStart; p != cmCTest::PartCount; p = cmCTest::Part(p + 1)) { // Skip parts we are not submitting. if (!this->SubmitPart[p]) { continue; } // Submit files from this part. std::vector const& pfiles = this->CTest->GetSubmitFiles(p); files.insert(files.end(), pfiles.begin(), pfiles.end()); } // Make sure files are unique, but preserve order. { // This endPos intermediate is needed to work around non-conformant C++11 // standard libraries that have erase(iterator,iterator) instead of // erase(const_iterator,const_iterator). size_t endPos = cmRemoveDuplicates(files) - files.cbegin(); files.erase(files.begin() + endPos, files.end()); } // Submit Done.xml last if (this->SubmitPart[cmCTest::PartDone]) { files.emplace_back("Done.xml"); } if (ofs) { ofs << "Upload files:" << std::endl; int cnt = 0; for (std::string const& file : files) { ofs << cnt << "\t" << file << std::endl; cnt++; } } cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "Submit files\n", this->Quiet); const char* specificTrack = this->CTest->GetSpecificTrack(); if (specificTrack) { cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " Send to track: " << specificTrack << std::endl, this->Quiet); } this->SetLogFile(&ofs); std::string url = this->CTest->GetSubmitURL(); cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " SubmitURL: " << url << '\n', this->Quiet); if (!this->SubmitUsingHTTP(buildDirectory + "/Testing/" + this->CTest->GetCurrentTag(), files, prefix, url)) { cmCTestLog(this->CTest, ERROR_MESSAGE, " Problems when submitting via HTTP\n"); ofs << " Problems when submitting via HTTP\n"; return -1; } if (this->HasErrors) { cmCTestLog(this->CTest, HANDLER_OUTPUT, " Errors occurred during submission.\n"); ofs << " Errors occurred during submission.\n"; } else { cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " Submission successful" << (this->HasWarnings ? ", with warnings." : "") << std::endl, this->Quiet); ofs << " Submission successful" << (this->HasWarnings ? ", with warnings." : "") << std::endl; } return 0; } std::string cmCTestSubmitHandler::GetSubmitResultsPrefix() { std::string buildname = cmCTest::SafeBuildIdField(this->CTest->GetCTestConfiguration("BuildName")); std::string name = this->CTest->GetCTestConfiguration("Site") + "___" + buildname + "___" + this->CTest->GetCurrentTag() + "-" + this->CTest->GetTestModelString() + "___XML___"; return name; } void cmCTestSubmitHandler::SelectParts(std::set const& parts) { // Check whether each part is selected. for (cmCTest::Part p = cmCTest::PartStart; p != cmCTest::PartCount; p = cmCTest::Part(p + 1)) { this->SubmitPart[p] = (std::set::const_iterator(parts.find(p)) != parts.end()); } } void cmCTestSubmitHandler::SelectFiles(cmCTest::SetOfStrings const& files) { this->Files.insert(files.begin(), files.end()); }