diff options
Diffstat (limited to 'Source/CTest/cmCTestSubmitHandler.cxx')
-rw-r--r-- | Source/CTest/cmCTestSubmitHandler.cxx | 908 |
1 files changed, 908 insertions, 0 deletions
diff --git a/Source/CTest/cmCTestSubmitHandler.cxx b/Source/CTest/cmCTestSubmitHandler.cxx new file mode 100644 index 0000000..1539635 --- /dev/null +++ b/Source/CTest/cmCTestSubmitHandler.cxx @@ -0,0 +1,908 @@ +/* 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 <chrono> +#include <sstream> +#include <stdio.h> +#include <stdlib.h> + +#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<char> cmCTestSubmitHandlerVectorOfChar; + +class cmCTestSubmitHandler::ResponseParser : public cmXMLParser +{ +public: + enum StatusType + { + STATUS_OK, + STATUS_WARNING, + STATUS_ERROR + }; + + StatusType Status = STATUS_OK; + std::string Filename; + std::string MD5; + std::string Message; + std::string BuildID; + +private: + std::vector<char> 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<int>(size * nmemb); + + cmCTestSubmitHandlerVectorOfChar* vec = + static_cast<cmCTestSubmitHandlerVectorOfChar*>(data); + const char* chPtr = static_cast<char*>(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<cmCTestSubmitHandlerVectorOfChar*>(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<std::string>& 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<std::string> 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<cmCTestScriptHandler*>( + 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<long>(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.data(), chunk.size()) + << "]" << std::endl, + this->Quiet); + this->ParseResponse(chunk); + } + if (!chunkDebug.empty()) { + cmCTestOptionalLog( + this->CTest, DEBUG, + "CURL debug output: [" + << cmCTestLogWrite(chunkDebug.data(), 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.data(), 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.data(), chunk.size()) + << std::endl; + cmCTestLog(this->CTest, ERROR_MESSAGE, + "CURL output: [" + << cmCTestLogWrite(chunk.data(), 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("<cdash") != std::string::npos) { + ResponseParser parser; + parser.Parse(output.c_str()); + + if (parser.Status != ResponseParser::STATUS_OK) { + this->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.data(), 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<std::string> 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<cmCTestScriptHandler*>(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<std::string> 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<std::string> 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<std::string> 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<cmCTest::Part> 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<cmCTest::Part>::const_iterator(parts.find(p)) != parts.end()); + } +} + +void cmCTestSubmitHandler::SelectFiles(cmCTest::SetOfStrings const& files) +{ + this->Files.insert(files.begin(), files.end()); +} |