diff options
author | MyungJoo Ham <myungjoo.ham@samsung.com> | 2017-10-11 15:16:57 +0900 |
---|---|---|
committer | MyungJoo Ham <myungjoo.ham@samsung.com> | 2017-10-11 15:16:57 +0900 |
commit | 915c76ded744c0f5f151402b9fa69f3fd8452573 (patch) | |
tree | ca6a387466543248890f346847acaa8343989b22 /Source/CTest | |
parent | 317dbdb79761ef65e45c7358cfc7571c6afa54ad (diff) | |
download | cmake-915c76ded744c0f5f151402b9fa69f3fd8452573.tar.gz cmake-915c76ded744c0f5f151402b9fa69f3fd8452573.tar.bz2 cmake-915c76ded744c0f5f151402b9fa69f3fd8452573.zip |
Imported Upstream version 3.9.4upstream/3.9.4
Diffstat (limited to 'Source/CTest')
95 files changed, 13052 insertions, 11795 deletions
diff --git a/Source/CTest/cmCTestBZR.cxx b/Source/CTest/cmCTestBZR.cxx index 381c70ce5..94f39c265 100644 --- a/Source/CTest/cmCTestBZR.cxx +++ b/Source/CTest/cmCTestBZR.cxx @@ -1,163 +1,148 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestBZR.h" #include "cmCTest.h" +#include "cmCTestVC.h" +#include "cmProcessTools.h" #include "cmSystemTools.h" #include "cmXMLParser.h" -#include "cmXMLSafe.h" - -#include <cmsys/RegularExpression.hxx> - -#include <cm_expat.h> -//---------------------------------------------------------------------------- -extern "C" -int cmBZRXMLParserUnknownEncodingHandler(void*, - const XML_Char *name, - XML_Encoding *info) +#include "cm_expat.h" +#include "cmsys/RegularExpression.hxx" +#include <list> +#include <map> +#include <ostream> +#include <stdlib.h> +#include <vector> + +extern "C" int cmBZRXMLParserUnknownEncodingHandler(void* /*unused*/, + const XML_Char* name, + XML_Encoding* info) { - static const int latin1[]= - { - 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, - 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, - 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, - 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, - 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, - 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, - 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, - 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, - 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, - 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, - 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, - 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, - 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, - 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, - 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, - 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F, - 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, - 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F, - 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, - 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178, - 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, - 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, - 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, - 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, - 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, - 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, - 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, - 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, - 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, - 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, - 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, - 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF - }; + static const int latin1[] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, + 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, 0x0010, 0x0011, + 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001A, + 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, 0x0020, 0x0021, 0x0022, 0x0023, + 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, + 0x002D, 0x002E, 0x002F, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, + 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, + 0x003F, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050, + 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, + 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, 0x0060, 0x0061, 0x0062, + 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006A, 0x006B, + 0x006C, 0x006D, 0x006E, 0x006F, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, + 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, + 0x007E, 0x007F, 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, + 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F, + 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, + 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178, 0x00A0, 0x00A1, + 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x00AA, + 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, 0x00B0, 0x00B1, 0x00B2, 0x00B3, + 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, + 0x00BD, 0x00BE, 0x00BF, 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, + 0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, + 0x00CF, 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, + 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, 0x00E0, + 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, 0x00E8, 0x00E9, + 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, 0x00F0, 0x00F1, 0x00F2, + 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, + 0x00FC, 0x00FD, 0x00FE, 0x00FF + }; // The BZR xml output plugin can use some encodings that are not // recognized by expat. This will lead to an error, e.g. "Error // parsing bzr log xml: unknown encoding", the following is a // workaround for these unknown encodings. - if(name == std::string("ascii") || name == std::string("cp1252") || - name == std::string("ANSI_X3.4-1968")) - { - for(unsigned int i=0;i<256;++i) info->map[i] = latin1[i]; - return 1; + if (name == std::string("ascii") || name == std::string("cp1252") || + name == std::string("ANSI_X3.4-1968")) { + for (unsigned int i = 0; i < 256; ++i) { + info->map[i] = latin1[i]; } + return 1; + } return 0; } -//---------------------------------------------------------------------------- -cmCTestBZR::cmCTestBZR(cmCTest* ct, std::ostream& log): - cmCTestGlobalVC(ct, log) +cmCTestBZR::cmCTestBZR(cmCTest* ct, std::ostream& log) + : cmCTestGlobalVC(ct, log) { this->PriorRev = this->Unknown; - // Even though it is specified in the documention, with bzr 1.13 + // Even though it is specified in the documentation, with bzr 1.13 // BZR_PROGRESS_BAR has no effect. In the future this bug might be fixed. // Since it doesn't hurt, we specify this environment variable. cmSystemTools::PutEnv("BZR_PROGRESS_BAR=none"); } -//---------------------------------------------------------------------------- cmCTestBZR::~cmCTestBZR() { } -//---------------------------------------------------------------------------- -class cmCTestBZR::InfoParser: public cmCTestVC::LineParser +class cmCTestBZR::InfoParser : public cmCTestVC::LineParser { public: - InfoParser(cmCTestBZR* bzr, const char* prefix): - BZR(bzr), CheckOutFound(false) - { + InfoParser(cmCTestBZR* bzr, const char* prefix) + : BZR(bzr) + , CheckOutFound(false) + { this->SetLog(&bzr->Log, prefix); this->RegexCheckOut.compile("checkout of branch: *([^\t\r\n]+)$"); this->RegexParent.compile("parent branch: *([^\t\r\n]+)$"); - } + } + private: cmCTestBZR* BZR; bool CheckOutFound; cmsys::RegularExpression RegexCheckOut; cmsys::RegularExpression RegexParent; - virtual bool ProcessLine() - { - if(this->RegexCheckOut.find(this->Line)) - { + bool ProcessLine() CM_OVERRIDE + { + if (this->RegexCheckOut.find(this->Line)) { this->BZR->URL = this->RegexCheckOut.match(1); CheckOutFound = true; - } - else if(!CheckOutFound && this->RegexParent.find(this->Line)) - { + } else if (!CheckOutFound && this->RegexParent.find(this->Line)) { this->BZR->URL = this->RegexParent.match(1); - } - return true; } + return true; + } }; -//---------------------------------------------------------------------------- -class cmCTestBZR::RevnoParser: public cmCTestVC::LineParser +class cmCTestBZR::RevnoParser : public cmCTestVC::LineParser { public: - RevnoParser(cmCTestBZR* bzr, const char* prefix, std::string& rev): - Rev(rev) - { + RevnoParser(cmCTestBZR* bzr, const char* prefix, std::string& rev) + : Rev(rev) + { this->SetLog(&bzr->Log, prefix); this->RegexRevno.compile("^([0-9]+)$"); - } + } + private: std::string& Rev; cmsys::RegularExpression RegexRevno; - virtual bool ProcessLine() - { - if(this->RegexRevno.find(this->Line)) - { + bool ProcessLine() CM_OVERRIDE + { + if (this->RegexRevno.find(this->Line)) { this->Rev = this->RegexRevno.match(1); - } - return true; } + return true; + } }; -//---------------------------------------------------------------------------- std::string cmCTestBZR::LoadInfo() { // Run "bzr info" to get the repository info from the work tree. const char* bzr = this->CommandLineTool.c_str(); - const char* bzr_info[] = {bzr, "info", 0}; + const char* bzr_info[] = { bzr, "info", CM_NULLPTR }; InfoParser iout(this, "info-out> "); OutputLogger ierr(this->Log, "info-err> "); this->RunChild(bzr_info, &iout, &ierr); // Run "bzr revno" to get the repository revision number from the work tree. - const char* bzr_revno[] = {bzr, "revno", 0}; + const char* bzr_revno[] = { bzr, "revno", CM_NULLPTR }; std::string rev; RevnoParser rout(this, "revno-out> ", rev); OutputLogger rerr(this->Log, "revno-err> "); @@ -166,51 +151,55 @@ std::string cmCTestBZR::LoadInfo() return rev; } -void cmCTestBZR::NoteOldRevision() +bool cmCTestBZR::NoteOldRevision() { this->OldRevision = this->LoadInfo(); this->Log << "Revision before update: " << this->OldRevision << "\n"; cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: " - << this->OldRevision << "\n"); + << this->OldRevision << "\n"); this->PriorRev.Rev = this->OldRevision; + return true; } -//---------------------------------------------------------------------------- -void cmCTestBZR::NoteNewRevision() +bool cmCTestBZR::NoteNewRevision() { this->NewRevision = this->LoadInfo(); this->Log << "Revision after update: " << this->NewRevision << "\n"; cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: " - << this->NewRevision << "\n"); + << this->NewRevision << "\n"); this->Log << "URL = " << this->URL << "\n"; + return true; } -//---------------------------------------------------------------------------- -class cmCTestBZR::LogParser: public cmCTestVC::OutputLogger, - private cmXMLParser +class cmCTestBZR::LogParser : public cmCTestVC::OutputLogger, + private cmXMLParser { public: - LogParser(cmCTestBZR* bzr, const char* prefix): - OutputLogger(bzr->Log, prefix), BZR(bzr), - EmailRegex("(.*) <([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+)>") - { this->InitializeParser(); } - ~LogParser() { this->CleanupParser(); } - - virtual int InitializeParser() - { - int res = cmXMLParser::InitializeParser(); - if (res) - { - XML_SetUnknownEncodingHandler(static_cast<XML_Parser>(this->Parser), - cmBZRXMLParserUnknownEncodingHandler, 0); - } - return res; - } + LogParser(cmCTestBZR* bzr, const char* prefix) + : OutputLogger(bzr->Log, prefix) + , BZR(bzr) + , EmailRegex("(.*) <([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+)>") + { + this->InitializeParser(); + } + ~LogParser() CM_OVERRIDE { this->CleanupParser(); } + + int InitializeParser() CM_OVERRIDE + { + int res = cmXMLParser::InitializeParser(); + if (res) { + XML_SetUnknownEncodingHandler(static_cast<XML_Parser>(this->Parser), + cmBZRXMLParserUnknownEncodingHandler, + CM_NULLPTR); + } + return res; + } + private: cmCTestBZR* BZR; typedef cmCTestBZR::Revision Revision; - typedef cmCTestBZR::Change Change; + typedef cmCTestBZR::Change Change; Revision Rev; std::vector<Change> Changes; Change CurChange; @@ -218,198 +207,164 @@ private: cmsys::RegularExpression EmailRegex; - virtual bool ProcessChunk(const char* data, int length) - { + bool ProcessChunk(const char* data, int length) CM_OVERRIDE + { this->OutputLogger::ProcessChunk(data, length); this->ParseChunk(data, length); return true; - } + } - virtual void StartElement(const char* name, const char**) - { + void StartElement(const std::string& name, const char** /*atts*/) CM_OVERRIDE + { this->CData.clear(); - if(strcmp(name, "log") == 0) - { + if (name == "log") { this->Rev = Revision(); this->Changes.clear(); - } + } // affected-files can contain blocks of // modified, unknown, renamed, kind-changed, removed, conflicts, added - else if(strcmp(name, "modified") == 0 - || strcmp(name, "renamed") == 0 - || strcmp(name, "kind-changed") == 0) - { + else if (name == "modified" || name == "renamed" || + name == "kind-changed") { this->CurChange = Change(); this->CurChange.Action = 'M'; - } - else if(strcmp(name, "added") == 0) - { + } else if (name == "added") { this->CurChange = Change(); this->CurChange = 'A'; - } - else if(strcmp(name, "removed") == 0) - { + } else if (name == "removed") { this->CurChange = Change(); this->CurChange = 'D'; - } - else if(strcmp(name, "unknown") == 0 - || strcmp(name, "conflicts") == 0) - { + } else if (name == "unknown" || name == "conflicts") { // Should not happen here this->CurChange = Change(); - } } + } - virtual void CharacterDataHandler(const char* data, int length) - { - this->CData.insert(this->CData.end(), data, data+length); - } + void CharacterDataHandler(const char* data, int length) CM_OVERRIDE + { + this->CData.insert(this->CData.end(), data, data + length); + } - virtual void EndElement(const char* name) - { - if(strcmp(name, "log") == 0) - { + void EndElement(const std::string& name) CM_OVERRIDE + { + if (name == "log") { this->BZR->DoRevision(this->Rev, this->Changes); - } - else if((strcmp(name, "file") == 0 || strcmp(name, "directory") == 0) - && !this->CData.empty()) - { + } else if (!this->CData.empty() && + (name == "file" || name == "directory")) { this->CurChange.Path.assign(&this->CData[0], this->CData.size()); cmSystemTools::ConvertToUnixSlashes(this->CurChange.Path); this->Changes.push_back(this->CurChange); - } - else if(strcmp(name, "symlink") == 0 && !this->CData.empty()) - { + } else if (!this->CData.empty() && name == "symlink") { // symlinks have an arobase at the end in the log - this->CurChange.Path.assign(&this->CData[0], this->CData.size()-1); + this->CurChange.Path.assign(&this->CData[0], this->CData.size() - 1); cmSystemTools::ConvertToUnixSlashes(this->CurChange.Path); this->Changes.push_back(this->CurChange); - } - else if(strcmp(name, "committer") == 0 && !this->CData.empty()) - { + } else if (!this->CData.empty() && name == "committer") { this->Rev.Author.assign(&this->CData[0], this->CData.size()); - if(this->EmailRegex.find(this->Rev.Author)) - { + if (this->EmailRegex.find(this->Rev.Author)) { this->Rev.Author = this->EmailRegex.match(1); this->Rev.EMail = this->EmailRegex.match(2); - } } - else if(strcmp(name, "timestamp") == 0 && !this->CData.empty()) - { + } else if (!this->CData.empty() && name == "timestamp") { this->Rev.Date.assign(&this->CData[0], this->CData.size()); - } - else if(strcmp(name, "message") == 0 && !this->CData.empty()) - { + } else if (!this->CData.empty() && name == "message") { this->Rev.Log.assign(&this->CData[0], this->CData.size()); - } - else if(strcmp(name, "revno") == 0 && !this->CData.empty()) - { + } else if (!this->CData.empty() && name == "revno") { this->Rev.Rev.assign(&this->CData[0], this->CData.size()); - } - this->CData.clear(); } + this->CData.clear(); + } - virtual void ReportError(int, int, const char* msg) - { + void ReportError(int /*line*/, int /*column*/, const char* msg) CM_OVERRIDE + { this->BZR->Log << "Error parsing bzr log xml: " << msg << "\n"; - } + } }; -//---------------------------------------------------------------------------- -class cmCTestBZR::UpdateParser: public cmCTestVC::LineParser +class cmCTestBZR::UpdateParser : public cmCTestVC::LineParser { public: - UpdateParser(cmCTestBZR* bzr, const char* prefix): BZR(bzr) - { + UpdateParser(cmCTestBZR* bzr, const char* prefix) + : BZR(bzr) + { this->SetLog(&bzr->Log, prefix); this->RegexUpdate.compile("^([-+R?XCP ])([NDKM ])([* ]) +(.+)$"); - } + } + private: cmCTestBZR* BZR; cmsys::RegularExpression RegexUpdate; - virtual bool ProcessChunk(const char* first, int length) - { + bool ProcessChunk(const char* first, int length) CM_OVERRIDE + { bool last_is_new_line = (*first == '\r' || *first == '\n'); const char* const last = first + length; - for(const char* c = first; c != last; ++c) - { - if(*c == '\r' || *c == '\n') - { - if(!last_is_new_line) - { + for (const char* c = first; c != last; ++c) { + if (*c == '\r' || *c == '\n') { + if (!last_is_new_line) { // Log this line. - if(this->Log && this->Prefix) - { + if (this->Log && this->Prefix) { *this->Log << this->Prefix << this->Line << "\n"; - } + } // Hand this line to the subclass implementation. - if(!this->ProcessLine()) - { + if (!this->ProcessLine()) { this->Line = ""; return false; - } + } this->Line = ""; last_is_new_line = true; - } } - else - { + } else { // Append this character to the line under construction. this->Line.append(1, *c); last_is_new_line = false; - } } - return true; } + return true; + } - bool ProcessLine() - { - if(this->RegexUpdate.find(this->Line)) - { + bool ProcessLine() CM_OVERRIDE + { + if (this->RegexUpdate.find(this->Line)) { this->DoPath(this->RegexUpdate.match(1)[0], this->RegexUpdate.match(2)[0], - this->RegexUpdate.match(3)[0], - this->RegexUpdate.match(4)); - } - return true; + this->RegexUpdate.match(3)[0], this->RegexUpdate.match(4)); } + return true; + } void DoPath(char c0, char c1, char c2, std::string path) - { - if(path.empty()) return; + { + if (path.empty()) { + return; + } cmSystemTools::ConvertToUnixSlashes(path); const std::string dir = cmSystemTools::GetFilenamePath(path); const std::string name = cmSystemTools::GetFilenameName(path); - if ( c0=='C' ) - { + if (c0 == 'C') { this->BZR->Dirs[dir][name].Status = PathConflicting; return; - } + } - if ( c1=='M' || c1=='K' || c1=='N' || c1=='D' || c2 =='*' ) - { + if (c1 == 'M' || c1 == 'K' || c1 == 'N' || c1 == 'D' || c2 == '*') { this->BZR->Dirs[dir][name].Status = PathUpdated; return; - } } + } }; -//---------------------------------------------------------------------------- bool cmCTestBZR::UpdateImpl() { // Get user-specified update options. std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); - if(opts.empty()) - { + if (opts.empty()) { opts = this->CTest->GetCTestConfiguration("BZRUpdateOptions"); - } - std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str()); + } + std::vector<std::string> args = cmSystemTools::ParseArguments(opts.c_str()); // TODO: if(this->CTest->GetTestModel() == cmCTest::NIGHTLY) @@ -418,15 +373,14 @@ bool cmCTestBZR::UpdateImpl() bzr_update.push_back(this->CommandLineTool.c_str()); bzr_update.push_back("pull"); - for(std::vector<cmStdString>::const_iterator ai = args.begin(); - ai != args.end(); ++ai) - { + for (std::vector<std::string>::const_iterator ai = args.begin(); + ai != args.end(); ++ai) { bzr_update.push_back(ai->c_str()); - } + } bzr_update.push_back(this->URL.c_str()); - bzr_update.push_back(0); + bzr_update.push_back(CM_NULLPTR); // For some reason bzr uses stderr to display the update status. OutputLogger out(this->Log, "pull-out> "); @@ -434,90 +388,88 @@ bool cmCTestBZR::UpdateImpl() return this->RunUpdateCommand(&bzr_update[0], &out, &err); } -//---------------------------------------------------------------------------- -void cmCTestBZR::LoadRevisions() +bool cmCTestBZR::LoadRevisions() { cmCTestLog(this->CTest, HANDLER_OUTPUT, " Gathering version information (one . per revision):\n" - " " << std::flush); + " " + << std::flush); // We are interested in every revision included in the update. this->Revisions.clear(); std::string revs; - if(atoi(this->OldRevision.c_str()) <= atoi(this->NewRevision.c_str())) - { + if (atoi(this->OldRevision.c_str()) <= atoi(this->NewRevision.c_str())) { // DoRevision takes care of discarding the information about OldRevision revs = this->OldRevision + ".." + this->NewRevision; - } - else - { - return; - } + } else { + return true; + } // Run "bzr log" to get all global revisions of interest. const char* bzr = this->CommandLineTool.c_str(); - const char* bzr_log[] = {bzr, "log", "-v", "-r", revs.c_str(), "--xml", - this->URL.c_str(), 0}; + const char* bzr_log[] = { + bzr, "log", "-v", "-r", revs.c_str(), "--xml", this->URL.c_str(), + CM_NULLPTR + }; { - LogParser out(this, "log-out> "); - OutputLogger err(this->Log, "log-err> "); - this->RunChild(bzr_log, &out, &err); + LogParser out(this, "log-out> "); + OutputLogger err(this->Log, "log-err> "); + this->RunChild(bzr_log, &out, &err); } cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); + return true; } -//---------------------------------------------------------------------------- -class cmCTestBZR::StatusParser: public cmCTestVC::LineParser +class cmCTestBZR::StatusParser : public cmCTestVC::LineParser { public: - StatusParser(cmCTestBZR* bzr, const char* prefix): BZR(bzr) - { + StatusParser(cmCTestBZR* bzr, const char* prefix) + : BZR(bzr) + { this->SetLog(&bzr->Log, prefix); this->RegexStatus.compile("^([-+R?XCP ])([NDKM ])([* ]) +(.+)$"); - } + } + private: cmCTestBZR* BZR; cmsys::RegularExpression RegexStatus; - bool ProcessLine() - { - if(this->RegexStatus.find(this->Line)) - { + bool ProcessLine() CM_OVERRIDE + { + if (this->RegexStatus.find(this->Line)) { this->DoPath(this->RegexStatus.match(1)[0], this->RegexStatus.match(2)[0], - this->RegexStatus.match(3)[0], - this->RegexStatus.match(4)); - } - return true; + this->RegexStatus.match(3)[0], this->RegexStatus.match(4)); } + return true; + } void DoPath(char c0, char c1, char c2, std::string path) - { - if(path.empty()) return; + { + if (path.empty()) { + return; + } cmSystemTools::ConvertToUnixSlashes(path); - if ( c0=='C' ) - { + if (c0 == 'C') { this->BZR->DoModification(PathConflicting, path); return; - } + } - if ( c0 == '+' || c0 == 'R' || c0 == 'P' - || c1=='M' || c1=='K' || c1=='N' || c1=='D' - || c2 =='*' ) - { + if (c0 == '+' || c0 == 'R' || c0 == 'P' || c1 == 'M' || c1 == 'K' || + c1 == 'N' || c1 == 'D' || c2 == '*') { this->BZR->DoModification(PathModified, path); return; - } } + } }; -//---------------------------------------------------------------------------- -void cmCTestBZR::LoadModifications() +bool cmCTestBZR::LoadModifications() { // Run "bzr status" which reports local modifications. const char* bzr = this->CommandLineTool.c_str(); - const char* bzr_status[] = {bzr, "status", "-SV", 0}; + const char* bzr_status[] = { bzr, "status", "-SV", CM_NULLPTR }; StatusParser out(this, "status-out> "); OutputLogger err(this->Log, "status-err> "); this->RunChild(bzr_status, &out, &err); + return true; } diff --git a/Source/CTest/cmCTestBZR.h b/Source/CTest/cmCTestBZR.h index df688e1ee..2e8e88f95 100644 --- a/Source/CTest/cmCTestBZR.h +++ b/Source/CTest/cmCTestBZR.h @@ -1,53 +1,52 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestBZR_h #define cmCTestBZR_h +#include "cmConfigure.h" + #include "cmCTestGlobalVC.h" +#include <iosfwd> +#include <string> + +class cmCTest; + /** \class cmCTestBZR * \brief Interaction with bzr command-line tool * */ -class cmCTestBZR: public cmCTestGlobalVC +class cmCTestBZR : public cmCTestGlobalVC { public: /** Construct with a CTest instance and update log stream. */ cmCTestBZR(cmCTest* ctest, std::ostream& log); - virtual ~cmCTestBZR(); + ~cmCTestBZR() CM_OVERRIDE; private: // Implement cmCTestVC internal API. - virtual void NoteOldRevision(); - virtual void NoteNewRevision(); - virtual bool UpdateImpl(); + bool NoteOldRevision() CM_OVERRIDE; + bool NoteNewRevision() CM_OVERRIDE; + bool UpdateImpl() CM_OVERRIDE; // URL of repository directory checked out in the working tree. std::string URL; std::string LoadInfo(); - void LoadModifications(); - void LoadRevisions(); + bool LoadModifications() CM_OVERRIDE; + bool LoadRevisions() CM_OVERRIDE; // Parsing helper classes. class InfoParser; - class RevnoParser; class LogParser; - class UpdateParser; + class RevnoParser; class StatusParser; + class UpdateParser; + friend class InfoParser; - friend class RevnoParser; friend class LogParser; + friend class RevnoParser; friend class UpdateParser; friend class StatusParser; }; diff --git a/Source/CTest/cmCTestBatchTestHandler.cxx b/Source/CTest/cmCTestBatchTestHandler.cxx index 934481b88..beee53a13 100644 --- a/Source/CTest/cmCTestBatchTestHandler.cxx +++ b/Source/CTest/cmCTestBatchTestHandler.cxx @@ -1,64 +1,55 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestBatchTestHandler.h" -#include "cmProcess.h" -#include "cmStandardIncludes.h" + #include "cmCTest.h" +#include "cmCTestMultiProcessHandler.h" +#include "cmCTestTestHandler.h" +#include "cmProcess.h" #include "cmSystemTools.h" -#include <stdlib.h> + +#include <map> +#include <utility> +#include <vector> cmCTestBatchTestHandler::~cmCTestBatchTestHandler() { } -//--------------------------------------------------------- void cmCTestBatchTestHandler::RunTests() { this->WriteBatchScript(); this->SubmitBatchScript(); } -//--------------------------------------------------------- void cmCTestBatchTestHandler::WriteBatchScript() { - this->Script = this->CTest->GetBinaryDir() - + "/Testing/CTestBatch.txt"; - std::fstream fout; - fout.open(this->Script.c_str(), std::ios::out); + this->Script = this->CTest->GetBinaryDir() + "/Testing/CTestBatch.txt"; + cmsys::ofstream fout; + fout.open(this->Script.c_str()); fout << "#!/bin/sh\n"; - for(TestMap::iterator i = this->Tests.begin(); i != this->Tests.end(); ++i) - { + for (TestMap::iterator i = this->Tests.begin(); i != this->Tests.end(); + ++i) { this->WriteSrunArgs(i->first, fout); this->WriteTestCommand(i->first, fout); fout << "\n"; - } + } fout.flush(); fout.close(); } -//--------------------------------------------------------- -void cmCTestBatchTestHandler::WriteSrunArgs(int test, std::fstream& fout) +void cmCTestBatchTestHandler::WriteSrunArgs(int test, std::ostream& fout) { cmCTestTestHandler::cmCTestTestProperties* properties = - this->Properties[test]; + this->Properties[test]; fout << "srun "; - //fout << "--jobid=" << test << " "; + // fout << "--jobid=" << test << " "; fout << "-J=" << properties->Name << " "; - //Write dependency information - /*if(this->Tests[test].size() > 0) + // Write dependency information + /*if(!this->Tests[test].empty()) { fout << "-P=afterany"; for(TestSet::iterator i = this->Tests[test].begin(); @@ -68,18 +59,15 @@ void cmCTestBatchTestHandler::WriteSrunArgs(int test, std::fstream& fout) } fout << " "; }*/ - if(properties->RunSerial) - { + if (properties->RunSerial) { fout << "--exclusive "; - } - if(properties->Processors > 1) - { + } + if (properties->Processors > 1) { fout << "-n" << properties->Processors << " "; - } + } } -//--------------------------------------------------------- -void cmCTestBatchTestHandler::WriteTestCommand(int test, std::fstream& fout) +void cmCTestBatchTestHandler::WriteTestCommand(int test, std::ostream& fout) { std::vector<std::string> args = this->Properties[test]->Args; std::vector<std::string> processArgs; @@ -88,47 +76,41 @@ void cmCTestBatchTestHandler::WriteTestCommand(int test, std::fstream& fout) command = this->TestHandler->FindTheExecutable(args[1].c_str()); command = cmSystemTools::ConvertToOutputPath(command.c_str()); - //Prepends memcheck args to our command string if this is a memcheck + // Prepends memcheck args to our command string if this is a memcheck this->TestHandler->GenerateTestCommand(processArgs, test); processArgs.push_back(command); - for(std::vector<std::string>::iterator arg = processArgs.begin(); - arg != processArgs.end(); ++arg) - { + for (std::vector<std::string>::iterator arg = processArgs.begin(); + arg != processArgs.end(); ++arg) { fout << *arg << " "; - } + } std::vector<std::string>::iterator i = args.begin(); - ++i; //the test name - ++i; //the executable (command) - if(args.size() > 2) - { + ++i; // the test name + ++i; // the executable (command) + if (args.size() > 2) { fout << "'"; - } - while(i != args.end()) - { - fout << "\"" << *i << "\""; //args to the test executable + } + while (i != args.end()) { + fout << "\"" << *i << "\""; // args to the test executable ++i; - if(i == args.end() && args.size() > 2) - { + if (i == args.end() && args.size() > 2) { fout << "'"; - } - fout << " "; } - //TODO ZACH build TestResult.FullCommandLine - //this->TestResult.FullCommandLine = this->TestCommand; + fout << " "; + } + // TODO ZACH build TestResult.FullCommandLine + // this->TestResult.FullCommandLine = this->TestCommand; } -//--------------------------------------------------------- void cmCTestBatchTestHandler::SubmitBatchScript() { cmProcess sbatch; std::vector<std::string> args; args.push_back(this->Script); args.push_back("-o"); - args.push_back(this->CTest->GetBinaryDir() - + "/Testing/CTestBatch.txt"); + args.push_back(this->CTest->GetBinaryDir() + "/Testing/CTestBatch.txt"); sbatch.SetCommand("sbatch"); sbatch.SetCommandArguments(args); diff --git a/Source/CTest/cmCTestBatchTestHandler.h b/Source/CTest/cmCTestBatchTestHandler.h index ab0d081ae..4a5dac1d6 100644 --- a/Source/CTest/cmCTestBatchTestHandler.h +++ b/Source/CTest/cmCTestBatchTestHandler.h @@ -1,22 +1,13 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestBatchTestHandler_h #define cmCTestBatchTestHandler_h -#include <cmStandardIncludes.h> -#include <cmCTestTestHandler.h> -#include <cmCTestMultiProcessHandler.h> -#include <cmCTestRunTest.h> +#include "cmConfigure.h" + +#include "cmCTestMultiProcessHandler.h" +#include "cmsys/FStream.hxx" +#include <string> /** \class cmCTestBatchTestHandler * \brief run parallel ctest @@ -26,12 +17,13 @@ class cmCTestBatchTestHandler : public cmCTestMultiProcessHandler { public: - ~cmCTestBatchTestHandler(); - virtual void RunTests(); + ~cmCTestBatchTestHandler() CM_OVERRIDE; + void RunTests() CM_OVERRIDE; + protected: void WriteBatchScript(); - void WriteSrunArgs(int test, std::fstream& fout); - void WriteTestCommand(int test, std::fstream& fout); + void WriteSrunArgs(int test, std::ostream& fout); + void WriteTestCommand(int test, std::ostream& fout); void SubmitBatchScript(); diff --git a/Source/CTest/cmCTestBuildAndTestHandler.cxx b/Source/CTest/cmCTestBuildAndTestHandler.cxx index 4fa3c53b9..cc290715c 100644 --- a/Source/CTest/cmCTestBuildAndTestHandler.cxx +++ b/Source/CTest/cmCTestBuildAndTestHandler.cxx @@ -1,47 +1,35 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestBuildAndTestHandler.h" -#include "cmSystemTools.h" #include "cmCTest.h" -#include "cmake.h" -#include "cmGlobalGenerator.h" -#include <cmsys/Process.h> #include "cmCTestTestHandler.h" +#include "cmGlobalGenerator.h" +#include "cmSystemTools.h" +#include "cmWorkingDirectory.h" +#include "cmake.h" + +#include "cmsys/Process.h" +#include <stdlib.h> -//---------------------------------------------------------------------- cmCTestBuildAndTestHandler::cmCTestBuildAndTestHandler() { - this->BuildTwoConfig = false; - this->BuildNoClean = false; - this->BuildNoCMake = false; + this->BuildTwoConfig = false; + this->BuildNoClean = false; + this->BuildNoCMake = false; this->Timeout = 0; } -//---------------------------------------------------------------------- void cmCTestBuildAndTestHandler::Initialize() { - this->BuildTargets.erase( - this->BuildTargets.begin(), this->BuildTargets.end()); + this->BuildTargets.clear(); this->Superclass::Initialize(); } -//---------------------------------------------------------------------- const char* cmCTestBuildAndTestHandler::GetOutput() { return this->Output.c_str(); } -//---------------------------------------------------------------------- int cmCTestBuildAndTestHandler::ProcessHandler() { this->Output = ""; @@ -52,271 +40,235 @@ int cmCTestBuildAndTestHandler::ProcessHandler() return retv; } -//---------------------------------------------------------------------- int cmCTestBuildAndTestHandler::RunCMake(std::string* outstring, - cmOStringStream &out, std::string &cmakeOutString, std::string &cwd, - cmake *cm) + std::ostringstream& out, + std::string& cmakeOutString, + cmake* cm) { unsigned int k; std::vector<std::string> args; - args.push_back(this->CTest->GetCMakeExecutable()); + args.push_back(cmSystemTools::GetCMakeCommand()); args.push_back(this->SourceDir); - if(this->BuildGenerator.size()) - { + if (!this->BuildGenerator.empty()) { std::string generator = "-G"; generator += this->BuildGenerator; args.push_back(generator); - } - if(this->BuildGeneratorToolset.size()) - { + } + if (!this->BuildGeneratorPlatform.empty()) { + std::string platform = "-A"; + platform += this->BuildGeneratorPlatform; + args.push_back(platform); + } + if (!this->BuildGeneratorToolset.empty()) { std::string toolset = "-T"; toolset += this->BuildGeneratorToolset; args.push_back(toolset); - } + } - const char* config = 0; - if ( this->CTest->GetConfigType().size() > 0 ) - { + const char* config = CM_NULLPTR; + if (!this->CTest->GetConfigType().empty()) { config = this->CTest->GetConfigType().c_str(); - } + } #ifdef CMAKE_INTDIR - if(!config) - { + if (!config) { config = CMAKE_INTDIR; - } + } #endif - if ( config ) - { - std::string btype - = "-DCMAKE_BUILD_TYPE:STRING=" + std::string(config); + if (config) { + std::string btype = "-DCMAKE_BUILD_TYPE:STRING=" + std::string(config); args.push_back(btype); - } + } - for(k=0; k < this->BuildOptions.size(); ++k) - { + for (k = 0; k < this->BuildOptions.size(); ++k) { args.push_back(this->BuildOptions[k]); - } - if (cm->Run(args) != 0) - { + } + if (cm->Run(args) != 0) { out << "Error: cmake execution failed\n"; out << cmakeOutString << "\n"; - // return to the original directory - cmSystemTools::ChangeDirectory(cwd.c_str()); - if(outstring) - { + if (outstring) { *outstring = out.str(); - } - else - { + } else { cmCTestLog(this->CTest, ERROR_MESSAGE, out.str() << std::endl); - } - return 1; } + return 1; + } // do another config? - if(this->BuildTwoConfig) - { - if (cm->Run(args) != 0) - { + if (this->BuildTwoConfig) { + if (cm->Run(args) != 0) { out << "Error: cmake execution failed\n"; out << cmakeOutString << "\n"; - // return to the original directory - cmSystemTools::ChangeDirectory(cwd.c_str()); - if(outstring) - { + if (outstring) { *outstring = out.str(); - } - else - { + } else { cmCTestLog(this->CTest, ERROR_MESSAGE, out.str() << std::endl); - } - return 1; } + return 1; } + } out << "======== CMake output ======\n"; out << cmakeOutString; out << "======== End CMake output ======\n"; return 0; } -//---------------------------------------------------------------------- -void CMakeMessageCallback(const char* m, const char*, bool&, void* s) +void CMakeMessageCallback(const char* m, const char* /*unused*/, + bool& /*unused*/, void* s) { std::string* out = (std::string*)s; *out += m; *out += "\n"; } -void CMakeProgressCallback(const char*msg, float , void * s) +void CMakeProgressCallback(const char* msg, float /*unused*/, void* s) { std::string* out = (std::string*)s; *out += msg; *out += "\n"; } -//---------------------------------------------------------------------- -void CMakeStdoutCallback(const char* m, int len, void* s) +void CMakeOutputCallback(const char* m, size_t len, void* s) { std::string* out = (std::string*)s; out->append(m, len); } -struct cmSetupOutputCaptureCleanup + +class cmCTestBuildAndTestCaptureRAII { - ~cmSetupOutputCaptureCleanup() + cmake& CM; + +public: + cmCTestBuildAndTestCaptureRAII(cmake& cm, std::string& s) + : CM(cm) { - cmSystemTools::SetErrorCallback(0, 0); - cmSystemTools::SetStdoutCallback(0, 0); + cmSystemTools::SetMessageCallback(CMakeMessageCallback, &s); + cmSystemTools::SetStdoutCallback(CMakeOutputCallback, &s); + cmSystemTools::SetStderrCallback(CMakeOutputCallback, &s); + this->CM.SetProgressCallback(CMakeProgressCallback, &s); + } + ~cmCTestBuildAndTestCaptureRAII() + { + this->CM.SetProgressCallback(CM_NULLPTR, CM_NULLPTR); + cmSystemTools::SetStderrCallback(CM_NULLPTR, CM_NULLPTR); + cmSystemTools::SetStdoutCallback(CM_NULLPTR, CM_NULLPTR); + cmSystemTools::SetMessageCallback(CM_NULLPTR, CM_NULLPTR); } }; -//---------------------------------------------------------------------- int cmCTestBuildAndTestHandler::RunCMakeAndTest(std::string* outstring) { - unsigned int k; - std::string cmakeOutString; - cmSystemTools::SetErrorCallback(CMakeMessageCallback, &cmakeOutString); - cmSystemTools::SetStdoutCallback(CMakeStdoutCallback, &cmakeOutString); - // make sure SetStdoutCallback and SetErrorCallback are set to null - // after this function exits so that they do not point at a destroyed - // string cmakeOutString - cmSetupOutputCaptureCleanup cleanup; - static_cast<void>(cleanup); - cmOStringStream out; - // if the generator and make program are not specified then it is an error - if (!this->BuildGenerator.size() || !this->BuildMakeProgram.size()) - { - if(outstring) - { - *outstring = - "--build-and-test requires that both the generator and makeprogram " - "be provided using the --build-generator and --build-makeprogram " - "command line options. "; - } - return 1; + if (this->BuildGenerator.empty()) { + if (outstring) { + *outstring = "--build-and-test requires that the generator " + "be provided using the --build-generator " + "command line option. "; } + return 1; + } - if ( this->CTest->GetConfigType().size() == 0 && - this->ConfigSample.size()) - { + cmake cm(cmake::RoleProject); + cm.SetHomeDirectory(""); + cm.SetHomeOutputDirectory(""); + std::string cmakeOutString; + cmCTestBuildAndTestCaptureRAII captureRAII(cm, cmakeOutString); + static_cast<void>(captureRAII); + std::ostringstream out; + + if (this->CTest->GetConfigType().empty() && !this->ConfigSample.empty()) { // use the config sample to set the ConfigType std::string fullPath; std::string resultingConfig; std::vector<std::string> extraPaths; std::vector<std::string> failed; - fullPath = - cmCTestTestHandler::FindExecutable(this->CTest, - this->ConfigSample.c_str(), - resultingConfig, - extraPaths, - failed); - if (fullPath.size() && resultingConfig.size()) - { + fullPath = cmCTestTestHandler::FindExecutable( + this->CTest, this->ConfigSample.c_str(), resultingConfig, extraPaths, + failed); + if (!fullPath.empty() && !resultingConfig.empty()) { this->CTest->SetConfigType(resultingConfig.c_str()); - } - out << "Using config sample with results: " - << fullPath << " and " << resultingConfig << std::endl; } + out << "Using config sample with results: " << fullPath << " and " + << resultingConfig << std::endl; + } // we need to honor the timeout specified, the timeout include cmake, build // and test time double clock_start = cmSystemTools::GetTime(); // make sure the binary dir is there - std::string cwd = cmSystemTools::GetCurrentWorkingDirectory(); - out << "Internal cmake changing into directory: " - << this->BinaryDir << std::endl; - if (!cmSystemTools::FileIsDirectory(this->BinaryDir.c_str())) - { + out << "Internal cmake changing into directory: " << this->BinaryDir + << std::endl; + if (!cmSystemTools::FileIsDirectory(this->BinaryDir)) { cmSystemTools::MakeDirectory(this->BinaryDir.c_str()); - } - cmSystemTools::ChangeDirectory(this->BinaryDir.c_str()); - - // should we cmake? - cmake cm; - cm.SetProgressCallback(CMakeProgressCallback, &cmakeOutString); + } + cmWorkingDirectory workdir(this->BinaryDir); - if(this->BuildNoCMake) - { - cm.SetGlobalGenerator(cm.CreateGlobalGenerator( - this->BuildGenerator.c_str())); + if (this->BuildNoCMake) { + // Make the generator available for the Build call below. + cm.SetGlobalGenerator(cm.CreateGlobalGenerator(this->BuildGenerator)); + cm.SetGeneratorPlatform(this->BuildGeneratorPlatform); cm.SetGeneratorToolset(this->BuildGeneratorToolset); - } - else - { + + // Load the cache to make CMAKE_MAKE_PROGRAM available. + cm.LoadCache(this->BinaryDir); + } else { // do the cmake step, no timeout here since it is not a sub process - if (this->RunCMake(outstring,out,cmakeOutString,cwd,&cm)) - { + if (this->RunCMake(outstring, out, cmakeOutString, &cm)) { return 1; - } } + } // do the build std::vector<std::string>::iterator tarIt; - if ( this->BuildTargets.size() == 0 ) - { + if (this->BuildTargets.empty()) { this->BuildTargets.push_back(""); - } - for ( tarIt = this->BuildTargets.begin(); - tarIt != this->BuildTargets.end(); ++ tarIt ) - { + } + for (tarIt = this->BuildTargets.begin(); tarIt != this->BuildTargets.end(); + ++tarIt) { double remainingTime = 0; - if (this->Timeout > 0) - { + if (this->Timeout > 0) { remainingTime = this->Timeout - cmSystemTools::GetTime() + clock_start; - if (remainingTime <= 0) - { - if(outstring) - { + if (remainingTime <= 0) { + if (outstring) { *outstring = "--build-and-test timeout exceeded. "; - } - return 1; } + return 1; } + } std::string output; - const char* config = 0; - if ( this->CTest->GetConfigType().size() > 0 ) - { + const char* config = CM_NULLPTR; + if (!this->CTest->GetConfigType().empty()) { config = this->CTest->GetConfigType().c_str(); - } + } #ifdef CMAKE_INTDIR - if(!config) - { + if (!config) { config = CMAKE_INTDIR; - } + } #endif - if(!config) - { + if (!config) { config = "Debug"; - } + } int retVal = cm.GetGlobalGenerator()->Build( - this->SourceDir.c_str(), this->BinaryDir.c_str(), - this->BuildProject.c_str(), tarIt->c_str(), - &output, this->BuildMakeProgram.c_str(), - config, - !this->BuildNoClean, - false, remainingTime); + this->SourceDir, this->BinaryDir, this->BuildProject, *tarIt, output, + this->BuildMakeProgram, config, !this->BuildNoClean, false, false, + remainingTime); out << output; // if the build failed then return - if (retVal) - { - if(outstring) - { - *outstring = out.str(); - } - return 1; + if (retVal) { + if (outstring) { + *outstring = out.str(); } + return 1; } - if(outstring) - { - *outstring = out.str(); - } + } + if (outstring) { + *outstring = out.str(); + } // if no test was specified then we are done - if (!this->TestCommand.size()) - { + if (this->TestCommand.empty()) { return 0; - } + } // now run the compiled test if we can find it // store the final location in fullPath @@ -324,219 +276,169 @@ int cmCTestBuildAndTestHandler::RunCMakeAndTest(std::string* outstring) std::string resultingConfig; std::vector<std::string> extraPaths; // if this->ExecutableDirectory is set try that as well - if (this->ExecutableDirectory.size()) - { + if (!this->ExecutableDirectory.empty()) { std::string tempPath = this->ExecutableDirectory; tempPath += "/"; tempPath += this->TestCommand; extraPaths.push_back(tempPath); - } + } std::vector<std::string> failed; fullPath = - cmCTestTestHandler::FindExecutable(this->CTest, - this->TestCommand.c_str(), - resultingConfig, - extraPaths, - failed); + cmCTestTestHandler::FindExecutable(this->CTest, this->TestCommand.c_str(), + resultingConfig, extraPaths, failed); - if(!cmSystemTools::FileExists(fullPath.c_str())) - { + if (!cmSystemTools::FileExists(fullPath.c_str())) { out << "Could not find path to executable, perhaps it was not built: " - << this->TestCommand << "\n"; + << this->TestCommand << "\n"; out << "tried to find it in these places:\n"; - out << fullPath.c_str() << "\n"; - for(unsigned int i=0; i < failed.size(); ++i) - { + out << fullPath << "\n"; + for (unsigned int i = 0; i < failed.size(); ++i) { out << failed[i] << "\n"; - } - if(outstring) - { - *outstring = out.str(); - } - else - { + } + if (outstring) { + *outstring = out.str(); + } else { cmCTestLog(this->CTest, ERROR_MESSAGE, out.str()); - } - // return to the original directory - cmSystemTools::ChangeDirectory(cwd.c_str()); - return 1; } + return 1; + } std::vector<const char*> testCommand; testCommand.push_back(fullPath.c_str()); - for(k=0; k < this->TestCommandArgs.size(); ++k) - { + for (size_t k = 0; k < this->TestCommandArgs.size(); ++k) { testCommand.push_back(this->TestCommandArgs[k].c_str()); - } - testCommand.push_back(0); + } + testCommand.push_back(CM_NULLPTR); std::string outs; int retval = 0; // run the test from the this->BuildRunDir if set - if(this->BuildRunDir.size()) - { + if (!this->BuildRunDir.empty()) { out << "Run test in directory: " << this->BuildRunDir << "\n"; - cmSystemTools::ChangeDirectory(this->BuildRunDir.c_str()); - } + cmSystemTools::ChangeDirectory(this->BuildRunDir); + } out << "Running test command: \"" << fullPath << "\""; - for(k=0; k < this->TestCommandArgs.size(); ++k) - { + for (size_t k = 0; k < this->TestCommandArgs.size(); ++k) { out << " \"" << this->TestCommandArgs[k] << "\""; - } + } out << "\n"; // how much time is remaining double remainingTime = 0; - if (this->Timeout > 0) - { + if (this->Timeout > 0) { remainingTime = this->Timeout - cmSystemTools::GetTime() + clock_start; - if (remainingTime <= 0) - { - if(outstring) - { + if (remainingTime <= 0) { + if (outstring) { *outstring = "--build-and-test timeout exceeded. "; - } - return 1; } + return 1; } + } - int runTestRes = this->CTest->RunTest(testCommand, &outs, &retval, 0, - remainingTime, 0); + int runTestRes = this->CTest->RunTest(testCommand, &outs, &retval, + CM_NULLPTR, remainingTime, CM_NULLPTR); - if(runTestRes != cmsysProcess_State_Exited || retval != 0) - { + if (runTestRes != cmsysProcess_State_Exited || retval != 0) { out << "Test command failed: " << testCommand[0] << "\n"; retval = 1; - } + } out << outs << "\n"; - if(outstring) - { + if (outstring) { *outstring = out.str(); - } - else - { + } else { cmCTestLog(this->CTest, OUTPUT, out.str() << std::endl); - } + } return retval; } -//---------------------------------------------------------------------- int cmCTestBuildAndTestHandler::ProcessCommandLineArguments( const std::string& currentArg, size_t& idx, const std::vector<std::string>& allArgs) { // --build-and-test options - if(currentArg.find("--build-and-test",0) == 0 && idx < allArgs.size() - 1) - { - if(idx+2 < allArgs.size()) - { + if (currentArg.find("--build-and-test", 0) == 0 && + idx < allArgs.size() - 1) { + if (idx + 2 < allArgs.size()) { idx++; this->SourceDir = allArgs[idx]; idx++; this->BinaryDir = allArgs[idx]; // dir must exist before CollapseFullPath is called cmSystemTools::MakeDirectory(this->BinaryDir.c_str()); - this->BinaryDir - = cmSystemTools::CollapseFullPath(this->BinaryDir.c_str()); - this->SourceDir - = cmSystemTools::CollapseFullPath(this->SourceDir.c_str()); - } - else - { + this->BinaryDir = cmSystemTools::CollapseFullPath(this->BinaryDir); + this->SourceDir = cmSystemTools::CollapseFullPath(this->SourceDir); + } else { cmCTestLog(this->CTest, ERROR_MESSAGE, - "--build-and-test must have source and binary dir" << std::endl); + "--build-and-test must have source and binary dir" + << std::endl); return 0; - } } - if(currentArg.find("--build-target",0) == 0 && idx < allArgs.size() - 1) - { + } + if (currentArg.find("--build-target", 0) == 0 && idx < allArgs.size() - 1) { idx++; this->BuildTargets.push_back(allArgs[idx]); - } - if(currentArg.find("--build-nocmake",0) == 0) - { + } + if (currentArg.find("--build-nocmake", 0) == 0) { this->BuildNoCMake = true; - } - if(currentArg.find("--build-run-dir",0) == 0 && idx < allArgs.size() - 1) - { + } + if (currentArg.find("--build-run-dir", 0) == 0 && idx < allArgs.size() - 1) { idx++; this->BuildRunDir = allArgs[idx]; - } - if(currentArg.find("--build-two-config",0) == 0) - { + } + if (currentArg.find("--build-two-config", 0) == 0) { this->BuildTwoConfig = true; - } - if(currentArg.find("--build-exe-dir",0) == 0 && idx < allArgs.size() - 1) - { + } + if (currentArg.find("--build-exe-dir", 0) == 0 && idx < allArgs.size() - 1) { idx++; this->ExecutableDirectory = allArgs[idx]; - } - if(currentArg.find("--test-timeout",0) == 0 && idx < allArgs.size() - 1) - { + } + if (currentArg.find("--test-timeout", 0) == 0 && idx < allArgs.size() - 1) { idx++; this->Timeout = atof(allArgs[idx].c_str()); - } - if(currentArg == "--build-generator" && idx < allArgs.size() - 1) - { + } + if (currentArg == "--build-generator" && idx < allArgs.size() - 1) { idx++; this->BuildGenerator = allArgs[idx]; - } - if(currentArg == "--build-generator-toolset" && - idx < allArgs.size() - 1) - { + } + if (currentArg == "--build-generator-platform" && idx < allArgs.size() - 1) { + idx++; + this->BuildGeneratorPlatform = allArgs[idx]; + } + if (currentArg == "--build-generator-toolset" && idx < allArgs.size() - 1) { idx++; this->BuildGeneratorToolset = allArgs[idx]; - } - if(currentArg.find("--build-project",0) == 0 && idx < allArgs.size() - 1) - { + } + if (currentArg.find("--build-project", 0) == 0 && idx < allArgs.size() - 1) { idx++; this->BuildProject = allArgs[idx]; - } - if(currentArg.find("--build-makeprogram",0) == 0 && - idx < allArgs.size() - 1) - { + } + if (currentArg.find("--build-makeprogram", 0) == 0 && + idx < allArgs.size() - 1) { idx++; this->BuildMakeProgram = allArgs[idx]; - } - if(currentArg.find("--build-config-sample",0) == 0 && - idx < allArgs.size() - 1) - { + } + if (currentArg.find("--build-config-sample", 0) == 0 && + idx < allArgs.size() - 1) { idx++; this->ConfigSample = allArgs[idx]; - } - if(currentArg.find("--build-noclean",0) == 0) - { + } + if (currentArg.find("--build-noclean", 0) == 0) { this->BuildNoClean = true; - } - if(currentArg.find("--build-options",0) == 0 && idx < allArgs.size() - 1) - { - ++idx; - bool done = false; - while(idx < allArgs.size() && !done) - { + } + if (currentArg.find("--build-options", 0) == 0) { + while (idx + 1 < allArgs.size() && allArgs[idx + 1] != "--build-target" && + allArgs[idx + 1] != "--test-command") { + ++idx; this->BuildOptions.push_back(allArgs[idx]); - if(idx+1 < allArgs.size() - && (allArgs[idx+1] == "--build-target" || - allArgs[idx+1] == "--test-command")) - { - done = true; - } - else - { - ++idx; - } - } } - if(currentArg.find("--test-command",0) == 0 && idx < allArgs.size() - 1) - { + } + if (currentArg.find("--test-command", 0) == 0 && idx < allArgs.size() - 1) { ++idx; this->TestCommand = allArgs[idx]; - while(idx+1 < allArgs.size()) - { + while (idx + 1 < allArgs.size()) { ++idx; this->TestCommandArgs.push_back(allArgs[idx]); - } } + } return 1; } - diff --git a/Source/CTest/cmCTestBuildAndTestHandler.h b/Source/CTest/cmCTestBuildAndTestHandler.h index ca50c6452..2e85e9fed 100644 --- a/Source/CTest/cmCTestBuildAndTestHandler.h +++ b/Source/CTest/cmCTestBuildAndTestHandler.h @@ -1,21 +1,16 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestBuildAndTestHandler_h #define cmCTestBuildAndTestHandler_h +#include "cmConfigure.h" #include "cmCTestGenericHandler.h" -#include "cmListFileCache.h" + +#include <sstream> +#include <stddef.h> +#include <string> +#include <vector> class cmake; @@ -26,17 +21,17 @@ class cmake; class cmCTestBuildAndTestHandler : public cmCTestGenericHandler { public: - cmTypeMacro(cmCTestBuildAndTestHandler, cmCTestGenericHandler); + typedef cmCTestGenericHandler Superclass; /* * The main entry point for this class */ - int ProcessHandler(); + int ProcessHandler() CM_OVERRIDE; //! Set all the build and test arguments - virtual int ProcessCommandLineArguments( - const std::string& currentArg, size_t& idx, - const std::vector<std::string>& allArgs); + int ProcessCommandLineArguments(const std::string& currentArg, size_t& idx, + const std::vector<std::string>& allArgs) + CM_OVERRIDE; /* * Get the output variable @@ -45,35 +40,34 @@ public: cmCTestBuildAndTestHandler(); - virtual void Initialize(); + void Initialize() CM_OVERRIDE; protected: ///! Run CMake and build a test and then run it as a single test. int RunCMakeAndTest(std::string* output); - int RunCMake(std::string* outstring, cmOStringStream &out, - std::string &cmakeOutString, - std::string &cwd, cmake *cm); + int RunCMake(std::string* outstring, std::ostringstream& out, + std::string& cmakeOutString, cmake* cm); - cmStdString Output; + std::string Output; - std::string BuildGenerator; - std::string BuildGeneratorToolset; + std::string BuildGenerator; + std::string BuildGeneratorPlatform; + std::string BuildGeneratorToolset; std::vector<std::string> BuildOptions; - bool BuildTwoConfig; - std::string BuildMakeProgram; - std::string ConfigSample; - std::string SourceDir; - std::string BinaryDir; - std::string BuildProject; - std::string TestCommand; - bool BuildNoClean; - std::string BuildRunDir; - std::string ExecutableDirectory; + bool BuildTwoConfig; + std::string BuildMakeProgram; + std::string ConfigSample; + std::string SourceDir; + std::string BinaryDir; + std::string BuildProject; + std::string TestCommand; + bool BuildNoClean; + std::string BuildRunDir; + std::string ExecutableDirectory; std::vector<std::string> TestCommandArgs; std::vector<std::string> BuildTargets; - bool BuildNoCMake; - double Timeout; + bool BuildNoCMake; + double Timeout; }; #endif - diff --git a/Source/CTest/cmCTestBuildCommand.cxx b/Source/CTest/cmCTestBuildCommand.cxx index 1f63185c1..4c8276007 100644 --- a/Source/CTest/cmCTestBuildCommand.cxx +++ b/Source/CTest/cmCTestBuildCommand.cxx @@ -1,71 +1,60 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestBuildCommand.h" #include "cmCTest.h" -#include "cmCTestGenericHandler.h" #include "cmCTestBuildHandler.h" -#include "cmake.h" +#include "cmCTestGenericHandler.h" #include "cmGlobalGenerator.h" +#include "cmMakefile.h" +#include "cmSystemTools.h" +#include "cmake.h" +#include <sstream> +#include <string.h> + +class cmExecutionStatus; -//---------------------------------------------------------------------------- cmCTestBuildCommand::cmCTestBuildCommand() { - this->GlobalGenerator = 0; + this->GlobalGenerator = CM_NULLPTR; this->Arguments[ctb_NUMBER_ERRORS] = "NUMBER_ERRORS"; this->Arguments[ctb_NUMBER_WARNINGS] = "NUMBER_WARNINGS"; this->Arguments[ctb_TARGET] = "TARGET"; this->Arguments[ctb_CONFIGURATION] = "CONFIGURATION"; this->Arguments[ctb_FLAGS] = "FLAGS"; this->Arguments[ctb_PROJECT_NAME] = "PROJECT_NAME"; - this->Arguments[ctb_LAST] = 0; + this->Arguments[ctb_LAST] = CM_NULLPTR; this->Last = ctb_LAST; } -//---------------------------------------------------------------------------- cmCTestBuildCommand::~cmCTestBuildCommand() { - if ( this->GlobalGenerator ) - { + if (this->GlobalGenerator) { delete this->GlobalGenerator; - this->GlobalGenerator = 0; - } + this->GlobalGenerator = CM_NULLPTR; + } } -//---------------------------------------------------------------------------- cmCTestGenericHandler* cmCTestBuildCommand::InitializeHandler() { - cmCTestGenericHandler* handler - = this->CTest->GetInitializedHandler("build"); - if ( !handler ) - { + cmCTestGenericHandler* handler = this->CTest->GetInitializedHandler("build"); + if (!handler) { this->SetError("internal CTest error. Cannot instantiate build handler"); - return 0; - } - this->Handler = (cmCTestBuildHandler*)handler; - - const char* ctestBuildCommand - = this->Makefile->GetDefinition("CTEST_BUILD_COMMAND"); - if ( ctestBuildCommand && *ctestBuildCommand ) - { - this->CTest->SetCTestConfiguration("MakeCommand", ctestBuildCommand); - } - else - { - const char* cmakeGeneratorName - = this->Makefile->GetDefinition("CTEST_CMAKE_GENERATOR"); - const char* cmakeProjectName - = (this->Values[ctb_PROJECT_NAME] && *this->Values[ctb_PROJECT_NAME]) + return CM_NULLPTR; + } + this->Handler = (cmCTestBuildHandler*)handler; + + const char* ctestBuildCommand = + this->Makefile->GetDefinition("CTEST_BUILD_COMMAND"); + if (ctestBuildCommand && *ctestBuildCommand) { + this->CTest->SetCTestConfiguration("MakeCommand", ctestBuildCommand, + this->Quiet); + } else { + const char* cmakeGeneratorName = + this->Makefile->GetDefinition("CTEST_CMAKE_GENERATOR"); + const char* cmakeProjectName = + (this->Values[ctb_PROJECT_NAME] && *this->Values[ctb_PROJECT_NAME]) ? this->Values[ctb_PROJECT_NAME] : this->Makefile->GetDefinition("CTEST_PROJECT_NAME"); @@ -74,77 +63,73 @@ cmCTestGenericHandler* cmCTestBuildCommand::InitializeHandler() // CTEST_CONFIGURATION_TYPE script variable, or ctest -C command // line argument... in that order. // - const char* ctestBuildConfiguration - = this->Makefile->GetDefinition("CTEST_BUILD_CONFIGURATION"); - const char* cmakeBuildConfiguration - = (this->Values[ctb_CONFIGURATION] && *this->Values[ctb_CONFIGURATION]) + const char* ctestBuildConfiguration = + this->Makefile->GetDefinition("CTEST_BUILD_CONFIGURATION"); + const char* cmakeBuildConfiguration = + (this->Values[ctb_CONFIGURATION] && *this->Values[ctb_CONFIGURATION]) ? this->Values[ctb_CONFIGURATION] : ((ctestBuildConfiguration && *ctestBuildConfiguration) - ? ctestBuildConfiguration - : this->CTest->GetConfigType().c_str()); + ? ctestBuildConfiguration + : this->CTest->GetConfigType().c_str()); - const char* cmakeBuildAdditionalFlags - = (this->Values[ctb_FLAGS] && *this->Values[ctb_FLAGS]) + const char* cmakeBuildAdditionalFlags = + (this->Values[ctb_FLAGS] && *this->Values[ctb_FLAGS]) ? this->Values[ctb_FLAGS] : this->Makefile->GetDefinition("CTEST_BUILD_FLAGS"); - const char* cmakeBuildTarget - = (this->Values[ctb_TARGET] && *this->Values[ctb_TARGET]) + const char* cmakeBuildTarget = + (this->Values[ctb_TARGET] && *this->Values[ctb_TARGET]) ? this->Values[ctb_TARGET] : this->Makefile->GetDefinition("CTEST_BUILD_TARGET"); - if ( cmakeGeneratorName && *cmakeGeneratorName && - cmakeProjectName && *cmakeProjectName ) - { - if ( !cmakeBuildConfiguration ) - { + if (cmakeGeneratorName && *cmakeGeneratorName && cmakeProjectName && + *cmakeProjectName) { + if (!cmakeBuildConfiguration) { cmakeBuildConfiguration = "Release"; - } - if ( this->GlobalGenerator ) - { - if ( strcmp(this->GlobalGenerator->GetName(), - cmakeGeneratorName) != 0 ) - { + } + if (this->GlobalGenerator) { + if (this->GlobalGenerator->GetName() != cmakeGeneratorName) { delete this->GlobalGenerator; - this->GlobalGenerator = 0; - } + this->GlobalGenerator = CM_NULLPTR; } - if ( !this->GlobalGenerator ) - { + } + if (!this->GlobalGenerator) { this->GlobalGenerator = this->Makefile->GetCMakeInstance()->CreateGlobalGenerator( cmakeGeneratorName); + if (!this->GlobalGenerator) { + std::string e = "could not create generator named \""; + e += cmakeGeneratorName; + e += "\""; + this->Makefile->IssueMessage(cmake::FATAL_ERROR, e); + cmSystemTools::SetFatalErrorOccured(); + return CM_NULLPTR; } - this->GlobalGenerator->FindMakeProgram(this->Makefile); - const char* cmakeMakeProgram - = this->Makefile->GetDefinition("CMAKE_MAKE_PROGRAM"); - if(strlen(cmakeBuildConfiguration) == 0) - { - const char* config = 0; + } + if (strlen(cmakeBuildConfiguration) == 0) { + const char* config = CM_NULLPTR; #ifdef CMAKE_INTDIR config = CMAKE_INTDIR; #endif - if(!config) - { + if (!config) { config = "Debug"; - } - cmakeBuildConfiguration = config; } + cmakeBuildConfiguration = config; + } std::string dir = this->CTest->GetCTestConfiguration("BuildDirectory"); - std::string buildCommand - = this->GlobalGenerator-> - GenerateBuildCommand(cmakeMakeProgram, - cmakeProjectName, dir.c_str(), - cmakeBuildAdditionalFlags, cmakeBuildTarget, - cmakeBuildConfiguration, true, false); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "SetMakeCommand:" - << buildCommand.c_str() << "\n"); - this->CTest->SetCTestConfiguration("MakeCommand", buildCommand.c_str()); - } - else - { - cmOStringStream ostr; + std::string buildCommand = + this->GlobalGenerator->GenerateCMakeBuildCommand( + cmakeBuildTarget ? cmakeBuildTarget : "", cmakeBuildConfiguration, + cmakeBuildAdditionalFlags ? cmakeBuildAdditionalFlags : "", + this->Makefile->IgnoreErrorsCMP0061()); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "SetMakeCommand:" << buildCommand << "\n", + this->Quiet); + this->CTest->SetCTestConfiguration("MakeCommand", buildCommand.c_str(), + this->Quiet); + } else { + std::ostringstream ostr; + /* clang-format off */ ostr << "has no project to build. If this is a " "\"built with CMake\" project, verify that CTEST_CMAKE_GENERATOR " "and CTEST_PROJECT_NAME are set." @@ -156,39 +141,38 @@ cmCTestGenericHandler* cmCTestBuildCommand::InitializeHandler() "\n" "Alternatively, set CTEST_BUILD_COMMAND to build the project " "with a custom command line."; - this->SetError(ostr.str().c_str()); - return 0; - } + /* clang-format on */ + this->SetError(ostr.str()); + return CM_NULLPTR; } + } - if(const char* useLaunchers = - this->Makefile->GetDefinition("CTEST_USE_LAUNCHERS")) - { - this->CTest->SetCTestConfiguration("UseLaunchers", useLaunchers); - } + if (const char* useLaunchers = + this->Makefile->GetDefinition("CTEST_USE_LAUNCHERS")) { + this->CTest->SetCTestConfiguration("UseLaunchers", useLaunchers, + this->Quiet); + } + handler->SetQuiet(this->Quiet); return handler; } - bool cmCTestBuildCommand::InitialPass(std::vector<std::string> const& args, - cmExecutionStatus &status) + cmExecutionStatus& status) { - bool ret = cmCTestHandlerCommand::InitialPass(args, status); - if ( this->Values[ctb_NUMBER_ERRORS] && *this->Values[ctb_NUMBER_ERRORS]) - { - cmOStringStream str; + bool ret = cmCTestHandlerCommand::InitialPass(args, status); + if (this->Values[ctb_NUMBER_ERRORS] && *this->Values[ctb_NUMBER_ERRORS]) { + std::ostringstream str; str << this->Handler->GetTotalErrors(); - this->Makefile->AddDefinition( - this->Values[ctb_NUMBER_ERRORS], str.str().c_str()); - } - if ( this->Values[ctb_NUMBER_WARNINGS] - && *this->Values[ctb_NUMBER_WARNINGS]) - { - cmOStringStream str; + this->Makefile->AddDefinition(this->Values[ctb_NUMBER_ERRORS], + str.str().c_str()); + } + if (this->Values[ctb_NUMBER_WARNINGS] && + *this->Values[ctb_NUMBER_WARNINGS]) { + std::ostringstream str; str << this->Handler->GetTotalWarnings(); - this->Makefile->AddDefinition( - this->Values[ctb_NUMBER_WARNINGS], str.str().c_str()); - } + this->Makefile->AddDefinition(this->Values[ctb_NUMBER_WARNINGS], + str.str().c_str()); + } return ret; } diff --git a/Source/CTest/cmCTestBuildCommand.h b/Source/CTest/cmCTestBuildCommand.h index cabc39b32..9cf6a9652 100644 --- a/Source/CTest/cmCTestBuildCommand.h +++ b/Source/CTest/cmCTestBuildCommand.h @@ -1,21 +1,20 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestBuildCommand_h #define cmCTestBuildCommand_h +#include "cmConfigure.h" + #include "cmCTestHandlerCommand.h" -class cmGlobalGenerator; +#include <string> +#include <vector> + class cmCTestBuildHandler; +class cmCTestGenericHandler; +class cmCommand; +class cmExecutionStatus; +class cmGlobalGenerator; /** \class cmCTestBuild * \brief Run a ctest script @@ -25,62 +24,34 @@ class cmCTestBuildHandler; class cmCTestBuildCommand : public cmCTestHandlerCommand { public: - cmCTestBuildCommand(); - ~cmCTestBuildCommand(); + ~cmCTestBuildCommand() CM_OVERRIDE; /** * This is a virtual constructor for the command. */ - virtual cmCommand* Clone() - { + cmCommand* Clone() CM_OVERRIDE + { cmCTestBuildCommand* ni = new cmCTestBuildCommand; ni->CTest = this->CTest; ni->CTestScriptHandler = this->CTestScriptHandler; return ni; - } + } /** * The name of the command as specified in CMakeList.txt. */ - virtual const char* GetName() const { return "ctest_build";} + std::string GetName() const CM_OVERRIDE { return "ctest_build"; } - /** - * Succinct documentation. - */ - virtual const char* GetTerseDocumentation() const - { - return "Build the project."; - } - virtual bool InitialPass(std::vector<std::string> const& args, - cmExecutionStatus &status); - /** - * More documentation. - */ - virtual const char* GetFullDocumentation() const - { - return - " ctest_build([BUILD build_dir] [TARGET target] [RETURN_VALUE res]\n" - " [APPEND][NUMBER_ERRORS val] [NUMBER_WARNINGS val])\n" - "Builds the given build directory and stores results in Build.xml. " - "If no BUILD is given, the CTEST_BINARY_DIRECTORY variable is used.\n" - "The TARGET variable can be used to specify a build target. If none " - "is specified, the \"all\" target will be built.\n" - "The RETURN_VALUE option specifies a variable in which to store the " - "return value of the native build tool. " - "The NUMBER_ERRORS and NUMBER_WARNINGS options specify variables in " - "which to store the number of build errors and warnings detected." - "\n" - CTEST_COMMAND_APPEND_OPTION_DOCS; - } - - cmTypeMacro(cmCTestBuildCommand, cmCTestHandlerCommand); + bool InitialPass(std::vector<std::string> const& args, + cmExecutionStatus& status) CM_OVERRIDE; cmGlobalGenerator* GlobalGenerator; protected: cmCTestBuildHandler* Handler; - enum { + enum + { ctb_BUILD = ct_LAST, ctb_NUMBER_ERRORS, ctb_NUMBER_WARNINGS, @@ -91,8 +62,7 @@ protected: ctb_LAST }; - cmCTestGenericHandler* InitializeHandler(); + cmCTestGenericHandler* InitializeHandler() CM_OVERRIDE; }; - #endif diff --git a/Source/CTest/cmCTestBuildHandler.cxx b/Source/CTest/cmCTestBuildHandler.cxx index 39eeb70a3..18ef05c67 100644 --- a/Source/CTest/cmCTestBuildHandler.cxx +++ b/Source/CTest/cmCTestBuildHandler.cxx @@ -1,43 +1,22 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestBuildHandler.h" +#include "cmAlgorithms.h" #include "cmCTest.h" -#include "cmake.h" -#include "cmMakefile.h" -#include "cmLocalGenerator.h" -#include "cmGlobalGenerator.h" -#include "cmGeneratedFileStream.h" -#include "cmXMLSafe.h" #include "cmFileTimeComparison.h" - -//#include <cmsys/RegularExpression.hxx> -#include <cmsys/Process.h> -#include <cmsys/Directory.hxx> - -// used for sleep -#ifdef _WIN32 -#include "windows.h" -#endif - +#include "cmGeneratedFileStream.h" +#include "cmMakefile.h" +#include "cmProcessOutput.h" +#include "cmSystemTools.h" +#include "cmXMLWriter.h" + +#include "cmsys/Directory.hxx" +#include "cmsys/FStream.hxx" +#include "cmsys/Process.h" +#include <set> #include <stdlib.h> -#include <time.h> -#include <math.h> -#include <float.h> - -#if defined(__BORLANDC__) -# pragma warn -8060 /* possibly incorrect assignment */ -#endif +#include <string.h> static const char* cmCTestErrorMatches[] = { "^[Bb]us [Ee]rror", @@ -69,13 +48,13 @@ static const char* cmCTestErrorMatches[] = { "^CMake Error.*:", ":[ \\t]cannot find", ":[ \\t]can't find", - ": \\*\\*\\* No rule to make target \\`.*\\'. Stop", + ": \\*\\*\\* No rule to make target [`'].*\\'. Stop", ": \\*\\*\\* No targets specified and no makefile found", ": Invalid loader fixup for symbol", ": Invalid fixups exist", ": Can't find library for", ": internal link edit command failed", - ": Unrecognized option \\`.*\\'", + ": Unrecognized option [`'].*\\'", "\", line [0-9]+\\.[0-9]+: [0-9]+-[0-9]+ \\([^WI]\\)", "ld: 0706-006 Cannot find or open library file: -l ", "ild: \\(argument error\\) can't find library argument ::", @@ -95,7 +74,7 @@ static const char* cmCTestErrorMatches[] = { "^The project cannot be built\\.", "^\\[ERROR\\]", "^Command .* failed with exit code", - 0 + CM_NULLPTR }; static const char* cmCTestErrorExceptions[] = { @@ -104,12 +83,13 @@ static const char* cmCTestErrorExceptions[] = { ": warning", ": \\(Warning\\)", ": note", + "Note:", "makefile:", "Makefile:", ":[ \\t]+Where:", "([^ :]+):([0-9]+): Warning", "------ Build started: .* ------", - 0 + CM_NULLPTR }; static const char* cmCTestWarningMatches[] = { @@ -134,7 +114,7 @@ static const char* cmCTestWarningMatches[] = { "cc-[0-9]* CC: REMARK File = .*, Line = [0-9]*", "^CMake Warning.*:", "^\\[WARNING\\]", - 0 + CM_NULLPTR }; static const char* cmCTestWarningExceptions[] = { @@ -154,7 +134,7 @@ static const char* cmCTestWarningExceptions[] = { "ld32: WARNING 85: definition of dataKey in", "cc: warning 422: Unknown option \"\\+b", "_with_warning_C", - 0 + CM_NULLPTR }; struct cmCTestBuildCompileErrorWarningRex @@ -164,19 +144,17 @@ struct cmCTestBuildCompileErrorWarningRex int LineIndex; }; -static cmCTestBuildCompileErrorWarningRex -cmCTestWarningErrorFileLine[] = { - { "^Warning W[0-9]+ ([a-zA-Z.\\:/0-9_+ ~-]+) ([0-9]+):", 1, 2 }, - { "^([a-zA-Z./0-9_+ ~-]+):([0-9]+):", 1, 2 }, - { "^([a-zA-Z.\\:/0-9_+ ~-]+)\\(([0-9]+)\\)", 1, 2 }, - { "^[0-9]+>([a-zA-Z.\\:/0-9_+ ~-]+)\\(([0-9]+)\\)", 1, 2 }, - { "^([a-zA-Z./0-9_+ ~-]+)\\(([0-9]+)\\)", 1, 2 }, - { "\"([a-zA-Z./0-9_+ ~-]+)\", line ([0-9]+)", 1, 2 }, - { "File = ([a-zA-Z./0-9_+ ~-]+), Line = ([0-9]+)", 1, 2 }, - { 0, 0, 0 } +static cmCTestBuildCompileErrorWarningRex cmCTestWarningErrorFileLine[] = { + { "^Warning W[0-9]+ ([a-zA-Z.\\:/0-9_+ ~-]+) ([0-9]+):", 1, 2 }, + { "^([a-zA-Z./0-9_+ ~-]+):([0-9]+):", 1, 2 }, + { "^([a-zA-Z.\\:/0-9_+ ~-]+)\\(([0-9]+)\\)", 1, 2 }, + { "^[0-9]+>([a-zA-Z.\\:/0-9_+ ~-]+)\\(([0-9]+)\\)", 1, 2 }, + { "^([a-zA-Z./0-9_+ ~-]+)\\(([0-9]+)\\)", 1, 2 }, + { "\"([a-zA-Z./0-9_+ ~-]+)\", line ([0-9]+)", 1, 2 }, + { "File = ([a-zA-Z./0-9_+ ~-]+), Line = ([0-9]+)", 1, 2 }, + { CM_NULLPTR, 0, 0 } }; -//---------------------------------------------------------------------- cmCTestBuildHandler::cmCTestBuildHandler() { this->MaxPreContext = 10; @@ -190,7 +168,6 @@ cmCTestBuildHandler::cmCTestBuildHandler() this->UseCTestLaunch = false; } -//---------------------------------------------------------------------- void cmCTestBuildHandler::Initialize() { this->Superclass::Initialize(); @@ -236,133 +213,113 @@ void cmCTestBuildHandler::Initialize() this->UseCTestLaunch = false; } -//---------------------------------------------------------------------- -void cmCTestBuildHandler::PopulateCustomVectors(cmMakefile *mf) +void cmCTestBuildHandler::PopulateCustomVectors(cmMakefile* mf) { this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_ERROR_MATCH", - this->CustomErrorMatches); + this->CustomErrorMatches); this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_ERROR_EXCEPTION", - this->CustomErrorExceptions); + this->CustomErrorExceptions); this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_WARNING_MATCH", - this->CustomWarningMatches); + this->CustomWarningMatches); this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_WARNING_EXCEPTION", - this->CustomWarningExceptions); - this->CTest->PopulateCustomInteger(mf, - "CTEST_CUSTOM_MAXIMUM_NUMBER_OF_ERRORS", - this->MaxErrors); - this->CTest->PopulateCustomInteger(mf, - "CTEST_CUSTOM_MAXIMUM_NUMBER_OF_WARNINGS", - this->MaxWarnings); + this->CustomWarningExceptions); + this->CTest->PopulateCustomInteger( + mf, "CTEST_CUSTOM_MAXIMUM_NUMBER_OF_ERRORS", this->MaxErrors); + this->CTest->PopulateCustomInteger( + mf, "CTEST_CUSTOM_MAXIMUM_NUMBER_OF_WARNINGS", this->MaxWarnings); int n = -1; this->CTest->PopulateCustomInteger(mf, "CTEST_CUSTOM_ERROR_PRE_CONTEXT", n); - if (n != -1) - { + if (n != -1) { this->MaxPreContext = static_cast<size_t>(n); - } + } n = -1; this->CTest->PopulateCustomInteger(mf, "CTEST_CUSTOM_ERROR_POST_CONTEXT", n); - if (n != -1) - { + if (n != -1) { this->MaxPostContext = static_cast<size_t>(n); - } + } // Record the user-specified custom warning rules. - if(const char* customWarningMatchers = - mf->GetDefinition("CTEST_CUSTOM_WARNING_MATCH")) - { + if (const char* customWarningMatchers = + mf->GetDefinition("CTEST_CUSTOM_WARNING_MATCH")) { cmSystemTools::ExpandListArgument(customWarningMatchers, this->ReallyCustomWarningMatches); - } - if(const char* customWarningExceptions = - mf->GetDefinition("CTEST_CUSTOM_WARNING_EXCEPTION")) - { + } + if (const char* customWarningExceptions = + mf->GetDefinition("CTEST_CUSTOM_WARNING_EXCEPTION")) { cmSystemTools::ExpandListArgument(customWarningExceptions, this->ReallyCustomWarningExceptions); - } + } } -//---------------------------------------------------------------------- std::string cmCTestBuildHandler::GetMakeCommand() { - std::string makeCommand - = this->CTest->GetCTestConfiguration("MakeCommand"); - cmCTestLog(this->CTest, - HANDLER_VERBOSE_OUTPUT, "MakeCommand:" << makeCommand << - "\n"); + std::string makeCommand = this->CTest->GetCTestConfiguration("MakeCommand"); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "MakeCommand:" << makeCommand << "\n", this->Quiet); std::string configType = this->CTest->GetConfigType(); - if (configType == "") - { - configType - = this->CTest->GetCTestConfiguration("DefaultCTestConfigurationType"); - } - if (configType == "") - { + if (configType == "") { + configType = + this->CTest->GetCTestConfiguration("DefaultCTestConfigurationType"); + } + if (configType == "") { configType = "Release"; - } + } - cmSystemTools::ReplaceString(makeCommand, - "${CTEST_CONFIGURATION_TYPE}", configType.c_str()); + cmSystemTools::ReplaceString(makeCommand, "${CTEST_CONFIGURATION_TYPE}", + configType.c_str()); return makeCommand; } -//---------------------------------------------------------------------- -//clearly it would be nice if this were broken up into a few smaller -//functions and commented... +// clearly it would be nice if this were broken up into a few smaller +// functions and commented... int cmCTestBuildHandler::ProcessHandler() { - cmCTestLog(this->CTest, HANDLER_OUTPUT, "Build project" << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "Build project" << std::endl, + this->Quiet); // do we have time for this - if (this->CTest->GetRemainingTimeAllowed() < 120) - { + if (this->CTest->GetRemainingTimeAllowed() < 120) { return 0; - } + } int entry; - for ( entry = 0; - cmCTestWarningErrorFileLine[entry].RegularExpressionString; - ++ entry ) - { + for (entry = 0; cmCTestWarningErrorFileLine[entry].RegularExpressionString; + ++entry) { cmCTestBuildHandler::cmCTestCompileErrorWarningRex r; - if ( r.RegularExpression.compile( - cmCTestWarningErrorFileLine[entry].RegularExpressionString) ) - { + if (r.RegularExpression.compile( + cmCTestWarningErrorFileLine[entry].RegularExpressionString)) { r.FileIndex = cmCTestWarningErrorFileLine[entry].FileIndex; r.LineIndex = cmCTestWarningErrorFileLine[entry].LineIndex; this->ErrorWarningFileLineRegex.push_back(r); - } - else - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Problem Compiling regular expression: " - << cmCTestWarningErrorFileLine[entry].RegularExpressionString - << std::endl); - } + } else { + cmCTestLog( + this->CTest, ERROR_MESSAGE, "Problem Compiling regular expression: " + << cmCTestWarningErrorFileLine[entry].RegularExpressionString + << std::endl); } + } // Determine build command and build directory std::string makeCommand = this->GetMakeCommand(); - if ( makeCommand.size() == 0 ) - { + if (makeCommand.empty()) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot find MakeCommand key in the DartConfiguration.tcl" - << std::endl); + "Cannot find MakeCommand key in the DartConfiguration.tcl" + << std::endl); return -1; - } + } - const std::string &buildDirectory - = this->CTest->GetCTestConfiguration("BuildDirectory"); - if ( buildDirectory.size() == 0 ) - { + 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); + "Cannot find BuildDirectory key in the DartConfiguration.tcl" + << std::endl); return -1; - } + } std::string const& useLaunchers = this->CTest->GetCTestConfiguration("UseLaunchers"); @@ -371,94 +328,81 @@ int cmCTestBuildHandler::ProcessHandler() // Create a last build log cmGeneratedFileStream ofs; double elapsed_time_start = cmSystemTools::GetTime(); - if ( !this->StartLogFile("Build", ofs) ) - { + if (!this->StartLogFile("Build", ofs)) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot create build log file" - << std::endl); - } + << std::endl); + } // Create lists of regular expression strings for errors, error exceptions, // warnings and warning exceptions. - std::vector<cmStdString>::size_type cc; - for ( cc = 0; cmCTestErrorMatches[cc]; cc ++ ) - { + std::vector<std::string>::size_type cc; + for (cc = 0; cmCTestErrorMatches[cc]; cc++) { this->CustomErrorMatches.push_back(cmCTestErrorMatches[cc]); - } - for ( cc = 0; cmCTestErrorExceptions[cc]; cc ++ ) - { + } + for (cc = 0; cmCTestErrorExceptions[cc]; cc++) { this->CustomErrorExceptions.push_back(cmCTestErrorExceptions[cc]); - } - for ( cc = 0; cmCTestWarningMatches[cc]; cc ++ ) - { + } + for (cc = 0; cmCTestWarningMatches[cc]; cc++) { this->CustomWarningMatches.push_back(cmCTestWarningMatches[cc]); - } + } - for ( cc = 0; cmCTestWarningExceptions[cc]; cc ++ ) - { + for (cc = 0; cmCTestWarningExceptions[cc]; cc++) { this->CustomWarningExceptions.push_back(cmCTestWarningExceptions[cc]); - } + } // Pre-compile regular expressions objects for all regular expressions - std::vector<cmStdString>::iterator it; - -#define cmCTestBuildHandlerPopulateRegexVector(strings, regexes) \ - regexes.clear(); \ - cmCTestLog(this->CTest, DEBUG, this << "Add " #regexes \ - << std::endl); \ - for ( it = strings.begin(); it != strings.end(); ++it ) \ - { \ - cmCTestLog(this->CTest, DEBUG, "Add " #strings ": " \ - << it->c_str() << std::endl); \ - regexes.push_back(it->c_str()); \ - } - cmCTestBuildHandlerPopulateRegexVector( - this->CustomErrorMatches, this->ErrorMatchRegex); - cmCTestBuildHandlerPopulateRegexVector( - this->CustomErrorExceptions, this->ErrorExceptionRegex); - cmCTestBuildHandlerPopulateRegexVector( - this->CustomWarningMatches, this->WarningMatchRegex); - cmCTestBuildHandlerPopulateRegexVector( - this->CustomWarningExceptions, this->WarningExceptionRegex); - + std::vector<std::string>::iterator it; + +#define cmCTestBuildHandlerPopulateRegexVector(strings, regexes) \ + regexes.clear(); \ + cmCTestOptionalLog(this->CTest, DEBUG, \ + this << "Add " #regexes << std::endl, this->Quiet); \ + for (it = (strings).begin(); it != (strings).end(); ++it) { \ + cmCTestOptionalLog(this->CTest, DEBUG, \ + "Add " #strings ": " << *it << std::endl, \ + this->Quiet); \ + (regexes).push_back(it->c_str()); \ + } + cmCTestBuildHandlerPopulateRegexVector(this->CustomErrorMatches, + this->ErrorMatchRegex); + cmCTestBuildHandlerPopulateRegexVector(this->CustomErrorExceptions, + this->ErrorExceptionRegex); + cmCTestBuildHandlerPopulateRegexVector(this->CustomWarningMatches, + this->WarningMatchRegex); + cmCTestBuildHandlerPopulateRegexVector(this->CustomWarningExceptions, + this->WarningExceptionRegex); // Determine source and binary tree substitutions to simplify the output. this->SimplifySourceDir = ""; this->SimplifyBuildDir = ""; - if ( this->CTest->GetCTestConfiguration("SourceDirectory").size() > 20 ) - { - std::string srcdir - = this->CTest->GetCTestConfiguration("SourceDirectory") + "/"; + if (this->CTest->GetCTestConfiguration("SourceDirectory").size() > 20) { + std::string srcdir = + this->CTest->GetCTestConfiguration("SourceDirectory") + "/"; std::string srcdirrep; - for ( cc = srcdir.size()-2; cc > 0; cc -- ) - { - if ( srcdir[cc] == '/' ) - { + for (cc = srcdir.size() - 2; cc > 0; cc--) { + if (srcdir[cc] == '/') { srcdirrep = srcdir.c_str() + cc; srcdirrep = "/..." + srcdirrep; - srcdir = srcdir.substr(0, cc+1); + srcdir = srcdir.substr(0, cc + 1); break; - } } - this->SimplifySourceDir = srcdir; } - if ( this->CTest->GetCTestConfiguration("BuildDirectory").size() > 20 ) - { - std::string bindir - = this->CTest->GetCTestConfiguration("BuildDirectory") + "/"; + this->SimplifySourceDir = srcdir; + } + if (this->CTest->GetCTestConfiguration("BuildDirectory").size() > 20) { + std::string bindir = + this->CTest->GetCTestConfiguration("BuildDirectory") + "/"; std::string bindirrep; - for ( cc = bindir.size()-2; cc > 0; cc -- ) - { - if ( bindir[cc] == '/' ) - { + for (cc = bindir.size() - 2; cc > 0; cc--) { + if (bindir[cc] == '/') { bindirrep = bindir.c_str() + cc; bindirrep = "/..." + bindirrep; - bindir = bindir.substr(0, cc+1); + bindir = bindir.substr(0, cc + 1); break; - } } - this->SimplifyBuildDir = bindir; } - + this->SimplifyBuildDir = bindir; + } // Ok, let's do the build @@ -467,16 +411,14 @@ int cmCTestBuildHandler::ProcessHandler() this->StartBuildTime = cmSystemTools::GetTime(); int retVal = 0; int res = cmsysProcess_State_Exited; - if ( !this->CTest->GetShowOnly() ) - { + if (!this->CTest->GetShowOnly()) { res = this->RunMakeCommand(makeCommand.c_str(), &retVal, - buildDirectory.c_str(), 0, ofs); - } - else - { - cmCTestLog(this->CTest, DEBUG, "Build with command: " << makeCommand - << std::endl); - } + buildDirectory.c_str(), 0, ofs); + } else { + cmCTestOptionalLog(this->CTest, DEBUG, + "Build with command: " << makeCommand << std::endl, + this->Quiet); + } // Remember end build time and calculate elapsed time this->EndBuild = this->CTest->CurrentTime(); @@ -485,154 +427,141 @@ int cmCTestBuildHandler::ProcessHandler() // Cleanups strings in the errors and warnings list. t_ErrorsAndWarningsVector::iterator evit; - if ( !this->SimplifySourceDir.empty() ) - { - for ( evit = this->ErrorsAndWarnings.begin(); - evit != this->ErrorsAndWarnings.end(); - ++ evit ) - { - cmSystemTools::ReplaceString( - evit->Text, this->SimplifySourceDir.c_str(), "/.../"); - cmSystemTools::ReplaceString( - evit->PreContext, this->SimplifySourceDir.c_str(), "/.../"); - cmSystemTools::ReplaceString( - evit->PostContext, this->SimplifySourceDir.c_str(), "/.../"); - } + if (!this->SimplifySourceDir.empty()) { + for (evit = this->ErrorsAndWarnings.begin(); + evit != this->ErrorsAndWarnings.end(); ++evit) { + cmSystemTools::ReplaceString(evit->Text, this->SimplifySourceDir.c_str(), + "/.../"); + cmSystemTools::ReplaceString(evit->PreContext, + this->SimplifySourceDir.c_str(), "/.../"); + cmSystemTools::ReplaceString(evit->PostContext, + this->SimplifySourceDir.c_str(), "/.../"); } - - if ( !this->SimplifyBuildDir.empty() ) - { - for ( evit = this->ErrorsAndWarnings.begin(); - evit != this->ErrorsAndWarnings.end(); - ++ evit ) - { - cmSystemTools::ReplaceString( - evit->Text, this->SimplifyBuildDir.c_str(), "/.../"); - cmSystemTools::ReplaceString( - evit->PreContext, this->SimplifyBuildDir.c_str(), "/.../"); - cmSystemTools::ReplaceString( - evit->PostContext, this->SimplifyBuildDir.c_str(), "/.../"); - } + } + + if (!this->SimplifyBuildDir.empty()) { + for (evit = this->ErrorsAndWarnings.begin(); + evit != this->ErrorsAndWarnings.end(); ++evit) { + cmSystemTools::ReplaceString(evit->Text, this->SimplifyBuildDir.c_str(), + "/.../"); + cmSystemTools::ReplaceString(evit->PreContext, + this->SimplifyBuildDir.c_str(), "/.../"); + cmSystemTools::ReplaceString(evit->PostContext, + this->SimplifyBuildDir.c_str(), "/.../"); } + } // Generate XML output cmGeneratedFileStream xofs; - if(!this->StartResultingXML(cmCTest::PartBuild, "Build", xofs)) - { + if (!this->StartResultingXML(cmCTest::PartBuild, "Build", xofs)) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot create build XML file" - << std::endl); + << std::endl); return -1; - } - this->GenerateXMLHeader(xofs); - if(this->UseCTestLaunch) - { - this->GenerateXMLLaunched(xofs); - } - else - { - this->GenerateXMLLogScraped(xofs); - } - this->GenerateXMLFooter(xofs, elapsed_build_time); - - if (res != cmsysProcess_State_Exited || retVal || this->TotalErrors > 0) - { + } + cmXMLWriter xml(xofs); + this->GenerateXMLHeader(xml); + if (this->UseCTestLaunch) { + this->GenerateXMLLaunched(xml); + } else { + this->GenerateXMLLogScraped(xml); + } + this->GenerateXMLFooter(xml, elapsed_build_time); + + if (res != cmsysProcess_State_Exited || retVal || this->TotalErrors > 0) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Error(s) when building project" - << std::endl); - } + << std::endl); + } // Display message about number of errors and warnings - cmCTestLog(this->CTest, HANDLER_OUTPUT, " " << this->TotalErrors - << (this->TotalErrors >= this->MaxErrors ? " or more" : "") - << " Compiler errors" << std::endl); - cmCTestLog(this->CTest, HANDLER_OUTPUT, " " << this->TotalWarnings - << (this->TotalWarnings >= this->MaxWarnings ? " or more" : "") - << " Compiler warnings" << std::endl); + cmCTestLog(this->CTest, HANDLER_OUTPUT, " " + << this->TotalErrors + << (this->TotalErrors >= this->MaxErrors ? " or more" : "") + << " Compiler errors" << std::endl); + cmCTestLog(this->CTest, HANDLER_OUTPUT, " " + << this->TotalWarnings + << (this->TotalWarnings >= this->MaxWarnings ? " or more" : "") + << " Compiler warnings" << std::endl); return retVal; } -//---------------------------------------------------------------------------- -void cmCTestBuildHandler::GenerateXMLHeader(std::ostream& os) +void cmCTestBuildHandler::GenerateXMLHeader(cmXMLWriter& xml) { - this->CTest->StartXML(os, this->AppendXML); - os << "<Build>\n" - << "\t<StartDateTime>" << this->StartBuild << "</StartDateTime>\n" - << "\t<StartBuildTime>" << - static_cast<unsigned int>(this->StartBuildTime) - << "</StartBuildTime>\n" - << "<BuildCommand>" - << cmXMLSafe(this->GetMakeCommand()) - << "</BuildCommand>" << std::endl; + this->CTest->StartXML(xml, this->AppendXML); + xml.StartElement("Build"); + xml.Element("StartDateTime", this->StartBuild); + xml.Element("StartBuildTime", + static_cast<unsigned int>(this->StartBuildTime)); + xml.Element("BuildCommand", this->GetMakeCommand()); } -//---------------------------------------------------------------------------- class cmCTestBuildHandler::FragmentCompare { public: - FragmentCompare(cmFileTimeComparison* ftc): FTC(ftc) {} - FragmentCompare(): FTC(0) {} - bool operator()(std::string const& l, std::string const& r) - { + FragmentCompare(cmFileTimeComparison* ftc) + : FTC(ftc) + { + } + FragmentCompare() + : FTC(CM_NULLPTR) + { + } + bool operator()(std::string const& l, std::string const& r) const + { // Order files by modification time. Use lexicographic order // among files with the same time. int result; - if(this->FTC->FileTimeCompare(l.c_str(), r.c_str(), &result) && - result != 0) - { + if (this->FTC->FileTimeCompare(l.c_str(), r.c_str(), &result) && + result != 0) { return result < 0; - } - else - { - return l < r; - } } + return l < r; + } + private: cmFileTimeComparison* FTC; }; -//---------------------------------------------------------------------------- -void cmCTestBuildHandler::GenerateXMLLaunched(std::ostream& os) +void cmCTestBuildHandler::GenerateXMLLaunched(cmXMLWriter& xml) { - if(this->CTestLaunchDir.empty()) - { + if (this->CTestLaunchDir.empty()) { return; - } + } // Sort XML fragments in chronological order. cmFileTimeComparison ftc; FragmentCompare fragmentCompare(&ftc); - typedef std::set<cmStdString, FragmentCompare> Fragments; + typedef std::set<std::string, FragmentCompare> Fragments; Fragments fragments(fragmentCompare); + // only report the first 50 warnings and first 50 errors + int numErrorsAllowed = this->MaxErrors; + int numWarningsAllowed = this->MaxWarnings; // Identify fragments on disk. cmsys::Directory launchDir; - launchDir.Load(this->CTestLaunchDir.c_str()); + launchDir.Load(this->CTestLaunchDir); unsigned long n = launchDir.GetNumberOfFiles(); - for(unsigned long i=0; i < n; ++i) - { + for (unsigned long i = 0; i < n; ++i) { const char* fname = launchDir.GetFile(i); - if(this->IsLaunchedErrorFile(fname)) - { + if (this->IsLaunchedErrorFile(fname) && numErrorsAllowed) { + numErrorsAllowed--; fragments.insert(this->CTestLaunchDir + "/" + fname); ++this->TotalErrors; - } - else if(this->IsLaunchedWarningFile(fname)) - { + } else if (this->IsLaunchedWarningFile(fname) && numWarningsAllowed) { + numWarningsAllowed--; fragments.insert(this->CTestLaunchDir + "/" + fname); ++this->TotalWarnings; - } } + } // Copy the fragments into the final XML file. - for(Fragments::const_iterator fi = fragments.begin(); - fi != fragments.end(); ++fi) - { - this->GenerateXMLLaunchedFragment(os, fi->c_str()); - } + for (Fragments::const_iterator fi = fragments.begin(); fi != fragments.end(); + ++fi) { + xml.FragmentFile(fi->c_str()); + } } -//---------------------------------------------------------------------------- -void cmCTestBuildHandler::GenerateXMLLogScraped(std::ostream& os) +void cmCTestBuildHandler::GenerateXMLLogScraped(cmXMLWriter& xml) { std::vector<cmCTestBuildErrorWarning>& ew = this->ErrorsAndWarnings; std::vector<cmCTestBuildErrorWarning>::iterator it; @@ -643,136 +572,101 @@ void cmCTestBuildHandler::GenerateXMLLogScraped(std::ostream& os) std::string srcdir = this->CTest->GetCTestConfiguration("SourceDirectory"); // make sure the source dir is in the correct case on windows // via a call to collapse full path. - srcdir = cmSystemTools::CollapseFullPath(srcdir.c_str()); + srcdir = cmSystemTools::CollapseFullPath(srcdir); srcdir += "/"; - for ( it = ew.begin(); - it != ew.end() && (numErrorsAllowed || numWarningsAllowed); it++ ) - { - cmCTestBuildErrorWarning *cm = &(*it); + for (it = ew.begin(); + it != ew.end() && (numErrorsAllowed || numWarningsAllowed); it++) { + cmCTestBuildErrorWarning* cm = &(*it); if ((cm->Error && numErrorsAllowed) || - (!cm->Error && numWarningsAllowed)) - { - if (cm->Error) - { + (!cm->Error && numWarningsAllowed)) { + if (cm->Error) { numErrorsAllowed--; - } - else - { + } else { numWarningsAllowed--; - } - os << "\t<" << (cm->Error ? "Error" : "Warning") << ">\n" - << "\t\t<BuildLogLine>" << cm->LogLine << "</BuildLogLine>\n" - << "\t\t<Text>" << cmXMLSafe(cm->Text).Quotes(false) - << "\n</Text>" << std::endl; + } + xml.StartElement(cm->Error ? "Error" : "Warning"); + xml.Element("BuildLogLine", cm->LogLine); + xml.Element("Text", cm->Text); std::vector<cmCTestCompileErrorWarningRex>::iterator rit; - for ( rit = this->ErrorWarningFileLineRegex.begin(); - rit != this->ErrorWarningFileLineRegex.end(); ++ rit ) - { + for (rit = this->ErrorWarningFileLineRegex.begin(); + rit != this->ErrorWarningFileLineRegex.end(); ++rit) { cmsys::RegularExpression* re = &rit->RegularExpression; - if ( re->find(cm->Text.c_str() ) ) - { + if (re->find(cm->Text.c_str())) { cm->SourceFile = re->match(rit->FileIndex); // At this point we need to make this->SourceFile relative to // the source root of the project, so cvs links will work cmSystemTools::ConvertToUnixSlashes(cm->SourceFile); - if(cm->SourceFile.find("/.../") != cm->SourceFile.npos) - { + if (cm->SourceFile.find("/.../") != std::string::npos) { cmSystemTools::ReplaceString(cm->SourceFile, "/.../", ""); - std::string::size_type p = cm->SourceFile.find("/"); - if(p != cm->SourceFile.npos) - { - cm->SourceFile = cm->SourceFile.substr( - p+1, cm->SourceFile.size()-p); - } + std::string::size_type p = cm->SourceFile.find('/'); + if (p != std::string::npos) { + cm->SourceFile = + cm->SourceFile.substr(p + 1, cm->SourceFile.size() - p); } - else - { + } else { // make sure it is a full path with the correct case - cm->SourceFile = cmSystemTools::CollapseFullPath( - cm->SourceFile.c_str()); - cmSystemTools::ReplaceString( - cm->SourceFile, srcdir.c_str(), ""); - } + cm->SourceFile = cmSystemTools::CollapseFullPath(cm->SourceFile); + cmSystemTools::ReplaceString(cm->SourceFile, srcdir.c_str(), ""); + } cm->LineNumber = atoi(re->match(rit->LineIndex).c_str()); break; - } } - if ( !cm->SourceFile.empty() && cm->LineNumber >= 0 ) - { - if ( cm->SourceFile.size() > 0 ) - { - os << "\t\t<SourceFile>" << cm->SourceFile << "</SourceFile>" - << std::endl; - } - if ( cm->SourceFileTail.size() > 0 ) - { - os << "\t\t<SourceFileTail>" << cm->SourceFileTail - << "</SourceFileTail>" << std::endl; - } - if ( cm->LineNumber >= 0 ) - { - os << "\t\t<SourceLineNumber>" << cm->LineNumber - << "</SourceLineNumber>" << std::endl; - } + } + if (!cm->SourceFile.empty() && cm->LineNumber >= 0) { + if (!cm->SourceFile.empty()) { + xml.Element("SourceFile", cm->SourceFile); + } + if (!cm->SourceFileTail.empty()) { + xml.Element("SourceFileTail", cm->SourceFileTail); + } + if (cm->LineNumber >= 0) { + xml.Element("SourceLineNumber", cm->LineNumber); } - os << "\t\t<PreContext>" << cmXMLSafe(cm->PreContext).Quotes(false) - << "</PreContext>\n" - << "\t\t<PostContext>" << cmXMLSafe(cm->PostContext).Quotes(false); + } + xml.Element("PreContext", cm->PreContext); + xml.StartElement("PostContext"); + xml.Content(cm->PostContext); // is this the last warning or error, if so notify if ((cm->Error && !numErrorsAllowed) || - (!cm->Error && !numWarningsAllowed)) - { - os << "\nThe maximum number of reported warnings or errors has been " - "reached!!!\n"; - } - os << "</PostContext>\n" - << "\t\t<RepeatCount>0</RepeatCount>\n" - << "</" << (cm->Error ? "Error" : "Warning") << ">\n\n" - << std::endl; + (!cm->Error && !numWarningsAllowed)) { + xml.Content("\nThe maximum number of reported warnings or errors " + "has been reached!!!\n"); } + xml.EndElement(); // PostContext + xml.Element("RepeatCount", "0"); + xml.EndElement(); // "Error" / "Warning" } + } } -//---------------------------------------------------------------------------- -void cmCTestBuildHandler::GenerateXMLFooter(std::ostream& os, +void cmCTestBuildHandler::GenerateXMLFooter(cmXMLWriter& xml, double elapsed_build_time) { - os << "\t<Log Encoding=\"base64\" Compression=\"/bin/gzip\">\n\t</Log>\n" - << "\t<EndDateTime>" << this->EndBuild << "</EndDateTime>\n" - << "\t<EndBuildTime>" << static_cast<unsigned int>(this->EndBuildTime) - << "</EndBuildTime>\n" - << "<ElapsedMinutes>" << static_cast<int>(elapsed_build_time/6)/10.0 - << "</ElapsedMinutes>" - << "</Build>" << std::endl; - this->CTest->EndXML(os); -} - -//---------------------------------------------------------------------------- -void cmCTestBuildHandler::GenerateXMLLaunchedFragment(std::ostream& os, - const char* fname) -{ - std::ifstream fin(fname, std::ios::in | std::ios::binary); - std::string line; - while(cmSystemTools::GetLineFromStream(fin, line)) - { - os << line << "\n"; - } + xml.StartElement("Log"); + xml.Attribute("Encoding", "base64"); + xml.Attribute("Compression", "bin/gzip"); + xml.EndElement(); // Log + + xml.Element("EndDateTime", this->EndBuild); + xml.Element("EndBuildTime", static_cast<unsigned int>(this->EndBuildTime)); + xml.Element("ElapsedMinutes", + static_cast<int>(elapsed_build_time / 6) / 10.0); + xml.EndElement(); // Build + this->CTest->EndXML(xml); } -//---------------------------------------------------------------------------- bool cmCTestBuildHandler::IsLaunchedErrorFile(const char* fname) { // error-{hash}.xml - return (strncmp(fname, "error-", 6) == 0 && - strcmp(fname+strlen(fname)-4, ".xml") == 0); + return (cmHasLiteralPrefix(fname, "error-") && + strcmp(fname + strlen(fname) - 4, ".xml") == 0); } -//---------------------------------------------------------------------------- bool cmCTestBuildHandler::IsLaunchedWarningFile(const char* fname) { // warning-{hash}.xml - return (strncmp(fname, "warning-", 8) == 0 && - strcmp(fname+strlen(fname)-4, ".xml") == 0); + return (cmHasLiteralPrefix(fname, "warning-") && + strcmp(fname + strlen(fname) - 4, ".xml") == 0); } //###################################################################### @@ -780,12 +674,12 @@ bool cmCTestBuildHandler::IsLaunchedWarningFile(const char* fname) //###################################################################### //###################################################################### -//---------------------------------------------------------------------------- class cmCTestBuildHandler::LaunchHelper { public: LaunchHelper(cmCTestBuildHandler* handler); ~LaunchHelper(); + private: cmCTestBuildHandler* Handler; cmCTest* CTest; @@ -795,19 +689,16 @@ private: std::vector<std::string> const& matchers); }; -//---------------------------------------------------------------------------- -cmCTestBuildHandler::LaunchHelper::LaunchHelper(cmCTestBuildHandler* handler): - Handler(handler), CTest(handler->CTest) +cmCTestBuildHandler::LaunchHelper::LaunchHelper(cmCTestBuildHandler* handler) + : Handler(handler) + , CTest(handler->CTest) { std::string tag = this->CTest->GetCurrentTag(); - if(tag.empty()) - { + if (tag.empty()) { // This is not for a dashboard submission, so there is no XML. // Skip enabling the launchers. this->Handler->UseCTestLaunch = false; - } - else - { + } else { // Compute a directory in which to store launcher fragments. std::string& launchDir = this->Handler->CTestLaunchDir; launchDir = this->CTest->GetBinaryDir(); @@ -816,36 +707,31 @@ cmCTestBuildHandler::LaunchHelper::LaunchHelper(cmCTestBuildHandler* handler): launchDir += "/Build"; // Clean out any existing launcher fragments. - cmSystemTools::RemoveADirectory(launchDir.c_str()); + cmSystemTools::RemoveADirectory(launchDir); - if(this->Handler->UseCTestLaunch) - { + if (this->Handler->UseCTestLaunch) { // Enable launcher fragments. cmSystemTools::MakeDirectory(launchDir.c_str()); this->WriteLauncherConfig(); std::string launchEnv = "CTEST_LAUNCH_LOGS="; launchEnv += launchDir; - cmSystemTools::PutEnv(launchEnv.c_str()); - } + cmSystemTools::PutEnv(launchEnv); } + } // If not using launchers, make sure they passthru. - if(!this->Handler->UseCTestLaunch) - { + if (!this->Handler->UseCTestLaunch) { cmSystemTools::UnsetEnv("CTEST_LAUNCH_LOGS"); - } + } } -//---------------------------------------------------------------------------- cmCTestBuildHandler::LaunchHelper::~LaunchHelper() { - if(this->Handler->UseCTestLaunch) - { + if (this->Handler->UseCTestLaunch) { cmSystemTools::UnsetEnv("CTEST_LAUNCH_LOGS"); - } + } } -//---------------------------------------------------------------------------- void cmCTestBuildHandler::LaunchHelper::WriteLauncherConfig() { this->WriteScrapeMatchers("Warning", @@ -861,55 +747,50 @@ void cmCTestBuildHandler::LaunchHelper::WriteLauncherConfig() fout << "set(CTEST_SOURCE_DIRECTORY \"" << srcdir << "\")\n"; } -//---------------------------------------------------------------------------- -void -cmCTestBuildHandler::LaunchHelper -::WriteScrapeMatchers(const char* purpose, - std::vector<std::string> const& matchers) +void cmCTestBuildHandler::LaunchHelper::WriteScrapeMatchers( + const char* purpose, std::vector<std::string> const& matchers) { - if(matchers.empty()) - { + if (matchers.empty()) { return; - } + } std::string fname = this->Handler->CTestLaunchDir; fname += "/Custom"; fname += purpose; fname += ".txt"; cmGeneratedFileStream fout(fname.c_str()); - for(std::vector<std::string>::const_iterator mi = matchers.begin(); - mi != matchers.end(); ++mi) - { + for (std::vector<std::string>::const_iterator mi = matchers.begin(); + mi != matchers.end(); ++mi) { fout << *mi << "\n"; - } + } } -//---------------------------------------------------------------------- -int cmCTestBuildHandler::RunMakeCommand(const char* command, - int* retVal, const char* dir, int timeout, std::ofstream& ofs) +int cmCTestBuildHandler::RunMakeCommand(const char* command, int* retVal, + const char* dir, int timeout, + std::ostream& ofs, Encoding encoding) { // First generate the command and arguments - std::vector<cmStdString> args = cmSystemTools::ParseArguments(command); + std::vector<std::string> args = cmSystemTools::ParseArguments(command); - if(args.size() < 1) - { + if (args.empty()) { return false; - } + } std::vector<const char*> argv; - for(std::vector<cmStdString>::const_iterator a = args.begin(); - a != args.end(); ++a) - { + for (std::vector<std::string>::const_iterator a = args.begin(); + a != args.end(); ++a) { argv.push_back(a->c_str()); - } - argv.push_back(0); + } + argv.push_back(CM_NULLPTR); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Run command:"); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Run command:", + this->Quiet); std::vector<const char*>::iterator ait; - for ( ait = argv.begin(); ait != argv.end() && *ait; ++ ait ) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " \"" << *ait << "\""); - } - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl); + for (ait = argv.begin(); ait != argv.end() && *ait; ++ait) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " \"" << *ait << "\"", this->Quiet); + } + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl, + this->Quiet); // Optionally use make rule launchers to record errors and warnings. LaunchHelper launchHelper(this); @@ -929,12 +810,16 @@ int cmCTestBuildHandler::RunMakeCommand(const char* command, char* data; int length; - cmCTestLog(this->CTest, HANDLER_OUTPUT, - " Each symbol represents " << tick_len << " bytes of output." - << std::endl - << (this->UseCTestLaunch? "" : - " '!' represents an error and '*' a warning.\n") - << " " << std::flush); + cmProcessOutput processOutput(encoding); + std::string strdata; + cmCTestOptionalLog( + this->CTest, HANDLER_PROGRESS_OUTPUT, " Each symbol represents " + << tick_len << " bytes of output." << std::endl + << (this->UseCTestLaunch + ? "" + : " '!' represents an error and '*' a warning.\n") + << " " << std::flush, + this->Quiet); // Initialize building structures this->BuildProcessingQueue.clear(); @@ -949,94 +834,97 @@ int cmCTestBuildHandler::RunMakeCommand(const char* command, // For every chunk of data int res; - while((res = cmsysProcess_WaitForData(cp, &data, &length, 0))) - { + while ((res = cmsysProcess_WaitForData(cp, &data, &length, CM_NULLPTR))) { // Replace '\0' with '\n', since '\0' does not really make sense. This is // for Visual Studio output - for(int cc =0; cc < length; ++cc) - { - if(data[cc] == 0) - { + for (int cc = 0; cc < length; ++cc) { + if (data[cc] == 0) { data[cc] = '\n'; - } } + } // Process the chunk of data - if ( res == cmsysProcess_Pipe_STDERR ) - { - this->ProcessBuffer(data, length, tick, tick_len, ofs, - &this->BuildProcessingErrorQueue); - } - else - { - this->ProcessBuffer(data, length, tick, tick_len, ofs, - &this->BuildProcessingQueue); - } + if (res == cmsysProcess_Pipe_STDERR) { + processOutput.DecodeText(data, length, strdata, 1); + this->ProcessBuffer(strdata.c_str(), strdata.size(), tick, tick_len, ofs, + &this->BuildProcessingErrorQueue); + } else { + processOutput.DecodeText(data, length, strdata, 2); + this->ProcessBuffer(strdata.c_str(), strdata.size(), tick, tick_len, ofs, + &this->BuildProcessingQueue); } - - this->ProcessBuffer(0, 0, tick, tick_len, ofs, &this->BuildProcessingQueue); - this->ProcessBuffer(0, 0, tick, tick_len, ofs, - &this->BuildProcessingErrorQueue); - cmCTestLog(this->CTest, OUTPUT, " Size of output: " - << ((this->BuildOutputLogSize + 512) / 1024) << "K" << std::endl); + } + processOutput.DecodeText(std::string(), strdata, 1); + if (!strdata.empty()) { + this->ProcessBuffer(strdata.c_str(), strdata.size(), tick, tick_len, ofs, + &this->BuildProcessingErrorQueue); + } + processOutput.DecodeText(std::string(), strdata, 2); + if (!strdata.empty()) { + this->ProcessBuffer(strdata.c_str(), strdata.size(), tick, tick_len, ofs, + &this->BuildProcessingQueue); + } + + this->ProcessBuffer(CM_NULLPTR, 0, tick, tick_len, ofs, + &this->BuildProcessingQueue); + this->ProcessBuffer(CM_NULLPTR, 0, tick, tick_len, ofs, + &this->BuildProcessingErrorQueue); + cmCTestOptionalLog(this->CTest, HANDLER_PROGRESS_OUTPUT, " Size of output: " + << ((this->BuildOutputLogSize + 512) / 1024) << "K" + << std::endl, + this->Quiet); // Properly handle output of the build command - cmsysProcess_WaitForExit(cp, 0); + cmsysProcess_WaitForExit(cp, CM_NULLPTR); int result = cmsysProcess_GetState(cp); - if(result == cmsysProcess_State_Exited) - { - if (retVal) - { + if (result == cmsysProcess_State_Exited) { + if (retVal) { *retVal = cmsysProcess_GetExitValue(cp); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Command exited with the value: " << *retVal << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Command exited with the value: " << *retVal + << std::endl, + this->Quiet); // if a non zero return value - if (*retVal) - { + if (*retVal) { // If there was an error running command, report that on the // dashboard. cmCTestBuildErrorWarning errorwarning; - errorwarning.LogLine = 1; - errorwarning.Text - = "*** WARNING non-zero return value in ctest from: "; - errorwarning.Text += argv[0]; - errorwarning.PreContext = ""; + errorwarning.LogLine = 1; + errorwarning.Text = + "*** WARNING non-zero return value in ctest from: "; + errorwarning.Text += argv[0]; + errorwarning.PreContext = ""; errorwarning.PostContext = ""; - errorwarning.Error = false; + errorwarning.Error = false; this->ErrorsAndWarnings.push_back(errorwarning); - this->TotalWarnings ++; - } + this->TotalWarnings++; } } - else if(result == cmsysProcess_State_Exception) - { - if (retVal) - { + } else if (result == cmsysProcess_State_Exception) { + if (retVal) { *retVal = cmsysProcess_GetExitException(cp); - cmCTestLog(this->CTest, WARNING, "There was an exception: " << *retVal - << std::endl); - } - } - else if(result == cmsysProcess_State_Expired) - { - cmCTestLog(this->CTest, WARNING, "There was a timeout" << std::endl); + cmCTestOptionalLog(this->CTest, WARNING, + "There was an exception: " << *retVal << std::endl, + this->Quiet); } - else if(result == cmsysProcess_State_Error) - { + } else if (result == cmsysProcess_State_Expired) { + cmCTestOptionalLog(this->CTest, WARNING, + "There was a timeout" << std::endl, this->Quiet); + } else if (result == cmsysProcess_State_Error) { // If there was an error running command, report that on the dashboard. cmCTestBuildErrorWarning errorwarning; - errorwarning.LogLine = 1; - errorwarning.Text = "*** ERROR executing: "; - errorwarning.Text += cmsysProcess_GetErrorString(cp); - errorwarning.PreContext = ""; + errorwarning.LogLine = 1; + errorwarning.Text = "*** ERROR executing: "; + errorwarning.Text += cmsysProcess_GetErrorString(cp); + errorwarning.PreContext = ""; errorwarning.PostContext = ""; - errorwarning.Error = true; + errorwarning.Error = true; this->ErrorsAndWarnings.push_back(errorwarning); - this->TotalErrors ++; + this->TotalErrors++; cmCTestLog(this->CTest, ERROR_MESSAGE, "There was an error: " - << cmsysProcess_GetErrorString(cp) << std::endl); - } + << cmsysProcess_GetErrorString(cp) << std::endl); + } cmsysProcess_Delete(cp); return result; @@ -1047,55 +935,43 @@ int cmCTestBuildHandler::RunMakeCommand(const char* command, //###################################################################### //###################################################################### -//---------------------------------------------------------------------- -void cmCTestBuildHandler::ProcessBuffer(const char* data, int length, - size_t& tick, size_t tick_len, std::ofstream& ofs, - t_BuildProcessingQueueType* queue) +void cmCTestBuildHandler::ProcessBuffer(const char* data, size_t length, + size_t& tick, size_t tick_len, + std::ostream& ofs, + t_BuildProcessingQueueType* queue) { const std::string::size_type tick_line_len = 50; const char* ptr; - for ( ptr = data; ptr < data+length; ptr ++ ) - { + for (ptr = data; ptr < data + length; ptr++) { queue->push_back(*ptr); - } + } this->BuildOutputLogSize += length; // until there are any lines left in the buffer - while ( 1 ) - { + while (true) { // Find the end of line t_BuildProcessingQueueType::iterator it; - for ( it = queue->begin(); - it != queue->end(); - ++ it ) - { - if ( *it == '\n' ) - { + for (it = queue->begin(); it != queue->end(); ++it) { + if (*it == '\n') { break; - } } + } // Once certain number of errors or warnings reached, ignore future errors // or warnings. - if ( this->TotalWarnings >= this->MaxWarnings ) - { + if (this->TotalWarnings >= this->MaxWarnings) { this->WarningQuotaReached = true; - } - if ( this->TotalErrors >= this->MaxErrors ) - { + } + if (this->TotalErrors >= this->MaxErrors) { this->ErrorQuotaReached = true; - } + } // If the end of line was found - if ( it != queue->end() ) - { + if (it != queue->end()) { // Create a contiguous array for the line this->CurrentProcessingLine.clear(); - t_BuildProcessingQueueType::iterator cit; - for ( cit = queue->begin(); cit != it; ++cit ) - { - this->CurrentProcessingLine.push_back(*cit); - } + this->CurrentProcessingLine.insert(this->CurrentProcessingLine.end(), + queue->begin(), it); this->CurrentProcessingLine.push_back(0); const char* line = &*this->CurrentProcessingLine.begin(); @@ -1103,120 +979,107 @@ void cmCTestBuildHandler::ProcessBuffer(const char* data, int length, int lineType = this->ProcessSingleLine(line); // Erase the line from the queue - queue->erase(queue->begin(), it+1); + queue->erase(queue->begin(), it + 1); // Depending on the line type, produce error or warning, or nothing cmCTestBuildErrorWarning errorwarning; bool found = false; - switch ( lineType ) - { - case b_WARNING_LINE: - this->LastTickChar = '*'; - errorwarning.Error = false; - found = true; - this->TotalWarnings ++; - break; - case b_ERROR_LINE: - this->LastTickChar = '!'; - errorwarning.Error = true; - found = true; - this->TotalErrors ++; - break; - } - if ( found ) - { + switch (lineType) { + case b_WARNING_LINE: + this->LastTickChar = '*'; + errorwarning.Error = false; + found = true; + this->TotalWarnings++; + break; + case b_ERROR_LINE: + this->LastTickChar = '!'; + errorwarning.Error = true; + found = true; + this->TotalErrors++; + break; + } + if (found) { // This is an error or warning, so generate report - errorwarning.LogLine = static_cast<int>(this->OutputLineCounter+1); - errorwarning.Text = line; - errorwarning.PreContext = ""; + errorwarning.LogLine = static_cast<int>(this->OutputLineCounter + 1); + errorwarning.Text = line; + errorwarning.PreContext = ""; errorwarning.PostContext = ""; // Copy pre-context to report - std::deque<cmStdString>::iterator pcit; - for ( pcit = this->PreContext.begin(); - pcit != this->PreContext.end(); - ++pcit ) - { + std::deque<std::string>::iterator pcit; + for (pcit = this->PreContext.begin(); pcit != this->PreContext.end(); + ++pcit) { errorwarning.PreContext += *pcit + "\n"; - } + } this->PreContext.clear(); // Store report this->ErrorsAndWarnings.push_back(errorwarning); - this->LastErrorOrWarning = this->ErrorsAndWarnings.end()-1; + this->LastErrorOrWarning = this->ErrorsAndWarnings.end() - 1; this->PostContextCount = 0; - } - else - { + } else { // This is not an error or warning. // So, figure out if this is a post-context line - if ( this->ErrorsAndWarnings.size() && - this->LastErrorOrWarning != this->ErrorsAndWarnings.end() && - this->PostContextCount < this->MaxPostContext ) - { - this->PostContextCount ++; + if (!this->ErrorsAndWarnings.empty() && + this->LastErrorOrWarning != this->ErrorsAndWarnings.end() && + this->PostContextCount < this->MaxPostContext) { + this->PostContextCount++; this->LastErrorOrWarning->PostContext += line; - if ( this->PostContextCount < this->MaxPostContext ) - { + if (this->PostContextCount < this->MaxPostContext) { this->LastErrorOrWarning->PostContext += "\n"; - } } - else - { + } else { // Otherwise store pre-context for the next error this->PreContext.push_back(line); - if ( this->PreContext.size() > this->MaxPreContext ) - { + if (this->PreContext.size() > this->MaxPreContext) { this->PreContext.erase(this->PreContext.begin(), - this->PreContext.end()-this->MaxPreContext); - } + this->PreContext.end() - + this->MaxPreContext); } } - this->OutputLineCounter ++; } - else - { + this->OutputLineCounter++; + } else { break; - } } + } // Now that the buffer is processed, display missing ticks int tickDisplayed = false; - while ( this->BuildOutputLogSize > (tick * tick_len) ) - { - tick ++; - cmCTestLog(this->CTest, HANDLER_OUTPUT, this->LastTickChar); + while (this->BuildOutputLogSize > (tick * tick_len)) { + tick++; + cmCTestOptionalLog(this->CTest, HANDLER_PROGRESS_OUTPUT, + this->LastTickChar, this->Quiet); tickDisplayed = true; - if ( tick % tick_line_len == 0 && tick > 0 ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Size: " - << ((this->BuildOutputLogSize + 512) / 1024) << "K" << std::endl - << " "); - } + if (tick % tick_line_len == 0 && tick > 0) { + cmCTestOptionalLog(this->CTest, HANDLER_PROGRESS_OUTPUT, " Size: " + << ((this->BuildOutputLogSize + 512) / 1024) << "K" + << std::endl + << " ", + this->Quiet); } - if ( tickDisplayed ) - { + } + if (tickDisplayed) { this->LastTickChar = '.'; - } + } // And if this is verbose output, display the content of the chunk cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - cmCTestLogWrite(data, length)); + cmCTestLogWrite(data, length)); // Always store the chunk to the file ofs << cmCTestLogWrite(data, length); } -//---------------------------------------------------------------------- int cmCTestBuildHandler::ProcessSingleLine(const char* data) { - if(this->UseCTestLaunch) - { + if (this->UseCTestLaunch) { // No log scraping when using launchers. return b_REGULAR_LINE; - } + } - cmCTestLog(this->CTest, DEBUG, "Line: [" << data << "]" << std::endl); + cmCTestOptionalLog(this->CTest, DEBUG, "Line: [" << data << "]" << std::endl, + this->Quiet); std::vector<cmsys::RegularExpression>::iterator it; @@ -1225,86 +1088,76 @@ int cmCTestBuildHandler::ProcessSingleLine(const char* data) // Check for regular expressions - if ( !this->ErrorQuotaReached ) - { + if (!this->ErrorQuotaReached) { // Errors int wrxCnt = 0; - for ( it = this->ErrorMatchRegex.begin(); - it != this->ErrorMatchRegex.end(); - ++ it ) - { - if ( it->find(data) ) - { + for (it = this->ErrorMatchRegex.begin(); it != this->ErrorMatchRegex.end(); + ++it) { + if (it->find(data)) { errorLine = 1; - cmCTestLog(this->CTest, DEBUG, " Error Line: " << data - << " (matches: " << this->CustomErrorMatches[wrxCnt] << ")" - << std::endl); + cmCTestOptionalLog(this->CTest, DEBUG, + " Error Line: " << data << " (matches: " + << this->CustomErrorMatches[wrxCnt] + << ")" << std::endl, + this->Quiet); break; - } - wrxCnt ++; } + wrxCnt++; + } // Error exceptions wrxCnt = 0; - for ( it = this->ErrorExceptionRegex.begin(); - it != this->ErrorExceptionRegex.end(); - ++ it ) - { - if ( it->find(data) ) - { + for (it = this->ErrorExceptionRegex.begin(); + it != this->ErrorExceptionRegex.end(); ++it) { + if (it->find(data)) { errorLine = 0; - cmCTestLog(this->CTest, DEBUG, " Not an error Line: " << data - << " (matches: " << this->CustomErrorExceptions[wrxCnt] << ")" - << std::endl); + cmCTestOptionalLog(this->CTest, DEBUG, " Not an error Line: " + << data << " (matches: " + << this->CustomErrorExceptions[wrxCnt] << ")" + << std::endl, + this->Quiet); break; - } - wrxCnt ++; } + wrxCnt++; } - if ( !this->WarningQuotaReached ) - { + } + if (!this->WarningQuotaReached) { // Warnings int wrxCnt = 0; - for ( it = this->WarningMatchRegex.begin(); - it != this->WarningMatchRegex.end(); - ++ it ) - { - if ( it->find(data) ) - { + for (it = this->WarningMatchRegex.begin(); + it != this->WarningMatchRegex.end(); ++it) { + if (it->find(data)) { warningLine = 1; - cmCTestLog(this->CTest, DEBUG, - " Warning Line: " << data - << " (matches: " << this->CustomWarningMatches[wrxCnt] << ")" - << std::endl); + cmCTestOptionalLog(this->CTest, DEBUG, " Warning Line: " + << data << " (matches: " + << this->CustomWarningMatches[wrxCnt] << ")" + << std::endl, + this->Quiet); break; - } - wrxCnt ++; } + wrxCnt++; + } wrxCnt = 0; // Warning exceptions - for ( it = this->WarningExceptionRegex.begin(); - it != this->WarningExceptionRegex.end(); - ++ it ) - { - if ( it->find(data) ) - { + for (it = this->WarningExceptionRegex.begin(); + it != this->WarningExceptionRegex.end(); ++it) { + if (it->find(data)) { warningLine = 0; - cmCTestLog(this->CTest, DEBUG, " Not a warning Line: " << data - << " (matches: " << this->CustomWarningExceptions[wrxCnt] << ")" - << std::endl); + cmCTestOptionalLog(this->CTest, DEBUG, " Not a warning Line: " + << data << " (matches: " + << this->CustomWarningExceptions[wrxCnt] << ")" + << std::endl, + this->Quiet); break; - } - wrxCnt ++; } + wrxCnt++; } - if ( errorLine ) - { + } + if (errorLine) { return b_ERROR_LINE; - } - if ( warningLine ) - { + } + if (warningLine) { return b_WARNING_LINE; - } + } return b_REGULAR_LINE; } - diff --git a/Source/CTest/cmCTestBuildHandler.h b/Source/CTest/cmCTestBuildHandler.h index 439efd604..ef3cddf1b 100644 --- a/Source/CTest/cmCTestBuildHandler.h +++ b/Source/CTest/cmCTestBuildHandler.h @@ -1,25 +1,22 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestBuildHandler_h #define cmCTestBuildHandler_h +#include "cmConfigure.h" #include "cmCTestGenericHandler.h" -#include "cmListFileCache.h" -#include <cmsys/RegularExpression.hxx> +#include "cmProcessOutput.h" +#include "cmsys/RegularExpression.hxx" +#include <deque> +#include <iosfwd> +#include <stddef.h> +#include <string> +#include <vector> class cmMakefile; +class cmXMLWriter; /** \class cmCTestBuildHandler * \brief A class that handles ctest -S invocations @@ -28,79 +25,80 @@ class cmMakefile; class cmCTestBuildHandler : public cmCTestGenericHandler { public: - cmTypeMacro(cmCTestBuildHandler, cmCTestGenericHandler); + typedef cmCTestGenericHandler Superclass; + typedef cmProcessOutput::Encoding Encoding; /* * The main entry point for this class */ - int ProcessHandler(); + int ProcessHandler() CM_OVERRIDE; cmCTestBuildHandler(); - void PopulateCustomVectors(cmMakefile *mf); + void PopulateCustomVectors(cmMakefile* mf) CM_OVERRIDE; /** * Initialize handler */ - virtual void Initialize(); + void Initialize() CM_OVERRIDE; - int GetTotalErrors() { return this->TotalErrors;} - int GetTotalWarnings() { return this->TotalWarnings;} + int GetTotalErrors() { return this->TotalErrors; } + int GetTotalWarnings() { return this->TotalWarnings; } private: std::string GetMakeCommand(); //! Run command specialized for make and configure. Returns process status // and retVal is return value or exception. - int RunMakeCommand(const char* command, - int* retVal, const char* dir, int timeout, - std::ofstream& ofs); + int RunMakeCommand(const char* command, int* retVal, const char* dir, + int timeout, std::ostream& ofs, + Encoding encoding = cmProcessOutput::Auto); - enum { + enum + { b_REGULAR_LINE, b_WARNING_LINE, b_ERROR_LINE }; class cmCTestCompileErrorWarningRex - { + { public: cmCTestCompileErrorWarningRex() {} int FileIndex; int LineIndex; cmsys::RegularExpression RegularExpression; - }; + }; struct cmCTestBuildErrorWarning { - bool Error; - int LogLine; + bool Error; + int LogLine; std::string Text; std::string SourceFile; std::string SourceFileTail; - int LineNumber; + int LineNumber; std::string PreContext; std::string PostContext; }; // generate the XML output - void GenerateXMLHeader(std::ostream& os); - void GenerateXMLLaunched(std::ostream& os); - void GenerateXMLLogScraped(std::ostream& os); - void GenerateXMLFooter(std::ostream& os, double elapsed_build_time); - void GenerateXMLLaunchedFragment(std::ostream& os, const char* fname); + void GenerateXMLHeader(cmXMLWriter& xml); + void GenerateXMLLaunched(cmXMLWriter& xml); + void GenerateXMLLogScraped(cmXMLWriter& xml); + void GenerateXMLFooter(cmXMLWriter& xml, double elapsed_build_time); bool IsLaunchedErrorFile(const char* fname); bool IsLaunchedWarningFile(const char* fname); - std::string StartBuild; - std::string EndBuild; - double StartBuildTime; - double EndBuildTime; + std::string StartBuild; + std::string EndBuild; + double StartBuildTime; + double EndBuildTime; - std::vector<cmStdString> CustomErrorMatches; - std::vector<cmStdString> CustomErrorExceptions; - std::vector<cmStdString> CustomWarningMatches; - std::vector<cmStdString> CustomWarningExceptions; + std::vector<std::string> CustomErrorMatches; + std::vector<std::string> CustomErrorExceptions; + std::vector<std::string> CustomWarningMatches; + std::vector<std::string> CustomWarningExceptions; std::vector<std::string> ReallyCustomWarningMatches; std::vector<std::string> ReallyCustomWarningExceptions; std::vector<cmCTestCompileErrorWarningRex> ErrorWarningFileLineRegex; @@ -112,39 +110,41 @@ private: typedef std::deque<char> t_BuildProcessingQueueType; - void ProcessBuffer(const char* data, int length, size_t& tick, - size_t tick_len, std::ofstream& ofs, t_BuildProcessingQueueType* queue); + void ProcessBuffer(const char* data, size_t length, size_t& tick, + size_t tick_len, std::ostream& ofs, + t_BuildProcessingQueueType* queue); int ProcessSingleLine(const char* data); - t_BuildProcessingQueueType BuildProcessingQueue; - t_BuildProcessingQueueType BuildProcessingErrorQueue; - size_t BuildOutputLogSize; - std::vector<char> CurrentProcessingLine; + t_BuildProcessingQueueType BuildProcessingQueue; + t_BuildProcessingQueueType BuildProcessingErrorQueue; + size_t BuildOutputLogSize; + std::vector<char> CurrentProcessingLine; - cmStdString SimplifySourceDir; - cmStdString SimplifyBuildDir; - size_t OutputLineCounter; + std::string SimplifySourceDir; + std::string SimplifyBuildDir; + size_t OutputLineCounter; typedef std::vector<cmCTestBuildErrorWarning> t_ErrorsAndWarningsVector; - t_ErrorsAndWarningsVector ErrorsAndWarnings; - t_ErrorsAndWarningsVector::iterator LastErrorOrWarning; - size_t PostContextCount; - size_t MaxPreContext; - size_t MaxPostContext; - std::deque<cmStdString> PreContext; + t_ErrorsAndWarningsVector ErrorsAndWarnings; + t_ErrorsAndWarningsVector::iterator LastErrorOrWarning; + size_t PostContextCount; + size_t MaxPreContext; + size_t MaxPostContext; + std::deque<std::string> PreContext; - int TotalErrors; - int TotalWarnings; - char LastTickChar; + int TotalErrors; + int TotalWarnings; + char LastTickChar; - bool ErrorQuotaReached; - bool WarningQuotaReached; + bool ErrorQuotaReached; + bool WarningQuotaReached; - int MaxErrors; - int MaxWarnings; + int MaxErrors; + int MaxWarnings; bool UseCTestLaunch; std::string CTestLaunchDir; class LaunchHelper; + friend class LaunchHelper; class FragmentCompare; }; diff --git a/Source/CTest/cmCTestCVS.cxx b/Source/CTest/cmCTestCVS.cxx index 726950704..fad360ba4 100644 --- a/Source/CTest/cmCTestCVS.cxx +++ b/Source/CTest/cmCTestCVS.cxx @@ -1,38 +1,31 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestCVS.h" #include "cmCTest.h" +#include "cmProcessTools.h" #include "cmSystemTools.h" -#include "cmXMLSafe.h" +#include "cmXMLWriter.h" -#include <cmsys/RegularExpression.hxx> +#include "cmsys/FStream.hxx" +#include "cmsys/RegularExpression.hxx" +#include <utility> -//---------------------------------------------------------------------------- -cmCTestCVS::cmCTestCVS(cmCTest* ct, std::ostream& log): cmCTestVC(ct, log) +cmCTestCVS::cmCTestCVS(cmCTest* ct, std::ostream& log) + : cmCTestVC(ct, log) { } -//---------------------------------------------------------------------------- cmCTestCVS::~cmCTestCVS() { } -//---------------------------------------------------------------------------- -class cmCTestCVS::UpdateParser: public cmCTestVC::LineParser +class cmCTestCVS::UpdateParser : public cmCTestVC::LineParser { public: - UpdateParser(cmCTestCVS* cvs, const char* prefix): CVS(cvs) - { + UpdateParser(cmCTestCVS* cvs, const char* prefix) + : CVS(cvs) + { this->SetLog(&cvs->Log, prefix); // See "man cvs", section "update output". this->RegexFileUpdated.compile("^([UP]) *(.*)"); @@ -43,7 +36,8 @@ public: this->RegexFileRemoved2.compile( "cvs[^ ]* update: " "warning: `?([^']*)'? is not \\(any longer\\) pertinent"); - } + } + private: cmCTestCVS* CVS; cmsys::RegularExpression RegexFileUpdated; @@ -52,266 +46,233 @@ private: cmsys::RegularExpression RegexFileRemoved1; cmsys::RegularExpression RegexFileRemoved2; - virtual bool ProcessLine() - { - if(this->RegexFileUpdated.find(this->Line)) - { + bool ProcessLine() CM_OVERRIDE + { + if (this->RegexFileUpdated.find(this->Line)) { this->DoFile(PathUpdated, this->RegexFileUpdated.match(2)); - } - else if(this->RegexFileModified.find(this->Line)) - { + } else if (this->RegexFileModified.find(this->Line)) { this->DoFile(PathModified, this->RegexFileModified.match(2)); - } - else if(this->RegexFileConflicting.find(this->Line)) - { + } else if (this->RegexFileConflicting.find(this->Line)) { this->DoFile(PathConflicting, this->RegexFileConflicting.match(2)); - } - else if(this->RegexFileRemoved1.find(this->Line)) - { + } else if (this->RegexFileRemoved1.find(this->Line)) { this->DoFile(PathUpdated, this->RegexFileRemoved1.match(1)); - } - else if(this->RegexFileRemoved2.find(this->Line)) - { + } else if (this->RegexFileRemoved2.find(this->Line)) { this->DoFile(PathUpdated, this->RegexFileRemoved2.match(1)); - } - return true; } + return true; + } void DoFile(PathStatus status, std::string const& file) - { + { std::string dir = cmSystemTools::GetFilenamePath(file); std::string name = cmSystemTools::GetFilenameName(file); this->CVS->Dirs[dir][name] = status; - } + } }; -//---------------------------------------------------------------------------- bool cmCTestCVS::UpdateImpl() { // Get user-specified update options. std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); - if(opts.empty()) - { + if (opts.empty()) { opts = this->CTest->GetCTestConfiguration("CVSUpdateOptions"); - if(opts.empty()) - { + if (opts.empty()) { opts = "-dP"; - } } - std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str()); + } + std::vector<std::string> args = cmSystemTools::ParseArguments(opts.c_str()); // Specify the start time for nightly testing. - if(this->CTest->GetTestModel() == cmCTest::NIGHTLY) - { + if (this->CTest->GetTestModel() == cmCTest::NIGHTLY) { args.push_back("-D" + this->GetNightlyTime() + " UTC"); - } + } // Run "cvs update" to update the work tree. std::vector<char const*> cvs_update; cvs_update.push_back(this->CommandLineTool.c_str()); cvs_update.push_back("-z3"); cvs_update.push_back("update"); - for(std::vector<cmStdString>::const_iterator ai = args.begin(); - ai != args.end(); ++ai) - { + for (std::vector<std::string>::const_iterator ai = args.begin(); + ai != args.end(); ++ai) { cvs_update.push_back(ai->c_str()); - } - cvs_update.push_back(0); + } + cvs_update.push_back(CM_NULLPTR); UpdateParser out(this, "up-out> "); UpdateParser err(this, "up-err> "); return this->RunUpdateCommand(&cvs_update[0], &out, &err); } -//---------------------------------------------------------------------------- -class cmCTestCVS::LogParser: public cmCTestVC::LineParser +class cmCTestCVS::LogParser : public cmCTestVC::LineParser { public: typedef cmCTestCVS::Revision Revision; - LogParser(cmCTestCVS* cvs, const char* prefix, std::vector<Revision>& revs): - CVS(cvs), Revisions(revs), Section(SectionHeader) - { + LogParser(cmCTestCVS* cvs, const char* prefix, std::vector<Revision>& revs) + : CVS(cvs) + , Revisions(revs) + , Section(SectionHeader) + { this->SetLog(&cvs->Log, prefix), - this->RegexRevision.compile("^revision +([^ ]*) *$"); + this->RegexRevision.compile("^revision +([^ ]*) *$"); this->RegexBranches.compile("^branches: .*$"); this->RegexPerson.compile("^date: +([^;]+); +author: +([^;]+);"); - } + } + private: cmCTestCVS* CVS; std::vector<Revision>& Revisions; cmsys::RegularExpression RegexRevision; cmsys::RegularExpression RegexBranches; cmsys::RegularExpression RegexPerson; - enum SectionType { SectionHeader, SectionRevisions, SectionEnd }; + enum SectionType + { + SectionHeader, + SectionRevisions, + SectionEnd + }; SectionType Section; Revision Rev; - virtual bool ProcessLine() - { - if(this->Line == ("=======================================" - "======================================")) - { + bool ProcessLine() CM_OVERRIDE + { + if (this->Line == ("=======================================" + "======================================")) { // This line ends the revision list. - if(this->Section == SectionRevisions) - { + if (this->Section == SectionRevisions) { this->FinishRevision(); - } - this->Section = SectionEnd; } - else if(this->Line == "----------------------------") - { + this->Section = SectionEnd; + } else if (this->Line == "----------------------------") { // This line divides revisions from the header and each other. - if(this->Section == SectionHeader) - { + if (this->Section == SectionHeader) { this->Section = SectionRevisions; - } - else if(this->Section == SectionRevisions) - { + } else if (this->Section == SectionRevisions) { this->FinishRevision(); - } } - else if(this->Section == SectionRevisions) - { - if(!this->Rev.Log.empty()) - { + } else if (this->Section == SectionRevisions) { + if (!this->Rev.Log.empty()) { // Continue the existing log. this->Rev.Log += this->Line; this->Rev.Log += "\n"; - } - else if(this->Rev.Rev.empty() && this->RegexRevision.find(this->Line)) - { + } else if (this->Rev.Rev.empty() && + this->RegexRevision.find(this->Line)) { this->Rev.Rev = this->RegexRevision.match(1); - } - else if(this->Rev.Date.empty() && this->RegexPerson.find(this->Line)) - { + } else if (this->Rev.Date.empty() && + this->RegexPerson.find(this->Line)) { this->Rev.Date = this->RegexPerson.match(1); this->Rev.Author = this->RegexPerson.match(2); - } - else if(!this->RegexBranches.find(this->Line)) - { + } else if (!this->RegexBranches.find(this->Line)) { // Start the log. this->Rev.Log += this->Line; this->Rev.Log += "\n"; - } } - return this->Section != SectionEnd; } + return this->Section != SectionEnd; + } void FinishRevision() - { - if(!this->Rev.Rev.empty()) - { + { + if (!this->Rev.Rev.empty()) { // Record this revision. + /* clang-format off */ this->CVS->Log << "Found revision " << this->Rev.Rev << "\n" << " author = " << this->Rev.Author << "\n" << " date = " << this->Rev.Date << "\n"; + /* clang-format on */ this->Revisions.push_back(this->Rev); // We only need two revisions. - if(this->Revisions.size() >= 2) - { + if (this->Revisions.size() >= 2) { this->Section = SectionEnd; - } } - this->Rev = Revision(); } + this->Rev = Revision(); + } }; -//---------------------------------------------------------------------------- std::string cmCTestCVS::ComputeBranchFlag(std::string const& dir) { // Compute the tag file location for this directory. std::string tagFile = this->SourceDirectory; - if(!dir.empty()) - { + if (!dir.empty()) { tagFile += "/"; tagFile += dir; - } + } tagFile += "/CVS/Tag"; // Lookup the branch in the tag file, if any. std::string tagLine; - std::ifstream tagStream(tagFile.c_str()); - if(tagStream && cmSystemTools::GetLineFromStream(tagStream, tagLine) && - tagLine.size() > 1 && tagLine[0] == 'T') - { + cmsys::ifstream tagStream(tagFile.c_str()); + if (tagStream && cmSystemTools::GetLineFromStream(tagStream, tagLine) && + tagLine.size() > 1 && tagLine[0] == 'T') { // Use the branch specified in the tag file. std::string flag = "-r"; flag += tagLine.substr(1); return flag; - } - else - { - // Use the default branch. - return "-b"; - } + } + // Use the default branch. + return "-b"; } -//---------------------------------------------------------------------------- -void cmCTestCVS::LoadRevisions(std::string const& file, - const char* branchFlag, +void cmCTestCVS::LoadRevisions(std::string const& file, const char* branchFlag, std::vector<Revision>& revisions) { cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush); // Run "cvs log" to get revisions of this file on this branch. const char* cvs = this->CommandLineTool.c_str(); - const char* cvs_log[] = - {cvs, "log", "-N", branchFlag, file.c_str(), 0}; + const char* cvs_log[] = { cvs, "log", "-N", + branchFlag, file.c_str(), CM_NULLPTR }; LogParser out(this, "log-out> ", revisions); OutputLogger err(this->Log, "log-err> "); this->RunChild(cvs_log, &out, &err); } -//---------------------------------------------------------------------------- -void cmCTestCVS::WriteXMLDirectory(std::ostream& xml, - std::string const& path, +void cmCTestCVS::WriteXMLDirectory(cmXMLWriter& xml, std::string const& path, Directory const& dir) { - const char* slash = path.empty()? "":"/"; - xml << "\t<Directory>\n" - << "\t\t<Name>" << cmXMLSafe(path) << "</Name>\n"; + const char* slash = path.empty() ? "" : "/"; + xml.StartElement("Directory"); + xml.Element("Name", path); // Lookup the branch checked out in the working tree. std::string branchFlag = this->ComputeBranchFlag(path); // Load revisions and write an entry for each file in this directory. std::vector<Revision> revisions; - for(Directory::const_iterator fi = dir.begin(); fi != dir.end(); ++fi) - { + for (Directory::const_iterator fi = dir.begin(); fi != dir.end(); ++fi) { std::string full = path + slash + fi->first; // Load two real or unknown revisions. revisions.clear(); - if(fi->second != PathUpdated) - { + if (fi->second != PathUpdated) { // For local modifications the current rev is unknown and the // prior rev is the latest from cvs. revisions.push_back(this->Unknown); - } + } this->LoadRevisions(full, branchFlag.c_str(), revisions); revisions.resize(2, this->Unknown); // Write the entry for this file with these revisions. File f(fi->second, &revisions[0], &revisions[1]); this->WriteXMLEntry(xml, path, fi->first, full, f); - } - xml << "\t</Directory>\n"; + } + xml.EndElement(); // Directory } -//---------------------------------------------------------------------------- -bool cmCTestCVS::WriteXMLUpdates(std::ostream& xml) +bool cmCTestCVS::WriteXMLUpdates(cmXMLWriter& xml) { cmCTestLog(this->CTest, HANDLER_OUTPUT, " Gathering version information (one . per updated file):\n" - " " << std::flush); + " " + << std::flush); - for(std::map<cmStdString, Directory>::const_iterator - di = this->Dirs.begin(); di != this->Dirs.end(); ++di) - { + for (std::map<std::string, Directory>::const_iterator di = + this->Dirs.begin(); + di != this->Dirs.end(); ++di) { this->WriteXMLDirectory(xml, di->first, di->second); - } + } cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); diff --git a/Source/CTest/cmCTestCVS.h b/Source/CTest/cmCTestCVS.h index b7fe567d8..1208cfad6 100644 --- a/Source/CTest/cmCTestCVS.h +++ b/Source/CTest/cmCTestCVS.h @@ -1,51 +1,55 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestCVS_h #define cmCTestCVS_h +#include "cmConfigure.h" + #include "cmCTestVC.h" +#include <iosfwd> +#include <map> +#include <string> +#include <vector> + +class cmCTest; +class cmXMLWriter; + /** \class cmCTestCVS * \brief Interaction with cvs command-line tool * */ -class cmCTestCVS: public cmCTestVC +class cmCTestCVS : public cmCTestVC { public: /** Construct with a CTest instance and update log stream. */ cmCTestCVS(cmCTest* ctest, std::ostream& log); - virtual ~cmCTestCVS(); + ~cmCTestCVS() CM_OVERRIDE; private: // Implement cmCTestVC internal API. - virtual bool UpdateImpl(); - virtual bool WriteXMLUpdates(std::ostream& xml); + bool UpdateImpl() CM_OVERRIDE; + bool WriteXMLUpdates(cmXMLWriter& xml) CM_OVERRIDE; // Update status for files in each directory. - class Directory: public std::map<cmStdString, PathStatus> {}; - std::map<cmStdString, Directory> Dirs; + class Directory : public std::map<std::string, PathStatus> + { + }; + std::map<std::string, Directory> Dirs; std::string ComputeBranchFlag(std::string const& dir); void LoadRevisions(std::string const& file, const char* branchFlag, std::vector<Revision>& revisions); - void WriteXMLDirectory(std::ostream& xml, std::string const& path, + void WriteXMLDirectory(cmXMLWriter& xml, std::string const& path, Directory const& dir); // Parsing helper classes. - class UpdateParser; class LogParser; - friend class UpdateParser; + class UpdateParser; + friend class LogParser; + friend class UpdateParser; }; #endif diff --git a/Source/CTest/cmCTestCommand.h b/Source/CTest/cmCTestCommand.h index e2ebba8fd..6fc237aac 100644 --- a/Source/CTest/cmCTestCommand.h +++ b/Source/CTest/cmCTestCommand.h @@ -1,14 +1,5 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestCommand_h #define cmCTestCommand_h @@ -27,14 +18,14 @@ class cmCTestScriptHandler; class cmCTestCommand : public cmCommand { public: - - cmCTestCommand() {this->CTest = 0; this->CTestScriptHandler = 0;} - - cmCTest *CTest; - cmCTestScriptHandler *CTestScriptHandler; - - cmTypeMacro(cmCTestCommand, cmCommand); + cmCTestCommand() + { + this->CTest = CM_NULLPTR; + this->CTestScriptHandler = CM_NULLPTR; + } + + cmCTest* CTest; + cmCTestScriptHandler* CTestScriptHandler; }; - #endif diff --git a/Source/CTest/cmCTestConfigureCommand.cxx b/Source/CTest/cmCTestConfigureCommand.cxx index db33cb68e..73e893df3 100644 --- a/Source/CTest/cmCTestConfigureCommand.cxx +++ b/Source/CTest/cmCTestConfigureCommand.cxx @@ -1,24 +1,22 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestConfigureCommand.h" -#include "cmGlobalGenerator.h" #include "cmCTest.h" #include "cmCTestGenericHandler.h" +#include "cmGlobalGenerator.h" +#include "cmMakefile.h" +#include "cmSystemTools.h" +#include "cmake.h" + +#include <sstream> +#include <string.h> +#include <vector> cmCTestConfigureCommand::cmCTestConfigureCommand() { this->Arguments[ctc_OPTIONS] = "OPTIONS"; - this->Arguments[ctc_LAST] = 0; + this->Arguments[ctc_LAST] = CM_NULLPTR; this->Last = ctc_LAST; } @@ -26,130 +24,130 @@ cmCTestGenericHandler* cmCTestConfigureCommand::InitializeHandler() { std::vector<std::string> options; - if (this->Values[ctc_OPTIONS]) - { + if (this->Values[ctc_OPTIONS]) { cmSystemTools::ExpandListArgument(this->Values[ctc_OPTIONS], options); - } + } - if ( this->CTest->GetCTestConfiguration("BuildDirectory").empty() ) - { - this->SetError("Build directory not specified. Either use BUILD " + if (this->CTest->GetCTestConfiguration("BuildDirectory").empty()) { + this->SetError( + "Build directory not specified. Either use BUILD " "argument to CTEST_CONFIGURE command or set CTEST_BINARY_DIRECTORY " "variable"); - return 0; - } + return CM_NULLPTR; + } - const char* ctestConfigureCommand - = this->Makefile->GetDefinition("CTEST_CONFIGURE_COMMAND"); + const char* ctestConfigureCommand = + this->Makefile->GetDefinition("CTEST_CONFIGURE_COMMAND"); - if ( ctestConfigureCommand && *ctestConfigureCommand ) - { + if (ctestConfigureCommand && *ctestConfigureCommand) { this->CTest->SetCTestConfiguration("ConfigureCommand", - ctestConfigureCommand); - } - else - { - const char* cmakeGeneratorName - = this->Makefile->GetDefinition("CTEST_CMAKE_GENERATOR"); - if ( cmakeGeneratorName && *cmakeGeneratorName ) - { - const std::string& source_dir - = this->CTest->GetCTestConfiguration("SourceDirectory"); - if ( source_dir.empty() ) - { - this->SetError("Source directory not specified. Either use SOURCE " + ctestConfigureCommand, this->Quiet); + } else { + const char* cmakeGeneratorName = + this->Makefile->GetDefinition("CTEST_CMAKE_GENERATOR"); + if (cmakeGeneratorName && *cmakeGeneratorName) { + const std::string& source_dir = + this->CTest->GetCTestConfiguration("SourceDirectory"); + if (source_dir.empty()) { + this->SetError( + "Source directory not specified. Either use SOURCE " "argument to CTEST_CONFIGURE command or set CTEST_SOURCE_DIRECTORY " "variable"); - return 0; - } + return CM_NULLPTR; + } const std::string cmakelists_file = source_dir + "/CMakeLists.txt"; - if ( !cmSystemTools::FileExists(cmakelists_file.c_str()) ) - { - cmOStringStream e; - e << "CMakeLists.txt file does not exist [" - << cmakelists_file << "]"; - this->SetError(e.str().c_str()); - return 0; - } + if (!cmSystemTools::FileExists(cmakelists_file.c_str())) { + std::ostringstream e; + e << "CMakeLists.txt file does not exist [" << cmakelists_file << "]"; + this->SetError(e.str()); + return CM_NULLPTR; + } bool multiConfig = false; bool cmakeBuildTypeInOptions = false; - cmGlobalGenerator *gg = + cmGlobalGenerator* gg = this->Makefile->GetCMakeInstance()->CreateGlobalGenerator( cmakeGeneratorName); - if(gg) - { + if (gg) { multiConfig = gg->IsMultiConfig(); delete gg; - } + } std::string cmakeConfigureCommand = "\""; - cmakeConfigureCommand += this->CTest->GetCMakeExecutable(); + cmakeConfigureCommand += cmSystemTools::GetCMakeCommand(); cmakeConfigureCommand += "\""; std::vector<std::string>::const_iterator it; std::string option; - for (it= options.begin(); it!=options.end(); ++it) - { + for (it = options.begin(); it != options.end(); ++it) { option = *it; cmakeConfigureCommand += " \""; cmakeConfigureCommand += option; cmakeConfigureCommand += "\""; - if ((0 != strstr(option.c_str(), "CMAKE_BUILD_TYPE=")) || - (0 != strstr(option.c_str(), "CMAKE_BUILD_TYPE:STRING="))) - { + if ((CM_NULLPTR != strstr(option.c_str(), "CMAKE_BUILD_TYPE=")) || + (CM_NULLPTR != + strstr(option.c_str(), "CMAKE_BUILD_TYPE:STRING="))) { cmakeBuildTypeInOptions = true; - } } + } if (!multiConfig && !cmakeBuildTypeInOptions && - !this->CTest->GetConfigType().empty()) - { + !this->CTest->GetConfigType().empty()) { cmakeConfigureCommand += " \"-DCMAKE_BUILD_TYPE:STRING="; cmakeConfigureCommand += this->CTest->GetConfigType(); cmakeConfigureCommand += "\""; - } + } + + if (this->Makefile->IsOn("CTEST_USE_LAUNCHERS")) { + cmakeConfigureCommand += " \"-DCTEST_USE_LAUNCHERS:BOOL=TRUE\""; + } cmakeConfigureCommand += " \"-G"; cmakeConfigureCommand += cmakeGeneratorName; cmakeConfigureCommand += "\""; + const char* cmakeGeneratorPlatform = + this->Makefile->GetDefinition("CTEST_CMAKE_GENERATOR_PLATFORM"); + if (cmakeGeneratorPlatform && *cmakeGeneratorPlatform) { + cmakeConfigureCommand += " \"-A"; + cmakeConfigureCommand += cmakeGeneratorPlatform; + cmakeConfigureCommand += "\""; + } + const char* cmakeGeneratorToolset = this->Makefile->GetDefinition("CTEST_CMAKE_GENERATOR_TOOLSET"); - if(cmakeGeneratorToolset && *cmakeGeneratorToolset) - { + if (cmakeGeneratorToolset && *cmakeGeneratorToolset) { cmakeConfigureCommand += " \"-T"; cmakeConfigureCommand += cmakeGeneratorToolset; cmakeConfigureCommand += "\""; - } + } cmakeConfigureCommand += " \""; cmakeConfigureCommand += source_dir; cmakeConfigureCommand += "\""; - this->CTest->SetCTestConfiguration("ConfigureCommand", - cmakeConfigureCommand.c_str()); - } - else - { - this->SetError("Configure command is not specified. If this is a " + this->CTest->SetCTestConfiguration( + "ConfigureCommand", cmakeConfigureCommand.c_str(), this->Quiet); + } else { + this->SetError( + "Configure command is not specified. If this is a " "\"built with CMake\" project, set CTEST_CMAKE_GENERATOR. If not, " "set CTEST_CONFIGURE_COMMAND."); - return 0; - } + return CM_NULLPTR; } + } - cmCTestGenericHandler* handler - = this->CTest->GetInitializedHandler("configure"); - if ( !handler ) - { + cmCTestGenericHandler* handler = + this->CTest->GetInitializedHandler("configure"); + if (!handler) { this->SetError( "internal CTest error. Cannot instantiate configure handler"); - return 0; - } + return CM_NULLPTR; + } + handler->SetQuiet(this->Quiet); return handler; } diff --git a/Source/CTest/cmCTestConfigureCommand.h b/Source/CTest/cmCTestConfigureCommand.h index b343fc1e9..917f5ab8b 100644 --- a/Source/CTest/cmCTestConfigureCommand.h +++ b/Source/CTest/cmCTestConfigureCommand.h @@ -1,19 +1,17 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestConfigureCommand_h #define cmCTestConfigureCommand_h +#include "cmConfigure.h" + #include "cmCTestHandlerCommand.h" +#include <string> + +class cmCTestGenericHandler; +class cmCommand; + /** \class cmCTestConfigure * \brief Run a ctest script * @@ -27,58 +25,28 @@ public: /** * This is a virtual constructor for the command. */ - virtual cmCommand* Clone() - { + cmCommand* Clone() CM_OVERRIDE + { cmCTestConfigureCommand* ni = new cmCTestConfigureCommand; ni->CTest = this->CTest; ni->CTestScriptHandler = this->CTestScriptHandler; return ni; - } + } /** * The name of the command as specified in CMakeList.txt. */ - virtual const char* GetName() const { return "ctest_configure";} - - /** - * Succinct documentation. - */ - virtual const char* GetTerseDocumentation() const - { - return "Configure the project build tree."; - } - - /** - * More documentation. - */ - virtual const char* GetFullDocumentation() const - { - return - " ctest_configure([BUILD build_dir] [SOURCE source_dir] [APPEND]\n" - " [OPTIONS options] [RETURN_VALUE res])\n" - "Configures the given build directory and stores results in " - "Configure.xml. " - "If no BUILD is given, the CTEST_BINARY_DIRECTORY variable is used. " - "If no SOURCE is given, the CTEST_SOURCE_DIRECTORY variable is used. " - "The OPTIONS argument specifies command line arguments to pass to " - "the configuration tool. " - "The RETURN_VALUE option specifies a variable in which to store the " - "return value of the native build tool." - "\n" - CTEST_COMMAND_APPEND_OPTION_DOCS; - } - - cmTypeMacro(cmCTestConfigureCommand, cmCTestHandlerCommand); + std::string GetName() const CM_OVERRIDE { return "ctest_configure"; } protected: - cmCTestGenericHandler* InitializeHandler(); + cmCTestGenericHandler* InitializeHandler() CM_OVERRIDE; - enum { + enum + { ctc_FIRST = ct_LAST, ctc_OPTIONS, ctc_LAST }; }; - #endif diff --git a/Source/CTest/cmCTestConfigureHandler.cxx b/Source/CTest/cmCTestConfigureHandler.cxx index 7c4129864..5d87f3fb4 100644 --- a/Source/CTest/cmCTestConfigureHandler.cxx +++ b/Source/CTest/cmCTestConfigureHandler.cxx @@ -1,132 +1,104 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestConfigureHandler.h" #include "cmCTest.h" #include "cmGeneratedFileStream.h" -#include "cmake.h" -#include "cmXMLSafe.h" -#include <cmsys/Process.h> +#include "cmSystemTools.h" +#include "cmXMLWriter.h" +#include <ostream> +#include <string> -//---------------------------------------------------------------------- cmCTestConfigureHandler::cmCTestConfigureHandler() { } -//---------------------------------------------------------------------- void cmCTestConfigureHandler::Initialize() { this->Superclass::Initialize(); } -//---------------------------------------------------------------------- -//clearly it would be nice if this were broken up into a few smaller -//functions and commented... +// clearly it would be nice if this were broken up into a few smaller +// functions and commented... int cmCTestConfigureHandler::ProcessHandler() { - cmCTestLog(this->CTest, HANDLER_OUTPUT, "Configure project" << std::endl); - std::string cCommand - = this->CTest->GetCTestConfiguration("ConfigureCommand"); - if ( cCommand.size() == 0 ) - { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + "Configure project" << std::endl, this->Quiet); + std::string cCommand = + this->CTest->GetCTestConfiguration("ConfigureCommand"); + if (cCommand.empty()) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot find ConfigureCommand key in the DartConfiguration.tcl" - << std::endl); + "Cannot find ConfigureCommand key in the DartConfiguration.tcl" + << std::endl); return -1; - } + } - std::string buildDirectory - = this->CTest->GetCTestConfiguration("BuildDirectory"); - if ( buildDirectory.size() == 0 ) - { + 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); + "Cannot find BuildDirectory key in the DartConfiguration.tcl" + << std::endl); return -1; - } + } double elapsed_time_start = cmSystemTools::GetTime(); std::string output; int retVal = 0; int res = 0; - if ( !this->CTest->GetShowOnly() ) - { + if (!this->CTest->GetShowOnly()) { cmGeneratedFileStream os; - if(!this->StartResultingXML(cmCTest::PartConfigure, "Configure", os)) - { + if (!this->StartResultingXML(cmCTest::PartConfigure, "Configure", os)) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot open configure file" - << std::endl); + << std::endl); return 1; - } + } std::string start_time = this->CTest->CurrentTime(); - unsigned int start_time_time = static_cast<unsigned int>( - cmSystemTools::GetTime()); + unsigned int start_time_time = + static_cast<unsigned int>(cmSystemTools::GetTime()); cmGeneratedFileStream ofs; this->StartLogFile("Configure", ofs); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Configure with command: " - << cCommand.c_str() << std::endl); - res = this->CTest->RunMakeCommand(cCommand.c_str(), &output, - &retVal, buildDirectory.c_str(), - 0, ofs); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Configure with command: " << cCommand << std::endl, + this->Quiet); + res = this->CTest->RunMakeCommand(cCommand.c_str(), output, &retVal, + buildDirectory.c_str(), 0, ofs); - if ( ofs ) - { + if (ofs) { ofs.close(); - } - - if ( os ) - { - this->CTest->StartXML(os, this->AppendXML); - os << "<Configure>\n" - << "\t<StartDateTime>" << start_time << "</StartDateTime>" - << std::endl - << "\t<StartConfigureTime>" << start_time_time - << "</StartConfigureTime>\n"; - - if ( res == cmsysProcess_State_Exited && retVal ) - { - os << retVal; - } - os << "<ConfigureCommand>" << cCommand.c_str() << "</ConfigureCommand>" - << std::endl; - cmCTestLog(this->CTest, DEBUG, "End" << std::endl); - os << "<Log>" << cmXMLSafe(output) << "</Log>" << std::endl; - std::string end_time = this->CTest->CurrentTime(); - os << "\t<ConfigureStatus>" << retVal << "</ConfigureStatus>\n" - << "\t<EndDateTime>" << end_time << "</EndDateTime>\n" - << "\t<EndConfigureTime>" << - static_cast<unsigned int>(cmSystemTools::GetTime()) - << "</EndConfigureTime>\n" - << "<ElapsedMinutes>" - << static_cast<int>( - (cmSystemTools::GetTime() - elapsed_time_start)/6)/10.0 - << "</ElapsedMinutes>" - << "</Configure>" << std::endl; - this->CTest->EndXML(os); - } } - else - { - cmCTestLog(this->CTest, DEBUG, "Configure with command: " << cCommand - << std::endl); + + if (os) { + cmXMLWriter xml(os); + this->CTest->StartXML(xml, this->AppendXML); + xml.StartElement("Configure"); + xml.Element("StartDateTime", start_time); + xml.Element("StartConfigureTime", start_time_time); + xml.Element("ConfigureCommand", cCommand); + cmCTestOptionalLog(this->CTest, DEBUG, "End" << std::endl, this->Quiet); + xml.Element("Log", output); + xml.Element("ConfigureStatus", retVal); + xml.Element("EndDateTime", this->CTest->CurrentTime()); + xml.Element("EndConfigureTime", + static_cast<unsigned int>(cmSystemTools::GetTime())); + xml.Element( + "ElapsedMinutes", + static_cast<int>((cmSystemTools::GetTime() - elapsed_time_start) / 6) / + 10.0); + xml.EndElement(); // Configure + this->CTest->EndXML(xml); } - if (! res || retVal ) - { + } else { + cmCTestOptionalLog(this->CTest, DEBUG, + "Configure with command: " << cCommand << std::endl, + this->Quiet); + } + if (!res || retVal) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Error(s) when configuring the project" << std::endl); + "Error(s) when configuring the project" << std::endl); return -1; - } + } return 0; } diff --git a/Source/CTest/cmCTestConfigureHandler.h b/Source/CTest/cmCTestConfigureHandler.h index d880bd7df..2b452829c 100644 --- a/Source/CTest/cmCTestConfigureHandler.h +++ b/Source/CTest/cmCTestConfigureHandler.h @@ -1,21 +1,11 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestConfigureHandler_h #define cmCTestConfigureHandler_h +#include "cmConfigure.h" #include "cmCTestGenericHandler.h" -#include "cmListFileCache.h" /** \class cmCTestConfigureHandler * \brief A class that handles ctest -S invocations @@ -24,16 +14,16 @@ class cmCTestConfigureHandler : public cmCTestGenericHandler { public: - cmTypeMacro(cmCTestConfigureHandler, cmCTestGenericHandler); + typedef cmCTestGenericHandler Superclass; /* * The main entry point for this class */ - int ProcessHandler(); + int ProcessHandler() CM_OVERRIDE; cmCTestConfigureHandler(); - void Initialize(); + void Initialize() CM_OVERRIDE; }; #endif diff --git a/Source/CTest/cmCTestCoverageCommand.cxx b/Source/CTest/cmCTestCoverageCommand.cxx index 72ff720d3..535da586d 100644 --- a/Source/CTest/cmCTestCoverageCommand.cxx +++ b/Source/CTest/cmCTestCoverageCommand.cxx @@ -1,72 +1,60 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestCoverageCommand.h" #include "cmCTest.h" #include "cmCTestCoverageHandler.h" -//---------------------------------------------------------------------------- +class cmCTestGenericHandler; + cmCTestCoverageCommand::cmCTestCoverageCommand() { this->LabelsMentioned = false; } -//---------------------------------------------------------------------------- cmCTestGenericHandler* cmCTestCoverageCommand::InitializeHandler() { - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "CoverageCommand", "CTEST_COVERAGE_COMMAND"); - + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "CoverageCommand", "CTEST_COVERAGE_COMMAND", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "CoverageExtraFlags", "CTEST_COVERAGE_EXTRA_FLAGS", + this->Quiet); cmCTestCoverageHandler* handler = static_cast<cmCTestCoverageHandler*>( this->CTest->GetInitializedHandler("coverage")); - if ( !handler ) - { + if (!handler) { this->SetError("internal CTest error. Cannot instantiate test handler"); - return 0; - } + return CM_NULLPTR; + } // If a LABELS option was given, select only files with the labels. - if(this->LabelsMentioned) - { + if (this->LabelsMentioned) { handler->SetLabelFilter(this->Labels); - } + } + handler->SetQuiet(this->Quiet); return handler; } -//---------------------------------------------------------------------------- bool cmCTestCoverageCommand::CheckArgumentKeyword(std::string const& arg) { // Look for arguments specific to this command. - if(arg == "LABELS") - { + if (arg == "LABELS") { this->ArgumentDoing = ArgumentDoingLabels; this->LabelsMentioned = true; return true; - } + } // Look for other arguments. return this->Superclass::CheckArgumentKeyword(arg); } -//---------------------------------------------------------------------------- bool cmCTestCoverageCommand::CheckArgumentValue(std::string const& arg) { // Handle states specific to this command. - if(this->ArgumentDoing == ArgumentDoingLabels) - { + if (this->ArgumentDoing == ArgumentDoingLabels) { this->Labels.insert(arg); return true; - } + } // Look for other arguments. return this->Superclass::CheckArgumentValue(arg); diff --git a/Source/CTest/cmCTestCoverageCommand.h b/Source/CTest/cmCTestCoverageCommand.h index 2fe762c45..78c4f61f6 100644 --- a/Source/CTest/cmCTestCoverageCommand.h +++ b/Source/CTest/cmCTestCoverageCommand.h @@ -1,19 +1,18 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestCoverageCommand_h #define cmCTestCoverageCommand_h +#include "cmConfigure.h" + #include "cmCTestHandlerCommand.h" +#include <set> +#include <string> + +class cmCTestGenericHandler; +class cmCommand; + /** \class cmCTestCoverage * \brief Run a ctest script * @@ -22,58 +21,31 @@ class cmCTestCoverageCommand : public cmCTestHandlerCommand { public: - cmCTestCoverageCommand(); /** * This is a virtual constructor for the command. */ - virtual cmCommand* Clone() - { + cmCommand* Clone() CM_OVERRIDE + { cmCTestCoverageCommand* ni = new cmCTestCoverageCommand; ni->CTest = this->CTest; ni->CTestScriptHandler = this->CTestScriptHandler; return ni; - } + } /** * The name of the command as specified in CMakeList.txt. */ - virtual const char* GetName() const { return "ctest_coverage";} + std::string GetName() const CM_OVERRIDE { return "ctest_coverage"; } - /** - * Succinct documentation. - */ - virtual const char* GetTerseDocumentation() const - { - return "Collect coverage tool results."; - } - - /** - * More documentation. - */ - virtual const char* GetFullDocumentation() const - { - return - " ctest_coverage([BUILD build_dir] [RETURN_VALUE res] [APPEND]\n" - " [LABELS label1 [label2 [...]]])\n" - "Perform the coverage of the given build directory and stores results " - "in Coverage.xml. The second argument is a variable that will hold " - "value." - "\n" - "The LABELS option filters the coverage report to include only " - "source files labeled with at least one of the labels specified." - "\n" - CTEST_COMMAND_APPEND_OPTION_DOCS; - } - - cmTypeMacro(cmCTestCoverageCommand, cmCTestHandlerCommand); + typedef cmCTestHandlerCommand Superclass; protected: - cmCTestGenericHandler* InitializeHandler(); + cmCTestGenericHandler* InitializeHandler() CM_OVERRIDE; - virtual bool CheckArgumentKeyword(std::string const& arg); - virtual bool CheckArgumentValue(std::string const& arg); + bool CheckArgumentKeyword(std::string const& arg) CM_OVERRIDE; + bool CheckArgumentValue(std::string const& arg) CM_OVERRIDE; enum { @@ -82,9 +54,7 @@ protected: }; bool LabelsMentioned; - std::set<cmStdString> Labels; + std::set<std::string> Labels; }; - #endif - diff --git a/Source/CTest/cmCTestCoverageHandler.cxx b/Source/CTest/cmCTestCoverageHandler.cxx index 20aded2b5..28fae1c7e 100644 --- a/Source/CTest/cmCTestCoverageHandler.cxx +++ b/Source/CTest/cmCTestCoverageHandler.cxx @@ -1,124 +1,109 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestCoverageHandler.h" -#include "cmParsePHPCoverage.h" -#include "cmParseGTMCoverage.h" -#include "cmParseCacheCoverage.h" + #include "cmCTest.h" -#include "cmake.h" -#include "cmMakefile.h" -#include "cmSystemTools.h" #include "cmGeneratedFileStream.h" -#include "cmXMLSafe.h" - -#include <cmsys/Process.h> -#include <cmsys/RegularExpression.hxx> -#include <cmsys/Glob.hxx> -#include <cmsys/stl/iterator> -#include <cmsys/stl/algorithm> +#include "cmParseBlanketJSCoverage.h" +#include "cmParseCacheCoverage.h" +#include "cmParseCoberturaCoverage.h" +#include "cmParseDelphiCoverage.h" +#include "cmParseGTMCoverage.h" +#include "cmParseJacocoCoverage.h" +#include "cmParsePHPCoverage.h" +#include "cmSystemTools.h" +#include "cmWorkingDirectory.h" +#include "cmXMLWriter.h" +#include "cmake.h" +#include "cmsys/FStream.hxx" +#include "cmsys/Glob.hxx" +#include "cmsys/Process.h" +#include "cmsys/RegularExpression.hxx" +#include <algorithm> +#include <iomanip> +#include <iterator> +#include <sstream> +#include <stdio.h> #include <stdlib.h> -#include <math.h> -#include <float.h> +#include <utility> -#define SAFEDIV(x,y) (((y)!=0)?((x)/(y)):(0)) +class cmMakefile; + +#define SAFEDIV(x, y) (((y) != 0) ? ((x) / (y)) : (0)) class cmCTestRunProcess { public: cmCTestRunProcess() - { - this->Process = cmsysProcess_New(); - this->PipeState = -1; - this->TimeOut = -1; - } + { + this->Process = cmsysProcess_New(); + this->PipeState = -1; + this->TimeOut = -1; + } ~cmCTestRunProcess() - { - if(!(this->PipeState == -1) - && !(this->PipeState == cmsysProcess_Pipe_None ) - && !(this->PipeState == cmsysProcess_Pipe_Timeout)) - { - this->WaitForExit(); - } - cmsysProcess_Delete(this->Process); - } + { + if (!(this->PipeState == -1) && + !(this->PipeState == cmsysProcess_Pipe_None) && + !(this->PipeState == cmsysProcess_Pipe_Timeout)) { + this->WaitForExit(); + } + cmsysProcess_Delete(this->Process); + } void SetCommand(const char* command) - { - this->CommandLineStrings.clear(); - this->CommandLineStrings.push_back(command);; - } + { + this->CommandLineStrings.clear(); + this->CommandLineStrings.push_back(command); + ; + } void AddArgument(const char* arg) - { - if(arg) - { - this->CommandLineStrings.push_back(arg); - } - } - void SetWorkingDirectory(const char* dir) - { - this->WorkingDirectory = dir; - } - void SetTimeout(double t) - { - this->TimeOut = t; + { + if (arg) { + this->CommandLineStrings.push_back(arg); } + } + void SetWorkingDirectory(const char* dir) { this->WorkingDirectory = dir; } + void SetTimeout(double t) { this->TimeOut = t; } bool StartProcess() - { - std::vector<const char*> args; - for(std::vector<std::string>::iterator i = - this->CommandLineStrings.begin(); - i != this->CommandLineStrings.end(); ++i) - { - args.push_back(i->c_str()); - } - args.push_back(0); // null terminate - cmsysProcess_SetCommand(this->Process, &*args.begin()); - if(this->WorkingDirectory.size()) - { - cmsysProcess_SetWorkingDirectory(this->Process, - this->WorkingDirectory.c_str()); - } - - cmsysProcess_SetOption(this->Process, - cmsysProcess_Option_HideWindow, 1); - if(this->TimeOut != -1) - { - cmsysProcess_SetTimeout(this->Process, this->TimeOut); - } - cmsysProcess_Execute(this->Process); - this->PipeState = cmsysProcess_GetState(this->Process); - // if the process is running or exited return true - if(this->PipeState == cmsysProcess_State_Executing - || this->PipeState == cmsysProcess_State_Exited) - { - return true; - } - return false; - } + { + std::vector<const char*> args; + for (std::vector<std::string>::iterator i = + this->CommandLineStrings.begin(); + i != this->CommandLineStrings.end(); ++i) { + args.push_back(i->c_str()); + } + args.push_back(CM_NULLPTR); // null terminate + cmsysProcess_SetCommand(this->Process, &*args.begin()); + if (!this->WorkingDirectory.empty()) { + cmsysProcess_SetWorkingDirectory(this->Process, + this->WorkingDirectory.c_str()); + } + + cmsysProcess_SetOption(this->Process, cmsysProcess_Option_HideWindow, 1); + if (this->TimeOut != -1) { + cmsysProcess_SetTimeout(this->Process, this->TimeOut); + } + cmsysProcess_Execute(this->Process); + this->PipeState = cmsysProcess_GetState(this->Process); + // if the process is running or exited return true + return this->PipeState == cmsysProcess_State_Executing || + this->PipeState == cmsysProcess_State_Exited; + } void SetStdoutFile(const char* fname) - { + { cmsysProcess_SetPipeFile(this->Process, cmsysProcess_Pipe_STDOUT, fname); - } + } void SetStderrFile(const char* fname) - { + { cmsysProcess_SetPipeFile(this->Process, cmsysProcess_Pipe_STDERR, fname); - } - int WaitForExit(double* timeout =0) - { - this->PipeState = cmsysProcess_WaitForExit(this->Process, - timeout); - return this->PipeState; - } - int GetProcessState() { return this->PipeState;} + } + int WaitForExit(double* timeout = CM_NULLPTR) + { + this->PipeState = cmsysProcess_WaitForExit(this->Process, timeout); + return this->PipeState; + } + int GetProcessState() { return this->PipeState; } + private: int PipeState; cmsysProcess* Process; @@ -127,26 +112,21 @@ private: double TimeOut; }; - -//---------------------------------------------------------------------- - -//---------------------------------------------------------------------- cmCTestCoverageHandler::cmCTestCoverageHandler() { } -//---------------------------------------------------------------------- void cmCTestCoverageHandler::Initialize() { this->Superclass::Initialize(); this->CustomCoverageExclude.clear(); this->SourceLabels.clear(); + this->TargetDirs.clear(); this->LabelIdMap.clear(); this->Labels.clear(); this->LabelFilter.clear(); } -//---------------------------------------------------------------------------- void cmCTestCoverageHandler::CleanCoverageLogFiles(std::ostream& log) { std::string logGlob = this->CTest->GetCTestConfiguration("BuildDirectory"); @@ -154,195 +134,172 @@ void cmCTestCoverageHandler::CleanCoverageLogFiles(std::ostream& log) logGlob += this->CTest->GetCurrentTag(); logGlob += "/CoverageLog*"; cmsys::Glob gl; - gl.FindFiles(logGlob.c_str()); + gl.FindFiles(logGlob); std::vector<std::string> const& files = gl.GetFiles(); - for(std::vector<std::string>::const_iterator fi = files.begin(); - fi != files.end(); ++fi) - { + for (std::vector<std::string>::const_iterator fi = files.begin(); + fi != files.end(); ++fi) { log << "Removing old coverage log: " << *fi << "\n"; - cmSystemTools::RemoveFile(fi->c_str()); - } + cmSystemTools::RemoveFile(*fi); + } } -//---------------------------------------------------------------------- bool cmCTestCoverageHandler::StartCoverageLogFile( cmGeneratedFileStream& covLogFile, int logFileCount) { char covLogFilename[1024]; sprintf(covLogFilename, "CoverageLog-%d", logFileCount); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Open file: " - << covLogFilename << std::endl); - if(!this->StartResultingXML(cmCTest::PartCoverage, - covLogFilename, covLogFile)) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot open log file: " - << covLogFilename << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Open file: " << covLogFilename << std::endl, + this->Quiet); + if (!this->StartResultingXML(cmCTest::PartCoverage, covLogFilename, + covLogFile)) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Cannot open log file: " << covLogFilename << std::endl); return false; - } - std::string local_start_time = this->CTest->CurrentTime(); - this->CTest->StartXML(covLogFile, this->AppendXML); - covLogFile << "<CoverageLog>" << std::endl - << "\t<StartDateTime>" << local_start_time << "</StartDateTime>" - << "\t<StartTime>" - << static_cast<unsigned int>(cmSystemTools::GetTime()) - << "</StartTime>" - << std::endl; + } return true; } -//---------------------------------------------------------------------- void cmCTestCoverageHandler::EndCoverageLogFile(cmGeneratedFileStream& ostr, - int logFileCount) + int logFileCount) { - std::string local_end_time = this->CTest->CurrentTime(); - ostr << "\t<EndDateTime>" << local_end_time << "</EndDateTime>" << std::endl - << "\t<EndTime>" << - static_cast<unsigned int>(cmSystemTools::GetTime()) - << "</EndTime>" << std::endl - << "</CoverageLog>" << std::endl; - this->CTest->EndXML(ostr); char covLogFilename[1024]; sprintf(covLogFilename, "CoverageLog-%d.xml", logFileCount); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Close file: " - << covLogFilename << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Close file: " << covLogFilename << std::endl, + this->Quiet); ostr.Close(); } -//---------------------------------------------------------------------- +void cmCTestCoverageHandler::StartCoverageLogXML(cmXMLWriter& xml) +{ + this->CTest->StartXML(xml, this->AppendXML); + xml.StartElement("CoverageLog"); + xml.Element("StartDateTime", this->CTest->CurrentTime()); + xml.Element("StartTime", + static_cast<unsigned int>(cmSystemTools::GetTime())); +} + +void cmCTestCoverageHandler::EndCoverageLogXML(cmXMLWriter& xml) +{ + xml.Element("EndDateTime", this->CTest->CurrentTime()); + xml.Element("EndTime", static_cast<unsigned int>(cmSystemTools::GetTime())); + xml.EndElement(); // CoverageLog + this->CTest->EndXML(xml); +} + bool cmCTestCoverageHandler::ShouldIDoCoverage(const char* file, - const char* srcDir, - const char* binDir) + const char* srcDir, + const char* binDir) { - if(this->IsFilteredOut(file)) - { + if (this->IsFilteredOut(file)) { return false; - } + } std::vector<cmsys::RegularExpression>::iterator sit; - for ( sit = this->CustomCoverageExcludeRegex.begin(); - sit != this->CustomCoverageExcludeRegex.end(); ++ sit ) - { - if ( sit->find(file) ) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " File " << file - << " is excluded in CTestCustom.ctest" << std::endl;); + for (sit = this->CustomCoverageExcludeRegex.begin(); + sit != this->CustomCoverageExcludeRegex.end(); ++sit) { + if (sit->find(file)) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " File " + << file << " is excluded in CTestCustom.ctest" + << std::endl; + , this->Quiet); return false; - } } + } std::string fSrcDir = cmSystemTools::CollapseFullPath(srcDir); std::string fBinDir = cmSystemTools::CollapseFullPath(binDir); std::string fFile = cmSystemTools::CollapseFullPath(file); - bool sourceSubDir = cmSystemTools::IsSubDirectory(fFile.c_str(), - fSrcDir.c_str()); - bool buildSubDir = cmSystemTools::IsSubDirectory(fFile.c_str(), - fBinDir.c_str()); + bool sourceSubDir = cmSystemTools::IsSubDirectory(fFile, fSrcDir); + bool buildSubDir = cmSystemTools::IsSubDirectory(fFile, fBinDir); // Always check parent directory of the file. - std::string fileDir = cmSystemTools::GetFilenamePath(fFile.c_str()); + std::string fileDir = cmSystemTools::GetFilenamePath(fFile); std::string checkDir; // We also need to check the binary/source directory pair. - if ( sourceSubDir && buildSubDir ) - { - if ( fSrcDir.size() > fBinDir.size() ) - { + if (sourceSubDir && buildSubDir) { + if (fSrcDir.size() > fBinDir.size()) { checkDir = fSrcDir; - } - else - { + } else { checkDir = fBinDir; - } } - else if ( sourceSubDir ) - { + } else if (sourceSubDir) { checkDir = fSrcDir; - } - else if ( buildSubDir ) - { + } else if (buildSubDir) { checkDir = fBinDir; - } - std::string ndc - = cmSystemTools::FileExistsInParentDirectories(".NoDartCoverage", - fFile.c_str(), checkDir.c_str()); - if ( ndc.size() ) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Found: " << ndc.c_str() - << " so skip coverage of " << file << std::endl); + } + std::string ndc = cmSystemTools::FileExistsInParentDirectories( + ".NoDartCoverage", fFile.c_str(), checkDir.c_str()); + if (!ndc.empty()) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Found: " << ndc << " so skip coverage of " << file + << std::endl, + this->Quiet); return false; - } + } // By now checkDir should be set to parent directory of the file. // Get the relative path to the file an apply it to the opposite directory. // If it is the same as fileDir, then ignore, otherwise check. std::string relPath; - if(checkDir.size() ) - { - relPath = cmSystemTools::RelativePath(checkDir.c_str(), - fFile.c_str()); - } - else - { + if (!checkDir.empty()) { + relPath = cmSystemTools::RelativePath(checkDir.c_str(), fFile.c_str()); + } else { relPath = fFile; - } - if ( checkDir == fSrcDir ) - { + } + if (checkDir == fSrcDir) { checkDir = fBinDir; - } - else - { + } else { checkDir = fSrcDir; - } + } fFile = checkDir + "/" + relPath; - fFile = cmSystemTools::GetFilenamePath(fFile.c_str()); + fFile = cmSystemTools::GetFilenamePath(fFile); - if ( fileDir == fFile ) - { + if (fileDir == fFile) { // This is in-source build, so we trust the previous check. return true; - } - - ndc = cmSystemTools::FileExistsInParentDirectories(".NoDartCoverage", - fFile.c_str(), checkDir.c_str()); - if ( ndc.size() ) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Found: " << ndc.c_str() - << " so skip coverage of: " << file << std::endl); + } + + ndc = cmSystemTools::FileExistsInParentDirectories( + ".NoDartCoverage", fFile.c_str(), checkDir.c_str()); + if (!ndc.empty()) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Found: " << ndc << " so skip coverage of: " << file + << std::endl, + this->Quiet); return false; - } + } // Ok, nothing in source tree, nothing in binary tree return true; } -//---------------------------------------------------------------------- -//clearly it would be nice if this were broken up into a few smaller -//functions and commented... +// clearly it would be nice if this were broken up into a few smaller +// functions and commented... int cmCTestCoverageHandler::ProcessHandler() { this->CTest->ClearSubmitFiles(cmCTest::PartCoverage); int error = 0; // do we have time for this - if (this->CTest->GetRemainingTimeAllowed() < 120) - { + if (this->CTest->GetRemainingTimeAllowed() < 120) { return error; - } + } std::string coverage_start_time = this->CTest->CurrentTime(); - unsigned int coverage_start_time_time = static_cast<unsigned int>( - cmSystemTools::GetTime()); - std::string sourceDir - = this->CTest->GetCTestConfiguration("SourceDirectory"); - std::string binaryDir - = this->CTest->GetCTestConfiguration("BuildDirectory"); + unsigned int coverage_start_time_time = + static_cast<unsigned int>(cmSystemTools::GetTime()); + std::string sourceDir = + this->CTest->GetCTestConfiguration("SourceDirectory"); + std::string binaryDir = this->CTest->GetCTestConfiguration("BuildDirectory"); this->LoadLabels(); cmGeneratedFileStream ofs; double elapsed_time_start = cmSystemTools::GetTime(); - if ( !this->StartLogFile("Coverage", ofs) ) - { + if (!this->StartLogFile("Coverage", ofs)) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot create LastCoverage.log file" << std::endl); - } + "Cannot create LastCoverage.log file" << std::endl); + } ofs << "Performing coverage: " << elapsed_time_start << std::endl; this->CleanCoverageLogFiles(ofs); @@ -350,376 +307,384 @@ int cmCTestCoverageHandler::ProcessHandler() cmSystemTools::ConvertToUnixSlashes(sourceDir); cmSystemTools::ConvertToUnixSlashes(binaryDir); - cmCTestLog(this->CTest, HANDLER_OUTPUT, "Performing coverage" << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + "Performing coverage" << std::endl, this->Quiet); cmCTestCoverageHandlerContainer cont; cont.Error = error; cont.SourceDir = sourceDir; cont.BinaryDir = binaryDir; cont.OFS = &ofs; + cont.Quiet = this->Quiet; // setup the regex exclude stuff this->CustomCoverageExcludeRegex.clear(); - std::vector<cmStdString>::iterator rexIt; - for ( rexIt = this->CustomCoverageExclude.begin(); - rexIt != this->CustomCoverageExclude.end(); - ++ rexIt ) - { + std::vector<std::string>::iterator rexIt; + for (rexIt = this->CustomCoverageExclude.begin(); + rexIt != this->CustomCoverageExclude.end(); ++rexIt) { this->CustomCoverageExcludeRegex.push_back( cmsys::RegularExpression(rexIt->c_str())); - } + } - if(this->HandleBullseyeCoverage(&cont)) - { + if (this->HandleBullseyeCoverage(&cont)) { return cont.Error; - } + } int file_count = 0; file_count += this->HandleGCovCoverage(&cont); error = cont.Error; - if ( file_count < 0 ) - { + if (file_count < 0) { return error; - } + } + file_count += this->HandleLCovCoverage(&cont); + error = cont.Error; + if (file_count < 0) { + return error; + } file_count += this->HandleTracePyCoverage(&cont); error = cont.Error; - if ( file_count < 0 ) - { + if (file_count < 0) { return error; - } + } file_count += this->HandlePHPCoverage(&cont); error = cont.Error; - if ( file_count < 0 ) - { + if (file_count < 0) { return error; - } + } + file_count += this->HandleCoberturaCoverage(&cont); + error = cont.Error; + if (file_count < 0) { + return error; + } + file_count += this->HandleMumpsCoverage(&cont); error = cont.Error; - if ( file_count < 0 ) - { + if (file_count < 0) { return error; - } + } + file_count += this->HandleJacocoCoverage(&cont); + error = cont.Error; + if (file_count < 0) { + return error; + } + + file_count += this->HandleBlanketJSCoverage(&cont); + error = cont.Error; + if (file_count < 0) { + return error; + } + + file_count += this->HandleDelphiCoverage(&cont); + error = cont.Error; + if (file_count < 0) { + return error; + } std::set<std::string> uncovered = this->FindUncoveredFiles(&cont); - if ( file_count == 0 ) - { - cmCTestLog(this->CTest, WARNING, + if (file_count == 0 && this->ExtraCoverageGlobs.empty()) { + cmCTestOptionalLog( + this->CTest, WARNING, " Cannot find any coverage files. Ignoring Coverage request." - << std::endl); + << std::endl, + this->Quiet); return error; - } + } cmGeneratedFileStream covSumFile; cmGeneratedFileStream covLogFile; + cmXMLWriter covSumXML(covSumFile); + cmXMLWriter covLogXML(covLogFile); - if(!this->StartResultingXML(cmCTest::PartCoverage, "Coverage", covSumFile)) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot open coverage summary file." << std::endl); + if (!this->StartResultingXML(cmCTest::PartCoverage, "Coverage", + covSumFile)) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot open coverage summary file." + << std::endl); return -1; - } + } + covSumFile.setf(std::ios::fixed, std::ios::floatfield); + covSumFile.precision(2); - this->CTest->StartXML(covSumFile, this->AppendXML); + this->CTest->StartXML(covSumXML, this->AppendXML); // Produce output xml files - covSumFile << "<Coverage>" << std::endl - << "\t<StartDateTime>" << coverage_start_time << "</StartDateTime>" - << std::endl - << "\t<StartTime>" << coverage_start_time_time << "</StartTime>" - << std::endl; + covSumXML.StartElement("Coverage"); + covSumXML.Element("StartDateTime", coverage_start_time); + covSumXML.Element("StartTime", coverage_start_time_time); int logFileCount = 0; - if ( !this->StartCoverageLogFile(covLogFile, logFileCount) ) - { + if (!this->StartCoverageLogFile(covLogFile, logFileCount)) { return -1; - } + } + this->StartCoverageLogXML(covLogXML); cmCTestCoverageHandlerContainer::TotalCoverageMap::iterator fileIterator; int cnt = 0; long total_tested = 0; long total_untested = 0; - //std::string fullSourceDir = sourceDir + "/"; - //std::string fullBinaryDir = binaryDir + "/"; - cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); - cmCTestLog(this->CTest, HANDLER_OUTPUT, - " Accumulating results (each . represents one file):" << std::endl); - cmCTestLog(this->CTest, HANDLER_OUTPUT, " "); + // std::string fullSourceDir = sourceDir + "/"; + // std::string fullBinaryDir = binaryDir + "/"; + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, std::endl, this->Quiet); + cmCTestOptionalLog( + this->CTest, HANDLER_OUTPUT, + " Accumulating results (each . represents one file):" << std::endl, + this->Quiet); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " ", this->Quiet); std::vector<std::string> errorsWhileAccumulating; file_count = 0; - for ( fileIterator = cont.TotalCoverage.begin(); - fileIterator != cont.TotalCoverage.end(); - ++fileIterator ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush); - file_count ++; - if ( file_count % 50 == 0 ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " processed: " << file_count - << " out of " - << cont.TotalCoverage.size() << std::endl); - cmCTestLog(this->CTest, HANDLER_OUTPUT, " "); - } + for (fileIterator = cont.TotalCoverage.begin(); + fileIterator != cont.TotalCoverage.end(); ++fileIterator) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "." << std::flush, + this->Quiet); + file_count++; + if (file_count % 50 == 0) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " processed: " + << file_count << " out of " + << cont.TotalCoverage.size() << std::endl, + this->Quiet); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " ", this->Quiet); + } const std::string fullFileName = fileIterator->first; - bool shouldIDoCoverage - = this->ShouldIDoCoverage(fullFileName.c_str(), - sourceDir.c_str(), binaryDir.c_str()); - if ( !shouldIDoCoverage ) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - ".NoDartCoverage found, so skip coverage check for: " - << fullFileName.c_str() - << std::endl); + bool shouldIDoCoverage = this->ShouldIDoCoverage( + fullFileName.c_str(), sourceDir.c_str(), binaryDir.c_str()); + if (!shouldIDoCoverage) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + ".NoDartCoverage found, so skip coverage check for: " + << fullFileName << std::endl, + this->Quiet); continue; - } + } - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Process file: " << fullFileName << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Process file: " << fullFileName << std::endl, + this->Quiet); - if ( !cmSystemTools::FileExists(fullFileName.c_str()) ) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot find file: " - << fullFileName.c_str() << std::endl); + if (!cmSystemTools::FileExists(fullFileName.c_str())) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Cannot find file: " << fullFileName << std::endl); continue; - } + } - if ( ++cnt % 100 == 0 ) - { + if (++cnt % 100 == 0) { + this->EndCoverageLogXML(covLogXML); this->EndCoverageLogFile(covLogFile, logFileCount); - logFileCount ++; - if ( !this->StartCoverageLogFile(covLogFile, logFileCount) ) - { + logFileCount++; + if (!this->StartCoverageLogFile(covLogFile, logFileCount)) { return -1; - } } + this->StartCoverageLogXML(covLogXML); + } - const std::string fileName - = cmSystemTools::GetFilenameName(fullFileName.c_str()); + const std::string fileName = cmSystemTools::GetFilenameName(fullFileName); std::string shortFileName = this->CTest->GetShortPathToFile(fullFileName.c_str()); - const cmCTestCoverageHandlerContainer::SingleFileCoverageVector& fcov - = fileIterator->second; - covLogFile << "\t<File Name=\"" << cmXMLSafe(fileName) - << "\" FullPath=\"" << cmXMLSafe(shortFileName) << "\">\n" - << "\t\t<Report>" << std::endl; - - std::ifstream ifs(fullFileName.c_str()); - if ( !ifs) - { - cmOStringStream ostr; - ostr << "Cannot open source file: " << fullFileName.c_str(); + const cmCTestCoverageHandlerContainer::SingleFileCoverageVector& fcov = + fileIterator->second; + covLogXML.StartElement("File"); + covLogXML.Attribute("Name", fileName); + covLogXML.Attribute("FullPath", shortFileName); + covLogXML.StartElement("Report"); + + cmsys::ifstream ifs(fullFileName.c_str()); + if (!ifs) { + std::ostringstream ostr; + ostr << "Cannot open source file: " << fullFileName; errorsWhileAccumulating.push_back(ostr.str()); - error ++; + error++; continue; - } + } int tested = 0; int untested = 0; cmCTestCoverageHandlerContainer::SingleFileCoverageVector::size_type cc; std::string line; - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Actually performing coverage for: " << fullFileName << std::endl); - for ( cc= 0; cc < fcov.size(); cc ++ ) - { - if ( !cmSystemTools::GetLineFromStream(ifs, line) && - cc != fcov.size() -1 ) - { - cmOStringStream ostr; - ostr << "Problem reading source file: " << fullFileName.c_str() - << " line:" << cc << " out total: " << fcov.size()-1; + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Actually performing coverage for: " << fullFileName + << std::endl, + this->Quiet); + for (cc = 0; cc < fcov.size(); cc++) { + if (!cmSystemTools::GetLineFromStream(ifs, line) && + cc != fcov.size() - 1) { + std::ostringstream ostr; + ostr << "Problem reading source file: " << fullFileName + << " line:" << cc << " out total: " << fcov.size() - 1; errorsWhileAccumulating.push_back(ostr.str()); - error ++; + error++; break; - } - covLogFile << "\t\t<Line Number=\"" << cc << "\" Count=\"" << fcov[cc] - << "\">" - << cmXMLSafe(line) << "</Line>" << std::endl; - if ( fcov[cc] == 0 ) - { - untested ++; - } - else if ( fcov[cc] > 0 ) - { - tested ++; - } } - if ( cmSystemTools::GetLineFromStream(ifs, line) ) - { - cmOStringStream ostr; - ostr << "Looks like there are more lines in the file: " << line; - errorsWhileAccumulating.push_back(ostr.str()); + covLogXML.StartElement("Line"); + covLogXML.Attribute("Number", cc); + covLogXML.Attribute("Count", fcov[cc]); + covLogXML.Content(line); + covLogXML.EndElement(); // Line + if (fcov[cc] == 0) { + untested++; + } else if (fcov[cc] > 0) { + tested++; } + } + if (cmSystemTools::GetLineFromStream(ifs, line)) { + std::ostringstream ostr; + ostr << "Looks like there are more lines in the file: " << fullFileName; + errorsWhileAccumulating.push_back(ostr.str()); + } float cper = 0; float cmet = 0; - if ( tested + untested > 0 ) - { + if (tested + untested > 0) { cper = (100 * SAFEDIV(static_cast<float>(tested), - static_cast<float>(tested + untested))); - cmet = ( SAFEDIV(static_cast<float>(tested + 10), - static_cast<float>(tested + untested + 10))); - } + static_cast<float>(tested + untested))); + cmet = (SAFEDIV(static_cast<float>(tested + 10), + static_cast<float>(tested + untested + 10))); + } total_tested += tested; total_untested += untested; - covLogFile << "\t\t</Report>" << std::endl - << "\t</File>" << std::endl; - covSumFile << "\t<File Name=\"" << cmXMLSafe(fileName) - << "\" FullPath=\"" << cmXMLSafe( - this->CTest->GetShortPathToFile(fullFileName.c_str())) - << "\" Covered=\"" << (tested+untested > 0 ? "true":"false") << "\">\n" - << "\t\t<LOCTested>" << tested << "</LOCTested>\n" - << "\t\t<LOCUnTested>" << untested << "</LOCUnTested>\n" - << "\t\t<PercentCoverage>"; - covSumFile.setf(std::ios::fixed, std::ios::floatfield); - covSumFile.precision(2); - covSumFile << (cper) << "</PercentCoverage>\n" - << "\t\t<CoverageMetric>"; - covSumFile.setf(std::ios::fixed, std::ios::floatfield); - covSumFile.precision(2); - covSumFile << (cmet) << "</CoverageMetric>\n"; - this->WriteXMLLabels(covSumFile, shortFileName); - covSumFile << "\t</File>" << std::endl; - } - - //Handle all the files in the extra coverage globs that have no cov data - for(std::set<std::string>::iterator i = uncovered.begin(); - i != uncovered.end(); ++i) - { + covLogXML.EndElement(); // Report + covLogXML.EndElement(); // File + covSumXML.StartElement("File"); + covSumXML.Attribute("Name", fileName); + covSumXML.Attribute("FullPath", + this->CTest->GetShortPathToFile(fullFileName.c_str())); + covSumXML.Attribute("Covered", tested + untested > 0 ? "true" : "false"); + covSumXML.Element("LOCTested", tested); + covSumXML.Element("LOCUnTested", untested); + covSumXML.Element("PercentCoverage", cper); + covSumXML.Element("CoverageMetric", cmet); + this->WriteXMLLabels(covSumXML, shortFileName); + covSumXML.EndElement(); // File + } + + // Handle all the files in the extra coverage globs that have no cov data + for (std::set<std::string>::iterator i = uncovered.begin(); + i != uncovered.end(); ++i) { std::string fileName = cmSystemTools::GetFilenameName(*i); std::string fullPath = cont.SourceDir + "/" + *i; - covLogFile << "\t<File Name=\"" << cmXMLSafe(fileName) - << "\" FullPath=\"" << cmXMLSafe(*i) << "\">\n" - << "\t\t<Report>" << std::endl; + covLogXML.StartElement("File"); + covLogXML.Attribute("Name", fileName); + covLogXML.Attribute("FullPath", *i); + covLogXML.StartElement("Report"); - std::ifstream ifs(fullPath.c_str()); - if (!ifs) - { - cmOStringStream ostr; - ostr << "Cannot open source file: " << fullPath.c_str(); + cmsys::ifstream ifs(fullPath.c_str()); + if (!ifs) { + std::ostringstream ostr; + ostr << "Cannot open source file: " << fullPath; errorsWhileAccumulating.push_back(ostr.str()); - error ++; + error++; + covLogXML.EndElement(); // Report + covLogXML.EndElement(); // File continue; - } + } int untested = 0; std::string line; - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Actually performing coverage for: " << i->c_str() << std::endl); - while (cmSystemTools::GetLineFromStream(ifs, line)) - { - covLogFile << "\t\t<Line Number=\"" << untested << "\" Count=\"0\">" - << cmXMLSafe(line) << "</Line>" << std::endl; - untested ++; - } - covLogFile << "\t\t</Report>\n\t</File>" << std::endl; + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Actually performing coverage for: " << *i << std::endl, + this->Quiet); + while (cmSystemTools::GetLineFromStream(ifs, line)) { + covLogXML.StartElement("Line"); + covLogXML.Attribute("Number", untested); + covLogXML.Attribute("Count", 0); + covLogXML.Content(line); + covLogXML.EndElement(); // Line + untested++; + } + covLogXML.EndElement(); // Report + covLogXML.EndElement(); // File total_untested += untested; - covSumFile << "\t<File Name=\"" << cmXMLSafe(fileName) - << "\" FullPath=\"" << cmXMLSafe(i->c_str()) - << "\" Covered=\"true\">\n" - << "\t\t<LOCTested>0</LOCTested>\n" - << "\t\t<LOCUnTested>" << untested << "</LOCUnTested>\n" - << "\t\t<PercentCoverage>0</PercentCoverage>\n" - << "\t\t<CoverageMetric>0</CoverageMetric>\n"; - this->WriteXMLLabels(covSumFile, *i); - covSumFile << "\t</File>" << std::endl; - } - + covSumXML.StartElement("File"); + covSumXML.Attribute("Name", fileName); + covSumXML.Attribute("FullPath", *i); + covSumXML.Attribute("Covered", "true"); + covSumXML.Element("LOCTested", 0); + covSumXML.Element("LOCUnTested", untested); + covSumXML.Element("PercentCoverage", 0); + covSumXML.Element("CoverageMetric", 0); + this->WriteXMLLabels(covSumXML, *i); + covSumXML.EndElement(); // File + } + + this->EndCoverageLogXML(covLogXML); this->EndCoverageLogFile(covLogFile, logFileCount); - if ( errorsWhileAccumulating.size() > 0 ) - { + if (!errorsWhileAccumulating.empty()) { cmCTestLog(this->CTest, ERROR_MESSAGE, std::endl); cmCTestLog(this->CTest, ERROR_MESSAGE, - "Error(s) while accumulating results:" << std::endl); + "Error(s) while accumulating results:" << std::endl); std::vector<std::string>::iterator erIt; - for ( erIt = errorsWhileAccumulating.begin(); - erIt != errorsWhileAccumulating.end(); - ++ erIt ) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - " " << erIt->c_str() << std::endl); - } + for (erIt = errorsWhileAccumulating.begin(); + erIt != errorsWhileAccumulating.end(); ++erIt) { + cmCTestLog(this->CTest, ERROR_MESSAGE, " " << *erIt << std::endl); } + } long total_lines = total_tested + total_untested; - float percent_coverage = 100 * SAFEDIV(static_cast<float>(total_tested), - static_cast<float>(total_lines)); - if ( total_lines == 0 ) - { + float percent_coverage = 100 * + SAFEDIV(static_cast<float>(total_tested), static_cast<float>(total_lines)); + if (total_lines == 0) { percent_coverage = 0; - } + } std::string end_time = this->CTest->CurrentTime(); - covSumFile << "\t<LOCTested>" << total_tested << "</LOCTested>\n" - << "\t<LOCUntested>" << total_untested << "</LOCUntested>\n" - << "\t<LOC>" << total_lines << "</LOC>\n" - << "\t<PercentCoverage>"; - covSumFile.setf(std::ios::fixed, std::ios::floatfield); - covSumFile.precision(2); - covSumFile << (percent_coverage)<< "</PercentCoverage>\n" - << "\t<EndDateTime>" << end_time << "</EndDateTime>\n" - << "\t<EndTime>" << - static_cast<unsigned int>(cmSystemTools::GetTime()) - << "</EndTime>\n"; - covSumFile << "<ElapsedMinutes>" << - static_cast<int>((cmSystemTools::GetTime() - elapsed_time_start)/6)/10.0 - << "</ElapsedMinutes>" - << "</Coverage>" << std::endl; - this->CTest->EndXML(covSumFile); - - cmCTestLog(this->CTest, HANDLER_OUTPUT, "" << std::endl - << "\tCovered LOC: " - << total_tested << std::endl - << "\tNot covered LOC: " << total_untested << std::endl - << "\tTotal LOC: " << total_lines << std::endl - << "\tPercentage Coverage: " - << std::setiosflags(std::ios::fixed) - << std::setprecision(2) - << (percent_coverage) << "%" << std::endl); + covSumXML.Element("LOCTested", total_tested); + covSumXML.Element("LOCUntested", total_untested); + covSumXML.Element("LOC", total_lines); + covSumXML.Element("PercentCoverage", percent_coverage); + covSumXML.Element("EndDateTime", end_time); + covSumXML.Element("EndTime", + static_cast<unsigned int>(cmSystemTools::GetTime())); + covSumXML.Element( + "ElapsedMinutes", + static_cast<int>((cmSystemTools::GetTime() - elapsed_time_start) / 6) / + 10.0); + covSumXML.EndElement(); // Coverage + this->CTest->EndXML(covSumXML); + + cmCTestLog(this->CTest, HANDLER_OUTPUT, "" + << std::endl + << "\tCovered LOC: " << total_tested << std::endl + << "\tNot covered LOC: " << total_untested << std::endl + << "\tTotal LOC: " << total_lines << std::endl + << "\tPercentage Coverage: " + << std::setiosflags(std::ios::fixed) << std::setprecision(2) + << (percent_coverage) << "%" << std::endl); ofs << "\tCovered LOC: " << total_tested << std::endl - << "\tNot covered LOC: " << total_untested << std::endl - << "\tTotal LOC: " << total_lines << std::endl - << "\tPercentage Coverage: " - << std::setiosflags(std::ios::fixed) - << std::setprecision(2) - << (percent_coverage) << "%" << std::endl; - + << "\tNot covered LOC: " << total_untested << std::endl + << "\tTotal LOC: " << total_lines << std::endl + << "\tPercentage Coverage: " << std::setiosflags(std::ios::fixed) + << std::setprecision(2) << (percent_coverage) << "%" << std::endl; - if ( error ) - { + if (error) { return -1; - } + } return 0; } -//---------------------------------------------------------------------- -void cmCTestCoverageHandler::PopulateCustomVectors(cmMakefile *mf) +void cmCTestCoverageHandler::PopulateCustomVectors(cmMakefile* mf) { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - " Add coverage exclude regular expressions." << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Add coverage exclude regular expressions." << std::endl, + this->Quiet); this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_COVERAGE_EXCLUDE", - this->CustomCoverageExclude); + this->CustomCoverageExclude); this->CTest->PopulateCustomVector(mf, "CTEST_EXTRA_COVERAGE_GLOB", - this->ExtraCoverageGlobs); - std::vector<cmStdString>::iterator it; - for ( it = this->CustomCoverageExclude.begin(); - it != this->CustomCoverageExclude.end(); - ++ it ) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " Add coverage exclude: " - << it->c_str() << std::endl); - } - for ( it = this->ExtraCoverageGlobs.begin(); - it != this->ExtraCoverageGlobs.end(); ++it) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " Add coverage glob: " - << it->c_str() << std::endl); - } + this->ExtraCoverageGlobs); + std::vector<std::string>::iterator it; + for (it = this->CustomCoverageExclude.begin(); + it != this->CustomCoverageExclude.end(); ++it) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Add coverage exclude: " << *it << std::endl, + this->Quiet); + } + for (it = this->ExtraCoverageGlobs.begin(); + it != this->ExtraCoverageGlobs.end(); ++it) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Add coverage glob: " << *it << std::endl, + this->Quiet); + } } -//---------------------------------------------------------------------- // Fix for issue #4971 where the case of the drive letter component of // the filenames might be different when analyzing gcov output. // @@ -731,130 +696,262 @@ void cmCTestCoverageHandler::PopulateCustomVectors(cmMakefile *mf) #define fnc(s) s #endif -//---------------------------------------------------------------------- -bool IsFileInDir(const std::string &infile, const std::string &indir) +bool IsFileInDir(const std::string& infile, const std::string& indir) { - std::string file = cmSystemTools::CollapseFullPath(infile.c_str()); - std::string dir = cmSystemTools::CollapseFullPath(indir.c_str()); - - if ( - file.size() > dir.size() && - (fnc(file.substr(0, dir.size())) == fnc(dir)) && - file[dir.size()] == '/' - ) - { - return true; - } + std::string file = cmSystemTools::CollapseFullPath(infile); + std::string dir = cmSystemTools::CollapseFullPath(indir); - return false; + return file.size() > dir.size() && + fnc(file.substr(0, dir.size())) == fnc(dir) && file[dir.size()] == '/'; } -//---------------------------------------------------------------------- int cmCTestCoverageHandler::HandlePHPCoverage( cmCTestCoverageHandlerContainer* cont) { cmParsePHPCoverage cov(*cont, this->CTest); std::string coverageDir = this->CTest->GetBinaryDir() + "/xdebugCoverage"; - if(cmSystemTools::FileIsDirectory(coverageDir.c_str())) - { + if (cmSystemTools::FileIsDirectory(coverageDir)) { cov.ReadPHPCoverageDirectory(coverageDir.c_str()); - } + } + return static_cast<int>(cont->TotalCoverage.size()); +} + +int cmCTestCoverageHandler::HandleCoberturaCoverage( + cmCTestCoverageHandlerContainer* cont) +{ + cmParseCoberturaCoverage cov(*cont, this->CTest); + + // Assume the coverage.xml is in the binary directory + // check for the COBERTURADIR environment variable, + // if it doesn't exist or is empty, assume the + // binary directory is used. + std::string coverageXMLFile; + if (!cmSystemTools::GetEnv("COBERTURADIR", coverageXMLFile) || + coverageXMLFile.empty()) { + coverageXMLFile = this->CTest->GetBinaryDir(); + } + // build the find file string with the directory from above + coverageXMLFile += "/coverage.xml"; + + if (cmSystemTools::FileExists(coverageXMLFile.c_str())) { + // If file exists, parse it + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Parsing Cobertura XML file: " << coverageXMLFile + << std::endl, + this->Quiet); + cov.ReadCoverageXML(coverageXMLFile.c_str()); + } else { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Cannot find Cobertura XML file: " << coverageXMLFile + << std::endl, + this->Quiet); + } return static_cast<int>(cont->TotalCoverage.size()); } -//---------------------------------------------------------------------- + int cmCTestCoverageHandler::HandleMumpsCoverage( cmCTestCoverageHandlerContainer* cont) { // try gtm coverage cmParseGTMCoverage cov(*cont, this->CTest); - std::string coverageFile = this->CTest->GetBinaryDir() + - "/gtm_coverage.mcov"; - if(cmSystemTools::FileExists(coverageFile.c_str())) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Parsing Cache Coverage: " << coverageFile - << std::endl); + std::string coverageFile = + this->CTest->GetBinaryDir() + "/gtm_coverage.mcov"; + if (cmSystemTools::FileExists(coverageFile.c_str())) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Parsing Cache Coverage: " << coverageFile << std::endl, + this->Quiet); cov.ReadCoverageFile(coverageFile.c_str()); return static_cast<int>(cont->TotalCoverage.size()); - } - else - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - " Cannot find foobar GTM coverage file: " << coverageFile - << std::endl); - } + } + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Cannot find GTM coverage file: " << coverageFile + << std::endl, + this->Quiet); cmParseCacheCoverage ccov(*cont, this->CTest); - coverageFile = this->CTest->GetBinaryDir() + - "/cache_coverage.cmcov"; - if(cmSystemTools::FileExists(coverageFile.c_str())) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Parsing Cache Coverage: " << coverageFile - << std::endl); + coverageFile = this->CTest->GetBinaryDir() + "/cache_coverage.cmcov"; + if (cmSystemTools::FileExists(coverageFile.c_str())) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Parsing Cache Coverage: " << coverageFile << std::endl, + this->Quiet); ccov.ReadCoverageFile(coverageFile.c_str()); - } - else - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - " Cannot find Cache coverage file: " << coverageFile - << std::endl); - } + } else { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Cannot find Cache coverage file: " << coverageFile + << std::endl, + this->Quiet); + } return static_cast<int>(cont->TotalCoverage.size()); } struct cmCTestCoverageHandlerLocale { cmCTestCoverageHandlerLocale() - { - if(const char* l = cmSystemTools::GetEnv("LC_ALL")) - { + { + std::string l; + if (cmSystemTools::GetEnv("LC_ALL", l)) { lc_all = l; - } - if(lc_all != "C") - { + } + if (lc_all != "C") { cmSystemTools::PutEnv("LC_ALL=C"); - } } + } ~cmCTestCoverageHandlerLocale() - { - if(!lc_all.empty()) - { - cmSystemTools::PutEnv(("LC_ALL=" + lc_all).c_str()); - } - else - { + { + if (!lc_all.empty()) { + cmSystemTools::PutEnv("LC_ALL=" + lc_all); + } else { cmSystemTools::UnsetEnv("LC_ALL"); - } } + } std::string lc_all; }; -//---------------------------------------------------------------------- +int cmCTestCoverageHandler::HandleJacocoCoverage( + cmCTestCoverageHandlerContainer* cont) +{ + cmParseJacocoCoverage cov = cmParseJacocoCoverage(*cont, this->CTest); + + // Search in the source directory. + cmsys::Glob g1; + std::vector<std::string> files; + g1.SetRecurse(true); + + std::string SourceDir = + this->CTest->GetCTestConfiguration("SourceDirectory"); + std::string coverageFile = SourceDir + "/*jacoco.xml"; + + g1.FindFiles(coverageFile); + files = g1.GetFiles(); + + // ...and in the binary directory. + cmsys::Glob g2; + std::vector<std::string> binFiles; + g2.SetRecurse(true); + std::string binaryDir = this->CTest->GetCTestConfiguration("BuildDirectory"); + std::string binCoverageFile = binaryDir + "/*jacoco.xml"; + g2.FindFiles(binCoverageFile); + binFiles = g2.GetFiles(); + if (!binFiles.empty()) { + files.insert(files.end(), binFiles.begin(), binFiles.end()); + } + + if (!files.empty()) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Found Jacoco Files, Performing Coverage" << std::endl, + this->Quiet); + cov.LoadCoverageData(files); + } else { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Cannot find Jacoco coverage files: " << coverageFile + << std::endl, + this->Quiet); + } + return static_cast<int>(cont->TotalCoverage.size()); +} + +int cmCTestCoverageHandler::HandleDelphiCoverage( + cmCTestCoverageHandlerContainer* cont) +{ + cmParseDelphiCoverage cov = cmParseDelphiCoverage(*cont, this->CTest); + cmsys::Glob g; + std::vector<std::string> files; + g.SetRecurse(true); + + std::string BinDir = this->CTest->GetBinaryDir(); + std::string coverageFile = BinDir + "/*(*.pas).html"; + + g.FindFiles(coverageFile); + files = g.GetFiles(); + if (!files.empty()) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Found Delphi HTML Files, Performing Coverage" + << std::endl, + this->Quiet); + cov.LoadCoverageData(files); + } else { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Cannot find Delphi coverage files: " << coverageFile + << std::endl, + this->Quiet); + } + return static_cast<int>(cont->TotalCoverage.size()); +} + +int cmCTestCoverageHandler::HandleBlanketJSCoverage( + cmCTestCoverageHandlerContainer* cont) +{ + cmParseBlanketJSCoverage cov = cmParseBlanketJSCoverage(*cont, this->CTest); + std::string SourceDir = + this->CTest->GetCTestConfiguration("SourceDirectory"); + + // Look for something other than output.json, still JSON extension. + std::string coverageFile = SourceDir + "/*.json"; + cmsys::Glob g; + std::vector<std::string> files; + std::vector<std::string> blanketFiles; + g.FindFiles(coverageFile); + files = g.GetFiles(); + // Ensure that the JSON files found are the result of the + // Blanket.js output. Check for the "node-jscoverage" + // string on the second line + std::string line; + for (unsigned int fileEntry = 0; fileEntry < files.size(); fileEntry++) { + cmsys::ifstream in(files[fileEntry].c_str()); + cmSystemTools::GetLineFromStream(in, line); + cmSystemTools::GetLineFromStream(in, line); + if (line.find("node-jscoverage") != std::string::npos) { + blanketFiles.push_back(files[fileEntry]); + } + } + // Take all files with the node-jscoverage string and parse those + if (!blanketFiles.empty()) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Found BlanketJS output JSON, Performing Coverage" + << std::endl, + this->Quiet); + cov.LoadCoverageData(files); + } else { + cmCTestOptionalLog( + this->CTest, HANDLER_VERBOSE_OUTPUT, + " Cannot find BlanketJS coverage files: " << coverageFile << std::endl, + this->Quiet); + } + return static_cast<int>(cont->TotalCoverage.size()); +} int cmCTestCoverageHandler::HandleGCovCoverage( cmCTestCoverageHandlerContainer* cont) { - std::string gcovCommand - = this->CTest->GetCTestConfiguration("CoverageCommand"); - std::string gcovExtraFlags - = this->CTest->GetCTestConfiguration("CoverageExtraFlags"); + std::string gcovCommand = + this->CTest->GetCTestConfiguration("CoverageCommand"); + if (gcovCommand.empty()) { + cmCTestLog(this->CTest, WARNING, "Could not find gcov." << std::endl); + return 0; + } + std::string gcovExtraFlags = + this->CTest->GetCTestConfiguration("CoverageExtraFlags"); + + // Immediately skip to next coverage option since codecov is only for Intel + // compiler + if (gcovCommand == "codecov") { + return 0; + } // Style 1 - std::string st1gcovOutputRex1 - = "[0-9]+\\.[0-9]+% of [0-9]+ (source |)lines executed in file (.*)$"; + std::string st1gcovOutputRex1 = + "[0-9]+\\.[0-9]+% of [0-9]+ (source |)lines executed in file (.*)$"; std::string st1gcovOutputRex2 = "^Creating (.*\\.gcov)\\."; cmsys::RegularExpression st1re1(st1gcovOutputRex1.c_str()); cmsys::RegularExpression st1re2(st1gcovOutputRex2.c_str()); - // Style 2 std::string st2gcovOutputRex1 = "^File *[`'](.*)'$"; - std::string st2gcovOutputRex2 - = "Lines executed: *[0-9]+\\.[0-9]+% of [0-9]+$"; + std::string st2gcovOutputRex2 = + "Lines executed: *[0-9]+\\.[0-9]+% of [0-9]+$"; std::string st2gcovOutputRex3 = "^(.*)reating [`'](.*\\.gcov)'"; std::string st2gcovOutputRex4 = "^(.*):unexpected EOF *$"; std::string st2gcovOutputRex5 = "^(.*):cannot open source file*$"; - std::string st2gcovOutputRex6 - = "^(.*):source file is newer than graph file `(.*)'$"; + std::string st2gcovOutputRex6 = + "^(.*):source file is newer than graph file `(.*)'$"; cmsys::RegularExpression st2re1(st2gcovOutputRex1.c_str()); cmsys::RegularExpression st2re2(st2gcovOutputRex2.c_str()); cmsys::RegularExpression st2re3(st2gcovOutputRex3.c_str()); @@ -866,29 +963,29 @@ int cmCTestCoverageHandler::HandleGCovCoverage( this->FindGCovFiles(files); std::vector<std::string>::iterator it; - if ( files.size() == 0 ) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - " Cannot find any GCov coverage files." - << std::endl); + if (files.empty()) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Cannot find any GCov coverage files." << std::endl, + this->Quiet); // No coverage files is a valid thing, so the exit code is 0 return 0; - } + } std::string testingDir = this->CTest->GetBinaryDir() + "/Testing"; std::string tempDir = testingDir + "/CoverageInfo"; - std::string currentDirectory = cmSystemTools::GetCurrentWorkingDirectory(); cmSystemTools::MakeDirectory(tempDir.c_str()); - cmSystemTools::ChangeDirectory(tempDir.c_str()); + cmWorkingDirectory workdir(tempDir); int gcovStyle = 0; std::set<std::string> missingFiles; - std::string actualSourceFile = ""; - cmCTestLog(this->CTest, HANDLER_OUTPUT, - " Processing coverage (each . represents one file):" << std::endl); - cmCTestLog(this->CTest, HANDLER_OUTPUT, " "); + std::string actualSourceFile; + cmCTestOptionalLog( + this->CTest, HANDLER_OUTPUT, + " Processing coverage (each . represents one file):" << std::endl, + this->Quiet); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " ", this->Quiet); int file_count = 0; // make sure output from gcov is in English! @@ -899,257 +996,216 @@ int cmCTestCoverageHandler::HandleGCovCoverage( // These are binary files that you give as input to gcov so that it will // give us text output we can analyze to summarize coverage. // - for ( it = files.begin(); it != files.end(); ++ it ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush); + for (it = files.begin(); it != files.end(); ++it) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "." << std::flush, + this->Quiet); // Call gcov to get coverage data for this *.gcda file: // - std::string fileDir = cmSystemTools::GetFilenamePath(it->c_str()); - std::string command = "\"" + gcovCommand + "\" " + - gcovExtraFlags + " " + - "-o \"" + fileDir + "\" " + - "\"" + *it + "\""; + std::string fileDir = cmSystemTools::GetFilenamePath(*it); + std::string command = "\"" + gcovCommand + "\" " + gcovExtraFlags + " " + + "-o \"" + fileDir + "\" " + "\"" + *it + "\""; - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, command.c_str() - << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + command << std::endl, this->Quiet); - std::string output = ""; - std::string errors = ""; + std::string output; + std::string errors; int retVal = 0; - *cont->OFS << "* Run coverage for: " << fileDir.c_str() << std::endl; - *cont->OFS << " Command: " << command.c_str() << std::endl; - int res = this->CTest->RunCommand(command.c_str(), &output, &errors, - &retVal, tempDir.c_str(), 0 /*this->TimeOut*/); - - *cont->OFS << " Output: " << output.c_str() << std::endl; - *cont->OFS << " Errors: " << errors.c_str() << std::endl; - if ( ! res ) - { + *cont->OFS << "* Run coverage for: " << fileDir << std::endl; + *cont->OFS << " Command: " << command << std::endl; + int res = + this->CTest->RunCommand(command.c_str(), &output, &errors, &retVal, + tempDir.c_str(), 0 /*this->TimeOut*/); + + *cont->OFS << " Output: " << output << std::endl; + *cont->OFS << " Errors: " << errors << std::endl; + if (!res) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Problem running coverage on file: " << it->c_str() << std::endl); + "Problem running coverage on file: " << *it << std::endl); cmCTestLog(this->CTest, ERROR_MESSAGE, - "Command produced error: " << errors << std::endl); - cont->Error ++; + "Command produced error: " << errors << std::endl); + cont->Error++; continue; - } - if ( retVal != 0 ) - { + } + if (retVal != 0) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Coverage command returned: " - << retVal << " while processing: " << it->c_str() << std::endl); + << retVal << " while processing: " << *it << std::endl); cmCTestLog(this->CTest, ERROR_MESSAGE, - "Command produced error: " << cont->Error << std::endl); - } - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Command produced error: " << cont->Error << std::endl); + } + cmCTestOptionalLog( + this->CTest, HANDLER_VERBOSE_OUTPUT, "--------------------------------------------------------------" - << std::endl - << output << std::endl - << "--------------------------------------------------------------" - << std::endl); + << std::endl + << output << std::endl + << "--------------------------------------------------------------" + << std::endl, + this->Quiet); - std::vector<cmStdString> lines; - std::vector<cmStdString>::iterator line; + std::vector<std::string> lines; + std::vector<std::string>::iterator line; cmSystemTools::Split(output.c_str(), lines); - for ( line = lines.begin(); line != lines.end(); ++line) - { + for (line = lines.begin(); line != lines.end(); ++line) { std::string sourceFile; std::string gcovFile; - cmCTestLog(this->CTest, DEBUG, "Line: [" << line->c_str() << "]" - << std::endl); + cmCTestOptionalLog(this->CTest, DEBUG, + "Line: [" << *line << "]" << std::endl, this->Quiet); - if ( line->size() == 0 ) - { + if (line->empty()) { // Ignore empty line; probably style 2 - } - else if ( st1re1.find(line->c_str()) ) - { - if ( gcovStyle == 0 ) - { + } else if (st1re1.find(line->c_str())) { + if (gcovStyle == 0) { gcovStyle = 1; - } - if ( gcovStyle != 1 ) - { + } + if (gcovStyle != 1) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e1" - << std::endl); - cont->Error ++; + << std::endl); + cont->Error++; break; - } + } actualSourceFile = ""; sourceFile = st1re1.match(2); - } - else if ( st1re2.find(line->c_str() ) ) - { - if ( gcovStyle == 0 ) - { + } else if (st1re2.find(line->c_str())) { + if (gcovStyle == 0) { gcovStyle = 1; - } - if ( gcovStyle != 1 ) - { + } + if (gcovStyle != 1) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e2" - << std::endl); - cont->Error ++; + << std::endl); + cont->Error++; break; - } + } gcovFile = st1re2.match(1); - } - else if ( st2re1.find(line->c_str() ) ) - { - if ( gcovStyle == 0 ) - { + } else if (st2re1.find(line->c_str())) { + if (gcovStyle == 0) { gcovStyle = 2; - } - if ( gcovStyle != 2 ) - { + } + if (gcovStyle != 2) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e3" - << std::endl); - cont->Error ++; + << std::endl); + cont->Error++; break; - } + } actualSourceFile = ""; sourceFile = st2re1.match(1); - } - else if ( st2re2.find(line->c_str() ) ) - { - if ( gcovStyle == 0 ) - { + } else if (st2re2.find(line->c_str())) { + if (gcovStyle == 0) { gcovStyle = 2; - } - if ( gcovStyle != 2 ) - { + } + if (gcovStyle != 2) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e4" - << std::endl); - cont->Error ++; + << std::endl); + cont->Error++; break; - } } - else if ( st2re3.find(line->c_str() ) ) - { - if ( gcovStyle == 0 ) - { + } else if (st2re3.find(line->c_str())) { + if (gcovStyle == 0) { gcovStyle = 2; - } - if ( gcovStyle != 2 ) - { + } + if (gcovStyle != 2) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e5" - << std::endl); - cont->Error ++; + << std::endl); + cont->Error++; break; - } + } gcovFile = st2re3.match(2); - } - else if ( st2re4.find(line->c_str() ) ) - { - if ( gcovStyle == 0 ) - { + } else if (st2re4.find(line->c_str())) { + if (gcovStyle == 0) { gcovStyle = 2; - } - if ( gcovStyle != 2 ) - { + } + if (gcovStyle != 2) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e6" - << std::endl); - cont->Error ++; + << std::endl); + cont->Error++; break; - } - - cmCTestLog(this->CTest, WARNING, "Warning: " << st2re4.match(1) - << " had unexpected EOF" << std::endl); } - else if ( st2re5.find(line->c_str() ) ) - { - if ( gcovStyle == 0 ) - { + + cmCTestOptionalLog(this->CTest, WARNING, + "Warning: " << st2re4.match(1) + << " had unexpected EOF" << std::endl, + this->Quiet); + } else if (st2re5.find(line->c_str())) { + if (gcovStyle == 0) { gcovStyle = 2; - } - if ( gcovStyle != 2 ) - { + } + if (gcovStyle != 2) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e7" - << std::endl); - cont->Error ++; + << std::endl); + cont->Error++; break; - } - - cmCTestLog(this->CTest, WARNING, "Warning: Cannot open file: " - << st2re5.match(1) << std::endl); } - else if ( st2re6.find(line->c_str() ) ) - { - if ( gcovStyle == 0 ) - { + + cmCTestOptionalLog(this->CTest, WARNING, "Warning: Cannot open file: " + << st2re5.match(1) << std::endl, + this->Quiet); + } else if (st2re6.find(line->c_str())) { + if (gcovStyle == 0) { gcovStyle = 2; - } - if ( gcovStyle != 2 ) - { + } + if (gcovStyle != 2) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e8" - << std::endl); - cont->Error ++; + << std::endl); + cont->Error++; break; - } - - cmCTestLog(this->CTest, WARNING, "Warning: File: " << st2re6.match(1) - << " is newer than " << st2re6.match(2) << std::endl); } - else - { + + cmCTestOptionalLog(this->CTest, WARNING, "Warning: File: " + << st2re6.match(1) << " is newer than " + << st2re6.match(2) << std::endl, + this->Quiet); + } else { // gcov 4.7 can have output lines saying "No executable lines" and // "Removing 'filename.gcov'"... Don't log those as "errors." - if(*line != "No executable lines" && - !cmSystemTools::StringStartsWith(line->c_str(), "Removing ")) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Unknown gcov output line: [" << line->c_str() << "]" - << std::endl); - cont->Error ++; - //abort(); - } + if (*line != "No executable lines" && + !cmSystemTools::StringStartsWith(line->c_str(), "Removing ")) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output line: [" + << *line << "]" << std::endl); + cont->Error++; + // abort(); } - + } // If the last line of gcov output gave us a valid value for gcovFile, // and we have an actualSourceFile, then insert a (or add to existing) // SingleFileCoverageVector for actualSourceFile: // - if ( !gcovFile.empty() && !actualSourceFile.empty() ) - { - cmCTestCoverageHandlerContainer::SingleFileCoverageVector& vec - = cont->TotalCoverage[actualSourceFile]; - - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " in gcovFile: " - << gcovFile << std::endl); - - std::ifstream ifile(gcovFile.c_str()); - if ( ! ifile ) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot open file: " - << gcovFile << std::endl); - } - else - { + if (!gcovFile.empty() && !actualSourceFile.empty()) { + cmCTestCoverageHandlerContainer::SingleFileCoverageVector& vec = + cont->TotalCoverage[actualSourceFile]; + + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " in gcovFile: " << gcovFile << std::endl, + this->Quiet); + + cmsys::ifstream ifile(gcovFile.c_str()); + if (!ifile) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Cannot open file: " << gcovFile << std::endl); + } else { long cnt = -1; std::string nl; - while ( cmSystemTools::GetLineFromStream(ifile, nl) ) - { - cnt ++; + while (cmSystemTools::GetLineFromStream(ifile, nl)) { + cnt++; - //TODO: Handle gcov 3.0 non-coverage lines + // TODO: Handle gcov 3.0 non-coverage lines // Skip empty lines - if ( !nl.size() ) - { + if (nl.empty()) { continue; - } + } // Skip unused lines - if ( nl.size() < 12 ) - { + if (nl.size() < 12) { continue; - } + } // Read the coverage count from the beginning of the gcov output // line @@ -1160,120 +1216,364 @@ int cmCTestCoverageHandler::HandleGCovCoverage( // output line std::string lineNumber = nl.substr(10, 5); - int lineIdx = atoi(lineNumber.c_str())-1; - if ( lineIdx >= 0 ) - { - while ( vec.size() <= static_cast<size_t>(lineIdx) ) - { + int lineIdx = atoi(lineNumber.c_str()) - 1; + if (lineIdx >= 0) { + while (vec.size() <= static_cast<size_t>(lineIdx)) { vec.push_back(-1); - } + } // Initially all entries are -1 (not used). If we get coverage // information, increment it to 0 first. - if ( vec[lineIdx] < 0 ) - { - if ( cov > 0 || prefix.find("#") != prefix.npos ) - { + if (vec[lineIdx] < 0) { + if (cov > 0 || prefix.find('#') != std::string::npos) { vec[lineIdx] = 0; - } } + } vec[lineIdx] += cov; - } } } - - actualSourceFile = ""; } + actualSourceFile = ""; + } - if ( !sourceFile.empty() && actualSourceFile.empty() ) - { + if (!sourceFile.empty() && actualSourceFile.empty()) { gcovFile = ""; // Is it in the source dir or the binary dir? // - if ( IsFileInDir(sourceFile, cont->SourceDir) ) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " produced s: " - << sourceFile.c_str() << std::endl); - *cont->OFS << " produced in source dir: " << sourceFile.c_str() - << std::endl; - actualSourceFile - = cmSystemTools::CollapseFullPath(sourceFile.c_str()); - } - else if ( IsFileInDir(sourceFile, cont->BinaryDir) ) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " produced b: " - << sourceFile.c_str() << std::endl); - *cont->OFS << " produced in binary dir: " << sourceFile.c_str() - << std::endl; - actualSourceFile - = cmSystemTools::CollapseFullPath(sourceFile.c_str()); - } + if (IsFileInDir(sourceFile, cont->SourceDir)) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " produced s: " << sourceFile << std::endl, + this->Quiet); + *cont->OFS << " produced in source dir: " << sourceFile + << std::endl; + actualSourceFile = cmSystemTools::CollapseFullPath(sourceFile); + } else if (IsFileInDir(sourceFile, cont->BinaryDir)) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " produced b: " << sourceFile << std::endl, + this->Quiet); + *cont->OFS << " produced in binary dir: " << sourceFile + << std::endl; + actualSourceFile = cmSystemTools::CollapseFullPath(sourceFile); + } - if ( actualSourceFile.empty() ) - { - if ( missingFiles.find(sourceFile) == missingFiles.end() ) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Something went wrong" << std::endl); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Cannot find file: [" - << sourceFile.c_str() << "]" << std::endl); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - " in source dir: [" - << cont->SourceDir.c_str() << "]" - << std::endl); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - " or binary dir: [" - << cont->BinaryDir.size() << "]" - << std::endl); + if (actualSourceFile.empty()) { + if (missingFiles.find(sourceFile) == missingFiles.end()) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Something went wrong" << std::endl, + this->Quiet); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Cannot find file: [" << sourceFile << "]" + << std::endl, + this->Quiet); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " in source dir: [" << cont->SourceDir << "]" + << std::endl, + this->Quiet); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " or binary dir: [" << cont->BinaryDir.size() + << "]" << std::endl, + this->Quiet); *cont->OFS << " Something went wrong. Cannot find file: " - << sourceFile.c_str() - << " in source dir: " << cont->SourceDir.c_str() - << " or binary dir: " << cont->BinaryDir.c_str() << std::endl; + << sourceFile << " in source dir: " << cont->SourceDir + << " or binary dir: " << cont->BinaryDir << std::endl; missingFiles.insert(sourceFile); - } } } } + } file_count++; - if ( file_count % 50 == 0 ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " processed: " << file_count - << " out of " << files.size() << std::endl); - cmCTestLog(this->CTest, HANDLER_OUTPUT, " "); + if (file_count % 50 == 0) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " processed: " << file_count << " out of " + << files.size() << std::endl, + this->Quiet); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " ", this->Quiet); + } + } + + return file_count; +} + +int cmCTestCoverageHandler::HandleLCovCoverage( + cmCTestCoverageHandlerContainer* cont) +{ + std::string lcovCommand = + this->CTest->GetCTestConfiguration("CoverageCommand"); + std::string lcovExtraFlags = + this->CTest->GetCTestConfiguration("CoverageExtraFlags"); + if (lcovCommand != "codecov") { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Not a valid Intel Coverage command." << std::endl, + this->Quiet); + return 0; + } + // There is only percentage completed output from LCOV + std::string st2lcovOutputRex3 = "[0-9]+%"; + cmsys::RegularExpression st2re3(st2lcovOutputRex3.c_str()); + + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " This is coverage command: " << lcovCommand << std::endl, + this->Quiet); + + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " These are coverage command flags: " << lcovExtraFlags + << std::endl, + this->Quiet); + + std::vector<std::string> files; + if (!this->FindLCovFiles(files)) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Error while finding LCov files.\n"); + return 0; + } + std::vector<std::string>::iterator it; + + if (files.empty()) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Cannot find any LCov coverage files." << std::endl, + this->Quiet); + // No coverage files is a valid thing, so the exit code is 0 + return 0; + } + std::string testingDir = this->CTest->GetBinaryDir(); + + std::set<std::string> missingFiles; + + std::string actualSourceFile; + cmCTestOptionalLog( + this->CTest, HANDLER_OUTPUT, + " Processing coverage (each . represents one file):" << std::endl, + this->Quiet); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " ", this->Quiet); + int file_count = 0; + + // make sure output from lcov is in English! + cmCTestCoverageHandlerLocale locale_C; + static_cast<void>(locale_C); + + // In intel compiler we have to call codecov only once in each executable + // directory. It collects all *.dyn files to generate .dpi file. + for (it = files.begin(); it != files.end(); ++it) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "." << std::flush, + this->Quiet); + std::string fileDir = cmSystemTools::GetFilenamePath(*it); + cmWorkingDirectory workdir(fileDir); + std::string command = "\"" + lcovCommand + "\" " + lcovExtraFlags + " "; + + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Current coverage dir: " << fileDir << std::endl, + this->Quiet); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + command << std::endl, this->Quiet); + + std::string output; + std::string errors; + int retVal = 0; + *cont->OFS << "* Run coverage for: " << fileDir << std::endl; + *cont->OFS << " Command: " << command << std::endl; + int res = + this->CTest->RunCommand(command.c_str(), &output, &errors, &retVal, + fileDir.c_str(), 0 /*this->TimeOut*/); + + *cont->OFS << " Output: " << output << std::endl; + *cont->OFS << " Errors: " << errors << std::endl; + if (!res) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Problem running coverage on file: " << *it << std::endl); + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Command produced error: " << errors << std::endl); + cont->Error++; + continue; + } + if (retVal != 0) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Coverage command returned: " + << retVal << " while processing: " << *it << std::endl); + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Command produced error: " << cont->Error << std::endl); + } + cmCTestOptionalLog( + this->CTest, HANDLER_VERBOSE_OUTPUT, + "--------------------------------------------------------------" + << std::endl + << output << std::endl + << "--------------------------------------------------------------" + << std::endl, + this->Quiet); + + std::vector<std::string> lines; + std::vector<std::string>::iterator line; + + cmSystemTools::Split(output.c_str(), lines); + + for (line = lines.begin(); line != lines.end(); ++line) { + std::string sourceFile; + std::string lcovFile; + + if (line->empty()) { + // Ignore empty line + } + // Look for LCOV files in binary directory + // Intel Compiler creates a CodeCoverage dir for each subfolder and + // each subfolder has LCOV files + cmsys::Glob gl; + gl.RecurseOn(); + gl.RecurseThroughSymlinksOff(); + std::string dir; + std::vector<std::string> lcovFiles; + dir = this->CTest->GetBinaryDir(); + std::string daGlob; + daGlob = dir; + daGlob += "/*.LCOV"; + cmCTestOptionalLog( + this->CTest, HANDLER_VERBOSE_OUTPUT, + " looking for LCOV files in: " << daGlob << std::endl, this->Quiet); + gl.FindFiles(daGlob); + // Keep a list of all LCOV files + lcovFiles.insert(lcovFiles.end(), gl.GetFiles().begin(), + gl.GetFiles().end()); + + for (std::vector<std::string>::iterator a = lcovFiles.begin(); + a != lcovFiles.end(); ++a) { + lcovFile = *a; + cmsys::ifstream srcead(lcovFile.c_str()); + if (!srcead) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Cannot open file: " << lcovFile << std::endl); + } + std::string srcname; + + int success = cmSystemTools::GetLineFromStream(srcead, srcname); + if (!success) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Error while parsing lcov file '" + << lcovFile << "':" + << " No source file name found!" << std::endl); + return 0; + } + srcname = srcname.substr(18); + // We can directly read found LCOV files to determine the source + // files + sourceFile = srcname; + actualSourceFile = srcname; + + for (std::vector<std::string>::iterator t = lcovFiles.begin(); + t != lcovFiles.end(); ++t) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Found LCOV File: " << *t << std::endl, + this->Quiet); + } + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "SourceFile: " << sourceFile << std::endl, + this->Quiet); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "lCovFile: " << lcovFile << std::endl, this->Quiet); + + // If we have some LCOV files to process + if (!lcovFile.empty() && !actualSourceFile.empty()) { + cmCTestCoverageHandlerContainer::SingleFileCoverageVector& vec = + cont->TotalCoverage[actualSourceFile]; + + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " in lcovFile: " << lcovFile << std::endl, + this->Quiet); + + cmsys::ifstream ifile(lcovFile.c_str()); + if (!ifile) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Cannot open file: " << lcovFile << std::endl); + } else { + long cnt = -1; + std::string nl; + + // Skip the first line + cmSystemTools::GetLineFromStream(ifile, nl); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "File is ready, start reading." << std::endl, + this->Quiet); + while (cmSystemTools::GetLineFromStream(ifile, nl)) { + cnt++; + + // Skip empty lines + if (nl.empty()) { + continue; + } + + // Skip unused lines + if (nl.size() < 12) { + continue; + } + + // Read the coverage count from the beginning of the lcov + // output line + std::string prefix = nl.substr(0, 17); + int cov = atoi(prefix.c_str()); + + // Read the line number starting at the 17th character of the + // lcov output line + std::string lineNumber = nl.substr(17, 7); + + int lineIdx = atoi(lineNumber.c_str()) - 1; + if (lineIdx >= 0) { + while (vec.size() <= static_cast<size_t>(lineIdx)) { + vec.push_back(-1); + } + + // Initially all entries are -1 (not used). If we get coverage + // information, increment it to 0 first. + if (vec[lineIdx] < 0) { + if (cov > 0 || prefix.find('#') != std::string::npos) { + vec[lineIdx] = 0; + } + } + + vec[lineIdx] += cov; + } + } + } + + actualSourceFile = ""; + } } } - cmSystemTools::ChangeDirectory(currentDirectory.c_str()); + file_count++; + + if (file_count % 50 == 0) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " processed: " << file_count << " out of " + << files.size() << std::endl, + this->Quiet); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " ", this->Quiet); + } + } + return file_count; } -//---------------------------------------------------------------------------- void cmCTestCoverageHandler::FindGCovFiles(std::vector<std::string>& files) { cmsys::Glob gl; gl.RecurseOn(); gl.RecurseThroughSymlinksOff(); - for(LabelMapType::const_iterator lmi = this->TargetDirs.begin(); - lmi != this->TargetDirs.end(); ++lmi) - { + for (LabelMapType::const_iterator lmi = this->TargetDirs.begin(); + lmi != this->TargetDirs.end(); ++lmi) { // Skip targets containing no interesting labels. - if(!this->IntersectsFilter(lmi->second)) - { + if (!this->IntersectsFilter(lmi->second)) { continue; - } + } // Coverage files appear next to their object files in the target // support directory. - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - " globbing for coverage in: " << lmi->first << std::endl); + cmCTestOptionalLog( + this->CTest, HANDLER_VERBOSE_OUTPUT, + " globbing for coverage in: " << lmi->first << std::endl, this->Quiet); std::string daGlob = lmi->first; daGlob += "/*.da"; gl.FindFiles(daGlob); @@ -1282,10 +1582,42 @@ void cmCTestCoverageHandler::FindGCovFiles(std::vector<std::string>& files) daGlob += "/*.gcda"; gl.FindFiles(daGlob); files.insert(files.end(), gl.GetFiles().begin(), gl.GetFiles().end()); - } + } +} + +bool cmCTestCoverageHandler::FindLCovFiles(std::vector<std::string>& files) +{ + cmsys::Glob gl; + gl.RecurseOff(); // No need of recurse if -prof_dir${BUILD_DIR} flag is + // used while compiling. + gl.RecurseThroughSymlinksOff(); + std::string buildDir = this->CTest->GetCTestConfiguration("BuildDirectory"); + cmWorkingDirectory workdir(buildDir); + + // Run profmerge to merge all *.dyn files into dpi files + if (!cmSystemTools::RunSingleCommand("profmerge")) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Error while running profmerge.\n"); + return false; + } + + // DPI file should appear in build directory + std::string daGlob; + daGlob = buildDir; + daGlob += "/*.dpi"; + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " looking for dpi files in: " << daGlob << std::endl, + this->Quiet); + if (!gl.FindFiles(daGlob)) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Error while finding files matching " << daGlob << std::endl); + return false; + } + files.insert(files.end(), gl.GetFiles().begin(), gl.GetFiles().end()); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Now searching in: " << daGlob << std::endl, this->Quiet); + return true; } -//---------------------------------------------------------------------- int cmCTestCoverageHandler::HandleTracePyCoverage( cmCTestCoverageHandlerContainer* cont) { @@ -1296,338 +1628,290 @@ int cmCTestCoverageHandler::HandleTracePyCoverage( gl.FindFiles(daGlob); std::vector<std::string> files = gl.GetFiles(); - if ( files.size() == 0 ) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - " Cannot find any Python Trace.py coverage files." - << std::endl); + if (files.empty()) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Cannot find any Python Trace.py coverage files." + << std::endl, + this->Quiet); // No coverage files is a valid thing, so the exit code is 0 return 0; - } + } std::string testingDir = this->CTest->GetBinaryDir() + "/Testing"; std::string tempDir = testingDir + "/CoverageInfo"; - std::string currentDirectory = cmSystemTools::GetCurrentWorkingDirectory(); cmSystemTools::MakeDirectory(tempDir.c_str()); - cmSystemTools::ChangeDirectory(tempDir.c_str()); - - cmSystemTools::ChangeDirectory(currentDirectory.c_str()); std::vector<std::string>::iterator fileIt; int file_count = 0; - for ( fileIt = files.begin(); fileIt != files.end(); ++ fileIt ) - { + for (fileIt = files.begin(); fileIt != files.end(); ++fileIt) { std::string fileName = this->FindFile(cont, *fileIt); - if ( fileName.empty() ) - { + if (fileName.empty()) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot find source Python file corresponding to: " - << fileIt->c_str() << std::endl); + "Cannot find source Python file corresponding to: " + << *fileIt << std::endl); continue; - } + } - std::string actualSourceFile - = cmSystemTools::CollapseFullPath(fileName.c_str()); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - " Check coverage for file: " << actualSourceFile.c_str() - << std::endl); - cmCTestCoverageHandlerContainer::SingleFileCoverageVector* vec - = &cont->TotalCoverage[actualSourceFile]; - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - " in file: " << fileIt->c_str() << std::endl); - std::ifstream ifile(fileIt->c_str()); - if ( ! ifile ) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot open file: " - << fileIt->c_str() << std::endl); - } - else - { + std::string actualSourceFile = cmSystemTools::CollapseFullPath(fileName); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Check coverage for file: " << actualSourceFile + << std::endl, + this->Quiet); + cmCTestCoverageHandlerContainer::SingleFileCoverageVector* vec = + &cont->TotalCoverage[actualSourceFile]; + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " in file: " << *fileIt << std::endl, this->Quiet); + cmsys::ifstream ifile(fileIt->c_str()); + if (!ifile) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Cannot open file: " << *fileIt << std::endl); + } else { long cnt = -1; std::string nl; - while ( cmSystemTools::GetLineFromStream(ifile, nl) ) - { - cnt ++; + while (cmSystemTools::GetLineFromStream(ifile, nl)) { + cnt++; // Skip empty lines - if ( !nl.size() ) - { + if (nl.empty()) { continue; - } + } // Skip unused lines - if ( nl.size() < 12 ) - { + if (nl.size() < 12) { continue; - } + } // Read the coverage count from the beginning of the Trace.py output // line std::string prefix = nl.substr(0, 6); - if ( prefix[5] != ' ' && prefix[5] != ':' ) - { + if (prefix[5] != ' ' && prefix[5] != ':') { // This is a hack. We should really do something more elaborate prefix = nl.substr(0, 7); - if ( prefix[6] != ' ' && prefix[6] != ':' ) - { + if (prefix[6] != ' ' && prefix[6] != ':') { prefix = nl.substr(0, 8); - if ( prefix[7] != ' ' && prefix[7] != ':' ) - { + if (prefix[7] != ' ' && prefix[7] != ':') { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Currently the limit is maximum coverage of 999999" - << std::endl); - } + "Currently the limit is maximum coverage of 999999" + << std::endl); } } + } int cov = atoi(prefix.c_str()); - if ( prefix[prefix.size()-1] != ':' ) - { + if (prefix[prefix.size() - 1] != ':') { // This line does not have ':' so no coverage here. That said, // Trace.py does not handle not covered lines versus comments etc. // So, this will be set to 0. cov = 0; - } - cmCTestLog(this->CTest, DEBUG, "Prefix: " << prefix.c_str() - << " cov: " << cov - << std::endl); + } + cmCTestOptionalLog( + this->CTest, DEBUG, + "Prefix: " << prefix << " cov: " << cov << std::endl, this->Quiet); // Read the line number starting at the 10th character of the gcov // output line long lineIdx = cnt; - if ( lineIdx >= 0 ) - { - while ( vec->size() <= - static_cast<size_t>(lineIdx) ) - { + if (lineIdx >= 0) { + while (vec->size() <= static_cast<size_t>(lineIdx)) { vec->push_back(-1); - } + } // Initially all entries are -1 (not used). If we get coverage // information, increment it to 0 first. - if ( (*vec)[lineIdx] < 0 ) - { - if ( cov >= 0 ) - { + if ((*vec)[lineIdx] < 0) { + if (cov >= 0) { (*vec)[lineIdx] = 0; - } } - (*vec)[lineIdx] += cov; } + (*vec)[lineIdx] += cov; } } - ++ file_count; } - cmSystemTools::ChangeDirectory(currentDirectory.c_str()); + ++file_count; + } return file_count; } -//---------------------------------------------------------------------- std::string cmCTestCoverageHandler::FindFile( - cmCTestCoverageHandlerContainer* cont, - std::string fileName) + cmCTestCoverageHandlerContainer* cont, std::string const& fileName) { - std::string fileNameNoE - = cmSystemTools::GetFilenameWithoutLastExtension(fileName); + std::string fileNameNoE = + cmSystemTools::GetFilenameWithoutLastExtension(fileName); // First check in source and binary directory std::string fullName = cont->SourceDir + "/" + fileNameNoE + ".py"; - if ( cmSystemTools::FileExists(fullName.c_str()) ) - { + if (cmSystemTools::FileExists(fullName.c_str())) { return fullName; - } + } fullName = cont->BinaryDir + "/" + fileNameNoE + ".py"; - if ( cmSystemTools::FileExists(fullName.c_str()) ) - { + if (cmSystemTools::FileExists(fullName.c_str())) { return fullName; - } + } return ""; } // This is a header put on each marked up source file -namespace -{ - const char* bullseyeHelp[] = - {" Coverage produced by bullseye covbr tool: ", - " www.bullseye.com/help/ref_covbr.html", - " * An arrow --> indicates incomplete coverage.", - " * An X indicates a function that was invoked, a switch label that ", - " was exercised, a try-block that finished, or an exception handler ", - " that was invoked.", - " * A T or F indicates a boolean decision that evaluated true or false,", - " respectively.", - " * A t or f indicates a boolean condition within a decision if the ", - " condition evaluated true or false, respectively.", - " * A k indicates a constant decision or condition.", - " * The slash / means this probe is excluded from summary results. ", - 0}; +namespace { +const char* bullseyeHelp[] = { + " Coverage produced by bullseye covbr tool: ", + " www.bullseye.com/help/ref_covbr.html", + " * An arrow --> indicates incomplete coverage.", + " * An X indicates a function that was invoked, a switch label that ", + " was exercised, a try-block that finished, or an exception handler ", + " that was invoked.", + " * A T or F indicates a boolean decision that evaluated true or false,", + " respectively.", + " * A t or f indicates a boolean condition within a decision if the ", + " condition evaluated true or false, respectively.", + " * A k indicates a constant decision or condition.", + " * The slash / means this probe is excluded from summary results. ", + CM_NULLPTR +}; } -//---------------------------------------------------------------------- int cmCTestCoverageHandler::RunBullseyeCoverageBranch( cmCTestCoverageHandlerContainer* cont, - std::set<cmStdString>& coveredFileNames, - std::vector<std::string>& files, + std::set<std::string>& coveredFileNames, std::vector<std::string>& files, std::vector<std::string>& filesFullPath) { - if(files.size() != filesFullPath.size()) - { + if (files.size() != filesFullPath.size()) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Files and full path files not the same size?:\n"); return 0; - } + } // create the output stream for the CoverageLog-N.xml file cmGeneratedFileStream covLogFile; + cmXMLWriter covLogXML(covLogFile); int logFileCount = 0; - if ( !this->StartCoverageLogFile(covLogFile, logFileCount) ) - { + if (!this->StartCoverageLogFile(covLogFile, logFileCount)) { return -1; - } + } + this->StartCoverageLogXML(covLogXML); // for each file run covbr on that file to get the coverage // information for that file std::string outputFile; - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "run covbr: " - << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "run covbr: " << std::endl, this->Quiet); - if(!this->RunBullseyeCommand(cont, "covbr", 0, outputFile)) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, "error running covbr for." << "\n"); + if (!this->RunBullseyeCommand(cont, "covbr", CM_NULLPTR, outputFile)) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "error running covbr for." + << "\n"); return -1; - } - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "covbr output in " << outputFile - << std::endl); + } + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "covbr output in " << outputFile << std::endl, + this->Quiet); // open the output file - std::ifstream fin(outputFile.c_str()); - if(!fin) - { + cmsys::ifstream fin(outputFile.c_str()); + if (!fin) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot open coverage file: " << - outputFile.c_str() << std::endl); + "Cannot open coverage file: " << outputFile << std::endl); return 0; - } - std::map<cmStdString, cmStdString> fileMap; + } + std::map<std::string, std::string> fileMap; std::vector<std::string>::iterator fp = filesFullPath.begin(); - for(std::vector<std::string>::iterator f = files.begin(); - f != files.end(); ++f, ++fp) - { + for (std::vector<std::string>::iterator f = files.begin(); f != files.end(); + ++f, ++fp) { fileMap[*f] = *fp; - } + } - int count =0; // keep count of the number of files + int count = 0; // keep count of the number of files // Now parse each line from the bullseye cov log file std::string lineIn; bool valid = false; // are we in a valid output file - int line = 0; // line of the current file - cmStdString file; - while(cmSystemTools::GetLineFromStream(fin, lineIn)) - { + int line = 0; // line of the current file + std::string file; + while (cmSystemTools::GetLineFromStream(fin, lineIn)) { bool startFile = false; - if(lineIn.size() > 1 && lineIn[lineIn.size()-1] == ':') - { - file = lineIn.substr(0, lineIn.size()-1); - if(coveredFileNames.find(file) != coveredFileNames.end()) - { + if (lineIn.size() > 1 && lineIn[lineIn.size() - 1] == ':') { + file = lineIn.substr(0, lineIn.size() - 1); + if (coveredFileNames.find(file) != coveredFileNames.end()) { startFile = true; - } } - if(startFile) - { + } + if (startFile) { // if we are in a valid file close it because a new one started - if(valid) - { - covLogFile << "\t\t</Report>" << std::endl - << "\t</File>" << std::endl; - } + if (valid) { + covLogXML.EndElement(); // Report + covLogXML.EndElement(); // File + } // only allow 100 files in each log file - if ( count != 0 && count % 100 == 0 ) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "start a new log file: " - << count - << std::endl); + if (count != 0 && count % 100 == 0) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "start a new log file: " << count << std::endl, + this->Quiet); + this->EndCoverageLogXML(covLogXML); this->EndCoverageLogFile(covLogFile, logFileCount); - logFileCount ++; - if ( !this->StartCoverageLogFile(covLogFile, logFileCount) ) - { + logFileCount++; + if (!this->StartCoverageLogFile(covLogFile, logFileCount)) { return -1; - } - count++; // move on one } - std::map<cmStdString, cmStdString>::iterator - i = fileMap.find(file); + this->StartCoverageLogXML(covLogXML); + count++; // move on one + } + std::map<std::string, std::string>::iterator i = fileMap.find(file); // if the file should be covered write out the header for that file - if(i != fileMap.end()) - { + if (i != fileMap.end()) { // we have a new file so count it in the output count++; - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Produce coverage for file: " - << file.c_str() << " " << count - << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Produce coverage for file: " << file << " " + << count << std::endl, + this->Quiet); // start the file output - covLogFile << "\t<File Name=\"" - << cmXMLSafe(i->first) - << "\" FullPath=\"" << cmXMLSafe( - this->CTest->GetShortPathToFile( - i->second.c_str())) << "\">" << std::endl - << "\t\t<Report>" << std::endl; + covLogXML.StartElement("File"); + covLogXML.Attribute("Name", i->first); + covLogXML.Attribute( + "FullPath", this->CTest->GetShortPathToFile(i->second.c_str())); + covLogXML.StartElement("Report"); // write the bullseye header - line =0; - for(int k =0; bullseyeHelp[k] != 0; ++k) - { - covLogFile << "\t\t<Line Number=\"" << line << "\" Count=\"-1\">" - << cmXMLSafe(bullseyeHelp[k]) - << "</Line>" << std::endl; + line = 0; + for (int k = 0; bullseyeHelp[k] != CM_NULLPTR; ++k) { + covLogXML.StartElement("Line"); + covLogXML.Attribute("Number", line); + covLogXML.Attribute("Count", -1); + covLogXML.Content(bullseyeHelp[k]); + covLogXML.EndElement(); // Line line++; - } - valid = true; // we are in a valid file section } - else - { + valid = true; // we are in a valid file section + } else { // this is not a file that we want coverage for valid = false; - } } + } // we are not at a start file, and we are in a valid file output the line - else if(valid) - { - covLogFile << "\t\t<Line Number=\"" << line << "\" Count=\"-1\">" - << cmXMLSafe(lineIn) - << "</Line>" << std::endl; + else if (valid) { + covLogXML.StartElement("Line"); + covLogXML.Attribute("Number", line); + covLogXML.Attribute("Count", -1); + covLogXML.Content(lineIn); + covLogXML.EndElement(); // Line line++; - } } + } // if we ran out of lines a valid file then close that file - if(valid) - { - covLogFile << "\t\t</Report>" << std::endl - << "\t</File>" << std::endl; - } + if (valid) { + covLogXML.EndElement(); // Report + covLogXML.EndElement(); // File + } + this->EndCoverageLogXML(covLogXML); this->EndCoverageLogFile(covLogFile, logFileCount); return 1; } -//---------------------------------------------------------------------- int cmCTestCoverageHandler::RunBullseyeCommand( - cmCTestCoverageHandlerContainer* cont, - const char* cmd, - const char* arg, + cmCTestCoverageHandlerContainer* cont, const char* cmd, const char* arg, std::string& outputFile) { std::string program = cmSystemTools::FindProgram(cmd); - if(program.size() == 0) - { + if (program.empty()) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot find :" << cmd << "\n"); return 0; - } - if(arg) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Run : " << program.c_str() << " " << arg << "\n"); - } - else - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Run : " << program.c_str() << "\n"); - } + } + if (arg) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Run : " << program << " " << arg << "\n", this->Quiet); + } else { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Run : " << program << "\n", this->Quiet); + } // create a process object and start it cmCTestRunProcess runCoverageSrc; runCoverageSrc.SetCommand(program.c_str()); @@ -1641,52 +1925,46 @@ int cmCTestCoverageHandler::RunBullseyeCommand( stderrFile += ".stderr"; runCoverageSrc.SetStdoutFile(stdoutFile.c_str()); runCoverageSrc.SetStderrFile(stderrFile.c_str()); - if(!runCoverageSrc.StartProcess()) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, "Could not run : " - << program.c_str() << " " << arg << "\n" - << "kwsys process state : " - << runCoverageSrc.GetProcessState()); + if (!runCoverageSrc.StartProcess()) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Could not run : " << program << " " << arg << "\n" + << "kwsys process state : " + << runCoverageSrc.GetProcessState()); return 0; - } + } // since we set the output file names wait for it to end runCoverageSrc.WaitForExit(); outputFile = stdoutFile; return 1; } -//---------------------------------------------------------------------- int cmCTestCoverageHandler::RunBullseyeSourceSummary( cmCTestCoverageHandlerContainer* cont) { // Run the covsrc command and create a temp outputfile std::string outputFile; - if(!this->RunBullseyeCommand(cont, "covsrc", "-c", outputFile)) - { + if (!this->RunBullseyeCommand(cont, "covsrc", "-c", outputFile)) { cmCTestLog(this->CTest, ERROR_MESSAGE, "error running covsrc:\n"); return 0; - } + } std::ostream& tmpLog = *cont->OFS; // copen the Coverage.xml file in the Testing directory cmGeneratedFileStream covSumFile; - if(!this->StartResultingXML(cmCTest::PartCoverage, "Coverage", covSumFile)) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot open coverage summary file." << std::endl); + cmXMLWriter xml(covSumFile); + if (!this->StartResultingXML(cmCTest::PartCoverage, "Coverage", + covSumFile)) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot open coverage summary file." + << std::endl); return 0; - } - this->CTest->StartXML(covSumFile, this->AppendXML); + } + this->CTest->StartXML(xml, this->AppendXML); double elapsed_time_start = cmSystemTools::GetTime(); std::string coverage_start_time = this->CTest->CurrentTime(); - covSumFile << "<Coverage>" << std::endl - << "\t<StartDateTime>" - << coverage_start_time << "</StartDateTime>" - << std::endl - << "\t<StartTime>" - << static_cast<unsigned int>(cmSystemTools::GetTime()) - << "</StartTime>" - << std::endl; + xml.StartElement("Coverage"); + xml.Element("StartDateTime", coverage_start_time); + xml.Element("StartTime", + static_cast<unsigned int>(cmSystemTools::GetTime())); std::string stdline; std::string errline; // expected output: @@ -1703,66 +1981,53 @@ int cmCTestCoverageHandler::RunBullseyeSourceSummary( double total_tested = 0; double total_untested = 0; double total_functions = 0; - double percent_coverage =0; - double number_files = 0; + double percent_coverage = 0; + double number_files = 0; std::vector<std::string> coveredFiles; std::vector<std::string> coveredFilesFullPath; // Read and parse the summary output file - std::ifstream fin(outputFile.c_str()); - if(!fin) - { + cmsys::ifstream fin(outputFile.c_str()); + if (!fin) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot open coverage summary file: " << - outputFile.c_str() << std::endl); + "Cannot open coverage summary file: " << outputFile + << std::endl); return 0; - } - std::set<cmStdString> coveredFileNames; - while(cmSystemTools::GetLineFromStream(fin, stdline)) - { + } + std::set<std::string> coveredFileNames; + while (cmSystemTools::GetLineFromStream(fin, stdline)) { // if we have a line of output from stdout - if(stdline.size()) - { + if (!stdline.empty()) { // parse the comma separated output - this->ParseBullsEyeCovsrcLine(stdline, - sourceFile, - functionsCalled, - totalFunctions, - percentFunction, - branchCovered, - totalBranches, - percentBranch); + this->ParseBullsEyeCovsrcLine( + stdline, sourceFile, functionsCalled, totalFunctions, percentFunction, + branchCovered, totalBranches, percentBranch); // The first line is the header - if(sourceFile == "Source" || sourceFile == "Total") - { + if (sourceFile == "Source" || sourceFile == "Total") { continue; - } + } std::string file = sourceFile; coveredFileNames.insert(file); - if(!cmSystemTools::FileIsFullPath(sourceFile.c_str())) - { + if (!cmSystemTools::FileIsFullPath(sourceFile.c_str())) { // file will be relative to the binary dir file = cont->BinaryDir; file += "/"; file += sourceFile; - } - file = cmSystemTools::CollapseFullPath(file.c_str()); - bool shouldIDoCoverage - = this->ShouldIDoCoverage(file.c_str(), - cont->SourceDir.c_str(), - cont->BinaryDir.c_str()); - if ( !shouldIDoCoverage ) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - ".NoDartCoverage found, so skip coverage check for: " - << file.c_str() - << std::endl); + } + file = cmSystemTools::CollapseFullPath(file); + bool shouldIDoCoverage = this->ShouldIDoCoverage( + file.c_str(), cont->SourceDir.c_str(), cont->BinaryDir.c_str()); + if (!shouldIDoCoverage) { + cmCTestOptionalLog( + this->CTest, HANDLER_VERBOSE_OUTPUT, + ".NoDartCoverage found, so skip coverage check for: " << file + << std::endl, + this->Quiet); continue; - } + } - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Doing coverage for: " - << file.c_str() - << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Doing coverage for: " << file << std::endl, + this->Quiet); coveredFiles.push_back(sourceFile); coveredFilesFullPath.push_back(file); @@ -1772,116 +2037,89 @@ int cmCTestCoverageHandler::RunBullseyeSourceSummary( total_tested += functionsCalled; total_untested += (totalFunctions - functionsCalled); - std::string fileName = cmSystemTools::GetFilenameName(file.c_str()); + std::string fileName = cmSystemTools::GetFilenameName(file); std::string shortFileName = this->CTest->GetShortPathToFile(file.c_str()); float cper = static_cast<float>(percentBranch + percentFunction); - if(totalBranches > 0) - { + if (totalBranches > 0) { cper /= 2.0f; - } - percent_coverage += cper; + } + percent_coverage += static_cast<double>(cper); float cmet = static_cast<float>(percentFunction + percentBranch); - if(totalBranches > 0) - { + if (totalBranches > 0) { cmet /= 2.0f; - } + } cmet /= 100.0f; - tmpLog << stdline.c_str() << "\n"; + tmpLog << stdline << "\n"; tmpLog << fileName << "\n"; - tmpLog << "functionsCalled: " << functionsCalled/100 << "\n"; - tmpLog << "totalFunctions: " << totalFunctions/100 << "\n"; + tmpLog << "functionsCalled: " << functionsCalled / 100 << "\n"; + tmpLog << "totalFunctions: " << totalFunctions / 100 << "\n"; tmpLog << "percentFunction: " << percentFunction << "\n"; tmpLog << "branchCovered: " << branchCovered << "\n"; tmpLog << "totalBranches: " << totalBranches << "\n"; tmpLog << "percentBranch: " << percentBranch << "\n"; tmpLog << "percentCoverage: " << percent_coverage << "\n"; tmpLog << "coverage metric: " << cmet << "\n"; - covSumFile << "\t<File Name=\"" << cmXMLSafe(sourceFile) - << "\" FullPath=\"" << cmXMLSafe(shortFileName) - << "\" Covered=\"" << (cmet>0?"true":"false") << "\">\n" - << "\t\t<BranchesTested>" - << branchCovered - << "</BranchesTested>\n" - << "\t\t<BranchesUnTested>" - << totalBranches - branchCovered - << "</BranchesUnTested>\n" - << "\t\t<FunctionsTested>" - << functionsCalled - << "</FunctionsTested>\n" - << "\t\t<FunctionsUnTested>" - << totalFunctions - functionsCalled - << "</FunctionsUnTested>\n" - // Hack for conversion of function to loc assume a function - // has 100 lines of code - << "\t\t<LOCTested>" << functionsCalled *100 - << "</LOCTested>\n" - << "\t\t<LOCUnTested>" - << (totalFunctions - functionsCalled)*100 - << "</LOCUnTested>\n" - << "\t\t<PercentCoverage>"; - covSumFile.setf(std::ios::fixed, std::ios::floatfield); - covSumFile.precision(2); - covSumFile << (cper) << "</PercentCoverage>\n" - << "\t\t<CoverageMetric>"; - covSumFile.setf(std::ios::fixed, std::ios::floatfield); - covSumFile.precision(2); - covSumFile << (cmet) << "</CoverageMetric>\n"; - this->WriteXMLLabels(covSumFile, shortFileName); - covSumFile << "\t</File>" << std::endl; - } - } + xml.StartElement("File"); + xml.Attribute("Name", sourceFile); + xml.Attribute("FullPath", shortFileName); + xml.Attribute("Covered", cmet > 0 ? "true" : "false"); + xml.Element("BranchesTested", branchCovered); + xml.Element("BranchesUnTested", totalBranches - branchCovered); + xml.Element("FunctionsTested", functionsCalled); + xml.Element("FunctionsUnTested", totalFunctions - functionsCalled); + // Hack for conversion of function to loc assume a function + // has 100 lines of code + xml.Element("LOCTested", functionsCalled * 100); + xml.Element("LOCUnTested", (totalFunctions - functionsCalled) * 100); + xml.Element("PercentCoverage", cper); + xml.Element("CoverageMetric", cmet); + this->WriteXMLLabels(xml, shortFileName); + xml.EndElement(); // File + } + } std::string end_time = this->CTest->CurrentTime(); - covSumFile << "\t<LOCTested>" << total_tested << "</LOCTested>\n" - << "\t<LOCUntested>" << total_untested << "</LOCUntested>\n" - << "\t<LOC>" << total_functions << "</LOC>\n" - << "\t<PercentCoverage>"; - covSumFile.setf(std::ios::fixed, std::ios::floatfield); - covSumFile.precision(2); - covSumFile - << SAFEDIV(percent_coverage,number_files)<< "</PercentCoverage>\n" - << "\t<EndDateTime>" << end_time << "</EndDateTime>\n" - << "\t<EndTime>" << static_cast<unsigned int>(cmSystemTools::GetTime()) - << "</EndTime>\n"; - covSumFile - << "<ElapsedMinutes>" << - static_cast<int>((cmSystemTools::GetTime() - elapsed_time_start)/6)/10.0 - << "</ElapsedMinutes>" - << "</Coverage>" << std::endl; - this->CTest->EndXML(covSumFile); + xml.Element("LOCTested", total_tested); + xml.Element("LOCUntested", total_untested); + xml.Element("LOC", total_functions); + xml.Element("PercentCoverage", SAFEDIV(percent_coverage, number_files)); + xml.Element("EndDateTime", end_time); + xml.Element("EndTime", static_cast<unsigned int>(cmSystemTools::GetTime())); + xml.Element( + "ElapsedMinutes", + static_cast<int>((cmSystemTools::GetTime() - elapsed_time_start) / 6) / + 10.0); + xml.EndElement(); // Coverage + this->CTest->EndXML(xml); // Now create the coverage information for each file - return this->RunBullseyeCoverageBranch(cont, - coveredFileNames, - coveredFiles, + return this->RunBullseyeCoverageBranch(cont, coveredFileNames, coveredFiles, coveredFilesFullPath); } -//---------------------------------------------------------------------- int cmCTestCoverageHandler::HandleBullseyeCoverage( cmCTestCoverageHandlerContainer* cont) { - const char* covfile = cmSystemTools::GetEnv("COVFILE"); - if(!covfile || strlen(covfile) == 0) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - " COVFILE environment variable not found, not running " - " bullseye\n"); + std::string covfile; + if (!cmSystemTools::GetEnv("COVFILE", covfile) || covfile.empty()) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " COVFILE environment variable not found, not running " + " bullseye\n", + this->Quiet); return 0; - } - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - " run covsrc with COVFILE=[" - << covfile - << "]" << std::endl); - if(!this->RunBullseyeSourceSummary(cont)) - { + } + cmCTestOptionalLog( + this->CTest, HANDLER_VERBOSE_OUTPUT, + " run covsrc with COVFILE=[" << covfile << "]" << std::endl, this->Quiet); + if (!this->RunBullseyeSourceSummary(cont)) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Error running bullseye summary.\n"); return 0; - } - cmCTestLog(this->CTest, DEBUG, "HandleBullseyeCoverage return 1 " - << std::endl); + } + cmCTestOptionalLog(this->CTest, DEBUG, + "HandleBullseyeCoverage return 1 " << std::endl, + this->Quiet); return 1; } @@ -1892,141 +2130,113 @@ bool cmCTestCoverageHandler::GetNextInt(std::string const& inputLine, std::string::size_type start = pos; pos = inputLine.find(',', start); value = atoi(inputLine.substr(start, pos).c_str()); - if(pos == inputLine.npos) - { + if (pos == std::string::npos) { return true; - } + } pos++; return true; } bool cmCTestCoverageHandler::ParseBullsEyeCovsrcLine( - std::string const& inputLine, - std::string& sourceFile, - int& functionsCalled, - int& totalFunctions, - int& percentFunction, - int& branchCovered, - int& totalBranches, - int& percentBranch) + std::string const& inputLine, std::string& sourceFile, int& functionsCalled, + int& totalFunctions, int& percentFunction, int& branchCovered, + int& totalBranches, int& percentBranch) { // find the first comma std::string::size_type pos = inputLine.find(','); - if(pos == inputLine.npos) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, "Error parsing string : " - << inputLine.c_str() << "\n"); + if (pos == std::string::npos) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Error parsing string : " << inputLine << "\n"); return false; - } + } // the source file has "" around it so extract out the file name - sourceFile = inputLine.substr(1,pos-2); + sourceFile = inputLine.substr(1, pos - 2); pos++; - if(!this->GetNextInt(inputLine, pos, functionsCalled)) - { + if (!this->GetNextInt(inputLine, pos, functionsCalled)) { return false; - } - if(!this->GetNextInt(inputLine, pos, totalFunctions)) - { + } + if (!this->GetNextInt(inputLine, pos, totalFunctions)) { return false; - } - if(!this->GetNextInt(inputLine, pos, percentFunction)) - { + } + if (!this->GetNextInt(inputLine, pos, percentFunction)) { return false; - } - if(!this->GetNextInt(inputLine, pos, branchCovered)) - { + } + if (!this->GetNextInt(inputLine, pos, branchCovered)) { return false; - } - if(!this->GetNextInt(inputLine, pos, totalBranches)) - { + } + if (!this->GetNextInt(inputLine, pos, totalBranches)) { return false; - } - if(!this->GetNextInt(inputLine, pos, percentBranch)) - { + } + if (!this->GetNextInt(inputLine, pos, percentBranch)) { return false; - } + } // should be at the end now - if(pos != inputLine.npos) - { + if (pos != std::string::npos) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Error parsing input : " - << inputLine.c_str() << " last pos not npos = " << pos << - "\n"); - } + << inputLine << " last pos not npos = " << pos << "\n"); + } return true; } -//---------------------------------------------------------------------- int cmCTestCoverageHandler::GetLabelId(std::string const& label) { LabelIdMapType::iterator i = this->LabelIdMap.find(label); - if(i == this->LabelIdMap.end()) - { + if (i == this->LabelIdMap.end()) { int n = int(this->Labels.size()); this->Labels.push_back(label); LabelIdMapType::value_type entry(label, n); i = this->LabelIdMap.insert(entry).first; - } + } return i->second; } -//---------------------------------------------------------------------- void cmCTestCoverageHandler::LoadLabels() { std::string fileList = this->CTest->GetBinaryDir(); fileList += cmake::GetCMakeFilesDirectory(); fileList += "/TargetDirectories.txt"; - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - " target directory list [" << fileList << "]\n"); - std::ifstream finList(fileList.c_str()); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " target directory list [" << fileList << "]\n", + this->Quiet); + cmsys::ifstream finList(fileList.c_str()); std::string line; - while(cmSystemTools::GetLineFromStream(finList, line)) - { + while (cmSystemTools::GetLineFromStream(finList, line)) { this->LoadLabels(line.c_str()); - } + } } -//---------------------------------------------------------------------- void cmCTestCoverageHandler::LoadLabels(const char* dir) { LabelSet& dirLabels = this->TargetDirs[dir]; std::string fname = dir; fname += "/Labels.txt"; - std::ifstream fin(fname.c_str()); - if(!fin) - { + cmsys::ifstream fin(fname.c_str()); + if (!fin) { return; - } + } - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - " loading labels from [" << fname << "]\n"); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " loading labels from [" << fname << "]\n", this->Quiet); bool inTarget = true; std::string source; std::string line; std::vector<int> targetLabels; - while(cmSystemTools::GetLineFromStream(fin, line)) - { - if(line.empty() || line[0] == '#') - { + while (cmSystemTools::GetLineFromStream(fin, line)) { + if (line.empty() || line[0] == '#') { // Ignore blank and comment lines. continue; - } - else if(line[0] == ' ') - { + } + if (line[0] == ' ') { // Label lines appear indented by one space. std::string label = line.substr(1); int id = this->GetLabelId(label); dirLabels.insert(id); - if(inTarget) - { + if (inTarget) { targetLabels.push_back(id); - } - else - { + } else { this->SourceLabels[source].insert(id); - } } - else - { + } else { // Non-indented lines specify a source file name. The first one // is the end of the target-wide labels. inTarget = false; @@ -2035,117 +2245,96 @@ void cmCTestCoverageHandler::LoadLabels(const char* dir) // Label the source with the target labels. LabelSet& labelSet = this->SourceLabels[source]; - for(std::vector<int>::const_iterator li = targetLabels.begin(); - li != targetLabels.end(); ++li) - { - labelSet.insert(*li); - } - } + labelSet.insert(targetLabels.begin(), targetLabels.end()); } + } } -//---------------------------------------------------------------------- -void cmCTestCoverageHandler::WriteXMLLabels(std::ofstream& os, +void cmCTestCoverageHandler::WriteXMLLabels(cmXMLWriter& xml, std::string const& source) { LabelMapType::const_iterator li = this->SourceLabels.find(source); - if(li != this->SourceLabels.end() && !li->second.empty()) - { - os << "\t\t<Labels>\n"; - for(LabelSet::const_iterator lsi = li->second.begin(); - lsi != li->second.end(); ++lsi) - { - os << "\t\t\t<Label>" << cmXMLSafe(this->Labels[*lsi]) << "</Label>\n"; - } - os << "\t\t</Labels>\n"; - } + if (li != this->SourceLabels.end() && !li->second.empty()) { + xml.StartElement("Labels"); + for (LabelSet::const_iterator lsi = li->second.begin(); + lsi != li->second.end(); ++lsi) { + xml.Element("Label", this->Labels[*lsi]); + } + xml.EndElement(); // Labels + } } -//---------------------------------------------------------------------------- -void -cmCTestCoverageHandler::SetLabelFilter(std::set<cmStdString> const& labels) +void cmCTestCoverageHandler::SetLabelFilter( + std::set<std::string> const& labels) { this->LabelFilter.clear(); - for(std::set<cmStdString>::const_iterator li = labels.begin(); - li != labels.end(); ++li) - { + for (std::set<std::string>::const_iterator li = labels.begin(); + li != labels.end(); ++li) { this->LabelFilter.insert(this->GetLabelId(*li)); - } + } } -//---------------------------------------------------------------------- bool cmCTestCoverageHandler::IntersectsFilter(LabelSet const& labels) { // If there is no label filter then nothing is filtered out. - if(this->LabelFilter.empty()) - { + if (this->LabelFilter.empty()) { return true; - } + } std::vector<int> ids; - cmsys_stl::set_intersection - (labels.begin(), labels.end(), - this->LabelFilter.begin(), this->LabelFilter.end(), - cmsys_stl::back_inserter(ids)); + std::set_intersection(labels.begin(), labels.end(), + this->LabelFilter.begin(), this->LabelFilter.end(), + std::back_inserter(ids)); return !ids.empty(); } -//---------------------------------------------------------------------- bool cmCTestCoverageHandler::IsFilteredOut(std::string const& source) { // If there is no label filter then nothing is filtered out. - if(this->LabelFilter.empty()) - { + if (this->LabelFilter.empty()) { return false; - } + } // The source is filtered out if it does not have any labels in // common with the filter set. std::string shortSrc = this->CTest->GetShortPathToFile(source.c_str()); LabelMapType::const_iterator li = this->SourceLabels.find(shortSrc); - if(li != this->SourceLabels.end()) - { + if (li != this->SourceLabels.end()) { return !this->IntersectsFilter(li->second); - } + } return true; } -//---------------------------------------------------------------------- std::set<std::string> cmCTestCoverageHandler::FindUncoveredFiles( cmCTestCoverageHandlerContainer* cont) { std::set<std::string> extraMatches; - for(std::vector<cmStdString>::iterator i = this->ExtraCoverageGlobs.begin(); - i != this->ExtraCoverageGlobs.end(); ++i) - { + for (std::vector<std::string>::iterator i = this->ExtraCoverageGlobs.begin(); + i != this->ExtraCoverageGlobs.end(); ++i) { cmsys::Glob gl; gl.RecurseOn(); gl.RecurseThroughSymlinksOff(); std::string glob = cont->SourceDir + "/" + *i; gl.FindFiles(glob); std::vector<std::string> files = gl.GetFiles(); - for(std::vector<std::string>::iterator f = files.begin(); - f != files.end(); ++f) - { - if(this->ShouldIDoCoverage(f->c_str(), - cont->SourceDir.c_str(), cont->BinaryDir.c_str())) - { - extraMatches.insert(this->CTest->GetShortPathToFile( - f->c_str())); - } + for (std::vector<std::string>::iterator f = files.begin(); + f != files.end(); ++f) { + if (this->ShouldIDoCoverage(f->c_str(), cont->SourceDir.c_str(), + cont->BinaryDir.c_str())) { + extraMatches.insert(this->CTest->GetShortPathToFile(f->c_str())); } } + } - if(extraMatches.size()) - { - for(cmCTestCoverageHandlerContainer::TotalCoverageMap::iterator i = - cont->TotalCoverage.begin(); i != cont->TotalCoverage.end(); ++i) - { - std::string shortPath = this->CTest->GetShortPathToFile( - i->first.c_str()); + if (!extraMatches.empty()) { + for (cmCTestCoverageHandlerContainer::TotalCoverageMap::iterator i = + cont->TotalCoverage.begin(); + i != cont->TotalCoverage.end(); ++i) { + std::string shortPath = + this->CTest->GetShortPathToFile(i->first.c_str()); extraMatches.erase(shortPath); - } } + } return extraMatches; } diff --git a/Source/CTest/cmCTestCoverageHandler.h b/Source/CTest/cmCTestCoverageHandler.h index 92b0b2285..933f60660 100644 --- a/Source/CTest/cmCTestCoverageHandler.h +++ b/Source/CTest/cmCTestCoverageHandler.h @@ -1,25 +1,23 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestCoverageHandler_h #define cmCTestCoverageHandler_h +#include "cmConfigure.h" #include "cmCTestGenericHandler.h" -#include "cmListFileCache.h" -#include <cmsys/RegularExpression.hxx> +#include "cmsys/RegularExpression.hxx" +#include <iosfwd> +#include <map> +#include <set> +#include <string> +#include <vector> class cmGeneratedFileStream; +class cmMakefile; +class cmXMLWriter; + class cmCTestCoverageHandlerContainer { public: @@ -30,73 +28,87 @@ public: typedef std::map<std::string, SingleFileCoverageVector> TotalCoverageMap; TotalCoverageMap TotalCoverage; std::ostream* OFS; + bool Quiet; }; /** \class cmCTestCoverageHandler - * \brief A class that handles coverage computaiton for ctest + * \brief A class that handles coverage computation for ctest * */ class cmCTestCoverageHandler : public cmCTestGenericHandler { public: - cmTypeMacro(cmCTestCoverageHandler, cmCTestGenericHandler); + typedef cmCTestGenericHandler Superclass; /* * The main entry point for this class */ - int ProcessHandler(); + int ProcessHandler() CM_OVERRIDE; cmCTestCoverageHandler(); - virtual void Initialize(); + void Initialize() CM_OVERRIDE; /** * This method is called when reading CTest custom file */ - void PopulateCustomVectors(cmMakefile *mf); + void PopulateCustomVectors(cmMakefile* mf) CM_OVERRIDE; /** Report coverage only for sources with these labels. */ - void SetLabelFilter(std::set<cmStdString> const& labels); + void SetLabelFilter(std::set<std::string> const& labels); private: bool ShouldIDoCoverage(const char* file, const char* srcDir, - const char* binDir); + const char* binDir); void CleanCoverageLogFiles(std::ostream& log); bool StartCoverageLogFile(cmGeneratedFileStream& ostr, int logFileCount); void EndCoverageLogFile(cmGeneratedFileStream& ostr, int logFileCount); + void StartCoverageLogXML(cmXMLWriter& xml); + void EndCoverageLogXML(cmXMLWriter& xml); + //! Handle coverage using GCC's GCov int HandleGCovCoverage(cmCTestCoverageHandlerContainer* cont); void FindGCovFiles(std::vector<std::string>& files); + //! Handle coverage using Intel's LCov + int HandleLCovCoverage(cmCTestCoverageHandlerContainer* cont); + bool FindLCovFiles(std::vector<std::string>& files); + //! Handle coverage using xdebug php coverage int HandlePHPCoverage(cmCTestCoverageHandlerContainer* cont); + + //! Handle coverage for Python with coverage.py + int HandleCoberturaCoverage(cmCTestCoverageHandlerContainer* cont); + //! Handle coverage for mumps int HandleMumpsCoverage(cmCTestCoverageHandlerContainer* cont); + //! Handle coverage for Jacoco + int HandleJacocoCoverage(cmCTestCoverageHandlerContainer* cont); + + //! Handle coverage for Delphi (Pascal) + int HandleDelphiCoverage(cmCTestCoverageHandlerContainer* cont); + + //! Handle coverage for Jacoco + int HandleBlanketJSCoverage(cmCTestCoverageHandlerContainer* cont); + //! Handle coverage using Bullseye int HandleBullseyeCoverage(cmCTestCoverageHandlerContainer* cont); int RunBullseyeSourceSummary(cmCTestCoverageHandlerContainer* cont); int RunBullseyeCoverageBranch(cmCTestCoverageHandlerContainer* cont, - std::set<cmStdString>& coveredFileNames, + std::set<std::string>& coveredFileNames, std::vector<std::string>& files, std::vector<std::string>& filesFullPath); - int RunBullseyeCommand( - cmCTestCoverageHandlerContainer* cont, - const char* cmd, - const char* arg, - std::string& outputFile); - bool ParseBullsEyeCovsrcLine( - std::string const& inputLine, - std::string& sourceFile, - int& functionsCalled, - int& totalFunctions, - int& percentFunction, - int& branchCovered, - int& totalBranches, - int& percentBranch); - bool GetNextInt(std::string const& inputLine, - std::string::size_type& pos, + int RunBullseyeCommand(cmCTestCoverageHandlerContainer* cont, + const char* cmd, const char* arg, + std::string& outputFile); + bool ParseBullsEyeCovsrcLine(std::string const& inputLine, + std::string& sourceFile, int& functionsCalled, + int& totalFunctions, int& percentFunction, + int& branchCovered, int& totalBranches, + int& percentBranch); + bool GetNextInt(std::string const& inputLine, std::string::size_type& pos, int& value); //! Handle Python coverage using Python's Trace.py int HandleTracePyCoverage(cmCTestCoverageHandlerContainer* cont); @@ -104,23 +116,24 @@ private: // Find the source file based on the source and build tree. This is used for // Trace.py mode, since that one does not tell us where the source file is. std::string FindFile(cmCTestCoverageHandlerContainer* cont, - std::string fileName); + std::string const& fileName); std::set<std::string> FindUncoveredFiles( cmCTestCoverageHandlerContainer* cont); - std::vector<cmStdString> CustomCoverageExclude; + std::vector<std::string> CustomCoverageExclude; std::vector<cmsys::RegularExpression> CustomCoverageExcludeRegex; - std::vector<cmStdString> ExtraCoverageGlobs; - + std::vector<std::string> ExtraCoverageGlobs; // Map from source file to label ids. - class LabelSet: public std::set<int> {}; - typedef std::map<cmStdString, LabelSet> LabelMapType; + class LabelSet : public std::set<int> + { + }; + typedef std::map<std::string, LabelSet> LabelMapType; LabelMapType SourceLabels; LabelMapType TargetDirs; // Map from label name to label id. - typedef std::map<cmStdString, int> LabelIdMapType; + typedef std::map<std::string, int> LabelIdMapType; LabelIdMapType LabelIdMap; std::vector<std::string> Labels; int GetLabelId(std::string const& label); @@ -128,7 +141,7 @@ private: // Label reading and writing methods. void LoadLabels(); void LoadLabels(const char* dir); - void WriteXMLLabels(std::ofstream& os, std::string const& source); + void WriteXMLLabels(cmXMLWriter& xml, std::string const& source); // Label-based filtering. std::set<int> LabelFilter; diff --git a/Source/CTest/cmCTestCurl.cxx b/Source/CTest/cmCTestCurl.cxx new file mode 100644 index 000000000..b80ea5ade --- /dev/null +++ b/Source/CTest/cmCTestCurl.cxx @@ -0,0 +1,276 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmCTestCurl.h" + +#include "cmCTest.h" +#include "cmSystemTools.h" + +#include "cmConfigure.h" +#include <ostream> +#include <stdio.h> + +cmCTestCurl::cmCTestCurl(cmCTest* ctest) +{ + this->CTest = ctest; + this->SetProxyType(); + this->UseHttp10 = false; + // In windows, this will init the winsock stuff + ::curl_global_init(CURL_GLOBAL_ALL); + // default is to verify https + this->VerifyPeerOff = false; + this->VerifyHostOff = false; + this->Quiet = false; + this->TimeOutSeconds = 0; + this->Curl = curl_easy_init(); +} + +cmCTestCurl::~cmCTestCurl() +{ + ::curl_easy_cleanup(this->Curl); + ::curl_global_cleanup(); +} + +std::string cmCTestCurl::Escape(std::string const& source) +{ + char* data1 = curl_easy_escape(this->Curl, source.c_str(), 0); + std::string ret = data1; + curl_free(data1); + return ret; +} + +namespace { +size_t curlWriteMemoryCallback(void* ptr, size_t size, size_t nmemb, + void* data) +{ + int realsize = (int)(size * nmemb); + + std::vector<char>* vec = static_cast<std::vector<char>*>(data); + const char* chPtr = static_cast<char*>(ptr); + vec->insert(vec->end(), chPtr, chPtr + realsize); + return realsize; +} + +size_t curlDebugCallback(CURL* /*unused*/, curl_infotype /*unused*/, + char* chPtr, size_t size, void* data) +{ + std::vector<char>* vec = static_cast<std::vector<char>*>(data); + vec->insert(vec->end(), chPtr, chPtr + size); + + return size; +} +} + +void cmCTestCurl::SetCurlOptions(std::vector<std::string> const& args) +{ + for (std::vector<std::string>::const_iterator i = args.begin(); + i != args.end(); ++i) { + if (*i == "CURLOPT_SSL_VERIFYPEER_OFF") { + this->VerifyPeerOff = true; + } + if (*i == "CURLOPT_SSL_VERIFYHOST_OFF") { + this->VerifyHostOff = true; + } + } +} + +bool cmCTestCurl::InitCurl() +{ + if (!this->Curl) { + return false; + } + if (this->VerifyPeerOff) { + curl_easy_setopt(this->Curl, CURLOPT_SSL_VERIFYPEER, 0); + } + if (this->VerifyHostOff) { + curl_easy_setopt(this->Curl, CURLOPT_SSL_VERIFYHOST, 0); + } + if (!this->HTTPProxy.empty()) { + curl_easy_setopt(this->Curl, CURLOPT_PROXY, this->HTTPProxy.c_str()); + curl_easy_setopt(this->Curl, CURLOPT_PROXYTYPE, this->HTTPProxyType); + if (!this->HTTPProxyAuth.empty()) { + curl_easy_setopt(this->Curl, CURLOPT_PROXYUSERPWD, + this->HTTPProxyAuth.c_str()); + } + } + if (this->UseHttp10) { + curl_easy_setopt(this->Curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + } + // enable HTTP ERROR parsing + curl_easy_setopt(this->Curl, CURLOPT_FAILONERROR, 1); + + // if there is little to no activity for too long stop submitting + if (this->TimeOutSeconds) { + curl_easy_setopt(this->Curl, CURLOPT_LOW_SPEED_LIMIT, 1); + curl_easy_setopt(this->Curl, CURLOPT_LOW_SPEED_TIME, this->TimeOutSeconds); + } + + return true; +} + +bool cmCTestCurl::UploadFile(std::string const& local_file, + std::string const& url, std::string const& fields, + std::string& response) +{ + response = ""; + if (!this->InitCurl()) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Initialization of curl failed"); + return false; + } + /* enable uploading */ + curl_easy_setopt(this->Curl, CURLOPT_UPLOAD, 1); + + /* HTTP PUT please */ + ::curl_easy_setopt(this->Curl, CURLOPT_PUT, 1); + ::curl_easy_setopt(this->Curl, CURLOPT_VERBOSE, 1); + + FILE* ftpfile = cmsys::SystemTools::Fopen(local_file, "rb"); + if (!ftpfile) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Could not open file for upload: " << local_file << "\n"); + return false; + } + // set the url + std::string upload_url = url; + upload_url += "?"; + upload_url += fields; + ::curl_easy_setopt(this->Curl, CURLOPT_URL, upload_url.c_str()); + // now specify which file to upload + ::curl_easy_setopt(this->Curl, CURLOPT_INFILE, ftpfile); + unsigned long filelen = cmSystemTools::FileLength(local_file); + // and give the size of the upload (optional) + ::curl_easy_setopt(this->Curl, CURLOPT_INFILESIZE, + static_cast<long>(filelen)); + ::curl_easy_setopt(this->Curl, CURLOPT_WRITEFUNCTION, + curlWriteMemoryCallback); + ::curl_easy_setopt(this->Curl, CURLOPT_DEBUGFUNCTION, curlDebugCallback); + // Set Content-Type to satisfy fussy modsecurity rules. + struct curl_slist* headers = + ::curl_slist_append(CM_NULLPTR, "Content-Type: text/xml"); + // Add any additional headers that the user specified. + for (std::vector<std::string>::const_iterator h = this->HttpHeaders.begin(); + h != this->HttpHeaders.end(); ++h) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Add HTTP Header: \"" << *h << "\"" << std::endl, + this->Quiet); + headers = ::curl_slist_append(headers, h->c_str()); + } + ::curl_easy_setopt(this->Curl, CURLOPT_HTTPHEADER, headers); + std::vector<char> responseData; + std::vector<char> debugData; + ::curl_easy_setopt(this->Curl, CURLOPT_FILE, (void*)&responseData); + ::curl_easy_setopt(this->Curl, CURLOPT_DEBUGDATA, (void*)&debugData); + ::curl_easy_setopt(this->Curl, CURLOPT_FAILONERROR, 1); + // Now run off and do what you've been told! + ::curl_easy_perform(this->Curl); + ::fclose(ftpfile); + ::curl_easy_setopt(this->Curl, CURLOPT_HTTPHEADER, NULL); + ::curl_slist_free_all(headers); + + if (!responseData.empty()) { + response = std::string(responseData.begin(), responseData.end()); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Curl response: [" << response << "]\n", this->Quiet); + } + std::string curlDebug; + if (!debugData.empty()) { + curlDebug = std::string(debugData.begin(), debugData.end()); + cmCTestOptionalLog(this->CTest, DEBUG, + "Curl debug: [" << curlDebug << "]\n", this->Quiet); + } + if (response.empty()) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "No response from server.\n" + << curlDebug); + return false; + } + return true; +} + +bool cmCTestCurl::HttpRequest(std::string const& url, + std::string const& fields, std::string& response) +{ + response = ""; + cmCTestOptionalLog(this->CTest, DEBUG, "HttpRequest\n" + << "url: " << url << "\n" + << "fields " << fields << "\n", + this->Quiet); + if (!this->InitCurl()) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Initialization of curl failed"); + return false; + } + curl_easy_setopt(this->Curl, CURLOPT_POST, 1); + curl_easy_setopt(this->Curl, CURLOPT_POSTFIELDS, fields.c_str()); + ::curl_easy_setopt(this->Curl, CURLOPT_URL, url.c_str()); + ::curl_easy_setopt(this->Curl, CURLOPT_FOLLOWLOCATION, 1); + // set response options + ::curl_easy_setopt(this->Curl, CURLOPT_WRITEFUNCTION, + curlWriteMemoryCallback); + ::curl_easy_setopt(this->Curl, CURLOPT_DEBUGFUNCTION, curlDebugCallback); + std::vector<char> responseData; + std::vector<char> debugData; + ::curl_easy_setopt(this->Curl, CURLOPT_FILE, (void*)&responseData); + ::curl_easy_setopt(this->Curl, CURLOPT_DEBUGDATA, (void*)&debugData); + ::curl_easy_setopt(this->Curl, CURLOPT_FAILONERROR, 1); + + // Add headers if any were specified. + struct curl_slist* headers = CM_NULLPTR; + if (!this->HttpHeaders.empty()) { + for (std::vector<std::string>::const_iterator h = + this->HttpHeaders.begin(); + h != this->HttpHeaders.end(); ++h) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Add HTTP Header: \"" << *h << "\"" << std::endl, + this->Quiet); + headers = ::curl_slist_append(headers, h->c_str()); + } + } + + ::curl_easy_setopt(this->Curl, CURLOPT_HTTPHEADER, headers); + CURLcode res = ::curl_easy_perform(this->Curl); + ::curl_slist_free_all(headers); + + if (!responseData.empty()) { + response = std::string(responseData.begin(), responseData.end()); + cmCTestOptionalLog(this->CTest, DEBUG, + "Curl response: [" << response << "]\n", this->Quiet); + } + if (!debugData.empty()) { + std::string curlDebug = std::string(debugData.begin(), debugData.end()); + cmCTestOptionalLog(this->CTest, DEBUG, + "Curl debug: [" << curlDebug << "]\n", this->Quiet); + } + cmCTestOptionalLog(this->CTest, DEBUG, "Curl res: " << res << "\n", + this->Quiet); + return (res == 0); +} + +void cmCTestCurl::SetProxyType() +{ + this->HTTPProxy = ""; + // this is the default + this->HTTPProxyType = CURLPROXY_HTTP; + this->HTTPProxyAuth = ""; + if (cmSystemTools::GetEnv("HTTP_PROXY", this->HTTPProxy)) { + std::string port; + if (cmSystemTools::GetEnv("HTTP_PROXY_PORT", port)) { + this->HTTPProxy += ":"; + this->HTTPProxy += port; + } + std::string type; + if (cmSystemTools::GetEnv("HTTP_PROXY_TYPE", type)) { + // HTTP/SOCKS4/SOCKS5 + if (type == "HTTP") { + this->HTTPProxyType = CURLPROXY_HTTP; + } else if (type == "SOCKS4") { + this->HTTPProxyType = CURLPROXY_SOCKS4; + } else if (type == "SOCKS5") { + this->HTTPProxyType = CURLPROXY_SOCKS5; + } + } + cmSystemTools::GetEnv("HTTP_PROXY_USER", this->HTTPProxyAuth); + std::string passwd; + if (cmSystemTools::GetEnv("HTTP_PROXY_PASSWD", passwd)) { + this->HTTPProxyAuth += ":"; + this->HTTPProxyAuth += passwd; + } + } +} diff --git a/Source/CTest/cmCTestCurl.h b/Source/CTest/cmCTestCurl.h new file mode 100644 index 000000000..427a39232 --- /dev/null +++ b/Source/CTest/cmCTestCurl.h @@ -0,0 +1,53 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmCTestCurl_h +#define cmCTestCurl_h + +#include "cmConfigure.h" // IWYU pragma: keep + +#include "cm_curl.h" +#include <string> +#include <vector> + +class cmCTest; + +class cmCTestCurl +{ +public: + cmCTestCurl(cmCTest*); + ~cmCTestCurl(); + bool UploadFile(std::string const& url, std::string const& file, + std::string const& fields, std::string& response); + bool HttpRequest(std::string const& url, std::string const& fields, + std::string& response); + // currently only supports CURLOPT_SSL_VERIFYPEER_OFF + // and CURLOPT_SSL_VERIFYHOST_OFF + void SetCurlOptions(std::vector<std::string> const& args); + void SetHttpHeaders(std::vector<std::string> const& v) + { + this->HttpHeaders = v; + } + void SetUseHttp10On() { this->UseHttp10 = true; } + void SetTimeOutSeconds(int s) { this->TimeOutSeconds = s; } + void SetQuiet(bool b) { this->Quiet = b; } + std::string Escape(std::string const& source); + +protected: + void SetProxyType(); + bool InitCurl(); + +private: + cmCTest* CTest; + CURL* Curl; + std::vector<std::string> HttpHeaders; + std::string HTTPProxyAuth; + std::string HTTPProxy; + curl_proxytype HTTPProxyType; + bool VerifyHostOff; + bool VerifyPeerOff; + bool UseHttp10; + bool Quiet; + int TimeOutSeconds; +}; + +#endif diff --git a/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.cxx b/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.cxx index abc33de68..f4531a1ee 100644 --- a/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.cxx +++ b/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.cxx @@ -1,36 +1,27 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestEmptyBinaryDirectoryCommand.h" #include "cmCTestScriptHandler.h" -bool cmCTestEmptyBinaryDirectoryCommand -::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &) +#include <sstream> + +class cmExecutionStatus; + +bool cmCTestEmptyBinaryDirectoryCommand::InitialPass( + std::vector<std::string> const& args, cmExecutionStatus& /*unused*/) { - if(args.size() != 1 ) - { + if (args.size() != 1) { this->SetError("called with incorrect number of arguments"); return false; - } + } - if ( !cmCTestScriptHandler::EmptyBinaryDirectory(args[0].c_str()) ) - { - cmOStringStream ostr; - ostr << "problem removing the binary directory: " << args[0].c_str(); - this->SetError(ostr.str().c_str()); + if (!cmCTestScriptHandler::EmptyBinaryDirectory(args[0].c_str())) { + std::ostringstream ostr; + ostr << "problem removing the binary directory: " << args[0]; + this->SetError(ostr.str()); return false; - } + } return true; } - - diff --git a/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.h b/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.h index a763fe9e9..503ed234d 100644 --- a/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.h +++ b/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.h @@ -1,19 +1,18 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestEmptyBinaryDirectoryCommand_h #define cmCTestEmptyBinaryDirectoryCommand_h +#include "cmConfigure.h" + #include "cmCTestCommand.h" +#include <string> +#include <vector> + +class cmCommand; +class cmExecutionStatus; + /** \class cmCTestEmptyBinaryDirectory * \brief Run a ctest script * @@ -23,56 +22,26 @@ class cmCTestEmptyBinaryDirectoryCommand : public cmCTestCommand { public: - cmCTestEmptyBinaryDirectoryCommand() {} /** * This is a virtual constructor for the command. */ - virtual cmCommand* Clone() - { - cmCTestEmptyBinaryDirectoryCommand* ni - = new cmCTestEmptyBinaryDirectoryCommand; + cmCommand* Clone() CM_OVERRIDE + { + cmCTestEmptyBinaryDirectoryCommand* ni = + new cmCTestEmptyBinaryDirectoryCommand; ni->CTest = this->CTest; ni->CTestScriptHandler = this->CTestScriptHandler; return ni; - } + } /** * This is called when the command is first encountered in * the CMakeLists.txt file. */ - virtual bool InitialPass(std::vector<std::string> const& args, - cmExecutionStatus &status); - - /** - * The name of the command as specified in CMakeList.txt. - */ - virtual const char* GetName() const { return "ctest_empty_binary_directory";} - - /** - * Succinct documentation. - */ - virtual const char* GetTerseDocumentation() const - { - return "empties the binary directory"; - } - - /** - * More documentation. - */ - virtual const char* GetFullDocumentation() const - { - return - " ctest_empty_binary_directory( directory )\n" - "Removes a binary directory. This command will perform some checks " - "prior to deleting the directory in an attempt to avoid malicious " - "or accidental directory deletion."; - } - - cmTypeMacro(cmCTestEmptyBinaryDirectoryCommand, cmCTestCommand); - + bool InitialPass(std::vector<std::string> const& args, + cmExecutionStatus& status) CM_OVERRIDE; }; - #endif diff --git a/Source/CTest/cmCTestGIT.cxx b/Source/CTest/cmCTestGIT.cxx index 5b3449181..230aedf3b 100644 --- a/Source/CTest/cmCTestGIT.cxx +++ b/Source/CTest/cmCTestGIT.cxx @@ -1,74 +1,65 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestGIT.h" +#include "cmsys/FStream.hxx" +#include "cmsys/Process.h" +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <vector> + +#include "cmAlgorithms.h" #include "cmCTest.h" +#include "cmCTestVC.h" +#include "cmProcessOutput.h" +#include "cmProcessTools.h" #include "cmSystemTools.h" -#include "cmXMLSafe.h" - -#include <cmsys/RegularExpression.hxx> -#include <cmsys/ios/sstream> -#include <cmsys/Process.h> -#include <sys/types.h> -#include <time.h> -#include <ctype.h> - -//---------------------------------------------------------------------------- static unsigned int cmCTestGITVersion(unsigned int epic, unsigned int major, unsigned int minor, unsigned int fix) { // 1.6.5.0 maps to 10605000 - return fix + minor*1000 + major*100000 + epic*10000000; + return fix + minor * 1000 + major * 100000 + epic * 10000000; } -//---------------------------------------------------------------------------- -cmCTestGIT::cmCTestGIT(cmCTest* ct, std::ostream& log): - cmCTestGlobalVC(ct, log) +cmCTestGIT::cmCTestGIT(cmCTest* ct, std::ostream& log) + : cmCTestGlobalVC(ct, log) { this->PriorRev = this->Unknown; this->CurrentGitVersion = 0; } -//---------------------------------------------------------------------------- cmCTestGIT::~cmCTestGIT() { } -//---------------------------------------------------------------------------- -class cmCTestGIT::OneLineParser: public cmCTestVC::LineParser +class cmCTestGIT::OneLineParser : public cmCTestVC::LineParser { public: - OneLineParser(cmCTestGIT* git, const char* prefix, - std::string& l): Line1(l) - { + OneLineParser(cmCTestGIT* git, const char* prefix, std::string& l) + : Line1(l) + { this->SetLog(&git->Log, prefix); - } + } + private: std::string& Line1; - virtual bool ProcessLine() - { + bool ProcessLine() CM_OVERRIDE + { // Only the first line is of interest. this->Line1 = this->Line; return false; - } + } }; -//---------------------------------------------------------------------------- std::string cmCTestGIT::GetWorkingRevision() { // Run plumbing "git rev-list" to get work tree revision. const char* git = this->CommandLineTool.c_str(); - const char* git_rev_list[] = {git, "rev-list", "-n", "1", "HEAD", "--", 0}; + const char* git_rev_list[] = { git, "rev-list", "-n", "1", + "HEAD", "--", CM_NULLPTR }; std::string rev; OneLineParser out(this, "rl-out> ", rev); OutputLogger err(this->Log, "rl-err> "); @@ -76,93 +67,88 @@ std::string cmCTestGIT::GetWorkingRevision() return rev; } -//---------------------------------------------------------------------------- -void cmCTestGIT::NoteOldRevision() +bool cmCTestGIT::NoteOldRevision() { this->OldRevision = this->GetWorkingRevision(); cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: " - << this->OldRevision << "\n"); + << this->OldRevision << "\n"); this->PriorRev.Rev = this->OldRevision; + return true; } -//---------------------------------------------------------------------------- -void cmCTestGIT::NoteNewRevision() +bool cmCTestGIT::NoteNewRevision() { this->NewRevision = this->GetWorkingRevision(); cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: " - << this->NewRevision << "\n"); + << this->NewRevision << "\n"); + return true; } -//---------------------------------------------------------------------------- std::string cmCTestGIT::FindGitDir() { std::string git_dir; // Run "git rev-parse --git-dir" to locate the real .git directory. const char* git = this->CommandLineTool.c_str(); - char const* git_rev_parse[] = {git, "rev-parse", "--git-dir", 0}; + char const* git_rev_parse[] = { git, "rev-parse", "--git-dir", CM_NULLPTR }; std::string git_dir_line; OneLineParser rev_parse_out(this, "rev-parse-out> ", git_dir_line); OutputLogger rev_parse_err(this->Log, "rev-parse-err> "); - if(this->RunChild(git_rev_parse, &rev_parse_out, &rev_parse_err)) - { + if (this->RunChild(git_rev_parse, &rev_parse_out, &rev_parse_err, CM_NULLPTR, + cmProcessOutput::UTF8)) { git_dir = git_dir_line; - } - if(git_dir.empty()) - { + } + if (git_dir.empty()) { git_dir = ".git"; - } + } // Git reports a relative path only when the .git directory is in // the current directory. - if(git_dir[0] == '.') - { + if (git_dir[0] == '.') { git_dir = this->SourceDirectory + "/" + git_dir; - } + } #if defined(_WIN32) && !defined(__CYGWIN__) - else if(git_dir[0] == '/') - { + else if (git_dir[0] == '/') { // Cygwin Git reports a full path that Cygwin understands, but we // are a Windows application. Run "cygpath" to get Windows path. std::string cygpath_exe = cmSystemTools::GetFilenamePath(git); cygpath_exe += "/cygpath.exe"; - if(cmSystemTools::FileExists(cygpath_exe.c_str())) - { - char const* cygpath[] = {cygpath_exe.c_str(), "-w", git_dir.c_str(), 0}; + if (cmSystemTools::FileExists(cygpath_exe.c_str())) { + char const* cygpath[] = { cygpath_exe.c_str(), "-w", git_dir.c_str(), + 0 }; OneLineParser cygpath_out(this, "cygpath-out> ", git_dir_line); OutputLogger cygpath_err(this->Log, "cygpath-err> "); - if(this->RunChild(cygpath, &cygpath_out, &cygpath_err)) - { + if (this->RunChild(cygpath, &cygpath_out, &cygpath_err, CM_NULLPTR, + cmProcessOutput::UTF8)) { git_dir = git_dir_line; - } } } + } #endif return git_dir; } -//---------------------------------------------------------------------------- std::string cmCTestGIT::FindTopDir() { std::string top_dir = this->SourceDirectory; // Run "git rev-parse --show-cdup" to locate the top of the tree. const char* git = this->CommandLineTool.c_str(); - char const* git_rev_parse[] = {git, "rev-parse", "--show-cdup", 0}; + char const* git_rev_parse[] = { git, "rev-parse", "--show-cdup", + CM_NULLPTR }; std::string cdup; OneLineParser rev_parse_out(this, "rev-parse-out> ", cdup); OutputLogger rev_parse_err(this->Log, "rev-parse-err> "); - if(this->RunChild(git_rev_parse, &rev_parse_out, &rev_parse_err) && - !cdup.empty()) - { + if (this->RunChild(git_rev_parse, &rev_parse_out, &rev_parse_err, CM_NULLPTR, + cmProcessOutput::UTF8) && + !cdup.empty()) { top_dir += "/"; top_dir += cdup; - top_dir = cmSystemTools::CollapseFullPath(top_dir.c_str()); - } + top_dir = cmSystemTools::CollapseFullPath(top_dir); + } return top_dir; } -//---------------------------------------------------------------------------- bool cmCTestGIT::UpdateByFetchAndReset() { const char* git = this->CommandLineTool.c_str(); @@ -174,146 +160,163 @@ bool cmCTestGIT::UpdateByFetchAndReset() // Add user-specified update options. std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); - if(opts.empty()) - { + if (opts.empty()) { opts = this->CTest->GetCTestConfiguration("GITUpdateOptions"); - } - std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str()); - for(std::vector<cmStdString>::const_iterator ai = args.begin(); - ai != args.end(); ++ai) - { + } + std::vector<std::string> args = cmSystemTools::ParseArguments(opts.c_str()); + for (std::vector<std::string>::const_iterator ai = args.begin(); + ai != args.end(); ++ai) { git_fetch.push_back(ai->c_str()); - } + } // Sentinel argument. - git_fetch.push_back(0); + git_fetch.push_back(CM_NULLPTR); // Fetch upstream refs. OutputLogger fetch_out(this->Log, "fetch-out> "); OutputLogger fetch_err(this->Log, "fetch-err> "); - if(!this->RunUpdateCommand(&git_fetch[0], &fetch_out, &fetch_err)) - { + if (!this->RunUpdateCommand(&git_fetch[0], &fetch_out, &fetch_err)) { return false; - } + } // Identify the merge head that would be used by "git pull". std::string sha1; { - std::string fetch_head = this->FindGitDir() + "/FETCH_HEAD"; - std::ifstream fin(fetch_head.c_str(), std::ios::in | std::ios::binary); - if(!fin) - { - this->Log << "Unable to open " << fetch_head << "\n"; - return false; + std::string fetch_head = this->FindGitDir() + "/FETCH_HEAD"; + cmsys::ifstream fin(fetch_head.c_str(), std::ios::in | std::ios::binary); + if (!fin) { + this->Log << "Unable to open " << fetch_head << "\n"; + return false; } - std::string line; - while(sha1.empty() && cmSystemTools::GetLineFromStream(fin, line)) - { - this->Log << "FETCH_HEAD> " << line << "\n"; - if(line.find("\tnot-for-merge\t") == line.npos) - { - std::string::size_type pos = line.find('\t'); - if(pos != line.npos) - { - sha1 = line.substr(0, pos); + std::string line; + while (sha1.empty() && cmSystemTools::GetLineFromStream(fin, line)) { + this->Log << "FETCH_HEAD> " << line << "\n"; + if (line.find("\tnot-for-merge\t") == std::string::npos) { + std::string::size_type pos = line.find('\t'); + if (pos != std::string::npos) { + sha1 = line.substr(0, pos); } } } - if(sha1.empty()) - { - this->Log << "FETCH_HEAD has no upstream branch candidate!\n"; - return false; + if (sha1.empty()) { + this->Log << "FETCH_HEAD has no upstream branch candidate!\n"; + return false; } } // Reset the local branch to point at that tracked from upstream. - char const* git_reset[] = {git, "reset", "--hard", sha1.c_str(), 0}; + char const* git_reset[] = { git, "reset", "--hard", sha1.c_str(), + CM_NULLPTR }; OutputLogger reset_out(this->Log, "reset-out> "); OutputLogger reset_err(this->Log, "reset-err> "); return this->RunChild(&git_reset[0], &reset_out, &reset_err); } -//---------------------------------------------------------------------------- bool cmCTestGIT::UpdateByCustom(std::string const& custom) { std::vector<std::string> git_custom_command; cmSystemTools::ExpandListArgument(custom, git_custom_command, true); std::vector<char const*> git_custom; - for(std::vector<std::string>::const_iterator - i = git_custom_command.begin(); i != git_custom_command.end(); ++i) - { + for (std::vector<std::string>::const_iterator i = git_custom_command.begin(); + i != git_custom_command.end(); ++i) { git_custom.push_back(i->c_str()); - } - git_custom.push_back(0); + } + git_custom.push_back(CM_NULLPTR); OutputLogger custom_out(this->Log, "custom-out> "); OutputLogger custom_err(this->Log, "custom-err> "); return this->RunUpdateCommand(&git_custom[0], &custom_out, &custom_err); } -//---------------------------------------------------------------------------- bool cmCTestGIT::UpdateInternal() { std::string custom = this->CTest->GetCTestConfiguration("GITUpdateCustom"); - if(!custom.empty()) - { + if (!custom.empty()) { return this->UpdateByCustom(custom); - } + } return this->UpdateByFetchAndReset(); } -//---------------------------------------------------------------------------- bool cmCTestGIT::UpdateImpl() { - if(!this->UpdateInternal()) - { + if (!this->UpdateInternal()) { return false; - } + } std::string top_dir = this->FindTopDir(); const char* git = this->CommandLineTool.c_str(); const char* recursive = "--recursive"; + const char* sync_recursive = "--recursive"; + + // Git < 1.6.5 did not support submodule --recursive + if (this->GetGitVersion() < cmCTestGITVersion(1, 6, 5, 0)) { + recursive = CM_NULLPTR; + // No need to require >= 1.6.5 if there are no submodules. + if (cmSystemTools::FileExists((top_dir + "/.gitmodules").c_str())) { + this->Log << "Git < 1.6.5 cannot update submodules recursively\n"; + } + } - // Git < 1.6.5.0 did not support --recursive - if(this->GetGitVersion() < cmCTestGITVersion(1,6,5,0)) - { - recursive = 0; - // No need to require >= 1.6.5.0 if there are no submodules. - if(cmSystemTools::FileExists((top_dir + "/.gitmodules").c_str())) - { - this->Log << "Git < 1.6.5.0 cannot update submodules recursively\n"; - } + // Git < 1.8.1 did not support sync --recursive + if (this->GetGitVersion() < cmCTestGITVersion(1, 8, 1, 0)) { + sync_recursive = CM_NULLPTR; + // No need to require >= 1.8.1 if there are no submodules. + if (cmSystemTools::FileExists((top_dir + "/.gitmodules").c_str())) { + this->Log << "Git < 1.8.1 cannot synchronize submodules recursively\n"; } + } - char const* git_submodule[] = {git, "submodule", "update", recursive, 0}; OutputLogger submodule_out(this->Log, "submodule-out> "); OutputLogger submodule_err(this->Log, "submodule-err> "); + + bool ret; + + std::string init_submodules = + this->CTest->GetCTestConfiguration("GITInitSubmodules"); + if (cmSystemTools::IsOn(init_submodules.c_str())) { + char const* git_submodule_init[] = { git, "submodule", "init", + CM_NULLPTR }; + ret = this->RunChild(git_submodule_init, &submodule_out, &submodule_err, + top_dir.c_str()); + + if (!ret) { + return false; + } + } + + char const* git_submodule_sync[] = { git, "submodule", "sync", + sync_recursive, CM_NULLPTR }; + ret = this->RunChild(git_submodule_sync, &submodule_out, &submodule_err, + top_dir.c_str()); + + if (!ret) { + return false; + } + + char const* git_submodule[] = { git, "submodule", "update", recursive, + CM_NULLPTR }; return this->RunChild(git_submodule, &submodule_out, &submodule_err, top_dir.c_str()); } -//---------------------------------------------------------------------------- unsigned int cmCTestGIT::GetGitVersion() { - if(!this->CurrentGitVersion) - { + if (!this->CurrentGitVersion) { const char* git = this->CommandLineTool.c_str(); - char const* git_version[] = {git, "--version", 0}; + char const* git_version[] = { git, "--version", CM_NULLPTR }; std::string version; OneLineParser version_out(this, "version-out> ", version); OutputLogger version_err(this->Log, "version-err> "); - unsigned int v[4] = {0,0,0,0}; - if(this->RunChild(git_version, &version_out, &version_err) && - sscanf(version.c_str(), "git version %u.%u.%u.%u", - &v[0], &v[1], &v[2], &v[3]) >= 3) - { + unsigned int v[4] = { 0, 0, 0, 0 }; + if (this->RunChild(git_version, &version_out, &version_err) && + sscanf(version.c_str(), "git version %u.%u.%u.%u", &v[0], &v[1], &v[2], + &v[3]) >= 3) { this->CurrentGitVersion = cmCTestGITVersion(v[0], v[1], v[2], v[3]); - } } + } return this->CurrentGitVersion; } -//---------------------------------------------------------------------------- /* Diff format: :src-mode dst-mode src-sha1 dst-sha1 status\0 @@ -324,76 +327,73 @@ unsigned int cmCTestGIT::GetGitVersion() line appears only for lines with status 'C' or 'R'. See 'git help diff-tree' for details. */ -class cmCTestGIT::DiffParser: public cmCTestVC::LineParser +class cmCTestGIT::DiffParser : public cmCTestVC::LineParser { public: - DiffParser(cmCTestGIT* git, const char* prefix): - LineParser('\0', false), GIT(git), DiffField(DiffFieldNone) - { + DiffParser(cmCTestGIT* git, const char* prefix) + : LineParser('\0', false) + , GIT(git) + , DiffField(DiffFieldNone) + { this->SetLog(&git->Log, prefix); - } + } typedef cmCTestGIT::Change Change; std::vector<Change> Changes; + protected: cmCTestGIT* GIT; - enum DiffFieldType { DiffFieldNone, DiffFieldChange, - DiffFieldSrc, DiffFieldDst }; + enum DiffFieldType + { + DiffFieldNone, + DiffFieldChange, + DiffFieldSrc, + DiffFieldDst + }; DiffFieldType DiffField; Change CurChange; void DiffReset() - { + { this->DiffField = DiffFieldNone; this->Changes.clear(); - } + } - virtual bool ProcessLine() - { - if(this->Line[0] == ':') - { + bool ProcessLine() CM_OVERRIDE + { + if (this->Line[0] == ':') { this->DiffField = DiffFieldChange; this->CurChange = Change(); - } - if(this->DiffField == DiffFieldChange) - { + } + if (this->DiffField == DiffFieldChange) { // :src-mode dst-mode src-sha1 dst-sha1 status - if(this->Line[0] != ':') - { + if (this->Line[0] != ':') { this->DiffField = DiffFieldNone; return true; - } - const char* src_mode_first = this->Line.c_str()+1; - const char* src_mode_last = this->ConsumeField(src_mode_first); + } + const char* src_mode_first = this->Line.c_str() + 1; + const char* src_mode_last = this->ConsumeField(src_mode_first); const char* dst_mode_first = this->ConsumeSpace(src_mode_last); - const char* dst_mode_last = this->ConsumeField(dst_mode_first); + const char* dst_mode_last = this->ConsumeField(dst_mode_first); const char* src_sha1_first = this->ConsumeSpace(dst_mode_last); - const char* src_sha1_last = this->ConsumeField(src_sha1_first); + const char* src_sha1_last = this->ConsumeField(src_sha1_first); const char* dst_sha1_first = this->ConsumeSpace(src_sha1_last); - const char* dst_sha1_last = this->ConsumeField(dst_sha1_first); - const char* status_first = this->ConsumeSpace(dst_sha1_last); - const char* status_last = this->ConsumeField(status_first); - if(status_first != status_last) - { + const char* dst_sha1_last = this->ConsumeField(dst_sha1_first); + const char* status_first = this->ConsumeSpace(dst_sha1_last); + const char* status_last = this->ConsumeField(status_first); + if (status_first != status_last) { this->CurChange.Action = *status_first; this->DiffField = DiffFieldSrc; - } - else - { + } else { this->DiffField = DiffFieldNone; - } } - else if(this->DiffField == DiffFieldSrc) - { + } else if (this->DiffField == DiffFieldSrc) { // src-path - if(this->CurChange.Action == 'C') - { + if (this->CurChange.Action == 'C') { // Convert copy to addition of destination. this->CurChange.Action = 'A'; this->DiffField = DiffFieldDst; - } - else if(this->CurChange.Action == 'R') - { + } else if (this->CurChange.Action == 'R') { // Convert rename to deletion of source and addition of destination. this->CurChange.Action = 'D'; this->CurChange.Path = this->Line; @@ -401,37 +401,36 @@ protected: this->CurChange = Change('A'); this->DiffField = DiffFieldDst; - } - else - { + } else { this->CurChange.Path = this->Line; this->Changes.push_back(this->CurChange); this->DiffField = this->DiffFieldNone; - } } - else if(this->DiffField == DiffFieldDst) - { + } else if (this->DiffField == DiffFieldDst) { // dst-path this->CurChange.Path = this->Line; this->Changes.push_back(this->CurChange); this->DiffField = this->DiffFieldNone; - } - return true; } + return true; + } const char* ConsumeSpace(const char* c) - { - while(*c && isspace(*c)) { ++c; } - return c; + { + while (*c && isspace(*c)) { + ++c; } - const char* ConsumeField(const char* c) - { - while(*c && !isspace(*c)) { ++c; } return c; + } + const char* ConsumeField(const char* c) + { + while (*c && !isspace(*c)) { + ++c; } + return c; + } }; -//---------------------------------------------------------------------------- /* Commit format: commit ...\n @@ -451,18 +450,25 @@ protected: The header may have more fields. See 'git help diff-tree'. */ -class cmCTestGIT::CommitParser: public cmCTestGIT::DiffParser +class cmCTestGIT::CommitParser : public cmCTestGIT::DiffParser { public: - CommitParser(cmCTestGIT* git, const char* prefix): - DiffParser(git, prefix), Section(SectionHeader) - { + CommitParser(cmCTestGIT* git, const char* prefix) + : DiffParser(git, prefix) + , Section(SectionHeader) + { this->Separator = SectionSep[this->Section]; - } + } private: typedef cmCTestGIT::Revision Revision; - enum SectionType { SectionHeader, SectionBody, SectionDiff, SectionCount }; + enum SectionType + { + SectionHeader, + SectionBody, + SectionDiff, + SectionCount + }; static char const SectionSep[SectionCount]; SectionType Section; Revision Rev; @@ -473,142 +479,147 @@ private: std::string EMail; unsigned long Time; long TimeZone; - Person(): Name(), EMail(), Time(0), TimeZone(0) {} + Person() + : Name() + , EMail() + , Time(0) + , TimeZone(0) + { + } }; void ParsePerson(const char* str, Person& person) - { + { // Person Name <person@domain.com> 1234567890 +0000 const char* c = str; - while(*c && isspace(*c)) { ++c; } + while (*c && isspace(*c)) { + ++c; + } const char* name_first = c; - while(*c && *c != '<') { ++c; } + while (*c && *c != '<') { + ++c; + } const char* name_last = c; - while(name_last != name_first && isspace(*(name_last-1))) { --name_last; } - person.Name.assign(name_first, name_last-name_first); + while (name_last != name_first && isspace(*(name_last - 1))) { + --name_last; + } + person.Name.assign(name_first, name_last - name_first); - const char* email_first = *c? ++c : c; - while(*c && *c != '>') { ++c; } - const char* email_last = *c? c++ : c; - person.EMail.assign(email_first, email_last-email_first); + const char* email_first = *c ? ++c : c; + while (*c && *c != '>') { + ++c; + } + const char* email_last = *c ? c++ : c; + person.EMail.assign(email_first, email_last - email_first); person.Time = strtoul(c, (char**)&c, 10); person.TimeZone = strtol(c, (char**)&c, 10); - } + } - virtual bool ProcessLine() - { - if(this->Line.empty()) - { - if(this->Section == SectionBody && this->LineEnd == '\0') - { + bool ProcessLine() CM_OVERRIDE + { + if (this->Line.empty()) { + if (this->Section == SectionBody && this->LineEnd == '\0') { // Skip SectionDiff this->NextSection(); - } - this->NextSection(); } - else - { - switch(this->Section) - { - case SectionHeader: this->DoHeaderLine(); break; - case SectionBody: this->DoBodyLine(); break; - case SectionDiff: this->DiffParser::ProcessLine(); break; - case SectionCount: break; // never happens - } + this->NextSection(); + } else { + switch (this->Section) { + case SectionHeader: + this->DoHeaderLine(); + break; + case SectionBody: + this->DoBodyLine(); + break; + case SectionDiff: + this->DiffParser::ProcessLine(); + break; + case SectionCount: + break; // never happens } - return true; } + return true; + } void NextSection() - { - this->Section = SectionType((this->Section+1) % SectionCount); + { + this->Section = SectionType((this->Section + 1) % SectionCount); this->Separator = SectionSep[this->Section]; - if(this->Section == SectionHeader) - { + if (this->Section == SectionHeader) { this->GIT->DoRevision(this->Rev, this->Changes); this->Rev = Revision(); this->DiffReset(); - } } + } void DoHeaderLine() - { + { // Look for header fields that we need. - if(strncmp(this->Line.c_str(), "commit ", 7) == 0) - { - this->Rev.Rev = this->Line.c_str()+7; - } - else if(strncmp(this->Line.c_str(), "author ", 7) == 0) - { + if (cmHasLiteralPrefix(this->Line.c_str(), "commit ")) { + this->Rev.Rev = this->Line.c_str() + 7; + } else if (cmHasLiteralPrefix(this->Line.c_str(), "author ")) { Person author; - this->ParsePerson(this->Line.c_str()+7, author); + this->ParsePerson(this->Line.c_str() + 7, author); this->Rev.Author = author.Name; this->Rev.EMail = author.EMail; this->Rev.Date = this->FormatDateTime(author); - } - else if(strncmp(this->Line.c_str(), "committer ", 10) == 0) - { + } else if (cmHasLiteralPrefix(this->Line.c_str(), "committer ")) { Person committer; - this->ParsePerson(this->Line.c_str()+10, committer); + this->ParsePerson(this->Line.c_str() + 10, committer); this->Rev.Committer = committer.Name; this->Rev.CommitterEMail = committer.EMail; this->Rev.CommitDate = this->FormatDateTime(committer); - } } + } void DoBodyLine() - { + { // Commit log lines are indented by 4 spaces. - if(this->Line.size() >= 4) - { + if (this->Line.size() >= 4) { this->Rev.Log += this->Line.substr(4); - } - this->Rev.Log += "\n"; } + this->Rev.Log += "\n"; + } std::string FormatDateTime(Person const& person) - { + { // Convert the time to a human-readable format that is also easy // to machine-parse: "CCYY-MM-DD hh:mm:ss". time_t seconds = static_cast<time_t>(person.Time); struct tm* t = gmtime(&seconds); char dt[1024]; - sprintf(dt, "%04d-%02d-%02d %02d:%02d:%02d", - t->tm_year+1900, t->tm_mon+1, t->tm_mday, - t->tm_hour, t->tm_min, t->tm_sec); + sprintf(dt, "%04d-%02d-%02d %02d:%02d:%02d", t->tm_year + 1900, + t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); std::string out = dt; // Add the time-zone field "+zone" or "-zone". char tz[32]; - if(person.TimeZone >= 0) - { + if (person.TimeZone >= 0) { sprintf(tz, " +%04ld", person.TimeZone); - } - else - { + } else { sprintf(tz, " -%04ld", -person.TimeZone); - } + } out += tz; return out; - } + } }; -char const cmCTestGIT::CommitParser::SectionSep[SectionCount] = -{'\n', '\n', '\0'}; +char const cmCTestGIT::CommitParser::SectionSep[SectionCount] = { '\n', '\n', + '\0' }; -//---------------------------------------------------------------------------- -void cmCTestGIT::LoadRevisions() +bool cmCTestGIT::LoadRevisions() { // Use 'git rev-list ... | git diff-tree ...' to get revisions. std::string range = this->OldRevision + ".." + this->NewRevision; const char* git = this->CommandLineTool.c_str(); - const char* git_rev_list[] = - {git, "rev-list", "--reverse", range.c_str(), "--", 0}; - const char* git_diff_tree[] = - {git, "diff-tree", "--stdin", "--always", "-z", "-r", "--pretty=raw", - "--encoding=utf-8", 0}; + const char* git_rev_list[] = { git, "rev-list", "--reverse", + range.c_str(), "--", CM_NULLPTR }; + const char* git_diff_tree[] = { + git, "diff-tree", "--stdin", "--always", "-z", + "-r", "--pretty=raw", "--encoding=utf-8", CM_NULLPTR + }; this->Log << this->ComputeCommandLine(git_rev_list) << " | " << this->ComputeCommandLine(git_diff_tree) << "\n"; @@ -619,34 +630,38 @@ void cmCTestGIT::LoadRevisions() CommitParser out(this, "dt-out> "); OutputLogger err(this->Log, "dt-err> "); - this->RunProcess(cp, &out, &err); + this->RunProcess(cp, &out, &err, cmProcessOutput::UTF8); // Send one extra zero-byte to terminate the last record. out.Process("", 1); cmsysProcess_Delete(cp); + return true; } -//---------------------------------------------------------------------------- -void cmCTestGIT::LoadModifications() +bool cmCTestGIT::LoadModifications() { const char* git = this->CommandLineTool.c_str(); // Use 'git update-index' to refresh the index w.r.t. the work tree. - const char* git_update_index[] = {git, "update-index", "--refresh", 0}; + const char* git_update_index[] = { git, "update-index", "--refresh", + CM_NULLPTR }; OutputLogger ui_out(this->Log, "ui-out> "); OutputLogger ui_err(this->Log, "ui-err> "); - this->RunChild(git_update_index, &ui_out, &ui_err); + this->RunChild(git_update_index, &ui_out, &ui_err, CM_NULLPTR, + cmProcessOutput::UTF8); // Use 'git diff-index' to get modified files. - const char* git_diff_index[] = {git, "diff-index", "-z", "HEAD", "--", 0}; + const char* git_diff_index[] = { git, "diff-index", "-z", + "HEAD", "--", CM_NULLPTR }; DiffParser out(this, "di-out> "); OutputLogger err(this->Log, "di-err> "); - this->RunChild(git_diff_index, &out, &err); + this->RunChild(git_diff_index, &out, &err, CM_NULLPTR, + cmProcessOutput::UTF8); - for(std::vector<Change>::const_iterator ci = out.Changes.begin(); - ci != out.Changes.end(); ++ci) - { + for (std::vector<Change>::const_iterator ci = out.Changes.begin(); + ci != out.Changes.end(); ++ci) { this->DoModification(PathModified, ci->Path); - } + } + return true; } diff --git a/Source/CTest/cmCTestGIT.h b/Source/CTest/cmCTestGIT.h index f4fae8f9d..4bf8294e6 100644 --- a/Source/CTest/cmCTestGIT.h +++ b/Source/CTest/cmCTestGIT.h @@ -1,38 +1,36 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestGIT_h #define cmCTestGIT_h +#include "cmConfigure.h" + #include "cmCTestGlobalVC.h" +#include <iosfwd> +#include <string> + +class cmCTest; + /** \class cmCTestGIT * \brief Interaction with git command-line tool * */ -class cmCTestGIT: public cmCTestGlobalVC +class cmCTestGIT : public cmCTestGlobalVC { public: /** Construct with a CTest instance and update log stream. */ cmCTestGIT(cmCTest* ctest, std::ostream& log); - virtual ~cmCTestGIT(); + ~cmCTestGIT() CM_OVERRIDE; private: unsigned int CurrentGitVersion; unsigned int GetGitVersion(); std::string GetWorkingRevision(); - virtual void NoteOldRevision(); - virtual void NoteNewRevision(); - virtual bool UpdateImpl(); + bool NoteOldRevision() CM_OVERRIDE; + bool NoteNewRevision() CM_OVERRIDE; + bool UpdateImpl() CM_OVERRIDE; std::string FindGitDir(); std::string FindTopDir(); @@ -41,17 +39,19 @@ private: bool UpdateByCustom(std::string const& custom); bool UpdateInternal(); - void LoadRevisions(); - void LoadModifications(); + bool LoadRevisions() CM_OVERRIDE; + bool LoadModifications() CM_OVERRIDE; -public: // needed by older Sun compilers + // "public" needed by older Sun compilers +public: // Parsing helper classes. - class OneLineParser; - class DiffParser; class CommitParser; - friend class OneLineParser; - friend class DiffParser; + class DiffParser; + class OneLineParser; + friend class CommitParser; + friend class DiffParser; + friend class OneLineParser; }; #endif diff --git a/Source/CTest/cmCTestGenericHandler.cxx b/Source/CTest/cmCTestGenericHandler.cxx index 5338f307b..19034c01b 100644 --- a/Source/CTest/cmCTestGenericHandler.cxx +++ b/Source/CTest/cmCTestGenericHandler.cxx @@ -1,171 +1,138 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestGenericHandler.h" -#include "cmSystemTools.h" + +#include "cmConfigure.h" +#include <sstream> +#include <utility> #include "cmCTest.h" +#include "cmSystemTools.h" -//---------------------------------------------------------------------- cmCTestGenericHandler::cmCTestGenericHandler() { this->HandlerVerbose = cmSystemTools::OUTPUT_NONE; - this->CTest = 0; + this->CTest = CM_NULLPTR; this->SubmitIndex = 0; this->AppendXML = false; + this->Quiet = false; + this->TestLoad = 0; } -//---------------------------------------------------------------------- cmCTestGenericHandler::~cmCTestGenericHandler() { } -//---------------------------------------------------------------------- -void cmCTestGenericHandler::SetOption(const char* op, const char* value) +void cmCTestGenericHandler::SetOption(const std::string& op, const char* value) { - if ( !op ) - { - return; - } - if ( !value ) - { - cmCTestGenericHandler::t_StringToString::iterator remit - = this->Options.find(op); - if ( remit != this->Options.end() ) - { + if (!value) { + cmCTestGenericHandler::t_StringToString::iterator remit = + this->Options.find(op); + if (remit != this->Options.end()) { this->Options.erase(remit); - } - return; } + return; + } this->Options[op] = value; } -//---------------------------------------------------------------------- -void cmCTestGenericHandler::SetPersistentOption(const char* op, +void cmCTestGenericHandler::SetPersistentOption(const std::string& op, const char* value) { this->SetOption(op, value); - if ( !op ) - { - return; - } - if ( !value ) - { - cmCTestGenericHandler::t_StringToString::iterator remit - = this->PersistentOptions.find(op); - if ( remit != this->PersistentOptions.end() ) - { + if (!value) { + cmCTestGenericHandler::t_StringToString::iterator remit = + this->PersistentOptions.find(op); + if (remit != this->PersistentOptions.end()) { this->PersistentOptions.erase(remit); - } - return; } + return; + } this->PersistentOptions[op] = value; } -//---------------------------------------------------------------------- void cmCTestGenericHandler::Initialize() { this->AppendXML = false; + this->TestLoad = 0; this->Options.clear(); t_StringToString::iterator it; - for ( it = this->PersistentOptions.begin(); - it != this->PersistentOptions.end(); - ++ it ) - { - this->Options[it->first.c_str()] = it->second.c_str(); - } + for (it = this->PersistentOptions.begin(); + it != this->PersistentOptions.end(); ++it) { + this->Options[it->first] = it->second; + } } -//---------------------------------------------------------------------- -const char* cmCTestGenericHandler::GetOption(const char* op) +const char* cmCTestGenericHandler::GetOption(const std::string& op) { - cmCTestGenericHandler::t_StringToString::iterator remit - = this->Options.find(op); - if ( remit == this->Options.end() ) - { - return 0; - } + cmCTestGenericHandler::t_StringToString::iterator remit = + this->Options.find(op); + if (remit == this->Options.end()) { + return CM_NULLPTR; + } return remit->second.c_str(); } -//---------------------------------------------------------------------- bool cmCTestGenericHandler::StartResultingXML(cmCTest::Part part, const char* name, cmGeneratedFileStream& xofs) { - if ( !name ) - { + if (!name) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot create resulting XML file without providing the name" - << std::endl;); + "Cannot create resulting XML file without providing the name" + << std::endl;); return false; - } - cmOStringStream ostr; + } + std::ostringstream ostr; ostr << name; - if ( this->SubmitIndex > 0 ) - { + if (this->SubmitIndex > 0) { ostr << "_" << this->SubmitIndex; - } + } ostr << ".xml"; - if(this->CTest->GetCurrentTag().empty()) - { + if (this->CTest->GetCurrentTag().empty()) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Current Tag empty, this may mean NightlyStartTime / " "CTEST_NIGHTLY_START_TIME was not set correctly. Or " "maybe you forgot to call ctest_start() before calling " - "ctest_configure()." << std::endl); + "ctest_configure()." + << std::endl); cmSystemTools::SetFatalErrorOccured(); return false; - } - if( !this->CTest->OpenOutputFile(this->CTest->GetCurrentTag(), - ostr.str().c_str(), xofs, true) ) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot create resulting XML file: " << ostr.str().c_str() - << std::endl); + } + if (!this->CTest->OpenOutputFile(this->CTest->GetCurrentTag(), ostr.str(), + xofs, true)) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot create resulting XML file: " + << ostr.str() << std::endl); return false; - } + } this->CTest->AddSubmitFile(part, ostr.str().c_str()); return true; } -//---------------------------------------------------------------------- bool cmCTestGenericHandler::StartLogFile(const char* name, - cmGeneratedFileStream& xofs) + cmGeneratedFileStream& xofs) { - if ( !name ) - { + if (!name) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot create log file without providing the name" << std::endl;); + "Cannot create log file without providing the name" + << std::endl;); return false; - } - cmOStringStream ostr; + } + std::ostringstream ostr; ostr << "Last" << name; - if ( this->SubmitIndex > 0 ) - { + if (this->SubmitIndex > 0) { ostr << "_" << this->SubmitIndex; - } - if ( !this->CTest->GetCurrentTag().empty() ) - { + } + if (!this->CTest->GetCurrentTag().empty()) { ostr << "_" << this->CTest->GetCurrentTag(); - } + } ostr << ".log"; - if( !this->CTest->OpenOutputFile("Temporary", ostr.str().c_str(), xofs) ) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot create log file: " - << ostr.str().c_str() << std::endl); + if (!this->CTest->OpenOutputFile("Temporary", ostr.str(), xofs)) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Cannot create log file: " << ostr.str() << std::endl); return false; - } + } return true; } diff --git a/Source/CTest/cmCTestGenericHandler.h b/Source/CTest/cmCTestGenericHandler.h index ba8febb61..e881252c9 100644 --- a/Source/CTest/cmCTestGenericHandler.h +++ b/Source/CTest/cmCTestGenericHandler.h @@ -1,45 +1,42 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestGenericHandler_h #define cmCTestGenericHandler_h +#include "cmConfigure.h" // IWYU pragma: keep + +#include <map> +#include <stddef.h> +#include <string> +#include <vector> -#include "cmObject.h" #include "cmCTest.h" -#include "cmSystemTools.h" //OutputOption +#include "cmSystemTools.h" -class cmMakefile; class cmCTestCommand; class cmGeneratedFileStream; +class cmMakefile; /** \class cmCTestGenericHandler * \brief A superclass of all CTest Handlers * */ -class cmCTestGenericHandler : public cmObject +class cmCTestGenericHandler { public: /** * If verbose then more informaiton is printed out */ void SetVerbose(bool val) - { this->HandlerVerbose = val ? - cmSystemTools::OUTPUT_MERGE : cmSystemTools::OUTPUT_NONE; } + { + this->HandlerVerbose = + val ? cmSystemTools::OUTPUT_MERGE : cmSystemTools::OUTPUT_NONE; + } /** * Populate internals from CTest custom scripts */ - virtual void PopulateCustomVectors(cmMakefile *) {} + virtual void PopulateCustomVectors(cmMakefile*) {} /** * Do the actual processing. Subclass has to override it. @@ -52,7 +49,10 @@ public: */ virtual int ProcessCommandLineArguments( const std::string& /*currentArg*/, size_t& /*idx*/, - const std::vector<std::string>& /*allArgs*/) { return 1; } + const std::vector<std::string>& /*allArgs*/) + { + return 1; + } /** * Initialize handler @@ -71,31 +71,33 @@ public: cmCTestGenericHandler(); virtual ~cmCTestGenericHandler(); - typedef std::map<cmStdString,cmStdString> t_StringToString; + typedef std::map<std::string, std::string> t_StringToString; + void SetPersistentOption(const std::string& op, const char* value); + void SetOption(const std::string& op, const char* value); + const char* GetOption(const std::string& op); - void SetPersistentOption(const char* op, const char* value); - void SetOption(const char* op, const char* value); - const char* GetOption(const char* op); - - void SetCommand(cmCTestCommand* command) - { - this->Command = command; - } + void SetCommand(cmCTestCommand* command) { this->Command = command; } void SetSubmitIndex(int idx) { this->SubmitIndex = idx; } int GetSubmitIndex() { return this->SubmitIndex; } void SetAppendXML(bool b) { this->AppendXML = b; } + void SetQuiet(bool b) { this->Quiet = b; } + bool GetQuiet() { return this->Quiet; } + void SetTestLoad(unsigned long load) { this->TestLoad = load; } + unsigned long GetTestLoad() const { return this->TestLoad; } protected: - bool StartResultingXML(cmCTest::Part part, - const char* name, cmGeneratedFileStream& xofs); + bool StartResultingXML(cmCTest::Part part, const char* name, + cmGeneratedFileStream& xofs); bool StartLogFile(const char* name, cmGeneratedFileStream& xofs); bool AppendXML; + bool Quiet; + unsigned long TestLoad; cmSystemTools::OutputOption HandlerVerbose; - cmCTest *CTest; + cmCTest* CTest; t_StringToString Options; t_StringToString PersistentOptions; @@ -104,4 +106,3 @@ protected: }; #endif - diff --git a/Source/CTest/cmCTestGlobalVC.cxx b/Source/CTest/cmCTestGlobalVC.cxx index 8c51102e5..25294b5ae 100644 --- a/Source/CTest/cmCTestGlobalVC.cxx +++ b/Source/CTest/cmCTestGlobalVC.cxx @@ -1,50 +1,37 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestGlobalVC.h" #include "cmCTest.h" #include "cmSystemTools.h" -#include "cmXMLSafe.h" +#include "cmXMLWriter.h" -#include <cmsys/RegularExpression.hxx> +#include <ostream> +#include <utility> -//---------------------------------------------------------------------------- -cmCTestGlobalVC::cmCTestGlobalVC(cmCTest* ct, std::ostream& log): - cmCTestVC(ct, log) +cmCTestGlobalVC::cmCTestGlobalVC(cmCTest* ct, std::ostream& log) + : cmCTestVC(ct, log) { this->PriorRev = this->Unknown; } -//---------------------------------------------------------------------------- cmCTestGlobalVC::~cmCTestGlobalVC() { } -//---------------------------------------------------------------------------- const char* cmCTestGlobalVC::LocalPath(std::string const& path) { return path.c_str(); } -//---------------------------------------------------------------------------- void cmCTestGlobalVC::DoRevision(Revision const& revision, std::vector<Change> const& changes) { // Ignore changes in the old revision. - if(revision.Rev == this->OldRevision) - { + if (revision.Rev == this->OldRevision) { this->PriorRev = revision; return; - } + } // Indicate we found a revision. cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush); @@ -54,27 +41,27 @@ void cmCTestGlobalVC::DoRevision(Revision const& revision, // Report this revision. Revision const& rev = this->Revisions.back(); + /* clang-format off */ this->Log << "Found revision " << rev.Rev << "\n" << " author = " << rev.Author << "\n" << " date = " << rev.Date << "\n"; + /* clang-format on */ // Update information about revisions of the changed files. - for(std::vector<Change>::const_iterator ci = changes.begin(); - ci != changes.end(); ++ci) - { - if(const char* local = this->LocalPath(ci->Path)) - { + for (std::vector<Change>::const_iterator ci = changes.begin(); + ci != changes.end(); ++ci) { + if (const char* local = this->LocalPath(ci->Path)) { std::string dir = cmSystemTools::GetFilenamePath(local); std::string name = cmSystemTools::GetFilenameName(local); File& file = this->Dirs[dir][name]; - file.PriorRev = file.Rev? file.Rev : &this->PriorRev; + file.PriorRev = file.Rev ? file.Rev : &this->PriorRev; file.Rev = &rev; - this->Log << " " << ci->Action << " " << local << " " << "\n"; - } + this->Log << " " << ci->Action << " " << local << " " + << "\n"; } + } } -//---------------------------------------------------------------------------- void cmCTestGlobalVC::DoModification(PathStatus status, std::string const& path) { @@ -84,59 +71,54 @@ void cmCTestGlobalVC::DoModification(PathStatus status, file.Status = status; // For local modifications the current rev is unknown and the // prior rev is the latest from svn. - if(!file.Rev && !file.PriorRev) - { + if (!file.Rev && !file.PriorRev) { file.PriorRev = &this->PriorRev; - } + } } -//---------------------------------------------------------------------------- -void cmCTestGlobalVC::WriteXMLDirectory(std::ostream& xml, +void cmCTestGlobalVC::WriteXMLDirectory(cmXMLWriter& xml, std::string const& path, Directory const& dir) { - const char* slash = path.empty()? "":"/"; - xml << "\t<Directory>\n" - << "\t\t<Name>" << cmXMLSafe(path) << "</Name>\n"; - for(Directory::const_iterator fi = dir.begin(); fi != dir.end(); ++fi) - { + const char* slash = path.empty() ? "" : "/"; + xml.StartElement("Directory"); + xml.Element("Name", path); + for (Directory::const_iterator fi = dir.begin(); fi != dir.end(); ++fi) { std::string full = path + slash + fi->first; this->WriteXMLEntry(xml, path, fi->first, full, fi->second); - } - xml << "\t</Directory>\n"; + } + xml.EndElement(); // Directory } -//---------------------------------------------------------------------------- -void cmCTestGlobalVC::WriteXMLGlobal(std::ostream& xml) +void cmCTestGlobalVC::WriteXMLGlobal(cmXMLWriter& xml) { - if(!this->NewRevision.empty()) - { - xml << "\t<Revision>" << this->NewRevision << "</Revision>\n"; - } - if(!this->OldRevision.empty() && this->OldRevision != this->NewRevision) - { - xml << "\t<PriorRevision>" << this->OldRevision << "</PriorRevision>\n"; - } + if (!this->NewRevision.empty()) { + xml.Element("Revision", this->NewRevision); + } + if (!this->OldRevision.empty() && this->OldRevision != this->NewRevision) { + xml.Element("PriorRevision", this->OldRevision); + } } -//---------------------------------------------------------------------------- -bool cmCTestGlobalVC::WriteXMLUpdates(std::ostream& xml) +bool cmCTestGlobalVC::WriteXMLUpdates(cmXMLWriter& xml) { + bool result = true; cmCTestLog(this->CTest, HANDLER_OUTPUT, " Gathering version information (one . per revision):\n" - " " << std::flush); - this->LoadRevisions(); + " " + << std::flush); + result = this->LoadRevisions() && result; cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); - this->LoadModifications(); + result = this->LoadModifications() && result; this->WriteXMLGlobal(xml); - for(std::map<cmStdString, Directory>::const_iterator - di = this->Dirs.begin(); di != this->Dirs.end(); ++di) - { + for (std::map<std::string, Directory>::const_iterator di = + this->Dirs.begin(); + di != this->Dirs.end(); ++di) { this->WriteXMLDirectory(xml, di->first, di->second); - } + } - return true; + return result; } diff --git a/Source/CTest/cmCTestGlobalVC.h b/Source/CTest/cmCTestGlobalVC.h index a648a5982..a5273d3dd 100644 --- a/Source/CTest/cmCTestGlobalVC.h +++ b/Source/CTest/cmCTestGlobalVC.h @@ -1,46 +1,53 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestGlobalVC_h #define cmCTestGlobalVC_h +#include "cmConfigure.h" + #include "cmCTestVC.h" +#include <iosfwd> +#include <list> +#include <map> +#include <string> +#include <vector> + +class cmCTest; +class cmXMLWriter; + /** \class cmCTestGlobalVC * \brief Base class for handling globally-versioned trees * */ -class cmCTestGlobalVC: public cmCTestVC +class cmCTestGlobalVC : public cmCTestVC { public: /** Construct with a CTest instance and update log stream. */ cmCTestGlobalVC(cmCTest* ctest, std::ostream& log); - virtual ~cmCTestGlobalVC(); + ~cmCTestGlobalVC() CM_OVERRIDE; protected: // Implement cmCTestVC internal API. - virtual bool WriteXMLUpdates(std::ostream& xml); + bool WriteXMLUpdates(cmXMLWriter& xml) CM_OVERRIDE; /** Represent a vcs-reported action for one path in a revision. */ struct Change { char Action; std::string Path; - Change(char a = '?'): Action(a) {} + Change(char a = '?') + : Action(a) + { + } }; // Update status for files in each directory. - class Directory: public std::map<cmStdString, File> {}; - std::map<cmStdString, Directory> Dirs; + class Directory : public std::map<std::string, File> + { + }; + std::map<std::string, Directory> Dirs; // Old and new repository revisions. std::string OldRevision; @@ -57,11 +64,11 @@ protected: virtual void DoRevision(Revision const& revision, std::vector<Change> const& changes); virtual void DoModification(PathStatus status, std::string const& path); - virtual void LoadModifications() = 0; - virtual void LoadRevisions() = 0; + virtual bool LoadModifications() = 0; + virtual bool LoadRevisions() = 0; - virtual void WriteXMLGlobal(std::ostream& xml); - void WriteXMLDirectory(std::ostream& xml, std::string const& path, + virtual void WriteXMLGlobal(cmXMLWriter& xml); + void WriteXMLDirectory(cmXMLWriter& xml, std::string const& path, Directory const& dir); }; diff --git a/Source/CTest/cmCTestHG.cxx b/Source/CTest/cmCTestHG.cxx index 86a7617f1..49f9a6500 100644 --- a/Source/CTest/cmCTestHG.cxx +++ b/Source/CTest/cmCTestHG.cxx @@ -1,105 +1,102 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestHG.h" #include "cmCTest.h" +#include "cmCTestVC.h" +#include "cmProcessTools.h" #include "cmSystemTools.h" #include "cmXMLParser.h" -#include <cmsys/RegularExpression.hxx> +#include "cmsys/RegularExpression.hxx" +#include <ostream> +#include <vector> -//---------------------------------------------------------------------------- -cmCTestHG::cmCTestHG(cmCTest* ct, std::ostream& log): - cmCTestGlobalVC(ct, log) +cmCTestHG::cmCTestHG(cmCTest* ct, std::ostream& log) + : cmCTestGlobalVC(ct, log) { this->PriorRev = this->Unknown; } -//---------------------------------------------------------------------------- cmCTestHG::~cmCTestHG() { } -//---------------------------------------------------------------------------- -class cmCTestHG::IdentifyParser: public cmCTestVC::LineParser +class cmCTestHG::IdentifyParser : public cmCTestVC::LineParser { public: - IdentifyParser(cmCTestHG* hg, const char* prefix, - std::string& rev): Rev(rev) - { + IdentifyParser(cmCTestHG* hg, const char* prefix, std::string& rev) + : Rev(rev) + { this->SetLog(&hg->Log, prefix); this->RegexIdentify.compile("^([0-9a-f]+)"); - } + } + private: std::string& Rev; cmsys::RegularExpression RegexIdentify; - bool ProcessLine() - { - if(this->RegexIdentify.find(this->Line)) - { + bool ProcessLine() CM_OVERRIDE + { + if (this->RegexIdentify.find(this->Line)) { this->Rev = this->RegexIdentify.match(1); return false; - } - return true; } + return true; + } }; -//---------------------------------------------------------------------------- -class cmCTestHG::StatusParser: public cmCTestVC::LineParser +class cmCTestHG::StatusParser : public cmCTestVC::LineParser { public: - StatusParser(cmCTestHG* hg, const char* prefix): HG(hg) - { + StatusParser(cmCTestHG* hg, const char* prefix) + : HG(hg) + { this->SetLog(&hg->Log, prefix); this->RegexStatus.compile("([MARC!?I]) (.*)"); - } + } private: cmCTestHG* HG; cmsys::RegularExpression RegexStatus; - bool ProcessLine() - { - if(this->RegexStatus.find(this->Line)) - { - this->DoPath(this->RegexStatus.match(1)[0], - this->RegexStatus.match(2)); - } - return true; + bool ProcessLine() CM_OVERRIDE + { + if (this->RegexStatus.find(this->Line)) { + this->DoPath(this->RegexStatus.match(1)[0], this->RegexStatus.match(2)); } + return true; + } void DoPath(char status, std::string const& path) - { - if(path.empty()) return; + { + if (path.empty()) { + return; + } // See "hg help status". Note that there is no 'conflict' status. - switch(status) - { - case 'M': case 'A': case '!': case 'R': + switch (status) { + case 'M': + case 'A': + case '!': + case 'R': this->HG->DoModification(PathModified, path); break; - case 'I': case '?': case 'C': case ' ': default: + case 'I': + case '?': + case 'C': + case ' ': + default: break; - } } + } }; -//---------------------------------------------------------------------------- std::string cmCTestHG::GetWorkingRevision() { // Run plumbing "hg identify" to get work tree revision. const char* hg = this->CommandLineTool.c_str(); - const char* hg_identify[] = {hg, "identify","-i", 0}; + const char* hg_identify[] = { hg, "identify", "-i", CM_NULLPTR }; std::string rev; IdentifyParser out(this, "rev-out> ", rev); OutputLogger err(this->Log, "rev-err> "); @@ -107,33 +104,32 @@ std::string cmCTestHG::GetWorkingRevision() return rev; } -//---------------------------------------------------------------------------- -void cmCTestHG::NoteOldRevision() +bool cmCTestHG::NoteOldRevision() { this->OldRevision = this->GetWorkingRevision(); cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: " - << this->OldRevision << "\n"); + << this->OldRevision << "\n"); this->PriorRev.Rev = this->OldRevision; + return true; } -//---------------------------------------------------------------------------- -void cmCTestHG::NoteNewRevision() +bool cmCTestHG::NoteNewRevision() { this->NewRevision = this->GetWorkingRevision(); cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: " - << this->NewRevision << "\n"); + << this->NewRevision << "\n"); + return true; } -//---------------------------------------------------------------------------- bool cmCTestHG::UpdateImpl() { // Use "hg pull" followed by "hg update" to update the working tree. { - const char* hg = this->CommandLineTool.c_str(); - const char* hg_pull[] = {hg, "pull","-v", 0}; - OutputLogger out(this->Log, "pull-out> "); - OutputLogger err(this->Log, "pull-err> "); - this->RunChild(&hg_pull[0], &out, &err); + const char* hg = this->CommandLineTool.c_str(); + const char* hg_pull[] = { hg, "pull", "-v", CM_NULLPTR }; + OutputLogger out(this->Log, "pull-out> "); + OutputLogger err(this->Log, "pull-err> "); + this->RunChild(&hg_pull[0], &out, &err); } // TODO: if(this->CTest->GetTestModel() == cmCTest::NIGHTLY) @@ -145,33 +141,34 @@ bool cmCTestHG::UpdateImpl() // Add user-specified update options. std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); - if(opts.empty()) - { + if (opts.empty()) { opts = this->CTest->GetCTestConfiguration("HGUpdateOptions"); - } - std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str()); - for(std::vector<cmStdString>::const_iterator ai = args.begin(); - ai != args.end(); ++ai) - { + } + std::vector<std::string> args = cmSystemTools::ParseArguments(opts.c_str()); + for (std::vector<std::string>::const_iterator ai = args.begin(); + ai != args.end(); ++ai) { hg_update.push_back(ai->c_str()); - } + } // Sentinel argument. - hg_update.push_back(0); + hg_update.push_back(CM_NULLPTR); OutputLogger out(this->Log, "update-out> "); OutputLogger err(this->Log, "update-err> "); return this->RunUpdateCommand(&hg_update[0], &out, &err); } -//---------------------------------------------------------------------------- -class cmCTestHG::LogParser: public cmCTestVC::OutputLogger, - private cmXMLParser +class cmCTestHG::LogParser : public cmCTestVC::OutputLogger, + private cmXMLParser { public: - LogParser(cmCTestHG* hg, const char* prefix): - OutputLogger(hg->Log, prefix), HG(hg) { this->InitializeParser(); } - ~LogParser() { this->CleanupParser(); } + LogParser(cmCTestHG* hg, const char* prefix) + : OutputLogger(hg->Log, prefix) + , HG(hg) + { + this->InitializeParser(); + } + ~LogParser() CM_OVERRIDE { this->CleanupParser(); } private: cmCTestHG* HG; @@ -182,119 +179,92 @@ private: Change CurChange; std::vector<char> CData; - virtual bool ProcessChunk(const char* data, int length) - { + bool ProcessChunk(const char* data, int length) CM_OVERRIDE + { this->OutputLogger::ProcessChunk(data, length); this->ParseChunk(data, length); return true; - } + } - virtual void StartElement(const char* name, const char** atts) - { + void StartElement(const std::string& name, const char** atts) CM_OVERRIDE + { this->CData.clear(); - if(strcmp(name, "logentry") == 0) - { + if (name == "logentry") { this->Rev = Revision(); - if(const char* rev = this->FindAttribute(atts, "revision")) - { + if (const char* rev = this->FindAttribute(atts, "revision")) { this->Rev.Rev = rev; - } - this->Changes.clear(); } + this->Changes.clear(); } + } - virtual void CharacterDataHandler(const char* data, int length) - { - this->CData.insert(this->CData.end(), data, data+length); - } + void CharacterDataHandler(const char* data, int length) CM_OVERRIDE + { + this->CData.insert(this->CData.end(), data, data + length); + } - virtual void EndElement(const char* name) - { - if(strcmp(name, "logentry") == 0) - { + void EndElement(const std::string& name) CM_OVERRIDE + { + if (name == "logentry") { this->HG->DoRevision(this->Rev, this->Changes); - } - else if(strcmp(name, "author") == 0 && !this->CData.empty()) - { + } else if (!this->CData.empty() && name == "author") { this->Rev.Author.assign(&this->CData[0], this->CData.size()); - } - else if ( strcmp(name, "email") == 0 && !this->CData.empty()) - { + } else if (!this->CData.empty() && name == "email") { this->Rev.EMail.assign(&this->CData[0], this->CData.size()); - } - else if(strcmp(name, "date") == 0 && !this->CData.empty()) - { + } else if (!this->CData.empty() && name == "date") { this->Rev.Date.assign(&this->CData[0], this->CData.size()); - } - else if(strcmp(name, "msg") == 0 && !this->CData.empty()) - { + } else if (!this->CData.empty() && name == "msg") { this->Rev.Log.assign(&this->CData[0], this->CData.size()); - } - else if(strcmp(name, "files") == 0 && !this->CData.empty()) - { + } else if (!this->CData.empty() && name == "files") { std::vector<std::string> paths = this->SplitCData(); - for(unsigned int i = 0; i < paths.size(); ++i) - { + for (unsigned int i = 0; i < paths.size(); ++i) { // Updated by default, will be modified using file_adds and // file_dels. this->CurChange = Change('U'); this->CurChange.Path = paths[i]; this->Changes.push_back(this->CurChange); - } } - else if(strcmp(name, "file_adds") == 0 && !this->CData.empty()) - { + } else if (!this->CData.empty() && name == "file_adds") { std::string added_paths(this->CData.begin(), this->CData.end()); - for(unsigned int i = 0; i < this->Changes.size(); ++i) - { - if(added_paths.find(this->Changes[i].Path) != std::string::npos) - { + for (unsigned int i = 0; i < this->Changes.size(); ++i) { + if (added_paths.find(this->Changes[i].Path) != std::string::npos) { this->Changes[i].Action = 'A'; - } } } - else if(strcmp(name, "file_dels") == 0 && !this->CData.empty()) - { + } else if (!this->CData.empty() && name == "file_dels") { std::string added_paths(this->CData.begin(), this->CData.end()); - for(unsigned int i = 0; i < this->Changes.size(); ++i) - { - if(added_paths.find(this->Changes[i].Path) != std::string::npos) - { + for (unsigned int i = 0; i < this->Changes.size(); ++i) { + if (added_paths.find(this->Changes[i].Path) != std::string::npos) { this->Changes[i].Action = 'D'; - } } } - this->CData.clear(); } + this->CData.clear(); + } std::vector<std::string> SplitCData() - { + { std::vector<std::string> output; std::string currPath; - for(unsigned int i=0; i < this->CData.size(); ++i) - { - if(this->CData[i] != ' ') - { + for (unsigned int i = 0; i < this->CData.size(); ++i) { + if (this->CData[i] != ' ') { currPath += this->CData[i]; - } - else - { + } else { output.push_back(currPath); currPath = ""; - } } + } output.push_back(currPath); return output; - } + } - virtual void ReportError(int, int, const char* msg) - { + void ReportError(int /*line*/, int /*column*/, const char* msg) CM_OVERRIDE + { this->HG->Log << "Error parsing hg log xml: " << msg << "\n"; - } + } }; -//---------------------------------------------------------------------------- -void cmCTestHG::LoadRevisions() +bool cmCTestHG::LoadRevisions() { // Use 'hg log' to get revisions in a xml format. // @@ -304,19 +274,20 @@ void cmCTestHG::LoadRevisions() // proper XML escapes. std::string range = this->OldRevision + ":" + this->NewRevision; const char* hg = this->CommandLineTool.c_str(); - const char* hgXMLTemplate = - "<logentry\n" - " revision=\"{node|short}\">\n" - " <author>{author|person}</author>\n" - " <email>{author|email}</email>\n" - " <date>{date|isodate}</date>\n" - " <msg>{desc}</msg>\n" - " <files>{files}</files>\n" - " <file_adds>{file_adds}</file_adds>\n" - " <file_dels>{file_dels}</file_dels>\n" - "</logentry>\n"; - const char* hg_log[] = {hg, "log","--removed", "-r", range.c_str(), - "--template", hgXMLTemplate, 0}; + const char* hgXMLTemplate = "<logentry\n" + " revision=\"{node|short}\">\n" + " <author>{author|person}</author>\n" + " <email>{author|email}</email>\n" + " <date>{date|isodate}</date>\n" + " <msg>{desc}</msg>\n" + " <files>{files}</files>\n" + " <file_adds>{file_adds}</file_adds>\n" + " <file_dels>{file_dels}</file_dels>\n" + "</logentry>\n"; + const char* hg_log[] = { + hg, "log", "--removed", "-r", range.c_str(), + "--template", hgXMLTemplate, CM_NULLPTR + }; LogParser out(this, "log-out> "); out.Process("<?xml version=\"1.0\"?>\n" @@ -324,15 +295,16 @@ void cmCTestHG::LoadRevisions() OutputLogger err(this->Log, "log-err> "); this->RunChild(hg_log, &out, &err); out.Process("</log>\n"); + return true; } -//---------------------------------------------------------------------------- -void cmCTestHG::LoadModifications() +bool cmCTestHG::LoadModifications() { // Use 'hg status' to get modified files. const char* hg = this->CommandLineTool.c_str(); - const char* hg_status[] = {hg, "status", 0}; + const char* hg_status[] = { hg, "status", CM_NULLPTR }; StatusParser out(this, "status-out> "); OutputLogger err(this->Log, "status-err> "); this->RunChild(hg_status, &out, &err); + return true; } diff --git a/Source/CTest/cmCTestHG.h b/Source/CTest/cmCTestHG.h index 1eaf9337e..ec9eaffe8 100644 --- a/Source/CTest/cmCTestHG.h +++ b/Source/CTest/cmCTestHG.h @@ -1,47 +1,46 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestHG_h #define cmCTestHG_h +#include "cmConfigure.h" + #include "cmCTestGlobalVC.h" +#include <iosfwd> +#include <string> + +class cmCTest; + /** \class cmCTestHG * \brief Interaction with Mercurial command-line tool * */ -class cmCTestHG: public cmCTestGlobalVC +class cmCTestHG : public cmCTestGlobalVC { public: /** Construct with a CTest instance and update log stream. */ cmCTestHG(cmCTest* ctest, std::ostream& log); - virtual ~cmCTestHG(); + ~cmCTestHG() CM_OVERRIDE; private: std::string GetWorkingRevision(); - virtual void NoteOldRevision(); - virtual void NoteNewRevision(); - virtual bool UpdateImpl(); + bool NoteOldRevision() CM_OVERRIDE; + bool NoteNewRevision() CM_OVERRIDE; + bool UpdateImpl() CM_OVERRIDE; - void LoadRevisions(); - void LoadModifications(); + bool LoadRevisions() CM_OVERRIDE; + bool LoadModifications() CM_OVERRIDE; // Parsing helper classes. class IdentifyParser; - class StatusParser; class LogParser; + class StatusParser; + friend class IdentifyParser; - friend class StatusParser; friend class LogParser; + friend class StatusParser; }; #endif diff --git a/Source/CTest/cmCTestHandlerCommand.cxx b/Source/CTest/cmCTestHandlerCommand.cxx index 2e2feb047..c99e450d0 100644 --- a/Source/CTest/cmCTestHandlerCommand.cxx +++ b/Source/CTest/cmCTestHandlerCommand.cxx @@ -1,63 +1,139 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestHandlerCommand.h" #include "cmCTest.h" #include "cmCTestGenericHandler.h" +#include "cmMakefile.h" +#include "cmSystemTools.h" +#include "cmWorkingDirectory.h" +#include "cmake.h" + +#include <sstream> +#include <stdlib.h> + +class cmExecutionStatus; cmCTestHandlerCommand::cmCTestHandlerCommand() { const size_t INIT_SIZE = 100; size_t cc; this->Arguments.reserve(INIT_SIZE); - for ( cc = 0; cc < INIT_SIZE; ++ cc ) - { - this->Arguments.push_back(0); - } + for (cc = 0; cc < INIT_SIZE; ++cc) { + this->Arguments.push_back(CM_NULLPTR); + } this->Arguments[ct_RETURN_VALUE] = "RETURN_VALUE"; + this->Arguments[ct_CAPTURE_CMAKE_ERROR] = "CAPTURE_CMAKE_ERROR"; this->Arguments[ct_SOURCE] = "SOURCE"; this->Arguments[ct_BUILD] = "BUILD"; this->Arguments[ct_SUBMIT_INDEX] = "SUBMIT_INDEX"; this->Last = ct_LAST; this->AppendXML = false; + this->Quiet = false; } -bool cmCTestHandlerCommand -::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &) +namespace { +// class to save and restore the error state for ctest_* commands +// if a ctest_* command has a CAPTURE_CMAKE_ERROR then put the error +// state into there and restore the system wide error to what +// it was before the command ran +class SaveRestoreErrorState { +public: + SaveRestoreErrorState() + { + this->InitialErrorState = cmSystemTools::GetErrorOccuredFlag(); + cmSystemTools::ResetErrorOccuredFlag(); // rest the error state + this->CaptureCMakeErrorValue = false; + } + // if the function has a CAPTURE_CMAKE_ERROR then we should restore + // the error state to what it was before the function was run + // if not then let the error state be what it is + void CaptureCMakeError() { this->CaptureCMakeErrorValue = true; } + ~SaveRestoreErrorState() + { + // if we are not saving the return value then make sure + // if it was in error it goes back to being in error + // otherwise leave it be what it is + if (!this->CaptureCMakeErrorValue) { + if (this->InitialErrorState) { + cmSystemTools::SetErrorOccured(); + } + return; + } + // if we have saved the error in a return variable + // then put things back exactly like they were + bool currentState = cmSystemTools::GetErrorOccuredFlag(); + // if the state changed during this command we need + // to handle it, if not then nothing needs to be done + if (currentState != this->InitialErrorState) { + // restore the initial error state + if (this->InitialErrorState) { + cmSystemTools::SetErrorOccured(); + } else { + cmSystemTools::ResetErrorOccuredFlag(); + } + } + } + +private: + bool InitialErrorState; + bool CaptureCMakeErrorValue; +}; +} + +bool cmCTestHandlerCommand::InitialPass(std::vector<std::string> const& args, + cmExecutionStatus& /*unused*/) +{ + // save error state and restore it if needed + SaveRestoreErrorState errorState; // Allocate space for argument values. this->Values.clear(); - this->Values.resize(this->Last, 0); + this->Values.resize(this->Last, CM_NULLPTR); // Process input arguments. this->ArgumentDoing = ArgumentDoingNone; - for(unsigned int i=0; i < args.size(); ++i) - { + // look at all arguments and do not short circuit on the first + // bad one so that CAPTURE_CMAKE_ERROR can override setting the + // global error state + bool foundBadArgument = false; + for (unsigned int i = 0; i < args.size(); ++i) { // Check this argument. - if(!this->CheckArgumentKeyword(args[i]) && - !this->CheckArgumentValue(args[i])) - { - cmOStringStream e; + if (!this->CheckArgumentKeyword(args[i]) && + !this->CheckArgumentValue(args[i])) { + std::ostringstream e; e << "called with unknown argument \"" << args[i] << "\"."; - this->SetError(e.str().c_str()); - return false; - } - - // Quit if an argument is invalid. - if(this->ArgumentDoing == ArgumentDoingError) - { - return false; + this->SetError(e.str()); + foundBadArgument = true; + } + // note bad argument + if (this->ArgumentDoing == ArgumentDoingError) { + foundBadArgument = true; + } + } + bool capureCMakeError = (this->Values[ct_CAPTURE_CMAKE_ERROR] && + *this->Values[ct_CAPTURE_CMAKE_ERROR]); + // now that arguments are parsed check to see if there is a + // CAPTURE_CMAKE_ERROR specified let the errorState object know. + if (capureCMakeError) { + errorState.CaptureCMakeError(); + } + // if we found a bad argument then exit before running command + if (foundBadArgument) { + // store the cmake error + if (capureCMakeError) { + this->Makefile->AddDefinition(this->Values[ct_CAPTURE_CMAKE_ERROR], + "-1"); + std::string const err = this->GetName() + " " + this->GetError(); + if (!cmSystemTools::FindLastString(err.c_str(), "unknown error.")) { + cmCTestLog(this->CTest, ERROR_MESSAGE, err << " error from command\n"); } + // return success because failure is recorded in CAPTURE_CMAKE_ERROR + return true; } + // return failure because of bad argument + return false; + } // Set the config type of this ctest to the current value of the // CTEST_CONFIGURATION_TYPE script variable if it is defined. @@ -65,134 +141,156 @@ bool cmCTestHandlerCommand // line. const char* ctestConfigType = this->Makefile->GetDefinition("CTEST_CONFIGURATION_TYPE"); - if (ctestConfigType) - { + if (ctestConfigType) { this->CTest->SetConfigType(ctestConfigType); - } + } - if ( this->Values[ct_BUILD] ) - { - this->CTest->SetCTestConfiguration("BuildDirectory", - cmSystemTools::CollapseFullPath( - this->Values[ct_BUILD]).c_str()); - } - else - { + if (this->Values[ct_BUILD]) { + this->CTest->SetCTestConfiguration( + "BuildDirectory", + cmSystemTools::CollapseFullPath(this->Values[ct_BUILD]).c_str(), + this->Quiet); + } else { const char* bdir = this->Makefile->GetSafeDefinition("CTEST_BINARY_DIRECTORY"); - if(bdir) - { - this-> - CTest->SetCTestConfiguration("BuildDirectory", - cmSystemTools::CollapseFullPath(bdir).c_str()); - } - else - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "CTEST_BINARY_DIRECTORY not set" << std::endl;); - } - } - if ( this->Values[ct_SOURCE] ) - { - cmCTestLog(this->CTest, DEBUG, - "Set source directory to: " << this->Values[ct_SOURCE] << std::endl); - this->CTest->SetCTestConfiguration("SourceDirectory", - cmSystemTools::CollapseFullPath( - this->Values[ct_SOURCE]).c_str()); + if (bdir) { + this->CTest->SetCTestConfiguration( + "BuildDirectory", cmSystemTools::CollapseFullPath(bdir).c_str(), + this->Quiet); + } else { + cmCTestLog(this->CTest, ERROR_MESSAGE, "CTEST_BINARY_DIRECTORY not set" + << std::endl;); } - else - { - this->CTest->SetCTestConfiguration("SourceDirectory", + } + if (this->Values[ct_SOURCE]) { + cmCTestLog(this->CTest, DEBUG, "Set source directory to: " + << this->Values[ct_SOURCE] << std::endl); + this->CTest->SetCTestConfiguration( + "SourceDirectory", + cmSystemTools::CollapseFullPath(this->Values[ct_SOURCE]).c_str(), + this->Quiet); + } else { + this->CTest->SetCTestConfiguration( + "SourceDirectory", cmSystemTools::CollapseFullPath( - this->Makefile->GetSafeDefinition("CTEST_SOURCE_DIRECTORY")).c_str()); - } + this->Makefile->GetSafeDefinition("CTEST_SOURCE_DIRECTORY")) + .c_str(), + this->Quiet); + } + + if (const char* changeId = + this->Makefile->GetDefinition("CTEST_CHANGE_ID")) { + this->CTest->SetCTestConfiguration("ChangeId", changeId, this->Quiet); + } cmCTestLog(this->CTest, DEBUG, "Initialize handler" << std::endl;); cmCTestGenericHandler* handler = this->InitializeHandler(); - if ( !handler ) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot instantiate test handler " << this->GetName() - << std::endl); - return false; + if (!handler) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot instantiate test handler " + << this->GetName() << std::endl); + if (capureCMakeError) { + this->Makefile->AddDefinition(this->Values[ct_CAPTURE_CMAKE_ERROR], + "-1"); + const char* err = this->GetError(); + if (err && !cmSystemTools::FindLastString(err, "unknown error.")) { + cmCTestLog(this->CTest, ERROR_MESSAGE, err << " error from command\n"); + } + return true; } + return false; + } handler->SetAppendXML(this->AppendXML); handler->PopulateCustomVectors(this->Makefile); - if ( this->Values[ct_SUBMIT_INDEX] ) - { - if(!this->CTest->GetDropSiteCDash() && this->CTest->GetDartVersion() <= 1) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, + if (this->Values[ct_SUBMIT_INDEX]) { + if (!this->CTest->GetDropSiteCDash() && + this->CTest->GetDartVersion() <= 1) { + cmCTestLog( + this->CTest, ERROR_MESSAGE, "Dart before version 2.0 does not support collecting submissions." - << std::endl - << "Please upgrade the server to Dart 2 or higher, or do not use " - "SUBMIT_INDEX." << std::endl); - } - else - { + << std::endl + << "Please upgrade the server to Dart 2 or higher, or do not use " + "SUBMIT_INDEX." + << std::endl); + } else { handler->SetSubmitIndex(atoi(this->Values[ct_SUBMIT_INDEX])); - } } - std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory(); - cmSystemTools::ChangeDirectory( - this->CTest->GetCTestConfiguration("BuildDirectory").c_str()); + } + cmWorkingDirectory workdir( + this->CTest->GetCTestConfiguration("BuildDirectory")); int res = handler->ProcessHandler(); - if ( this->Values[ct_RETURN_VALUE] && *this->Values[ct_RETURN_VALUE]) - { - cmOStringStream str; + if (this->Values[ct_RETURN_VALUE] && *this->Values[ct_RETURN_VALUE]) { + std::ostringstream str; str << res; - this->Makefile->AddDefinition( - this->Values[ct_RETURN_VALUE], str.str().c_str()); + this->Makefile->AddDefinition(this->Values[ct_RETURN_VALUE], + str.str().c_str()); + } + this->ProcessAdditionalValues(handler); + // log the error message if there was an error + if (capureCMakeError) { + const char* returnString = "0"; + if (cmSystemTools::GetErrorOccuredFlag()) { + returnString = "-1"; + const char* err = this->GetError(); + // print out the error if it is not "unknown error" which means + // there was no message + if (err && !cmSystemTools::FindLastString(err, "unknown error.")) { + cmCTestLog(this->CTest, ERROR_MESSAGE, err); + } } - cmSystemTools::ChangeDirectory(current_dir.c_str()); + // store the captured cmake error state 0 or -1 + this->Makefile->AddDefinition(this->Values[ct_CAPTURE_CMAKE_ERROR], + returnString); + } return true; } -//---------------------------------------------------------------------------- +void cmCTestHandlerCommand::ProcessAdditionalValues(cmCTestGenericHandler*) +{ +} + bool cmCTestHandlerCommand::CheckArgumentKeyword(std::string const& arg) { // Look for non-value arguments common to all commands. - if(arg == "APPEND") - { + if (arg == "APPEND") { this->ArgumentDoing = ArgumentDoingNone; this->AppendXML = true; return true; - } + } + if (arg == "QUIET") { + this->ArgumentDoing = ArgumentDoingNone; + this->Quiet = true; + return true; + } // Check for a keyword in our argument/value table. - for(unsigned int k=0; k < this->Arguments.size(); ++k) - { - if(this->Arguments[k] && arg == this->Arguments[k]) - { + for (unsigned int k = 0; k < this->Arguments.size(); ++k) { + if (this->Arguments[k] && arg == this->Arguments[k]) { this->ArgumentDoing = ArgumentDoingKeyword; this->ArgumentIndex = k; return true; - } } + } return false; } -//---------------------------------------------------------------------------- bool cmCTestHandlerCommand::CheckArgumentValue(std::string const& arg) { - if(this->ArgumentDoing == ArgumentDoingKeyword) - { + if (this->ArgumentDoing == ArgumentDoingKeyword) { this->ArgumentDoing = ArgumentDoingNone; unsigned int k = this->ArgumentIndex; - if(this->Values[k]) - { - cmOStringStream e; + if (this->Values[k]) { + std::ostringstream e; e << "Called with more than one value for " << this->Arguments[k]; this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str()); this->ArgumentDoing = ArgumentDoingError; return true; - } + } this->Values[k] = arg.c_str(); - cmCTestLog(this->CTest, DEBUG, "Set " << this->Arguments[k] - << " to " << arg << "\n"); + cmCTestLog(this->CTest, DEBUG, "Set " << this->Arguments[k] << " to " + << arg << "\n"); return true; - } + } return false; } diff --git a/Source/CTest/cmCTestHandlerCommand.h b/Source/CTest/cmCTestHandlerCommand.h index 8ef2b8028..0ea061225 100644 --- a/Source/CTest/cmCTestHandlerCommand.h +++ b/Source/CTest/cmCTestHandlerCommand.h @@ -1,20 +1,18 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestHandlerCommand_h #define cmCTestHandlerCommand_h +#include "cmConfigure.h" + #include "cmCTestCommand.h" +#include <stddef.h> +#include <string> +#include <vector> + class cmCTestGenericHandler; +class cmExecutionStatus; /** \class cmCTestHandler * \brief Run a ctest script @@ -27,27 +25,33 @@ public: cmCTestHandlerCommand(); /** + * The name of the command as specified in CMakeList.txt. + */ + virtual std::string GetName() const = 0; + + /** * This is called when the command is first encountered in * the CMakeLists.txt file. */ - virtual bool InitialPass(std::vector<std::string> const& args, - cmExecutionStatus &status); - - cmTypeMacro(cmCTestHandlerCommand, cmCTestCommand); + bool InitialPass(std::vector<std::string> const& args, + cmExecutionStatus& status) CM_OVERRIDE; enum - { + { ct_NONE, ct_RETURN_VALUE, + ct_CAPTURE_CMAKE_ERROR, ct_BUILD, ct_SOURCE, ct_SUBMIT_INDEX, ct_LAST - }; + }; protected: virtual cmCTestGenericHandler* InitializeHandler() = 0; + virtual void ProcessAdditionalValues(cmCTestGenericHandler* handler); + // Command argument handling. virtual bool CheckArgumentKeyword(std::string const& arg); virtual bool CheckArgumentValue(std::string const& arg); @@ -62,6 +66,7 @@ protected: unsigned int ArgumentIndex; bool AppendXML; + bool Quiet; std::string ReturnVariable; std::vector<const char*> Arguments; @@ -69,9 +74,9 @@ protected: size_t Last; }; -#define CTEST_COMMAND_APPEND_OPTION_DOCS \ - "The APPEND option marks results for append to those previously " \ - "submitted to a dashboard server since the last ctest_start. " \ +#define CTEST_COMMAND_APPEND_OPTION_DOCS \ + "The APPEND option marks results for append to those previously " \ + "submitted to a dashboard server since the last ctest_start. " \ "Append semantics are defined by the dashboard server in use." #endif diff --git a/Source/CTest/cmCTestLaunch.cxx b/Source/CTest/cmCTestLaunch.cxx index 9831d0200..5b213518a 100644 --- a/Source/CTest/cmCTestLaunch.cxx +++ b/Source/CTest/cmCTestLaunch.cxx @@ -1,37 +1,43 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmCTestLaunch.h" - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. +#include "cmConfigure.h" - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ -#include "cmCTestLaunch.h" +#include "cmsys/FStream.hxx" +#include "cmsys/Process.h" +#include "cmsys/RegularExpression.hxx" +#include <iostream> +#include <stdlib.h> +#include <string.h> +#include "cmCryptoHash.h" #include "cmGeneratedFileStream.h" +#include "cmGlobalGenerator.h" +#include "cmMakefile.h" +#include "cmProcessOutput.h" +#include "cmStateSnapshot.h" #include "cmSystemTools.h" -#include "cmXMLSafe.h" +#include "cmXMLWriter.h" +#include "cm_auto_ptr.hxx" #include "cmake.h" -#include <cmsys/MD5.h> -#include <cmsys/Process.h> -#include <cmsys/RegularExpression.hxx> +#ifdef _WIN32 +#include <fcntl.h> // for _O_BINARY +#include <io.h> // for _setmode +#include <stdio.h> // for std{out,err} and fileno +#endif -//---------------------------------------------------------------------------- cmCTestLaunch::cmCTestLaunch(int argc, const char* const* argv) { this->Passthru = true; - this->Process = 0; + this->Process = CM_NULLPTR; this->ExitCode = 1; this->CWD = cmSystemTools::GetCurrentWorkingDirectory(); - if(!this->ParseArguments(argc, argv)) - { + if (!this->ParseArguments(argc, argv)) { return; - } + } this->ComputeFileNames(); @@ -41,148 +47,117 @@ cmCTestLaunch::cmCTestLaunch(int argc, const char* const* argv) this->Process = cmsysProcess_New(); } -//---------------------------------------------------------------------------- cmCTestLaunch::~cmCTestLaunch() { cmsysProcess_Delete(this->Process); - if(!this->Passthru) - { - cmSystemTools::RemoveFile(this->LogOut.c_str()); - cmSystemTools::RemoveFile(this->LogErr.c_str()); - } + if (!this->Passthru) { + cmSystemTools::RemoveFile(this->LogOut); + cmSystemTools::RemoveFile(this->LogErr); + } } -//---------------------------------------------------------------------------- bool cmCTestLaunch::ParseArguments(int argc, const char* const* argv) { // Launcher options occur first and are separated from the real // command line by a '--' option. - enum Doing { DoingNone, - DoingOutput, - DoingSource, - DoingLanguage, - DoingTargetName, - DoingTargetType, - DoingBuildDir, - DoingCount }; + enum Doing + { + DoingNone, + DoingOutput, + DoingSource, + DoingLanguage, + DoingTargetName, + DoingTargetType, + DoingBuildDir, + DoingCount, + DoingFilterPrefix + }; Doing doing = DoingNone; int arg0 = 0; - for(int i=1; !arg0 && i < argc; ++i) - { + for (int i = 1; !arg0 && i < argc; ++i) { const char* arg = argv[i]; - if(strcmp(arg, "--") == 0) - { - arg0 = i+1; - } - else if(strcmp(arg, "--output") == 0) - { + if (strcmp(arg, "--") == 0) { + arg0 = i + 1; + } else if (strcmp(arg, "--output") == 0) { doing = DoingOutput; - } - else if(strcmp(arg, "--source") == 0) - { + } else if (strcmp(arg, "--source") == 0) { doing = DoingSource; - } - else if(strcmp(arg, "--language") == 0) - { + } else if (strcmp(arg, "--language") == 0) { doing = DoingLanguage; - } - else if(strcmp(arg, "--target-name") == 0) - { + } else if (strcmp(arg, "--target-name") == 0) { doing = DoingTargetName; - } - else if(strcmp(arg, "--target-type") == 0) - { + } else if (strcmp(arg, "--target-type") == 0) { doing = DoingTargetType; - } - else if(strcmp(arg, "--build-dir") == 0) - { + } else if (strcmp(arg, "--build-dir") == 0) { doing = DoingBuildDir; - } - else if(doing == DoingOutput) - { + } else if (strcmp(arg, "--filter-prefix") == 0) { + doing = DoingFilterPrefix; + } else if (doing == DoingOutput) { this->OptionOutput = arg; doing = DoingNone; - } - else if(doing == DoingSource) - { + } else if (doing == DoingSource) { this->OptionSource = arg; doing = DoingNone; - } - else if(doing == DoingLanguage) - { + } else if (doing == DoingLanguage) { this->OptionLanguage = arg; - if(this->OptionLanguage == "CXX") - { + if (this->OptionLanguage == "CXX") { this->OptionLanguage = "C++"; - } - doing = DoingNone; } - else if(doing == DoingTargetName) - { + doing = DoingNone; + } else if (doing == DoingTargetName) { this->OptionTargetName = arg; doing = DoingNone; - } - else if(doing == DoingTargetType) - { + } else if (doing == DoingTargetType) { this->OptionTargetType = arg; doing = DoingNone; - } - else if(doing == DoingBuildDir) - { + } else if (doing == DoingBuildDir) { this->OptionBuildDir = arg; doing = DoingNone; - } + } else if (doing == DoingFilterPrefix) { + this->OptionFilterPrefix = arg; + doing = DoingNone; } + } // Extract the real command line. - if(arg0) - { + if (arg0) { this->RealArgC = argc - arg0; this->RealArgV = argv + arg0; - for(int i=0; i < this->RealArgC; ++i) - { + for (int i = 0; i < this->RealArgC; ++i) { this->HandleRealArg(this->RealArgV[i]); - } - return true; - } - else - { - this->RealArgC = 0; - this->RealArgV = 0; - std::cerr << "No launch/command separator ('--') found!\n"; - return false; } + return true; + } + this->RealArgC = 0; + this->RealArgV = CM_NULLPTR; + std::cerr << "No launch/command separator ('--') found!\n"; + return false; } -//---------------------------------------------------------------------------- void cmCTestLaunch::HandleRealArg(const char* arg) { #ifdef _WIN32 // Expand response file arguments. - if(arg[0] == '@' && cmSystemTools::FileExists(arg+1)) - { - std::ifstream fin(arg+1); + if (arg[0] == '@' && cmSystemTools::FileExists(arg + 1)) { + cmsys::ifstream fin(arg + 1); std::string line; - while(cmSystemTools::GetLineFromStream(fin, line)) - { + while (cmSystemTools::GetLineFromStream(fin, line)) { cmSystemTools::ParseWindowsCommandLine(line.c_str(), this->RealArgs); - } - return; } + return; + } #endif this->RealArgs.push_back(arg); } -//---------------------------------------------------------------------------- void cmCTestLaunch::ComputeFileNames() { // We just passthru the behavior of the real command unless the // CTEST_LAUNCH_LOGS environment variable is set. const char* d = getenv("CTEST_LAUNCH_LOGS"); - if(!(d && *d)) - { + if (!(d && *d)) { return; - } + } this->Passthru = false; // The environment variable specifies the directory into which we @@ -193,18 +168,14 @@ void cmCTestLaunch::ComputeFileNames() // We hash the input command working dir and command line to obtain // a repeatable and (probably) unique name for log files. - char hash[32]; - cmsysMD5* md5 = cmsysMD5_New(); - cmsysMD5_Initialize(md5); - cmsysMD5_Append(md5, (unsigned char const*)(this->CWD.c_str()), -1); - for(std::vector<std::string>::const_iterator ai = this->RealArgs.begin(); - ai != this->RealArgs.end(); ++ai) - { - cmsysMD5_Append(md5, (unsigned char const*)ai->c_str(), -1); - } - cmsysMD5_FinalizeHex(md5, hash); - cmsysMD5_Delete(md5); - this->LogHash.assign(hash, 32); + cmCryptoHash md5(cmCryptoHash::AlgoMD5); + md5.Initialize(); + md5.Append(this->CWD); + for (std::vector<std::string>::const_iterator ai = this->RealArgs.begin(); + ai != this->RealArgs.end(); ++ai) { + md5.Append(*ai); + } + this->LogHash = md5.FinalizeHex(); // We store stdout and stderr in temporary log files. this->LogOut = this->LogDir; @@ -217,82 +188,88 @@ void cmCTestLaunch::ComputeFileNames() this->LogErr += "-err.txt"; } -//---------------------------------------------------------------------------- void cmCTestLaunch::RunChild() { // Ignore noopt make rules - if(this->RealArgs.empty() || this->RealArgs[0] == ":") - { + if (this->RealArgs.empty() || this->RealArgs[0] == ":") { this->ExitCode = 0; return; - } + } // Prepare to run the real command. cmsysProcess* cp = this->Process; cmsysProcess_SetCommand(cp, this->RealArgV); - std::ofstream fout; - std::ofstream ferr; - if(this->Passthru) - { + cmsys::ofstream fout; + cmsys::ofstream ferr; + if (this->Passthru) { // In passthru mode we just share the output pipes. cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDOUT, 1); cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDERR, 1); - } - else - { + } else { // In full mode we record the child output pipes to log files. - fout.open(this->LogOut.c_str(), - std::ios::out | std::ios::binary); - ferr.open(this->LogErr.c_str(), - std::ios::out | std::ios::binary); - } + fout.open(this->LogOut.c_str(), std::ios::out | std::ios::binary); + ferr.open(this->LogErr.c_str(), std::ios::out | std::ios::binary); + } + +#ifdef _WIN32 + // Do this so that newline transformation is not done when writing to cout + // and cerr below. + _setmode(fileno(stdout), _O_BINARY); + _setmode(fileno(stderr), _O_BINARY); +#endif // Run the real command. cmsysProcess_Execute(cp); // Record child stdout and stderr if necessary. - if(!this->Passthru) - { - char* data = 0; + if (!this->Passthru) { + char* data = CM_NULLPTR; int length = 0; - while(int p = cmsysProcess_WaitForData(cp, &data, &length, 0)) - { - if(p == cmsysProcess_Pipe_STDOUT) - { - fout.write(data, length); - std::cout.write(data, length); + cmProcessOutput processOutput; + std::string strdata; + while (int p = cmsysProcess_WaitForData(cp, &data, &length, CM_NULLPTR)) { + if (p == cmsysProcess_Pipe_STDOUT) { + processOutput.DecodeText(data, length, strdata, 1); + fout.write(strdata.c_str(), strdata.size()); + std::cout.write(strdata.c_str(), strdata.size()); this->HaveOut = true; - } - else if(p == cmsysProcess_Pipe_STDERR) - { - ferr.write(data, length); - std::cerr.write(data, length); + } else if (p == cmsysProcess_Pipe_STDERR) { + processOutput.DecodeText(data, length, strdata, 2); + ferr.write(strdata.c_str(), strdata.size()); + std::cerr.write(strdata.c_str(), strdata.size()); this->HaveErr = true; - } } } + processOutput.DecodeText(std::string(), strdata, 1); + if (!strdata.empty()) { + fout.write(strdata.c_str(), strdata.size()); + std::cout.write(strdata.c_str(), strdata.size()); + } + processOutput.DecodeText(std::string(), strdata, 2); + if (!strdata.empty()) { + ferr.write(strdata.c_str(), strdata.size()); + std::cerr.write(strdata.c_str(), strdata.size()); + } + } // Wait for the real command to finish. - cmsysProcess_WaitForExit(cp, 0); + cmsysProcess_WaitForExit(cp, CM_NULLPTR); this->ExitCode = cmsysProcess_GetExitValue(cp); } -//---------------------------------------------------------------------------- int cmCTestLaunch::Run() { - if(!this->Process) - { + if (!this->Process) { std::cerr << "Could not allocate cmsysProcess instance!\n"; return -1; - } + } this->RunChild(); - if(this->CheckResults()) - { + if (this->CheckResults()) { return this->ExitCode; - } + } this->LoadConfig(); this->WriteXML(); @@ -300,13 +277,11 @@ int cmCTestLaunch::Run() return this->ExitCode; } -//---------------------------------------------------------------------------- void cmCTestLaunch::LoadLabels() { - if(this->OptionBuildDir.empty() || this->OptionTargetName.empty()) - { + if (this->OptionBuildDir.empty() || this->OptionTargetName.empty()) { return; - } + } // Labels are listed in per-target files. std::string fname = this->OptionBuildDir; @@ -320,42 +295,35 @@ void cmCTestLaunch::LoadLabels() cmSystemTools::ConvertToUnixSlashes(source); // Load the labels file. - std::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary); - if(!fin) { return; } + cmsys::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary); + if (!fin) { + return; + } bool inTarget = true; bool inSource = false; std::string line; - while(cmSystemTools::GetLineFromStream(fin, line)) - { - if(line.empty() || line[0] == '#') - { + while (cmSystemTools::GetLineFromStream(fin, line)) { + if (line.empty() || line[0] == '#') { // Ignore blank and comment lines. continue; - } - else if(line[0] == ' ') - { + } + if (line[0] == ' ') { // Label lines appear indented by one space. - if(inTarget || inSource) - { - this->Labels.insert(line.c_str()+1); - } + if (inTarget || inSource) { + this->Labels.insert(line.c_str() + 1); } - else if(!this->OptionSource.empty() && !inSource) - { + } else if (!this->OptionSource.empty() && !inSource) { // Non-indented lines specify a source file name. The first one // is the end of the target-wide labels. Use labels following a // matching source. inTarget = false; inSource = this->SourceMatches(line, source); - } - else - { + } else { return; - } } + } } -//---------------------------------------------------------------------------- bool cmCTestLaunch::SourceMatches(std::string const& lhs, std::string const& rhs) { @@ -366,251 +334,212 @@ bool cmCTestLaunch::SourceMatches(std::string const& lhs, return lhs == rhs; } -//---------------------------------------------------------------------------- bool cmCTestLaunch::IsError() const { return this->ExitCode != 0; } -//---------------------------------------------------------------------------- void cmCTestLaunch::WriteXML() { // Name the xml file. std::string logXML = this->LogDir; - logXML += this->IsError()? "error-" : "warning-"; + logXML += this->IsError() ? "error-" : "warning-"; logXML += this->LogHash; logXML += ".xml"; // Use cmGeneratedFileStream to atomically create the report file. cmGeneratedFileStream fxml(logXML.c_str()); - fxml << "\t<Failure type=\"" - << (this->IsError()? "Error" : "Warning") << "\">\n"; - this->WriteXMLAction(fxml); - this->WriteXMLCommand(fxml); - this->WriteXMLResult(fxml); - this->WriteXMLLabels(fxml); - fxml << "\t</Failure>\n"; + cmXMLWriter xml(fxml, 2); + xml.StartElement("Failure"); + xml.Attribute("type", this->IsError() ? "Error" : "Warning"); + this->WriteXMLAction(xml); + this->WriteXMLCommand(xml); + this->WriteXMLResult(xml); + this->WriteXMLLabels(xml); + xml.EndElement(); // Failure } -//---------------------------------------------------------------------------- -void cmCTestLaunch::WriteXMLAction(std::ostream& fxml) +void cmCTestLaunch::WriteXMLAction(cmXMLWriter& xml) { - fxml << "\t\t<!-- Meta-information about the build action -->\n"; - fxml << "\t\t<Action>\n"; + xml.Comment("Meta-information about the build action"); + xml.StartElement("Action"); // TargetName - if(!this->OptionTargetName.empty()) - { - fxml << "\t\t\t<TargetName>" - << cmXMLSafe(this->OptionTargetName) - << "</TargetName>\n"; - } + if (!this->OptionTargetName.empty()) { + xml.Element("TargetName", this->OptionTargetName); + } // Language - if(!this->OptionLanguage.empty()) - { - fxml << "\t\t\t<Language>" - << cmXMLSafe(this->OptionLanguage) - << "</Language>\n"; - } + if (!this->OptionLanguage.empty()) { + xml.Element("Language", this->OptionLanguage); + } // SourceFile - if(!this->OptionSource.empty()) - { + if (!this->OptionSource.empty()) { std::string source = this->OptionSource; cmSystemTools::ConvertToUnixSlashes(source); // If file is in source tree use its relative location. - if(cmSystemTools::FileIsFullPath(this->SourceDir.c_str()) && - cmSystemTools::FileIsFullPath(source.c_str()) && - cmSystemTools::IsSubDirectory(source.c_str(), - this->SourceDir.c_str())) - { - source = cmSystemTools::RelativePath(this->SourceDir.c_str(), - source.c_str()); - } - - fxml << "\t\t\t<SourceFile>" - << cmXMLSafe(source) - << "</SourceFile>\n"; + if (cmSystemTools::FileIsFullPath(this->SourceDir.c_str()) && + cmSystemTools::FileIsFullPath(source.c_str()) && + cmSystemTools::IsSubDirectory(source, this->SourceDir)) { + source = + cmSystemTools::RelativePath(this->SourceDir.c_str(), source.c_str()); } + xml.Element("SourceFile", source); + } + // OutputFile - if(!this->OptionOutput.empty()) - { - fxml << "\t\t\t<OutputFile>" - << cmXMLSafe(this->OptionOutput) - << "</OutputFile>\n"; - } + if (!this->OptionOutput.empty()) { + xml.Element("OutputFile", this->OptionOutput); + } // OutputType - const char* outputType = 0; - if(!this->OptionTargetType.empty()) - { - if(this->OptionTargetType == "EXECUTABLE") - { + const char* outputType = CM_NULLPTR; + if (!this->OptionTargetType.empty()) { + if (this->OptionTargetType == "EXECUTABLE") { outputType = "executable"; - } - else if(this->OptionTargetType == "SHARED_LIBRARY") - { + } else if (this->OptionTargetType == "SHARED_LIBRARY") { outputType = "shared library"; - } - else if(this->OptionTargetType == "MODULE_LIBRARY") - { + } else if (this->OptionTargetType == "MODULE_LIBRARY") { outputType = "module library"; - } - else if(this->OptionTargetType == "STATIC_LIBRARY") - { + } else if (this->OptionTargetType == "STATIC_LIBRARY") { outputType = "static library"; - } } - else if(!this->OptionSource.empty()) - { + } else if (!this->OptionSource.empty()) { outputType = "object file"; - } - if(outputType) - { - fxml << "\t\t\t<OutputType>" - << cmXMLSafe(outputType) - << "</OutputType>\n"; - } + } + if (outputType) { + xml.Element("OutputType", outputType); + } - fxml << "\t\t</Action>\n"; + xml.EndElement(); // Action } -//---------------------------------------------------------------------------- -void cmCTestLaunch::WriteXMLCommand(std::ostream& fxml) +void cmCTestLaunch::WriteXMLCommand(cmXMLWriter& xml) { - fxml << "\n"; - fxml << "\t\t<!-- Details of command -->\n"; - fxml << "\t\t<Command>\n"; - if(!this->CWD.empty()) - { - fxml << "\t\t\t<WorkingDirectory>" - << cmXMLSafe(this->CWD) - << "</WorkingDirectory>\n"; - } - for(std::vector<std::string>::const_iterator ai = this->RealArgs.begin(); - ai != this->RealArgs.end(); ++ai) - { - fxml << "\t\t\t<Argument>" - << cmXMLSafe(ai->c_str()) - << "</Argument>\n"; - } - fxml << "\t\t</Command>\n"; + xml.Comment("Details of command"); + xml.StartElement("Command"); + if (!this->CWD.empty()) { + xml.Element("WorkingDirectory", this->CWD); + } + for (std::vector<std::string>::const_iterator ai = this->RealArgs.begin(); + ai != this->RealArgs.end(); ++ai) { + xml.Element("Argument", *ai); + } + xml.EndElement(); // Command } -//---------------------------------------------------------------------------- -void cmCTestLaunch::WriteXMLResult(std::ostream& fxml) +void cmCTestLaunch::WriteXMLResult(cmXMLWriter& xml) { - fxml << "\n"; - fxml << "\t\t<!-- Result of command -->\n"; - fxml << "\t\t<Result>\n"; + xml.Comment("Result of command"); + xml.StartElement("Result"); // StdOut - fxml << "\t\t\t<StdOut>"; - this->DumpFileToXML(fxml, this->LogOut); - fxml << "</StdOut>\n"; + xml.StartElement("StdOut"); + this->DumpFileToXML(xml, this->LogOut); + xml.EndElement(); // StdOut // StdErr - fxml << "\t\t\t<StdErr>"; - this->DumpFileToXML(fxml, this->LogErr); - fxml << "</StdErr>\n"; + xml.StartElement("StdErr"); + this->DumpFileToXML(xml, this->LogErr); + xml.EndElement(); // StdErr // ExitCondition - fxml << "\t\t\t<ExitCondition>"; + xml.StartElement("ExitCondition"); cmsysProcess* cp = this->Process; - switch (cmsysProcess_GetState(cp)) - { + switch (cmsysProcess_GetState(cp)) { case cmsysProcess_State_Starting: - fxml << "No process has been executed"; break; + xml.Content("No process has been executed"); + break; case cmsysProcess_State_Executing: - fxml << "The process is still executing"; break; + xml.Content("The process is still executing"); + break; case cmsysProcess_State_Disowned: - fxml << "Disowned"; break; + xml.Content("Disowned"); + break; case cmsysProcess_State_Killed: - fxml << "Killed by parent"; break; + xml.Content("Killed by parent"); + break; case cmsysProcess_State_Expired: - fxml << "Killed when timeout expired"; break; + xml.Content("Killed when timeout expired"); + break; case cmsysProcess_State_Exited: - fxml << this->ExitCode; break; + xml.Content(this->ExitCode); + break; case cmsysProcess_State_Exception: - fxml << "Terminated abnormally: " - << cmXMLSafe(cmsysProcess_GetExceptionString(cp)); break; + xml.Content("Terminated abnormally: "); + xml.Content(cmsysProcess_GetExceptionString(cp)); + break; case cmsysProcess_State_Error: - fxml << "Error administrating child process: " - << cmXMLSafe(cmsysProcess_GetErrorString(cp)); break; - }; - fxml << "</ExitCondition>\n"; + xml.Content("Error administrating child process: "); + xml.Content(cmsysProcess_GetErrorString(cp)); + break; + }; + xml.EndElement(); // ExitCondition - fxml << "\t\t</Result>\n"; + xml.EndElement(); // Result } -//---------------------------------------------------------------------------- -void cmCTestLaunch::WriteXMLLabels(std::ostream& fxml) +void cmCTestLaunch::WriteXMLLabels(cmXMLWriter& xml) { this->LoadLabels(); - if(!this->Labels.empty()) - { - fxml << "\n"; - fxml << "\t\t<!-- Interested parties -->\n"; - fxml << "\t\t<Labels>\n"; - for(std::set<cmStdString>::const_iterator li = this->Labels.begin(); - li != this->Labels.end(); ++li) - { - fxml << "\t\t\t<Label>" << cmXMLSafe(*li) << "</Label>\n"; - } - fxml << "\t\t</Labels>\n"; - } + if (!this->Labels.empty()) { + xml.Comment("Interested parties"); + xml.StartElement("Labels"); + for (std::set<std::string>::const_iterator li = this->Labels.begin(); + li != this->Labels.end(); ++li) { + xml.Element("Label", *li); + } + xml.EndElement(); // Labels + } } -//---------------------------------------------------------------------------- -void cmCTestLaunch::DumpFileToXML(std::ostream& fxml, - std::string const& fname) +void cmCTestLaunch::DumpFileToXML(cmXMLWriter& xml, std::string const& fname) { - std::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary); + cmsys::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary); std::string line; const char* sep = ""; - while(cmSystemTools::GetLineFromStream(fin, line)) - { - fxml << sep << cmXMLSafe(line).Quotes(false); - sep = "\n"; + + while (cmSystemTools::GetLineFromStream(fin, line)) { + if (MatchesFilterPrefix(line)) { + continue; } + + xml.Content(sep); + xml.Content(line); + sep = "\n"; + } } -//---------------------------------------------------------------------------- bool cmCTestLaunch::CheckResults() { // Skip XML in passthru mode. - if(this->Passthru) - { + if (this->Passthru) { return true; - } + } // We always report failure for error conditions. - if(this->IsError()) - { + if (this->IsError()) { return false; - } + } // Scrape the output logs to look for warnings. - if((this->HaveErr && this->ScrapeLog(this->LogErr)) || - (this->HaveOut && this->ScrapeLog(this->LogOut))) - { + if ((this->HaveErr && this->ScrapeLog(this->LogErr)) || + (this->HaveOut && this->ScrapeLog(this->LogOut))) { return false; - } + } return true; } -//---------------------------------------------------------------------------- void cmCTestLaunch::LoadScrapeRules() { - if(this->ScrapeRulesLoaded) - { + if (this->ScrapeRulesLoaded) { return; - } + } this->ScrapeRulesLoaded = true; // Common compiler warning formats. These are much simpler than the @@ -625,95 +554,86 @@ void cmCTestLaunch::LoadScrapeRules() this->LoadScrapeRules("WarningSuppress", this->RegexWarningSuppress); } -//---------------------------------------------------------------------------- -void -cmCTestLaunch -::LoadScrapeRules(const char* purpose, - std::vector<cmsys::RegularExpression>& regexps) +void cmCTestLaunch::LoadScrapeRules( + const char* purpose, std::vector<cmsys::RegularExpression>& regexps) { std::string fname = this->LogDir; fname += "Custom"; fname += purpose; fname += ".txt"; - std::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary); + cmsys::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary); std::string line; cmsys::RegularExpression rex; - while(cmSystemTools::GetLineFromStream(fin, line)) - { - if(rex.compile(line.c_str())) - { + while (cmSystemTools::GetLineFromStream(fin, line)) { + if (rex.compile(line.c_str())) { regexps.push_back(rex); - } } + } } -//---------------------------------------------------------------------------- bool cmCTestLaunch::ScrapeLog(std::string const& fname) { this->LoadScrapeRules(); // Look for log file lines matching warning expressions but not // suppression expressions. - std::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary); + cmsys::ifstream fin(fname.c_str(), std::ios::in | std::ios::binary); std::string line; - while(cmSystemTools::GetLineFromStream(fin, line)) - { - if(this->Match(line.c_str(), this->RegexWarning) && - !this->Match(line.c_str(), this->RegexWarningSuppress)) - { + while (cmSystemTools::GetLineFromStream(fin, line)) { + if (MatchesFilterPrefix(line)) { + continue; + } + + if (this->Match(line, this->RegexWarning) && + !this->Match(line, this->RegexWarningSuppress)) { return true; - } } + } return false; } -//---------------------------------------------------------------------------- bool cmCTestLaunch::Match(std::string const& line, std::vector<cmsys::RegularExpression>& regexps) { - for(std::vector<cmsys::RegularExpression>::iterator ri = regexps.begin(); - ri != regexps.end(); ++ri) - { - if(ri->find(line.c_str())) - { + for (std::vector<cmsys::RegularExpression>::iterator ri = regexps.begin(); + ri != regexps.end(); ++ri) { + if (ri->find(line.c_str())) { return true; - } } + } return false; } -//---------------------------------------------------------------------------- +bool cmCTestLaunch::MatchesFilterPrefix(std::string const& line) const +{ + return !this->OptionFilterPrefix.empty() && + cmSystemTools::StringStartsWith(line, this->OptionFilterPrefix.c_str()); +} + int cmCTestLaunch::Main(int argc, const char* const argv[]) { - if(argc == 2) - { + if (argc == 2) { std::cerr << "ctest --launch: this mode is for internal CTest use only" << std::endl; return 1; - } + } cmCTestLaunch self(argc, argv); return self.Run(); } -//---------------------------------------------------------------------------- -#include "cmGlobalGenerator.h" -#include "cmLocalGenerator.h" -#include "cmMakefile.h" -#include "cmake.h" -#include <cmsys/auto_ptr.hxx> void cmCTestLaunch::LoadConfig() { - cmake cm; - cmGlobalGenerator gg; - gg.SetCMakeInstance(&cm); - cmsys::auto_ptr<cmLocalGenerator> lg(gg.CreateLocalGenerator()); - cmMakefile* mf = lg->GetMakefile(); + cmake cm(cmake::RoleScript); + cm.SetHomeDirectory(""); + cm.SetHomeOutputDirectory(""); + cm.GetCurrentSnapshot().SetDefaultDefinitions(); + cmGlobalGenerator gg(&cm); + CM_AUTO_PTR<cmMakefile> mf(new cmMakefile(&gg, cm.GetCurrentSnapshot())); std::string fname = this->LogDir; fname += "CTestLaunchConfig.cmake"; - if(cmSystemTools::FileExists(fname.c_str()) && - mf->ReadListFile(0, fname.c_str())) - { + if (cmSystemTools::FileExists(fname.c_str()) && + mf->ReadListFile(fname.c_str())) { this->SourceDir = mf->GetSafeDefinition("CTEST_SOURCE_DIRECTORY"); cmSystemTools::ConvertToUnixSlashes(this->SourceDir); - } + } } diff --git a/Source/CTest/cmCTestLaunch.h b/Source/CTest/cmCTestLaunch.h index 7457e8357..29986ff60 100644 --- a/Source/CTest/cmCTestLaunch.h +++ b/Source/CTest/cmCTestLaunch.h @@ -1,19 +1,16 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestLaunch_h #define cmCTestLaunch_h -#include "cmStandardIncludes.h" -#include <cmsys/RegularExpression.hxx> +#include "cmConfigure.h" // IWYU pragma: keep + +#include "cmsys/RegularExpression.hxx" +#include <set> +#include <string> +#include <vector> + +class cmXMLWriter; /** \class cmCTestLaunch * \brief Launcher for make rules to report results for ctest @@ -25,6 +22,7 @@ class cmCTestLaunch public: /** Entry point from ctest executable main(). */ static int Main(int argc, const char* const argv[]); + private: // Initialize the launcher from its command line. cmCTestLaunch(int argc, const char* const* argv); @@ -45,6 +43,7 @@ private: std::string OptionTargetName; std::string OptionTargetType; std::string OptionBuildDir; + std::string OptionFilterPrefix; bool ParseArguments(int argc, const char* const* argv); // The real command line appearing after launcher arguments. @@ -72,10 +71,9 @@ private: bool HaveErr; // Labels associated with the build rule. - std::set<cmStdString> Labels; + std::set<std::string> Labels; void LoadLabels(); - bool SourceMatches(std::string const& lhs, - std::string const& rhs); + bool SourceMatches(std::string const& lhs, std::string const& rhs); // Regular expressions to match warnings and their exceptions. bool ScrapeRulesLoaded; @@ -87,14 +85,15 @@ private: bool ScrapeLog(std::string const& fname); bool Match(std::string const& line, std::vector<cmsys::RegularExpression>& regexps); + bool MatchesFilterPrefix(std::string const& line) const; // Methods to generate the xml fragment. void WriteXML(); - void WriteXMLAction(std::ostream& fxml); - void WriteXMLCommand(std::ostream& fxml); - void WriteXMLResult(std::ostream& fxml); - void WriteXMLLabels(std::ostream& fxml); - void DumpFileToXML(std::ostream& fxml, std::string const& fname); + void WriteXMLAction(cmXMLWriter& xml); + void WriteXMLCommand(cmXMLWriter& xml); + void WriteXMLResult(cmXMLWriter& xml); + void WriteXMLLabels(cmXMLWriter& xml); + void DumpFileToXML(cmXMLWriter& xml, std::string const& fname); // Configuration void LoadConfig(); diff --git a/Source/CTest/cmCTestMemCheckCommand.cxx b/Source/CTest/cmCTestMemCheckCommand.cxx index 535c9934f..b9cae3bfe 100644 --- a/Source/CTest/cmCTestMemCheckCommand.cxx +++ b/Source/CTest/cmCTestMemCheckCommand.cxx @@ -1,32 +1,54 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestMemCheckCommand.h" +#include <sstream> +#include <string> +#include <vector> + #include "cmCTest.h" #include "cmCTestGenericHandler.h" +#include "cmCTestMemCheckHandler.h" +#include "cmMakefile.h" +cmCTestMemCheckCommand::cmCTestMemCheckCommand() +{ + this->Arguments[ctm_DEFECT_COUNT] = "DEFECT_COUNT"; + this->Arguments[ctm_LAST] = CM_NULLPTR; + this->Last = ctm_LAST; +} cmCTestGenericHandler* cmCTestMemCheckCommand::InitializeActualHandler() { - cmCTestGenericHandler* handler - = this->CTest->GetInitializedHandler("memcheck"); - - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "MemoryCheckCommand", "CTEST_MEMORYCHECK_COMMAND"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "MemoryCheckCommandOptions", "CTEST_MEMORYCHECK_COMMAND_OPTIONS"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "MemoryCheckSuppressionFile", "CTEST_MEMORYCHECK_SUPPRESSIONS_FILE"); - + cmCTestGenericHandler* handler = + this->CTest->GetInitializedHandler("memcheck"); + + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "MemoryCheckType", "CTEST_MEMORYCHECK_TYPE", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "MemoryCheckSanitizerOptions", + "CTEST_MEMORYCHECK_SANITIZER_OPTIONS", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "MemoryCheckCommand", "CTEST_MEMORYCHECK_COMMAND", + this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "MemoryCheckCommandOptions", + "CTEST_MEMORYCHECK_COMMAND_OPTIONS", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "MemoryCheckSuppressionFile", + "CTEST_MEMORYCHECK_SUPPRESSIONS_FILE", this->Quiet); + + handler->SetQuiet(this->Quiet); return handler; } +void cmCTestMemCheckCommand::ProcessAdditionalValues( + cmCTestGenericHandler* handler) +{ + if (this->Values[ctm_DEFECT_COUNT] && *this->Values[ctm_DEFECT_COUNT]) { + std::ostringstream str; + str << static_cast<cmCTestMemCheckHandler*>(handler)->GetDefectCount(); + this->Makefile->AddDefinition(this->Values[ctm_DEFECT_COUNT], + str.str().c_str()); + } +} diff --git a/Source/CTest/cmCTestMemCheckCommand.h b/Source/CTest/cmCTestMemCheckCommand.h index 6db47aec8..fa595594a 100644 --- a/Source/CTest/cmCTestMemCheckCommand.h +++ b/Source/CTest/cmCTestMemCheckCommand.h @@ -1,20 +1,14 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestMemCheckCommand_h #define cmCTestMemCheckCommand_h +#include "cmConfigure.h" + #include "cmCTestTestCommand.h" class cmCTestGenericHandler; +class cmCommand; /** \class cmCTestMemCheck * \brief Run a ctest script @@ -24,65 +18,29 @@ class cmCTestGenericHandler; class cmCTestMemCheckCommand : public cmCTestTestCommand { public: - - cmCTestMemCheckCommand() {} + cmCTestMemCheckCommand(); /** * This is a virtual constructor for the command. */ - virtual cmCommand* Clone() - { + cmCommand* Clone() CM_OVERRIDE + { cmCTestMemCheckCommand* ni = new cmCTestMemCheckCommand; ni->CTest = this->CTest; ni->CTestScriptHandler = this->CTestScriptHandler; return ni; - } - - /** - * The name of the command as specified in CMakeList.txt. - */ - virtual const char* GetName() const { return "ctest_memcheck";} - - /** - * Succinct documentation. - */ - virtual const char* GetTerseDocumentation() const - { - return "Run tests with a dynamic analysis tool."; - } + } - /** - * More documentation. - */ - virtual const char* GetFullDocumentation() const - { - return - " ctest_memcheck([BUILD build_dir] [RETURN_VALUE res] [APPEND]\n" - " [START start number] [END end number]\n" - " [STRIDE stride number] [EXCLUDE exclude regex ]\n" - " [INCLUDE include regex] \n" - " [EXCLUDE_LABEL exclude regex] \n" - " [INCLUDE_LABEL label regex] \n" - " [PARALLEL_LEVEL level] )\n" - "Tests the given build directory and stores results in MemCheck.xml. " - "The second argument is a variable that will hold value. Optionally, " - "you can specify the starting test number START, the ending test number " - "END, the number of tests to skip between each test STRIDE, a regular " - "expression for tests to run INCLUDE, or a regular expression for tests " - "not to run EXCLUDE. EXCLUDE_LABEL and INCLUDE_LABEL are regular " - "expressions for tests to be included or excluded by the test " - "property LABEL. PARALLEL_LEVEL should be set to a positive number " - "representing the number of tests to be run in parallel." - "\n" - CTEST_COMMAND_APPEND_OPTION_DOCS; - } +protected: + cmCTestGenericHandler* InitializeActualHandler() CM_OVERRIDE; - cmTypeMacro(cmCTestMemCheckCommand, cmCTestTestCommand); + void ProcessAdditionalValues(cmCTestGenericHandler* handler) CM_OVERRIDE; -protected: - cmCTestGenericHandler* InitializeActualHandler(); + enum + { + ctm_DEFECT_COUNT = ctt_LAST, + ctm_LAST + }; }; - #endif - diff --git a/Source/CTest/cmCTestMemCheckHandler.cxx b/Source/CTest/cmCTestMemCheckHandler.cxx index 3ae2ac6a7..c35f0bc70 100644 --- a/Source/CTest/cmCTestMemCheckHandler.cxx +++ b/Source/CTest/cmCTestMemCheckHandler.cxx @@ -1,29 +1,18 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestMemCheckHandler.h" -#include "cmXMLParser.h" + #include "cmCTest.h" -#include "cmake.h" -#include "cmGeneratedFileStream.h" -#include <cmsys/Process.h> -#include <cmsys/RegularExpression.hxx> -#include <cmsys/Base64.h> -#include "cmMakefile.h" -#include "cmXMLSafe.h" - -#include <stdlib.h> -#include <math.h> -#include <float.h> +#include "cmSystemTools.h" +#include "cmXMLParser.h" +#include "cmXMLWriter.h" + +#include "cmsys/FStream.hxx" +#include "cmsys/Glob.hxx" +#include "cmsys/RegularExpression.hxx" +#include <iostream> +#include <sstream> +#include <string.h> struct CatToErrorType { @@ -31,172 +20,106 @@ struct CatToErrorType int ErrorCode; }; - static CatToErrorType cmCTestMemCheckBoundsChecker[] = { // Error tags - {"Write Overrun", cmCTestMemCheckHandler::ABW}, - {"Read Overrun", cmCTestMemCheckHandler::ABR}, - {"Memory Overrun", cmCTestMemCheckHandler::ABW}, - {"Allocation Conflict", cmCTestMemCheckHandler::FMM}, - {"Bad Pointer Use", cmCTestMemCheckHandler::FMW}, - {"Dangling Pointer", cmCTestMemCheckHandler::FMR}, - {0,0} + { "Write Overrun", cmCTestMemCheckHandler::ABW }, + { "Read Overrun", cmCTestMemCheckHandler::ABR }, + { "Memory Overrun", cmCTestMemCheckHandler::ABW }, + { "Allocation Conflict", cmCTestMemCheckHandler::FMM }, + { "Bad Pointer Use", cmCTestMemCheckHandler::FMW }, + { "Dangling Pointer", cmCTestMemCheckHandler::FMR }, + { CM_NULLPTR, 0 } }; +static void xmlReportError(int line, const char* msg, void* data) +{ + cmCTest* ctest = (cmCTest*)data; + cmCTestLog(ctest, ERROR_MESSAGE, "Error parsing XML in stream at line " + << line << ": " << msg << std::endl); +} + // parse the xml file containing the results of last BoundsChecker run class cmBoundsCheckerParser : public cmXMLParser { public: - cmBoundsCheckerParser(cmCTest* c) { this->CTest = c;} - void StartElement(const char* name, const char** atts) - { - if(strcmp(name, "MemoryLeak") == 0) - { - this->Errors.push_back(cmCTestMemCheckHandler::MLK); - } - if(strcmp(name, "ResourceLeak") == 0) - { - this->Errors.push_back(cmCTestMemCheckHandler::MLK); - } - if(strcmp(name, "Error") == 0) - { - this->ParseError(atts); - } - if(strcmp(name, "Dangling Pointer") == 0) - { - this->ParseError(atts); - } - // Create the log - cmOStringStream ostr; - ostr << name << ":\n"; - int i = 0; - for(; atts[i] != 0; i+=2) - { - ostr << " " << cmXMLSafe(atts[i]) - << " - " << cmXMLSafe(atts[i+1]) << "\n"; - } - ostr << "\n"; - this->Log += ostr.str(); + cmBoundsCheckerParser(cmCTest* c) + { + this->CTest = c; + this->SetErrorCallback(xmlReportError, (void*)c); + } + void StartElement(const std::string& name, const char** atts) CM_OVERRIDE + { + if (name == "MemoryLeak" || name == "ResourceLeak") { + this->Errors.push_back(cmCTestMemCheckHandler::MLK); + } else if (name == "Error" || name == "Dangling Pointer") { + this->ParseError(atts); } - void EndElement(const char* ) - { + // Create the log + std::ostringstream ostr; + ostr << name << ":\n"; + int i = 0; + for (; atts[i] != CM_NULLPTR; i += 2) { + ostr << " " << atts[i] << " - " << atts[i + 1] << "\n"; } + ostr << "\n"; + this->Log += ostr.str(); + } + void EndElement(const std::string& /*name*/) CM_OVERRIDE {} const char* GetAttribute(const char* name, const char** atts) - { - int i = 0; - for(; atts[i] != 0; ++i) - { - if(strcmp(name, atts[i]) == 0) - { - return atts[i+1]; - } - } - return 0; + { + int i = 0; + for (; atts[i] != CM_NULLPTR; ++i) { + if (strcmp(name, atts[i]) == 0) { + return atts[i + 1]; + } } + return CM_NULLPTR; + } void ParseError(const char** atts) - { - CatToErrorType* ptr = cmCTestMemCheckBoundsChecker; - const char* cat = this->GetAttribute("ErrorCategory", atts); - if(!cat) - { - this->Errors.push_back(cmCTestMemCheckHandler::ABW); // do not know - cmCTestLog(this->CTest, ERROR_MESSAGE, - "No Category found in Bounds checker XML\n" ); - return; - } - while(ptr->ErrorCategory && cat) - { - if(strcmp(ptr->ErrorCategory, cat) == 0) - { - this->Errors.push_back(ptr->ErrorCode); - return; // found it we are done - } - ptr++; - } - if(ptr->ErrorCategory) - { - this->Errors.push_back(cmCTestMemCheckHandler::ABW); // do not know - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Found unknown Bounds Checker error " - << ptr->ErrorCategory << std::endl); - } + { + CatToErrorType* ptr = cmCTestMemCheckBoundsChecker; + const char* cat = this->GetAttribute("ErrorCategory", atts); + if (!cat) { + this->Errors.push_back(cmCTestMemCheckHandler::ABW); // do not know + cmCTestLog(this->CTest, ERROR_MESSAGE, + "No Category found in Bounds checker XML\n"); + return; + } + while (ptr->ErrorCategory && cat) { + if (strcmp(ptr->ErrorCategory, cat) == 0) { + this->Errors.push_back(ptr->ErrorCode); + return; // found it we are done + } + ptr++; + } + if (ptr->ErrorCategory) { + this->Errors.push_back(cmCTestMemCheckHandler::ABW); // do not know + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Found unknown Bounds Checker error " << ptr->ErrorCategory + << std::endl); } + } cmCTest* CTest; std::vector<int> Errors; std::string Log; }; -#define BOUNDS_CHECKER_MARKER \ -"******######*****Begin BOUNDS CHECKER XML******######******" -//---------------------------------------------------------------------- -static const char* cmCTestMemCheckResultStrings[] = { - "ABR", - "ABW", - "ABWL", - "COR", - "EXU", - "FFM", - "FIM", - "FMM", - "FMR", - "FMW", - "FUM", - "IPR", - "IPW", - "MAF", - "MLK", - "MPK", - "NPR", - "ODS", - "PAR", - "PLK", - "UMC", - "UMR", - 0 -}; - - -//---------------------------------------------------------------------- -static const char* cmCTestMemCheckResultLongStrings[] = { - "Threading Problem", - "ABW", - "ABWL", - "COR", - "EXU", - "FFM", - "FIM", - "Mismatched deallocation", - "FMR", - "FMW", - "FUM", - "IPR", - "IPW", - "MAF", - "Memory Leak", - "Potential Memory Leak", - "NPR", - "ODS", - "Invalid syscall param", - "PLK", - "Uninitialized Memory Conditional", - "Uninitialized Memory Read", - 0 -}; - +#define BOUNDS_CHECKER_MARKER \ + "******######*****Begin BOUNDS CHECKER XML******######******" -//---------------------------------------------------------------------- cmCTestMemCheckHandler::cmCTestMemCheckHandler() { this->MemCheck = true; this->CustomMaximumPassedTestOutputSize = 0; this->CustomMaximumFailedTestOutputSize = 0; + this->LogWithPID = false; } -//---------------------------------------------------------------------- void cmCTestMemCheckHandler::Initialize() { this->Superclass::Initialize(); + this->LogWithPID = false; this->CustomMaximumPassedTestOutputSize = 0; this->CustomMaximumFailedTestOutputSize = 0; this->MemoryTester = ""; @@ -204,353 +127,452 @@ void cmCTestMemCheckHandler::Initialize() this->MemoryTesterOptions.clear(); this->MemoryTesterStyle = UNKNOWN; this->MemoryTesterOutputFile = ""; - int cc; - for ( cc = 0; cc < NO_MEMORY_FAULT; cc ++ ) - { - this->MemoryTesterGlobalResults[cc] = 0; - } - + this->DefectCount = 0; } -//---------------------------------------------------------------------- int cmCTestMemCheckHandler::PreProcessHandler() { - if ( !this->InitializeMemoryChecking() ) - { + if (!this->InitializeMemoryChecking()) { return 0; - } + } - if ( !this->ExecuteCommands(this->CustomPreMemCheck) ) - { + if (!this->ExecuteCommands(this->CustomPreMemCheck)) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Problem executing pre-memcheck command(s)." << std::endl); + "Problem executing pre-memcheck command(s)." << std::endl); return 0; - } + } return 1; } -//---------------------------------------------------------------------- int cmCTestMemCheckHandler::PostProcessHandler() { - if ( !this->ExecuteCommands(this->CustomPostMemCheck) ) - { + if (!this->ExecuteCommands(this->CustomPostMemCheck)) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Problem executing post-memcheck command(s)." << std::endl); + "Problem executing post-memcheck command(s)." << std::endl); return 0; - } + } return 1; } -//---------------------------------------------------------------------- void cmCTestMemCheckHandler::GenerateTestCommand( std::vector<std::string>& args, int test) { - std::vector<cmStdString>::size_type pp; - cmStdString index; - cmOStringStream stream; - std::string memcheckcommand - = cmSystemTools::ConvertToOutputPath(this->MemoryTester.c_str()); + std::vector<std::string>::size_type pp; + std::string index; + std::ostringstream stream; + std::string memcheckcommand = + cmSystemTools::ConvertToOutputPath(this->MemoryTester.c_str()); stream << test; index = stream.str(); - for ( pp = 0; pp < this->MemoryTesterDynamicOptions.size(); pp ++ ) - { - cmStdString arg = this->MemoryTesterDynamicOptions[pp]; - cmStdString::size_type pos = arg.find("??"); - if (pos != cmStdString::npos) - { + for (pp = 0; pp < this->MemoryTesterDynamicOptions.size(); pp++) { + std::string arg = this->MemoryTesterDynamicOptions[pp]; + std::string::size_type pos = arg.find("??"); + if (pos != std::string::npos) { arg.replace(pos, 2, index); - } + } args.push_back(arg); memcheckcommand += " \""; memcheckcommand += arg; memcheckcommand += "\""; + } + // Create a copy of the memory tester environment variable. + // This is used for memory testing programs that pass options + // via environment varaibles. + std::string memTesterEnvironmentVariable = + this->MemoryTesterEnvironmentVariable; + for (pp = 0; pp < this->MemoryTesterOptions.size(); pp++) { + if (!memTesterEnvironmentVariable.empty()) { + // If we are using env to pass options, append all the options to + // this string with space separation. + memTesterEnvironmentVariable += " " + this->MemoryTesterOptions[pp]; } - for ( pp = 0; pp < this->MemoryTesterOptions.size(); pp ++ ) - { - args.push_back(this->MemoryTesterOptions[pp]); - memcheckcommand += " \""; - memcheckcommand += this->MemoryTesterOptions[pp]; - memcheckcommand += "\""; + // for regular options just add them to args and memcheckcommand + // which is just used for display + else { + args.push_back(this->MemoryTesterOptions[pp]); + memcheckcommand += " \""; + memcheckcommand += this->MemoryTesterOptions[pp]; + memcheckcommand += "\""; + } + } + // if this is an env option type, then add the env string as a single + // argument. + if (!memTesterEnvironmentVariable.empty()) { + std::string::size_type pos = memTesterEnvironmentVariable.find("??"); + if (pos != std::string::npos) { + memTesterEnvironmentVariable.replace(pos, 2, index); } - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Memory check command: " - << memcheckcommand << std::endl); + memcheckcommand += " " + memTesterEnvironmentVariable; + args.push_back(memTesterEnvironmentVariable); + } + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Memory check command: " << memcheckcommand << std::endl, + this->Quiet); +} + +void cmCTestMemCheckHandler::InitializeResultsVectors() +{ + // fill these members + // cmsys::vector<std::string> ResultStrings; + // cmsys::vector<std::string> ResultStringsLong; + // cmsys::vector<int> GlobalResults; + this->ResultStringsLong.clear(); + this->ResultStrings.clear(); + this->GlobalResults.clear(); + // If we are working with style checkers that dynamically fill + // the results strings then return. + if (this->MemoryTesterStyle > cmCTestMemCheckHandler::BOUNDS_CHECKER) { + return; + } + + // define the standard set of errors + //---------------------------------------------------------------------- + static const char* cmCTestMemCheckResultStrings[] = { + "ABR", "ABW", "ABWL", "COR", "EXU", "FFM", "FIM", "FMM", + "FMR", "FMW", "FUM", "IPR", "IPW", "MAF", "MLK", "MPK", + "NPR", "ODS", "PAR", "PLK", "UMC", "UMR", CM_NULLPTR + }; + static const char* cmCTestMemCheckResultLongStrings[] = { + "Threading Problem", + "ABW", + "ABWL", + "COR", + "EXU", + "FFM", + "FIM", + "Mismatched deallocation", + "FMR", + "FMW", + "FUM", + "IPR", + "IPW", + "MAF", + "Memory Leak", + "Potential Memory Leak", + "NPR", + "ODS", + "Invalid syscall param", + "PLK", + "Uninitialized Memory Conditional", + "Uninitialized Memory Read", + CM_NULLPTR + }; + this->GlobalResults.clear(); + for (int i = 0; cmCTestMemCheckResultStrings[i] != CM_NULLPTR; ++i) { + this->ResultStrings.push_back(cmCTestMemCheckResultStrings[i]); + this->ResultStringsLong.push_back(cmCTestMemCheckResultLongStrings[i]); + this->GlobalResults.push_back(0); + } } -//---------------------------------------------------------------------- -void cmCTestMemCheckHandler::PopulateCustomVectors(cmMakefile *mf) +void cmCTestMemCheckHandler::PopulateCustomVectors(cmMakefile* mf) { this->cmCTestTestHandler::PopulateCustomVectors(mf); this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_PRE_MEMCHECK", - this->CustomPreMemCheck); + this->CustomPreMemCheck); this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_POST_MEMCHECK", - this->CustomPostMemCheck); + this->CustomPostMemCheck); - this->CTest->PopulateCustomVector(mf, - "CTEST_CUSTOM_MEMCHECK_IGNORE", - this->CustomTestsIgnore); + this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_MEMCHECK_IGNORE", + this->CustomTestsIgnore); + std::string cmake = cmSystemTools::GetCMakeCommand(); + this->CTest->SetCTestConfiguration("CMakeCommand", cmake.c_str(), + this->Quiet); } -//---------------------------------------------------------------------- -void cmCTestMemCheckHandler::GenerateDartOutput(std::ostream& os) +int cmCTestMemCheckHandler::GetDefectCount() { - if ( !this->CTest->GetProduceXML() ) - { - return; - } + return this->DefectCount; +} - this->CTest->StartXML(os, this->AppendXML); - os << "<DynamicAnalysis Checker=\""; - switch ( this->MemoryTesterStyle ) - { +void cmCTestMemCheckHandler::GenerateDartOutput(cmXMLWriter& xml) +{ + if (!this->CTest->GetProduceXML()) { + return; + } + this->CTest->StartXML(xml, this->AppendXML); + xml.StartElement("DynamicAnalysis"); + switch (this->MemoryTesterStyle) { case cmCTestMemCheckHandler::VALGRIND: - os << "Valgrind"; + xml.Attribute("Checker", "Valgrind"); break; case cmCTestMemCheckHandler::PURIFY: - os << "Purify"; + xml.Attribute("Checker", "Purify"); break; case cmCTestMemCheckHandler::BOUNDS_CHECKER: - os << "BoundsChecker"; + xml.Attribute("Checker", "BoundsChecker"); + break; + case cmCTestMemCheckHandler::ADDRESS_SANITIZER: + xml.Attribute("Checker", "AddressSanitizer"); + break; + case cmCTestMemCheckHandler::LEAK_SANITIZER: + xml.Attribute("Checker", "LeakSanitizer"); + break; + case cmCTestMemCheckHandler::THREAD_SANITIZER: + xml.Attribute("Checker", "ThreadSanitizer"); + break; + case cmCTestMemCheckHandler::MEMORY_SANITIZER: + xml.Attribute("Checker", "MemorySanitizer"); + break; + case cmCTestMemCheckHandler::UB_SANITIZER: + xml.Attribute("Checker", "UndefinedBehaviorSanitizer"); break; default: - os << "Unknown"; - } - os << "\">" << std::endl; + xml.Attribute("Checker", "Unknown"); + } - os << "\t<StartDateTime>" << this->StartTest << "</StartDateTime>\n" - << "\t<StartTestTime>" << this->StartTestTime << "</StartTestTime>\n" - << "\t<TestList>\n"; + xml.Element("StartDateTime", this->StartTest); + xml.Element("StartTestTime", this->StartTestTime); + xml.StartElement("TestList"); cmCTestMemCheckHandler::TestResultsVector::size_type cc; - for ( cc = 0; cc < this->TestResults.size(); cc ++ ) - { - cmCTestTestResult *result = &this->TestResults[cc]; + for (cc = 0; cc < this->TestResults.size(); cc++) { + cmCTestTestResult* result = &this->TestResults[cc]; std::string testPath = result->Path + "/" + result->Name; - os << "\t\t<Test>" << cmXMLSafe( - this->CTest->GetShortPathToFile(testPath.c_str())) - << "</Test>" << std::endl; - } - os << "\t</TestList>\n"; - cmCTestLog(this->CTest, HANDLER_OUTPUT, - "-- Processing memory checking output: "); + xml.Element("Test", this->CTest->GetShortPathToFile(testPath.c_str())); + } + xml.EndElement(); // TestList + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + "-- Processing memory checking output:\n", this->Quiet); size_t total = this->TestResults.size(); - size_t step = total / 10; - size_t current = 0; - for ( cc = 0; cc < this->TestResults.size(); cc ++ ) - { - cmCTestTestResult *result = &this->TestResults[cc]; + for (cc = 0; cc < this->TestResults.size(); cc++) { + cmCTestTestResult* result = &this->TestResults[cc]; std::string memcheckstr; - int memcheckresults[cmCTestMemCheckHandler::NO_MEMORY_FAULT]; - int kk; + std::vector<int> memcheckresults(this->ResultStrings.size(), 0); bool res = this->ProcessMemCheckOutput(result->Output, memcheckstr, - memcheckresults); - if ( res && result->Status == cmCTestMemCheckHandler::COMPLETED ) - { + memcheckresults); + if (res && result->Status == cmCTestMemCheckHandler::COMPLETED) { continue; - } - this->CleanTestOutput(memcheckstr, + } + this->CleanTestOutput( + memcheckstr, static_cast<size_t>(this->CustomMaximumFailedTestOutputSize)); - this->WriteTestResultHeader(os, result); - os << "\t\t<Results>" << std::endl; - for ( kk = 0; cmCTestMemCheckResultLongStrings[kk]; kk ++ ) - { - if ( memcheckresults[kk] ) - { - os << "\t\t\t<Defect type=\"" << cmCTestMemCheckResultLongStrings[kk] - << "\">" - << memcheckresults[kk] - << "</Defect>" << std::endl; - } - this->MemoryTesterGlobalResults[kk] += memcheckresults[kk]; + this->WriteTestResultHeader(xml, result); + xml.StartElement("Results"); + int memoryErrors = 0; + for (std::vector<int>::size_type kk = 0; kk < memcheckresults.size(); + ++kk) { + if (memcheckresults[kk]) { + xml.StartElement("Defect"); + xml.Attribute("type", this->ResultStringsLong[kk]); + xml.Content(memcheckresults[kk]); + memoryErrors += memcheckresults[kk]; + xml.EndElement(); // Defect } - - std::string logTag; - if(this->CTest->ShouldCompressMemCheckOutput()) - { + this->GlobalResults[kk] += memcheckresults[kk]; + } + xml.EndElement(); // Results + if (memoryErrors > 0) { + const int maxTestNameWidth = this->CTest->GetMaxTestNameWidth(); + std::string outname = result->Name + " "; + outname.resize(maxTestNameWidth + 4, '.'); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, cc + 1 + << "/" << total << " MemCheck: #" + << result->TestCount << ": " << outname + << " Defects: " << memoryErrors << std::endl, + this->Quiet); + } + xml.StartElement("Log"); + if (this->CTest->ShouldCompressTestOutput()) { this->CTest->CompressString(memcheckstr); - logTag = "\t<Log compression=\"gzip\" encoding=\"base64\">\n"; - } - else - { - logTag = "\t<Log>\n"; - } - - os - << "\t\t</Results>\n" - << logTag << cmXMLSafe(memcheckstr) << std::endl - << "\t</Log>\n"; - this->WriteTestResultFooter(os, result); - if ( current < cc ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, "#" << std::flush); - current += step; - } + xml.Attribute("compression", "gzip"); + xml.Attribute("encoding", "base64"); } - cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); - cmCTestLog(this->CTest, HANDLER_OUTPUT, "Memory checking results:" - << std::endl); - os << "\t<DefectList>" << std::endl; - for ( cc = 0; cmCTestMemCheckResultStrings[cc]; cc ++ ) - { - if ( this->MemoryTesterGlobalResults[cc] ) - { -#ifdef cerr -# undef cerr -#endif + xml.Content(memcheckstr); + xml.EndElement(); // Log + + this->WriteTestResultFooter(xml, result); + } + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + "MemCheck log files can be found here: " + "( * corresponds to test number)" + << std::endl, + this->Quiet); + std::string output = this->MemoryTesterOutputFile; + cmSystemTools::ReplaceString(output, "??", "*"); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, output << std::endl, + this->Quiet); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + "Memory checking results:" << std::endl, this->Quiet); + xml.StartElement("DefectList"); + for (cc = 0; cc < this->GlobalResults.size(); cc++) { + if (this->GlobalResults[cc]) { std::cerr.width(35); -#define cerr no_cerr - cmCTestLog(this->CTest, HANDLER_OUTPUT, - cmCTestMemCheckResultLongStrings[cc] << " - " - << this->MemoryTesterGlobalResults[cc] << std::endl); - os << "\t\t<Defect Type=\"" << cmCTestMemCheckResultLongStrings[cc] - << "\"/>" << std::endl; - } + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + this->ResultStringsLong[cc] + << " - " << this->GlobalResults[cc] << std::endl, + this->Quiet); + xml.StartElement("Defect"); + xml.Attribute("Type", this->ResultStringsLong[cc]); + xml.EndElement(); } - os << "\t</DefectList>" << std::endl; - - os << "\t<EndDateTime>" << this->EndTest << "</EndDateTime>" << std::endl; - os << "\t<EndTestTime>" << this->EndTestTime - << "</EndTestTime>" << std::endl; - os << "<ElapsedMinutes>" - << static_cast<int>(this->ElapsedTestingTime/6)/10.0 - << "</ElapsedMinutes>\n"; - - os << "</DynamicAnalysis>" << std::endl; - this->CTest->EndXML(os); + } + xml.EndElement(); // DefectList + xml.Element("EndDateTime", this->EndTest); + xml.Element("EndTestTime", this->EndTestTime); + xml.Element("ElapsedMinutes", + static_cast<int>(this->ElapsedTestingTime / 6) / 10.0); + xml.EndElement(); // DynamicAnalysis + this->CTest->EndXML(xml); } -//---------------------------------------------------------------------- bool cmCTestMemCheckHandler::InitializeMemoryChecking() { + this->MemoryTesterEnvironmentVariable = ""; + this->MemoryTester = ""; // Setup the command - if ( cmSystemTools::FileExists(this->CTest->GetCTestConfiguration( - "MemoryCheckCommand").c_str()) ) - { - this->MemoryTester - = this->CTest->GetCTestConfiguration("MemoryCheckCommand").c_str(); - + if (cmSystemTools::FileExists( + this->CTest->GetCTestConfiguration("MemoryCheckCommand").c_str())) { + this->MemoryTester = + this->CTest->GetCTestConfiguration("MemoryCheckCommand"); + std::string testerName = + cmSystemTools::GetFilenameName(this->MemoryTester); // determine the checker type - if ( this->MemoryTester.find("valgrind") != std::string::npos ) - { - this->MemoryTesterStyle = cmCTestMemCheckHandler::VALGRIND; - } - else if ( this->MemoryTester.find("purify") != std::string::npos ) - { + if (testerName.find("valgrind") != std::string::npos || + this->CTest->GetCTestConfiguration("MemoryCheckType") == "Valgrind") { + this->MemoryTesterStyle = cmCTestMemCheckHandler::VALGRIND; + } else if (testerName.find("purify") != std::string::npos) { this->MemoryTesterStyle = cmCTestMemCheckHandler::PURIFY; - } - else if ( this->MemoryTester.find("BC") != std::string::npos ) - { + } else if (testerName.find("BC") != std::string::npos) { this->MemoryTesterStyle = cmCTestMemCheckHandler::BOUNDS_CHECKER; - } - else - { + } else { this->MemoryTesterStyle = cmCTestMemCheckHandler::UNKNOWN; - } } - else if ( cmSystemTools::FileExists(this->CTest->GetCTestConfiguration( - "PurifyCommand").c_str()) ) - { - this->MemoryTester - = this->CTest->GetCTestConfiguration("PurifyCommand").c_str(); + } else if (cmSystemTools::FileExists( + this->CTest->GetCTestConfiguration("PurifyCommand").c_str())) { + this->MemoryTester = this->CTest->GetCTestConfiguration("PurifyCommand"); this->MemoryTesterStyle = cmCTestMemCheckHandler::PURIFY; - } - else if ( cmSystemTools::FileExists(this->CTest->GetCTestConfiguration( - "ValgrindCommand").c_str()) ) - { - this->MemoryTester - = this->CTest->GetCTestConfiguration("ValgrindCommand").c_str(); + } else if (cmSystemTools::FileExists( + this->CTest->GetCTestConfiguration("ValgrindCommand") + .c_str())) { + this->MemoryTester = this->CTest->GetCTestConfiguration("ValgrindCommand"); this->MemoryTesterStyle = cmCTestMemCheckHandler::VALGRIND; - } - else if ( cmSystemTools::FileExists(this->CTest->GetCTestConfiguration( - "BoundsCheckerCommand").c_str()) ) - { - this->MemoryTester - = this->CTest->GetCTestConfiguration("BoundsCheckerCommand").c_str(); + } else if (cmSystemTools::FileExists( + this->CTest->GetCTestConfiguration("BoundsCheckerCommand") + .c_str())) { + this->MemoryTester = + this->CTest->GetCTestConfiguration("BoundsCheckerCommand"); this->MemoryTesterStyle = cmCTestMemCheckHandler::BOUNDS_CHECKER; + } + if (this->CTest->GetCTestConfiguration("MemoryCheckType") == + "AddressSanitizer") { + this->MemoryTester = this->CTest->GetCTestConfiguration("CMakeCommand"); + this->MemoryTesterStyle = cmCTestMemCheckHandler::ADDRESS_SANITIZER; + this->LogWithPID = true; // even if we give the log file the pid is added + } + if (this->CTest->GetCTestConfiguration("MemoryCheckType") == + "LeakSanitizer") { + this->MemoryTester = this->CTest->GetCTestConfiguration("CMakeCommand"); + this->MemoryTesterStyle = cmCTestMemCheckHandler::LEAK_SANITIZER; + this->LogWithPID = true; // even if we give the log file the pid is added + } + if (this->CTest->GetCTestConfiguration("MemoryCheckType") == + "ThreadSanitizer") { + this->MemoryTester = this->CTest->GetCTestConfiguration("CMakeCommand"); + this->MemoryTesterStyle = cmCTestMemCheckHandler::THREAD_SANITIZER; + this->LogWithPID = true; // even if we give the log file the pid is added + } + if (this->CTest->GetCTestConfiguration("MemoryCheckType") == + "MemorySanitizer") { + this->MemoryTester = this->CTest->GetCTestConfiguration("CMakeCommand"); + this->MemoryTesterStyle = cmCTestMemCheckHandler::MEMORY_SANITIZER; + this->LogWithPID = true; // even if we give the log file the pid is added + } + if (this->CTest->GetCTestConfiguration("MemoryCheckType") == + "UndefinedBehaviorSanitizer") { + this->MemoryTester = this->CTest->GetCTestConfiguration("CMakeCommand"); + this->MemoryTesterStyle = cmCTestMemCheckHandler::UB_SANITIZER; + this->LogWithPID = true; // even if we give the log file the pid is added + } + // Check the MemoryCheckType + if (this->MemoryTesterStyle == cmCTestMemCheckHandler::UNKNOWN) { + std::string checkType = + this->CTest->GetCTestConfiguration("MemoryCheckType"); + if (checkType == "Purify") { + this->MemoryTesterStyle = cmCTestMemCheckHandler::PURIFY; + } else if (checkType == "BoundsChecker") { + this->MemoryTesterStyle = cmCTestMemCheckHandler::BOUNDS_CHECKER; + } else if (checkType == "Valgrind") { + this->MemoryTesterStyle = cmCTestMemCheckHandler::VALGRIND; } - else - { - cmCTestLog(this->CTest, WARNING, - "Memory checker (MemoryCheckCommand) " - "not set, or cannot find the specified program." - << std::endl); + } + if (this->MemoryTester.empty()) { + cmCTestOptionalLog(this->CTest, WARNING, + "Memory checker (MemoryCheckCommand) " + "not set, or cannot find the specified program." + << std::endl, + this->Quiet); return false; - } + } // Setup the options std::string memoryTesterOptions; - if ( this->CTest->GetCTestConfiguration( - "MemoryCheckCommandOptions").size() ) - { - memoryTesterOptions = this->CTest->GetCTestConfiguration( - "MemoryCheckCommandOptions"); - } - else if ( this->CTest->GetCTestConfiguration( - "ValgrindCommandOptions").size() ) - { - memoryTesterOptions = this->CTest->GetCTestConfiguration( - "ValgrindCommandOptions"); - } - this->MemoryTesterOptions - = cmSystemTools::ParseArguments(memoryTesterOptions.c_str()); - - this->MemoryTesterOutputFile - = this->CTest->GetBinaryDir() - + "/Testing/Temporary/MemoryChecker.??.log"; - - switch ( this->MemoryTesterStyle ) - { - case cmCTestMemCheckHandler::VALGRIND: - { - if ( this->MemoryTesterOptions.empty() ) - { + if (!this->CTest->GetCTestConfiguration("MemoryCheckCommandOptions") + .empty()) { + memoryTesterOptions = + this->CTest->GetCTestConfiguration("MemoryCheckCommandOptions"); + } else if (!this->CTest->GetCTestConfiguration("ValgrindCommandOptions") + .empty()) { + memoryTesterOptions = + this->CTest->GetCTestConfiguration("ValgrindCommandOptions"); + } + this->MemoryTesterOptions = + cmSystemTools::ParseArguments(memoryTesterOptions.c_str()); + + this->MemoryTesterOutputFile = + this->CTest->GetBinaryDir() + "/Testing/Temporary/MemoryChecker.??.log"; + + switch (this->MemoryTesterStyle) { + case cmCTestMemCheckHandler::VALGRIND: { + if (this->MemoryTesterOptions.empty()) { this->MemoryTesterOptions.push_back("-q"); this->MemoryTesterOptions.push_back("--tool=memcheck"); this->MemoryTesterOptions.push_back("--leak-check=yes"); this->MemoryTesterOptions.push_back("--show-reachable=yes"); this->MemoryTesterOptions.push_back("--num-callers=50"); - } - if ( this->CTest->GetCTestConfiguration( - "MemoryCheckSuppressionFile").size() ) - { - if ( !cmSystemTools::FileExists(this->CTest->GetCTestConfiguration( - "MemoryCheckSuppressionFile").c_str()) ) - { + } + if (!this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile") + .empty()) { + if (!cmSystemTools::FileExists( + this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile") + .c_str())) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot find memory checker suppression file: " - << this->CTest->GetCTestConfiguration( - "MemoryCheckSuppressionFile").c_str() << std::endl); + "Cannot find memory checker suppression file: " + << this->CTest->GetCTestConfiguration( + "MemoryCheckSuppressionFile") + << std::endl); return false; - } - std::string suppressions = "--suppressions=" - + this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile"); - this->MemoryTesterOptions.push_back(suppressions); } - std::string outputFile = "--log-file=" - + this->MemoryTesterOutputFile; + std::string suppressions = "--suppressions=" + + this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile"); + this->MemoryTesterOptions.push_back(suppressions); + } + std::string outputFile = "--log-file=" + this->MemoryTesterOutputFile; this->MemoryTesterDynamicOptions.push_back(outputFile); break; - } - case cmCTestMemCheckHandler::PURIFY: - { + } + case cmCTestMemCheckHandler::PURIFY: { std::string outputFile; #ifdef _WIN32 - if( this->CTest->GetCTestConfiguration( - "MemoryCheckSuppressionFile").size() ) - { - if( !cmSystemTools::FileExists(this->CTest->GetCTestConfiguration( - "MemoryCheckSuppressionFile").c_str()) ) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot find memory checker suppression file: " - << this->CTest->GetCTestConfiguration( - "MemoryCheckSuppressionFile").c_str() << std::endl); + if (this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile") + .size()) { + if (!cmSystemTools::FileExists( + this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile") + .c_str())) { + cmCTestLog( + this->CTest, ERROR_MESSAGE, + "Cannot find memory checker suppression file: " + << this->CTest + ->GetCTestConfiguration("MemoryCheckSuppressionFile") + .c_str() + << std::endl); return false; - } - std::string filterFiles = "/FilterFiles=" - + this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile"); - this->MemoryTesterOptions.push_back(filterFiles); } + std::string filterFiles = "/FilterFiles=" + + this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile"); + this->MemoryTesterOptions.push_back(filterFiles); + } outputFile = "/SAVETEXTDATA="; #else outputFile = "-log-file="; @@ -558,12 +580,11 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking() outputFile += this->MemoryTesterOutputFile; this->MemoryTesterDynamicOptions.push_back(outputFile); break; - } - case cmCTestMemCheckHandler::BOUNDS_CHECKER: - { + } + case cmCTestMemCheckHandler::BOUNDS_CHECKER: { this->BoundsCheckerXMLFile = this->MemoryTesterOutputFile; - std::string dpbdFile = this->CTest->GetBinaryDir() - + "/Testing/Temporary/MemoryChecker.??.DPbd"; + std::string dpbdFile = this->CTest->GetBinaryDir() + + "/Testing/Temporary/MemoryChecker.??.DPbd"; this->BoundsCheckerDPBDFile = dpbdFile; this->MemoryTesterDynamicOptions.push_back("/B"); this->MemoryTesterDynamicOptions.push_back(dpbdFile); @@ -571,127 +592,223 @@ bool cmCTestMemCheckHandler::InitializeMemoryChecking() this->MemoryTesterDynamicOptions.push_back(this->MemoryTesterOutputFile); this->MemoryTesterOptions.push_back("/M"); break; + } + // these are almost the same but the env var used is different + case cmCTestMemCheckHandler::ADDRESS_SANITIZER: + case cmCTestMemCheckHandler::LEAK_SANITIZER: + case cmCTestMemCheckHandler::THREAD_SANITIZER: + case cmCTestMemCheckHandler::MEMORY_SANITIZER: + case cmCTestMemCheckHandler::UB_SANITIZER: { + // To pass arguments to ThreadSanitizer the environment variable + // TSAN_OPTIONS is used. This is done with the cmake -E env command. + // The MemoryTesterDynamicOptions is setup with the -E env + // Then the MemoryTesterEnvironmentVariable gets the + // TSAN_OPTIONS string with the log_path in it. + this->MemoryTesterDynamicOptions.push_back("-E"); + this->MemoryTesterDynamicOptions.push_back("env"); + std::string envVar; + std::string extraOptions; + std::string suppressionsOption; + if (!this->CTest->GetCTestConfiguration("MemoryCheckSanitizerOptions") + .empty()) { + extraOptions = ":" + + this->CTest->GetCTestConfiguration("MemoryCheckSanitizerOptions"); + } + if (!this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile") + .empty()) { + suppressionsOption = ":suppressions=" + + this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile"); } + if (this->MemoryTesterStyle == + cmCTestMemCheckHandler::ADDRESS_SANITIZER) { + envVar = "ASAN_OPTIONS"; + } else if (this->MemoryTesterStyle == + cmCTestMemCheckHandler::LEAK_SANITIZER) { + envVar = "LSAN_OPTIONS"; + } else if (this->MemoryTesterStyle == + cmCTestMemCheckHandler::THREAD_SANITIZER) { + envVar = "TSAN_OPTIONS"; + } else if (this->MemoryTesterStyle == + cmCTestMemCheckHandler::MEMORY_SANITIZER) { + envVar = "MSAN_OPTIONS"; + } else if (this->MemoryTesterStyle == + cmCTestMemCheckHandler::UB_SANITIZER) { + envVar = "UBSAN_OPTIONS"; + } + // Quote log_path with single quotes; see + // https://bugs.chromium.org/p/chromium/issues/detail?id=467936 + std::string outputFile = + envVar + "=log_path='" + this->MemoryTesterOutputFile + "'"; + this->MemoryTesterEnvironmentVariable = + outputFile + suppressionsOption + extraOptions; + break; + } default: cmCTestLog(this->CTest, ERROR_MESSAGE, - "Do not understand memory checker: " << this->MemoryTester.c_str() - << std::endl); + "Do not understand memory checker: " << this->MemoryTester + << std::endl); return false; - } + } - std::vector<cmStdString>::size_type cc; - for ( cc = 0; cmCTestMemCheckResultStrings[cc]; cc ++ ) - { - this->MemoryTesterGlobalResults[cc] = 0; - } + this->InitializeResultsVectors(); + // std::vector<std::string>::size_type cc; + // for ( cc = 0; cmCTestMemCheckResultStrings[cc]; cc ++ ) + // { + // this->MemoryTesterGlobalResults[cc] = 0; + // } return true; } -//---------------------------------------------------------------------- bool cmCTestMemCheckHandler::ProcessMemCheckOutput(const std::string& str, - std::string& log, int* results) + std::string& log, + std::vector<int>& results) { - std::string::size_type cc; - for ( cc = 0; cc < cmCTestMemCheckHandler::NO_MEMORY_FAULT; cc ++ ) - { - results[cc] = 0; - } + switch (this->MemoryTesterStyle) { + case cmCTestMemCheckHandler::VALGRIND: + return this->ProcessMemCheckValgrindOutput(str, log, results); + case cmCTestMemCheckHandler::PURIFY: + return this->ProcessMemCheckPurifyOutput(str, log, results); + case cmCTestMemCheckHandler::ADDRESS_SANITIZER: + case cmCTestMemCheckHandler::LEAK_SANITIZER: + case cmCTestMemCheckHandler::THREAD_SANITIZER: + case cmCTestMemCheckHandler::MEMORY_SANITIZER: + case cmCTestMemCheckHandler::UB_SANITIZER: + return this->ProcessMemCheckSanitizerOutput(str, log, results); + case cmCTestMemCheckHandler::BOUNDS_CHECKER: + return this->ProcessMemCheckBoundsCheckerOutput(str, log, results); + default: + log.append("\nMemory checking style used was: "); + log.append("None that I know"); + log = str; + return true; + } +} - if ( this->MemoryTesterStyle == cmCTestMemCheckHandler::VALGRIND ) - { - return this->ProcessMemCheckValgrindOutput(str, log, results); - } - else if ( this->MemoryTesterStyle == cmCTestMemCheckHandler::PURIFY ) - { - return this->ProcessMemCheckPurifyOutput(str, log, results); +std::vector<int>::size_type cmCTestMemCheckHandler::FindOrAddWarning( + const std::string& warning) +{ + for (std::vector<std::string>::size_type i = 0; + i < this->ResultStrings.size(); ++i) { + if (this->ResultStrings[i] == warning) { + return i; } - else if ( this->MemoryTesterStyle == - cmCTestMemCheckHandler::BOUNDS_CHECKER ) - { - return this->ProcessMemCheckBoundsCheckerOutput(str, log, results); + } + this->GlobalResults.push_back(0); // this must stay the same size + this->ResultStrings.push_back(warning); + this->ResultStringsLong.push_back(warning); + return this->ResultStrings.size() - 1; +} +bool cmCTestMemCheckHandler::ProcessMemCheckSanitizerOutput( + const std::string& str, std::string& log, std::vector<int>& result) +{ + std::string regex; + switch (this->MemoryTesterStyle) { + case cmCTestMemCheckHandler::ADDRESS_SANITIZER: + regex = "ERROR: AddressSanitizer: (.*) on.*"; + break; + case cmCTestMemCheckHandler::LEAK_SANITIZER: + // use leakWarning regex + break; + case cmCTestMemCheckHandler::THREAD_SANITIZER: + regex = "WARNING: ThreadSanitizer: (.*) \\(pid=.*\\)"; + break; + case cmCTestMemCheckHandler::MEMORY_SANITIZER: + regex = "WARNING: MemorySanitizer: (.*)"; + break; + case cmCTestMemCheckHandler::UB_SANITIZER: + regex = "runtime error: (.*)"; + break; + default: + break; + } + cmsys::RegularExpression sanitizerWarning(regex); + cmsys::RegularExpression leakWarning("(Direct|Indirect) leak of .*"); + int defects = 0; + std::vector<std::string> lines; + cmSystemTools::Split(str.c_str(), lines); + std::ostringstream ostr; + log = ""; + for (std::vector<std::string>::iterator i = lines.begin(); i != lines.end(); + ++i) { + std::string resultFound; + if (leakWarning.find(*i)) { + resultFound = leakWarning.match(1) + " leak"; + } else if (sanitizerWarning.find(*i)) { + resultFound = sanitizerWarning.match(1); } - else - { - log.append("\nMemory checking style used was: "); - log.append("None that I know"); - log = str; + if (!resultFound.empty()) { + std::vector<int>::size_type idx = this->FindOrAddWarning(resultFound); + if (result.empty() || idx > result.size() - 1) { + result.push_back(1); + } else { + result[idx]++; + } + defects++; + ostr << "<b>" << this->ResultStrings[idx] << "</b> "; } - - - return true; + ostr << *i << std::endl; + } + log = ostr.str(); + this->DefectCount += defects; + return defects == 0; } - -//---------------------------------------------------------------------- bool cmCTestMemCheckHandler::ProcessMemCheckPurifyOutput( - const std::string& str, std::string& log, - int* results) + const std::string& str, std::string& log, std::vector<int>& results) { - std::vector<cmStdString> lines; + std::vector<std::string> lines; cmSystemTools::Split(str.c_str(), lines); - cmOStringStream ostr; + std::ostringstream ostr; log = ""; cmsys::RegularExpression pfW("^\\[[WEI]\\] ([A-Z][A-Z][A-Z][A-Z]*): "); int defects = 0; - for( std::vector<cmStdString>::iterator i = lines.begin(); - i != lines.end(); ++i) - { - int failure = cmCTestMemCheckHandler::NO_MEMORY_FAULT; - if ( pfW.find(*i) ) - { - int cc; - for ( cc = 0; cc < cmCTestMemCheckHandler::NO_MEMORY_FAULT; cc ++ ) - { - if ( pfW.match(1) == cmCTestMemCheckResultStrings[cc] ) - { + for (std::vector<std::string>::iterator i = lines.begin(); i != lines.end(); + ++i) { + std::vector<int>::size_type failure = this->ResultStrings.size(); + if (pfW.find(*i)) { + std::vector<int>::size_type cc; + for (cc = 0; cc < this->ResultStrings.size(); cc++) { + if (pfW.match(1) == this->ResultStrings[cc]) { failure = cc; break; - } } - if ( cc == cmCTestMemCheckHandler::NO_MEMORY_FAULT ) - { + } + if (cc == this->ResultStrings.size()) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown Purify memory fault: " - << pfW.match(1) << std::endl); + << pfW.match(1) << std::endl); ostr << "*** Unknown Purify memory fault: " << pfW.match(1) - << std::endl; - } - } - if ( failure != NO_MEMORY_FAULT ) - { - ostr << "<b>" << cmCTestMemCheckResultStrings[failure] << "</b> "; - results[failure] ++; - defects ++; + << std::endl; } - ostr << cmXMLSafe(*i) << std::endl; } + if (failure != this->ResultStrings.size()) { + ostr << "<b>" << this->ResultStrings[failure] << "</b> "; + results[failure]++; + defects++; + } + ostr << *i << std::endl; + } log = ostr.str(); - if ( defects ) - { - return false; - } - return true; + this->DefectCount += defects; + return defects == 0; } -//---------------------------------------------------------------------- bool cmCTestMemCheckHandler::ProcessMemCheckValgrindOutput( - const std::string& str, std::string& log, - int* results) + const std::string& str, std::string& log, std::vector<int>& results) { - std::vector<cmStdString> lines; + std::vector<std::string> lines; cmSystemTools::Split(str.c_str(), lines); bool unlimitedOutput = false; - if(str.find("CTEST_FULL_OUTPUT") != str.npos || - this->CustomMaximumFailedTestOutputSize == 0) - { + if (str.find("CTEST_FULL_OUTPUT") != std::string::npos || + this->CustomMaximumFailedTestOutputSize == 0) { unlimitedOutput = true; - } + } std::string::size_type cc; - cmOStringStream ostr; + std::ostringstream ostr; log = ""; int defects = 0; @@ -704,7 +821,7 @@ bool cmCTestMemCheckHandler::ProcessMemCheckValgrindOutput( "== .*Mismatched free\\(\\) / delete / delete \\[\\]"); cmsys::RegularExpression vgMLK1( "== .*[0-9,]+ bytes in [0-9,]+ blocks are definitely lost" - " in loss record [0-9,]+ of [0-9,]+"); + " in loss record [0-9,]+ of [0-9,]+"); cmsys::RegularExpression vgMLK2( "== .*[0-9,]+ \\([0-9,]+ direct, [0-9,]+ indirect\\)" " bytes in [0-9,]+ blocks are definitely lost" @@ -723,300 +840,261 @@ bool cmCTestMemCheckHandler::ProcessMemCheckValgrindOutput( "== .*Use of uninitialised value of size [0-9,]+"); cmsys::RegularExpression vgUMR2("== .*Invalid read of size [0-9,]+"); cmsys::RegularExpression vgUMR3("== .*Jump to the invalid address "); - cmsys::RegularExpression vgUMR4("== .*Syscall param .* contains " + cmsys::RegularExpression vgUMR4( + "== .*Syscall param .* contains " "uninitialised or unaddressable byte\\(s\\)"); cmsys::RegularExpression vgUMR5("== .*Syscall param .* uninitialised"); cmsys::RegularExpression vgIPW("== .*Invalid write of size [0-9,]+"); cmsys::RegularExpression vgABR("== .*pthread_mutex_unlock: mutex is " - "locked by a different thread"); + "locked by a different thread"); std::vector<std::string::size_type> nonValGrindOutput; double sttime = cmSystemTools::GetTime(); - cmCTestLog(this->CTest, DEBUG, "Start test: " << lines.size() << std::endl); + cmCTestOptionalLog(this->CTest, DEBUG, + "Start test: " << lines.size() << std::endl, this->Quiet); std::string::size_type totalOutputSize = 0; - bool outputFull = false; - for ( cc = 0; cc < lines.size(); cc ++ ) - { - cmCTestLog(this->CTest, DEBUG, "test line " - << lines[cc] << std::endl); - - if ( valgrindLine.find(lines[cc]) ) - { - cmCTestLog(this->CTest, DEBUG, "valgrind line " - << lines[cc] << std::endl); + for (cc = 0; cc < lines.size(); cc++) { + cmCTestOptionalLog(this->CTest, DEBUG, + "test line " << lines[cc] << std::endl, this->Quiet); + + if (valgrindLine.find(lines[cc])) { + cmCTestOptionalLog(this->CTest, DEBUG, + "valgrind line " << lines[cc] << std::endl, + this->Quiet); int failure = cmCTestMemCheckHandler::NO_MEMORY_FAULT; - if ( vgFIM.find(lines[cc]) ) - { + if (vgFIM.find(lines[cc])) { failure = cmCTestMemCheckHandler::FIM; - } - else if ( vgFMM.find(lines[cc]) ) - { + } else if (vgFMM.find(lines[cc])) { failure = cmCTestMemCheckHandler::FMM; - } - else if ( vgMLK1.find(lines[cc]) ) - { + } else if (vgMLK1.find(lines[cc])) { failure = cmCTestMemCheckHandler::MLK; - } - else if ( vgMLK2.find(lines[cc]) ) - { + } else if (vgMLK2.find(lines[cc])) { failure = cmCTestMemCheckHandler::MLK; - } - else if ( vgPAR.find(lines[cc]) ) - { + } else if (vgPAR.find(lines[cc])) { failure = cmCTestMemCheckHandler::PAR; - } - else if ( vgMPK1.find(lines[cc]) ) - { + } else if (vgMPK1.find(lines[cc])) { failure = cmCTestMemCheckHandler::MPK; - } - else if ( vgMPK2.find(lines[cc]) ) - { + } else if (vgMPK2.find(lines[cc])) { failure = cmCTestMemCheckHandler::MPK; - } - else if ( vgUMC.find(lines[cc]) ) - { + } else if (vgUMC.find(lines[cc])) { failure = cmCTestMemCheckHandler::UMC; - } - else if ( vgUMR1.find(lines[cc]) ) - { + } else if (vgUMR1.find(lines[cc])) { failure = cmCTestMemCheckHandler::UMR; - } - else if ( vgUMR2.find(lines[cc]) ) - { + } else if (vgUMR2.find(lines[cc])) { failure = cmCTestMemCheckHandler::UMR; - } - else if ( vgUMR3.find(lines[cc]) ) - { + } else if (vgUMR3.find(lines[cc])) { failure = cmCTestMemCheckHandler::UMR; - } - else if ( vgUMR4.find(lines[cc]) ) - { + } else if (vgUMR4.find(lines[cc])) { failure = cmCTestMemCheckHandler::UMR; - } - else if ( vgUMR5.find(lines[cc]) ) - { + } else if (vgUMR5.find(lines[cc])) { failure = cmCTestMemCheckHandler::UMR; - } - else if ( vgIPW.find(lines[cc]) ) - { + } else if (vgIPW.find(lines[cc])) { failure = cmCTestMemCheckHandler::IPW; - } - else if ( vgABR.find(lines[cc]) ) - { + } else if (vgABR.find(lines[cc])) { failure = cmCTestMemCheckHandler::ABR; - } + } - if ( failure != cmCTestMemCheckHandler::NO_MEMORY_FAULT ) - { - ostr << "<b>" << cmCTestMemCheckResultStrings[failure] << "</b> "; - results[failure] ++; - defects ++; - } - totalOutputSize += lines[cc].size(); - ostr << cmXMLSafe(lines[cc]) << std::endl; + if (failure != cmCTestMemCheckHandler::NO_MEMORY_FAULT) { + ostr << "<b>" << this->ResultStrings[failure] << "</b> "; + results[failure]++; + defects++; } - else - { + totalOutputSize += lines[cc].size(); + ostr << lines[cc] << std::endl; + } else { nonValGrindOutput.push_back(cc); - } } + } // Now put all all the non valgrind output into the test output - if(!outputFull) - { - for(std::vector<std::string::size_type>::iterator i = - nonValGrindOutput.begin(); i != nonValGrindOutput.end(); ++i) - { - totalOutputSize += lines[*i].size(); - cmCTestLog(this->CTest, DEBUG, "before xml safe " - << lines[*i] << std::endl); - cmCTestLog(this->CTest, DEBUG, "after xml safe " - << cmXMLSafe(lines[*i]) << std::endl); - - ostr << cmXMLSafe(lines[*i]) << std::endl; - if(!unlimitedOutput && totalOutputSize > - static_cast<size_t>(this->CustomMaximumFailedTestOutputSize)) - { - outputFull = true; - ostr << "....\n"; - ostr << "Test Output for this test has been truncated see testing" - " machine logs for full output,\n"; - ostr << "or put CTEST_FULL_OUTPUT in the output of " - "this test program.\n"; - } - } + // This should be last in case it gets truncated by the output + // limiting code + for (std::vector<std::string::size_type>::iterator i = + nonValGrindOutput.begin(); + i != nonValGrindOutput.end(); ++i) { + totalOutputSize += lines[*i].size(); + ostr << lines[*i] << std::endl; + if (!unlimitedOutput && + totalOutputSize > + static_cast<size_t>(this->CustomMaximumFailedTestOutputSize)) { + ostr << "....\n"; + ostr << "Test Output for this test has been truncated see testing" + " machine logs for full output,\n"; + ostr << "or put CTEST_FULL_OUTPUT in the output of " + "this test program.\n"; + break; // stop the copy of output if we are full } - cmCTestLog(this->CTest, DEBUG, "End test (elapsed: " - << (cmSystemTools::GetTime() - sttime) << std::endl); + } + cmCTestOptionalLog(this->CTest, DEBUG, "End test (elapsed: " + << (cmSystemTools::GetTime() - sttime) << std::endl, + this->Quiet); log = ostr.str(); - if ( defects ) - { - return false; - } - return true; + this->DefectCount += defects; + return defects == 0; } - - -//---------------------------------------------------------------------- bool cmCTestMemCheckHandler::ProcessMemCheckBoundsCheckerOutput( - const std::string& str, std::string& log, - int* results) + const std::string& str, std::string& log, std::vector<int>& results) { log = ""; double sttime = cmSystemTools::GetTime(); - std::vector<cmStdString> lines; + std::vector<std::string> lines; cmSystemTools::Split(str.c_str(), lines); - cmCTestLog(this->CTest, DEBUG, "Start test: " << lines.size() << std::endl); - std::vector<cmStdString>::size_type cc; - for ( cc = 0; cc < lines.size(); cc ++ ) - { - if(lines[cc] == BOUNDS_CHECKER_MARKER) - { + cmCTestOptionalLog(this->CTest, DEBUG, + "Start test: " << lines.size() << std::endl, this->Quiet); + std::vector<std::string>::size_type cc; + for (cc = 0; cc < lines.size(); cc++) { + if (lines[cc] == BOUNDS_CHECKER_MARKER) { break; - } } + } cmBoundsCheckerParser parser(this->CTest); parser.InitializeParser(); - if(cc < lines.size()) - { - for(cc++; cc < lines.size(); ++cc) - { + if (cc < lines.size()) { + for (cc++; cc < lines.size(); ++cc) { std::string& theLine = lines[cc]; // check for command line arguments that are not escaped // correctly by BC - if(theLine.find("TargetArgs=") != theLine.npos) - { + if (theLine.find("TargetArgs=") != std::string::npos) { // skip this because BC gets it wrong and we can't parse it - } - else if(!parser.ParseChunk(theLine.c_str(), theLine.size())) - { + } else if (!parser.ParseChunk(theLine.c_str(), theLine.size())) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Error in ParseChunk: " << theLine.c_str() - << std::endl); - } + "Error in ParseChunk: " << theLine << std::endl); } } + } int defects = 0; - for(cc =0; cc < parser.Errors.size(); ++cc) - { + for (cc = 0; cc < parser.Errors.size(); ++cc) { results[parser.Errors[cc]]++; defects++; - } - cmCTestLog(this->CTest, DEBUG, "End test (elapsed: " - << (cmSystemTools::GetTime() - sttime) << std::endl); - if(defects) - { + } + cmCTestOptionalLog(this->CTest, DEBUG, "End test (elapsed: " + << (cmSystemTools::GetTime() - sttime) << std::endl, + this->Quiet); + if (defects) { // only put the output of Bounds Checker if there were // errors or leaks detected log = parser.Log; - return false; + } + this->DefectCount += defects; + return defects == 0; +} + +// PostProcessTest memcheck results +void cmCTestMemCheckHandler::PostProcessTest(cmCTestTestResult& res, int test) +{ + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "PostProcessTest memcheck results for : " << res.Name + << std::endl, + this->Quiet); + if (this->MemoryTesterStyle == cmCTestMemCheckHandler::BOUNDS_CHECKER) { + this->PostProcessBoundsCheckerTest(res, test); + } else { + std::vector<std::string> files; + this->TestOutputFileNames(test, files); + for (std::vector<std::string>::iterator i = files.begin(); + i != files.end(); ++i) { + this->AppendMemTesterOutput(res, *i); } - return true; + } } // This method puts the bounds checker output file into the output // for the test -void -cmCTestMemCheckHandler::PostProcessBoundsCheckerTest(cmCTestTestResult& res, - int test) +void cmCTestMemCheckHandler::PostProcessBoundsCheckerTest( + cmCTestTestResult& res, int test) { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "PostProcessBoundsCheckerTest for : " - << res.Name.c_str() << std::endl); - cmStdString ofile = testOutputFileName(test); - if ( ofile.empty() ) - { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "PostProcessBoundsCheckerTest for : " << res.Name + << std::endl, + this->Quiet); + std::vector<std::string> files; + this->TestOutputFileNames(test, files); + if (files.empty()) { return; - } + } + std::string ofile = files[0]; + if (ofile.empty()) { + return; + } // put a scope around this to close ifs so the file can be removed { - std::ifstream ifs(ofile.c_str()); - if ( !ifs ) - { - std::string log = "Cannot read memory tester output file: " + ofile; - cmCTestLog(this->CTest, ERROR_MESSAGE, log.c_str() << std::endl); - return; + cmsys::ifstream ifs(ofile.c_str()); + if (!ifs) { + std::string log = "Cannot read memory tester output file: " + ofile; + cmCTestLog(this->CTest, ERROR_MESSAGE, log << std::endl); + return; } - res.Output += BOUNDS_CHECKER_MARKER; - res.Output += "\n"; - std::string line; - while ( cmSystemTools::GetLineFromStream(ifs, line) ) - { - res.Output += line; + res.Output += BOUNDS_CHECKER_MARKER; res.Output += "\n"; + std::string line; + while (cmSystemTools::GetLineFromStream(ifs, line)) { + res.Output += line; + res.Output += "\n"; } } cmSystemTools::Delay(1000); - cmSystemTools::RemoveFile(this->BoundsCheckerDPBDFile.c_str()); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Remove: " - << this->BoundsCheckerDPBDFile.c_str() << std::endl); - cmSystemTools::RemoveFile(this->BoundsCheckerXMLFile.c_str()); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Remove: " - << this->BoundsCheckerXMLFile.c_str() << std::endl); + cmSystemTools::RemoveFile(this->BoundsCheckerDPBDFile); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Remove: " << this->BoundsCheckerDPBDFile << std::endl, + this->Quiet); + cmSystemTools::RemoveFile(this->BoundsCheckerXMLFile); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Remove: " << this->BoundsCheckerXMLFile << std::endl, + this->Quiet); } -void -cmCTestMemCheckHandler::PostProcessPurifyTest(cmCTestTestResult& res, - int test) +void cmCTestMemCheckHandler::AppendMemTesterOutput(cmCTestTestResult& res, + std::string const& ofile) { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "PostProcessPurifyTest for : " - << res.Name.c_str() << std::endl); - appendMemTesterOutput(res, test); -} - -void -cmCTestMemCheckHandler::PostProcessValgrindTest(cmCTestTestResult& res, - int test) -{ - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "PostProcessValgrindTest for : " - << res.Name.c_str() << std::endl); - appendMemTesterOutput(res, test); -} - -void -cmCTestMemCheckHandler::appendMemTesterOutput(cmCTestTestResult& res, - int test) -{ - cmStdString ofile = testOutputFileName(test); - - if ( ofile.empty() ) - { - return; - } - std::ifstream ifs(ofile.c_str()); - if ( !ifs ) - { - std::string log = "Cannot read memory tester output file: " + ofile; - cmCTestLog(this->CTest, ERROR_MESSAGE, log.c_str() << std::endl); + if (ofile.empty()) { return; + } + // put ifs in scope so file can be deleted if needed + { + cmsys::ifstream ifs(ofile.c_str()); + if (!ifs) { + std::string log = "Cannot read memory tester output file: " + ofile; + cmCTestLog(this->CTest, ERROR_MESSAGE, log << std::endl); + return; } - std::string line; - while ( cmSystemTools::GetLineFromStream(ifs, line) ) - { - res.Output += line; - res.Output += "\n"; + std::string line; + while (cmSystemTools::GetLineFromStream(ifs, line)) { + res.Output += line; + res.Output += "\n"; } + } + if (this->LogWithPID) { + cmSystemTools::RemoveFile(ofile); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Remove: " << ofile << "\n", this->Quiet); + } } -cmStdString -cmCTestMemCheckHandler::testOutputFileName(int test) +void cmCTestMemCheckHandler::TestOutputFileNames( + int test, std::vector<std::string>& files) { - cmStdString index; - cmOStringStream stream; + std::string index; + std::ostringstream stream; stream << test; index = stream.str(); - cmStdString ofile = this->MemoryTesterOutputFile; - cmStdString::size_type pos = ofile.find("??"); + std::string ofile = this->MemoryTesterOutputFile; + std::string::size_type pos = ofile.find("??"); ofile.replace(pos, 2, index); - - if ( !cmSystemTools::FileExists(ofile.c_str()) ) - { - std::string log = "Cannot find memory tester output file: " - + ofile; - cmCTestLog(this->CTest, ERROR_MESSAGE, log.c_str() << std::endl); - ofile = ""; + if (this->LogWithPID) { + ofile += ".*"; + cmsys::Glob g; + g.FindFiles(ofile); + if (g.GetFiles().empty()) { + std::string log = "Cannot find memory tester output file: " + ofile; + cmCTestLog(this->CTest, ERROR_MESSAGE, log << std::endl); + ofile = ""; + } else { + files = g.GetFiles(); + return; } - - return ofile; + } else if (!cmSystemTools::FileExists(ofile.c_str())) { + std::string log = "Cannot find memory tester output file: " + ofile; + cmCTestLog(this->CTest, ERROR_MESSAGE, log << std::endl); + ofile = ""; + } + files.push_back(ofile); } diff --git a/Source/CTest/cmCTestMemCheckHandler.h b/Source/CTest/cmCTestMemCheckHandler.h index 040d2e092..333c2e2dd 100644 --- a/Source/CTest/cmCTestMemCheckHandler.h +++ b/Source/CTest/cmCTestMemCheckHandler.h @@ -1,23 +1,17 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestMemCheckHandler_h #define cmCTestMemCheckHandler_h +#include "cmConfigure.h" #include "cmCTestTestHandler.h" -#include "cmListFileCache.h" + +#include <string> +#include <vector> class cmMakefile; +class cmXMLWriter; /** \class cmCTestMemCheckHandler * \brief A class that handles ctest -S invocations @@ -26,29 +20,42 @@ class cmMakefile; class cmCTestMemCheckHandler : public cmCTestTestHandler { friend class cmCTestRunTest; + public: - cmTypeMacro(cmCTestMemCheckHandler, cmCTestTestHandler); + typedef cmCTestTestHandler Superclass; - void PopulateCustomVectors(cmMakefile *mf); + void PopulateCustomVectors(cmMakefile* mf) CM_OVERRIDE; cmCTestMemCheckHandler(); - void Initialize(); + void Initialize() CM_OVERRIDE; + + int GetDefectCount(); + protected: - virtual int PreProcessHandler(); - virtual int PostProcessHandler(); - virtual void GenerateTestCommand(std::vector<std::string>& args, int test); + int PreProcessHandler() CM_OVERRIDE; + int PostProcessHandler() CM_OVERRIDE; + void GenerateTestCommand(std::vector<std::string>& args, + int test) CM_OVERRIDE; private: - - enum { // Memory checkers + enum + { // Memory checkers UNKNOWN = 0, VALGRIND, PURIFY, - BOUNDS_CHECKER + BOUNDS_CHECKER, + // checkers after here do not use the standard error list + ADDRESS_SANITIZER, + LEAK_SANITIZER, + THREAD_SANITIZER, + MEMORY_SANITIZER, + UB_SANITIZER }; + public: - enum { // Memory faults + enum + { // Memory faults ABR = 0, ABW, ABWL, @@ -73,8 +80,10 @@ public: UMR, NO_MEMORY_FAULT }; + private: - enum { // Program statuses + enum + { // Program statuses NOT_RUN = 0, TIMEOUT, SEGFAULT, @@ -86,14 +95,25 @@ private: BAD_COMMAND, COMPLETED }; - std::string BoundsCheckerDPBDFile; - std::string BoundsCheckerXMLFile; - std::string MemoryTester; - std::vector<cmStdString> MemoryTesterDynamicOptions; - std::vector<cmStdString> MemoryTesterOptions; - int MemoryTesterStyle; - std::string MemoryTesterOutputFile; - int MemoryTesterGlobalResults[NO_MEMORY_FAULT]; + std::string BoundsCheckerDPBDFile; + std::string BoundsCheckerXMLFile; + std::string MemoryTester; + std::vector<std::string> MemoryTesterDynamicOptions; + std::vector<std::string> MemoryTesterOptions; + int MemoryTesterStyle; + std::string MemoryTesterOutputFile; + std::string MemoryTesterEnvironmentVariable; + // these are used to store the types of errors that can show up + std::vector<std::string> ResultStrings; + std::vector<std::string> ResultStringsLong; + std::vector<int> GlobalResults; + bool LogWithPID; // does log file add pid + int DefectCount; + + std::vector<int>::size_type FindOrAddWarning(const std::string& warning); + // initialize the ResultStrings and ResultStringsLong for + // this type of checker + void InitializeResultsVectors(); ///! Initialize memory checking subsystem. bool InitializeMemoryChecking(); @@ -101,34 +121,35 @@ private: /** * Generate the Dart compatible output */ - void GenerateDartOutput(std::ostream& os); + void GenerateDartOutput(cmXMLWriter& xml) CM_OVERRIDE; - std::vector<cmStdString> CustomPreMemCheck; - std::vector<cmStdString> CustomPostMemCheck; + std::vector<std::string> CustomPreMemCheck; + std::vector<std::string> CustomPostMemCheck; //! Parse Valgrind/Purify/Bounds Checker result out of the output - //string. After running, log holds the output and results hold the - //different memmory errors. - bool ProcessMemCheckOutput(const std::string& str, - std::string& log, int* results); - bool ProcessMemCheckValgrindOutput(const std::string& str, - std::string& log, int* results); - bool ProcessMemCheckPurifyOutput(const std::string& str, - std::string& log, int* results); + // string. After running, log holds the output and results hold the + // different memmory errors. + bool ProcessMemCheckOutput(const std::string& str, std::string& log, + std::vector<int>& results); + bool ProcessMemCheckValgrindOutput(const std::string& str, std::string& log, + std::vector<int>& results); + bool ProcessMemCheckPurifyOutput(const std::string& str, std::string& log, + std::vector<int>& results); + bool ProcessMemCheckSanitizerOutput(const std::string& str, std::string& log, + std::vector<int>& results); bool ProcessMemCheckBoundsCheckerOutput(const std::string& str, - std::string& log, int* results); + std::string& log, + std::vector<int>& results); - void PostProcessPurifyTest(cmCTestTestResult& res, int test); + void PostProcessTest(cmCTestTestResult& res, int test); void PostProcessBoundsCheckerTest(cmCTestTestResult& res, int test); - void PostProcessValgrindTest(cmCTestTestResult& res, int test); ///! append MemoryTesterOutputFile to the test log - void appendMemTesterOutput(cmCTestTestHandler::cmCTestTestResult& res, - int test); + void AppendMemTesterOutput(cmCTestTestHandler::cmCTestTestResult& res, + std::string const& filename); ///! generate the output filename for the given test index - cmStdString testOutputFileName(int test); + void TestOutputFileNames(int test, std::vector<std::string>& files); }; #endif - diff --git a/Source/CTest/cmCTestMultiProcessHandler.cxx b/Source/CTest/cmCTestMultiProcessHandler.cxx index 76ddeea01..2c16a0d1e 100644 --- a/Source/CTest/cmCTestMultiProcessHandler.cxx +++ b/Source/CTest/cmCTestMultiProcessHandler.cxx @@ -1,35 +1,41 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestMultiProcessHandler.h" -#include "cmProcess.h" -#include "cmStandardIncludes.h" + #include "cmCTest.h" +#include "cmCTestRunTest.h" +#include "cmCTestScriptHandler.h" +#include "cmCTestTestHandler.h" #include "cmSystemTools.h" -#include <stdlib.h> +#include "cmWorkingDirectory.h" + +#include "cmsys/FStream.hxx" +#include "cmsys/String.hxx" +#include "cmsys/SystemInformation.hxx" +#include <algorithm> +#include <iomanip> +#include <list> +#include <math.h> +#include <sstream> #include <stack> -#include <float.h> +#include <stdlib.h> +#include <utility> class TestComparator { public: - TestComparator(cmCTestMultiProcessHandler* handler) : Handler(handler) {} + TestComparator(cmCTestMultiProcessHandler* handler) + : Handler(handler) + { + } ~TestComparator() {} // Sorts tests in descending order of cost - bool operator() (int index1, int index2) const - { + bool operator()(int index1, int index2) const + { return Handler->Properties[index1]->Cost > Handler->Properties[index2]->Cost; - } + } private: cmCTestMultiProcessHandler* Handler; @@ -38,136 +44,155 @@ private: cmCTestMultiProcessHandler::cmCTestMultiProcessHandler() { this->ParallelLevel = 1; + this->TestLoad = 0; this->Completed = 0; this->RunningCount = 0; this->StopTimePassed = false; + this->HasCycles = false; + this->SerialTestRunning = false; } cmCTestMultiProcessHandler::~cmCTestMultiProcessHandler() { } - // Set the tests -void -cmCTestMultiProcessHandler::SetTests(TestMap& tests, - PropertiesMap& properties) +// Set the tests +void cmCTestMultiProcessHandler::SetTests(TestMap& tests, + PropertiesMap& properties) { this->Tests = tests; this->Properties = properties; this->Total = this->Tests.size(); // set test run map to false for all - for(TestMap::iterator i = this->Tests.begin(); - i != this->Tests.end(); ++i) - { + for (TestMap::iterator i = this->Tests.begin(); i != this->Tests.end(); + ++i) { this->TestRunningMap[i->first] = false; this->TestFinishMap[i->first] = false; - } - if(!this->CTest->GetShowOnly()) - { + } + if (!this->CTest->GetShowOnly()) { this->ReadCostData(); - this->CreateTestCostList(); + this->HasCycles = !this->CheckCycles(); + if (this->HasCycles) { + return; } + this->CreateTestCostList(); + } } - // Set the max number of tests that can be run at the same time. +// Set the max number of tests that can be run at the same time. void cmCTestMultiProcessHandler::SetParallelLevel(size_t level) { this->ParallelLevel = level < 1 ? 1 : level; } -//--------------------------------------------------------- +void cmCTestMultiProcessHandler::SetTestLoad(unsigned long load) +{ + this->TestLoad = load; +} + void cmCTestMultiProcessHandler::RunTests() { this->CheckResume(); - if(!this->CheckCycles()) - { + if (this->HasCycles) { return; - } + } this->TestHandler->SetMaxIndex(this->FindMaxIndex()); this->StartNextTests(); - while(this->Tests.size() != 0) - { - if(this->StopTimePassed) - { + while (!this->Tests.empty()) { + if (this->StopTimePassed) { return; - } + } this->CheckOutput(); this->StartNextTests(); - } + } // let all running tests finish - while(this->CheckOutput()) - { - } + while (this->CheckOutput()) { + } this->MarkFinished(); this->UpdateCostData(); } -//--------------------------------------------------------- void cmCTestMultiProcessHandler::StartTestProcess(int test) { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "test " << test << "\n"); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "test " << test << "\n", this->Quiet); this->TestRunningMap[test] = true; // mark the test as running // now remove the test itself this->EraseTest(test); this->RunningCount += GetProcessorsUsed(test); cmCTestRunTest* testRun = new cmCTestRunTest(this->TestHandler); + if (this->CTest->GetRepeatUntilFail()) { + testRun->SetRunUntilFailOn(); + testRun->SetNumberOfRuns(this->CTest->GetTestRepeat()); + } testRun->SetIndex(test); testRun->SetTestProperties(this->Properties[test]); - std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory(); - cmSystemTools::ChangeDirectory(this->Properties[test]->Directory.c_str()); + // Find any failed dependencies for this test. We assume the more common + // scenario has no failed tests, so make it the outer loop. + for (std::vector<std::string>::const_iterator it = this->Failed->begin(); + it != this->Failed->end(); ++it) { + if (this->Properties[test]->RequireSuccessDepends.find(*it) != + this->Properties[test]->RequireSuccessDepends.end()) { + testRun->AddFailedDependency(*it); + } + } + + cmWorkingDirectory workdir(this->Properties[test]->Directory); // Lock the resources we'll be using this->LockResources(test); - if(testRun->StartTest(this->Total)) - { + if (testRun->StartTest(this->Total)) { this->RunningTests.insert(testRun); - } - else if(testRun->IsStopTimePassed()) - { + } else if (testRun->IsStopTimePassed()) { this->StopTimePassed = true; delete testRun; return; + } else { + + for (TestMap::iterator j = this->Tests.begin(); j != this->Tests.end(); + ++j) { + j->second.erase(test); } - else - { + this->UnlockResources(test); this->Completed++; this->TestFinishMap[test] = true; this->TestRunningMap[test] = false; this->RunningCount -= GetProcessorsUsed(test); testRun->EndTest(this->Completed, this->Total, false); - this->Failed->push_back(this->Properties[test]->Name); - delete testRun; + if (!this->Properties[test]->Disabled) { + this->Failed->push_back(this->Properties[test]->Name); } - cmSystemTools::ChangeDirectory(current_dir.c_str()); + delete testRun; + } } -//--------------------------------------------------------- void cmCTestMultiProcessHandler::LockResources(int index) { - for(std::set<std::string>::iterator i = - this->Properties[index]->LockedResources.begin(); - i != this->Properties[index]->LockedResources.end(); ++i) - { - this->LockedResources.insert(*i); - } + this->LockedResources.insert( + this->Properties[index]->LockedResources.begin(), + this->Properties[index]->LockedResources.end()); + + if (this->Properties[index]->RunSerial) { + this->SerialTestRunning = true; + } } -//--------------------------------------------------------- void cmCTestMultiProcessHandler::UnlockResources(int index) { - for(std::set<std::string>::iterator i = - this->Properties[index]->LockedResources.begin(); - i != this->Properties[index]->LockedResources.end(); ++i) - { + for (std::set<std::string>::iterator i = + this->Properties[index]->LockedResources.begin(); + i != this->Properties[index]->LockedResources.end(); ++i) { this->LockedResources.erase(*i); - } + } + if (this->Properties[index]->RunSerial) { + this->SerialTestRunning = false; + } } -//--------------------------------------------------------- void cmCTestMultiProcessHandler::EraseTest(int test) { this->Tests.erase(test); @@ -175,157 +200,202 @@ void cmCTestMultiProcessHandler::EraseTest(int test) std::find(this->SortedTests.begin(), this->SortedTests.end(), test)); } -//--------------------------------------------------------- inline size_t cmCTestMultiProcessHandler::GetProcessorsUsed(int test) { - size_t processors = - static_cast<int>(this->Properties[test]->Processors); - //If this is set to run serially, it must run alone. - //Also, if processors setting is set higher than the -j - //setting, we default to using all of the process slots. - if(this->Properties[test]->RunSerial - || processors > this->ParallelLevel) - { + size_t processors = static_cast<int>(this->Properties[test]->Processors); + // If processors setting is set higher than the -j + // setting, we default to using all of the process slots. + if (processors > this->ParallelLevel) { processors = this->ParallelLevel; - } + } return processors; } -//--------------------------------------------------------- +std::string cmCTestMultiProcessHandler::GetName(int test) +{ + return this->Properties[test]->Name; +} + bool cmCTestMultiProcessHandler::StartTest(int test) { - //Check for locked resources - for(std::set<std::string>::iterator i = - this->Properties[test]->LockedResources.begin(); - i != this->Properties[test]->LockedResources.end(); ++i) - { - if(this->LockedResources.find(*i) != this->LockedResources.end()) - { + // Check for locked resources + for (std::set<std::string>::iterator i = + this->Properties[test]->LockedResources.begin(); + i != this->Properties[test]->LockedResources.end(); ++i) { + if (this->LockedResources.find(*i) != this->LockedResources.end()) { return false; - } } + } - // copy the depend tests locally because when - // a test is finished it will be removed from the depend list - // and we don't want to be iterating a list while removing from it - TestSet depends = this->Tests[test]; - size_t totalDepends = depends.size(); - if(totalDepends) - { - for(TestSet::const_iterator i = depends.begin(); - i != depends.end(); ++i) - { - // if the test is not already running then start it - if(!this->TestRunningMap[*i]) - { - // this test might be finished, but since - // this is a copy of the depend map we might - // still have it - if(!this->TestFinishMap[*i]) - { - // only start one test in this function - return this->StartTest(*i); - } - else - { - // the depend has been and finished - totalDepends--; - } - } - } - } // if there are no depends left then run this test - if(totalDepends == 0) - { + if (this->Tests[test].empty()) { this->StartTestProcess(test); return true; - } + } // This test was not able to start because it is waiting // on depends to run return false; } -//--------------------------------------------------------- void cmCTestMultiProcessHandler::StartNextTests() { size_t numToStart = 0; - if(this->RunningCount < this->ParallelLevel) - { + if (this->RunningCount < this->ParallelLevel) { numToStart = this->ParallelLevel - this->RunningCount; - } + } - if(numToStart == 0) - { + if (numToStart == 0) { + return; + } + + // Don't start any new tests if one with the RUN_SERIAL property + // is already running. + if (this->SerialTestRunning) { return; + } + + bool allTestsFailedTestLoadCheck = false; + bool usedFakeLoadForTesting = false; + size_t minProcessorsRequired = this->ParallelLevel; + std::string testWithMinProcessors; + + cmsys::SystemInformation info; + + unsigned long systemLoad = 0; + size_t spareLoad = 0; + if (this->TestLoad > 0) { + // Activate possible wait. + allTestsFailedTestLoadCheck = true; + + // Check for a fake load average value used in testing. + std::string fake_load_value; + if (cmSystemTools::GetEnv("__CTEST_FAKE_LOAD_AVERAGE_FOR_TESTING", + fake_load_value)) { + usedFakeLoadForTesting = true; + if (!cmSystemTools::StringToULong(fake_load_value.c_str(), + &systemLoad)) { + cmSystemTools::Error("Failed to parse fake load value: ", + fake_load_value.c_str()); + } + } + // If it's not set, look up the true load average. + else { + systemLoad = static_cast<unsigned long>(ceil(info.GetLoadAverage())); } + spareLoad = + (this->TestLoad > systemLoad ? this->TestLoad - systemLoad : 0); + + // Don't start more tests than the spare load can support. + if (numToStart > spareLoad) { + numToStart = spareLoad; + } + } TestList copy = this->SortedTests; - for(TestList::iterator test = copy.begin(); test != copy.end(); ++test) - { - //in case this test has already been started due to dependency - if(this->TestRunningMap[*test] || this->TestFinishMap[*test]) - { + for (TestList::iterator test = copy.begin(); test != copy.end(); ++test) { + // Take a nap if we're currently performing a RUN_SERIAL test. + if (this->SerialTestRunning) { + break; + } + // We can only start a RUN_SERIAL test if no other tests are also running. + if (this->Properties[*test]->RunSerial && this->RunningCount > 0) { continue; - } + } + size_t processors = GetProcessorsUsed(*test); - if(processors > numToStart) - { - return; + bool testLoadOk = true; + if (this->TestLoad > 0) { + if (processors <= spareLoad) { + cmCTestLog(this->CTest, DEBUG, "OK to run " + << GetName(*test) << ", it requires " << processors + << " procs & system load is: " << systemLoad + << std::endl); + allTestsFailedTestLoadCheck = false; + } else { + testLoadOk = false; } - if(this->StartTest(*test)) - { - if(this->StopTimePassed) - { + } + + if (processors <= minProcessorsRequired) { + minProcessorsRequired = processors; + testWithMinProcessors = GetName(*test); + } + + if (testLoadOk && processors <= numToStart && this->StartTest(*test)) { + if (this->StopTimePassed) { return; - } - numToStart -= processors; } - if(numToStart == 0) - { - return; - } - } + + numToStart -= processors; + } else if (numToStart == 0) { + break; + } + } + + if (allTestsFailedTestLoadCheck) { + cmCTestLog(this->CTest, HANDLER_OUTPUT, "***** WAITING, "); + if (this->SerialTestRunning) { + cmCTestLog(this->CTest, HANDLER_OUTPUT, + "Waiting for RUN_SERIAL test to finish."); + } else { + /* clang-format off */ + cmCTestLog(this->CTest, HANDLER_OUTPUT, + "System Load: " << systemLoad << ", " + "Max Allowed Load: " << this->TestLoad << ", " + "Smallest test " << testWithMinProcessors << + " requires " << minProcessorsRequired); + /* clang-format on */ + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, "*****" << std::endl); + + if (usedFakeLoadForTesting) { + // Break out of the infinite loop of waiting for our fake load + // to come down. + this->StopTimePassed = true; + } else { + // Wait between 1 and 5 seconds before trying again. + cmCTestScriptHandler::SleepInSeconds(cmSystemTools::RandomSeed() % 5 + + 1); + } + } } -//--------------------------------------------------------- bool cmCTestMultiProcessHandler::CheckOutput() { // no more output we are done - if(this->RunningTests.size() == 0) - { + if (this->RunningTests.empty()) { return false; - } + } std::vector<cmCTestRunTest*> finished; std::string out, err; - for(std::set<cmCTestRunTest*>::const_iterator i = this->RunningTests.begin(); - i != this->RunningTests.end(); ++i) - { + for (std::set<cmCTestRunTest*>::const_iterator i = + this->RunningTests.begin(); + i != this->RunningTests.end(); ++i) { cmCTestRunTest* p = *i; - if(!p->CheckOutput()) - { + if (!p->CheckOutput()) { finished.push_back(p); - } } - for( std::vector<cmCTestRunTest*>::iterator i = finished.begin(); - i != finished.end(); ++i) - { + } + for (std::vector<cmCTestRunTest*>::iterator i = finished.begin(); + i != finished.end(); ++i) { this->Completed++; cmCTestRunTest* p = *i; int test = p->GetIndex(); - if(p->EndTest(this->Completed, this->Total, true)) - { + bool testResult = p->EndTest(this->Completed, this->Total, true); + if (p->StartAgain()) { + this->Completed--; // remove the completed test because run again + continue; + } + if (testResult) { this->Passed->push_back(p->GetTestProperties()->Name); - } - else - { + } else { this->Failed->push_back(p->GetTestProperties()->Name); - } - for(TestMap::iterator j = this->Tests.begin(); - j != this->Tests.end(); ++j) - { + } + for (TestMap::iterator j = this->Tests.begin(); j != this->Tests.end(); + ++j) { j->second.erase(test); - } + } this->TestFinishMap[test] = true; this->TestRunningMap[test] = false; this->RunningTests.erase(p); @@ -333,300 +403,385 @@ bool cmCTestMultiProcessHandler::CheckOutput() this->UnlockResources(test); this->RunningCount -= GetProcessorsUsed(test); delete p; - } + } return true; } -//--------------------------------------------------------- void cmCTestMultiProcessHandler::UpdateCostData() { std::string fname = this->CTest->GetCostDataFile(); std::string tmpout = fname + ".tmp"; - std::fstream fout; - fout.open(tmpout.c_str(), std::ios::out); + cmsys::ofstream fout; + fout.open(tmpout.c_str()); PropertiesMap temp = this->Properties; - if(cmSystemTools::FileExists(fname.c_str())) - { - std::ifstream fin; + if (cmSystemTools::FileExists(fname.c_str())) { + cmsys::ifstream fin; fin.open(fname.c_str()); std::string line; - while(std::getline(fin, line)) - { - if(line == "---") break; - std::vector<cmsys::String> parts = - cmSystemTools::SplitString(line.c_str(), ' '); - //Format: <name> <previous_runs> <avg_cost> - if(parts.size() < 3) break; + while (std::getline(fin, line)) { + if (line == "---") { + break; + } + std::vector<cmsys::String> parts = cmSystemTools::SplitString(line, ' '); + // Format: <name> <previous_runs> <avg_cost> + if (parts.size() < 3) { + break; + } std::string name = parts[0]; int prev = atoi(parts[1].c_str()); float cost = static_cast<float>(atof(parts[2].c_str())); int index = this->SearchByName(name); - if(index == -1) - { + if (index == -1) { // This test is not in memory. We just rewrite the entry fout << name << " " << prev << " " << cost << "\n"; - } - else - { + } else { // Update with our new average cost fout << name << " " << this->Properties[index]->PreviousRuns << " " - << this->Properties[index]->Cost << "\n"; + << this->Properties[index]->Cost << "\n"; temp.erase(index); - } } - fin.close(); - cmSystemTools::RemoveFile(fname.c_str()); } + fin.close(); + cmSystemTools::RemoveFile(fname); + } // Add all tests not previously listed in the file - for(PropertiesMap::iterator i = temp.begin(); i != temp.end(); ++i) - { + for (PropertiesMap::iterator i = temp.begin(); i != temp.end(); ++i) { fout << i->second->Name << " " << i->second->PreviousRuns << " " - << i->second->Cost << "\n"; - } + << i->second->Cost << "\n"; + } // Write list of failed tests fout << "---\n"; - for(std::vector<cmStdString>::iterator i = this->Failed->begin(); - i != this->Failed->end(); ++i) - { - fout << i->c_str() << "\n"; - } + for (std::vector<std::string>::iterator i = this->Failed->begin(); + i != this->Failed->end(); ++i) { + fout << *i << "\n"; + } fout.close(); cmSystemTools::RenameFile(tmpout.c_str(), fname.c_str()); } -//--------------------------------------------------------- void cmCTestMultiProcessHandler::ReadCostData() { std::string fname = this->CTest->GetCostDataFile(); - if(cmSystemTools::FileExists(fname.c_str(), true)) - { - std::ifstream fin; + if (cmSystemTools::FileExists(fname.c_str(), true)) { + cmsys::ifstream fin; fin.open(fname.c_str()); std::string line; - while(std::getline(fin, line)) - { - if(line == "---") break; + while (std::getline(fin, line)) { + if (line == "---") { + break; + } - std::vector<cmsys::String> parts = - cmSystemTools::SplitString(line.c_str(), ' '); + std::vector<cmsys::String> parts = cmSystemTools::SplitString(line, ' '); // Probably an older version of the file, will be fixed next run - if(parts.size() < 3) - { + if (parts.size() < 3) { fin.close(); return; - } + } std::string name = parts[0]; int prev = atoi(parts[1].c_str()); float cost = static_cast<float>(atof(parts[2].c_str())); int index = this->SearchByName(name); - if(index == -1) continue; + if (index == -1) { + continue; + } this->Properties[index]->PreviousRuns = prev; // When not running in parallel mode, don't use cost data - if(this->ParallelLevel > 1 && - this->Properties[index] && - this->Properties[index]->Cost == 0) - { + if (this->ParallelLevel > 1 && this->Properties[index] && + this->Properties[index]->Cost == 0) { this->Properties[index]->Cost = cost; - } } + } // Next part of the file is the failed tests - while(std::getline(fin, line)) - { - if(line != "") - { + while (std::getline(fin, line)) { + if (line != "") { this->LastTestsFailed.push_back(line); - } } - fin.close(); } + fin.close(); + } } -//--------------------------------------------------------- -int cmCTestMultiProcessHandler::SearchByName(std::string name) +int cmCTestMultiProcessHandler::SearchByName(std::string const& name) { int index = -1; - for(PropertiesMap::iterator i = this->Properties.begin(); - i != this->Properties.end(); ++i) - { - if(i->second->Name == name) - { + for (PropertiesMap::iterator i = this->Properties.begin(); + i != this->Properties.end(); ++i) { + if (i->second->Name == name) { index = i->first; - } } + } return index; } -//--------------------------------------------------------- void cmCTestMultiProcessHandler::CreateTestCostList() { - for(TestMap::iterator i = this->Tests.begin(); - i != this->Tests.end(); ++i) - { - SortedTests.push_back(i->first); - - //If the test failed last time, it should be run first, so max the cost. - //Only do this for parallel runs; in non-parallel runs, avoid clobbering - //the test's explicitly set cost. - if(this->ParallelLevel > 1 && - std::find(this->LastTestsFailed.begin(), this->LastTestsFailed.end(), - this->Properties[i->first]->Name) != this->LastTestsFailed.end()) - { - this->Properties[i->first]->Cost = FLT_MAX; + if (this->ParallelLevel > 1) { + CreateParallelTestCostList(); + } else { + CreateSerialTestCostList(); + } +} + +void cmCTestMultiProcessHandler::CreateParallelTestCostList() +{ + TestSet alreadySortedTests; + + std::list<TestSet> priorityStack; + priorityStack.push_back(TestSet()); + TestSet& topLevel = priorityStack.back(); + + // In parallel test runs add previously failed tests to the front + // of the cost list and queue other tests for further sorting + for (TestMap::const_iterator i = this->Tests.begin(); i != this->Tests.end(); + ++i) { + if (std::find(this->LastTestsFailed.begin(), this->LastTestsFailed.end(), + this->Properties[i->first]->Name) != + this->LastTestsFailed.end()) { + // If the test failed last time, it should be run first. + this->SortedTests.push_back(i->first); + alreadySortedTests.insert(i->first); + } else { + topLevel.insert(i->first); + } + } + + // In parallel test runs repeatedly move dependencies of the tests on + // the current dependency level to the next level until no + // further dependencies exist. + while (!priorityStack.back().empty()) { + TestSet& previousSet = priorityStack.back(); + priorityStack.push_back(TestSet()); + TestSet& currentSet = priorityStack.back(); + + for (TestSet::const_iterator i = previousSet.begin(); + i != previousSet.end(); ++i) { + TestSet const& dependencies = this->Tests[*i]; + currentSet.insert(dependencies.begin(), dependencies.end()); + } + + for (TestSet::const_iterator i = currentSet.begin(); i != currentSet.end(); + ++i) { + previousSet.erase(*i); + } + } + + // Remove the empty dependency level + priorityStack.pop_back(); + + // Reverse iterate over the different dependency levels (deepest first). + // Sort tests within each level by COST and append them to the cost list. + for (std::list<TestSet>::reverse_iterator i = priorityStack.rbegin(); + i != priorityStack.rend(); ++i) { + TestSet const& currentSet = *i; + TestComparator comp(this); + + TestList sortedCopy; + + sortedCopy.insert(sortedCopy.end(), currentSet.begin(), currentSet.end()); + + std::stable_sort(sortedCopy.begin(), sortedCopy.end(), comp); + + for (TestList::const_iterator j = sortedCopy.begin(); + j != sortedCopy.end(); ++j) { + if (alreadySortedTests.find(*j) == alreadySortedTests.end()) { + this->SortedTests.push_back(*j); + alreadySortedTests.insert(*j); } } + } +} + +void cmCTestMultiProcessHandler::GetAllTestDependencies(int test, + TestList& dependencies) +{ + TestSet const& dependencySet = this->Tests[test]; + for (TestSet::const_iterator i = dependencySet.begin(); + i != dependencySet.end(); ++i) { + GetAllTestDependencies(*i, dependencies); + dependencies.push_back(*i); + } +} + +void cmCTestMultiProcessHandler::CreateSerialTestCostList() +{ + TestList presortedList; + + for (TestMap::iterator i = this->Tests.begin(); i != this->Tests.end(); + ++i) { + presortedList.push_back(i->first); + } TestComparator comp(this); - std::stable_sort(SortedTests.begin(), SortedTests.end(), comp); + std::stable_sort(presortedList.begin(), presortedList.end(), comp); + + TestSet alreadySortedTests; + + for (TestList::const_iterator i = presortedList.begin(); + i != presortedList.end(); ++i) { + int test = *i; + + if (alreadySortedTests.find(test) != alreadySortedTests.end()) { + continue; + } + + TestList dependencies; + GetAllTestDependencies(test, dependencies); + + for (TestList::const_iterator j = dependencies.begin(); + j != dependencies.end(); ++j) { + int testDependency = *j; + + if (alreadySortedTests.find(testDependency) == + alreadySortedTests.end()) { + alreadySortedTests.insert(testDependency); + this->SortedTests.push_back(testDependency); + } + } + + alreadySortedTests.insert(test); + this->SortedTests.push_back(test); + } } -//--------------------------------------------------------- void cmCTestMultiProcessHandler::WriteCheckpoint(int index) { - std::string fname = this->CTest->GetBinaryDir() - + "/Testing/Temporary/CTestCheckpoint.txt"; - std::fstream fout; - fout.open(fname.c_str(), std::ios::app | std::ios::out); + std::string fname = + this->CTest->GetBinaryDir() + "/Testing/Temporary/CTestCheckpoint.txt"; + cmsys::ofstream fout; + fout.open(fname.c_str(), std::ios::app); fout << index << "\n"; fout.close(); } -//--------------------------------------------------------- void cmCTestMultiProcessHandler::MarkFinished() { - std::string fname = this->CTest->GetBinaryDir() - + "/Testing/Temporary/CTestCheckpoint.txt"; - cmSystemTools::RemoveFile(fname.c_str()); + std::string fname = + this->CTest->GetBinaryDir() + "/Testing/Temporary/CTestCheckpoint.txt"; + cmSystemTools::RemoveFile(fname); } -//--------------------------------------------------------- -//For ShowOnly mode +// For ShowOnly mode void cmCTestMultiProcessHandler::PrintTestList() { this->TestHandler->SetMaxIndex(this->FindMaxIndex()); int count = 0; for (PropertiesMap::iterator it = this->Properties.begin(); - it != this->Properties.end(); ++it) - { + it != this->Properties.end(); ++it) { count++; cmCTestTestHandler::cmCTestTestProperties& p = *it->second; - //push working dir - std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory(); - cmSystemTools::ChangeDirectory(p.Directory.c_str()); + cmWorkingDirectory workdir(p.Directory); cmCTestRunTest testRun(this->TestHandler); testRun.SetIndex(p.Index); testRun.SetTestProperties(&p); - testRun.ComputeArguments(); //logs the command in verbose mode + testRun.ComputeArguments(); // logs the command in verbose mode - if(p.Labels.size()) //print the labels - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Labels:"); - } - for(std::vector<std::string>::iterator label = p.Labels.begin(); - label != p.Labels.end(); ++label) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " " << *label); - } - if(p.Labels.size()) //print the labels - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl); - } + if (!p.Labels.empty()) // print the labels + { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Labels:", + this->Quiet); + } + for (std::vector<std::string>::iterator label = p.Labels.begin(); + label != p.Labels.end(); ++label) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " " << *label, + this->Quiet); + } + if (!p.Labels.empty()) // print the labels + { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl, + this->Quiet); + } - if (this->TestHandler->MemCheck) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Memory Check"); - } - else - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Test"); - } - cmOStringStream indexStr; + if (this->TestHandler->MemCheck) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " Memory Check", + this->Quiet); + } else { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " Test", this->Quiet); + } + std::ostringstream indexStr; indexStr << " #" << p.Index << ":"; - cmCTestLog(this->CTest, HANDLER_OUTPUT, + cmCTestOptionalLog( + this->CTest, HANDLER_OUTPUT, std::setw(3 + getNumWidth(this->TestHandler->GetMaxIndex())) - << indexStr.str().c_str()); - cmCTestLog(this->CTest, HANDLER_OUTPUT, " "); - cmCTestLog(this->CTest, HANDLER_OUTPUT, p.Name.c_str() << std::endl); - //pop working dir - cmSystemTools::ChangeDirectory(current_dir.c_str()); - } - - cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl << "Total Tests: " - << this->Total << std::endl); + << indexStr.str(), + this->Quiet); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " " << p.Name, + this->Quiet); + if (p.Disabled) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " (Disabled)", + this->Quiet); + } + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, std::endl, this->Quiet); + } + + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, std::endl + << "Total Tests: " << this->Total << std::endl, + this->Quiet); } void cmCTestMultiProcessHandler::PrintLabels() { std::set<std::string> allLabels; for (PropertiesMap::iterator it = this->Properties.begin(); - it != this->Properties.end(); ++it) - { + it != this->Properties.end(); ++it) { cmCTestTestHandler::cmCTestTestProperties& p = *it->second; allLabels.insert(p.Labels.begin(), p.Labels.end()); - } - - if(allLabels.size()) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, "All Labels:" << std::endl); - } - else - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, "No Labels Exist" << std::endl); - } - for(std::set<std::string>::iterator label = allLabels.begin(); - label != allLabels.end(); ++label) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " " << *label << std::endl); - } + } + + if (!allLabels.empty()) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "All Labels:" << std::endl, + this->Quiet); + } else { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + "No Labels Exist" << std::endl, this->Quiet); + } + for (std::set<std::string>::iterator label = allLabels.begin(); + label != allLabels.end(); ++label) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " " << *label << std::endl, this->Quiet); + } } -//--------------------------------------------------------- void cmCTestMultiProcessHandler::CheckResume() { - std::string fname = this->CTest->GetBinaryDir() - + "/Testing/Temporary/CTestCheckpoint.txt"; - if(this->CTest->GetFailover()) - { - if(cmSystemTools::FileExists(fname.c_str(), true)) - { - *this->TestHandler->LogFile << "Resuming previously interrupted test set" - << std::endl + std::string fname = + this->CTest->GetBinaryDir() + "/Testing/Temporary/CTestCheckpoint.txt"; + if (this->CTest->GetFailover()) { + if (cmSystemTools::FileExists(fname.c_str(), true)) { + *this->TestHandler->LogFile + << "Resuming previously interrupted test set" << std::endl << "----------------------------------------------------------" << std::endl; - std::ifstream fin; + cmsys::ifstream fin; fin.open(fname.c_str()); std::string line; - while(std::getline(fin, line)) - { + while (std::getline(fin, line)) { int index = atoi(line.c_str()); this->RemoveTest(index); - } - fin.close(); } + fin.close(); } - else if(cmSystemTools::FileExists(fname.c_str(), true)) - { - cmSystemTools::RemoveFile(fname.c_str()); - } + } else if (cmSystemTools::FileExists(fname.c_str(), true)) { + cmSystemTools::RemoveFile(fname); + } } -//--------------------------------------------------------- void cmCTestMultiProcessHandler::RemoveTest(int index) { this->EraseTest(index); @@ -636,61 +791,54 @@ void cmCTestMultiProcessHandler::RemoveTest(int index) this->Completed++; } -//--------------------------------------------------------- int cmCTestMultiProcessHandler::FindMaxIndex() { int max = 0; cmCTestMultiProcessHandler::TestMap::iterator i = this->Tests.begin(); - for(; i != this->Tests.end(); ++i) - { - if(i->first > max) - { + for (; i != this->Tests.end(); ++i) { + if (i->first > max) { max = i->first; - } } + } return max; } -//Returns true if no cycles exist in the dependency graph +// Returns true if no cycles exist in the dependency graph bool cmCTestMultiProcessHandler::CheckCycles() { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Checking test dependency graph..." << std::endl); - for(TestMap::iterator it = this->Tests.begin(); - it != this->Tests.end(); ++it) - { - //DFS from each element to itself + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Checking test dependency graph..." << std::endl, + this->Quiet); + for (TestMap::iterator it = this->Tests.begin(); it != this->Tests.end(); + ++it) { + // DFS from each element to itself int root = it->first; std::set<int> visited; std::stack<int> s; s.push(root); - while(!s.empty()) - { + while (!s.empty()) { int test = s.top(); s.pop(); - if(visited.insert(test).second) - { - for(TestSet::iterator d = this->Tests[test].begin(); - d != this->Tests[test].end(); ++d) - { - if(*d == root) - { - //cycle exists - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Error: a cycle exists in the test dependency graph " - "for the test \"" << this->Properties[root]->Name << - "\".\nPlease fix the cycle and run ctest again.\n"); + if (visited.insert(test).second) { + for (TestSet::iterator d = this->Tests[test].begin(); + d != this->Tests[test].end(); ++d) { + if (*d == root) { + // cycle exists + cmCTestLog( + this->CTest, ERROR_MESSAGE, + "Error: a cycle exists in the test dependency graph " + "for the test \"" + << this->Properties[root]->Name + << "\".\nPlease fix the cycle and run ctest again.\n"); return false; - } - else - { - s.push(*d); - } } + s.push(*d); } } } - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Checking test dependency graph end" << std::endl); + } + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Checking test dependency graph end" << std::endl, + this->Quiet); return true; } diff --git a/Source/CTest/cmCTestMultiProcessHandler.h b/Source/CTest/cmCTestMultiProcessHandler.h index cd21d9187..dccc2c896 100644 --- a/Source/CTest/cmCTestMultiProcessHandler.h +++ b/Source/CTest/cmCTestMultiProcessHandler.h @@ -1,20 +1,19 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestMultiProcessHandler_h #define cmCTestMultiProcessHandler_h -#include <cmStandardIncludes.h> -#include <cmCTestTestHandler.h> -#include <cmCTestRunTest.h> +#include "cmConfigure.h" // IWYU pragma: keep + +#include "cmCTestTestHandler.h" +#include <map> +#include <set> +#include <stddef.h> +#include <string> +#include <vector> + +class cmCTest; +class cmCTestRunTest; /** \class cmCTestMultiProcessHandler * \brief run parallel ctest @@ -24,12 +23,21 @@ class cmCTestMultiProcessHandler { friend class TestComparator; + public: - struct TestSet : public std::set<int> {}; - struct TestMap : public std::map<int, TestSet> {}; - struct TestList : public std::vector<int> {}; - struct PropertiesMap : public - std::map<int, cmCTestTestHandler::cmCTestTestProperties*> {}; + struct TestSet : public std::set<int> + { + }; + struct TestMap : public std::map<int, TestSet> + { + }; + struct TestList : public std::vector<int> + { + }; + struct PropertiesMap + : public std::map<int, cmCTestTestHandler::cmCTestTestProperties*> + { + }; cmCTestMultiProcessHandler(); virtual ~cmCTestMultiProcessHandler(); @@ -37,26 +45,32 @@ public: void SetTests(TestMap& tests, PropertiesMap& properties); // Set the max number of tests that can be run at the same time. void SetParallelLevel(size_t); + void SetTestLoad(unsigned long load); virtual void RunTests(); void PrintTestList(); void PrintLabels(); - void SetPassFailVectors(std::vector<cmStdString>* passed, - std::vector<cmStdString>* failed) - { + void SetPassFailVectors(std::vector<std::string>* passed, + std::vector<std::string>* failed) + { this->Passed = passed; this->Failed = failed; - } + } void SetTestResults(std::vector<cmCTestTestHandler::cmCTestTestResult>* r) - { this->TestResults = r; } + { + this->TestResults = r; + } - void SetCTest(cmCTest* ctest) { this->CTest = ctest;} + void SetCTest(cmCTest* ctest) { this->CTest = ctest; } - void SetTestHandler(cmCTestTestHandler * handler) - { this->TestHandler = handler; } + void SetTestHandler(cmCTestTestHandler* handler) + { + this->TestHandler = handler; + } - cmCTestTestHandler * GetTestHandler() - { return this->TestHandler; } + cmCTestTestHandler* GetTestHandler() { return this->TestHandler; } + + void SetQuiet(bool b) { this->Quiet = b; } protected: // Start the next test or tests as many as are allowed by // ParallelLevel @@ -69,9 +83,15 @@ protected: void UpdateCostData(); void ReadCostData(); // Return index of a test based on its name - int SearchByName(std::string name); + int SearchByName(std::string const& name); void CreateTestCostList(); + + void GetAllTestDependencies(int test, TestList& dependencies); + void CreateSerialTestCostList(); + + void CreateParallelTestCostList(); + // Removes the checkpoint file void MarkFinished(); void EraseTest(int index); @@ -79,38 +99,43 @@ protected: // check all running processes for output and exit case bool CheckOutput(); void RemoveTest(int index); - //Check if we need to resume an interrupted test set + // Check if we need to resume an interrupted test set void CheckResume(); - //Check if there are any circular dependencies + // Check if there are any circular dependencies bool CheckCycles(); int FindMaxIndex(); inline size_t GetProcessorsUsed(int index); + std::string GetName(int index); void LockResources(int index); void UnlockResources(int index); // map from test number to set of depend tests TestMap Tests; TestList SortedTests; - //Total number of tests we'll be running + // Total number of tests we'll be running size_t Total; - //Number of tests that are complete + // Number of tests that are complete size_t Completed; size_t RunningCount; bool StopTimePassed; - //list of test properties (indices concurrent to the test map) + // list of test properties (indices concurrent to the test map) PropertiesMap Properties; std::map<int, bool> TestRunningMap; std::map<int, bool> TestFinishMap; - std::map<int, cmStdString> TestOutput; - std::vector<cmStdString>* Passed; - std::vector<cmStdString>* Failed; + std::map<int, std::string> TestOutput; + std::vector<std::string>* Passed; + std::vector<std::string>* Failed; std::vector<std::string> LastTestsFailed; std::set<std::string> LockedResources; std::vector<cmCTestTestHandler::cmCTestTestResult>* TestResults; size_t ParallelLevel; // max number of process that can be run at once - std::set<cmCTestRunTest*> RunningTests; // current running tests - cmCTestTestHandler * TestHandler; + unsigned long TestLoad; + std::set<cmCTestRunTest*> RunningTests; // current running tests + cmCTestTestHandler* TestHandler; cmCTest* CTest; + bool HasCycles; + bool Quiet; + bool SerialTestRunning; }; #endif diff --git a/Source/CTest/cmCTestP4.cxx b/Source/CTest/cmCTestP4.cxx new file mode 100644 index 000000000..c80221634 --- /dev/null +++ b/Source/CTest/cmCTestP4.cxx @@ -0,0 +1,529 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmCTestP4.h" + +#include "cmCTest.h" +#include "cmCTestVC.h" +#include "cmProcessTools.h" +#include "cmSystemTools.h" + +#include "cmsys/RegularExpression.hxx" +#include <algorithm> +#include <ostream> +#include <time.h> +#include <utility> + +cmCTestP4::cmCTestP4(cmCTest* ct, std::ostream& log) + : cmCTestGlobalVC(ct, log) +{ + this->PriorRev = this->Unknown; +} + +cmCTestP4::~cmCTestP4() +{ +} + +class cmCTestP4::IdentifyParser : public cmCTestVC::LineParser +{ +public: + IdentifyParser(cmCTestP4* p4, const char* prefix, std::string& rev) + : Rev(rev) + { + this->SetLog(&p4->Log, prefix); + this->RegexIdentify.compile("^Change ([0-9]+) on"); + } + +private: + std::string& Rev; + cmsys::RegularExpression RegexIdentify; + + bool ProcessLine() CM_OVERRIDE + { + if (this->RegexIdentify.find(this->Line)) { + this->Rev = this->RegexIdentify.match(1); + return false; + } + return true; + } +}; + +class cmCTestP4::ChangesParser : public cmCTestVC::LineParser +{ +public: + ChangesParser(cmCTestP4* p4, const char* prefix) + : P4(p4) + { + this->SetLog(&P4->Log, prefix); + this->RegexIdentify.compile("^Change ([0-9]+) on"); + } + +private: + cmsys::RegularExpression RegexIdentify; + cmCTestP4* P4; + + bool ProcessLine() CM_OVERRIDE + { + if (this->RegexIdentify.find(this->Line)) { + P4->ChangeLists.push_back(this->RegexIdentify.match(1)); + } + return true; + } +}; + +class cmCTestP4::UserParser : public cmCTestVC::LineParser +{ +public: + UserParser(cmCTestP4* p4, const char* prefix) + : P4(p4) + { + this->SetLog(&P4->Log, prefix); + this->RegexUser.compile("^(.+) <(.*)> \\((.*)\\) accessed (.*)$"); + } + +private: + cmsys::RegularExpression RegexUser; + cmCTestP4* P4; + + bool ProcessLine() CM_OVERRIDE + { + if (this->RegexUser.find(this->Line)) { + User NewUser; + + NewUser.UserName = this->RegexUser.match(1); + NewUser.EMail = this->RegexUser.match(2); + NewUser.Name = this->RegexUser.match(3); + NewUser.AccessTime = this->RegexUser.match(4); + P4->Users[this->RegexUser.match(1)] = NewUser; + + return false; + } + return true; + } +}; + +/* Diff format: +==== //depot/file#rev - /absolute/path/to/file ==== +(diff data) +==== //depot/file2#rev - /absolute/path/to/file2 ==== +(diff data) +==== //depot/file3#rev - /absolute/path/to/file3 ==== +==== //depot/file4#rev - /absolute/path/to/file4 ==== +(diff data) +*/ +class cmCTestP4::DiffParser : public cmCTestVC::LineParser +{ +public: + DiffParser(cmCTestP4* p4, const char* prefix) + : P4(p4) + , AlreadyNotified(false) + { + this->SetLog(&P4->Log, prefix); + this->RegexDiff.compile("^==== (.*)#[0-9]+ - (.*)"); + } + +private: + cmCTestP4* P4; + bool AlreadyNotified; + std::string CurrentPath; + cmsys::RegularExpression RegexDiff; + + bool ProcessLine() CM_OVERRIDE + { + if (!this->Line.empty() && this->Line[0] == '=' && + this->RegexDiff.find(this->Line)) { + CurrentPath = this->RegexDiff.match(1); + AlreadyNotified = false; + } else { + if (!AlreadyNotified) { + P4->DoModification(PathModified, CurrentPath); + AlreadyNotified = true; + } + } + return true; + } +}; + +cmCTestP4::User cmCTestP4::GetUserData(const std::string& username) +{ + std::map<std::string, cmCTestP4::User>::const_iterator it = + Users.find(username); + + if (it == Users.end()) { + std::vector<char const*> p4_users; + SetP4Options(p4_users); + p4_users.push_back("users"); + p4_users.push_back("-m"); + p4_users.push_back("1"); + p4_users.push_back(username.c_str()); + p4_users.push_back(CM_NULLPTR); + + UserParser out(this, "users-out> "); + OutputLogger err(this->Log, "users-err> "); + RunChild(&p4_users[0], &out, &err); + + // The user should now be added to the map. Search again. + it = Users.find(username); + if (it == Users.end()) { + return cmCTestP4::User(); + } + } + + return it->second; +} + +/* Commit format: + +Change 1111111 by user@client on 2013/09/26 11:50:36 + + text + text + +Affected files ... + +... //path/to/file#rev edit +... //path/to/file#rev add +... //path/to/file#rev delete +... //path/to/file#rev integrate +*/ +class cmCTestP4::DescribeParser : public cmCTestVC::LineParser +{ +public: + DescribeParser(cmCTestP4* p4, const char* prefix) + : LineParser('\n', false) + , P4(p4) + , Section(SectionHeader) + { + this->SetLog(&P4->Log, prefix); + this->RegexHeader.compile("^Change ([0-9]+) by (.+)@(.+) on (.*)$"); + this->RegexDiff.compile("^\\.\\.\\. (.*)#[0-9]+ ([^ ]+)$"); + } + +private: + cmsys::RegularExpression RegexHeader; + cmsys::RegularExpression RegexDiff; + cmCTestP4* P4; + + typedef cmCTestP4::Revision Revision; + typedef cmCTestP4::Change Change; + std::vector<Change> Changes; + enum SectionType + { + SectionHeader, + SectionBody, + SectionDiffHeader, + SectionDiff, + SectionCount + }; + SectionType Section; + Revision Rev; + + bool ProcessLine() CM_OVERRIDE + { + if (this->Line.empty()) { + this->NextSection(); + } else { + switch (this->Section) { + case SectionHeader: + this->DoHeaderLine(); + break; + case SectionBody: + this->DoBodyLine(); + break; + case SectionDiffHeader: + break; // nothing to do + case SectionDiff: + this->DoDiffLine(); + break; + case SectionCount: + break; // never happens + } + } + return true; + } + + void NextSection() + { + if (this->Section == SectionDiff) { + this->P4->DoRevision(this->Rev, this->Changes); + this->Rev = Revision(); + } + + this->Section = SectionType((this->Section + 1) % SectionCount); + } + + void DoHeaderLine() + { + if (this->RegexHeader.find(this->Line)) { + this->Rev.Rev = this->RegexHeader.match(1); + this->Rev.Date = this->RegexHeader.match(4); + + cmCTestP4::User user = P4->GetUserData(this->RegexHeader.match(2)); + this->Rev.Author = user.Name; + this->Rev.EMail = user.EMail; + + this->Rev.Committer = this->Rev.Author; + this->Rev.CommitterEMail = this->Rev.EMail; + this->Rev.CommitDate = this->Rev.Date; + } + } + + void DoBodyLine() + { + if (this->Line[0] == '\t') { + this->Rev.Log += this->Line.substr(1); + } + this->Rev.Log += "\n"; + } + + void DoDiffLine() + { + if (this->RegexDiff.find(this->Line)) { + Change change; + std::string Path = this->RegexDiff.match(1); + if (Path.length() > 2 && Path[0] == '/' && Path[1] == '/') { + size_t found = Path.find('/', 2); + if (found != std::string::npos) { + Path = Path.substr(found + 1); + } + } + + change.Path = Path; + std::string action = this->RegexDiff.match(2); + + if (action == "add") { + change.Action = 'A'; + } else if (action == "delete") { + change.Action = 'D'; + } else if (action == "edit" || action == "integrate") { + change.Action = 'M'; + } + + Changes.push_back(change); + } + } +}; + +void cmCTestP4::SetP4Options(std::vector<char const*>& CommandOptions) +{ + if (P4Options.empty()) { + const char* p4 = this->CommandLineTool.c_str(); + P4Options.push_back(p4); + + // The CTEST_P4_CLIENT variable sets the P4 client used when issuing + // Perforce commands, if it's different from the default one. + std::string client = this->CTest->GetCTestConfiguration("P4Client"); + if (!client.empty()) { + P4Options.push_back("-c"); + P4Options.push_back(client); + } + + // Set the message language to be English, in case the P4 admin + // has localized them + P4Options.push_back("-L"); + P4Options.push_back("en"); + + // The CTEST_P4_OPTIONS variable adds additional Perforce command line + // options before the main command + std::string opts = this->CTest->GetCTestConfiguration("P4Options"); + std::vector<std::string> args = + cmSystemTools::ParseArguments(opts.c_str()); + + P4Options.insert(P4Options.end(), args.begin(), args.end()); + } + + CommandOptions.clear(); + for (std::vector<std::string>::iterator i = P4Options.begin(); + i != P4Options.end(); ++i) { + CommandOptions.push_back(i->c_str()); + } +} + +std::string cmCTestP4::GetWorkingRevision() +{ + std::vector<char const*> p4_identify; + SetP4Options(p4_identify); + + p4_identify.push_back("changes"); + p4_identify.push_back("-m"); + p4_identify.push_back("1"); + p4_identify.push_back("-t"); + + std::string source = this->SourceDirectory + "/...#have"; + p4_identify.push_back(source.c_str()); + p4_identify.push_back(CM_NULLPTR); + + std::string rev; + IdentifyParser out(this, "p4_changes-out> ", rev); + OutputLogger err(this->Log, "p4_changes-err> "); + + bool result = RunChild(&p4_identify[0], &out, &err); + + // If there was a problem contacting the server return "<unknown>" + if (!result) { + return "<unknown>"; + } + + if (rev.empty()) { + return "0"; + } + return rev; +} + +bool cmCTestP4::NoteOldRevision() +{ + this->OldRevision = this->GetWorkingRevision(); + + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: " + << this->OldRevision << "\n"); + this->PriorRev.Rev = this->OldRevision; + return true; +} + +bool cmCTestP4::NoteNewRevision() +{ + this->NewRevision = this->GetWorkingRevision(); + + cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: " + << this->NewRevision << "\n"); + return true; +} + +bool cmCTestP4::LoadRevisions() +{ + std::vector<char const*> p4_changes; + SetP4Options(p4_changes); + + // Use 'p4 changes ...@old,new' to get a list of changelists + std::string range = this->SourceDirectory + "/..."; + + // If any revision is unknown it means we couldn't contact the server. + // Do not process updates + if (this->OldRevision == "<unknown>" || this->NewRevision == "<unknown>") { + cmCTestLog(this->CTest, HANDLER_OUTPUT, " At least one of the revisions " + << "is unknown. No repository changes will be reported.\n"); + return false; + } + + range.append("@") + .append(this->OldRevision) + .append(",") + .append(this->NewRevision); + + p4_changes.push_back("changes"); + p4_changes.push_back(range.c_str()); + p4_changes.push_back(CM_NULLPTR); + + ChangesParser out(this, "p4_changes-out> "); + OutputLogger err(this->Log, "p4_changes-err> "); + + ChangeLists.clear(); + this->RunChild(&p4_changes[0], &out, &err); + + if (ChangeLists.empty()) { + return true; + } + + // p4 describe -s ...@1111111,2222222 + std::vector<char const*> p4_describe; + for (std::vector<std::string>::reverse_iterator i = ChangeLists.rbegin(); + i != ChangeLists.rend(); ++i) { + SetP4Options(p4_describe); + p4_describe.push_back("describe"); + p4_describe.push_back("-s"); + p4_describe.push_back(i->c_str()); + p4_describe.push_back(CM_NULLPTR); + + DescribeParser outDescribe(this, "p4_describe-out> "); + OutputLogger errDescribe(this->Log, "p4_describe-err> "); + this->RunChild(&p4_describe[0], &outDescribe, &errDescribe); + } + return true; +} + +bool cmCTestP4::LoadModifications() +{ + std::vector<char const*> p4_diff; + SetP4Options(p4_diff); + + p4_diff.push_back("diff"); + + // Ideally we would use -Od but not all clients support it + p4_diff.push_back("-dn"); + std::string source = this->SourceDirectory + "/..."; + p4_diff.push_back(source.c_str()); + p4_diff.push_back(CM_NULLPTR); + + DiffParser out(this, "p4_diff-out> "); + OutputLogger err(this->Log, "p4_diff-err> "); + this->RunChild(&p4_diff[0], &out, &err); + return true; +} + +bool cmCTestP4::UpdateCustom(const std::string& custom) +{ + std::vector<std::string> p4_custom_command; + cmSystemTools::ExpandListArgument(custom, p4_custom_command, true); + + std::vector<char const*> p4_custom; + for (std::vector<std::string>::const_iterator i = p4_custom_command.begin(); + i != p4_custom_command.end(); ++i) { + p4_custom.push_back(i->c_str()); + } + p4_custom.push_back(CM_NULLPTR); + + OutputLogger custom_out(this->Log, "p4_customsync-out> "); + OutputLogger custom_err(this->Log, "p4_customsync-err> "); + + return this->RunUpdateCommand(&p4_custom[0], &custom_out, &custom_err); +} + +bool cmCTestP4::UpdateImpl() +{ + std::string custom = this->CTest->GetCTestConfiguration("P4UpdateCustom"); + if (!custom.empty()) { + return this->UpdateCustom(custom); + } + + // If we couldn't get a revision number before updating, abort. + if (this->OldRevision == "<unknown>") { + this->UpdateCommandLine = "Unknown current revision"; + cmCTestLog(this->CTest, ERROR_MESSAGE, " Unknown current revision\n"); + return false; + } + + std::vector<char const*> p4_sync; + SetP4Options(p4_sync); + + p4_sync.push_back("sync"); + + // Get user-specified update options. + std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); + if (opts.empty()) { + opts = this->CTest->GetCTestConfiguration("P4UpdateOptions"); + } + std::vector<std::string> args = cmSystemTools::ParseArguments(opts.c_str()); + for (std::vector<std::string>::const_iterator ai = args.begin(); + ai != args.end(); ++ai) { + p4_sync.push_back(ai->c_str()); + } + + std::string source = this->SourceDirectory + "/..."; + + // Specify the start time for nightly testing. + if (this->CTest->GetTestModel() == cmCTest::NIGHTLY) { + std::string date = this->GetNightlyTime(); + // CTest reports the date as YYYY-MM-DD, Perforce needs it as YYYY/MM/DD + std::replace(date.begin(), date.end(), '-', '/'); + + // Revision specification: /...@"YYYY/MM/DD HH:MM:SS" + source.append("@\"").append(date).append("\""); + } + + p4_sync.push_back(source.c_str()); + p4_sync.push_back(CM_NULLPTR); + + OutputLogger out(this->Log, "p4_sync-out> "); + OutputLogger err(this->Log, "p4_sync-err> "); + + return this->RunUpdateCommand(&p4_sync[0], &out, &err); +} diff --git a/Source/CTest/cmCTestP4.h b/Source/CTest/cmCTestP4.h new file mode 100644 index 000000000..e234efbfb --- /dev/null +++ b/Source/CTest/cmCTestP4.h @@ -0,0 +1,76 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmCTestP4_h +#define cmCTestP4_h + +#include "cmConfigure.h" + +#include "cmCTestGlobalVC.h" + +#include <iosfwd> +#include <map> +#include <string> +#include <vector> + +class cmCTest; + +/** \class cmCTestP4 + * \brief Interaction with the Perforce command-line tool + * + */ +class cmCTestP4 : public cmCTestGlobalVC +{ +public: + /** Construct with a CTest instance and update log stream. */ + cmCTestP4(cmCTest* ctest, std::ostream& log); + + ~cmCTestP4() CM_OVERRIDE; + +private: + std::vector<std::string> ChangeLists; + + struct User + { + std::string UserName; + std::string Name; + std::string EMail; + std::string AccessTime; + + User() + : UserName() + , Name() + , EMail() + , AccessTime() + { + } + }; + std::map<std::string, User> Users; + std::vector<std::string> P4Options; + + User GetUserData(const std::string& username); + void SetP4Options(std::vector<char const*>& options); + + std::string GetWorkingRevision(); + bool NoteOldRevision() CM_OVERRIDE; + bool NoteNewRevision() CM_OVERRIDE; + bool UpdateImpl() CM_OVERRIDE; + bool UpdateCustom(const std::string& custom); + + bool LoadRevisions() CM_OVERRIDE; + bool LoadModifications() CM_OVERRIDE; + + class ChangesParser; + class DescribeParser; + class DiffParser; + // Parsing helper classes. + class IdentifyParser; + class UserParser; + + friend class IdentifyParser; + friend class ChangesParser; + friend class UserParser; + friend class DescribeParser; + friend class DiffParser; +}; + +#endif diff --git a/Source/CTest/cmCTestReadCustomFilesCommand.cxx b/Source/CTest/cmCTestReadCustomFilesCommand.cxx index 5db01f98d..b21be872f 100644 --- a/Source/CTest/cmCTestReadCustomFilesCommand.cxx +++ b/Source/CTest/cmCTestReadCustomFilesCommand.cxx @@ -1,35 +1,23 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestReadCustomFilesCommand.h" #include "cmCTest.h" -bool cmCTestReadCustomFilesCommand -::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &) +class cmExecutionStatus; + +bool cmCTestReadCustomFilesCommand::InitialPass( + std::vector<std::string> const& args, cmExecutionStatus& /*unused*/) { - if (args.size() < 1) - { + if (args.empty()) { this->SetError("called with incorrect number of arguments"); return false; - } + } std::vector<std::string>::const_iterator dit; - for ( dit = args.begin(); dit != args.end(); ++ dit ) - { - this->CTest->ReadCustomConfigurationFileTree(dit->c_str(), - this->Makefile); - } + for (dit = args.begin(); dit != args.end(); ++dit) { + this->CTest->ReadCustomConfigurationFileTree(dit->c_str(), this->Makefile); + } return true; } - - diff --git a/Source/CTest/cmCTestReadCustomFilesCommand.h b/Source/CTest/cmCTestReadCustomFilesCommand.h index b984c84e9..5989fa0fc 100644 --- a/Source/CTest/cmCTestReadCustomFilesCommand.h +++ b/Source/CTest/cmCTestReadCustomFilesCommand.h @@ -1,19 +1,18 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestReadCustomFilesCommand_h #define cmCTestReadCustomFilesCommand_h +#include "cmConfigure.h" + #include "cmCTestCommand.h" +#include <string> +#include <vector> + +class cmCommand; +class cmExecutionStatus; + /** \class cmCTestReadCustomFiles * \brief Run a ctest script * @@ -23,53 +22,24 @@ class cmCTestReadCustomFilesCommand : public cmCTestCommand { public: - cmCTestReadCustomFilesCommand() {} /** * This is a virtual constructor for the command. */ - virtual cmCommand* Clone() - { + cmCommand* Clone() CM_OVERRIDE + { cmCTestReadCustomFilesCommand* ni = new cmCTestReadCustomFilesCommand; ni->CTest = this->CTest; return ni; - } + } /** * This is called when the command is first encountered in * the CMakeLists.txt file. */ - virtual bool InitialPass(std::vector<std::string> const& args, - cmExecutionStatus &status); - - /** - * The name of the command as specified in CMakeList.txt. - */ - virtual const char* GetName() const { return "ctest_read_custom_files";} - - /** - * Succinct documentation. - */ - virtual const char* GetTerseDocumentation() const - { - return "read CTestCustom files."; - } - - /** - * More documentation. - */ - virtual const char* GetFullDocumentation() const - { - return - " ctest_read_custom_files( directory ... )\n" - "Read all the CTestCustom.ctest or CTestCustom.cmake files from " - "the given directory."; - } - - cmTypeMacro(cmCTestReadCustomFilesCommand, cmCTestCommand); - + bool InitialPass(std::vector<std::string> const& args, + cmExecutionStatus& status) CM_OVERRIDE; }; - #endif diff --git a/Source/CTest/cmCTestRunScriptCommand.cxx b/Source/CTest/cmCTestRunScriptCommand.cxx index fe429bd9d..238284a3f 100644 --- a/Source/CTest/cmCTestRunScriptCommand.cxx +++ b/Source/CTest/cmCTestRunScriptCommand.cxx @@ -1,65 +1,49 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestRunScriptCommand.h" #include "cmCTestScriptHandler.h" +#include "cmMakefile.h" + +#include <sstream> -bool cmCTestRunScriptCommand -::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &) +class cmExecutionStatus; + +bool cmCTestRunScriptCommand::InitialPass(std::vector<std::string> const& args, + cmExecutionStatus& /*unused*/) { - if(args.size() < 1 ) - { + if (args.empty()) { this->CTestScriptHandler->RunCurrentScript(); return true; - } + } bool np = false; unsigned int i = 0; - if (args[i] == "NEW_PROCESS") - { + if (args[i] == "NEW_PROCESS") { np = true; i++; - } + } int start = i; // run each script std::string returnVariable; - for (i = start; i < args.size(); ++i) - { - if(args[i] == "RETURN_VALUE") - { + for (i = start; i < args.size(); ++i) { + if (args[i] == "RETURN_VALUE") { ++i; - if(i < args.size()) - { + if (i < args.size()) { returnVariable = args[i]; - } } } - for (i = start; i < args.size(); ++i) - { - if(args[i] == "RETURN_VALUE") - { + } + for (i = start; i < args.size(); ++i) { + if (args[i] == "RETURN_VALUE") { ++i; - } - else - { + } else { int ret; - cmCTestScriptHandler::RunScript(this->CTest, args[i].c_str(), !np, - &ret); - cmOStringStream str; + cmCTestScriptHandler::RunScript(this->CTest, args[i].c_str(), !np, &ret); + std::ostringstream str; str << ret; - this->Makefile->AddDefinition(returnVariable.c_str(), str.str().c_str()); - } + this->Makefile->AddDefinition(returnVariable, str.str().c_str()); } + } return true; } - - diff --git a/Source/CTest/cmCTestRunScriptCommand.h b/Source/CTest/cmCTestRunScriptCommand.h index 05e78991a..9bd09659d 100644 --- a/Source/CTest/cmCTestRunScriptCommand.h +++ b/Source/CTest/cmCTestRunScriptCommand.h @@ -1,19 +1,18 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestRunScriptCommand_h #define cmCTestRunScriptCommand_h +#include "cmConfigure.h" + #include "cmCTestCommand.h" +#include <string> +#include <vector> + +class cmCommand; +class cmExecutionStatus; + /** \class cmCTestRunScript * \brief Run a ctest script * @@ -23,58 +22,25 @@ class cmCTestRunScriptCommand : public cmCTestCommand { public: - cmCTestRunScriptCommand() {} /** * This is a virtual constructor for the command. */ - virtual cmCommand* Clone() - { + cmCommand* Clone() CM_OVERRIDE + { cmCTestRunScriptCommand* ni = new cmCTestRunScriptCommand; ni->CTest = this->CTest; ni->CTestScriptHandler = this->CTestScriptHandler; return ni; - } + } /** * This is called when the command is first encountered in * the CMakeLists.txt file. */ - virtual bool InitialPass(std::vector<std::string> const& args, - cmExecutionStatus &status); - - /** - * The name of the command as specified in CMakeList.txt. - */ - virtual const char* GetName() const { return "ctest_run_script";} - - /** - * Succinct documentation. - */ - virtual const char* GetTerseDocumentation() const - { - return "runs a ctest -S script"; - } - - /** - * More documentation. - */ - virtual const char* GetFullDocumentation() const - { - return - " ctest_run_script([NEW_PROCESS] script_file_name script_file_name1 \n" - " script_file_name2 ... [RETURN_VALUE var])\n" - "Runs a script or scripts much like if it was run from ctest -S. " - "If no argument is provided then the current script is run using " - "the current settings of the variables. If NEW_PROCESS is specified " - "then each script will be run in a separate process." - "If RETURN_VALUE is specified the return value of the last script " - "run will be put into var."; - } - - cmTypeMacro(cmCTestRunScriptCommand, cmCTestCommand); + bool InitialPass(std::vector<std::string> const& args, + cmExecutionStatus& status) CM_OVERRIDE; }; - #endif diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx index 0e2fa41b9..0c4269e3d 100644 --- a/Source/CTest/cmCTestRunTest.cxx +++ b/Source/CTest/cmCTestRunTest.cxx @@ -1,77 +1,96 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestRunTest.h" -#include "cmCTestMemCheckHandler.h" + #include "cmCTest.h" +#include "cmCTestMemCheckHandler.h" +#include "cmCTestTestHandler.h" +#include "cmProcess.h" #include "cmSystemTools.h" -#include "cm_curl.h" +#include "cmWorkingDirectory.h" -#include <cm_zlib.h> -#include <cmsys/Base64.h> +#include "cmConfigure.h" +#include "cm_curl.h" +#include "cm_zlib.h" +#include "cmsys/Base64.h" +#include "cmsys/Process.h" +#include "cmsys/RegularExpression.hxx" +#include <iomanip> +#include <sstream> +#include <stdio.h> +#include <time.h> +#include <utility> cmCTestRunTest::cmCTestRunTest(cmCTestTestHandler* handler) { this->CTest = handler->CTest; this->TestHandler = handler; - this->TestProcess = 0; - this->TestResult.ExecutionTime =0; + this->TestProcess = CM_NULLPTR; + this->TestResult.ExecutionTime = 0; this->TestResult.ReturnValue = 0; this->TestResult.Status = cmCTestTestHandler::NOT_RUN; this->TestResult.TestCount = 0; - this->TestResult.Properties = 0; + this->TestResult.Properties = CM_NULLPTR; this->ProcessOutput = ""; this->CompressedOutput = ""; this->CompressionRatio = 2; this->StopTimePassed = false; + this->NumberOfRunsLeft = 1; // default to 1 run of the test + this->RunUntilFail = false; // default to run the test once + this->RunAgain = false; // default to not having to run again } cmCTestRunTest::~cmCTestRunTest() { } -//---------------------------------------------------------------------------- bool cmCTestRunTest::CheckOutput() { // Read lines for up to 0.1 seconds of total time. double timeout = 0.1; double timeEnd = cmSystemTools::GetTime() + timeout; std::string line; - while((timeout = timeEnd - cmSystemTools::GetTime(), timeout > 0)) - { + while ((timeout = timeEnd - cmSystemTools::GetTime(), timeout > 0)) { int p = this->TestProcess->GetNextOutputLine(line, timeout); - if(p == cmsysProcess_Pipe_None) - { + if (p == cmsysProcess_Pipe_None) { // Process has terminated and all output read. return false; - } - else if(p == cmsysProcess_Pipe_STDOUT || - p == cmsysProcess_Pipe_STDERR) - { + } + if (p == cmsysProcess_Pipe_STDOUT) { // Store this line of output. - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - this->GetIndex() << ": " << line << std::endl); + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->GetIndex() + << ": " << line << std::endl); this->ProcessOutput += line; this->ProcessOutput += "\n"; + + // Check for TIMEOUT_AFTER_MATCH property. + if (!this->TestProperties->TimeoutRegularExpressions.empty()) { + std::vector< + std::pair<cmsys::RegularExpression, std::string> >::iterator regIt; + for (regIt = this->TestProperties->TimeoutRegularExpressions.begin(); + regIt != this->TestProperties->TimeoutRegularExpressions.end(); + ++regIt) { + if (regIt->first.find(this->ProcessOutput.c_str())) { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->GetIndex() + << ": " + << "Test timeout changed to " + << this->TestProperties->AlternateTimeout + << std::endl); + this->TestProcess->ResetStartTime(); + this->TestProcess->ChangeTimeout( + this->TestProperties->AlternateTimeout); + this->TestProperties->TimeoutRegularExpressions.clear(); + break; + } + } } - else // if(p == cmsysProcess_Pipe_Timeout) - { + } else { // if(p == cmsysProcess_Pipe_Timeout) break; - } } + } return true; } -//--------------------------------------------------------- // Streamed compression of test output. The compressed data // is appended to this->CompressedOutput void cmCTestRunTest::CompressOutput() @@ -79,10 +98,9 @@ void cmCTestRunTest::CompressOutput() int ret; z_stream strm; - unsigned char* in = - reinterpret_cast<unsigned char*>( + unsigned char* in = reinterpret_cast<unsigned char*>( const_cast<char*>(this->ProcessOutput.c_str())); - //zlib makes the guarantee that this is the maximum output size + // zlib makes the guarantee that this is the maximum output size int outSize = static_cast<int>( static_cast<double>(this->ProcessOutput.size()) * 1.001 + 13.0); unsigned char* out = new unsigned char[outSize]; @@ -90,12 +108,11 @@ void cmCTestRunTest::CompressOutput() strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; - ret = deflateInit(&strm, -1); //default compression level - if (ret != Z_OK) - { + ret = deflateInit(&strm, -1); // default compression level + if (ret != Z_OK) { delete[] out; return; - } + } strm.avail_in = static_cast<uInt>(this->ProcessOutput.size()); strm.next_in = in; @@ -103,134 +120,124 @@ void cmCTestRunTest::CompressOutput() strm.next_out = out; ret = deflate(&strm, Z_FINISH); - if(ret == Z_STREAM_ERROR || ret != Z_STREAM_END) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, "Error during output " - "compression. Sending uncompressed output." << std::endl); + if (ret != Z_STREAM_END) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Error during output compression. Sending uncompressed output." + << std::endl); delete[] out; return; - } + } (void)deflateEnd(&strm); - unsigned char *encoded_buffer - = new unsigned char[static_cast<int>(outSize * 1.5)]; + unsigned char* encoded_buffer = + new unsigned char[static_cast<int>(outSize * 1.5)]; - unsigned long rlen - = cmsysBase64_Encode(out, strm.total_out, encoded_buffer, 1); + size_t rlen = cmsysBase64_Encode(out, strm.total_out, encoded_buffer, 1); - for(unsigned long i = 0; i < rlen; i++) - { + this->CompressedOutput.clear(); + for (size_t i = 0; i < rlen; i++) { this->CompressedOutput += encoded_buffer[i]; - } + } - if(strm.total_in) - { - this->CompressionRatio = static_cast<double>(strm.total_out) / - static_cast<double>(strm.total_in); - } + if (strm.total_in) { + this->CompressionRatio = + static_cast<double>(strm.total_out) / static_cast<double>(strm.total_in); + } - delete [] encoded_buffer; - delete [] out; + delete[] encoded_buffer; + delete[] out; } -//--------------------------------------------------------- bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started) { if ((!this->TestHandler->MemCheck && - this->CTest->ShouldCompressTestOutput()) || + this->CTest->ShouldCompressTestOutput()) || (this->TestHandler->MemCheck && - this->CTest->ShouldCompressMemCheckOutput())) - { + this->CTest->ShouldCompressTestOutput())) { this->CompressOutput(); - } + } this->WriteLogOutputTop(completed, total); std::string reason; bool passed = true; - int res = started ? this->TestProcess->GetProcessStatus() - : cmsysProcess_State_Error; + int res = + started ? this->TestProcess->GetProcessStatus() : cmsysProcess_State_Error; int retVal = this->TestProcess->GetExitValue(); - std::vector<std::pair<cmsys::RegularExpression, - std::string> >::iterator passIt; + std::vector<std::pair<cmsys::RegularExpression, std::string> >::iterator + passIt; bool forceFail = false; + bool skipped = false; bool outputTestErrorsToConsole = false; - if ( this->TestProperties->RequiredRegularExpressions.size() > 0 ) - { + if (!this->TestProperties->RequiredRegularExpressions.empty() && + this->FailedDependencies.empty()) { bool found = false; - for ( passIt = this->TestProperties->RequiredRegularExpressions.begin(); - passIt != this->TestProperties->RequiredRegularExpressions.end(); - ++ passIt ) - { - if ( passIt->first.find(this->ProcessOutput.c_str()) ) - { + for (passIt = this->TestProperties->RequiredRegularExpressions.begin(); + passIt != this->TestProperties->RequiredRegularExpressions.end(); + ++passIt) { + if (passIt->first.find(this->ProcessOutput.c_str())) { found = true; reason = "Required regular expression found."; break; - } } - if ( !found ) - { + } + if (!found) { reason = "Required regular expression not found."; forceFail = true; - } - reason += "Regex=["; - for ( passIt = this->TestProperties->RequiredRegularExpressions.begin(); - passIt != this->TestProperties->RequiredRegularExpressions.end(); - ++ passIt ) - { + } + reason += "Regex=["; + for (passIt = this->TestProperties->RequiredRegularExpressions.begin(); + passIt != this->TestProperties->RequiredRegularExpressions.end(); + ++passIt) { reason += passIt->second; reason += "\n"; - } - reason += "]"; } - if ( this->TestProperties->ErrorRegularExpressions.size() > 0 ) - { - for ( passIt = this->TestProperties->ErrorRegularExpressions.begin(); - passIt != this->TestProperties->ErrorRegularExpressions.end(); - ++ passIt ) - { - if ( passIt->first.find(this->ProcessOutput.c_str()) ) - { + reason += "]"; + } + if (!this->TestProperties->ErrorRegularExpressions.empty() && + this->FailedDependencies.empty()) { + for (passIt = this->TestProperties->ErrorRegularExpressions.begin(); + passIt != this->TestProperties->ErrorRegularExpressions.end(); + ++passIt) { + if (passIt->first.find(this->ProcessOutput.c_str())) { reason = "Error regular expression found in output."; reason += " Regex=["; reason += passIt->second; reason += "]"; forceFail = true; break; - } } } - if (res == cmsysProcess_State_Exited) - { - bool success = - !forceFail && (retVal == 0 || - this->TestProperties->RequiredRegularExpressions.size()); - if((success && !this->TestProperties->WillFail) - || (!success && this->TestProperties->WillFail)) - { + } + if (res == cmsysProcess_State_Exited) { + bool success = !forceFail && + (retVal == 0 || + !this->TestProperties->RequiredRegularExpressions.empty()); + if (this->TestProperties->SkipReturnCode >= 0 && + this->TestProperties->SkipReturnCode == retVal) { + this->TestResult.Status = cmCTestTestHandler::NOT_RUN; + std::ostringstream s; + s << "SKIP_RETURN_CODE=" << this->TestProperties->SkipReturnCode; + this->TestResult.CompletionStatus = s.str(); + cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Skipped "); + skipped = true; + } else if ((success && !this->TestProperties->WillFail) || + (!success && this->TestProperties->WillFail)) { this->TestResult.Status = cmCTestTestHandler::COMPLETED; - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Passed " ); - } - else - { + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Passed "); + } else { this->TestResult.Status = cmCTestTestHandler::FAILED; - cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Failed " << reason ); + cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Failed " << reason); outputTestErrorsToConsole = this->CTest->OutputTestOutputOnTestFailure; - } } - else if ( res == cmsysProcess_State_Expired ) - { + } else if (res == cmsysProcess_State_Expired) { cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Timeout "); this->TestResult.Status = cmCTestTestHandler::TIMEOUT; outputTestErrorsToConsole = this->CTest->OutputTestOutputOnTestFailure; - } - else if ( res == cmsysProcess_State_Exception ) - { + } else if (res == cmsysProcess_State_Exception) { outputTestErrorsToConsole = this->CTest->OutputTestOutputOnTestFailure; cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Exception: "); - switch(this->TestProcess->GetExitException()) - { + switch (this->TestProcess->GetExitException()) { case cmsysProcess_Exception_Fault: cmCTestLog(this->CTest, HANDLER_OUTPUT, "SegFault"); this->TestResult.Status = cmCTestTestHandler::SEGFAULT; @@ -250,59 +257,52 @@ bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started) default: cmCTestLog(this->CTest, HANDLER_OUTPUT, "Other"); this->TestResult.Status = cmCTestTestHandler::OTHER_FAULT; - } } - else //cmsysProcess_State_Error - { + } else if ("Disabled" == this->TestResult.CompletionStatus) { + cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Not Run (Disabled) "); + } else // cmsysProcess_State_Error + { cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Not Run "); - } + } passed = this->TestResult.Status == cmCTestTestHandler::COMPLETED; char buf[1024]; sprintf(buf, "%6.2f sec", this->TestProcess->GetTotalTime()); - cmCTestLog(this->CTest, HANDLER_OUTPUT, buf << "\n" ); + cmCTestLog(this->CTest, HANDLER_OUTPUT, buf << "\n"); - if ( outputTestErrorsToConsole ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, this->ProcessOutput << std::endl ); - } + if (outputTestErrorsToConsole) { + cmCTestLog(this->CTest, HANDLER_OUTPUT, this->ProcessOutput << std::endl); + } - if ( this->TestHandler->LogFile ) - { + if (this->TestHandler->LogFile) { *this->TestHandler->LogFile << "Test time = " << buf << std::endl; - } - - // Set the working directory to the tests directory - std::string oldpath = cmSystemTools::GetCurrentWorkingDirectory(); - cmSystemTools::ChangeDirectory(this->TestProperties->Directory.c_str()); - - this->DartProcessing(); - - // restore working directory - cmSystemTools::ChangeDirectory(oldpath.c_str()); + } + // Set the working directory to the tests directory to process Dart files. + { + cmWorkingDirectory workdir(this->TestProperties->Directory); + this->DartProcessing(); + } // if this is doing MemCheck then all the output needs to be put into // Output since that is what is parsed by cmCTestMemCheckHandler - if(!this->TestHandler->MemCheck && started) - { - this->TestHandler->CleanTestOutput(this->ProcessOutput, - static_cast<size_t> - (this->TestResult.Status == cmCTestTestHandler::COMPLETED ? - this->TestHandler->CustomMaximumPassedTestOutputSize : - this->TestHandler->CustomMaximumFailedTestOutputSize)); - } + if (!this->TestHandler->MemCheck && started) { + this->TestHandler->CleanTestOutput( + this->ProcessOutput, + static_cast<size_t>( + this->TestResult.Status == cmCTestTestHandler::COMPLETED + ? this->TestHandler->CustomMaximumPassedTestOutputSize + : this->TestHandler->CustomMaximumFailedTestOutputSize)); + } this->TestResult.Reason = reason; - if (this->TestHandler->LogFile) - { + if (this->TestHandler->LogFile) { bool pass = true; const char* reasonType = "Test Pass Reason"; - if(this->TestResult.Status != cmCTestTestHandler::COMPLETED && - this->TestResult.Status != cmCTestTestHandler::NOT_RUN) - { + if (this->TestResult.Status != cmCTestTestHandler::COMPLETED && + this->TestResult.Status != cmCTestTestHandler::NOT_RUN) { reasonType = "Test Fail Reason"; pass = false; - } + } double ttime = this->TestProcess->GetTotalTime(); int hours = static_cast<int>(ttime / (60 * 60)); int minutes = static_cast<int>(ttime / 60) % 60; @@ -312,105 +312,134 @@ bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started) *this->TestHandler->LogFile << "----------------------------------------------------------" << std::endl; - if(this->TestResult.Reason.size()) - { + if (!this->TestResult.Reason.empty()) { *this->TestHandler->LogFile << reasonType << ":\n" - << this->TestResult.Reason << "\n"; - } - else - { - if(pass) - { + << this->TestResult.Reason << "\n"; + } else { + if (pass) { *this->TestHandler->LogFile << "Test Passed.\n"; - } - else - { + } else { *this->TestHandler->LogFile << "Test Failed.\n"; - } } - *this->TestHandler->LogFile << "\"" << this->TestProperties->Name.c_str() + } + *this->TestHandler->LogFile + << "\"" << this->TestProperties->Name << "\" end time: " << this->CTest->CurrentTime() << std::endl - << "\"" << this->TestProperties->Name.c_str() << "\" time elapsed: " - << buffer << std::endl + << "\"" << this->TestProperties->Name << "\" time elapsed: " << buffer + << std::endl << "----------------------------------------------------------" - << std::endl << std::endl; - } + << std::endl + << std::endl; + } // if the test actually started and ran // record the results in TestResult - if(started) - { + if (started) { bool compress = !this->TestHandler->MemCheck && - this->CompressionRatio < 1 && - this->CTest->ShouldCompressTestOutput(); - this->TestResult.Output = compress ? this->CompressedOutput - : this->ProcessOutput; + this->CompressionRatio < 1 && this->CTest->ShouldCompressTestOutput(); + this->TestResult.Output = + compress ? this->CompressedOutput : this->ProcessOutput; this->TestResult.CompressOutput = compress; this->TestResult.ReturnValue = this->TestProcess->GetExitValue(); - this->TestResult.CompletionStatus = "Completed"; + if (!skipped) { + this->TestResult.CompletionStatus = "Completed"; + } this->TestResult.ExecutionTime = this->TestProcess->GetTotalTime(); this->MemCheckPostProcess(); this->ComputeWeightedCost(); - } - // Always push the current TestResult onto the + } + // If the test does not need to rerun push the current TestResult onto the // TestHandler vector - this->TestHandler->TestResults.push_back(this->TestResult); + if (!this->NeedsToRerun()) { + this->TestHandler->TestResults.push_back(this->TestResult); + } delete this->TestProcess; - return passed; + return passed || skipped; } -//---------------------------------------------------------------------- +bool cmCTestRunTest::StartAgain() +{ + if (!this->RunAgain) { + return false; + } + this->RunAgain = false; // reset + // change to tests directory + cmWorkingDirectory workdir(this->TestProperties->Directory); + this->StartTest(this->TotalNumberOfTests); + return true; +} + +bool cmCTestRunTest::NeedsToRerun() +{ + this->NumberOfRunsLeft--; + if (this->NumberOfRunsLeft == 0) { + return false; + } + // if number of runs left is not 0, and we are running until + // we find a failed test, then return true so the test can be + // restarted + if (this->RunUntilFail && + this->TestResult.Status == cmCTestTestHandler::COMPLETED) { + this->RunAgain = true; + return true; + } + return false; +} void cmCTestRunTest::ComputeWeightedCost() { double prev = static_cast<double>(this->TestProperties->PreviousRuns); double avgcost = static_cast<double>(this->TestProperties->Cost); double current = this->TestResult.ExecutionTime; - if(this->TestResult.Status == cmCTestTestHandler::COMPLETED) - { + if (this->TestResult.Status == cmCTestTestHandler::COMPLETED) { this->TestProperties->Cost = static_cast<float>(((prev * avgcost) + current) / (prev + 1.0)); this->TestProperties->PreviousRuns++; - } + } } -//---------------------------------------------------------------------- void cmCTestRunTest::MemCheckPostProcess() { - if(!this->TestHandler->MemCheck) - { + if (!this->TestHandler->MemCheck) { return; - } - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->Index - << ": process test output now: " - << this->TestProperties->Name.c_str() << " " - << this->TestResult.Name.c_str() << std::endl); - cmCTestMemCheckHandler * handler = static_cast<cmCTestMemCheckHandler*> - (this->TestHandler); - switch ( handler->MemoryTesterStyle ) - { - case cmCTestMemCheckHandler::VALGRIND: - handler->PostProcessValgrindTest(this->TestResult, this->Index); - break; - case cmCTestMemCheckHandler::PURIFY: - handler->PostProcessPurifyTest(this->TestResult, this->Index); - break; - case cmCTestMemCheckHandler::BOUNDS_CHECKER: - handler->PostProcessBoundsCheckerTest(this->TestResult, this->Index); - break; - default: - break; - } + } + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->Index + << ": process test output now: " + << this->TestProperties->Name << " " + << this->TestResult.Name << std::endl, + this->TestHandler->GetQuiet()); + cmCTestMemCheckHandler* handler = + static_cast<cmCTestMemCheckHandler*>(this->TestHandler); + handler->PostProcessTest(this->TestResult, this->Index); } -//---------------------------------------------------------------------- // Starts the execution of a test. Returns once it has started bool cmCTestRunTest::StartTest(size_t total) { - cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(2*getNumWidth(total) + 8) - << "Start " - << std::setw(getNumWidth(this->TestHandler->GetMaxIndex())) - << this->TestProperties->Index << ": " - << this->TestProperties->Name << std::endl); + this->TotalNumberOfTests = total; // save for rerun case + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(2 * getNumWidth(total) + 8) + << "Start " + << std::setw(getNumWidth(this->TestHandler->GetMaxIndex())) + << this->TestProperties->Index << ": " + << this->TestProperties->Name << std::endl); + this->ProcessOutput.clear(); + + // Return immediately if test is disabled + if (this->TestProperties->Disabled) { + this->TestResult.Properties = this->TestProperties; + this->TestResult.ExecutionTime = 0; + this->TestResult.CompressOutput = false; + this->TestResult.ReturnValue = -1; + this->TestResult.CompletionStatus = "Disabled"; + this->TestResult.Status = cmCTestTestHandler::NOT_RUN; + this->TestResult.TestCount = this->TestProperties->Index; + this->TestResult.Name = this->TestProperties->Name; + this->TestResult.Path = this->TestProperties->Directory; + this->TestProcess = new cmProcess; + this->TestResult.Output = "Disabled"; + this->TestResult.FullCommandLine = ""; + return false; + } + this->ComputeArguments(); std::vector<std::string>& args = this->TestProperties->Args; this->TestResult.Properties = this->TestProperties; @@ -421,167 +450,175 @@ bool cmCTestRunTest::StartTest(size_t total) this->TestResult.Status = cmCTestTestHandler::BAD_COMMAND; this->TestResult.TestCount = this->TestProperties->Index; this->TestResult.Name = this->TestProperties->Name; - this->TestResult.Path = this->TestProperties->Directory.c_str(); + this->TestResult.Path = this->TestProperties->Directory; - if(args.size() >= 2 && args[1] == "NOT_AVAILABLE") - { + if (!this->FailedDependencies.empty()) { + this->TestProcess = new cmProcess; + std::string msg = "Failed test dependencies:"; + for (std::set<std::string>::const_iterator it = + this->FailedDependencies.begin(); + it != this->FailedDependencies.end(); ++it) { + msg += " " + *it; + } + *this->TestHandler->LogFile << msg << std::endl; + cmCTestLog(this->CTest, HANDLER_OUTPUT, msg << std::endl); + this->TestResult.Output = msg; + this->TestResult.FullCommandLine = ""; + this->TestResult.CompletionStatus = "Fixture dependency failed"; + this->TestResult.Status = cmCTestTestHandler::NOT_RUN; + return false; + } + + if (args.size() >= 2 && args[1] == "NOT_AVAILABLE") { this->TestProcess = new cmProcess; std::string msg; - if(this->CTest->GetConfigType().empty()) - { + if (this->CTest->GetConfigType().empty()) { msg = "Test not available without configuration."; msg += " (Missing \"-C <config>\"?)"; - } - else - { + } else { msg = "Test not available in configuration \""; msg += this->CTest->GetConfigType(); msg += "\"."; - } + } *this->TestHandler->LogFile << msg << std::endl; cmCTestLog(this->CTest, ERROR_MESSAGE, msg << std::endl); this->TestResult.Output = msg; this->TestResult.FullCommandLine = ""; - this->TestResult.CompletionStatus = "Not Run"; + this->TestResult.CompletionStatus = "Missing Configuration"; this->TestResult.Status = cmCTestTestHandler::NOT_RUN; return false; - } + } // Check if all required files exist - for(std::vector<std::string>::iterator i = - this->TestProperties->RequiredFiles.begin(); - i != this->TestProperties->RequiredFiles.end(); ++i) - { + for (std::vector<std::string>::iterator i = + this->TestProperties->RequiredFiles.begin(); + i != this->TestProperties->RequiredFiles.end(); ++i) { std::string file = *i; - if(!cmSystemTools::FileExists(file.c_str())) - { - //Required file was not found + if (!cmSystemTools::FileExists(file.c_str())) { + // Required file was not found this->TestProcess = new cmProcess; - *this->TestHandler->LogFile << "Unable to find required file: " - << file.c_str() << std::endl; - cmCTestLog(this->CTest, ERROR_MESSAGE, "Unable to find required file: " - << file.c_str() << std::endl); + *this->TestHandler->LogFile << "Unable to find required file: " << file + << std::endl; + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Unable to find required file: " << file << std::endl); this->TestResult.Output = "Unable to find required file: " + file; this->TestResult.FullCommandLine = ""; - this->TestResult.CompletionStatus = "Not Run"; + this->TestResult.CompletionStatus = "Required Files Missing"; this->TestResult.Status = cmCTestTestHandler::NOT_RUN; return false; - } } + } // log and return if we did not find the executable - if (this->ActualCommand == "") - { + if (this->ActualCommand == "") { // if the command was not found create a TestResult object // that has that information this->TestProcess = new cmProcess; - *this->TestHandler->LogFile << "Unable to find executable: " - << args[1].c_str() << std::endl; - cmCTestLog(this->CTest, ERROR_MESSAGE, "Unable to find executable: " - << args[1].c_str() << std::endl); + *this->TestHandler->LogFile << "Unable to find executable: " << args[1] + << std::endl; + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Unable to find executable: " << args[1] << std::endl); this->TestResult.Output = "Unable to find executable: " + args[1]; this->TestResult.FullCommandLine = ""; - this->TestResult.CompletionStatus = "Not Run"; + this->TestResult.CompletionStatus = "Unable to find executable"; this->TestResult.Status = cmCTestTestHandler::NOT_RUN; return false; - } + } this->StartTime = this->CTest->CurrentTime(); double timeout = this->ResolveTimeout(); - if(this->StopTimePassed) - { + if (this->StopTimePassed) { return false; - } + } return this->ForkProcess(timeout, this->TestProperties->ExplicitTimeout, &this->TestProperties->Environment); } -//---------------------------------------------------------------------- void cmCTestRunTest::ComputeArguments() { + this->Arguments.clear(); // reset becaue this might be a rerun std::vector<std::string>::const_iterator j = this->TestProperties->Args.begin(); ++j; // skip test name - // find the test executable - if(this->TestHandler->MemCheck) - { - cmCTestMemCheckHandler * handler = static_cast<cmCTestMemCheckHandler*> - (this->TestHandler); - this->ActualCommand = handler->MemoryTester.c_str(); + if (this->TestHandler->MemCheck) { + cmCTestMemCheckHandler* handler = + static_cast<cmCTestMemCheckHandler*>(this->TestHandler); + this->ActualCommand = handler->MemoryTester; this->TestProperties->Args[1] = this->TestHandler->FindTheExecutable( this->TestProperties->Args[1].c_str()); - } - else - { - this->ActualCommand = - this->TestHandler->FindTheExecutable( + } else { + this->ActualCommand = this->TestHandler->FindTheExecutable( this->TestProperties->Args[1].c_str()); - ++j; //skip the executable (it will be actualCommand) - } - std::string testCommand - = cmSystemTools::ConvertToOutputPath(this->ActualCommand.c_str()); + ++j; // skip the executable (it will be actualCommand) + } + std::string testCommand = + cmSystemTools::ConvertToOutputPath(this->ActualCommand.c_str()); - //Prepends memcheck args to our command string + // Prepends memcheck args to our command string this->TestHandler->GenerateTestCommand(this->Arguments, this->Index); - for(std::vector<std::string>::iterator i = this->Arguments.begin(); - i != this->Arguments.end(); ++i) - { + for (std::vector<std::string>::iterator i = this->Arguments.begin(); + i != this->Arguments.end(); ++i) { testCommand += " \""; testCommand += *i; testCommand += "\""; - } + } - for(;j != this->TestProperties->Args.end(); ++j) - { + for (; j != this->TestProperties->Args.end(); ++j) { testCommand += " \""; testCommand += *j; testCommand += "\""; this->Arguments.push_back(*j); - } + } this->TestResult.FullCommandLine = testCommand; + // Print the test command in verbose mode cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl - << this->Index << ": " - << (this->TestHandler->MemCheck?"MemCheck":"Test") - << " command: " << testCommand - << std::endl); + << this->Index << ": " + << (this->TestHandler->MemCheck ? "MemCheck" : "Test") + << " command: " << testCommand << std::endl); + + // Print any test-specific env vars in verbose mode + if (!this->TestProperties->Environment.empty()) { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->Index + << ": " + << "Environment variables: " << std::endl); + } + for (std::vector<std::string>::const_iterator e = + this->TestProperties->Environment.begin(); + e != this->TestProperties->Environment.end(); ++e) { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->Index << ": " << *e + << std::endl); + } } -//---------------------------------------------------------------------- void cmCTestRunTest::DartProcessing() { if (!this->ProcessOutput.empty() && - this->ProcessOutput.find("<DartMeasurement") != this->ProcessOutput.npos) - { - if (this->TestHandler->DartStuff.find(this->ProcessOutput.c_str())) - { - std::string dartString = this->TestHandler->DartStuff.match(1); + this->ProcessOutput.find("<DartMeasurement") != std::string::npos) { + if (this->TestHandler->DartStuff.find(this->ProcessOutput.c_str())) { + this->TestResult.DartString = this->TestHandler->DartStuff.match(1); // keep searching and replacing until none are left - while (this->TestHandler->DartStuff1.find(this->ProcessOutput.c_str())) - { + while (this->TestHandler->DartStuff1.find(this->ProcessOutput.c_str())) { // replace the exact match for the string - cmSystemTools::ReplaceString(this->ProcessOutput, - this->TestHandler->DartStuff1.match(1).c_str(), ""); - } - this->TestResult.RegressionImages - = this->TestHandler->GenerateRegressionImages(dartString); + cmSystemTools::ReplaceString( + this->ProcessOutput, this->TestHandler->DartStuff1.match(1).c_str(), + ""); } } + } } -//---------------------------------------------------------------------- double cmCTestRunTest::ResolveTimeout() { double timeout = this->TestProperties->Timeout; - if(this->CTest->GetStopTime() == "") - { + if (this->CTest->GetStopTime() == "") { return timeout; - } + } struct tm* lctime; - time_t current_time = time(0); + time_t current_time = time(CM_NULLPTR); lctime = gmtime(¤t_time); int gm_hour = lctime->tm_hour; time_t gm_time = mktime(lctime); @@ -589,87 +626,77 @@ double cmCTestRunTest::ResolveTimeout() int local_hour = lctime->tm_hour; int tzone_offset = local_hour - gm_hour; - if(gm_time > current_time && gm_hour < local_hour) - { + if (gm_time > current_time && gm_hour < local_hour) { // this means gm_time is on the next day tzone_offset -= 24; - } - else if(gm_time < current_time && gm_hour > local_hour) - { + } else if (gm_time < current_time && gm_hour > local_hour) { // this means gm_time is on the previous day tzone_offset += 24; - } + } tzone_offset *= 100; char buf[1024]; // add todays year day and month to the time in str because // curl_getdate no longer assumes the day is today - sprintf(buf, "%d%02d%02d %s %+05i", - lctime->tm_year + 1900, - lctime->tm_mon + 1, - lctime->tm_mday, - this->CTest->GetStopTime().c_str(), - tzone_offset); + sprintf(buf, "%d%02d%02d %s %+05i", lctime->tm_year + 1900, + lctime->tm_mon + 1, lctime->tm_mday, + this->CTest->GetStopTime().c_str(), tzone_offset); time_t stop_time = curl_getdate(buf, ¤t_time); - if(stop_time == -1) - { + if (stop_time == -1) { return timeout; - } - - //the stop time refers to the next day - if(this->CTest->NextDayStopTime) - { - stop_time += 24*60*60; - } - int stop_timeout = static_cast<int>(stop_time - current_time) % (24*60*60); + } + + // the stop time refers to the next day + if (this->CTest->NextDayStopTime) { + stop_time += 24 * 60 * 60; + } + int stop_timeout = + static_cast<int>(stop_time - current_time) % (24 * 60 * 60); this->CTest->LastStopTimeout = stop_timeout; - if(stop_timeout <= 0 || stop_timeout > this->CTest->LastStopTimeout) - { + if (stop_timeout <= 0 || stop_timeout > this->CTest->LastStopTimeout) { cmCTestLog(this->CTest, ERROR_MESSAGE, "The stop time has been passed. " - "Stopping all tests." << std::endl); + "Stopping all tests." + << std::endl); this->StopTimePassed = true; return 0; - } - return timeout == 0 ? stop_timeout : - (timeout < stop_timeout ? timeout : stop_timeout); + } + return timeout == 0 ? stop_timeout + : (timeout < stop_timeout ? timeout : stop_timeout); } -//---------------------------------------------------------------------- bool cmCTestRunTest::ForkProcess(double testTimeOut, bool explicitTimeout, - std::vector<std::string>* environment) + std::vector<std::string>* environment) { this->TestProcess = new cmProcess; this->TestProcess->SetId(this->Index); this->TestProcess->SetWorkingDirectory( - this->TestProperties->Directory.c_str()); + this->TestProperties->Directory.c_str()); this->TestProcess->SetCommand(this->ActualCommand.c_str()); this->TestProcess->SetCommandArguments(this->Arguments); // determine how much time we have double timeout = this->CTest->GetRemainingTimeAllowed() - 120; - if (this->CTest->GetTimeOut() > 0 && this->CTest->GetTimeOut() < timeout) - { + if (this->CTest->GetTimeOut() > 0 && this->CTest->GetTimeOut() < timeout) { timeout = this->CTest->GetTimeOut(); - } - if (testTimeOut > 0 - && testTimeOut < this->CTest->GetRemainingTimeAllowed()) - { + } + if (testTimeOut > 0 && + testTimeOut < this->CTest->GetRemainingTimeAllowed()) { timeout = testTimeOut; - } + } // always have at least 1 second if we got to here - if (timeout <= 0) - { + if (timeout <= 0) { timeout = 1; - } + } // handle timeout explicitly set to 0 - if (testTimeOut == 0 && explicitTimeout) - { + if (testTimeOut == 0 && explicitTimeout) { timeout = 0; - } - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->Index << ": " - << "Test timeout computed to be: " << timeout << "\n"); + } + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->Index + << ": " + << "Test timeout computed to be: " << timeout << "\n", + this->TestHandler->GetQuiet()); this->TestProcess->SetTimeout(timeout); @@ -677,67 +704,82 @@ bool cmCTestRunTest::ForkProcess(double testTimeOut, bool explicitTimeout, cmSystemTools::SaveRestoreEnvironment sre; #endif - if (environment && environment->size()>0) - { + if (environment && !environment->empty()) { cmSystemTools::AppendEnv(*environment); - } + } return this->TestProcess->StartProcess(); } void cmCTestRunTest::WriteLogOutputTop(size_t completed, size_t total) { - cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(getNumWidth(total)) - << completed << "/"); - cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(getNumWidth(total)) - << total << " "); - - if ( this->TestHandler->MemCheck ) - { + // if this is the last or only run of this test + // then print out completed / total + // Only issue is if a test fails and we are running until fail + // then it will never print out the completed / total, same would + // got for run until pass. Trick is when this is called we don't + // yet know if we are passing or failing. + if (this->NumberOfRunsLeft == 1) { + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(getNumWidth(total)) + << completed << "/"); + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(getNumWidth(total)) + << total << " "); + } + // if this is one of several runs of a test just print blank space + // to keep things neat + else { + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(getNumWidth(total)) + << " " + << " "); + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(getNumWidth(total)) + << " " + << " "); + } + + if (this->TestHandler->MemCheck) { cmCTestLog(this->CTest, HANDLER_OUTPUT, "MemCheck"); - } - else - { + } else { cmCTestLog(this->CTest, HANDLER_OUTPUT, "Test"); - } + } - cmOStringStream indexStr; + std::ostringstream indexStr; indexStr << " #" << this->Index << ":"; cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(3 + getNumWidth(this->TestHandler->GetMaxIndex())) - << indexStr.str().c_str()); + << indexStr.str()); cmCTestLog(this->CTest, HANDLER_OUTPUT, " "); const int maxTestNameWidth = this->CTest->GetMaxTestNameWidth(); std::string outname = this->TestProperties->Name + " "; outname.resize(maxTestNameWidth + 4, '.'); *this->TestHandler->LogFile << this->TestProperties->Index << "/" - << this->TestHandler->TotalNumberOfTests << " Testing: " - << this->TestProperties->Name << std::endl; + << this->TestHandler->TotalNumberOfTests + << " Testing: " << this->TestProperties->Name + << std::endl; *this->TestHandler->LogFile << this->TestProperties->Index << "/" - << this->TestHandler->TotalNumberOfTests - << " Test: " << this->TestProperties->Name.c_str() << std::endl; + << this->TestHandler->TotalNumberOfTests + << " Test: " << this->TestProperties->Name + << std::endl; *this->TestHandler->LogFile << "Command: \"" << this->ActualCommand << "\""; for (std::vector<std::string>::iterator i = this->Arguments.begin(); - i != this->Arguments.end(); ++i) - { - *this->TestHandler->LogFile - << " \"" << i->c_str() << "\""; - } - *this->TestHandler->LogFile << std::endl + i != this->Arguments.end(); ++i) { + *this->TestHandler->LogFile << " \"" << *i << "\""; + } + *this->TestHandler->LogFile + << std::endl << "Directory: " << this->TestProperties->Directory << std::endl - << "\"" << this->TestProperties->Name.c_str() << "\" start time: " - << this->StartTime << std::endl; + << "\"" << this->TestProperties->Name + << "\" start time: " << this->StartTime << std::endl; *this->TestHandler->LogFile << "Output:" << std::endl << "----------------------------------------------------------" << std::endl; - *this->TestHandler->LogFile - << this->ProcessOutput.c_str() << "<end of output>" << std::endl; + *this->TestHandler->LogFile << this->ProcessOutput << "<end of output>" + << std::endl; cmCTestLog(this->CTest, HANDLER_OUTPUT, outname.c_str()); - cmCTestLog(this->CTest, DEBUG, "Testing " - << this->TestProperties->Name.c_str() << " ... "); + cmCTestLog(this->CTest, DEBUG, "Testing " << this->TestProperties->Name + << " ... "); } diff --git a/Source/CTest/cmCTestRunTest.h b/Source/CTest/cmCTestRunTest.h index 476f3e126..d3bb2296f 100644 --- a/Source/CTest/cmCTestRunTest.h +++ b/Source/CTest/cmCTestRunTest.h @@ -1,20 +1,19 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestRunTest_h #define cmCTestRunTest_h -#include <cmStandardIncludes.h> -#include <cmCTestTestHandler.h> -#include <cmProcess.h> +#include "cmConfigure.h" // IWYU pragma: keep + +#include <set> +#include <stddef.h> +#include <string> +#include <vector> + +#include "cmCTestTestHandler.h" + +class cmCTest; +class cmProcess; /** \class cmRunTest * \brief represents a single test to be run @@ -27,22 +26,35 @@ public: cmCTestRunTest(cmCTestTestHandler* handler); ~cmCTestRunTest(); - void SetTestProperties(cmCTestTestHandler::cmCTestTestProperties * prop) - { this->TestProperties = prop; } + void SetNumberOfRuns(int n) { this->NumberOfRunsLeft = n; } + void SetRunUntilFailOn() { this->RunUntilFail = true; } + void SetTestProperties(cmCTestTestHandler::cmCTestTestProperties* prop) + { + this->TestProperties = prop; + } - cmCTestTestHandler::cmCTestTestProperties * GetTestProperties() - { return this->TestProperties; } + cmCTestTestHandler::cmCTestTestProperties* GetTestProperties() + { + return this->TestProperties; + } void SetIndex(int i) { this->Index = i; } int GetIndex() { return this->Index; } + void AddFailedDependency(const std::string& failedTest) + { + this->FailedDependencies.insert(failedTest); + } + std::string GetProcessOutput() { return this->ProcessOutput; } bool IsStopTimePassed() { return this->StopTimePassed; } cmCTestTestHandler::cmCTestTestResult GetTestResults() - { return this->TestResult; } + { + return this->TestResult; + } // Read and store output. Returns true if it must be called again. bool CheckOutput(); @@ -50,15 +62,19 @@ public: // Compresses the output, writing to CompressedOutput void CompressOutput(); - //launch the test process, return whether it started correctly + // launch the test process, return whether it started correctly bool StartTest(size_t total); - //capture and report the test results + // capture and report the test results bool EndTest(size_t completed, size_t total, bool started); - //Called by ctest -N to log the command string + // Called by ctest -N to log the command string void ComputeArguments(); void ComputeWeightedCost(); + + bool StartAgain(); + private: + bool NeedsToRerun(); void DartProcessing(); void ExeNotFound(std::string exe); // Figures out a final timeout which is min(STOP_TIME, NOW+TIMEOUT) @@ -66,18 +82,18 @@ private: bool ForkProcess(double testTimeOut, bool explicitTimeout, std::vector<std::string>* environment); void WriteLogOutputTop(size_t completed, size_t total); - //Run post processing of the process output for MemCheck + // Run post processing of the process output for MemCheck void MemCheckPostProcess(); - cmCTestTestHandler::cmCTestTestProperties * TestProperties; - //Pointer back to the "parent"; the handler that invoked this test run - cmCTestTestHandler * TestHandler; - cmCTest * CTest; - cmProcess * TestProcess; - //If the executable to run is ctest, don't create a new process; - //just instantiate a new cmTest. (Can be disabled for a single test - //if this option is set to false.) - //bool OptimizeForCTest; + cmCTestTestHandler::cmCTestTestProperties* TestProperties; + // Pointer back to the "parent"; the handler that invoked this test run + cmCTestTestHandler* TestHandler; + cmCTest* CTest; + cmProcess* TestProcess; + // If the executable to run is ctest, don't create a new process; + // just instantiate a new cmTest. (Can be disabled for a single test + // if this option is set to false.) + // bool OptimizeForCTest; bool UsePrefixCommand; std::string PrefixCommand; @@ -85,28 +101,30 @@ private: std::string ProcessOutput; std::string CompressedOutput; double CompressionRatio; - //The test results + // The test results cmCTestTestHandler::cmCTestTestResult TestResult; int Index; + std::set<std::string> FailedDependencies; std::string StartTime; std::string ActualCommand; std::vector<std::string> Arguments; bool StopTimePassed; + bool RunUntilFail; + int NumberOfRunsLeft; + bool RunAgain; + size_t TotalNumberOfTests; }; inline int getNumWidth(size_t n) { int numWidth = 1; - if(n >= 10) - { + if (n >= 10) { numWidth = 2; - } - if(n >= 100) - { + } + if (n >= 100) { numWidth = 3; - } + } return numWidth; } #endif - diff --git a/Source/CTest/cmCTestSVN.cxx b/Source/CTest/cmCTestSVN.cxx index 2668c8eef..f60f78c21 100644 --- a/Source/CTest/cmCTestSVN.cxx +++ b/Source/CTest/cmCTestSVN.cxx @@ -1,41 +1,35 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestSVN.h" #include "cmCTest.h" +#include "cmCTestVC.h" +#include "cmProcessTools.h" #include "cmSystemTools.h" #include "cmXMLParser.h" -#include "cmXMLSafe.h" +#include "cmXMLWriter.h" -#include <cmsys/RegularExpression.hxx> +#include "cmsys/RegularExpression.hxx" +#include <map> +#include <ostream> +#include <stdlib.h> +#include <string.h> -struct cmCTestSVN::Revision: public cmCTestVC::Revision +struct cmCTestSVN::Revision : public cmCTestVC::Revision { cmCTestSVN::SVNInfo* SVNInfo; }; -//---------------------------------------------------------------------------- -cmCTestSVN::cmCTestSVN(cmCTest* ct, std::ostream& log): - cmCTestGlobalVC(ct, log) +cmCTestSVN::cmCTestSVN(cmCTest* ct, std::ostream& log) + : cmCTestGlobalVC(ct, log) { this->PriorRev = this->Unknown; } -//---------------------------------------------------------------------------- cmCTestSVN::~cmCTestSVN() { } -//---------------------------------------------------------------------------- void cmCTestSVN::CleanupImpl() { std::vector<const char*> svn_cleanup; @@ -45,64 +39,51 @@ void cmCTestSVN::CleanupImpl() this->RunSVNCommand(svn_cleanup, &out, &err); } -//---------------------------------------------------------------------------- -class cmCTestSVN::InfoParser: public cmCTestVC::LineParser +class cmCTestSVN::InfoParser : public cmCTestVC::LineParser { public: - InfoParser(cmCTestSVN* svn, - const char* prefix, - std::string& rev, - SVNInfo& svninfo): - Rev(rev), SVNRepo(svninfo) - { + InfoParser(cmCTestSVN* svn, const char* prefix, std::string& rev, + SVNInfo& svninfo) + : Rev(rev) + , SVNRepo(svninfo) + { this->SetLog(&svn->Log, prefix); this->RegexRev.compile("^Revision: ([0-9]+)"); this->RegexURL.compile("^URL: +([^ ]+) *$"); this->RegexRoot.compile("^Repository Root: +([^ ]+) *$"); - } + } + private: std::string& Rev; cmCTestSVN::SVNInfo& SVNRepo; cmsys::RegularExpression RegexRev; cmsys::RegularExpression RegexURL; cmsys::RegularExpression RegexRoot; - virtual bool ProcessLine() - { - if(this->RegexRev.find(this->Line)) - { + bool ProcessLine() CM_OVERRIDE + { + if (this->RegexRev.find(this->Line)) { this->Rev = this->RegexRev.match(1); - } - else if(this->RegexURL.find(this->Line)) - { + } else if (this->RegexURL.find(this->Line)) { this->SVNRepo.URL = this->RegexURL.match(1); - } - else if(this->RegexRoot.find(this->Line)) - { + } else if (this->RegexRoot.find(this->Line)) { this->SVNRepo.Root = this->RegexRoot.match(1); - } - return true; } + return true; + } }; -//---------------------------------------------------------------------------- static bool cmCTestSVNPathStarts(std::string const& p1, std::string const& p2) { // Does path p1 start with path p2? - if(p1.size() == p2.size()) - { + if (p1.size() == p2.size()) { return p1 == p2; - } - else if(p1.size() > p2.size() && p1[p2.size()] == '/') - { + } + if (p1.size() > p2.size() && p1[p2.size()] == '/') { return strncmp(p1.c_str(), p2.c_str(), p2.size()) == 0; - } - else - { - return false; - } + } + return false; } -//---------------------------------------------------------------------------- std::string cmCTestSVN::LoadInfo(SVNInfo& svninfo) { // Run "svn info" to get the repository info from the work tree. @@ -116,51 +97,46 @@ std::string cmCTestSVN::LoadInfo(SVNInfo& svninfo) return rev; } -//---------------------------------------------------------------------------- -void cmCTestSVN::NoteOldRevision() +bool cmCTestSVN::NoteOldRevision() { - // Info for root repository - this->Repositories.push_back( SVNInfo("") ); - this->RootInfo = &(this->Repositories.back()); - // Info for the external repositories - this->LoadExternals(); - - // Get info for all the repositories - std::list<SVNInfo>::iterator itbeg = this->Repositories.begin(); - std::list<SVNInfo>::iterator itend = this->Repositories.end(); - for( ; itbeg != itend ; itbeg++) - { + if (!this->LoadRepositories()) { + return false; + } + + std::vector<SVNInfo>::iterator itbeg = this->Repositories.begin(); + std::vector<SVNInfo>::iterator itend = this->Repositories.end(); + for (; itbeg != itend; itbeg++) { SVNInfo& svninfo = *itbeg; svninfo.OldRevision = this->LoadInfo(svninfo); this->Log << "Revision for repository '" << svninfo.LocalPath << "' before update: " << svninfo.OldRevision << "\n"; - cmCTestLog(this->CTest, HANDLER_OUTPUT, - " Old revision of external repository '" - << svninfo.LocalPath << "' is: " - << svninfo.OldRevision << "\n"); - } + cmCTestLog( + this->CTest, HANDLER_OUTPUT, " Old revision of external repository '" + << svninfo.LocalPath << "' is: " << svninfo.OldRevision << "\n"); + } // Set the global old revision to the one of the root this->OldRevision = this->RootInfo->OldRevision; this->PriorRev.Rev = this->OldRevision; + return true; } -//---------------------------------------------------------------------------- -void cmCTestSVN::NoteNewRevision() +bool cmCTestSVN::NoteNewRevision() { - // Get info for the external repositories - std::list<SVNInfo>::iterator itbeg = this->Repositories.begin(); - std::list<SVNInfo>::iterator itend = this->Repositories.end(); - for( ; itbeg != itend ; itbeg++) - { + if (!this->LoadRepositories()) { + return false; + } + + std::vector<SVNInfo>::iterator itbeg = this->Repositories.begin(); + std::vector<SVNInfo>::iterator itend = this->Repositories.end(); + for (; itbeg != itend; itbeg++) { SVNInfo& svninfo = *itbeg; svninfo.NewRevision = this->LoadInfo(svninfo); this->Log << "Revision for repository '" << svninfo.LocalPath << "' after update: " << svninfo.NewRevision << "\n"; - cmCTestLog(this->CTest, HANDLER_OUTPUT, - " New revision of external repository '" - << svninfo.LocalPath << "' is: " - << svninfo.NewRevision << "\n"); + cmCTestLog( + this->CTest, HANDLER_OUTPUT, " New revision of external repository '" + << svninfo.LocalPath << "' is: " << svninfo.NewRevision << "\n"); // svninfo.Root = ""; // uncomment to test GuessBase this->Log << "Repository '" << svninfo.LocalPath @@ -170,23 +146,21 @@ void cmCTestSVN::NoteNewRevision() // Compute the base path the working tree has checked out under // the repository root. - if(!svninfo.Root.empty() - && cmCTestSVNPathStarts(svninfo.URL, svninfo.Root)) - { - svninfo.Base = cmCTest::DecodeURL( - svninfo.URL.substr(svninfo.Root.size())); + if (!svninfo.Root.empty() && + cmCTestSVNPathStarts(svninfo.URL, svninfo.Root)) { + svninfo.Base = + cmCTest::DecodeURL(svninfo.URL.substr(svninfo.Root.size())); svninfo.Base += "/"; - } + } this->Log << "Repository '" << svninfo.LocalPath << "' Base = " << svninfo.Base << "\n"; - } // Set the global new revision to the one of the root this->NewRevision = this->RootInfo->NewRevision; + return true; } -//---------------------------------------------------------------------------- void cmCTestSVN::GuessBase(SVNInfo& svninfo, std::vector<Change> const& changes) { @@ -195,21 +169,18 @@ void cmCTestSVN::GuessBase(SVNInfo& svninfo, // changes under it. // Consider each possible URL suffix from longest to shortest. - for(std::string::size_type slash = svninfo.URL.find('/'); - svninfo.Base.empty() && slash != std::string::npos; - slash = svninfo.URL.find('/', slash+1)) - { + for (std::string::size_type slash = svninfo.URL.find('/'); + svninfo.Base.empty() && slash != std::string::npos; + slash = svninfo.URL.find('/', slash + 1)) { // If the URL suffix is a prefix of at least one path then it is the base. std::string base = cmCTest::DecodeURL(svninfo.URL.substr(slash)); - for(std::vector<Change>::const_iterator ci = changes.begin(); - svninfo.Base.empty() && ci != changes.end(); ++ci) - { - if(cmCTestSVNPathStarts(ci->Path, base)) - { + for (std::vector<Change>::const_iterator ci = changes.begin(); + svninfo.Base.empty() && ci != changes.end(); ++ci) { + if (cmCTestSVNPathStarts(ci->Path, base)) { svninfo.Base = base; - } } } + } // We always append a slash so that we know paths beginning in the // base lie under its path. If no base was found then the working @@ -220,89 +191,88 @@ void cmCTestSVN::GuessBase(SVNInfo& svninfo, this->Log << "Guessed Base = " << svninfo.Base << "\n"; } -//---------------------------------------------------------------------------- -class cmCTestSVN::UpdateParser: public cmCTestVC::LineParser +class cmCTestSVN::UpdateParser : public cmCTestVC::LineParser { public: - UpdateParser(cmCTestSVN* svn, const char* prefix): SVN(svn) - { + UpdateParser(cmCTestSVN* svn, const char* prefix) + : SVN(svn) + { this->SetLog(&svn->Log, prefix); this->RegexUpdate.compile("^([ADUCGE ])([ADUCGE ])[B ] +(.+)$"); - } + } + private: cmCTestSVN* SVN; cmsys::RegularExpression RegexUpdate; - bool ProcessLine() - { - if(this->RegexUpdate.find(this->Line)) - { + bool ProcessLine() CM_OVERRIDE + { + if (this->RegexUpdate.find(this->Line)) { this->DoPath(this->RegexUpdate.match(1)[0], - this->RegexUpdate.match(2)[0], - this->RegexUpdate.match(3)); - } - return true; + this->RegexUpdate.match(2)[0], this->RegexUpdate.match(3)); } + return true; + } void DoPath(char path_status, char prop_status, std::string const& path) - { - char status = (path_status != ' ')? path_status : prop_status; + { + char status = (path_status != ' ') ? path_status : prop_status; std::string dir = cmSystemTools::GetFilenamePath(path); std::string name = cmSystemTools::GetFilenameName(path); // See "svn help update". - switch(status) - { + switch (status) { case 'G': this->SVN->Dirs[dir][name].Status = PathModified; break; case 'C': this->SVN->Dirs[dir][name].Status = PathConflicting; break; - case 'A': case 'D': case 'U': + case 'A': + case 'D': + case 'U': this->SVN->Dirs[dir][name].Status = PathUpdated; break; case 'E': // TODO? - case '?': case ' ': default: + case '?': + case ' ': + default: break; - } } + } }; -//---------------------------------------------------------------------------- bool cmCTestSVN::UpdateImpl() { // Get user-specified update options. std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions"); - if(opts.empty()) - { + if (opts.empty()) { opts = this->CTest->GetCTestConfiguration("SVNUpdateOptions"); - } - std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str()); + } + std::vector<std::string> args = cmSystemTools::ParseArguments(opts.c_str()); // Specify the start time for nightly testing. - if(this->CTest->GetTestModel() == cmCTest::NIGHTLY) - { + if (this->CTest->GetTestModel() == cmCTest::NIGHTLY) { args.push_back("-r{" + this->GetNightlyTime() + " +0000}"); - } + } std::vector<char const*> svn_update; svn_update.push_back("update"); - for(std::vector<cmStdString>::const_iterator ai = args.begin(); - ai != args.end(); ++ai) - { + for (std::vector<std::string>::const_iterator ai = args.begin(); + ai != args.end(); ++ai) { svn_update.push_back(ai->c_str()); - } + } UpdateParser out(this, "up-out> "); OutputLogger err(this->Log, "up-err> "); return this->RunSVNCommand(svn_update, &out, &err); } -//---------------------------------------------------------------------------- bool cmCTestSVN::RunSVNCommand(std::vector<char const*> const& parameters, - OutputParser* out, OutputParser* err) + OutputParser* out, OutputParser* err) { - if(parameters.empty()) return false; + if (parameters.empty()) { + return false; + } std::vector<char const*> args; args.push_back(this->CommandLineTool.c_str()); @@ -311,38 +281,35 @@ bool cmCTestSVN::RunSVNCommand(std::vector<char const*> const& parameters, args.push_back("--non-interactive"); - std::string userOptions = - this->CTest->GetCTestConfiguration("SVNOptions"); + std::string userOptions = this->CTest->GetCTestConfiguration("SVNOptions"); - std::vector<cmStdString> parsedUserOptions = + std::vector<std::string> parsedUserOptions = cmSystemTools::ParseArguments(userOptions.c_str()); - for(std::vector<cmStdString>::iterator i = parsedUserOptions.begin(); - i != parsedUserOptions.end(); ++i) - { + for (std::vector<std::string>::iterator i = parsedUserOptions.begin(); + i != parsedUserOptions.end(); ++i) { args.push_back(i->c_str()); - } + } - args.push_back(0); + args.push_back(CM_NULLPTR); - if(strcmp(parameters[0], "update") == 0) - { + if (strcmp(parameters[0], "update") == 0) { return RunUpdateCommand(&args[0], out, err); - } - else - { - return RunChild(&args[0], out, err); - } + } + return RunChild(&args[0], out, err); } -//---------------------------------------------------------------------------- -class cmCTestSVN::LogParser: public cmCTestVC::OutputLogger, - private cmXMLParser +class cmCTestSVN::LogParser : public cmCTestVC::OutputLogger, + private cmXMLParser { public: - LogParser(cmCTestSVN* svn, const char* prefix, SVNInfo& svninfo): - OutputLogger(svn->Log, prefix), SVN(svn), SVNRepo(svninfo) - { this->InitializeParser(); } - ~LogParser() { this->CleanupParser(); } + LogParser(cmCTestSVN* svn, const char* prefix, SVNInfo& svninfo) + : OutputLogger(svn->Log, prefix) + , SVN(svn) + , SVNRepo(svninfo) + { + this->InitializeParser(); + } + ~LogParser() CM_OVERRIDE { this->CleanupParser(); } private: cmCTestSVN* SVN; cmCTestSVN::SVNInfo& SVNRepo; @@ -354,101 +321,83 @@ private: Change CurChange; std::vector<char> CData; - virtual bool ProcessChunk(const char* data, int length) - { + bool ProcessChunk(const char* data, int length) CM_OVERRIDE + { this->OutputLogger::ProcessChunk(data, length); this->ParseChunk(data, length); return true; - } + } - virtual void StartElement(const char* name, const char** atts) - { + void StartElement(const std::string& name, const char** atts) CM_OVERRIDE + { this->CData.clear(); - if(strcmp(name, "logentry") == 0) - { + if (name == "logentry") { this->Rev = Revision(); this->Rev.SVNInfo = &SVNRepo; - if(const char* rev = this->FindAttribute(atts, "revision")) - { + if (const char* rev = this->FindAttribute(atts, "revision")) { this->Rev.Rev = rev; - } - this->Changes.clear(); } - else if(strcmp(name, "path") == 0) - { + this->Changes.clear(); + } else if (name == "path") { this->CurChange = Change(); - if(const char* action = this->FindAttribute(atts, "action")) - { + if (const char* action = this->FindAttribute(atts, "action")) { this->CurChange.Action = action[0]; - } } } + } - virtual void CharacterDataHandler(const char* data, int length) - { - this->CData.insert(this->CData.end(), data, data+length); - } + void CharacterDataHandler(const char* data, int length) CM_OVERRIDE + { + this->CData.insert(this->CData.end(), data, data + length); + } - virtual void EndElement(const char* name) - { - if(strcmp(name, "logentry") == 0) - { + void EndElement(const std::string& name) CM_OVERRIDE + { + if (name == "logentry") { this->SVN->DoRevisionSVN(this->Rev, this->Changes); - } - else if(strcmp(name, "path") == 0 && !this->CData.empty()) - { + } else if (!this->CData.empty() && name == "path") { std::string orig_path(&this->CData[0], this->CData.size()); - std::string new_path = SVNRepo.BuildLocalPath( orig_path ); + std::string new_path = SVNRepo.BuildLocalPath(orig_path); this->CurChange.Path.assign(new_path); this->Changes.push_back(this->CurChange); - } - else if(strcmp(name, "author") == 0 && !this->CData.empty()) - { + } else if (!this->CData.empty() && name == "author") { this->Rev.Author.assign(&this->CData[0], this->CData.size()); - } - else if(strcmp(name, "date") == 0 && !this->CData.empty()) - { + } else if (!this->CData.empty() && name == "date") { this->Rev.Date.assign(&this->CData[0], this->CData.size()); - } - else if(strcmp(name, "msg") == 0 && !this->CData.empty()) - { + } else if (!this->CData.empty() && name == "msg") { this->Rev.Log.assign(&this->CData[0], this->CData.size()); - } - this->CData.clear(); } + this->CData.clear(); + } - virtual void ReportError(int, int, const char* msg) - { + void ReportError(int /*line*/, int /*column*/, const char* msg) CM_OVERRIDE + { this->SVN->Log << "Error parsing svn log xml: " << msg << "\n"; - } + } }; -//---------------------------------------------------------------------------- -void cmCTestSVN::LoadRevisions() +bool cmCTestSVN::LoadRevisions() { + bool result = true; // Get revisions for all the external repositories - std::list<SVNInfo>::iterator itbeg = this->Repositories.begin(); - std::list<SVNInfo>::iterator itend = this->Repositories.end(); - for( ; itbeg != itend ; itbeg++) - { + std::vector<SVNInfo>::iterator itbeg = this->Repositories.begin(); + std::vector<SVNInfo>::iterator itend = this->Repositories.end(); + for (; itbeg != itend; itbeg++) { SVNInfo& svninfo = *itbeg; - LoadRevisions(svninfo); - } + result = this->LoadRevisions(svninfo) && result; + } + return result; } -//---------------------------------------------------------------------------- -void cmCTestSVN::LoadRevisions(SVNInfo &svninfo) +bool cmCTestSVN::LoadRevisions(SVNInfo& svninfo) { // We are interested in every revision included in the update. std::string revs; - if(atoi(svninfo.OldRevision.c_str()) < atoi(svninfo.NewRevision.c_str())) - { + if (atoi(svninfo.OldRevision.c_str()) < atoi(svninfo.NewRevision.c_str())) { revs = "-r" + svninfo.OldRevision + ":" + svninfo.NewRevision; - } - else - { + } else { revs = "-r" + svninfo.NewRevision; - } + } // Run "svn log" to get all global revisions of interest. std::vector<const char*> svn_log; @@ -459,72 +408,75 @@ void cmCTestSVN::LoadRevisions(SVNInfo &svninfo) svn_log.push_back(svninfo.LocalPath.c_str()); LogParser out(this, "log-out> ", svninfo); OutputLogger err(this->Log, "log-err> "); - this->RunSVNCommand(svn_log, &out, &err); + return this->RunSVNCommand(svn_log, &out, &err); } -//---------------------------------------------------------------------------- void cmCTestSVN::DoRevisionSVN(Revision const& revision, std::vector<Change> const& changes) { // Guess the base checkout path from the changes if necessary. - if(this->RootInfo->Base.empty() && !changes.empty()) - { + if (this->RootInfo->Base.empty() && !changes.empty()) { this->GuessBase(*this->RootInfo, changes); - } + } // Ignore changes in the old revision for external repositories - if(revision.Rev == revision.SVNInfo->OldRevision - && revision.SVNInfo->LocalPath != "") - { + if (revision.Rev == revision.SVNInfo->OldRevision && + revision.SVNInfo->LocalPath != "") { return; - } + } this->cmCTestGlobalVC::DoRevision(revision, changes); } -//---------------------------------------------------------------------------- -class cmCTestSVN::StatusParser: public cmCTestVC::LineParser +class cmCTestSVN::StatusParser : public cmCTestVC::LineParser { public: - StatusParser(cmCTestSVN* svn, const char* prefix): SVN(svn) - { + StatusParser(cmCTestSVN* svn, const char* prefix) + : SVN(svn) + { this->SetLog(&svn->Log, prefix); this->RegexStatus.compile("^([ACDIMRX?!~ ])([CM ])[ L]... +(.+)$"); - } + } + private: cmCTestSVN* SVN; cmsys::RegularExpression RegexStatus; - bool ProcessLine() - { - if(this->RegexStatus.find(this->Line)) - { + bool ProcessLine() CM_OVERRIDE + { + if (this->RegexStatus.find(this->Line)) { this->DoPath(this->RegexStatus.match(1)[0], - this->RegexStatus.match(2)[0], - this->RegexStatus.match(3)); - } - return true; + this->RegexStatus.match(2)[0], this->RegexStatus.match(3)); } + return true; + } void DoPath(char path_status, char prop_status, std::string const& path) - { - char status = (path_status != ' ')? path_status : prop_status; + { + char status = (path_status != ' ') ? path_status : prop_status; // See "svn help status". - switch(status) - { - case 'M': case '!': case 'A': case 'D': case 'R': + switch (status) { + case 'M': + case '!': + case 'A': + case 'D': + case 'R': this->SVN->DoModification(PathModified, path); break; - case 'C': case '~': + case 'C': + case '~': this->SVN->DoModification(PathConflicting, path); break; - case 'X': case 'I': case '?': case ' ': default: + case 'X': + case 'I': + case '?': + case ' ': + default: break; - } } + } }; -//---------------------------------------------------------------------------- -void cmCTestSVN::LoadModifications() +bool cmCTestSVN::LoadModifications() { // Run "svn status" which reports local modifications. std::vector<const char*> svn_status; @@ -532,86 +484,85 @@ void cmCTestSVN::LoadModifications() StatusParser out(this, "status-out> "); OutputLogger err(this->Log, "status-err> "); this->RunSVNCommand(svn_status, &out, &err); + return true; } -//---------------------------------------------------------------------------- -void cmCTestSVN::WriteXMLGlobal(std::ostream& xml) +void cmCTestSVN::WriteXMLGlobal(cmXMLWriter& xml) { this->cmCTestGlobalVC::WriteXMLGlobal(xml); - xml << "\t<SVNPath>" << this->RootInfo->Base << "</SVNPath>\n"; + xml.Element("SVNPath", this->RootInfo->Base); } -//---------------------------------------------------------------------------- -class cmCTestSVN::ExternalParser: public cmCTestVC::LineParser +class cmCTestSVN::ExternalParser : public cmCTestVC::LineParser { public: - ExternalParser(cmCTestSVN* svn, const char* prefix): SVN(svn) - { + ExternalParser(cmCTestSVN* svn, const char* prefix) + : SVN(svn) + { this->SetLog(&svn->Log, prefix); this->RegexExternal.compile("^X..... +(.+)$"); - } + } + private: cmCTestSVN* SVN; cmsys::RegularExpression RegexExternal; - bool ProcessLine() - { - if(this->RegexExternal.find(this->Line)) - { + bool ProcessLine() CM_OVERRIDE + { + if (this->RegexExternal.find(this->Line)) { this->DoPath(this->RegexExternal.match(1)); - } - return true; } + return true; + } void DoPath(std::string const& path) - { + { // Get local path relative to the source directory std::string local_path; - if(path.size() > this->SVN->SourceDirectory.size() && - strncmp(path.c_str(), this->SVN->SourceDirectory.c_str(), - this->SVN->SourceDirectory.size()) == 0) - { + if (path.size() > this->SVN->SourceDirectory.size() && + strncmp(path.c_str(), this->SVN->SourceDirectory.c_str(), + this->SVN->SourceDirectory.size()) == 0) { local_path = path.c_str() + this->SVN->SourceDirectory.size() + 1; - } - else - { + } else { local_path = path; - } - this->SVN->Repositories.push_back( SVNInfo(local_path.c_str()) ); } + this->SVN->Repositories.push_back(SVNInfo(local_path.c_str())); + } }; -//---------------------------------------------------------------------------- -void cmCTestSVN::LoadExternals() +bool cmCTestSVN::LoadRepositories() { + if (!this->Repositories.empty()) { + return true; + } + + // Info for root repository + this->Repositories.push_back(SVNInfo("")); + this->RootInfo = &(this->Repositories.back()); + // Run "svn status" to get the list of external repositories std::vector<const char*> svn_status; svn_status.push_back("status"); ExternalParser out(this, "external-out> "); OutputLogger err(this->Log, "external-err> "); - this->RunSVNCommand(svn_status, &out, &err); + return this->RunSVNCommand(svn_status, &out, &err); } -//---------------------------------------------------------------------------- std::string cmCTestSVN::SVNInfo::BuildLocalPath(std::string const& path) const { std::string local_path; // Add local path prefix if not empty - if (!this->LocalPath.empty()) - { + if (!this->LocalPath.empty()) { local_path += this->LocalPath; local_path += "/"; - } + } // Add path with base prefix removed - if(path.size() > this->Base.size() && - strncmp(path.c_str(), this->Base.c_str(), this->Base.size()) == 0) - { + if (path.size() > this->Base.size() && + strncmp(path.c_str(), this->Base.c_str(), this->Base.size()) == 0) { local_path += (path.c_str() + this->Base.size()); - } - else - { + } else { local_path += path; } diff --git a/Source/CTest/cmCTestSVN.h b/Source/CTest/cmCTestSVN.h index c6548e3fb..46b077835 100644 --- a/Source/CTest/cmCTestSVN.h +++ b/Source/CTest/cmCTestSVN.h @@ -1,45 +1,49 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestSVN_h #define cmCTestSVN_h +#include "cmConfigure.h" + #include "cmCTestGlobalVC.h" +#include <iosfwd> +#include <string> +#include <vector> + +class cmCTest; +class cmXMLWriter; + /** \class cmCTestSVN * \brief Interaction with subversion command-line tool * */ -class cmCTestSVN: public cmCTestGlobalVC +class cmCTestSVN : public cmCTestGlobalVC { public: /** Construct with a CTest instance and update log stream. */ cmCTestSVN(cmCTest* ctest, std::ostream& log); - virtual ~cmCTestSVN(); + ~cmCTestSVN() CM_OVERRIDE; private: // Implement cmCTestVC internal API. - virtual void CleanupImpl(); - virtual void NoteOldRevision(); - virtual void NoteNewRevision(); - virtual bool UpdateImpl(); + void CleanupImpl() CM_OVERRIDE; + bool NoteOldRevision() CM_OVERRIDE; + bool NoteNewRevision() CM_OVERRIDE; + bool UpdateImpl() CM_OVERRIDE; bool RunSVNCommand(std::vector<char const*> const& parameters, - OutputParser* out, OutputParser* err); + OutputParser* out, OutputParser* err); // Information about an SVN repository (root repository or external) - struct SVNInfo { + struct SVNInfo + { - SVNInfo(const char* path) : LocalPath(path) {} + SVNInfo(const char* path) + : LocalPath(path) + { + } // Remove base from the filename std::string BuildLocalPath(std::string const& path) const; @@ -58,38 +62,39 @@ private: // Old and new repository revisions. std::string OldRevision; std::string NewRevision; - }; // Extended revision structure to include info about external it refers to. struct Revision; + friend struct Revision; // Info of all the repositories (root, externals and nested ones). - std::list<SVNInfo> Repositories; + std::vector<SVNInfo> Repositories; // Pointer to the infos of the root repository. SVNInfo* RootInfo; std::string LoadInfo(SVNInfo& svninfo); - void LoadExternals(); - void LoadModifications(); - void LoadRevisions(); - void LoadRevisions(SVNInfo& svninfo); + bool LoadRepositories(); + bool LoadModifications() CM_OVERRIDE; + bool LoadRevisions() CM_OVERRIDE; + bool LoadRevisions(SVNInfo& svninfo); - void GuessBase(SVNInfo &svninfo, std::vector<Change> const& changes); + void GuessBase(SVNInfo& svninfo, std::vector<Change> const& changes); void DoRevisionSVN(Revision const& revision, std::vector<Change> const& changes); - void WriteXMLGlobal(std::ostream& xml); + void WriteXMLGlobal(cmXMLWriter& xml) CM_OVERRIDE; + class ExternalParser; // Parsing helper classes. class InfoParser; class LogParser; class StatusParser; class UpdateParser; - class ExternalParser; + friend class InfoParser; friend class LogParser; friend class StatusParser; diff --git a/Source/CTest/cmCTestScriptHandler.cxx b/Source/CTest/cmCTestScriptHandler.cxx index 8643cb3f7..1d29dfac2 100644 --- a/Source/CTest/cmCTestScriptHandler.cxx +++ b/Source/CTest/cmCTestScriptHandler.cxx @@ -1,44 +1,19 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestScriptHandler.h" -#include "cmCTest.h" -#include "cmake.h" -#include "cmFunctionBlocker.h" -#include "cmMakefile.h" -#include "cmLocalGenerator.h" -#include "cmGlobalGenerator.h" -#include "cmGeneratedFileStream.h" - -//#include <cmsys/RegularExpression.hxx> -#include <cmsys/Process.h> - -// used for sleep -#ifdef _WIN32 -#include "windows.h" -#endif - +#include "cmsys/Directory.hxx" +#include "cmsys/Process.h" +#include <map> +#include <sstream> +#include <stdio.h> #include <stdlib.h> -#include <time.h> -#include <math.h> -#include <float.h> - -// needed for sleep -#if !defined(_WIN32) -# include <unistd.h> -#endif +#include <string.h> +#include <utility> +#include "cmCTest.h" #include "cmCTestBuildCommand.h" +#include "cmCTestCommand.h" #include "cmCTestConfigureCommand.h" #include "cmCTestCoverageCommand.h" #include "cmCTestEmptyBinaryDirectoryCommand.h" @@ -51,6 +26,24 @@ #include "cmCTestTestCommand.h" #include "cmCTestUpdateCommand.h" #include "cmCTestUploadCommand.h" +#include "cmFunctionBlocker.h" +#include "cmGeneratedFileStream.h" +#include "cmGlobalGenerator.h" +#include "cmMakefile.h" +#include "cmState.h" +#include "cmStateDirectory.h" +#include "cmStateSnapshot.h" +#include "cmSystemTools.h" +#include "cmake.h" + +#ifdef _WIN32 +#include <windows.h> +#else +#include <unistd.h> +#endif + +class cmExecutionStatus; +struct cmListFileFunction; #define CTEST_INITIAL_CMAKE_OUTPUT_FILE_NAME "CTestInitialCMakeOutput.log" @@ -59,44 +52,40 @@ class cmCTestScriptFunctionBlocker : public cmFunctionBlocker { public: cmCTestScriptFunctionBlocker() {} - virtual ~cmCTestScriptFunctionBlocker() {} - virtual bool IsFunctionBlocked(const cmListFileFunction& lff, - cmMakefile &mf, - cmExecutionStatus &); - //virtual bool ShouldRemove(const cmListFileFunction& lff, cmMakefile &mf); - //virtual void ScopeEnded(cmMakefile &mf); + ~cmCTestScriptFunctionBlocker() CM_OVERRIDE {} + bool IsFunctionBlocked(const cmListFileFunction& lff, cmMakefile& mf, + cmExecutionStatus& /*status*/) CM_OVERRIDE; + // virtual bool ShouldRemove(const cmListFileFunction& lff, cmMakefile &mf); + // virtual void ScopeEnded(cmMakefile &mf); cmCTestScriptHandler* CTestScriptHandler; }; // simply update the time and don't block anything -bool cmCTestScriptFunctionBlocker:: -IsFunctionBlocked(const cmListFileFunction& , cmMakefile &, - cmExecutionStatus &) +bool cmCTestScriptFunctionBlocker::IsFunctionBlocked( + const cmListFileFunction& /*lff*/, cmMakefile& /*mf*/, + cmExecutionStatus& /*status*/) { this->CTestScriptHandler->UpdateElapsedTime(); return false; } -//---------------------------------------------------------------------- cmCTestScriptHandler::cmCTestScriptHandler() { this->Backup = false; this->EmptyBinDir = false; this->EmptyBinDirOnce = false; - this->Makefile = 0; - this->LocalGenerator = 0; - this->CMake = 0; - this->GlobalGenerator = 0; + this->Makefile = CM_NULLPTR; + this->CMake = CM_NULLPTR; + this->GlobalGenerator = CM_NULLPTR; this->ScriptStartTime = 0; // the *60 is becuase the settings are in minutes but GetTime is seconds - this->MinimumInterval = 30*60; + this->MinimumInterval = 30 * 60; this->ContinuousDuration = -1; } -//---------------------------------------------------------------------- void cmCTestScriptHandler::Initialize() { this->Superclass::Initialize(); @@ -118,102 +107,71 @@ void cmCTestScriptHandler::Initialize() this->CMOutFile = ""; this->ExtraUpdates.clear(); - this->MinimumInterval = 20*60; + this->MinimumInterval = 20 * 60; this->ContinuousDuration = -1; // what time in seconds did this script start running this->ScriptStartTime = 0; - this->Makefile = 0; - if (this->LocalGenerator) - { - delete this->LocalGenerator; - } - this->LocalGenerator = 0; - if (this->GlobalGenerator) - { - delete this->GlobalGenerator; - } - this->GlobalGenerator = 0; - if (this->CMake) - { - delete this->CMake; - } + delete this->Makefile; + this->Makefile = CM_NULLPTR; + + delete this->GlobalGenerator; + this->GlobalGenerator = CM_NULLPTR; + + delete this->CMake; } -//---------------------------------------------------------------------- cmCTestScriptHandler::~cmCTestScriptHandler() { - // local generator owns the makefile - this->Makefile = 0; - if (this->LocalGenerator) - { - delete this->LocalGenerator; - } - this->LocalGenerator = 0; - if (this->GlobalGenerator) - { - delete this->GlobalGenerator; - } - this->GlobalGenerator = 0; - if (this->CMake) - { - delete this->CMake; - } + delete this->Makefile; + delete this->GlobalGenerator; + delete this->CMake; } - -//---------------------------------------------------------------------- // just adds an argument to the vector -void cmCTestScriptHandler::AddConfigurationScript(const char *script, +void cmCTestScriptHandler::AddConfigurationScript(const char* script, bool pscope) { this->ConfigurationScripts.push_back(script); this->ScriptProcessScope.push_back(pscope); } - -//---------------------------------------------------------------------- // the generic entry point for handling scripts, this routine will run all // the scripts provides a -S arguments int cmCTestScriptHandler::ProcessHandler() { int res = 0; - for (size_t i=0; i < this->ConfigurationScripts.size(); ++i) - { + for (size_t i = 0; i < this->ConfigurationScripts.size(); ++i) { // for each script run it - res += this->RunConfigurationScript - (cmSystemTools::CollapseFullPath(this->ConfigurationScripts[i].c_str()), - this->ScriptProcessScope[i]); - } - if ( res ) - { + res |= this->RunConfigurationScript( + cmSystemTools::CollapseFullPath(this->ConfigurationScripts[i]), + this->ScriptProcessScope[i]); + } + if (res) { return -1; - } + } return 0; } void cmCTestScriptHandler::UpdateElapsedTime() { - if (this->LocalGenerator) - { + if (this->Makefile) { // set the current elapsed time char timeString[20]; - int itime = static_cast<unsigned int>(cmSystemTools::GetTime() - - this->ScriptStartTime); - sprintf(timeString,"%i",itime); - this->LocalGenerator->GetMakefile()->AddDefinition("CTEST_ELAPSED_TIME", - timeString); - } + int itime = static_cast<unsigned int>(cmSystemTools::GetTime() - + this->ScriptStartTime); + sprintf(timeString, "%i", itime); + this->Makefile->AddDefinition("CTEST_ELAPSED_TIME", timeString); + } } -//---------------------------------------------------------------------- -void cmCTestScriptHandler::AddCTestCommand(cmCTestCommand* command) +void cmCTestScriptHandler::AddCTestCommand(std::string const& name, + cmCTestCommand* command) { - cmCTestCommand* newCom = command; - newCom->CTest = this->CTest; - newCom->CTestScriptHandler = this; - this->CMake->AddCommand(newCom); + command->CTest = this->CTest; + command->CTestScriptHandler = this; + this->CMake->GetState()->AddBuiltinCommand(name, command); } int cmCTestScriptHandler::ExecuteScript(const std::string& total_script_arg) @@ -221,153 +179,134 @@ int cmCTestScriptHandler::ExecuteScript(const std::string& total_script_arg) // execute the script passing in the arguments to the script as well as the // arguments from this invocation of cmake std::vector<const char*> argv; - argv.push_back(this->CTest->GetCTestExecutable()); + argv.push_back(cmSystemTools::GetCTestCommand().c_str()); argv.push_back("-SR"); argv.push_back(total_script_arg.c_str()); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Executable for CTest is: " << - this->CTest->GetCTestExecutable() << "\n"); + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Executable for CTest is: " + << cmSystemTools::GetCTestCommand() << "\n"); // now pass through all the other arguments - std::vector<cmStdString> &initArgs = + std::vector<std::string>& initArgs = this->CTest->GetInitialCommandLineArguments(); //*** need to make sure this does not have the current script *** - for(size_t i=1; i < initArgs.size(); ++i) - { + for (size_t i = 1; i < initArgs.size(); ++i) { argv.push_back(initArgs[i].c_str()); - } - argv.push_back(0); + } + argv.push_back(CM_NULLPTR); // Now create process object cmsysProcess* cp = cmsysProcess_New(); cmsysProcess_SetCommand(cp, &*argv.begin()); - //cmsysProcess_SetWorkingDirectory(cp, dir); + // cmsysProcess_SetWorkingDirectory(cp, dir); cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1); - //cmsysProcess_SetTimeout(cp, timeout); + // cmsysProcess_SetTimeout(cp, timeout); cmsysProcess_Execute(cp); std::vector<char> out; std::vector<char> err; std::string line; int pipe = cmSystemTools::WaitForLine(cp, line, 100.0, out, err); - while(pipe != cmsysProcess_Pipe_None) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Output: " - << line << "\n"); - if(pipe == cmsysProcess_Pipe_STDERR) - { + while (pipe != cmsysProcess_Pipe_None) { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Output: " << line + << "\n"); + if (pipe == cmsysProcess_Pipe_STDERR) { cmCTestLog(this->CTest, ERROR_MESSAGE, line << "\n"); - } - else if(pipe == cmsysProcess_Pipe_STDOUT) - { + } else if (pipe == cmsysProcess_Pipe_STDOUT) { cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, line << "\n"); - } - pipe = cmSystemTools::WaitForLine(cp, line, 100, out, err); } + pipe = cmSystemTools::WaitForLine(cp, line, 100, out, err); + } // Properly handle output of the build command - cmsysProcess_WaitForExit(cp, 0); + cmsysProcess_WaitForExit(cp, CM_NULLPTR); int result = cmsysProcess_GetState(cp); int retVal = 0; bool failed = false; - if(result == cmsysProcess_State_Exited) - { + if (result == cmsysProcess_State_Exited) { retVal = cmsysProcess_GetExitValue(cp); - } - else if(result == cmsysProcess_State_Exception) - { + } else if (result == cmsysProcess_State_Exception) { retVal = cmsysProcess_GetExitException(cp); cmCTestLog(this->CTest, ERROR_MESSAGE, "\tThere was an exception: " - << cmsysProcess_GetExceptionString(cp) << " " << - retVal << std::endl); + << cmsysProcess_GetExceptionString(cp) << " " << retVal + << std::endl); failed = true; - } - else if(result == cmsysProcess_State_Expired) - { + } else if (result == cmsysProcess_State_Expired) { cmCTestLog(this->CTest, ERROR_MESSAGE, "\tThere was a timeout" - << std::endl); + << std::endl); failed = true; - } - else if(result == cmsysProcess_State_Error) - { + } else if (result == cmsysProcess_State_Error) { cmCTestLog(this->CTest, ERROR_MESSAGE, "\tError executing ctest: " - << cmsysProcess_GetErrorString(cp) << std::endl); + << cmsysProcess_GetErrorString(cp) << std::endl); failed = true; - } + } cmsysProcess_Delete(cp); - if(failed) - { - cmOStringStream message; + if (failed) { + std::ostringstream message; message << "Error running command: ["; message << result << "] "; - for(std::vector<const char*>::iterator i = argv.begin(); - i != argv.end(); ++i) - { - if(*i) - { - message << *i << " "; - } + for (std::vector<const char*>::iterator i = argv.begin(); i != argv.end(); + ++i) { + if (*i) { + message << *i << " "; } - cmCTestLog(this->CTest, ERROR_MESSAGE, - message.str() << argv[0] << std::endl); - return -1; } + cmCTestLog(this->CTest, ERROR_MESSAGE, message.str() << argv[0] + << std::endl); + return -1; + } return retVal; } +static void ctestScriptProgressCallback(const char* m, float /*unused*/, + void* cd) +{ + cmCTest* ctest = static_cast<cmCTest*>(cd); + if (m && *m) { + cmCTestLog(ctest, HANDLER_OUTPUT, "-- " << m << std::endl); + } +} + void cmCTestScriptHandler::CreateCMake() { // create a cmake instance to read the configuration script - if (this->CMake) - { + if (this->CMake) { delete this->CMake; delete this->GlobalGenerator; - delete this->LocalGenerator; - } - this->CMake = new cmake; + delete this->Makefile; + } + this->CMake = new cmake(cmake::RoleScript); + this->CMake->SetHomeDirectory(""); + this->CMake->SetHomeOutputDirectory(""); + this->CMake->GetCurrentSnapshot().SetDefaultDefinitions(); this->CMake->AddCMakePaths(); - this->GlobalGenerator = new cmGlobalGenerator; - this->GlobalGenerator->SetCMakeInstance(this->CMake); - - this->LocalGenerator = this->GlobalGenerator->CreateLocalGenerator(); - this->Makefile = this->LocalGenerator->GetMakefile(); + this->GlobalGenerator = new cmGlobalGenerator(this->CMake); - // Set CMAKE_CURRENT_SOURCE_DIR and CMAKE_CURRENT_BINARY_DIR. - // Also, some commands need Makefile->GetCurrentDirectory(). + cmStateSnapshot snapshot = this->CMake->GetCurrentSnapshot(); std::string cwd = cmSystemTools::GetCurrentWorkingDirectory(); - this->Makefile->SetStartDirectory(cwd.c_str()); - this->Makefile->SetStartOutputDirectory(cwd.c_str()); - - // remove all cmake commands which are not scriptable, since they can't be - // used in ctest scripts - this->CMake->RemoveUnscriptableCommands(); - - // add any ctest specific commands, probably should have common superclass - // for ctest commands to clean this up. If a couple more commands are - // created with the same format lets do that - ken - this->AddCTestCommand(new cmCTestBuildCommand); - this->AddCTestCommand(new cmCTestConfigureCommand); - this->AddCTestCommand(new cmCTestCoverageCommand); - this->AddCTestCommand(new cmCTestEmptyBinaryDirectoryCommand); - this->AddCTestCommand(new cmCTestMemCheckCommand); - this->AddCTestCommand(new cmCTestReadCustomFilesCommand); - this->AddCTestCommand(new cmCTestRunScriptCommand); - this->AddCTestCommand(new cmCTestSleepCommand); - this->AddCTestCommand(new cmCTestStartCommand); - this->AddCTestCommand(new cmCTestSubmitCommand); - this->AddCTestCommand(new cmCTestTestCommand); - this->AddCTestCommand(new cmCTestUpdateCommand); - this->AddCTestCommand(new cmCTestUploadCommand); -} - -void cmCTestScriptHandler::GetCommandDocumentation( - std::vector<cmDocumentationEntry>& v) const -{ - this->CMake->GetCommandDocumentation(v); + snapshot.GetDirectory().SetCurrentSource(cwd); + snapshot.GetDirectory().SetCurrentBinary(cwd); + this->Makefile = new cmMakefile(this->GlobalGenerator, snapshot); + + this->CMake->SetProgressCallback(ctestScriptProgressCallback, this->CTest); + + this->AddCTestCommand("ctest_build", new cmCTestBuildCommand); + this->AddCTestCommand("ctest_configure", new cmCTestConfigureCommand); + this->AddCTestCommand("ctest_coverage", new cmCTestCoverageCommand); + this->AddCTestCommand("ctest_empty_binary_directory", + new cmCTestEmptyBinaryDirectoryCommand); + this->AddCTestCommand("ctest_memcheck", new cmCTestMemCheckCommand); + this->AddCTestCommand("ctest_read_custom_files", + new cmCTestReadCustomFilesCommand); + this->AddCTestCommand("ctest_run_script", new cmCTestRunScriptCommand); + this->AddCTestCommand("ctest_sleep", new cmCTestSleepCommand); + this->AddCTestCommand("ctest_start", new cmCTestStartCommand); + this->AddCTestCommand("ctest_submit", new cmCTestSubmitCommand); + this->AddCTestCommand("ctest_test", new cmCTestTestCommand); + this->AddCTestCommand("ctest_update", new cmCTestUpdateCommand); + this->AddCTestCommand("ctest_upload", new cmCTestUploadCommand); } -//---------------------------------------------------------------------- // this sets up some variables for the script to use, creates the required // cmake instance and generators, and then reads in the script int cmCTestScriptHandler::ReadInScript(const std::string& total_script_arg) @@ -380,89 +319,81 @@ int cmCTestScriptHandler::ReadInScript(const std::string& total_script_arg) // passed into the scripts as S_ARG std::string script = total_script_arg; std::string script_arg; - if (total_script_arg.find(",") != std::string::npos) - { - script = total_script_arg.substr(0,total_script_arg.find(",")); - script_arg = total_script_arg.substr(total_script_arg.find(",")+1); - } + const std::string::size_type comma_pos = total_script_arg.find(','); + if (comma_pos != std::string::npos) { + script = total_script_arg.substr(0, comma_pos); + script_arg = total_script_arg.substr(comma_pos + 1); + } // make sure the file exists - if (!cmSystemTools::FileExists(script.c_str())) - { + if (!cmSystemTools::FileExists(script.c_str())) { cmSystemTools::Error("Cannot find file: ", script.c_str()); return 1; - } + } // read in the list file to fill the cache // create a cmake instance to read the configuration script this->CreateCMake(); // set a variable with the path to the current script - this->Makefile->AddDefinition("CTEST_SCRIPT_DIRECTORY", - cmSystemTools::GetFilenamePath(script).c_str()); - this->Makefile->AddDefinition("CTEST_SCRIPT_NAME", - cmSystemTools::GetFilenameName(script).c_str()); + this->Makefile->AddDefinition( + "CTEST_SCRIPT_DIRECTORY", cmSystemTools::GetFilenamePath(script).c_str()); + this->Makefile->AddDefinition( + "CTEST_SCRIPT_NAME", cmSystemTools::GetFilenameName(script).c_str()); this->Makefile->AddDefinition("CTEST_EXECUTABLE_NAME", - this->CTest->GetCTestExecutable()); + cmSystemTools::GetCTestCommand().c_str()); this->Makefile->AddDefinition("CMAKE_EXECUTABLE_NAME", - this->CTest->GetCMakeExecutable()); + cmSystemTools::GetCMakeCommand().c_str()); this->Makefile->AddDefinition("CTEST_RUN_CURRENT_SCRIPT", true); this->UpdateElapsedTime(); // add the script arg if defined - if (script_arg.size()) - { + if (!script_arg.empty()) { this->Makefile->AddDefinition("CTEST_SCRIPT_ARG", script_arg.c_str()); - } + } + +#if defined(__CYGWIN__) + this->Makefile->AddDefinition("CMAKE_LEGACY_CYGWIN_WIN32", "0"); +#endif // always add a function blocker to update the elapsed time - cmCTestScriptFunctionBlocker *f = new cmCTestScriptFunctionBlocker(); + cmCTestScriptFunctionBlocker* f = new cmCTestScriptFunctionBlocker(); f->CTestScriptHandler = this; this->Makefile->AddFunctionBlocker(f); - /* Execute CTestScriptMode.cmake, which loads CMakeDetermineSystem and CMakeSystemSpecificInformation, so that variables like CMAKE_SYSTEM and also the search paths for libraries, header and executables are set correctly and can be used. Makes new-style ctest scripting easier. */ std::string systemFile = - this->Makefile->GetModulesFile("CTestScriptMode.cmake"); - if (!this->Makefile->ReadListFile(0, systemFile.c_str()) || - cmSystemTools::GetErrorOccuredFlag()) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, "Error in read:" - << systemFile.c_str() << "\n"); + this->Makefile->GetModulesFile("CTestScriptMode.cmake"); + if (!this->Makefile->ReadListFile(systemFile.c_str()) || + cmSystemTools::GetErrorOccuredFlag()) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Error in read:" << systemFile + << "\n"); return 2; - } + } // Add definitions of variables passed in on the command line: - const std::map<std::string, std::string> &defs = + const std::map<std::string, std::string>& defs = this->CTest->GetDefinitions(); for (std::map<std::string, std::string>::const_iterator it = defs.begin(); - it != defs.end(); ++it) - { - this->Makefile->AddDefinition(it->first.c_str(), it->second.c_str()); - } + it != defs.end(); ++it) { + this->Makefile->AddDefinition(it->first, it->second.c_str()); + } // finally read in the script - if (!this->Makefile->ReadListFile(0, script.c_str()) || - cmSystemTools::GetErrorOccuredFlag()) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, "Error in read script: " - << script.c_str() - << std::endl); + if (!this->Makefile->ReadListFile(script.c_str()) || + cmSystemTools::GetErrorOccuredFlag()) { // Reset the error flag so that it can run more than - // one script with an error when you - // use ctest_run_script + // one script with an error when you use ctest_run_script. cmSystemTools::ResetErrorOccuredFlag(); return 2; - } + } return 0; } - -//---------------------------------------------------------------------- // extract variabels from the script to set ivars int cmCTestScriptHandler::ExtractVariables() { @@ -470,80 +401,64 @@ int cmCTestScriptHandler::ExtractVariables() const char* minInterval; const char* contDuration; - this->SourceDir - = this->Makefile->GetSafeDefinition("CTEST_SOURCE_DIRECTORY"); - this->BinaryDir - = this->Makefile->GetSafeDefinition("CTEST_BINARY_DIRECTORY"); + this->SourceDir = + this->Makefile->GetSafeDefinition("CTEST_SOURCE_DIRECTORY"); + this->BinaryDir = + this->Makefile->GetSafeDefinition("CTEST_BINARY_DIRECTORY"); // add in translations for src and bin - cmSystemTools::AddKeepPath(this->SourceDir.c_str()); - cmSystemTools::AddKeepPath(this->BinaryDir.c_str()); - - this->CTestCmd - = this->Makefile->GetSafeDefinition("CTEST_COMMAND"); - this->CVSCheckOut - = this->Makefile->GetSafeDefinition("CTEST_CVS_CHECKOUT"); - this->CTestRoot - = this->Makefile->GetSafeDefinition("CTEST_DASHBOARD_ROOT"); - this->UpdateCmd - = this->Makefile->GetSafeDefinition("CTEST_UPDATE_COMMAND"); - if ( this->UpdateCmd.empty() ) - { - this->UpdateCmd - = this->Makefile->GetSafeDefinition("CTEST_CVS_COMMAND"); - } - this->CTestEnv - = this->Makefile->GetSafeDefinition("CTEST_ENVIRONMENT"); - this->InitialCache - = this->Makefile->GetSafeDefinition("CTEST_INITIAL_CACHE"); - this->CMakeCmd - = this->Makefile->GetSafeDefinition("CTEST_CMAKE_COMMAND"); - this->CMOutFile - = this->Makefile->GetSafeDefinition("CTEST_CMAKE_OUTPUT_FILE_NAME"); - - this->Backup - = this->Makefile->IsOn("CTEST_BACKUP_AND_RESTORE"); - this->EmptyBinDir - = this->Makefile->IsOn("CTEST_START_WITH_EMPTY_BINARY_DIRECTORY"); - this->EmptyBinDirOnce - = this->Makefile->IsOn("CTEST_START_WITH_EMPTY_BINARY_DIRECTORY_ONCE"); - - minInterval - = this->Makefile->GetDefinition("CTEST_CONTINUOUS_MINIMUM_INTERVAL"); - contDuration - = this->Makefile->GetDefinition("CTEST_CONTINUOUS_DURATION"); + cmSystemTools::AddKeepPath(this->SourceDir); + cmSystemTools::AddKeepPath(this->BinaryDir); + + this->CTestCmd = this->Makefile->GetSafeDefinition("CTEST_COMMAND"); + this->CVSCheckOut = this->Makefile->GetSafeDefinition("CTEST_CVS_CHECKOUT"); + this->CTestRoot = this->Makefile->GetSafeDefinition("CTEST_DASHBOARD_ROOT"); + this->UpdateCmd = this->Makefile->GetSafeDefinition("CTEST_UPDATE_COMMAND"); + if (this->UpdateCmd.empty()) { + this->UpdateCmd = this->Makefile->GetSafeDefinition("CTEST_CVS_COMMAND"); + } + this->CTestEnv = this->Makefile->GetSafeDefinition("CTEST_ENVIRONMENT"); + this->InitialCache = + this->Makefile->GetSafeDefinition("CTEST_INITIAL_CACHE"); + this->CMakeCmd = this->Makefile->GetSafeDefinition("CTEST_CMAKE_COMMAND"); + this->CMOutFile = + this->Makefile->GetSafeDefinition("CTEST_CMAKE_OUTPUT_FILE_NAME"); + + this->Backup = this->Makefile->IsOn("CTEST_BACKUP_AND_RESTORE"); + this->EmptyBinDir = + this->Makefile->IsOn("CTEST_START_WITH_EMPTY_BINARY_DIRECTORY"); + this->EmptyBinDirOnce = + this->Makefile->IsOn("CTEST_START_WITH_EMPTY_BINARY_DIRECTORY_ONCE"); + + minInterval = + this->Makefile->GetDefinition("CTEST_CONTINUOUS_MINIMUM_INTERVAL"); + contDuration = this->Makefile->GetDefinition("CTEST_CONTINUOUS_DURATION"); char updateVar[40]; int i; - for (i = 1; i < 10; ++i) - { - sprintf(updateVar,"CTEST_EXTRA_UPDATES_%i",i); - const char *updateVal = this->Makefile->GetDefinition(updateVar); - if ( updateVal ) - { - if ( this->UpdateCmd.empty() ) - { - cmSystemTools::Error(updateVar, - " specified without specifying CTEST_CVS_COMMAND."); + for (i = 1; i < 10; ++i) { + sprintf(updateVar, "CTEST_EXTRA_UPDATES_%i", i); + const char* updateVal = this->Makefile->GetDefinition(updateVar); + if (updateVal) { + if (this->UpdateCmd.empty()) { + cmSystemTools::Error( + updateVar, " specified without specifying CTEST_CVS_COMMAND."); return 12; - } - this->ExtraUpdates.push_back(updateVal); } + this->ExtraUpdates.push_back(updateVal); } + } // in order to backup and restore we also must have the cvs root - if (this->Backup && this->CVSCheckOut.empty()) - { + if (this->Backup && this->CVSCheckOut.empty()) { cmSystemTools::Error( "Backup was requested without specifying CTEST_CVS_CHECKOUT."); return 3; - } + } // make sure the required info is here - if (this->SourceDir.empty() || - this->BinaryDir.empty() || - this->CTestCmd.empty()) - { + if (this->SourceDir.empty() || this->BinaryDir.empty() || + this->CTestCmd.empty()) { std::string msg = "CTEST_SOURCE_DIRECTORY = "; msg += (!this->SourceDir.empty()) ? this->SourceDir.c_str() : "(Null)"; msg += "\nCTEST_BINARY_DIRECTORY = "; @@ -554,45 +469,39 @@ int cmCTestScriptHandler::ExtractVariables() "Some required settings in the configuration file were missing:\n", msg.c_str()); return 4; - } + } // if the dashboard root isn't specified then we can compute it from the // this->SourceDir - if (this->CTestRoot.empty() ) - { - this->CTestRoot = cmSystemTools::GetFilenamePath(this->SourceDir).c_str(); - } + if (this->CTestRoot.empty()) { + this->CTestRoot = cmSystemTools::GetFilenamePath(this->SourceDir); + } // the script may override the minimum continuous interval - if (minInterval) - { + if (minInterval) { this->MinimumInterval = 60 * atof(minInterval); - } - if (contDuration) - { + } + if (contDuration) { this->ContinuousDuration = 60.0 * atof(contDuration); - } - + } this->UpdateElapsedTime(); return 0; } -//---------------------------------------------------------------------- void cmCTestScriptHandler::SleepInSeconds(unsigned int secondsToWait) { #if defined(_WIN32) - Sleep(1000*secondsToWait); + Sleep(1000 * secondsToWait); #else - sleep(secondsToWait); + sleep(secondsToWait); #endif } -//---------------------------------------------------------------------- // run a specific script -int cmCTestScriptHandler::RunConfigurationScript -(const std::string& total_script_arg, bool pscope) +int cmCTestScriptHandler::RunConfigurationScript( + const std::string& total_script_arg, bool pscope) { #ifdef CMAKE_BUILD_WITH_CMAKE cmSystemTools::SaveRestoreEnvironment sre; @@ -600,36 +509,29 @@ int cmCTestScriptHandler::RunConfigurationScript int result; - this->ScriptStartTime = - cmSystemTools::GetTime(); + this->ScriptStartTime = cmSystemTools::GetTime(); // read in the script - if (pscope) - { + if (pscope) { cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Reading Script: " << total_script_arg << std::endl); + "Reading Script: " << total_script_arg << std::endl); result = this->ReadInScript(total_script_arg); - } - else - { + } else { cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Executing Script: " << total_script_arg << std::endl); + "Executing Script: " << total_script_arg << std::endl); result = this->ExecuteScript(total_script_arg); - } - if (result) - { + } + if (result) { return result; - } + } // only run the curent script if we should - if (this->Makefile && this->Makefile->IsOn("CTEST_RUN_CURRENT_SCRIPT")) - { + if (this->Makefile && this->Makefile->IsOn("CTEST_RUN_CURRENT_SCRIPT")) { return this->RunCurrentScript(); - } + } return result; } -//---------------------------------------------------------------------- int cmCTestScriptHandler::RunCurrentScript() { int result; @@ -642,57 +544,47 @@ int cmCTestScriptHandler::RunCurrentScript() // extract the vars from the cache and store in ivars result = this->ExtractVariables(); - if (result) - { + if (result) { return result; - } + } // set any environment variables - if (!this->CTestEnv.empty()) - { + if (!this->CTestEnv.empty()) { std::vector<std::string> envArgs; - cmSystemTools::ExpandListArgument(this->CTestEnv.c_str(),envArgs); + cmSystemTools::ExpandListArgument(this->CTestEnv, envArgs); cmSystemTools::AppendEnv(envArgs); - } + } // now that we have done most of the error checking finally run the // dashboard, we may be asked to repeatedly run this dashboard, such as // for a continuous, do we ned to run it more than once? - if ( this->ContinuousDuration >= 0 ) - { + if (this->ContinuousDuration >= 0) { this->UpdateElapsedTime(); - double ending_time = cmSystemTools::GetTime() + this->ContinuousDuration; - if (this->EmptyBinDirOnce) - { + double ending_time = cmSystemTools::GetTime() + this->ContinuousDuration; + if (this->EmptyBinDirOnce) { this->EmptyBinDir = true; - } - do - { + } + do { double interval = cmSystemTools::GetTime(); result = this->RunConfigurationDashboard(); interval = cmSystemTools::GetTime() - interval; - if (interval < this->MinimumInterval) - { + if (interval < this->MinimumInterval) { this->SleepInSeconds( static_cast<unsigned int>(this->MinimumInterval - interval)); - } - if (this->EmptyBinDirOnce) - { + } + if (this->EmptyBinDirOnce) { this->EmptyBinDir = false; - } } - while (cmSystemTools::GetTime() < ending_time); - } + } while (cmSystemTools::GetTime() < ending_time); + } // otherwise just run it once - else - { + else { result = this->RunConfigurationDashboard(); - } + } return result; } -//---------------------------------------------------------------------- int cmCTestScriptHandler::CheckOutSourceDir() { std::string command; @@ -701,26 +593,23 @@ int cmCTestScriptHandler::CheckOutSourceDir() bool res; if (!cmSystemTools::FileExists(this->SourceDir.c_str()) && - !this->CVSCheckOut.empty()) - { + !this->CVSCheckOut.empty()) { // we must now checkout the src dir output = ""; cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Run cvs: " << this->CVSCheckOut << std::endl); - res = cmSystemTools::RunSingleCommand(this->CVSCheckOut.c_str(), &output, - &retVal, this->CTestRoot.c_str(), this->HandlerVerbose, - 0 /*this->TimeOut*/); - if (!res || retVal != 0) - { + "Run cvs: " << this->CVSCheckOut << std::endl); + res = cmSystemTools::RunSingleCommand( + this->CVSCheckOut.c_str(), &output, &output, &retVal, + this->CTestRoot.c_str(), this->HandlerVerbose, 0 /*this->TimeOut*/); + if (!res || retVal != 0) { cmSystemTools::Error("Unable to perform cvs checkout:\n", output.c_str()); return 6; - } } + } return 0; } -//---------------------------------------------------------------------- int cmCTestScriptHandler::BackupDirectories() { int retVal; @@ -732,17 +621,14 @@ int cmCTestScriptHandler::BackupDirectories() this->BackupBinaryDir += "_CMakeBackup"; // backup the binary and src directories if requested - if (this->Backup) - { + if (this->Backup) { // if for some reason those directories exist then first delete them - if (cmSystemTools::FileExists(this->BackupSourceDir.c_str())) - { - cmSystemTools::RemoveADirectory(this->BackupSourceDir.c_str()); - } - if (cmSystemTools::FileExists(this->BackupBinaryDir.c_str())) - { - cmSystemTools::RemoveADirectory(this->BackupBinaryDir.c_str()); - } + if (cmSystemTools::FileExists(this->BackupSourceDir.c_str())) { + cmSystemTools::RemoveADirectory(this->BackupSourceDir); + } + if (cmSystemTools::FileExists(this->BackupBinaryDir.c_str())) { + cmSystemTools::RemoveADirectory(this->BackupBinaryDir); + } // first rename the src and binary directories rename(this->SourceDir.c_str(), this->BackupSourceDir.c_str()); @@ -750,18 +636,15 @@ int cmCTestScriptHandler::BackupDirectories() // we must now checkout the src dir retVal = this->CheckOutSourceDir(); - if (retVal) - { + if (retVal) { this->RestoreBackupDirectories(); return retVal; - } } + } return 0; } - -//---------------------------------------------------------------------- int cmCTestScriptHandler::PerformExtraUpdates() { std::string command; @@ -771,39 +654,31 @@ int cmCTestScriptHandler::PerformExtraUpdates() // do an initial cvs update as required command = this->UpdateCmd; - std::vector<cmStdString>::iterator it; - for (it = this->ExtraUpdates.begin(); - it != this->ExtraUpdates.end(); - ++ it ) - { + std::vector<std::string>::iterator it; + for (it = this->ExtraUpdates.begin(); it != this->ExtraUpdates.end(); ++it) { std::vector<std::string> cvsArgs; - cmSystemTools::ExpandListArgument(it->c_str(),cvsArgs); - if (cvsArgs.size() == 2) - { + cmSystemTools::ExpandListArgument(*it, cvsArgs); + if (cvsArgs.size() == 2) { std::string fullCommand = command; fullCommand += " update "; fullCommand += cvsArgs[1]; output = ""; retVal = 0; - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Run Update: " - << fullCommand.c_str() << std::endl); - res = cmSystemTools::RunSingleCommand(fullCommand.c_str(), &output, - &retVal, cvsArgs[0].c_str(), + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Run Update: " << fullCommand << std::endl); + res = cmSystemTools::RunSingleCommand( + fullCommand.c_str(), &output, &output, &retVal, cvsArgs[0].c_str(), this->HandlerVerbose, 0 /*this->TimeOut*/); - if (!res || retVal != 0) - { - cmSystemTools::Error("Unable to perform extra updates:\n", - it->c_str(), "\nWith output:\n", - output.c_str()); + if (!res || retVal != 0) { + cmSystemTools::Error("Unable to perform extra updates:\n", it->c_str(), + "\nWith output:\n", output.c_str()); return 0; - } } } + } return 0; } - -//---------------------------------------------------------------------- // run a single dashboard entry int cmCTestScriptHandler::RunConfigurationDashboard() { @@ -816,198 +691,170 @@ int cmCTestScriptHandler::RunConfigurationDashboard() // make sure the src directory is there, if it isn't then we might be able // to check it out from cvs retVal = this->CheckOutSourceDir(); - if (retVal) - { + if (retVal) { return retVal; - } + } // backup the dirs if requested retVal = this->BackupDirectories(); - if (retVal) - { + if (retVal) { return retVal; - } + } // clear the binary directory? - if (this->EmptyBinDir) - { - if ( !cmCTestScriptHandler::EmptyBinaryDirectory( - this->BinaryDir.c_str()) ) - { + if (this->EmptyBinDir) { + if (!cmCTestScriptHandler::EmptyBinaryDirectory(this->BinaryDir.c_str())) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Problem removing the binary directory" << std::endl); - } + "Problem removing the binary directory" << std::endl); } + } // make sure the binary directory exists if it isn't the srcdir if (!cmSystemTools::FileExists(this->BinaryDir.c_str()) && - this->SourceDir != this->BinaryDir) - { - if (!cmSystemTools::MakeDirectory(this->BinaryDir.c_str())) - { + this->SourceDir != this->BinaryDir) { + if (!cmSystemTools::MakeDirectory(this->BinaryDir.c_str())) { cmSystemTools::Error("Unable to create the binary directory:\n", this->BinaryDir.c_str()); this->RestoreBackupDirectories(); return 7; - } } + } // if the binary directory and the source directory are the same, // and we are starting with an empty binary directory, then that means // we must check out the source tree - if (this->EmptyBinDir && this->SourceDir == this->BinaryDir) - { + if (this->EmptyBinDir && this->SourceDir == this->BinaryDir) { // make sure we have the required info - if (this->CVSCheckOut.empty()) - { - cmSystemTools::Error("You have specified the source and binary " + if (this->CVSCheckOut.empty()) { + cmSystemTools::Error( + "You have specified the source and binary " "directories to be the same (an in source build). You have also " "specified that the binary directory is to be erased. This means " "that the source will have to be checked out from CVS. But you have " "not specified CTEST_CVS_CHECKOUT"); return 8; - } + } // we must now checkout the src dir retVal = this->CheckOutSourceDir(); - if (retVal) - { + if (retVal) { this->RestoreBackupDirectories(); return retVal; - } } + } // backup the dirs if requested retVal = this->PerformExtraUpdates(); - if (retVal) - { + if (retVal) { return retVal; - } + } // put the initial cache into the bin dir - if (!this->InitialCache.empty()) - { + if (!this->InitialCache.empty()) { if (!this->WriteInitialCache(this->BinaryDir.c_str(), - this->InitialCache.c_str())) - { + this->InitialCache.c_str())) { this->RestoreBackupDirectories(); return 9; - } } + } // do an initial cmake to setup the DartConfig file int cmakeFailed = 0; std::string cmakeFailedOuput; - if (!this->CMakeCmd.empty()) - { + if (!this->CMakeCmd.empty()) { command = this->CMakeCmd; command += " \""; command += this->SourceDir; output = ""; command += "\""; retVal = 0; - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Run cmake command: " - << command.c_str() << std::endl); - res = cmSystemTools::RunSingleCommand(command.c_str(), &output, - &retVal, this->BinaryDir.c_str(), + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Run cmake command: " << command << std::endl); + res = cmSystemTools::RunSingleCommand( + command.c_str(), &output, &output, &retVal, this->BinaryDir.c_str(), this->HandlerVerbose, 0 /*this->TimeOut*/); - if ( !this->CMOutFile.empty() ) - { + if (!this->CMOutFile.empty()) { std::string cmakeOutputFile = this->CMOutFile; - if ( !cmSystemTools::FileIsFullPath(cmakeOutputFile.c_str()) ) - { + if (!cmSystemTools::FileIsFullPath(cmakeOutputFile.c_str())) { cmakeOutputFile = this->BinaryDir + "/" + cmakeOutputFile; - } + } cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Write CMake output to file: " << cmakeOutputFile.c_str() - << std::endl); + "Write CMake output to file: " << cmakeOutputFile + << std::endl); cmGeneratedFileStream fout(cmakeOutputFile.c_str()); - if ( fout ) - { + if (fout) { fout << output.c_str(); - } - else - { + } else { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot open CMake output file: " - << cmakeOutputFile.c_str() << " for writing" << std::endl); - } + "Cannot open CMake output file: " + << cmakeOutputFile << " for writing" << std::endl); } - if (!res || retVal != 0) - { + } + if (!res || retVal != 0) { // even if this fails continue to the next step cmakeFailed = 1; cmakeFailedOuput = output; - } } + } // run ctest, it may be more than one command in here std::vector<std::string> ctestCommands; - cmSystemTools::ExpandListArgument(this->CTestCmd,ctestCommands); + cmSystemTools::ExpandListArgument(this->CTestCmd, ctestCommands); // for each variable/argument do a putenv - for (unsigned i = 0; i < ctestCommands.size(); ++i) - { + for (unsigned i = 0; i < ctestCommands.size(); ++i) { command = ctestCommands[i]; output = ""; retVal = 0; - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Run ctest command: " - << command.c_str() << std::endl); - res = cmSystemTools::RunSingleCommand(command.c_str(), &output, - &retVal, this->BinaryDir.c_str(), this->HandlerVerbose, - 0 /*this->TimeOut*/); + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Run ctest command: " << command << std::endl); + res = cmSystemTools::RunSingleCommand( + command.c_str(), &output, &output, &retVal, this->BinaryDir.c_str(), + this->HandlerVerbose, 0 /*this->TimeOut*/); // did something critical fail in ctest - if (!res || cmakeFailed || - retVal & cmCTest::BUILD_ERRORS) - { + if (!res || cmakeFailed || retVal & cmCTest::BUILD_ERRORS) { this->RestoreBackupDirectories(); - if (cmakeFailed) - { + if (cmakeFailed) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Unable to run cmake:" << std::endl - << cmakeFailedOuput.c_str() << std::endl); + "Unable to run cmake:" << std::endl + << cmakeFailedOuput << std::endl); return 10; - } + } cmCTestLog(this->CTest, ERROR_MESSAGE, - "Unable to run ctest:" << std::endl - << "command: " << command.c_str() << std::endl - << "output: " << output.c_str() << std::endl); - if (!res) - { + "Unable to run ctest:" << std::endl + << "command: " << command << std::endl + << "output: " << output << std::endl); + if (!res) { return 11; - } - return retVal * 100; } + return retVal * 100; } + } // if all was succesful, delete the backup dirs to free up disk space - if (this->Backup) - { - cmSystemTools::RemoveADirectory(this->BackupSourceDir.c_str()); - cmSystemTools::RemoveADirectory(this->BackupBinaryDir.c_str()); - } + if (this->Backup) { + cmSystemTools::RemoveADirectory(this->BackupSourceDir); + cmSystemTools::RemoveADirectory(this->BackupBinaryDir); + } return 0; } -//------------------------------------------------------------------------- bool cmCTestScriptHandler::WriteInitialCache(const char* directory, const char* text) { std::string cacheFile = directory; cacheFile += "/CMakeCache.txt"; cmGeneratedFileStream fout(cacheFile.c_str()); - if(!fout) - { + if (!fout) { return false; - } + } - if (text!=0) - { + if (text != CM_NULLPTR) { fout.write(text, strlen(text)); - } + } // Make sure the operating system has finished writing the file // before closing it. This will ensure the file is finished before @@ -1017,77 +864,111 @@ bool cmCTestScriptHandler::WriteInitialCache(const char* directory, return true; } -//------------------------------------------------------------------------- void cmCTestScriptHandler::RestoreBackupDirectories() { // if we backed up the dirs and the build failed, then restore // the backed up dirs - if (this->Backup) - { + if (this->Backup) { // if for some reason those directories exist then first delete them - if (cmSystemTools::FileExists(this->SourceDir.c_str())) - { - cmSystemTools::RemoveADirectory(this->SourceDir.c_str()); - } - if (cmSystemTools::FileExists(this->BinaryDir.c_str())) - { - cmSystemTools::RemoveADirectory(this->BinaryDir.c_str()); - } + if (cmSystemTools::FileExists(this->SourceDir.c_str())) { + cmSystemTools::RemoveADirectory(this->SourceDir); + } + if (cmSystemTools::FileExists(this->BinaryDir.c_str())) { + cmSystemTools::RemoveADirectory(this->BinaryDir); + } // rename the src and binary directories rename(this->BackupSourceDir.c_str(), this->SourceDir.c_str()); rename(this->BackupBinaryDir.c_str(), this->BinaryDir.c_str()); - } + } } -bool cmCTestScriptHandler::RunScript(cmCTest* ctest, const char *sname, +bool cmCTestScriptHandler::RunScript(cmCTest* ctest, const char* sname, bool InProcess, int* returnValue) { cmCTestScriptHandler* sh = new cmCTestScriptHandler(); sh->SetCTestInstance(ctest); - sh->AddConfigurationScript(sname,InProcess); + sh->AddConfigurationScript(sname, InProcess); int res = sh->ProcessHandler(); - if(returnValue) - { + if (returnValue) { *returnValue = res; - } + } delete sh; return true; } -bool cmCTestScriptHandler::EmptyBinaryDirectory(const char *sname) +bool cmCTestScriptHandler::EmptyBinaryDirectory(const char* sname) { // try to avoid deleting root - if (!sname || strlen(sname) < 2) - { + if (!sname || strlen(sname) < 2) { return false; - } + } + + // consider non existing target directory a success + if (!cmSystemTools::FileExists(sname)) { + return true; + } // try to avoid deleting directories that we shouldn't std::string check = sname; check += "/CMakeCache.txt"; - if(cmSystemTools::FileExists(check.c_str()) && - !cmSystemTools::RemoveADirectory(sname)) - { + + if (!cmSystemTools::FileExists(check.c_str())) { return false; + } + + for (int i = 0; i < 5; ++i) { + if (TryToRemoveBinaryDirectoryOnce(sname)) { + return true; } - return true; + cmSystemTools::Delay(100); + } + + return false; +} + +bool cmCTestScriptHandler::TryToRemoveBinaryDirectoryOnce( + const std::string& directoryPath) +{ + cmsys::Directory directory; + directory.Load(directoryPath); + + for (unsigned long i = 0; i < directory.GetNumberOfFiles(); ++i) { + std::string path = directory.GetFile(i); + + if (path == "." || path == ".." || path == "CMakeCache.txt") { + continue; + } + + std::string fullPath = directoryPath + std::string("/") + path; + + bool isDirectory = cmSystemTools::FileIsDirectory(fullPath) && + !cmSystemTools::FileIsSymlink(fullPath); + + if (isDirectory) { + if (!cmSystemTools::RemoveADirectory(fullPath)) { + return false; + } + } else { + if (!cmSystemTools::RemoveFile(fullPath)) { + return false; + } + } + } + + return cmSystemTools::RemoveADirectory(directoryPath); } -//------------------------------------------------------------------------- double cmCTestScriptHandler::GetRemainingTimeAllowed() { - if (!this->Makefile) - { + if (!this->Makefile) { return 1.0e7; - } + } - const char *timelimitS - = this->Makefile->GetDefinition("CTEST_TIME_LIMIT"); + const char* timelimitS = this->Makefile->GetDefinition("CTEST_TIME_LIMIT"); - if (!timelimitS) - { + if (!timelimitS) { return 1.0e7; - } + } double timelimit = atof(timelimitS); diff --git a/Source/CTest/cmCTestScriptHandler.h b/Source/CTest/cmCTestScriptHandler.h index 9d852cab6..667870260 100644 --- a/Source/CTest/cmCTestScriptHandler.h +++ b/Source/CTest/cmCTestScriptHandler.h @@ -1,27 +1,20 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestScriptHandler_h #define cmCTestScriptHandler_h +#include "cmConfigure.h" #include "cmCTestGenericHandler.h" -#include "cmListFileCache.h" -class cmMakefile; -class cmLocalGenerator; +#include <string> +#include <vector> + +class cmCTest; +class cmCTestCommand; class cmGlobalGenerator; +class cmMakefile; class cmake; -class cmCTestCommand; /** \class cmCTestScriptHandler * \brief A class that handles ctest -S invocations @@ -62,29 +55,29 @@ class cmCTestCommand; class cmCTestScriptHandler : public cmCTestGenericHandler { public: - cmTypeMacro(cmCTestScriptHandler, cmCTestGenericHandler); + typedef cmCTestGenericHandler Superclass; /** * Add a script to run, and if is should run in the current process */ - void AddConfigurationScript(const char *, bool pscope); + void AddConfigurationScript(const char*, bool pscope); /** * Run a dashboard using a specified confiuration script */ - int ProcessHandler(); + int ProcessHandler() CM_OVERRIDE; /* * Run a script */ - static bool RunScript(cmCTest* ctest, const char *script, bool InProcess, - int* returnValue); + static bool RunScript(cmCTest* ctest, const char* script, bool InProcess, + int* returnValue); int RunCurrentScript(); /* * Empty Binary Directory */ - static bool EmptyBinaryDirectory(const char *dir); + static bool EmptyBinaryDirectory(const char* dir); /* * Write an initial CMakeCache.txt from the given contents. @@ -105,13 +98,12 @@ public: double GetRemainingTimeAllowed(); cmCTestScriptHandler(); - ~cmCTestScriptHandler(); + ~cmCTestScriptHandler() CM_OVERRIDE; - void Initialize(); + void Initialize() CM_OVERRIDE; void CreateCMake(); - void GetCommandDocumentation(std::vector<cmDocumentationEntry>& v) const; - cmake* GetCMake() { return this->CMake;} + cmake* GetCMake() { return this->CMake; } private: // reads in a script int ReadInScript(const std::string& total_script_arg); @@ -134,28 +126,31 @@ private: int RunConfigurationDashboard(); // Add ctest command - void AddCTestCommand(cmCTestCommand* command); + void AddCTestCommand(std::string const& name, cmCTestCommand* command); + + // Try to remove the binary directory once + static bool TryToRemoveBinaryDirectoryOnce(const std::string& directoryPath); - std::vector<cmStdString> ConfigurationScripts; + std::vector<std::string> ConfigurationScripts; std::vector<bool> ScriptProcessScope; bool Backup; bool EmptyBinDir; bool EmptyBinDirOnce; - cmStdString SourceDir; - cmStdString BinaryDir; - cmStdString BackupSourceDir; - cmStdString BackupBinaryDir; - cmStdString CTestRoot; - cmStdString CVSCheckOut; - cmStdString CTestCmd; - cmStdString UpdateCmd; - cmStdString CTestEnv; - cmStdString InitialCache; - cmStdString CMakeCmd; - cmStdString CMOutFile; - std::vector<cmStdString> ExtraUpdates; + std::string SourceDir; + std::string BinaryDir; + std::string BackupSourceDir; + std::string BackupBinaryDir; + std::string CTestRoot; + std::string CVSCheckOut; + std::string CTestCmd; + std::string UpdateCmd; + std::string CTestEnv; + std::string InitialCache; + std::string CMakeCmd; + std::string CMOutFile; + std::vector<std::string> ExtraUpdates; double MinimumInterval; double ContinuousDuration; @@ -163,10 +158,9 @@ private: // what time in seconds did this script start running double ScriptStartTime; - cmMakefile *Makefile; - cmLocalGenerator *LocalGenerator; - cmGlobalGenerator *GlobalGenerator; - cmake *CMake; + cmMakefile* Makefile; + cmGlobalGenerator* GlobalGenerator; + cmake* CMake; }; #endif diff --git a/Source/CTest/cmCTestSleepCommand.cxx b/Source/CTest/cmCTestSleepCommand.cxx index 7e8755066..2752cd370 100644 --- a/Source/CTest/cmCTestSleepCommand.cxx +++ b/Source/CTest/cmCTestSleepCommand.cxx @@ -1,55 +1,43 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestSleepCommand.h" #include "cmCTestScriptHandler.h" -#include <stdlib.h> // required for atoi -bool cmCTestSleepCommand -::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &) +#include <stdlib.h> + +class cmExecutionStatus; + +bool cmCTestSleepCommand::InitialPass(std::vector<std::string> const& args, + cmExecutionStatus& /*unused*/) { - if (args.size() < 1) - { + if (args.empty()) { this->SetError("called with incorrect number of arguments"); return false; - } + } // sleep for specified seconds unsigned int time1 = atoi(args[0].c_str()); - if(args.size() == 1 ) - { + if (args.size() == 1) { cmCTestScriptHandler::SleepInSeconds(time1); // update the elapsed time since it could have slept for a while this->CTestScriptHandler->UpdateElapsedTime(); return true; - } + } // sleep up to a duration - if(args.size() == 3 ) - { + if (args.size() == 3) { unsigned int duration = atoi(args[1].c_str()); unsigned int time2 = atoi(args[2].c_str()); - if (time1 + duration > time2) - { + if (time1 + duration > time2) { duration = (time1 + duration - time2); cmCTestScriptHandler::SleepInSeconds(duration); // update the elapsed time since it could have slept for a while this->CTestScriptHandler->UpdateElapsedTime(); - } - return true; } + return true; + } this->SetError("called with incorrect number of arguments"); return false; } - - diff --git a/Source/CTest/cmCTestSleepCommand.h b/Source/CTest/cmCTestSleepCommand.h index 0f51ddfeb..f0b5f1eeb 100644 --- a/Source/CTest/cmCTestSleepCommand.h +++ b/Source/CTest/cmCTestSleepCommand.h @@ -1,19 +1,18 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestSleepCommand_h #define cmCTestSleepCommand_h +#include "cmConfigure.h" + #include "cmCTestCommand.h" +#include <string> +#include <vector> + +class cmCommand; +class cmExecutionStatus; + /** \class cmCTestSleep * \brief Run a ctest script * @@ -23,55 +22,25 @@ class cmCTestSleepCommand : public cmCTestCommand { public: - cmCTestSleepCommand() {} /** * This is a virtual constructor for the command. */ - virtual cmCommand* Clone() - { + cmCommand* Clone() CM_OVERRIDE + { cmCTestSleepCommand* ni = new cmCTestSleepCommand; ni->CTest = this->CTest; ni->CTestScriptHandler = this->CTestScriptHandler; return ni; - } + } /** * This is called when the command is first encountered in * the CMakeLists.txt file. */ - virtual bool InitialPass(std::vector<std::string> const& args, - cmExecutionStatus &status); - - /** - * The name of the command as specified in CMakeList.txt. - */ - virtual const char* GetName() const { return "ctest_sleep";} - - /** - * Succinct documentation. - */ - virtual const char* GetTerseDocumentation() const - { - return "sleeps for some amount of time"; - } - - /** - * More documentation. - */ - virtual const char* GetFullDocumentation() const - { - return - " ctest_sleep(<seconds>)\n" - "Sleep for given number of seconds.\n" - " ctest_sleep(<time1> <duration> <time2>)\n" - "Sleep for t=(time1 + duration - time2) seconds if t > 0."; - } - - cmTypeMacro(cmCTestSleepCommand, cmCTestCommand); - + bool InitialPass(std::vector<std::string> const& args, + cmExecutionStatus& status) CM_OVERRIDE; }; - #endif diff --git a/Source/CTest/cmCTestStartCommand.cxx b/Source/CTest/cmCTestStartCommand.cxx index 228a17300..01a874b45 100644 --- a/Source/CTest/cmCTestStartCommand.cxx +++ b/Source/CTest/cmCTestStartCommand.cxx @@ -1,92 +1,84 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestStartCommand.h" #include "cmCTest.h" -#include "cmLocalGenerator.h" -#include "cmGlobalGenerator.h" #include "cmCTestVC.h" #include "cmGeneratedFileStream.h" +#include "cmMakefile.h" +#include "cmSystemTools.h" + +#include <sstream> +#include <stddef.h> + +class cmExecutionStatus; cmCTestStartCommand::cmCTestStartCommand() { this->CreateNewTag = true; + this->Quiet = false; } -bool cmCTestStartCommand -::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &) +bool cmCTestStartCommand::InitialPass(std::vector<std::string> const& args, + cmExecutionStatus& /*unused*/) { - if (args.size() < 1) - { + if (args.empty()) { this->SetError("called with incorrect number of arguments"); return false; - } + } size_t cnt = 0; const char* smodel = args[cnt].c_str(); - const char* src_dir = 0; - const char* bld_dir = 0; + const char* src_dir = CM_NULLPTR; + const char* bld_dir = CM_NULLPTR; cnt++; - this->CTest->SetSpecificTrack(0); - if ( cnt < args.size() -1 ) - { - if ( args[cnt] == "TRACK" ) - { - cnt ++; + this->CTest->SetSpecificTrack(CM_NULLPTR); + if (cnt < args.size() - 1) { + if (args[cnt] == "TRACK") { + cnt++; this->CTest->SetSpecificTrack(args[cnt].c_str()); - cnt ++; - } + cnt++; } + } - if (cnt < args.size()) - { - if (args[cnt] == "APPEND") - { - cnt ++; + if (cnt < args.size()) { + if (args[cnt] == "APPEND") { + cnt++; this->CreateNewTag = false; - } } + } + if (cnt < args.size()) { + if (args[cnt] == "QUIET") { + cnt++; + this->Quiet = true; + } + } - if ( cnt < args.size() ) - { + if (cnt < args.size()) { src_dir = args[cnt].c_str(); - cnt ++; - if ( cnt < args.size() ) - { + cnt++; + if (cnt < args.size()) { bld_dir = args[cnt].c_str(); - } } - if ( !src_dir ) - { + } + if (!src_dir) { src_dir = this->Makefile->GetDefinition("CTEST_SOURCE_DIRECTORY"); - } - if ( !bld_dir) - { + } + if (!bld_dir) { bld_dir = this->Makefile->GetDefinition("CTEST_BINARY_DIRECTORY"); - } - if ( !src_dir ) - { + } + if (!src_dir) { this->SetError("source directory not specified. Specify source directory " - "as an argument or set CTEST_SOURCE_DIRECTORY"); + "as an argument or set CTEST_SOURCE_DIRECTORY"); return false; - } - if ( !bld_dir) - { + } + if (!bld_dir) { this->SetError("binary directory not specified. Specify binary directory " - "as an argument or set CTEST_BINARY_DIRECTORY"); + "as an argument or set CTEST_BINARY_DIRECTORY"); return false; - } + } cmSystemTools::AddKeepPath(src_dir); cmSystemTools::AddKeepPath(bld_dir); @@ -95,45 +87,44 @@ bool cmCTestStartCommand std::string sourceDir = cmSystemTools::CollapseFullPath(src_dir); std::string binaryDir = cmSystemTools::CollapseFullPath(bld_dir); - this->CTest->SetCTestConfiguration("SourceDirectory", sourceDir.c_str()); - this->CTest->SetCTestConfiguration("BuildDirectory", binaryDir.c_str()); - - cmCTestLog(this->CTest, HANDLER_OUTPUT, "Run dashboard with model " - << smodel << std::endl - << " Source directory: " << src_dir << std::endl - << " Build directory: " << bld_dir << std::endl); + this->CTest->SetCTestConfiguration("SourceDirectory", sourceDir.c_str(), + this->Quiet); + this->CTest->SetCTestConfiguration("BuildDirectory", binaryDir.c_str(), + this->Quiet); + + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "Run dashboard with model " + << smodel << std::endl + << " Source directory: " << src_dir << std::endl + << " Build directory: " << bld_dir << std::endl, + this->Quiet); const char* track = this->CTest->GetSpecificTrack(); - if ( track ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, - " Track: " << track << std::endl); - } + if (track) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Track: " << track << std::endl, this->Quiet); + } // Log startup actions. std::string startLogFile = binaryDir + "/Testing/Temporary/LastStart.log"; cmGeneratedFileStream ofs(startLogFile.c_str()); - if(!ofs) - { + if (!ofs) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot create log file: LastStart.log" << std::endl); return false; - } + } // Make sure the source directory exists. - if(!this->InitialCheckout(ofs, sourceDir)) - { + if (!this->InitialCheckout(ofs, sourceDir)) { return false; - } - if(!cmSystemTools::FileIsDirectory(sourceDir.c_str())) - { - cmOStringStream e; + } + if (!cmSystemTools::FileIsDirectory(sourceDir)) { + std::ostringstream e; e << "given source path\n" << " " << sourceDir << "\n" << "which is not an existing directory. " << "Set CTEST_CHECKOUT_COMMAND to a command line to create it."; - this->SetError(e.str().c_str()); + this->SetError(e.str()); return false; - } + } this->Makefile->AddDefinition("CTEST_RUN_CURRENT_SCRIPT", "OFF"); this->CTest->SetSuppressUpdatingCTestConfiguration(true); @@ -144,27 +135,23 @@ bool cmCTestStartCommand return this->CTest->InitializeFromCommand(this); } -//---------------------------------------------------------------------------- -bool cmCTestStartCommand::InitialCheckout( - std::ostream& ofs, std::string const& sourceDir) +bool cmCTestStartCommand::InitialCheckout(std::ostream& ofs, + std::string const& sourceDir) { // Use the user-provided command to create the source tree. - const char* initialCheckoutCommand - = this->Makefile->GetDefinition("CTEST_CHECKOUT_COMMAND"); - if(!initialCheckoutCommand) - { + const char* initialCheckoutCommand = + this->Makefile->GetDefinition("CTEST_CHECKOUT_COMMAND"); + if (!initialCheckoutCommand) { initialCheckoutCommand = this->Makefile->GetDefinition("CTEST_CVS_CHECKOUT"); - } - if(initialCheckoutCommand) - { + } + if (initialCheckoutCommand) { // Use a generic VC object to run and log the command. cmCTestVC vc(this->CTest, ofs); - vc.SetSourceDirectory(sourceDir.c_str()); - if(!vc.InitialCheckout(initialCheckoutCommand)) - { + vc.SetSourceDirectory(sourceDir); + if (!vc.InitialCheckout(initialCheckoutCommand)) { return false; - } } + } return true; } diff --git a/Source/CTest/cmCTestStartCommand.h b/Source/CTest/cmCTestStartCommand.h index 6be47703e..b4943f92d 100644 --- a/Source/CTest/cmCTestStartCommand.h +++ b/Source/CTest/cmCTestStartCommand.h @@ -1,19 +1,19 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestStartCommand_h #define cmCTestStartCommand_h +#include "cmConfigure.h" + #include "cmCTestCommand.h" +#include <iosfwd> +#include <string> +#include <vector> + +class cmCommand; +class cmExecutionStatus; + /** \class cmCTestStart * \brief Run a ctest script * @@ -22,71 +22,42 @@ class cmCTestStartCommand : public cmCTestCommand { public: - cmCTestStartCommand(); /** * This is a virtual constructor for the command. */ - virtual cmCommand* Clone() - { + cmCommand* Clone() CM_OVERRIDE + { cmCTestStartCommand* ni = new cmCTestStartCommand; ni->CTest = this->CTest; ni->CTestScriptHandler = this->CTestScriptHandler; ni->CreateNewTag = this->CreateNewTag; + ni->Quiet = this->Quiet; return ni; - } + } /** * This is called when the command is first encountered in * the CMakeLists.txt file. */ - virtual bool InitialPass(std::vector<std::string> const& args, - cmExecutionStatus &status); + bool InitialPass(std::vector<std::string> const& args, + cmExecutionStatus& status) CM_OVERRIDE; /** * Will this invocation of ctest_start create a new TAG file? */ - bool ShouldCreateNewTag() - { - return this->CreateNewTag; - } - - /** - * The name of the command as specified in CMakeList.txt. - */ - virtual const char* GetName() const { return "ctest_start";} + bool ShouldCreateNewTag() { return this->CreateNewTag; } /** - * Succinct documentation. + * Should this invocation of ctest_start output non-error messages? */ - virtual const char* GetTerseDocumentation() const - { - return "Starts the testing for a given model"; - } - - /** - * More documentation. - */ - virtual const char* GetFullDocumentation() const - { - return - " ctest_start(Model [TRACK <track>] [APPEND] [source [binary]])\n" - "Starts the testing for a given model. The command should be called " - "after the binary directory is initialized. If the 'source' and " - "'binary' directory are not specified, it reads the " - "CTEST_SOURCE_DIRECTORY and CTEST_BINARY_DIRECTORY. If the track is " - "specified, the submissions will go to the specified track. " - "If APPEND is used, the existing TAG is used rather than " - "creating a new one based on the current time stamp."; - } - - cmTypeMacro(cmCTestStartCommand, cmCTestCommand); + bool ShouldBeQuiet() { return this->Quiet; } private: bool InitialCheckout(std::ostream& ofs, std::string const& sourceDir); bool CreateNewTag; + bool Quiet; }; - #endif diff --git a/Source/CTest/cmCTestSubmitCommand.cxx b/Source/CTest/cmCTestSubmitCommand.cxx index 24974e3bc..409eb51fc 100644 --- a/Source/CTest/cmCTestSubmitCommand.cxx +++ b/Source/CTest/cmCTestSubmitCommand.cxx @@ -1,117 +1,101 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestSubmitCommand.h" #include "cmCTest.h" #include "cmCTestGenericHandler.h" #include "cmCTestSubmitHandler.h" +#include "cmMakefile.h" +#include "cmSystemTools.h" +#include "cmake.h" + +#include <sstream> + +class cmExecutionStatus; cmCTestGenericHandler* cmCTestSubmitCommand::InitializeHandler() { - const char* ctestDropMethod - = this->Makefile->GetDefinition("CTEST_DROP_METHOD"); - const char* ctestDropSite - = this->Makefile->GetDefinition("CTEST_DROP_SITE"); - const char* ctestDropLocation - = this->Makefile->GetDefinition("CTEST_DROP_LOCATION"); - const char* ctestTriggerSite - = this->Makefile->GetDefinition("CTEST_TRIGGER_SITE"); - bool ctestDropSiteCDash - = this->Makefile->IsOn("CTEST_DROP_SITE_CDASH"); - - if ( !ctestDropMethod ) - { + const char* ctestDropMethod = + this->Makefile->GetDefinition("CTEST_DROP_METHOD"); + const char* ctestDropSite = this->Makefile->GetDefinition("CTEST_DROP_SITE"); + const char* ctestDropLocation = + this->Makefile->GetDefinition("CTEST_DROP_LOCATION"); + const char* ctestTriggerSite = + this->Makefile->GetDefinition("CTEST_TRIGGER_SITE"); + bool ctestDropSiteCDash = this->Makefile->IsOn("CTEST_DROP_SITE_CDASH"); + const char* ctestProjectName = + this->Makefile->GetDefinition("CTEST_PROJECT_NAME"); + if (!ctestDropMethod) { ctestDropMethod = "http"; - } + } - if ( !ctestDropSite ) - { + if (!ctestDropSite) { // error: CDash requires CTEST_DROP_SITE definition // in CTestConfig.cmake - } - if ( !ctestDropLocation ) - { + } + if (!ctestDropLocation) { // error: CDash requires CTEST_DROP_LOCATION definition // in CTestConfig.cmake - } - - this->CTest->SetCTestConfiguration("DropMethod", ctestDropMethod); - this->CTest->SetCTestConfiguration("DropSite", ctestDropSite); - this->CTest->SetCTestConfiguration("DropLocation", ctestDropLocation); - - this->CTest->SetCTestConfiguration("IsCDash", - ctestDropSiteCDash ? "TRUE" : "FALSE"); + } + this->CTest->SetCTestConfiguration("ProjectName", ctestProjectName, + this->Quiet); + this->CTest->SetCTestConfiguration("DropMethod", ctestDropMethod, + this->Quiet); + this->CTest->SetCTestConfiguration("DropSite", ctestDropSite, this->Quiet); + this->CTest->SetCTestConfiguration("DropLocation", ctestDropLocation, + this->Quiet); + + this->CTest->SetCTestConfiguration( + "IsCDash", ctestDropSiteCDash ? "TRUE" : "FALSE", this->Quiet); // Only propagate TriggerSite for non-CDash projects: // - if ( !ctestDropSiteCDash ) - { - this->CTest->SetCTestConfiguration("TriggerSite", ctestTriggerSite); - } - - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "CurlOptions", "CTEST_CURL_OPTIONS"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "DropSiteUser", "CTEST_DROP_SITE_USER"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "DropSitePassword", "CTEST_DROP_SITE_PASSWORD"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "ScpCommand", "CTEST_SCP_COMMAND"); - - const char* notesFilesVariable - = this->Makefile->GetDefinition("CTEST_NOTES_FILES"); - if (notesFilesVariable) - { + if (!ctestDropSiteCDash) { + this->CTest->SetCTestConfiguration("TriggerSite", ctestTriggerSite, + this->Quiet); + } + + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "CurlOptions", "CTEST_CURL_OPTIONS", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "DropSiteUser", "CTEST_DROP_SITE_USER", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "DropSitePassword", "CTEST_DROP_SITE_PASSWORD", + this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "ScpCommand", "CTEST_SCP_COMMAND", this->Quiet); + + const char* notesFilesVariable = + this->Makefile->GetDefinition("CTEST_NOTES_FILES"); + if (notesFilesVariable) { std::vector<std::string> notesFiles; - std::vector<cmStdString> newNotesFiles; - cmSystemTools::ExpandListArgument(notesFilesVariable,notesFiles); - std::vector<std::string>::iterator it; - for ( it = notesFiles.begin(); - it != notesFiles.end(); - ++ it ) - { - newNotesFiles.push_back(*it); - } + cmCTest::VectorOfStrings newNotesFiles; + cmSystemTools::ExpandListArgument(notesFilesVariable, notesFiles); + newNotesFiles.insert(newNotesFiles.end(), notesFiles.begin(), + notesFiles.end()); this->CTest->GenerateNotesFile(newNotesFiles); - } + } - const char* extraFilesVariable - = this->Makefile->GetDefinition("CTEST_EXTRA_SUBMIT_FILES"); - if (extraFilesVariable) - { + const char* extraFilesVariable = + this->Makefile->GetDefinition("CTEST_EXTRA_SUBMIT_FILES"); + if (extraFilesVariable) { std::vector<std::string> extraFiles; - std::vector<cmStdString> newExtraFiles; - cmSystemTools::ExpandListArgument(extraFilesVariable,extraFiles); - std::vector<std::string>::iterator it; - for ( it = extraFiles.begin(); - it != extraFiles.end(); - ++ it ) - { - newExtraFiles.push_back(*it); - } - if ( !this->CTest->SubmitExtraFiles(newExtraFiles)) - { + cmCTest::VectorOfStrings newExtraFiles; + cmSystemTools::ExpandListArgument(extraFilesVariable, extraFiles); + newExtraFiles.insert(newExtraFiles.end(), extraFiles.begin(), + extraFiles.end()); + if (!this->CTest->SubmitExtraFiles(newExtraFiles)) { this->SetError("problem submitting extra files."); - return 0; - } + return CM_NULLPTR; } + } - cmCTestGenericHandler* handler - = this->CTest->GetInitializedHandler("submit"); - if ( !handler ) - { + cmCTestGenericHandler* handler = + this->CTest->GetInitializedHandler("submit"); + if (!handler) { this->SetError("internal CTest error. Cannot instantiate submit handler"); - return 0; - } + return CM_NULLPTR; + } // If no FILES or PARTS given, *all* PARTS are submitted by default. // @@ -127,8 +111,7 @@ cmCTestGenericHandler* cmCTestSubmitCommand::InitializeHandler() // If given explicit FILES to submit, pass them to the handler. // - if(this->FilesMentioned) - { + if (this->FilesMentioned) { // Intentionally select *no* PARTS. (Pass an empty set.) If PARTS // were also explicitly mentioned, they will be selected below... // But FILES with no PARTS mentioned should just submit the FILES @@ -138,117 +121,152 @@ cmCTestGenericHandler* cmCTestSubmitCommand::InitializeHandler() static_cast<cmCTestSubmitHandler*>(handler)->SelectParts(noParts); static_cast<cmCTestSubmitHandler*>(handler)->SelectFiles(this->Files); - } + } // If a PARTS option was given, select only the named parts for submission. // - if(this->PartsMentioned) - { + if (this->PartsMentioned) { static_cast<cmCTestSubmitHandler*>(handler)->SelectParts(this->Parts); - } - - static_cast<cmCTestSubmitHandler*>(handler)->SetOption("RetryDelay", - this->RetryDelay.c_str()); - static_cast<cmCTestSubmitHandler*>(handler)->SetOption("RetryCount", - this->RetryCount.c_str()); - static_cast<cmCTestSubmitHandler*>(handler)->SetOption("InternalTest", - this->InternalTest ? "ON" : "OFF"); - + } + + // Pass along any HTTPHEADER to the handler if this option was given. + if (!this->HttpHeaders.empty()) { + static_cast<cmCTestSubmitHandler*>(handler)->SetHttpHeaders( + this->HttpHeaders); + } + + static_cast<cmCTestSubmitHandler*>(handler)->SetOption( + "RetryDelay", this->RetryDelay.c_str()); + static_cast<cmCTestSubmitHandler*>(handler)->SetOption( + "RetryCount", this->RetryCount.c_str()); + static_cast<cmCTestSubmitHandler*>(handler)->SetOption( + "InternalTest", this->InternalTest ? "ON" : "OFF"); + + handler->SetQuiet(this->Quiet); + + if (this->CDashUpload) { + static_cast<cmCTestSubmitHandler*>(handler)->SetOption( + "CDashUploadFile", this->CDashUploadFile.c_str()); + static_cast<cmCTestSubmitHandler*>(handler)->SetOption( + "CDashUploadType", this->CDashUploadType.c_str()); + } return handler; } +bool cmCTestSubmitCommand::InitialPass(std::vector<std::string> const& args, + cmExecutionStatus& status) +{ + this->CDashUpload = !args.empty() && args[0] == "CDASH_UPLOAD"; + return this->cmCTestHandlerCommand::InitialPass(args, status); +} -//---------------------------------------------------------------------------- bool cmCTestSubmitCommand::CheckArgumentKeyword(std::string const& arg) { - // Look for arguments specific to this command. - if(arg == "PARTS") - { - this->ArgumentDoing = ArgumentDoingParts; - this->PartsMentioned = true; - return true; + if (this->CDashUpload) { + // Arguments specific to the CDASH_UPLOAD signature. + if (arg == "CDASH_UPLOAD") { + this->ArgumentDoing = ArgumentDoingCDashUpload; + return true; } - if(arg == "FILES") - { - this->ArgumentDoing = ArgumentDoingFiles; - this->FilesMentioned = true; - return true; + if (arg == "CDASH_UPLOAD_TYPE") { + this->ArgumentDoing = ArgumentDoingCDashUploadType; + return true; } + } else { + // Arguments that cannot be used with CDASH_UPLOAD. + if (arg == "PARTS") { + this->ArgumentDoing = ArgumentDoingParts; + this->PartsMentioned = true; + return true; + } + + if (arg == "FILES") { + this->ArgumentDoing = ArgumentDoingFiles; + this->FilesMentioned = true; + return true; + } + } + // Arguments used by both modes. + if (arg == "HTTPHEADER") { + this->ArgumentDoing = ArgumentDoingHttpHeader; + return true; + } - if(arg == "RETRY_COUNT") - { + if (arg == "RETRY_COUNT") { this->ArgumentDoing = ArgumentDoingRetryCount; return true; - } + } - if(arg == "RETRY_DELAY") - { + if (arg == "RETRY_DELAY") { this->ArgumentDoing = ArgumentDoingRetryDelay; return true; - } + } - if(arg == "INTERNAL_TEST_CHECKSUM") - { + if (arg == "INTERNAL_TEST_CHECKSUM") { this->InternalTest = true; return true; - } + } // Look for other arguments. return this->Superclass::CheckArgumentKeyword(arg); } - -//---------------------------------------------------------------------------- bool cmCTestSubmitCommand::CheckArgumentValue(std::string const& arg) { // Handle states specific to this command. - if(this->ArgumentDoing == ArgumentDoingParts) - { + if (this->ArgumentDoing == ArgumentDoingParts) { cmCTest::Part p = this->CTest->GetPartFromName(arg.c_str()); - if(p != cmCTest::PartCount) - { + if (p != cmCTest::PartCount) { this->Parts.insert(p); - } - else - { - cmOStringStream e; + } else { + std::ostringstream e; e << "Part name \"" << arg << "\" is invalid."; this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str()); this->ArgumentDoing = ArgumentDoingError; - } - return true; } - - if(this->ArgumentDoing == ArgumentDoingFiles) - { - cmStdString filename(arg); - if(cmSystemTools::FileExists(filename.c_str())) - { - this->Files.insert(filename); - } - else - { - cmOStringStream e; - e << "File \"" << filename << "\" does not exist. Cannot submit " - << "a non-existent file."; + return true; + } + + if (this->ArgumentDoing == ArgumentDoingFiles) { + if (cmSystemTools::FileExists(arg.c_str())) { + this->Files.insert(arg); + } else { + std::ostringstream e; + e << "File \"" << arg << "\" does not exist. Cannot submit " + << "a non-existent file."; this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str()); this->ArgumentDoing = ArgumentDoingError; - } - return true; } + return true; + } - if(this->ArgumentDoing == ArgumentDoingRetryCount) - { + if (this->ArgumentDoing == ArgumentDoingHttpHeader) { + this->HttpHeaders.push_back(arg); + return true; + } + + if (this->ArgumentDoing == ArgumentDoingRetryCount) { this->RetryCount = arg; return true; - } + } - if(this->ArgumentDoing == ArgumentDoingRetryDelay) - { + if (this->ArgumentDoing == ArgumentDoingRetryDelay) { this->RetryDelay = arg; return true; - } + } + + if (this->ArgumentDoing == ArgumentDoingCDashUpload) { + this->ArgumentDoing = ArgumentDoingNone; + this->CDashUploadFile = arg; + return true; + } + + if (this->ArgumentDoing == ArgumentDoingCDashUploadType) { + this->ArgumentDoing = ArgumentDoingNone; + this->CDashUploadType = arg; + return true; + } // Look for other arguments. return this->Superclass::CheckArgumentValue(arg); diff --git a/Source/CTest/cmCTestSubmitCommand.h b/Source/CTest/cmCTestSubmitCommand.h index 53ee8754e..cf65cdc8d 100644 --- a/Source/CTest/cmCTestSubmitCommand.h +++ b/Source/CTest/cmCTestSubmitCommand.h @@ -1,19 +1,20 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestSubmitCommand_h #define cmCTestSubmitCommand_h -#include "cmCTestHandlerCommand.h" +#include "cmConfigure.h" + #include "cmCTest.h" +#include "cmCTestHandlerCommand.h" + +#include <set> +#include <string> +#include <vector> + +class cmCTestGenericHandler; +class cmCommand; +class cmExecutionStatus; /** \class cmCTestSubmit * \brief Run a ctest script @@ -24,77 +25,42 @@ class cmCTestSubmitCommand : public cmCTestHandlerCommand { public: - cmCTestSubmitCommand() - { + { this->PartsMentioned = false; this->FilesMentioned = false; this->InternalTest = false; this->RetryCount = ""; this->RetryDelay = ""; - } + this->CDashUpload = false; + } /** * This is a virtual constructor for the command. */ - virtual cmCommand* Clone() - { + cmCommand* Clone() CM_OVERRIDE + { cmCTestSubmitCommand* ni = new cmCTestSubmitCommand; ni->CTest = this->CTest; ni->CTestScriptHandler = this->CTestScriptHandler; return ni; - } + } - /** - * The name of the command as specified in CMakeList.txt. - */ - virtual const char* GetName() const { return "ctest_submit";} + bool InitialPass(std::vector<std::string> const& args, + cmExecutionStatus& status) CM_OVERRIDE; /** - * Succinct documentation. - */ - virtual const char* GetTerseDocumentation() const - { - return "Submit results to a dashboard server."; - } - - /** - * More documentation. + * The name of the command as specified in CMakeList.txt. */ - virtual const char* GetFullDocumentation() const - { - return - " ctest_submit([PARTS ...] [FILES ...] [RETRY_COUNT count] " - " [RETRY_DELAY delay][RETURN_VALUE res])\n" - "By default all available parts are submitted if no PARTS or FILES " - "are specified. " - "The PARTS option lists a subset of parts to be submitted. " - "Valid part names are:\n" - " Start = nothing\n" - " Update = ctest_update results, in Update.xml\n" - " Configure = ctest_configure results, in Configure.xml\n" - " Build = ctest_build results, in Build.xml\n" - " Test = ctest_test results, in Test.xml\n" - " Coverage = ctest_coverage results, in Coverage.xml\n" - " MemCheck = ctest_memcheck results, in DynamicAnalysis.xml\n" - " Notes = Files listed by CTEST_NOTES_FILES, in Notes.xml\n" - " ExtraFiles = Files listed by CTEST_EXTRA_SUBMIT_FILES\n" - " Submit = nothing\n" - "The FILES option explicitly lists specific files to be submitted. " - "Each individual file must exist at the time of the call.\n" - "The RETRY_DELAY option specifies how long in seconds to wait after " - "a timed-out submission before attempting to re-submit.\n" - "The RETRY_COUNT option specifies how many times to retry a timed-out " - "submission.\n"; - } + std::string GetName() const CM_OVERRIDE { return "ctest_submit"; } - cmTypeMacro(cmCTestSubmitCommand, cmCTestHandlerCommand); + typedef cmCTestHandlerCommand Superclass; protected: - cmCTestGenericHandler* InitializeHandler(); + cmCTestGenericHandler* InitializeHandler() CM_OVERRIDE; - virtual bool CheckArgumentKeyword(std::string const& arg); - virtual bool CheckArgumentValue(std::string const& arg); + bool CheckArgumentKeyword(std::string const& arg) CM_OVERRIDE; + bool CheckArgumentValue(std::string const& arg) CM_OVERRIDE; enum { @@ -102,6 +68,9 @@ protected: ArgumentDoingFiles, ArgumentDoingRetryDelay, ArgumentDoingRetryCount, + ArgumentDoingCDashUpload, + ArgumentDoingCDashUploadType, + ArgumentDoingHttpHeader, ArgumentDoingLast2 }; @@ -112,7 +81,10 @@ protected: cmCTest::SetOfStrings Files; std::string RetryCount; std::string RetryDelay; + bool CDashUpload; + std::string CDashUploadFile; + std::string CDashUploadType; + std::vector<std::string> HttpHeaders; }; - #endif diff --git a/Source/CTest/cmCTestSubmitHandler.cxx b/Source/CTest/cmCTestSubmitHandler.cxx index 941d34877..8d62fa156 100644 --- a/Source/CTest/cmCTestSubmitHandler.cxx +++ b/Source/CTest/cmCTestSubmitHandler.cxx @@ -1,161 +1,140 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* 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 "cmSystemTools.h" -#include "cmVersion.h" -#include "cmGeneratedFileStream.h" +#include "cm_curl.h" +#include "cm_jsoncpp_reader.h" +#include "cm_jsoncpp_value.h" +#include "cmsys/Process.h" +#include <sstream> +#include <stdio.h> +#include <stdlib.h> + #include "cmCTest.h" +#include "cmCTestCurl.h" +#include "cmCTestScriptHandler.h" +#include "cmCurl.h" +#include "cmGeneratedFileStream.h" +#include "cmProcessOutput.h" +#include "cmState.h" +#include "cmSystemTools.h" +#include "cmThirdParty.h" +#include "cmWorkingDirectory.h" #include "cmXMLParser.h" +#include "cmake.h" -#include <cmsys/Process.h> -#include <cmsys/Base64.h> - -// For XML-RPC submission +#if defined(CTEST_USE_XMLRPC) +#include "cmVersion.h" +#include "cm_sys_stat.h" #include "cm_xmlrpc.h" - -// For curl submission -#include "cm_curl.h" - -#include <sys/stat.h> +#endif #define SUBMIT_TIMEOUT_IN_SECONDS_DEFAULT 120 typedef std::vector<char> cmCTestSubmitHandlerVectorOfChar; -//---------------------------------------------------------------------------- -class cmCTestSubmitHandler::ResponseParser: public cmXMLParser +class cmCTestSubmitHandler::ResponseParser : public cmXMLParser { public: ResponseParser() { this->Status = STATUS_OK; } - ~ResponseParser() {} + ~ResponseParser() CM_OVERRIDE {} public: - enum StatusType - { + { STATUS_OK, STATUS_WARNING, STATUS_ERROR - }; + }; StatusType Status; - std::string CDashVersion; std::string Filename; std::string MD5; std::string Message; private: - std::vector<char> CurrentValue; std::string GetCurrentValue() - { + { std::string val; - if(this->CurrentValue.size()) - { + if (!this->CurrentValue.empty()) { val.assign(&this->CurrentValue[0], this->CurrentValue.size()); - } - return val; } + return val; + } - virtual void StartElement(const char* name, const char** atts) - { + void StartElement(const std::string& /*name*/, + const char** /*atts*/) CM_OVERRIDE + { this->CurrentValue.clear(); - if(strcmp(name, "cdash") == 0) - { - this->CDashVersion = this->FindAttribute(atts, "version"); - } - } + } - virtual void CharacterDataHandler(const char* data, int length) - { - this->CurrentValue.insert(this->CurrentValue.end(), data, data+length); - } + void CharacterDataHandler(const char* data, int length) CM_OVERRIDE + { + this->CurrentValue.insert(this->CurrentValue.end(), data, data + length); + } - virtual void EndElement(const char* name) - { - if(strcmp(name, "status") == 0) - { + void EndElement(const std::string& name) CM_OVERRIDE + { + if (name == "status") { std::string status = cmSystemTools::UpperCase(this->GetCurrentValue()); - if(status == "OK" || status == "SUCCESS") - { + if (status == "OK" || status == "SUCCESS") { this->Status = STATUS_OK; - } - else if(status == "WARNING") - { + } else if (status == "WARNING") { this->Status = STATUS_WARNING; - } - else - { + } else { this->Status = STATUS_ERROR; - } } - else if(strcmp(name, "filename") == 0) - { + } else if (name == "filename") { this->Filename = this->GetCurrentValue(); - } - else if(strcmp(name, "md5") == 0) - { + } else if (name == "md5") { this->MD5 = this->GetCurrentValue(); - } - else if(strcmp(name, "message") == 0) - { + } else if (name == "message") { this->Message = this->GetCurrentValue(); - } } + } }; - -static size_t -cmCTestSubmitHandlerWriteMemoryCallback(void *ptr, size_t size, size_t nmemb, - void *data) +static size_t cmCTestSubmitHandlerWriteMemoryCallback(void* ptr, size_t size, + size_t nmemb, void* data) { int realsize = (int)(size * nmemb); - cmCTestSubmitHandlerVectorOfChar *vec - = static_cast<cmCTestSubmitHandlerVectorOfChar*>(data); + 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 *, curl_infotype, char *chPtr, - size_t size, void *data) +static size_t cmCTestSubmitHandlerCurlDebugCallback(CURL* /*unused*/, + curl_infotype /*unused*/, + char* chPtr, size_t size, + void* data) { - cmCTestSubmitHandlerVectorOfChar *vec - = static_cast<cmCTestSubmitHandlerVectorOfChar*>(data); + cmCTestSubmitHandlerVectorOfChar* vec = + static_cast<cmCTestSubmitHandlerVectorOfChar*>(data); vec->insert(vec->end(), chPtr, chPtr + size); return size; } -//---------------------------------------------------------------------------- -cmCTestSubmitHandler::cmCTestSubmitHandler() : HTTPProxy(), FTPProxy() +cmCTestSubmitHandler::cmCTestSubmitHandler() + : HTTPProxy() + , FTPProxy() { 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)) - { + for (cmCTest::Part p = cmCTest::PartStart; p != cmCTest::PartCount; + p = cmCTest::Part(p + 1)) { this->SubmitPart[p] = true; - } + } this->CDash = false; this->HasWarnings = false; this->HasErrors = false; @@ -165,17 +144,16 @@ void cmCTestSubmitHandler::Initialize() this->HTTPProxyAuth = ""; this->FTPProxy = ""; this->FTPProxyType = 0; - this->LogFile = 0; + this->LogFile = CM_NULLPTR; this->Files.clear(); } -//---------------------------------------------------------------------------- -bool cmCTestSubmitHandler::SubmitUsingFTP(const cmStdString& localprefix, - const std::set<cmStdString>& files, - const cmStdString& remoteprefix, - const cmStdString& url) +bool cmCTestSubmitHandler::SubmitUsingFTP(const std::string& localprefix, + const std::set<std::string>& files, + const std::string& remoteprefix, + const std::string& url) { - CURL *curl; + CURL* curl; CURLcode res; FILE* ftpfile; char error_buffer[1024]; @@ -184,28 +162,24 @@ bool cmCTestSubmitHandler::SubmitUsingFTP(const cmStdString& localprefix, ::curl_global_init(CURL_GLOBAL_ALL); cmCTest::SetOfStrings::const_iterator file; - for ( file = files.begin(); file != files.end(); ++file ) - { + for (file = files.begin(); file != files.end(); ++file) { /* get a curl handle */ curl = curl_easy_init(); - if(curl) - { + if (curl) { // Using proxy - if ( this->FTPProxyType > 0 ) - { + if (this->FTPProxyType > 0) { curl_easy_setopt(curl, CURLOPT_PROXY, this->FTPProxy.c_str()); - switch (this->FTPProxyType) - { - 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); - } + switch (this->FTPProxyType) { + 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); } + } // enable uploading ::curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); @@ -213,193 +187,191 @@ bool cmCTestSubmitHandler::SubmitUsingFTP(const cmStdString& localprefix, // 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); + SUBMIT_TIMEOUT_IN_SECONDS_DEFAULT); ::curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); - cmStdString local_file = *file; - if ( !cmSystemTools::FileExists(local_file.c_str()) ) - { + std::string local_file = *file; + if (!cmSystemTools::FileExists(local_file.c_str())) { local_file = localprefix + "/" + *file; - } - cmStdString upload_as - = url + "/" + remoteprefix + cmSystemTools::GetFilenameName(*file); - - struct stat st; - if ( ::stat(local_file.c_str(), &st) ) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, " Cannot find file: " - << local_file.c_str() << std::endl); + } + std::string upload_as = + url + "/" + remoteprefix + cmSystemTools::GetFilenameName(*file); + + if (!cmSystemTools::FileExists(local_file.c_str())) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + " Cannot find file: " << local_file << std::endl); ::curl_easy_cleanup(curl); ::curl_global_cleanup(); return false; - } + } + unsigned long filelen = cmSystemTools::FileLength(local_file); - ftpfile = ::fopen(local_file.c_str(), "rb"); - *this->LogFile << "\tUpload file: " << local_file.c_str() << " to " - << upload_as.c_str() << std::endl; - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " Upload file: " - << local_file.c_str() << " to " - << upload_as.c_str() << std::endl); + ftpfile = cmsys::SystemTools::Fopen(local_file, "rb"); + *this->LogFile << "\tUpload file: " << local_file << " to " << upload_as + << std::endl; + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Upload file: " << local_file << " to " + << upload_as << std::endl, + this->Quiet); ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); // specify target - ::curl_easy_setopt(curl,CURLOPT_URL, upload_as.c_str()); + ::curl_easy_setopt(curl, CURLOPT_URL, upload_as.c_str()); // 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>(st.st_size)); + ::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); + cmCTestSubmitHandlerWriteMemoryCallback); ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, - cmCTestSubmitHandlerCurlDebugCallback); + cmCTestSubmitHandlerCurlDebugCallback); /* we pass our 'chunk' struct to the callback function */ cmCTestSubmitHandlerVectorOfChar chunk; cmCTestSubmitHandlerVectorOfChar chunkDebug; - ::curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk); - ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void *)&chunkDebug); + ::curl_easy_setopt(curl, CURLOPT_FILE, (void*)&chunk); + ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void*)&chunkDebug); // Now run off and do what you've been told! res = ::curl_easy_perform(curl); - if ( chunk.size() > 0 ) - { - cmCTestLog(this->CTest, DEBUG, "CURL output: [" - << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "]" - << std::endl); - } - if ( chunkDebug.size() > 0 ) - { - cmCTestLog(this->CTest, DEBUG, "CURL debug output: [" - << cmCTestLogWrite(&*chunkDebug.begin(), chunkDebug.size()) << "]" - << std::endl); - } + if (!chunk.empty()) { + cmCTestOptionalLog(this->CTest, DEBUG, "CURL output: [" + << cmCTestLogWrite(&*chunk.begin(), chunk.size()) + << "]" << std::endl, + this->Quiet); + } + if (!chunkDebug.empty()) { + cmCTestOptionalLog( + this->CTest, DEBUG, "CURL debug output: [" + << cmCTestLogWrite(&*chunkDebug.begin(), chunkDebug.size()) << "]" + << std::endl, + this->Quiet); + } fclose(ftpfile); - if ( res ) - { + if (res) { + cmCTestLog(this->CTest, ERROR_MESSAGE, " Error when uploading file: " + << local_file << std::endl); cmCTestLog(this->CTest, ERROR_MESSAGE, - " Error when uploading file: " - << local_file.c_str() << std::endl); - cmCTestLog(this->CTest, ERROR_MESSAGE, " Error message was: " - << error_buffer << std::endl); - *this->LogFile << " Error when uploading file: " - << local_file.c_str() + " Error message was: " << error_buffer << std::endl); + *this->LogFile << " Error when uploading file: " << local_file << std::endl - << " Error message was: " - << error_buffer << std::endl + << " Error message was: " << error_buffer << std::endl << " Curl output was: "; // avoid dereference of empty vector - if(chunk.size()) - { + if (!chunk.empty()) { *this->LogFile << cmCTestLogWrite(&*chunk.begin(), chunk.size()); cmCTestLog(this->CTest, ERROR_MESSAGE, "CURL output: [" - << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "]" - << std::endl); - } + << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "]" + << std::endl); + } *this->LogFile << std::endl; ::curl_easy_cleanup(curl); ::curl_global_cleanup(); return false; - } + } // always cleanup ::curl_easy_cleanup(curl); - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Uploaded: " + local_file - << std::endl); - } + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Uploaded: " + local_file << std::endl, + this->Quiet); } + } ::curl_global_cleanup(); return true; } -//---------------------------------------------------------------------------- // Uploading files is simpler -bool cmCTestSubmitHandler::SubmitUsingHTTP(const cmStdString& localprefix, - const std::set<cmStdString>& files, - const cmStdString& remoteprefix, - const cmStdString& url) +bool cmCTestSubmitHandler::SubmitUsingHTTP(const std::string& localprefix, + const std::set<std::string>& files, + const std::string& remoteprefix, + const std::string& url) { - CURL *curl; + 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(CM_NULLPTR, "Content-Type: text/xml"); + + // Add any additional headers that the user specified. + for (std::vector<std::string>::const_iterator h = this->HttpHeaders.begin(); + h != this->HttpHeaders.end(); ++h) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " 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); - cmStdString dropMethod(this->CTest->GetCTestConfiguration("DropMethod")); - cmStdString curlopt(this->CTest->GetCTestConfiguration("CurlOptions")); + std::string dropMethod(this->CTest->GetCTestConfiguration("DropMethod")); + std::string curlopt(this->CTest->GetCTestConfiguration("CurlOptions")); std::vector<std::string> args; - cmSystemTools::ExpandListArgument(curlopt.c_str(), args); + cmSystemTools::ExpandListArgument(curlopt, args); bool verifyPeerOff = false; bool verifyHostOff = false; - for( std::vector<std::string>::iterator i = args.begin(); - i != args.end(); ++i) - { - if(*i == "CURLOPT_SSL_VERIFYPEER_OFF") - { + for (std::vector<std::string>::iterator i = args.begin(); i != args.end(); + ++i) { + if (*i == "CURLOPT_SSL_VERIFYPEER_OFF") { verifyPeerOff = true; - } - if(*i == "CURLOPT_SSL_VERIFYHOST_OFF") - { + } + if (*i == "CURLOPT_SSL_VERIFYHOST_OFF") { verifyHostOff = true; - } } - cmStdString::size_type kk; + } + std::string::size_type kk; cmCTest::SetOfStrings::const_iterator file; - for ( file = files.begin(); file != files.end(); ++file ) - { + for (file = files.begin(); file != files.end(); ++file) { /* get a curl handle */ curl = curl_easy_init(); - if(curl) - { - if(verifyPeerOff) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - " Set CURLOPT_SSL_VERIFYPEER to off\n"); + 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) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - " Set CURLOPT_SSL_VERIFYHOST to off\n"); + } + 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 ) - { + 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.size() > 0) - { - curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, - this->HTTPProxyAuth.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()) - { + } + 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 */ @@ -408,165 +380,156 @@ bool cmCTestSubmitHandler::SubmitUsingHTTP(const cmStdString& localprefix, // 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); + SUBMIT_TIMEOUT_IN_SECONDS_DEFAULT); /* HTTP PUT please */ ::curl_easy_setopt(curl, CURLOPT_PUT, 1); ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); - cmStdString local_file = *file; - if ( !cmSystemTools::FileExists(local_file.c_str()) ) - { + ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + std::string local_file = *file; + if (!cmSystemTools::FileExists(local_file.c_str())) { local_file = localprefix + "/" + *file; - } - cmStdString remote_file - = remoteprefix + cmSystemTools::GetFilenameName(*file); + } + std::string remote_file = + remoteprefix + cmSystemTools::GetFilenameName(*file); - *this->LogFile << "\tUpload file: " << local_file.c_str() << " to " - << remote_file.c_str() << std::endl; + *this->LogFile << "\tUpload file: " << local_file << " to " + << remote_file << std::endl; - cmStdString ofile = ""; - for ( kk = 0; kk < remote_file.size(); kk ++ ) - { + std::string ofile; + for (kk = 0; kk < remote_file.size(); kk++) { char c = remote_file[kk]; char hexCh[4] = { 0, 0, 0, 0 }; hexCh[0] = c; - switch ( c ) - { - case '+': - case '?': - case '/': - case '\\': - case '&': - case ' ': - case '=': - case '%': - sprintf(hexCh, "%%%02X", (int)c); - ofile.append(hexCh); - break; - default: - ofile.append(hexCh); - } + switch (c) { + case '+': + case '?': + case '/': + case '\\': + case '&': + case ' ': + case '=': + case '%': + sprintf(hexCh, "%%%02X", (int)c); + ofile.append(hexCh); + break; + default: + ofile.append(hexCh); } - cmStdString upload_as - = url + ((url.find("?",0) == cmStdString::npos) ? "?" : "&") - + "FileName=" + ofile; + } + std::string upload_as = url + + ((url.find('?') == std::string::npos) ? '?' : '&') + "FileName=" + + ofile; upload_as += "&MD5="; - if(cmSystemTools::IsOn(this->GetOption("InternalTest"))) - { + if (cmSystemTools::IsOn(this->GetOption("InternalTest"))) { upload_as += "bad_md5sum"; - } - else - { + } else { char md5[33]; - cmSystemTools::ComputeFileMD5(local_file.c_str(), md5); + cmSystemTools::ComputeFileMD5(local_file, md5); md5[32] = 0; upload_as += md5; - } + } - struct stat st; - if ( ::stat(local_file.c_str(), &st) ) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, " Cannot find file: " - << local_file.c_str() << std::endl); + if (!cmSystemTools::FileExists(local_file.c_str())) { + 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 = ::fopen(local_file.c_str(), "rb"); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " Upload file: " - << local_file.c_str() << " to " - << upload_as.c_str() << " Size: " << st.st_size << std::endl); + 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()); + ::curl_easy_setopt(curl, CURLOPT_URL, upload_as.c_str()); // 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>(st.st_size)); + ::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); + cmCTestSubmitHandlerWriteMemoryCallback); ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, - cmCTestSubmitHandlerCurlDebugCallback); + cmCTestSubmitHandlerCurlDebugCallback); /* we pass our 'chunk' struct to the callback function */ cmCTestSubmitHandlerVectorOfChar chunk; cmCTestSubmitHandlerVectorOfChar chunkDebug; - ::curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk); - ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void *)&chunkDebug); + ::curl_easy_setopt(curl, CURLOPT_FILE, (void*)&chunk); + ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void*)&chunkDebug); // Now run off and do what you've been told! res = ::curl_easy_perform(curl); - if(cmSystemTools::IsOn(this->GetOption("InternalTest")) && - cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, - this->CTest->GetCDashVersion().c_str(), "1.7")) - { - // mock failure output for internal test case - std::string mock_output = "<cdash version=\"1.7.0\">\n" - " <status>ERROR</status>\n" - " <message>Checksum failed for file.</message>\n" - "</cdash>\n"; - chunk.clear(); - chunk.assign(mock_output.begin(), mock_output.end()); - } - - if ( chunk.size() > 0 ) - { - cmCTestLog(this->CTest, DEBUG, "CURL output: [" - << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "]" - << std::endl); + if (!chunk.empty()) { + cmCTestOptionalLog(this->CTest, DEBUG, "CURL output: [" + << cmCTestLogWrite(&*chunk.begin(), chunk.size()) + << "]" << std::endl, + this->Quiet); this->ParseResponse(chunk); - } - if ( chunkDebug.size() > 0 ) - { - cmCTestLog(this->CTest, DEBUG, "CURL debug output: [" - << cmCTestLogWrite(&*chunkDebug.begin(), chunkDebug.size()) << "]" - << std::endl); - } + } + 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") == NULL ? - "" : this->GetOption("RetryDelay"); - std::string retryCount = this->GetOption("RetryCount") == NULL ? - "" : this->GetOption("RetryCount"); - - int delay = retryDelay == "" ? atoi(this->CTest->GetCTestConfiguration( - "CTestSubmitRetryDelay").c_str()) : atoi(retryDelay.c_str()); - int count = retryCount == "" ? atoi(this->CTest->GetCTestConfiguration( - "CTestSubmitRetryCount").c_str()) : atoi(retryCount.c_str()); - - for(int i = 0; i < count; i++) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, - " Submit failed, waiting " << delay << " seconds...\n"); + if (res != CURLE_OK || this->HasErrors) { + std::string retryDelay = this->GetOption("RetryDelay") == CM_NULLPTR + ? "" + : this->GetOption("RetryDelay"); + std::string retryCount = this->GetOption("RetryCount") == CM_NULLPTR + ? "" + : this->GetOption("RetryCount"); + + int delay = retryDelay == "" + ? atoi(this->CTest->GetCTestConfiguration("CTestSubmitRetryDelay") + .c_str()) + : atoi(retryDelay.c_str()); + int count = retryCount == "" + ? 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 + << " seconds...\n", + this->Quiet); double stop = cmSystemTools::GetTime() + delay; - while(cmSystemTools::GetTime() < stop) - { + while (cmSystemTools::GetTime() < stop) { cmSystemTools::Delay(100); - } + } - cmCTestLog(this->CTest, HANDLER_OUTPUT, - " Retry submission: Attempt " << (i + 1) << " of " - << count << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Retry submission: Attempt " + << (i + 1) << " of " << count << std::endl, + this->Quiet); ::fclose(ftpfile); - ftpfile = ::fopen(local_file.c_str(), "rb"); + ftpfile = cmsys::SystemTools::Fopen(local_file, "rb"); ::curl_easy_setopt(curl, CURLOPT_INFILE, ftpfile); chunk.clear(); @@ -575,134 +538,121 @@ bool cmCTestSubmitHandler::SubmitUsingHTTP(const cmStdString& localprefix, res = ::curl_easy_perform(curl); - if ( chunk.size() > 0 ) - { - cmCTestLog(this->CTest, DEBUG, "CURL output: [" - << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "]" - << std::endl); + 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) - { + if (res == CURLE_OK && !this->HasErrors) { break; - } } } + } fclose(ftpfile); - if ( res ) - { + if (res) { + cmCTestLog(this->CTest, ERROR_MESSAGE, " Error when uploading file: " + << local_file << std::endl); cmCTestLog(this->CTest, ERROR_MESSAGE, - " Error when uploading file: " - << local_file.c_str() << std::endl); - cmCTestLog(this->CTest, ERROR_MESSAGE, " Error message was: " - << error_buffer << std::endl); - *this->LogFile << " Error when uploading file: " - << local_file.c_str() + " 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.size()) - { + 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); - } + << 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); - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Uploaded: " + local_file - << std::endl); - } + 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) +void cmCTestSubmitHandler::ParseResponse( + cmCTestSubmitHandlerVectorOfChar chunk) { - std::string output = ""; + std::string output; output.append(chunk.begin(), chunk.end()); - if(output.find("<cdash") != output.npos) - { + if (output.find("<cdash") != std::string::npos) { ResponseParser parser; parser.Parse(output.c_str()); - if(parser.Status != ResponseParser::STATUS_OK) - { + if (parser.Status != ResponseParser::STATUS_OK) { this->HasErrors = true; - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Submission failed: " << - parser.Message << std::endl); + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Submission failed: " << parser.Message << std::endl); return; - } } + } output = cmSystemTools::UpperCase(output); - if(output.find("WARNING") != std::string::npos) - { + if (output.find("WARNING") != std::string::npos) { this->HasWarnings = true; - } - if(output.find("ERROR") != std::string::npos) - { + } + 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"); - } + if (this->HasWarnings || this->HasErrors) { + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Server Response:\n" + << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "\n"); + } } -//---------------------------------------------------------------------------- -bool cmCTestSubmitHandler::TriggerUsingHTTP( - const std::set<cmStdString>& files, - const cmStdString& remoteprefix, - const cmStdString& url) +bool cmCTestSubmitHandler::TriggerUsingHTTP(const std::set<std::string>& files, + const std::string& remoteprefix, + const std::string& url) { - CURL *curl; + CURL* curl; char error_buffer[1024]; + /* In windows, this will init the winsock stuff */ ::curl_global_init(CURL_GLOBAL_ALL); cmCTest::SetOfStrings::const_iterator file; - for ( file = files.begin(); file != files.end(); ++file ) - { + for (file = files.begin(); file != files.end(); ++file) { /* get a curl handle */ curl = curl_easy_init(); - if(curl) - { + if (curl) { // Using proxy - if ( this->HTTPProxyType > 0 ) - { + 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.size() > 0) - { - curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, - this->HTTPProxyAuth.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()); } - } } + } ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); @@ -711,126 +661,120 @@ bool cmCTestSubmitHandler::TriggerUsingHTTP( // specify handler for output ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, - cmCTestSubmitHandlerWriteMemoryCallback); + cmCTestSubmitHandlerWriteMemoryCallback); ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, - cmCTestSubmitHandlerCurlDebugCallback); + cmCTestSubmitHandlerCurlDebugCallback); /* we pass our 'chunk' struct to the callback function */ cmCTestSubmitHandlerVectorOfChar chunk; cmCTestSubmitHandlerVectorOfChar chunkDebug; - ::curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk); - ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void *)&chunkDebug); - - cmStdString rfile - = remoteprefix + cmSystemTools::GetFilenameName(*file); - cmStdString ofile = ""; - cmStdString::iterator kk; - for ( kk = rfile.begin(); kk < rfile.end(); ++ kk) - { + ::curl_easy_setopt(curl, CURLOPT_FILE, (void*)&chunk); + ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void*)&chunkDebug); + + std::string rfile = remoteprefix + cmSystemTools::GetFilenameName(*file); + std::string ofile; + std::string::iterator kk; + for (kk = rfile.begin(); kk < rfile.end(); ++kk) { char c = *kk; char hexCh[4] = { 0, 0, 0, 0 }; hexCh[0] = c; - switch ( c ) - { - case '+': - case '?': - case '/': - case '\\': - case '&': - case ' ': - case '=': - case '%': - sprintf(hexCh, "%%%02X", (int)c); - ofile.append(hexCh); - break; - default: - ofile.append(hexCh); - } + switch (c) { + case '+': + case '?': + case '/': + case '\\': + case '&': + case ' ': + case '=': + case '%': + sprintf(hexCh, "%%%02X", (int)c); + ofile.append(hexCh); + break; + default: + ofile.append(hexCh); } - cmStdString turl - = url + ((url.find("?",0) == cmStdString::npos) ? "?" : "&") - + "xmlfile=" + ofile; - *this->LogFile << "Trigger url: " << turl.c_str() << std::endl; - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " Trigger url: " - << turl.c_str() << std::endl); + } + std::string turl = url + + ((url.find('?') == std::string::npos) ? '?' : '&') + "xmlfile=" + + ofile; + *this->LogFile << "Trigger url: " << turl << std::endl; + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Trigger url: " << turl << std::endl, this->Quiet); curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); curl_easy_setopt(curl, CURLOPT_URL, turl.c_str()); - if ( curl_easy_perform(curl) ) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, " Error when triggering: " - << turl.c_str() << std::endl); - cmCTestLog(this->CTest, ERROR_MESSAGE, " Error message was: " - << error_buffer << std::endl); + if (curl_easy_perform(curl)) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + " Error when triggering: " << turl << std::endl); + cmCTestLog(this->CTest, ERROR_MESSAGE, + " Error message was: " << error_buffer << std::endl); *this->LogFile << "\tTriggering failed with error: " << error_buffer << std::endl << " Error message was: " << error_buffer << std::endl; - if(chunk.size()) - { - *this->LogFile - << " Curl output was: " - << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << std::endl; + 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); - } + << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "]" + << std::endl); + } ::curl_easy_cleanup(curl); ::curl_global_cleanup(); return false; - } + } - if ( chunk.size() > 0 ) - { - cmCTestLog(this->CTest, DEBUG, "CURL output: [" - << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "]" - << std::endl); - } - if ( chunkDebug.size() > 0 ) - { - cmCTestLog(this->CTest, DEBUG, "CURL debug output: [" - << cmCTestLogWrite(&*chunkDebug.begin(), chunkDebug.size()) - << "]" << std::endl); - } + if (!chunk.empty()) { + cmCTestOptionalLog(this->CTest, DEBUG, "CURL output: [" + << cmCTestLogWrite(&*chunk.begin(), chunk.size()) + << "]" << std::endl, + this->Quiet); + } + if (!chunkDebug.empty()) { + cmCTestOptionalLog( + this->CTest, DEBUG, "CURL debug output: [" + << cmCTestLogWrite(&*chunkDebug.begin(), chunkDebug.size()) << "]" + << std::endl, + this->Quiet); + } // always cleanup ::curl_easy_cleanup(curl); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl); - } + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl, + this->Quiet); } + } ::curl_global_cleanup(); - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Dart server triggered..." - << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Dart server triggered..." << std::endl, this->Quiet); return true; } -//---------------------------------------------------------------------------- -bool cmCTestSubmitHandler::SubmitUsingSCP( - const cmStdString& scp_command, - const cmStdString& localprefix, - const std::set<cmStdString>& files, - const cmStdString& remoteprefix, - const cmStdString& url) +bool cmCTestSubmitHandler::SubmitUsingSCP(const std::string& scp_command, + const std::string& localprefix, + const std::set<std::string>& files, + const std::string& remoteprefix, + const std::string& url) { - if ( !scp_command.size() || !localprefix.size() || - !files.size() || !remoteprefix.size() || !url.size() ) - { - return 0; - } + if (scp_command.empty() || localprefix.empty() || files.empty() || + remoteprefix.empty() || url.empty()) { + return false; + } + std::vector<const char*> argv; argv.push_back(scp_command.c_str()); // Scp command argv.push_back(scp_command.c_str()); // Dummy string for file argv.push_back(scp_command.c_str()); // Dummy string for remote url - argv.push_back(0); + argv.push_back(CM_NULLPTR); cmsysProcess* cp = cmsysProcess_New(); cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1); - //cmsysProcess_SetTimeout(cp, timeout); + // cmsysProcess_SetTimeout(cp, timeout); int problems = 0; cmCTest::SetOfStrings::const_iterator file; - for ( file = files.begin(); file != files.end(); ++file ) - { + for (file = files.begin(); file != files.end(); ++file) { int retVal; std::string lfname = localprefix; @@ -840,124 +784,114 @@ bool cmCTestSubmitHandler::SubmitUsingSCP( argv[1] = lfname.c_str(); std::string rfname = url + "/" + remoteprefix + *file; argv[2] = rfname.c_str(); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Execute \"" << argv[0] - << "\" \"" << argv[1] << "\" \"" - << argv[2] << "\"" << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Execute \"" + << argv[0] << "\" \"" << argv[1] << "\" \"" << argv[2] + << "\"" << std::endl, + this->Quiet); *this->LogFile << "Execute \"" << argv[0] << "\" \"" << argv[1] << "\" \"" - << argv[2] << "\"" << std::endl; + << argv[2] << "\"" << std::endl; cmsysProcess_SetCommand(cp, &*argv.begin()); cmsysProcess_Execute(cp); char* data; int length; + cmProcessOutput processOutput; + std::string strdata; + + while (cmsysProcess_WaitForData(cp, &data, &length, CM_NULLPTR)) { + processOutput.DecodeText(data, length, strdata); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + cmCTestLogWrite(strdata.c_str(), strdata.size()), + this->Quiet); + } + processOutput.DecodeText(std::string(), strdata); + if (!strdata.empty()) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + cmCTestLogWrite(strdata.c_str(), strdata.size()), + this->Quiet); + } - while(cmsysProcess_WaitForData(cp, &data, &length, 0)) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - cmCTestLogWrite(data, length)); - } - - cmsysProcess_WaitForExit(cp, 0); + cmsysProcess_WaitForExit(cp, CM_NULLPTR); int result = cmsysProcess_GetState(cp); - if(result == cmsysProcess_State_Exited) - { + if (result == cmsysProcess_State_Exited) { retVal = cmsysProcess_GetExitValue(cp); - if ( retVal != 0 ) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "\tSCP returned: " - << retVal << std::endl); + if (retVal != 0) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "\tSCP returned: " << retVal << std::endl, + this->Quiet); *this->LogFile << "\tSCP returned: " << retVal << std::endl; - problems ++; - } + problems++; } - else if(result == cmsysProcess_State_Exception) - { + } else if (result == cmsysProcess_State_Exception) { retVal = cmsysProcess_GetExitException(cp); - cmCTestLog(this->CTest, ERROR_MESSAGE, "\tThere was an exception: " - << retVal << std::endl); + cmCTestLog(this->CTest, ERROR_MESSAGE, + "\tThere was an exception: " << retVal << std::endl); *this->LogFile << "\tThere was an exception: " << retVal << std::endl; - problems ++; - } - else if(result == cmsysProcess_State_Expired) - { + problems++; + } else if (result == cmsysProcess_State_Expired) { cmCTestLog(this->CTest, ERROR_MESSAGE, "\tThere was a timeout" - << std::endl); + << std::endl); *this->LogFile << "\tThere was a timeout" << std::endl; - problems ++; - } - else if(result == cmsysProcess_State_Error) - { + problems++; + } else if (result == cmsysProcess_State_Error) { cmCTestLog(this->CTest, ERROR_MESSAGE, "\tError executing SCP: " - << cmsysProcess_GetErrorString(cp) << std::endl); + << cmsysProcess_GetErrorString(cp) << std::endl); *this->LogFile << "\tError executing SCP: " - << cmsysProcess_GetErrorString(cp) << std::endl; - problems ++; - } + << cmsysProcess_GetErrorString(cp) << std::endl; + problems++; } + } cmsysProcess_Delete(cp); - if ( problems ) - { - return false; - } - return true; + return problems == 0; } -//---------------------------------------------------------------------------- -bool cmCTestSubmitHandler::SubmitUsingCP( - const cmStdString& localprefix, - const std::set<cmStdString>& files, - const cmStdString& remoteprefix, - const cmStdString& destination) +bool cmCTestSubmitHandler::SubmitUsingCP(const std::string& localprefix, + const std::set<std::string>& files, + const std::string& remoteprefix, + const std::string& destination) { - if ( !localprefix.size() || - !files.size() || !remoteprefix.size() || !destination.size() ) - { + if (localprefix.empty() || files.empty() || remoteprefix.empty() || + destination.empty()) { + /* clang-format off */ cmCTestLog(this->CTest, ERROR_MESSAGE, "Missing arguments for submit via cp:\n" << "\tlocalprefix: " << localprefix << "\n" << "\tNumber of files: " << files.size() << "\n" << "\tremoteprefix: " << remoteprefix << "\n" << "\tdestination: " << destination << std::endl); - return 0; - } + /* clang-format on */ + return false; + } + cmCTest::SetOfStrings::const_iterator file; - bool problems = false; - for ( file = files.begin(); file != files.end(); ++file ) - { + for (file = files.begin(); file != files.end(); ++file) { std::string lfname = localprefix; cmSystemTools::ConvertToUnixSlashes(lfname); lfname += "/" + *file; std::string rfname = destination + "/" + remoteprefix + *file; - cmSystemTools::CopyFileAlways(lfname.c_str(), rfname.c_str()); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " Copy file: " - << lfname.c_str() << " to " - << rfname.c_str() << std::endl); - } + cmSystemTools::CopyFileAlways(lfname, rfname); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " Copy file: " + << lfname << " to " << rfname << std::endl, + this->Quiet); + } std::string tagDoneFile = destination + "/" + remoteprefix + "DONE"; - cmSystemTools::Touch(tagDoneFile.c_str(), true); - if ( problems ) - { - return false; - } + cmSystemTools::Touch(tagDoneFile, true); return true; } - -//---------------------------------------------------------------------------- #if defined(CTEST_USE_XMLRPC) -bool cmCTestSubmitHandler::SubmitUsingXMLRPC(const cmStdString& localprefix, - const std::set<cmStdString>& files, - const cmStdString& remoteprefix, - const cmStdString& url) +bool cmCTestSubmitHandler::SubmitUsingXMLRPC( + const std::string& localprefix, const std::set<std::string>& files, + const std::string& remoteprefix, const std::string& url) { xmlrpc_env env; char ctestString[] = "CTest"; std::string ctestVersionString = cmVersion::GetCMakeVersion(); char* ctestVersion = const_cast<char*>(ctestVersionString.c_str()); - cmStdString realURL = url + "/" + remoteprefix + "/Command/"; + std::string realURL = url + "/" + remoteprefix + "/Command/"; /* Start up our XML-RPC client library. */ xmlrpc_client_init(XMLRPC_CLIENT_NO_FLAGS, ctestString, ctestVersion); @@ -966,76 +900,72 @@ bool cmCTestSubmitHandler::SubmitUsingXMLRPC(const cmStdString& localprefix, xmlrpc_env_init(&env); /* Call the famous server at UserLand. */ - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Submitting to: " - << realURL.c_str() << " (" << remoteprefix.c_str() << ")" << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " Submitting to: " + << realURL << " (" << remoteprefix << ")" << std::endl, + this->Quiet); cmCTest::SetOfStrings::const_iterator file; - for ( file = files.begin(); file != files.end(); ++file ) - { - xmlrpc_value *result; + for (file = files.begin(); file != files.end(); ++file) { + xmlrpc_value* result; - cmStdString local_file = *file; - if ( !cmSystemTools::FileExists(local_file.c_str()) ) - { + std::string local_file = *file; + if (!cmSystemTools::FileExists(local_file.c_str())) { local_file = localprefix + "/" + *file; - } - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Submit file: " - << local_file.c_str() << std::endl); + } + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Submit file: " << local_file << std::endl, + this->Quiet); struct stat st; - if ( ::stat(local_file.c_str(), &st) ) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, " Cannot find file: " - << local_file.c_str() << std::endl); + if (::stat(local_file.c_str(), &st)) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + " Cannot find file: " << local_file << std::endl); return false; - } + } // off_t can be bigger than size_t. fread takes size_t. // make sure the file is not too big. - if(static_cast<off_t>(static_cast<size_t>(st.st_size)) != - static_cast<off_t>(st.st_size)) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, " File too big: " - << local_file.c_str() << std::endl); + if (static_cast<off_t>(static_cast<size_t>(st.st_size)) != + static_cast<off_t>(st.st_size)) { + cmCTestLog(this->CTest, ERROR_MESSAGE, " File too big: " << local_file + << std::endl); return false; - } + } size_t fileSize = static_cast<size_t>(st.st_size); - FILE* fp = fopen(local_file.c_str(), "rb"); - if ( !fp ) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, " Cannot open file: " - << local_file.c_str() << std::endl); + FILE* fp = cmsys::SystemTools::Fopen(local_file, "rb"); + if (!fp) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + " Cannot open file: " << local_file << std::endl); return false; - } + } - unsigned char *fileBuffer = new unsigned char[fileSize]; - if ( fread(fileBuffer, 1, fileSize, fp) != fileSize ) - { - delete [] fileBuffer; + unsigned char* fileBuffer = new unsigned char[fileSize]; + if (fread(fileBuffer, 1, fileSize, fp) != fileSize) { + delete[] fileBuffer; fclose(fp); - cmCTestLog(this->CTest, ERROR_MESSAGE, " Cannot read file: " - << local_file.c_str() << std::endl); + cmCTestLog(this->CTest, ERROR_MESSAGE, + " Cannot read file: " << local_file << std::endl); return false; - } + } fclose(fp); char remoteCommand[] = "Submit.put"; char* pRealURL = const_cast<char*>(realURL.c_str()); - result = xmlrpc_client_call(&env, pRealURL, remoteCommand, - "(6)", fileBuffer, (xmlrpc_int32)fileSize ); + result = xmlrpc_client_call(&env, pRealURL, remoteCommand, "(6)", + fileBuffer, (xmlrpc_int32)fileSize); - delete [] fileBuffer; + delete[] fileBuffer; - if ( env.fault_occurred ) - { + if (env.fault_occurred) { cmCTestLog(this->CTest, ERROR_MESSAGE, " Submission problem: " - << env.fault_string << " (" << env.fault_code << ")" << std::endl); + << env.fault_string << " (" << env.fault_code << ")" + << std::endl); xmlrpc_env_clean(&env); xmlrpc_client_cleanup(); return false; - } + } /* Dispose of our result value. */ xmlrpc_DECREF(result); - } + } /* Clean up our error-handling environment. */ xmlrpc_env_clean(&env); @@ -1045,126 +975,335 @@ bool cmCTestSubmitHandler::SubmitUsingXMLRPC(const cmStdString& localprefix, return true; } #else -bool cmCTestSubmitHandler::SubmitUsingXMLRPC(cmStdString const&, - std::set<cmStdString> const&, - cmStdString const&, - cmStdString const&) +bool cmCTestSubmitHandler::SubmitUsingXMLRPC( + std::string const& /*unused*/, std::set<std::string> const& /*unused*/, + std::string const& /*unused*/, std::string const& /*unused*/) { return false; } #endif -//---------------------------------------------------------------------------- +void cmCTestSubmitHandler::ConstructCDashURL(std::string& dropMethod, + std::string& url) +{ + dropMethod = this->CTest->GetCTestConfiguration("DropMethod"); + url = dropMethod; + url += "://"; + if (!this->CTest->GetCTestConfiguration("DropSiteUser").empty()) { + url += this->CTest->GetCTestConfiguration("DropSiteUser"); + cmCTestOptionalLog( + this->CTest, HANDLER_OUTPUT, + this->CTest->GetCTestConfiguration("DropSiteUser").c_str(), this->Quiet); + if (!this->CTest->GetCTestConfiguration("DropSitePassword").empty()) { + url += ":" + this->CTest->GetCTestConfiguration("DropSitePassword"); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, ":******", this->Quiet); + } + url += "@"; + } + url += this->CTest->GetCTestConfiguration("DropSite") + + this->CTest->GetCTestConfiguration("DropLocation"); +} + +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 dropMethod; + std::string url; + this->ConstructCDashURL(dropMethod, url); + std::string::size_type pos = url.find("submit.php?"); + url = url.substr(0, pos + 10); + if (!(dropMethod == "http" || dropMethod == "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") == CM_NULLPTR + ? "" + : this->GetOption("RetryDelay"); + std::string retryCountString = this->GetOption("RetryCount") == CM_NULLPTR + ? "" + : this->GetOption("RetryCount"); + unsigned long retryDelay = 0; + if (retryDelayString != "") { + if (!cmSystemTools::StringToULong(retryDelayString.c_str(), &retryDelay)) { + cmCTestLog(this->CTest, WARNING, "Invalid value for 'RETRY_DELAY' : " + << retryDelayString << std::endl); + } + } + unsigned long retryCount = 0; + if (retryCountString != "") { + if (!cmSystemTools::StringToULong(retryCountString.c_str(), &retryCount)) { + cmCTestLog(this->CTest, WARNING, "Invalid value for 'RETRY_DELAY' : " + << retryCountString << std::endl); + } + } + + char md5sum[33]; + md5sum[32] = 0; + cmSystemTools::ComputeFileMD5(file, md5sum); + // 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; + str << "project=" + << curl.Escape(this->CTest->GetCTestConfiguration("ProjectName")) << "&"; + if (subproject) { + str << "subproject=" << curl.Escape(subproject) << "&"; + } + 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=" << (int)cmSystemTools::GetTime() << "&" + << "endtime=" << (int)cmSystemTools::GetTime() << "&" + << "datafilesmd5[0]=" << md5sum << "&" + << "type=" << curl.Escape(typeString); + std::string 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 + << " seconds...\n", + this->Quiet); + + double stop = cmSystemTools::GetTime() + static_cast<double>(retryDelay); + while (cmSystemTools::GetTime() < 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 + << " seconds...\n", + this->Quiet); + + double stop = cmSystemTools::GetTime() + static_cast<double>(retryDelay); + while (cmSystemTools::GetTime() < 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); + } std::string iscdash = this->CTest->GetCTestConfiguration("IsCDash"); // cdash does not need to trigger so just return true - if(iscdash.size()) - { + if (!iscdash.empty()) { this->CDash = true; - } + } - const std::string &buildDirectory - = this->CTest->GetCTestConfiguration("BuildDirectory"); - if ( buildDirectory.size() == 0 ) - { + 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); + "Cannot find BuildDirectory key in the DartConfiguration.tcl" + << std::endl); return -1; - } + } - if ( getenv("HTTP_PROXY") ) - { + if (getenv("HTTP_PROXY")) { this->HTTPProxyType = 1; this->HTTPProxy = getenv("HTTP_PROXY"); - if ( getenv("HTTP_PROXY_PORT") ) - { + if (getenv("HTTP_PROXY_PORT")) { this->HTTPProxy += ":"; this->HTTPProxy += getenv("HTTP_PROXY_PORT"); - } - if ( getenv("HTTP_PROXY_TYPE") ) - { - cmStdString type = getenv("HTTP_PROXY_TYPE"); + } + if (getenv("HTTP_PROXY_TYPE")) { + std::string type = getenv("HTTP_PROXY_TYPE"); // HTTP/SOCKS4/SOCKS5 - if ( type == "HTTP" ) - { + if (type == "HTTP") { this->HTTPProxyType = 1; - } - else if ( type == "SOCKS4" ) - { + } else if (type == "SOCKS4") { this->HTTPProxyType = 2; - } - else if ( type == "SOCKS5" ) - { + } else if (type == "SOCKS5") { this->HTTPProxyType = 3; - } } - if ( getenv("HTTP_PROXY_USER") ) - { + } + if (getenv("HTTP_PROXY_USER")) { this->HTTPProxyAuth = getenv("HTTP_PROXY_USER"); - } - if ( getenv("HTTP_PROXY_PASSWD") ) - { + } + if (getenv("HTTP_PROXY_PASSWD")) { this->HTTPProxyAuth += ":"; this->HTTPProxyAuth += getenv("HTTP_PROXY_PASSWD"); - } } + } - if ( getenv("FTP_PROXY") ) - { + if (getenv("FTP_PROXY")) { this->FTPProxyType = 1; this->FTPProxy = getenv("FTP_PROXY"); - if ( getenv("FTP_PROXY_PORT") ) - { + if (getenv("FTP_PROXY_PORT")) { this->FTPProxy += ":"; this->FTPProxy += getenv("FTP_PROXY_PORT"); - } - if ( getenv("FTP_PROXY_TYPE") ) - { - cmStdString type = getenv("FTP_PROXY_TYPE"); + } + if (getenv("FTP_PROXY_TYPE")) { + std::string type = getenv("FTP_PROXY_TYPE"); // HTTP/SOCKS4/SOCKS5 - if ( type == "HTTP" ) - { + if (type == "HTTP") { this->FTPProxyType = 1; - } - else if ( type == "SOCKS4" ) - { + } else if (type == "SOCKS4") { this->FTPProxyType = 2; - } - else if ( type == "SOCKS5" ) - { + } else if (type == "SOCKS5") { this->FTPProxyType = 3; - } } } - - if ( this->HTTPProxy.size() > 0 ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Use HTTP Proxy: " - << this->HTTPProxy << std::endl); - } - if ( this->FTPProxy.size() > 0 ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Use FTP Proxy: " - << this->FTPProxy << std::endl); - } + } + + if (!this->HTTPProxy.empty()) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Use HTTP Proxy: " << this->HTTPProxy << std::endl, + this->Quiet); + } + if (!this->FTPProxy.empty()) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Use FTP Proxy: " << this->FTPProxy << std::endl, + this->Quiet); + } cmGeneratedFileStream ofs; this->StartLogFile("Submit", ofs); cmCTest::SetOfStrings files; std::string prefix = this->GetSubmitResultsPrefix(); - if (!this->Files.empty()) - { + if (!this->Files.empty()) { // Submit the explicitly selected files: // - cmCTest::SetOfStrings::const_iterator it; - for (it = this->Files.begin(); it != this->Files.end(); ++it) - { - files.insert(*it); - } - } + files.insert(this->Files.begin(), this->Files.end()); + } // Add to the list of files to submit from any selected, existing parts: // @@ -1176,333 +1315,292 @@ int cmCTestSubmitHandler::ProcessHandler() 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")) - { - cmCTest::VectorOfStrings gfiles; - std::string gpath - = buildDirectory + "/Testing/" + this->CTest->GetCurrentTag(); + 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*"; - cmCTestLog(this->CTest, DEBUG, "Globbing for: " << gpath.c_str() - << std::endl); - if ( cmSystemTools::SimpleGlob(gpath, gfiles, 1) ) - { + cmCTestOptionalLog(this->CTest, DEBUG, + "Globbing for: " << gpath << std::endl, this->Quiet); + if (cmSystemTools::SimpleGlob(gpath, gfiles, 1)) { size_t cc; - for ( cc = 0; cc < gfiles.size(); cc ++ ) - { + for (cc = 0; cc < gfiles.size(); cc++) { gfiles[cc] = gfiles[cc].substr(glen); - cmCTestLog(this->CTest, DEBUG, "Glob file: " << gfiles[cc].c_str() - << std::endl); + cmCTestOptionalLog(this->CTest, DEBUG, + "Glob file: " << gfiles[cc] << std::endl, + this->Quiet); this->CTest->AddSubmitFile(cmCTest::PartCoverage, gfiles[cc].c_str()); - } } - else - { + } 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)) - { + for (cmCTest::Part p = cmCTest::PartStart; p != cmCTest::PartCount; + p = cmCTest::Part(p + 1)) { // Skip parts we are not submitting. - if(!this->SubmitPart[p]) - { + if (!this->SubmitPart[p]) { continue; - } + } // Submit files from this part. std::vector<std::string> const& pfiles = this->CTest->GetSubmitFiles(p); - for(std::vector<std::string>::const_iterator pi = pfiles.begin(); - pi != pfiles.end(); ++pi) - { - files.insert(*pi); - } - } + files.insert(pfiles.begin(), pfiles.end()); + } - if ( ofs ) - { + if (ofs) { ofs << "Upload files:" << std::endl; int cnt = 0; cmCTest::SetOfStrings::iterator it; - for ( it = files.begin(); it != files.end(); ++ it ) - { - ofs << cnt << "\t" << it->c_str() << std::endl; - cnt ++; - } + for (it = files.begin(); it != files.end(); ++it) { + ofs << cnt << "\t" << *it << std::endl; + cnt++; } - cmCTestLog(this->CTest, HANDLER_OUTPUT, "Submit files (using " - << this->CTest->GetCTestConfiguration("DropMethod") << ")" - << std::endl); + } + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "Submit files (using " + << this->CTest->GetCTestConfiguration("DropMethod") + << ")" << std::endl, + this->Quiet); const char* specificTrack = this->CTest->GetSpecificTrack(); - if ( specificTrack ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Send to track: " - << specificTrack << std::endl); - } + if (specificTrack) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Send to track: " << specificTrack << std::endl, + this->Quiet); + } this->SetLogFile(&ofs); - cmStdString dropMethod(this->CTest->GetCTestConfiguration("DropMethod")); + std::string dropMethod(this->CTest->GetCTestConfiguration("DropMethod")); - if ( dropMethod == "" || dropMethod == "ftp" ) - { + if (dropMethod == "" || dropMethod == "ftp") { ofs << "Using drop method: FTP" << std::endl; - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Using FTP submit method" - << std::endl - << " Drop site: ftp://"); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Using FTP submit method" << std::endl + << " Drop site: ftp://", + this->Quiet); std::string url = "ftp://"; url += cmCTest::MakeURLSafe( - this->CTest->GetCTestConfiguration("DropSiteUser")) + ":" + - cmCTest::MakeURLSafe(this->CTest->GetCTestConfiguration( - "DropSitePassword")) + "@" + - this->CTest->GetCTestConfiguration("DropSite") + - cmCTest::MakeURLSafe( - this->CTest->GetCTestConfiguration("DropLocation")); - if ( this->CTest->GetCTestConfiguration("DropSiteUser").size() > 0 ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, - this->CTest->GetCTestConfiguration( - "DropSiteUser").c_str()); - if ( this->CTest->GetCTestConfiguration("DropSitePassword").size() > 0 ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, ":******"); - } - cmCTestLog(this->CTest, HANDLER_OUTPUT, "@"); + this->CTest->GetCTestConfiguration("DropSiteUser")) + + ":" + cmCTest::MakeURLSafe( + this->CTest->GetCTestConfiguration("DropSitePassword")) + + "@" + this->CTest->GetCTestConfiguration("DropSite") + + cmCTest::MakeURLSafe(this->CTest->GetCTestConfiguration("DropLocation")); + if (!this->CTest->GetCTestConfiguration("DropSiteUser").empty()) { + cmCTestOptionalLog( + this->CTest, HANDLER_OUTPUT, + this->CTest->GetCTestConfiguration("DropSiteUser").c_str(), + this->Quiet); + if (!this->CTest->GetCTestConfiguration("DropSitePassword").empty()) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, ":******", + this->Quiet); } - cmCTestLog(this->CTest, HANDLER_OUTPUT, - this->CTest->GetCTestConfiguration("DropSite") - << this->CTest->GetCTestConfiguration("DropLocation") << std::endl); - if ( !this->SubmitUsingFTP(buildDirectory + "/Testing/" - + this->CTest->GetCurrentTag(), - files, prefix, url) ) - { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "@", this->Quiet); + } + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + this->CTest->GetCTestConfiguration("DropSite") + << this->CTest->GetCTestConfiguration("DropLocation") + << std::endl, + this->Quiet); + if (!this->SubmitUsingFTP(buildDirectory + "/Testing/" + + this->CTest->GetCurrentTag(), + files, prefix, url)) { cmCTestLog(this->CTest, ERROR_MESSAGE, - " Problems when submitting via FTP" - << std::endl); + " Problems when submitting via FTP" << std::endl); ofs << " Problems when submitting via FTP" << std::endl; return -1; - } - if(!this->CDash) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Using HTTP trigger method" - << std::endl - << " Trigger site: " - << this->CTest->GetCTestConfiguration("TriggerSite") - << std::endl); - if ( !this-> - TriggerUsingHTTP(files, prefix, - this->CTest->GetCTestConfiguration("TriggerSite"))) - { + } + if (!this->CDash) { + cmCTestOptionalLog( + this->CTest, HANDLER_OUTPUT, " Using HTTP trigger method" + << std::endl + << " Trigger site: " + << this->CTest->GetCTestConfiguration("TriggerSite") << std::endl, + this->Quiet); + if (!this->TriggerUsingHTTP( + files, prefix, + this->CTest->GetCTestConfiguration("TriggerSite"))) { cmCTestLog(this->CTest, ERROR_MESSAGE, " Problems when triggering via HTTP" << std::endl); ofs << " Problems when triggering via HTTP" << std::endl; return -1; - } - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Submission successful" - << std::endl); + } + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Submission successful" << std::endl, this->Quiet); ofs << " Submission successful" << std::endl; return 0; - } } - else if ( dropMethod == "http" || dropMethod == "https" ) - { + } else if (dropMethod == "http" || dropMethod == "https") { std::string url = dropMethod; url += "://"; ofs << "Using drop method: " << dropMethod << std::endl; - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Using HTTP submit method" - << std::endl - << " Drop site:" << url); - if ( this->CTest->GetCTestConfiguration("DropSiteUser").size() > 0 ) - { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Using HTTP submit method" << std::endl + << " Drop site:" << url, + this->Quiet); + if (!this->CTest->GetCTestConfiguration("DropSiteUser").empty()) { url += this->CTest->GetCTestConfiguration("DropSiteUser"); - cmCTestLog(this->CTest, HANDLER_OUTPUT, - this->CTest->GetCTestConfiguration("DropSiteUser").c_str()); - if ( this->CTest->GetCTestConfiguration("DropSitePassword").size() > 0 ) - { + cmCTestOptionalLog( + this->CTest, HANDLER_OUTPUT, + this->CTest->GetCTestConfiguration("DropSiteUser").c_str(), + this->Quiet); + if (!this->CTest->GetCTestConfiguration("DropSitePassword").empty()) { url += ":" + this->CTest->GetCTestConfiguration("DropSitePassword"); - cmCTestLog(this->CTest, HANDLER_OUTPUT, ":******"); - } - url += "@"; - cmCTestLog(this->CTest, HANDLER_OUTPUT, "@"); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, ":******", + this->Quiet); } + url += "@"; + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "@", this->Quiet); + } url += this->CTest->GetCTestConfiguration("DropSite") + this->CTest->GetCTestConfiguration("DropLocation"); - cmCTestLog(this->CTest, HANDLER_OUTPUT, - this->CTest->GetCTestConfiguration("DropSite") - << this->CTest->GetCTestConfiguration("DropLocation") << std::endl); - if ( !this->SubmitUsingHTTP(buildDirectory + "/Testing/" + - this->CTest->GetCurrentTag(), files, prefix, url) ) - { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + this->CTest->GetCTestConfiguration("DropSite") + << this->CTest->GetCTestConfiguration("DropLocation") + << std::endl, + this->Quiet); + if (!this->SubmitUsingHTTP(buildDirectory + "/Testing/" + + this->CTest->GetCurrentTag(), + files, prefix, url)) { cmCTestLog(this->CTest, ERROR_MESSAGE, - " Problems when submitting via HTTP" << std::endl); + " Problems when submitting via HTTP" << std::endl); ofs << " Problems when submitting via HTTP" << std::endl; return -1; - } - if(!this->CDash) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Using HTTP trigger method" - << std::endl - << " Trigger site: " - << this->CTest->GetCTestConfiguration("TriggerSite") - << std::endl); - if ( !this-> - TriggerUsingHTTP(files, prefix, - this->CTest->GetCTestConfiguration("TriggerSite"))) - { + } + if (!this->CDash) { + cmCTestOptionalLog( + this->CTest, HANDLER_OUTPUT, " Using HTTP trigger method" + << std::endl + << " Trigger site: " + << this->CTest->GetCTestConfiguration("TriggerSite") << std::endl, + this->Quiet); + if (!this->TriggerUsingHTTP( + files, prefix, + this->CTest->GetCTestConfiguration("TriggerSite"))) { cmCTestLog(this->CTest, ERROR_MESSAGE, " Problems when triggering via HTTP" << std::endl); ofs << " Problems when triggering via HTTP" << std::endl; return -1; - } } - if(this->HasErrors) - { + } + if (this->HasErrors) { cmCTestLog(this->CTest, HANDLER_OUTPUT, " Errors occurred during " - "submission." << std::endl); + "submission." + << std::endl); ofs << " Errors occurred during submission. " << std::endl; - } - else - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Submission successful" << - (this->HasWarnings ? ", with warnings." : "") << std::endl); - ofs << " Submission successful" << - (this->HasWarnings ? ", with warnings." : "") << std::endl; - } + } 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; - } - else if ( dropMethod == "xmlrpc" ) - { + } else if (dropMethod == "xmlrpc") { #if defined(CTEST_USE_XMLRPC) ofs << "Using drop method: XML-RPC" << std::endl; - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Using XML-RPC submit method" - << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Using XML-RPC submit method" << std::endl, + this->Quiet); std::string url = this->CTest->GetCTestConfiguration("DropSite"); prefix = this->CTest->GetCTestConfiguration("DropLocation"); - if ( !this->SubmitUsingXMLRPC(buildDirectory + "/Testing/" + - this->CTest->GetCurrentTag(), files, prefix, url) ) - { + if (!this->SubmitUsingXMLRPC(buildDirectory + "/Testing/" + + this->CTest->GetCurrentTag(), + files, prefix, url)) { cmCTestLog(this->CTest, ERROR_MESSAGE, - " Problems when submitting via XML-RPC" << std::endl); + " Problems when submitting via XML-RPC" << std::endl); ofs << " Problems when submitting via XML-RPC" << std::endl; return -1; - } - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Submission successful" - << std::endl); + } + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Submission successful" << std::endl, this->Quiet); ofs << " Submission successful" << std::endl; return 0; #else cmCTestLog(this->CTest, ERROR_MESSAGE, " Submission method \"xmlrpc\" not compiled into CTest!" - << std::endl); + << std::endl); return -1; #endif - } - else if ( dropMethod == "scp" ) - { + } else if (dropMethod == "scp") { std::string url; - std::string oldWorkingDirectory; - if ( this->CTest->GetCTestConfiguration("DropSiteUser").size() > 0 ) - { + if (!this->CTest->GetCTestConfiguration("DropSiteUser").empty()) { url += this->CTest->GetCTestConfiguration("DropSiteUser") + "@"; - } + } url += this->CTest->GetCTestConfiguration("DropSite") + ":" + this->CTest->GetCTestConfiguration("DropLocation"); // change to the build directory so that we can uses a relative path // on windows since scp dosn't support "c:" a drive in the path - oldWorkingDirectory = cmSystemTools::GetCurrentWorkingDirectory(); - cmSystemTools::ChangeDirectory(buildDirectory.c_str()); - - if ( !this->SubmitUsingSCP( - this->CTest->GetCTestConfiguration("ScpCommand"), - "Testing/"+this->CTest->GetCurrentTag(), files, prefix, url) ) - { - cmSystemTools::ChangeDirectory(oldWorkingDirectory.c_str()); + cmWorkingDirectory workdir(buildDirectory); + + if (!this->SubmitUsingSCP(this->CTest->GetCTestConfiguration("ScpCommand"), + "Testing/" + this->CTest->GetCurrentTag(), files, + prefix, url)) { cmCTestLog(this->CTest, ERROR_MESSAGE, - " Problems when submitting via SCP" - << std::endl); + " Problems when submitting via SCP" << std::endl); ofs << " Problems when submitting via SCP" << std::endl; return -1; - } - cmSystemTools::ChangeDirectory(oldWorkingDirectory.c_str()); - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Submission successful" - << std::endl); + } + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Submission successful" << std::endl, this->Quiet); ofs << " Submission successful" << std::endl; return 0; - } - else if ( dropMethod == "cp" ) - { - std::string location - = this->CTest->GetCTestConfiguration("DropLocation"); - + } else if (dropMethod == "cp") { + std::string location = this->CTest->GetCTestConfiguration("DropLocation"); // change to the build directory so that we can uses a relative path // on windows since scp dosn't support "c:" a drive in the path - std::string - oldWorkingDirectory = cmSystemTools::GetCurrentWorkingDirectory(); - cmSystemTools::ChangeDirectory(buildDirectory.c_str()); - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " Change directory: " - << buildDirectory.c_str() << std::endl); - - if ( !this->SubmitUsingCP( - "Testing/"+this->CTest->GetCurrentTag(), - files, - prefix, - location) ) - { - cmSystemTools::ChangeDirectory(oldWorkingDirectory.c_str()); + cmWorkingDirectory workdir(buildDirectory); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Change directory: " << buildDirectory << std::endl, + this->Quiet); + + if (!this->SubmitUsingCP("Testing/" + this->CTest->GetCurrentTag(), files, + prefix, location)) { cmCTestLog(this->CTest, ERROR_MESSAGE, - " Problems when submitting via CP" - << std::endl); + " Problems when submitting via CP" << std::endl); ofs << " Problems when submitting via cp" << std::endl; return -1; - } - cmSystemTools::ChangeDirectory(oldWorkingDirectory.c_str()); - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Submission successful" - << std::endl); + } + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Submission successful" << std::endl, this->Quiet); ofs << " Submission successful" << std::endl; return 0; - } + } cmCTestLog(this->CTest, ERROR_MESSAGE, " Unknown submission method: \"" - << dropMethod << "\"" << std::endl); + << dropMethod << "\"" << std::endl); return -1; } -//---------------------------------------------------------------------------- std::string cmCTestSubmitHandler::GetSubmitResultsPrefix() { - std::string name = this->CTest->GetCTestConfiguration("Site") + - "___" + this->CTest->GetCTestConfiguration("BuildName") + - "___" + this->CTest->GetCurrentTag() + "-" + + 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)) - { + 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) { - cmCTest::SetOfStrings::const_iterator it; - for (it = files.begin(); it != files.end(); ++it) - { - this->Files.insert(*it); - } + this->Files.insert(files.begin(), files.end()); } diff --git a/Source/CTest/cmCTestSubmitHandler.h b/Source/CTest/cmCTestSubmitHandler.h index 14eac80b5..2923f4f3d 100644 --- a/Source/CTest/cmCTestSubmitHandler.h +++ b/Source/CTest/cmCTestSubmitHandler.h @@ -1,19 +1,18 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestSubmitHandler_h #define cmCTestSubmitHandler_h +#include "cmConfigure.h" + +#include "cmCTest.h" #include "cmCTestGenericHandler.h" +#include <iosfwd> +#include <set> +#include <string> +#include <vector> + /** \class cmCTestSubmitHandler * \brief Helper class for CTest * @@ -23,17 +22,17 @@ class cmCTestSubmitHandler : public cmCTestGenericHandler { public: - cmTypeMacro(cmCTestSubmitHandler, cmCTestGenericHandler); + typedef cmCTestGenericHandler Superclass; cmCTestSubmitHandler(); - ~cmCTestSubmitHandler() { this->LogFile = 0; } + ~cmCTestSubmitHandler() CM_OVERRIDE { this->LogFile = CM_NULLPTR; } /* * The main entry point for this class */ - int ProcessHandler(); + int ProcessHandler() CM_OVERRIDE; - void Initialize(); + void Initialize() CM_OVERRIDE; /** Specify a set of parts (by name) to submit. */ void SelectParts(std::set<cmCTest::Part> const& parts); @@ -41,39 +40,46 @@ public: /** Specify a set of files to submit. */ void SelectFiles(cmCTest::SetOfStrings const& files); + // handle the cdash file upload protocol + int HandleCDashUploadFile(std::string const& file, std::string const& type); + + void SetHttpHeaders(std::vector<std::string> const& v) + { + this->HttpHeaders = v; + } + + void ConstructCDashURL(std::string& dropMethod, std::string& url); + private: void SetLogFile(std::ostream* ost) { this->LogFile = ost; } /** * Submit file using various ways */ - bool SubmitUsingFTP(const cmStdString& localprefix, - const std::set<cmStdString>& files, - const cmStdString& remoteprefix, - const cmStdString& url); - bool SubmitUsingHTTP(const cmStdString& localprefix, - const std::set<cmStdString>& files, - const cmStdString& remoteprefix, - const cmStdString& url); - bool SubmitUsingSCP(const cmStdString& scp_command, - const cmStdString& localprefix, - const std::set<cmStdString>& files, - const cmStdString& remoteprefix, - const cmStdString& url); - - bool SubmitUsingCP( const cmStdString& localprefix, - const std::set<cmStdString>& files, - const cmStdString& remoteprefix, - const cmStdString& url); - - bool TriggerUsingHTTP(const std::set<cmStdString>& files, - const cmStdString& remoteprefix, - const cmStdString& url); - - bool SubmitUsingXMLRPC(const cmStdString& localprefix, - const std::set<cmStdString>& files, - const cmStdString& remoteprefix, - const cmStdString& url); + bool SubmitUsingFTP(const std::string& localprefix, + const std::set<std::string>& files, + const std::string& remoteprefix, const std::string& url); + bool SubmitUsingHTTP(const std::string& localprefix, + const std::set<std::string>& files, + const std::string& remoteprefix, + const std::string& url); + bool SubmitUsingSCP(const std::string& scp_command, + const std::string& localprefix, + const std::set<std::string>& files, + const std::string& remoteprefix, const std::string& url); + + bool SubmitUsingCP(const std::string& localprefix, + const std::set<std::string>& files, + const std::string& remoteprefix, const std::string& url); + + bool TriggerUsingHTTP(const std::set<std::string>& files, + const std::string& remoteprefix, + const std::string& url); + + bool SubmitUsingXMLRPC(const std::string& localprefix, + const std::set<std::string>& files, + const std::string& remoteprefix, + const std::string& url); typedef std::vector<char> cmCTestSubmitHandlerVectorOfChar; @@ -81,18 +87,20 @@ private: std::string GetSubmitResultsPrefix(); - class ResponseParser; - cmStdString HTTPProxy; - int HTTPProxyType; - cmStdString HTTPProxyAuth; - cmStdString FTPProxy; - int FTPProxyType; + class ResponseParser; + + std::string HTTPProxy; + int HTTPProxyType; + std::string HTTPProxyAuth; + std::string FTPProxy; + int FTPProxyType; std::ostream* LogFile; bool SubmitPart[cmCTest::PartCount]; bool CDash; bool HasWarnings; bool HasErrors; cmCTest::SetOfStrings Files; + std::vector<std::string> HttpHeaders; }; #endif diff --git a/Source/CTest/cmCTestTestCommand.cxx b/Source/CTest/cmCTestTestCommand.cxx index 231f035fb..075b1403a 100644 --- a/Source/CTest/cmCTestTestCommand.cxx +++ b/Source/CTest/cmCTestTestCommand.cxx @@ -1,18 +1,15 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestTestCommand.h" #include "cmCTest.h" #include "cmCTestGenericHandler.h" +#include "cmMakefile.h" +#include "cmSystemTools.h" + +#include <sstream> +#include <stdlib.h> +#include <vector> cmCTestTestCommand::cmCTestTestCommand() { @@ -23,10 +20,14 @@ cmCTestTestCommand::cmCTestTestCommand() this->Arguments[ctt_INCLUDE] = "INCLUDE"; this->Arguments[ctt_EXCLUDE_LABEL] = "EXCLUDE_LABEL"; this->Arguments[ctt_INCLUDE_LABEL] = "INCLUDE_LABEL"; + this->Arguments[ctt_EXCLUDE_FIXTURE] = "EXCLUDE_FIXTURE"; + this->Arguments[ctt_EXCLUDE_FIXTURE_SETUP] = "EXCLUDE_FIXTURE_SETUP"; + this->Arguments[ctt_EXCLUDE_FIXTURE_CLEANUP] = "EXCLUDE_FIXTURE_CLEANUP"; this->Arguments[ctt_PARALLEL_LEVEL] = "PARALLEL_LEVEL"; this->Arguments[ctt_SCHEDULE_RANDOM] = "SCHEDULE_RANDOM"; this->Arguments[ctt_STOP_TIME] = "STOP_TIME"; - this->Arguments[ctt_LAST] = 0; + this->Arguments[ctt_TEST_LOAD] = "TEST_LOAD"; + this->Arguments[ctt_LAST] = CM_NULLPTR; this->Last = ctt_LAST; } @@ -35,74 +36,95 @@ cmCTestGenericHandler* cmCTestTestCommand::InitializeHandler() const char* ctestTimeout = this->Makefile->GetDefinition("CTEST_TEST_TIMEOUT"); - double timeout = this->CTest->GetTimeOut(); - if ( ctestTimeout ) - { + double timeout; + if (ctestTimeout) { timeout = atof(ctestTimeout); - } - else - { - if ( timeout <= 0 ) - { + } else { + timeout = this->CTest->GetTimeOut(); + if (timeout <= 0) { // By default use timeout of 10 minutes timeout = 600; - } } + } this->CTest->SetTimeOut(timeout); cmCTestGenericHandler* handler = this->InitializeActualHandler(); - if ( this->Values[ctt_START] || this->Values[ctt_END] || - this->Values[ctt_STRIDE] ) - { - cmOStringStream testsToRunString; - if ( this->Values[ctt_START] ) - { + if (this->Values[ctt_START] || this->Values[ctt_END] || + this->Values[ctt_STRIDE]) { + std::ostringstream testsToRunString; + if (this->Values[ctt_START]) { testsToRunString << this->Values[ctt_START]; - } + } testsToRunString << ","; - if ( this->Values[ctt_END] ) - { + if (this->Values[ctt_END]) { testsToRunString << this->Values[ctt_END]; - } + } testsToRunString << ","; - if ( this->Values[ctt_STRIDE] ) - { + if (this->Values[ctt_STRIDE]) { testsToRunString << this->Values[ctt_STRIDE]; - } - handler->SetOption("TestsToRunInformation", - testsToRunString.str().c_str()); } - if(this->Values[ctt_EXCLUDE]) - { + handler->SetOption("TestsToRunInformation", + testsToRunString.str().c_str()); + } + if (this->Values[ctt_EXCLUDE]) { handler->SetOption("ExcludeRegularExpression", this->Values[ctt_EXCLUDE]); - } - if(this->Values[ctt_INCLUDE]) - { + } + if (this->Values[ctt_INCLUDE]) { handler->SetOption("IncludeRegularExpression", this->Values[ctt_INCLUDE]); - } - if(this->Values[ctt_EXCLUDE_LABEL]) - { + } + if (this->Values[ctt_EXCLUDE_LABEL]) { handler->SetOption("ExcludeLabelRegularExpression", this->Values[ctt_EXCLUDE_LABEL]); - } - if(this->Values[ctt_INCLUDE_LABEL]) - { + } + if (this->Values[ctt_INCLUDE_LABEL]) { handler->SetOption("LabelRegularExpression", this->Values[ctt_INCLUDE_LABEL]); - } - if(this->Values[ctt_PARALLEL_LEVEL]) - { - handler->SetOption("ParallelLevel", - this->Values[ctt_PARALLEL_LEVEL]); - } - if(this->Values[ctt_SCHEDULE_RANDOM]) - { - handler->SetOption("ScheduleRandom", - this->Values[ctt_SCHEDULE_RANDOM]); - } - if(this->Values[ctt_STOP_TIME]) - { + } + if (this->Values[ctt_EXCLUDE_FIXTURE]) { + handler->SetOption("ExcludeFixtureRegularExpression", + this->Values[ctt_EXCLUDE_FIXTURE]); + } + if (this->Values[ctt_EXCLUDE_FIXTURE_SETUP]) { + handler->SetOption("ExcludeFixtureSetupRegularExpression", + this->Values[ctt_EXCLUDE_FIXTURE_SETUP]); + } + if (this->Values[ctt_EXCLUDE_FIXTURE_CLEANUP]) { + handler->SetOption("ExcludeFixtureCleanupRegularExpression", + this->Values[ctt_EXCLUDE_FIXTURE_CLEANUP]); + } + if (this->Values[ctt_PARALLEL_LEVEL]) { + handler->SetOption("ParallelLevel", this->Values[ctt_PARALLEL_LEVEL]); + } + if (this->Values[ctt_SCHEDULE_RANDOM]) { + handler->SetOption("ScheduleRandom", this->Values[ctt_SCHEDULE_RANDOM]); + } + if (this->Values[ctt_STOP_TIME]) { this->CTest->SetStopTime(this->Values[ctt_STOP_TIME]); + } + + // Test load is determined by: TEST_LOAD argument, + // or CTEST_TEST_LOAD script variable, or ctest --test-load + // command line argument... in that order. + unsigned long testLoad; + const char* ctestTestLoad = this->Makefile->GetDefinition("CTEST_TEST_LOAD"); + if (this->Values[ctt_TEST_LOAD] && *this->Values[ctt_TEST_LOAD]) { + if (!cmSystemTools::StringToULong(this->Values[ctt_TEST_LOAD], + &testLoad)) { + testLoad = 0; + cmCTestLog(this->CTest, WARNING, "Invalid value for 'TEST_LOAD' : " + << this->Values[ctt_TEST_LOAD] << std::endl); } + } else if (ctestTestLoad && *ctestTestLoad) { + if (!cmSystemTools::StringToULong(ctestTestLoad, &testLoad)) { + testLoad = 0; + cmCTestLog(this->CTest, WARNING, "Invalid value for 'CTEST_TEST_LOAD' : " + << ctestTestLoad << std::endl); + } + } else { + testLoad = this->CTest->GetTestLoad(); + } + handler->SetTestLoad(testLoad); + + handler->SetQuiet(this->Quiet); return handler; } diff --git a/Source/CTest/cmCTestTestCommand.h b/Source/CTest/cmCTestTestCommand.h index 130cb6973..be7e7831f 100644 --- a/Source/CTest/cmCTestTestCommand.h +++ b/Source/CTest/cmCTestTestCommand.h @@ -1,19 +1,17 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestTestCommand_h #define cmCTestTestCommand_h +#include "cmConfigure.h" + #include "cmCTestHandlerCommand.h" +#include <string> + +class cmCTestGenericHandler; +class cmCommand; + /** \class cmCTestTest * \brief Run a ctest script * @@ -22,71 +20,30 @@ class cmCTestTestCommand : public cmCTestHandlerCommand { public: - cmCTestTestCommand(); /** * This is a virtual constructor for the command. */ - virtual cmCommand* Clone() - { + cmCommand* Clone() CM_OVERRIDE + { cmCTestTestCommand* ni = new cmCTestTestCommand; ni->CTest = this->CTest; ni->CTestScriptHandler = this->CTestScriptHandler; return ni; - } + } /** * The name of the command as specified in CMakeList.txt. */ - virtual const char* GetName() const { return "ctest_test";} - - /** - * Succinct documentation. - */ - virtual const char* GetTerseDocumentation() const - { - return "Run tests in the project build tree."; - } - - /** - * More documentation. - */ - virtual const char* GetFullDocumentation() const - { - return - " ctest_test([BUILD build_dir] [APPEND]\n" - " [START start number] [END end number]\n" - " [STRIDE stride number] [EXCLUDE exclude regex ]\n" - " [INCLUDE include regex] [RETURN_VALUE res] \n" - " [EXCLUDE_LABEL exclude regex] \n" - " [INCLUDE_LABEL label regex] \n" - " [PARALLEL_LEVEL level] \n" - " [SCHEDULE_RANDOM on] \n" - " [STOP_TIME time of day]) \n" - "Tests the given build directory and stores results in Test.xml. The " - "second argument is a variable that will hold value. Optionally, " - "you can specify the starting test number START, the ending test number " - "END, the number of tests to skip between each test STRIDE, a regular " - "expression for tests to run INCLUDE, or a regular expression for tests " - "to not run EXCLUDE. EXCLUDE_LABEL and INCLUDE_LABEL are regular " - "expression for test to be included or excluded by the test " - "property LABEL. PARALLEL_LEVEL should be set to a positive number " - "representing the number of tests to be run in parallel. " - "SCHEDULE_RANDOM will launch tests in a random order, and is " - "typically used to detect implicit test dependencies. STOP_TIME is the " - "time of day at which the tests should all stop running." - "\n" - CTEST_COMMAND_APPEND_OPTION_DOCS; - } - - cmTypeMacro(cmCTestTestCommand, cmCTestHandlerCommand); + std::string GetName() const CM_OVERRIDE { return "ctest_test"; } protected: virtual cmCTestGenericHandler* InitializeActualHandler(); - cmCTestGenericHandler* InitializeHandler(); + cmCTestGenericHandler* InitializeHandler() CM_OVERRIDE; - enum { + enum + { ctt_BUILD = ct_LAST, ctt_RETURN_VALUE, ctt_START, @@ -96,12 +53,15 @@ protected: ctt_INCLUDE, ctt_EXCLUDE_LABEL, ctt_INCLUDE_LABEL, + ctt_EXCLUDE_FIXTURE, + ctt_EXCLUDE_FIXTURE_SETUP, + ctt_EXCLUDE_FIXTURE_CLEANUP, ctt_PARALLEL_LEVEL, ctt_SCHEDULE_RANDOM, ctt_STOP_TIME, + ctt_TEST_LOAD, ctt_LAST }; }; - #endif diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx index 497774d1b..674be6052 100644 --- a/Source/CTest/cmCTestTestHandler.cxx +++ b/Source/CTest/cmCTestTestHandler.cxx @@ -1,424 +1,319 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmCTestTestHandler.h" - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +#include "cmsys/Base64.h" +#include "cmsys/Directory.hxx" +#include "cmsys/FStream.hxx" +#include "cmsys/RegularExpression.hxx" +#include <algorithm> +#include <functional> +#include <iomanip> +#include <iterator> +#include <set> +#include <sstream> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> -#include "cmCTestTestHandler.h" -#include "cmCTestMultiProcessHandler.h" -#include "cmCTestBatchTestHandler.h" +#include "cmAlgorithms.h" #include "cmCTest.h" -#include "cmCTestRunTest.h" -#include "cmake.h" +#include "cmCTestBatchTestHandler.h" +#include "cmCTestMultiProcessHandler.h" +#include "cmCommand.h" #include "cmGeneratedFileStream.h" -#include <cmsys/Process.h> -#include <cmsys/RegularExpression.hxx> -#include <cmsys/Base64.h> -#include "cmMakefile.h" #include "cmGlobalGenerator.h" -#include "cmLocalGenerator.h" -#include "cmCommand.h" +#include "cmMakefile.h" +#include "cmState.h" +#include "cmStateSnapshot.h" #include "cmSystemTools.h" -#include "cmXMLSafe.h" +#include "cmWorkingDirectory.h" +#include "cmXMLWriter.h" +#include "cm_auto_ptr.hxx" #include "cm_utf8.h" +#include "cmake.h" -#include <stdlib.h> -#include <math.h> -#include <float.h> - -#include <set> +class cmExecutionStatus; -//---------------------------------------------------------------------- class cmCTestSubdirCommand : public cmCommand { public: /** * This is a virtual constructor for the command. */ - virtual cmCommand* Clone() - { + cmCommand* Clone() CM_OVERRIDE + { cmCTestSubdirCommand* c = new cmCTestSubdirCommand; c->TestHandler = this->TestHandler; return c; - } + } /** * This is called when the command is first encountered in * the CMakeLists.txt file. */ - virtual bool InitialPass(std::vector<std::string> const& args, - cmExecutionStatus &); - - /** - * The name of the command as specified in CMakeList.txt. - */ - virtual const char* GetName() const { return "subdirs";} - - // Unused methods - virtual const char* GetTerseDocumentation() const { return ""; } - virtual const char* GetFullDocumentation() const { return ""; } - - cmTypeMacro(cmCTestSubdirCommand, cmCommand); + bool InitialPass(std::vector<std::string> const& args, + cmExecutionStatus& /*unused*/) CM_OVERRIDE; cmCTestTestHandler* TestHandler; }; -//---------------------------------------------------------------------- -bool cmCTestSubdirCommand -::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &) +bool cmCTestSubdirCommand::InitialPass(std::vector<std::string> const& args, + cmExecutionStatus& /*unused*/) { - if(args.size() < 1 ) - { + if (args.empty()) { this->SetError("called with incorrect number of arguments"); return false; - } + } std::vector<std::string>::const_iterator it; std::string cwd = cmSystemTools::GetCurrentWorkingDirectory(); - for ( it = args.begin(); it != args.end(); ++ it ) - { + for (it = args.begin(); it != args.end(); ++it) { std::string fname; - if(cmSystemTools::FileIsFullPath(it->c_str())) - { + if (cmSystemTools::FileIsFullPath(it->c_str())) { fname = *it; - } - else - { + } else { fname = cwd; fname += "/"; fname += *it; - } + } - if ( !cmSystemTools::FileIsDirectory(fname.c_str()) ) - { + if (!cmSystemTools::FileIsDirectory(fname)) { // No subdirectory? So what... continue; + } + bool readit = false; + { + cmWorkingDirectory workdir(fname); + const char* testFilename; + if (cmSystemTools::FileExists("CTestTestfile.cmake")) { + // does the CTestTestfile.cmake exist ? + testFilename = "CTestTestfile.cmake"; + } else if (cmSystemTools::FileExists("DartTestfile.txt")) { + // does the DartTestfile.txt exist ? + testFilename = "DartTestfile.txt"; + } else { + // No CTestTestfile? Who cares... + continue; } - cmSystemTools::ChangeDirectory(fname.c_str()); - const char* testFilename; - if( cmSystemTools::FileExists("CTestTestfile.cmake") ) - { - // does the CTestTestfile.cmake exist ? - testFilename = "CTestTestfile.cmake"; - } - else if( cmSystemTools::FileExists("DartTestfile.txt") ) - { - // does the DartTestfile.txt exist ? - testFilename = "DartTestfile.txt"; - } - else - { - // No CTestTestfile? Who cares... - continue; - } - fname += "/"; - fname += testFilename; - bool readit = - this->Makefile->ReadListFile(this->Makefile->GetCurrentListFile(), - fname.c_str()); - cmSystemTools::ChangeDirectory(cwd.c_str()); - if(!readit) - { + fname += "/"; + fname += testFilename; + readit = this->Makefile->ReadDependentFile(fname.c_str()); + } + if (!readit) { std::string m = "Could not find include file: "; m += fname; - this->SetError(m.c_str()); + this->SetError(m); return false; - } } - cmSystemTools::ChangeDirectory(cwd.c_str()); + } return true; } -//---------------------------------------------------------------------- class cmCTestAddSubdirectoryCommand : public cmCommand { public: /** * This is a virtual constructor for the command. */ - virtual cmCommand* Clone() - { + cmCommand* Clone() CM_OVERRIDE + { cmCTestAddSubdirectoryCommand* c = new cmCTestAddSubdirectoryCommand; c->TestHandler = this->TestHandler; return c; - } + } /** * This is called when the command is first encountered in * the CMakeLists.txt file. */ - virtual bool InitialPass(std::vector<std::string> const& args, - cmExecutionStatus &); - - /** - * The name of the command as specified in CMakeList.txt. - */ - virtual const char* GetName() const { return "add_subdirectory";} - - // Unused methods - virtual const char* GetTerseDocumentation() const { return ""; } - virtual const char* GetFullDocumentation() const { return ""; } - - cmTypeMacro(cmCTestAddSubdirectoryCommand, cmCommand); + bool InitialPass(std::vector<std::string> const& args, + cmExecutionStatus& /*unused*/) CM_OVERRIDE; cmCTestTestHandler* TestHandler; }; -//---------------------------------------------------------------------- -bool cmCTestAddSubdirectoryCommand -::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &) +bool cmCTestAddSubdirectoryCommand::InitialPass( + std::vector<std::string> const& args, cmExecutionStatus& /*unused*/) { - if(args.size() < 1 ) - { + if (args.empty()) { this->SetError("called with incorrect number of arguments"); return false; - } + } - std::string cwd = cmSystemTools::GetCurrentWorkingDirectory(); - cmSystemTools::ChangeDirectory(cwd.c_str()); - std::string fname = cwd; + std::string fname = cmSystemTools::GetCurrentWorkingDirectory(); fname += "/"; - fname += args[1]; + fname += args[0]; - if ( !cmSystemTools::FileExists(fname.c_str()) ) - { + if (!cmSystemTools::FileExists(fname.c_str())) { // No subdirectory? So what... return true; + } + bool readit = false; + { + const char* testFilename; + if (cmSystemTools::FileExists("CTestTestfile.cmake")) { + // does the CTestTestfile.cmake exist ? + testFilename = "CTestTestfile.cmake"; + } else if (cmSystemTools::FileExists("DartTestfile.txt")) { + // does the DartTestfile.txt exist ? + testFilename = "DartTestfile.txt"; + } else { + // No CTestTestfile? Who cares... + return true; } - cmSystemTools::ChangeDirectory(fname.c_str()); - const char* testFilename; - if( cmSystemTools::FileExists("CTestTestfile.cmake") ) - { - // does the CTestTestfile.cmake exist ? - testFilename = "CTestTestfile.cmake"; - } - else if( cmSystemTools::FileExists("DartTestfile.txt") ) - { - // does the DartTestfile.txt exist ? - testFilename = "DartTestfile.txt"; - } - else - { - // No CTestTestfile? Who cares... - cmSystemTools::ChangeDirectory(cwd.c_str()); - return true; - } - fname += "/"; - fname += testFilename; - bool readit = - this->Makefile->ReadListFile(this->Makefile->GetCurrentListFile(), - fname.c_str()); - cmSystemTools::ChangeDirectory(cwd.c_str()); - if(!readit) - { + fname += "/"; + fname += testFilename; + readit = this->Makefile->ReadDependentFile(fname.c_str()); + } + if (!readit) { std::string m = "Could not find include file: "; m += fname; - this->SetError(m.c_str()); + this->SetError(m); return false; - } + } return true; } -//---------------------------------------------------------------------- class cmCTestAddTestCommand : public cmCommand { public: /** * This is a virtual constructor for the command. */ - virtual cmCommand* Clone() - { + cmCommand* Clone() CM_OVERRIDE + { cmCTestAddTestCommand* c = new cmCTestAddTestCommand; c->TestHandler = this->TestHandler; return c; - } + } /** * This is called when the command is first encountered in * the CMakeLists.txt file. */ - virtual bool InitialPass(std::vector<std::string> const&, - cmExecutionStatus &); - - /** - * The name of the command as specified in CMakeList.txt. - */ - virtual const char* GetName() const { return "ADD_TEST";} - - // Unused methods - virtual const char* GetTerseDocumentation() const { return ""; } - virtual const char* GetFullDocumentation() const { return ""; } - - cmTypeMacro(cmCTestAddTestCommand, cmCommand); + bool InitialPass(std::vector<std::string> const& /*args*/, + cmExecutionStatus& /*unused*/) CM_OVERRIDE; cmCTestTestHandler* TestHandler; }; -//---------------------------------------------------------------------- -bool cmCTestAddTestCommand -::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &) +bool cmCTestAddTestCommand::InitialPass(std::vector<std::string> const& args, + cmExecutionStatus& /*unused*/) { - if ( args.size() < 2 ) - { + if (args.size() < 2) { this->SetError("called with incorrect number of arguments"); return false; - } + } return this->TestHandler->AddTest(args); } -//---------------------------------------------------------------------- class cmCTestSetTestsPropertiesCommand : public cmCommand { public: /** * This is a virtual constructor for the command. */ - virtual cmCommand* Clone() - { - cmCTestSetTestsPropertiesCommand* c - = new cmCTestSetTestsPropertiesCommand; + cmCommand* Clone() CM_OVERRIDE + { + cmCTestSetTestsPropertiesCommand* c = new cmCTestSetTestsPropertiesCommand; c->TestHandler = this->TestHandler; return c; - } + } /** * This is called when the command is first encountered in * the CMakeLists.txt file. - */ - virtual bool InitialPass(std::vector<std::string> const&, - cmExecutionStatus &); - - /** - * The name of the command as specified in CMakeList.txt. */ - virtual const char* GetName() const { return "SET_TESTS_PROPERTIES";} - - // Unused methods - virtual const char* GetTerseDocumentation() const { return ""; } - virtual const char* GetFullDocumentation() const { return ""; } - - cmTypeMacro(cmCTestSetTestsPropertiesCommand, cmCommand); + bool InitialPass(std::vector<std::string> const& /*args*/, + cmExecutionStatus& /*unused*/) CM_OVERRIDE; cmCTestTestHandler* TestHandler; }; -//---------------------------------------------------------------------- -bool cmCTestSetTestsPropertiesCommand -::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &) +bool cmCTestSetTestsPropertiesCommand::InitialPass( + std::vector<std::string> const& args, cmExecutionStatus& /*unused*/) { return this->TestHandler->SetTestsProperties(args); } -//---------------------------------------------------------------------- // get the next number in a string with numbers separated by , // pos is the start of the search and pos2 is the end of the search // pos becomes pos2 after a call to GetNextNumber. // -1 is returned at the end of the list. -inline int GetNextNumber(std::string const& in, - int& val, +inline int GetNextNumber(std::string const& in, int& val, std::string::size_type& pos, std::string::size_type& pos2) { pos2 = in.find(',', pos); - if(pos2 != in.npos) - { - if(pos2-pos == 0) - { + if (pos2 != std::string::npos) { + if (pos2 - pos == 0) { val = -1; - } - else - { - val = atoi(in.substr(pos, pos2-pos).c_str()); - } - pos = pos2+1; - return 1; - } - else - { - if(in.size()-pos == 0) - { - val = -1; - } - else - { - val = atoi(in.substr(pos, in.size()-pos).c_str()); - } - return 0; + } else { + val = atoi(in.substr(pos, pos2 - pos).c_str()); } + pos = pos2 + 1; + return 1; + } + if (in.size() - pos == 0) { + val = -1; + } else { + val = atoi(in.substr(pos, in.size() - pos).c_str()); + } + return 0; } -//---------------------------------------------------------------------- // get the next number in a string with numbers separated by , // pos is the start of the search and pos2 is the end of the search // pos becomes pos2 after a call to GetNextNumber. // -1 is returned at the end of the list. -inline int GetNextRealNumber(std::string const& in, - double& val, +inline int GetNextRealNumber(std::string const& in, double& val, std::string::size_type& pos, std::string::size_type& pos2) { pos2 = in.find(',', pos); - if(pos2 != in.npos) - { - if(pos2-pos == 0) - { + if (pos2 != std::string::npos) { + if (pos2 - pos == 0) { val = -1; - } - else - { - val = atof(in.substr(pos, pos2-pos).c_str()); - } - pos = pos2+1; - return 1; - } - else - { - if(in.size()-pos == 0) - { - val = -1; - } - else - { - val = atof(in.substr(pos, in.size()-pos).c_str()); - } - return 0; + } else { + val = atof(in.substr(pos, pos2 - pos).c_str()); } + pos = pos2 + 1; + return 1; + } + if (in.size() - pos == 0) { + val = -1; + } else { + val = atof(in.substr(pos, in.size() - pos).c_str()); + } + return 0; } - -//---------------------------------------------------------------------- cmCTestTestHandler::cmCTestTestHandler() { this->UseUnion = false; - this->UseIncludeLabelRegExpFlag = false; - this->UseExcludeLabelRegExpFlag = false; - this->UseIncludeRegExpFlag = false; - this->UseExcludeRegExpFlag = false; - this->UseExcludeRegExpFirst = false; + this->UseIncludeLabelRegExpFlag = false; + this->UseExcludeLabelRegExpFlag = false; + this->UseIncludeRegExpFlag = false; + this->UseExcludeRegExpFlag = false; + this->UseExcludeRegExpFirst = false; this->CustomMaximumPassedTestOutputSize = 1 * 1024; this->CustomMaximumFailedTestOutputSize = 300 * 1024; this->MemCheck = false; - this->LogFile = 0; + this->LogFile = CM_NULLPTR; // regex to detect <DartMeasurement>...</DartMeasurement> - this->DartStuff.compile( - "(<DartMeasurement.*/DartMeasurement[a-zA-Z]*>)"); + this->DartStuff.compile("(<DartMeasurement.*/DartMeasurement[a-zA-Z]*>)"); // regex to detect each individual <DartMeasurement>...</DartMeasurement> this->DartStuff1.compile( "(<DartMeasurement[^<]*</DartMeasurement[a-zA-Z]*>)"); } -//---------------------------------------------------------------------- void cmCTestTestHandler::Initialize() { this->Superclass::Initialize(); @@ -447,117 +342,120 @@ void cmCTestTestHandler::Initialize() this->ExcludeLabelRegularExpression = ""; this->IncludeRegExp = ""; this->ExcludeRegExp = ""; + this->ExcludeFixtureRegExp.clear(); + this->ExcludeFixtureSetupRegExp.clear(); + this->ExcludeFixtureCleanupRegExp.clear(); TestsToRunString = ""; this->UseUnion = false; this->TestList.clear(); } -//---------------------------------------------------------------------- -void cmCTestTestHandler::PopulateCustomVectors(cmMakefile *mf) +void cmCTestTestHandler::PopulateCustomVectors(cmMakefile* mf) { this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_PRE_TEST", - this->CustomPreTest); + this->CustomPreTest); this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_POST_TEST", - this->CustomPostTest); - this->CTest->PopulateCustomVector(mf, - "CTEST_CUSTOM_TESTS_IGNORE", - this->CustomTestsIgnore); - this->CTest->PopulateCustomInteger(mf, - "CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE", - this->CustomMaximumPassedTestOutputSize); - this->CTest->PopulateCustomInteger(mf, - "CTEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE", - this->CustomMaximumFailedTestOutputSize); + this->CustomPostTest); + this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_TESTS_IGNORE", + this->CustomTestsIgnore); + this->CTest->PopulateCustomInteger( + mf, "CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE", + this->CustomMaximumPassedTestOutputSize); + this->CTest->PopulateCustomInteger( + mf, "CTEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE", + this->CustomMaximumFailedTestOutputSize); } -//---------------------------------------------------------------------- int cmCTestTestHandler::PreProcessHandler() { - if ( !this->ExecuteCommands(this->CustomPreTest) ) - { + if (!this->ExecuteCommands(this->CustomPreTest)) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Problem executing pre-test command(s)." << std::endl); + "Problem executing pre-test command(s)." << std::endl); return 0; - } + } return 1; } -//---------------------------------------------------------------------- int cmCTestTestHandler::PostProcessHandler() { - if ( !this->ExecuteCommands(this->CustomPostTest) ) - { + if (!this->ExecuteCommands(this->CustomPostTest)) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Problem executing post-test command(s)." << std::endl); + "Problem executing post-test command(s)." << std::endl); return 0; - } + } return 1; } -//---------------------------------------------------------------------- -//clearly it would be nice if this were broken up into a few smaller -//functions and commented... +// clearly it would be nice if this were broken up into a few smaller +// functions and commented... int cmCTestTestHandler::ProcessHandler() { // Update internal data structure from generic one this->SetTestsToRunInformation(this->GetOption("TestsToRunInformation")); this->SetUseUnion(cmSystemTools::IsOn(this->GetOption("UseUnion"))); - if(cmSystemTools::IsOn(this->GetOption("ScheduleRandom"))) - { + if (cmSystemTools::IsOn(this->GetOption("ScheduleRandom"))) { this->CTest->SetScheduleType("Random"); - } - if(this->GetOption("ParallelLevel")) - { + } + if (this->GetOption("ParallelLevel")) { this->CTest->SetParallelLevel(atoi(this->GetOption("ParallelLevel"))); - } + } const char* val; val = this->GetOption("LabelRegularExpression"); - if ( val ) - { + if (val) { this->UseIncludeLabelRegExpFlag = true; this->IncludeLabelRegExp = val; - } + } val = this->GetOption("ExcludeLabelRegularExpression"); - if ( val ) - { + if (val) { this->UseExcludeLabelRegExpFlag = true; - this->ExcludeLabelRegularExpression = val; - } + this->ExcludeLabelRegExp = val; + } val = this->GetOption("IncludeRegularExpression"); - if ( val ) - { + if (val) { this->UseIncludeRegExp(); this->SetIncludeRegExp(val); - } + } val = this->GetOption("ExcludeRegularExpression"); - if ( val ) - { + if (val) { this->UseExcludeRegExp(); this->SetExcludeRegExp(val); - } + } + val = this->GetOption("ExcludeFixtureRegularExpression"); + if (val) { + this->ExcludeFixtureRegExp = val; + } + val = this->GetOption("ExcludeFixtureSetupRegularExpression"); + if (val) { + this->ExcludeFixtureSetupRegExp = val; + } + val = this->GetOption("ExcludeFixtureCleanupRegularExpression"); + if (val) { + this->ExcludeFixtureCleanupRegExp = val; + } + this->SetRerunFailed(cmSystemTools::IsOn(this->GetOption("RerunFailed"))); this->TestResults.clear(); - cmCTestLog(this->CTest, HANDLER_OUTPUT, - (this->MemCheck ? "Memory check" : "Test") - << " project " << cmSystemTools::GetCurrentWorkingDirectory() - << std::endl); - if ( ! this->PreProcessHandler() ) - { + cmCTestOptionalLog( + this->CTest, HANDLER_OUTPUT, (this->MemCheck ? "Memory check" : "Test") + << " project " << cmSystemTools::GetCurrentWorkingDirectory() + << std::endl, + this->Quiet); + if (!this->PreProcessHandler()) { return -1; - } + } cmGeneratedFileStream mLogFile; this->StartLogFile((this->MemCheck ? "DynamicAnalysis" : "Test"), mLogFile); this->LogFile = &mLogFile; - std::vector<cmStdString> passed; - std::vector<cmStdString> failed; + std::vector<std::string> passed; + std::vector<std::string> failed; int total; - //start the real time clock + // start the real time clock double clock_start, clock_finish; clock_start = cmSystemTools::GetTime(); @@ -567,473 +465,748 @@ int cmCTestTestHandler::ProcessHandler() total = int(passed.size()) + int(failed.size()); - if (total == 0) - { - if ( !this->CTest->GetShowOnly() && !this->CTest->ShouldPrintLabels() ) - { + if (total == 0) { + if (!this->CTest->GetShowOnly() && !this->CTest->ShouldPrintLabels()) { cmCTestLog(this->CTest, ERROR_MESSAGE, "No tests were found!!!" - << std::endl); + << std::endl); + } + } else { + if (this->HandlerVerbose && !passed.empty() && + (this->UseIncludeRegExpFlag || this->UseExcludeRegExpFlag)) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl + << "The following tests passed:" << std::endl, + this->Quiet); + for (std::vector<std::string>::iterator j = passed.begin(); + j != passed.end(); ++j) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "\t" << *j << std::endl, this->Quiet); } } - else - { - if (this->HandlerVerbose && passed.size() && - (this->UseIncludeRegExpFlag || this->UseExcludeRegExpFlag)) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl - << "The following tests passed:" << std::endl); - for(std::vector<cmStdString>::iterator j = passed.begin(); - j != passed.end(); ++j) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "\t" << *j - << std::endl); - } + + typedef std::set<cmCTestTestHandler::cmCTestTestResult, + cmCTestTestResultLess> + SetOfTests; + SetOfTests resultsSet(this->TestResults.begin(), this->TestResults.end()); + std::vector<cmCTestTestHandler::cmCTestTestResult> disabledTests; + + for (SetOfTests::iterator ftit = resultsSet.begin(); + ftit != resultsSet.end(); ++ftit) { + if (cmHasLiteralPrefix(ftit->CompletionStatus, "SKIP_RETURN_CODE=") || + ftit->CompletionStatus == "Disabled") { + disabledTests.push_back(*ftit); } + } float percent = float(passed.size()) * 100.0f / float(total); - if ( failed.size() > 0 && percent > 99) - { + if (!failed.empty() && percent > 99) { percent = 99; - } + } cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl - << static_cast<int>(percent + .5) << "% tests passed, " - << failed.size() << " tests failed out of " - << total << std::endl); - if(this->CTest->GetLabelSummary()) - { + << static_cast<int>(percent + .5f) << "% tests passed, " + << failed.size() << " tests failed out of " << total + << std::endl); + if (this->CTest->GetLabelSummary()) { this->PrintLabelSummary(); - } + } char realBuf[1024]; sprintf(realBuf, "%6.2f sec", (double)(clock_finish - clock_start)); - cmCTestLog(this->CTest, HANDLER_OUTPUT, "\nTotal Test time (real) = " - << realBuf << "\n" ); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + "\nTotal Test time (real) = " << realBuf << "\n", + this->Quiet); - if (failed.size()) - { + if (!disabledTests.empty()) { cmGeneratedFileStream ofs; cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl - << "The following tests FAILED:" << std::endl); - this->StartLogFile("TestsFailed", ofs); + << "The following tests did not run:" << std::endl); + this->StartLogFile("TestsDisabled", ofs); + + const char* disabled_reason; + for (std::vector<cmCTestTestHandler::cmCTestTestResult>::iterator dtit = + disabledTests.begin(); + dtit != disabledTests.end(); ++dtit) { + ofs << dtit->TestCount << ":" << dtit->Name << std::endl; + if (dtit->CompletionStatus == "Disabled") { + disabled_reason = "Disabled"; + } else { + disabled_reason = "Skipped"; + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, "\t" + << std::setw(3) << dtit->TestCount << " - " << dtit->Name + << " (" << disabled_reason << ")" << std::endl); + } + } - typedef std::set<cmCTestTestHandler::cmCTestTestResult, - cmCTestTestResultLess> SetOfTests; - SetOfTests resultsSet(this->TestResults.begin(), - this->TestResults.end()); + if (!failed.empty()) { + cmGeneratedFileStream ofs; + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl + << "The following tests FAILED:" << std::endl); + this->StartLogFile("TestsFailed", ofs); - for(SetOfTests::iterator ftit = resultsSet.begin(); - ftit != resultsSet.end(); ++ftit) - { - if ( ftit->Status != cmCTestTestHandler::COMPLETED ) - { + for (SetOfTests::iterator ftit = resultsSet.begin(); + ftit != resultsSet.end(); ++ftit) { + if (ftit->Status != cmCTestTestHandler::COMPLETED && + !cmHasLiteralPrefix(ftit->CompletionStatus, "SKIP_RETURN_CODE=") && + ftit->CompletionStatus != "Disabled") { ofs << ftit->TestCount << ":" << ftit->Name << std::endl; - cmCTestLog(this->CTest, HANDLER_OUTPUT, "\t" << std::setw(3) - << ftit->TestCount << " - " - << ftit->Name.c_str() << " (" - << this->GetTestStatus(ftit->Status) << ")" - << std::endl); - } + cmCTestLog( + this->CTest, HANDLER_OUTPUT, "\t" + << std::setw(3) << ftit->TestCount << " - " << ftit->Name << " (" + << this->GetTestStatus(ftit->Status) << ")" << std::endl); } } } + } - if ( this->CTest->GetProduceXML() ) - { + if (this->CTest->GetProduceXML()) { cmGeneratedFileStream xmlfile; - if( !this->StartResultingXML( + if (!this->StartResultingXML( (this->MemCheck ? cmCTest::PartMemCheck : cmCTest::PartTest), - (this->MemCheck ? "DynamicAnalysis" : "Test"), xmlfile) ) - { + (this->MemCheck ? "DynamicAnalysis" : "Test"), xmlfile)) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot create " - << (this->MemCheck ? "memory check" : "testing") - << " XML file" << std::endl); - this->LogFile = 0; + << (this->MemCheck ? "memory check" : "testing") + << " XML file" << std::endl); + this->LogFile = CM_NULLPTR; return 1; - } - this->GenerateDartOutput(xmlfile); } + cmXMLWriter xml(xmlfile); + this->GenerateDartOutput(xml); + } - if ( ! this->PostProcessHandler() ) - { - this->LogFile = 0; + if (!this->PostProcessHandler()) { + this->LogFile = CM_NULLPTR; return -1; - } + } - if ( !failed.empty() ) - { - this->LogFile = 0; + if (!failed.empty()) { + this->LogFile = CM_NULLPTR; return -1; - } - this->LogFile = 0; + } + this->LogFile = CM_NULLPTR; return 0; } -//---------------------------------------------------------------------- void cmCTestTestHandler::PrintLabelSummary() { cmCTestTestHandler::ListOfTests::iterator it = this->TestList.begin(); - cmCTestTestHandler::TestResultsVector::iterator ri = - this->TestResults.begin(); - std::map<cmStdString, double> labelTimes; - std::set<cmStdString> labels; + std::map<std::string, double> labelTimes; + std::map<std::string, int> labelCounts; + std::set<std::string> labels; // initialize maps std::string::size_type maxlen = 0; - for(; it != this->TestList.end(); ++it) - { + for (; it != this->TestList.end(); ++it) { cmCTestTestProperties& p = *it; - if(p.Labels.size() != 0) - { - for(std::vector<std::string>::iterator l = p.Labels.begin(); - l != p.Labels.end(); ++l) - { - if((*l).size() > maxlen) - { + if (!p.Labels.empty()) { + for (std::vector<std::string>::iterator l = p.Labels.begin(); + l != p.Labels.end(); ++l) { + if ((*l).size() > maxlen) { maxlen = (*l).size(); - } + } labels.insert(*l); labelTimes[*l] = 0; - } + labelCounts[*l] = 0; } } - ri = this->TestResults.begin(); + } + cmCTestTestHandler::TestResultsVector::iterator ri = + this->TestResults.begin(); // fill maps - for(; ri != this->TestResults.end(); ++ri) - { - cmCTestTestResult &result = *ri; + for (; ri != this->TestResults.end(); ++ri) { + cmCTestTestResult& result = *ri; cmCTestTestProperties& p = *result.Properties; - if(p.Labels.size() != 0) - { - for(std::vector<std::string>::iterator l = p.Labels.begin(); - l != p.Labels.end(); ++l) - { + if (!p.Labels.empty()) { + for (std::vector<std::string>::iterator l = p.Labels.begin(); + l != p.Labels.end(); ++l) { labelTimes[*l] += result.ExecutionTime; - } + ++labelCounts[*l]; } } + } // now print times - if(labels.size()) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, "\nLabel Time Summary:"); - } - for(std::set<cmStdString>::const_iterator i = labels.begin(); - i != labels.end(); ++i) - { + if (!labels.empty()) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "\nLabel Time Summary:", + this->Quiet); + } + for (std::set<std::string>::const_iterator i = labels.begin(); + i != labels.end(); ++i) { std::string label = *i; - label.resize(maxlen +3, ' '); + label.resize(maxlen + 3, ' '); + char buf[1024]; sprintf(buf, "%6.2f sec", labelTimes[*i]); - cmCTestLog(this->CTest, HANDLER_OUTPUT, "\n" - << label << " = " << buf ); - if ( this->LogFile ) - { - *this->LogFile << "\n" << *i << " = " - << buf << "\n"; - } + + std::ostringstream labelCountStr; + labelCountStr << "(" << labelCounts[*i] << " test"; + if (labelCounts[*i] > 1) { + labelCountStr << "s"; } - if(labels.size()) - { - if(this->LogFile) - { + labelCountStr << ")"; + + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "\n" + << label << " = " << buf << " " + << labelCountStr.str(), + this->Quiet); + if (this->LogFile) { + *this->LogFile << "\n" << *i << " = " << buf << "\n"; + } + } + if (!labels.empty()) { + if (this->LogFile) { *this->LogFile << "\n"; - } - cmCTestLog(this->CTest, HANDLER_OUTPUT, "\n"); } - + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "\n", this->Quiet); + } } -//---------------------------------------------------------------------- void cmCTestTestHandler::CheckLabelFilterInclude(cmCTestTestProperties& it) { // if not using Labels to filter then return - if (!this->UseIncludeLabelRegExpFlag ) - { + if (!this->UseIncludeLabelRegExpFlag) { return; - } + } // if there are no labels and we are filtering by labels // then exclude the test as it does not have the label - if(it.Labels.size() == 0 ) - { + if (it.Labels.empty()) { it.IsInBasedOnREOptions = false; return; - } + } // check to see if the label regular expression matches - bool found = false; // assume it does not match + bool found = false; // assume it does not match // loop over all labels and look for match - for(std::vector<std::string>::iterator l = it.Labels.begin(); - l != it.Labels.end(); ++l) - { - if(this->IncludeLabelRegularExpression.find(*l)) - { + for (std::vector<std::string>::iterator l = it.Labels.begin(); + l != it.Labels.end(); ++l) { + if (this->IncludeLabelRegularExpression.find(*l)) { found = true; - } } + } // if no match was found, exclude the test - if(!found) - { + if (!found) { it.IsInBasedOnREOptions = false; - } + } } - -//---------------------------------------------------------------------- void cmCTestTestHandler::CheckLabelFilterExclude(cmCTestTestProperties& it) { // if not using Labels to filter then return - if (!this->UseExcludeLabelRegExpFlag ) - { + if (!this->UseExcludeLabelRegExpFlag) { return; - } + } // if there are no labels and we are excluding by labels // then do nothing as a no label can not be a match - if(it.Labels.size() == 0 ) - { + if (it.Labels.empty()) { return; - } + } // check to see if the label regular expression matches - bool found = false; // assume it does not match + bool found = false; // assume it does not match // loop over all labels and look for match - for(std::vector<std::string>::iterator l = it.Labels.begin(); - l != it.Labels.end(); ++l) - { - if(this->ExcludeLabelRegularExpression.find(*l)) - { + for (std::vector<std::string>::iterator l = it.Labels.begin(); + l != it.Labels.end(); ++l) { + if (this->ExcludeLabelRegularExpression.find(*l)) { found = true; - } } + } // if match was found, exclude the test - if(found) - { + if (found) { it.IsInBasedOnREOptions = false; - } + } } -//---------------------------------------------------------------------- void cmCTestTestHandler::CheckLabelFilter(cmCTestTestProperties& it) { this->CheckLabelFilterInclude(it); this->CheckLabelFilterExclude(it); } -//---------------------------------------------------------------------- void cmCTestTestHandler::ComputeTestList() { this->TestList.clear(); // clear list of test this->GetListOfTests(); + + if (this->RerunFailed) { + this->ComputeTestListForRerunFailed(); + return; + } + cmCTestTestHandler::ListOfTests::size_type tmsize = this->TestList.size(); // how many tests are in based on RegExp? int inREcnt = 0; cmCTestTestHandler::ListOfTests::iterator it; - for ( it = this->TestList.begin(); it != this->TestList.end(); it ++ ) - { + for (it = this->TestList.begin(); it != this->TestList.end(); it++) { this->CheckLabelFilter(*it); - if (it->IsInBasedOnREOptions) - { - inREcnt ++; - } + if (it->IsInBasedOnREOptions) { + inREcnt++; } + } // expand the test list based on the union flag - if (this->UseUnion) - { + if (this->UseUnion) { this->ExpandTestsToRunInformation((int)tmsize); - } - else - { + } else { this->ExpandTestsToRunInformation(inREcnt); - } + } // Now create a final list of tests to run int cnt = 0; inREcnt = 0; - std::string last_directory = ""; + std::string last_directory; ListOfTests finalList; - for ( it = this->TestList.begin(); it != this->TestList.end(); it ++ ) - { - cnt ++; - if (it->IsInBasedOnREOptions) - { + for (it = this->TestList.begin(); it != this->TestList.end(); it++) { + cnt++; + if (it->IsInBasedOnREOptions) { inREcnt++; - } + } - if (this->UseUnion) - { + if (this->UseUnion) { // if it is not in the list and not in the regexp then skip - if ((this->TestsToRun.size() && - std::find(this->TestsToRun.begin(), this->TestsToRun.end(), cnt) - == this->TestsToRun.end()) && !it->IsInBasedOnREOptions) - { + if ((!this->TestsToRun.empty() && + std::find(this->TestsToRun.begin(), this->TestsToRun.end(), cnt) == + this->TestsToRun.end()) && + !it->IsInBasedOnREOptions) { continue; - } } - else - { + } else { // is this test in the list of tests to run? If not then skip it - if ((this->TestsToRun.size() && - std::find(this->TestsToRun.begin(), - this->TestsToRun.end(), inREcnt) - == this->TestsToRun.end()) || !it->IsInBasedOnREOptions) - { + if ((!this->TestsToRun.empty() && + std::find(this->TestsToRun.begin(), this->TestsToRun.end(), + inREcnt) == this->TestsToRun.end()) || + !it->IsInBasedOnREOptions) { continue; - } } - it->Index = cnt; // save the index into the test list for this test - finalList.push_back(*it); } + it->Index = cnt; // save the index into the test list for this test + finalList.push_back(*it); + } + + UpdateForFixtures(finalList); + // Save the total number of tests before exclusions this->TotalNumberOfTests = this->TestList.size(); // Set the TestList to the final list of all test this->TestList = finalList; + + this->UpdateMaxTestNameWidth(); +} + +void cmCTestTestHandler::ComputeTestListForRerunFailed() +{ + this->ExpandTestsToRunInformationForRerunFailed(); + + cmCTestTestHandler::ListOfTests::iterator it; + ListOfTests finalList; + int cnt = 0; + for (it = this->TestList.begin(); it != this->TestList.end(); it++) { + cnt++; + + // if this test is not in our list of tests to run, then skip it. + if ((!this->TestsToRun.empty() && + std::find(this->TestsToRun.begin(), this->TestsToRun.end(), cnt) == + this->TestsToRun.end())) { + continue; + } + + it->Index = cnt; + finalList.push_back(*it); + } + + UpdateForFixtures(finalList); + + // Save the total number of tests before exclusions + this->TotalNumberOfTests = this->TestList.size(); + + // Set the TestList to the list of failed tests to rerun + this->TestList = finalList; + + this->UpdateMaxTestNameWidth(); +} + +void cmCTestTestHandler::UpdateForFixtures(ListOfTests& tests) const +{ + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Updating test list for fixtures" << std::endl, + this->Quiet); + + // Prepare regular expression evaluators + std::string setupRegExp(this->ExcludeFixtureRegExp); + std::string cleanupRegExp(this->ExcludeFixtureRegExp); + if (!this->ExcludeFixtureSetupRegExp.empty()) { + if (setupRegExp.empty()) { + setupRegExp = this->ExcludeFixtureSetupRegExp; + } else { + setupRegExp.append("(" + setupRegExp + ")|(" + + this->ExcludeFixtureSetupRegExp + ")"); + } + } + if (!this->ExcludeFixtureCleanupRegExp.empty()) { + if (cleanupRegExp.empty()) { + cleanupRegExp = this->ExcludeFixtureCleanupRegExp; + } else { + cleanupRegExp.append("(" + cleanupRegExp + ")|(" + + this->ExcludeFixtureCleanupRegExp + ")"); + } + } + cmsys::RegularExpression excludeSetupRegex(setupRegExp); + cmsys::RegularExpression excludeCleanupRegex(cleanupRegExp); + + // Prepare some maps to help us find setup and cleanup tests for + // any given fixture + typedef ListOfTests::const_iterator TestIterator; + typedef std::multimap<std::string, TestIterator> FixtureDependencies; + typedef FixtureDependencies::const_iterator FixtureDepsIterator; + FixtureDependencies fixtureSetups; + FixtureDependencies fixtureCleanups; + + for (ListOfTests::const_iterator it = this->TestList.begin(); + it != this->TestList.end(); ++it) { + const cmCTestTestProperties& p = *it; + + const std::set<std::string>& setups = p.FixturesSetup; + for (std::set<std::string>::const_iterator depsIt = setups.begin(); + depsIt != setups.end(); ++depsIt) { + fixtureSetups.insert(std::make_pair(*depsIt, it)); + } + + const std::set<std::string>& cleanups = p.FixturesCleanup; + for (std::set<std::string>::const_iterator depsIt = cleanups.begin(); + depsIt != cleanups.end(); ++depsIt) { + fixtureCleanups.insert(std::make_pair(*depsIt, it)); + } + } + + // Prepare fast lookup of tests already included in our list of tests + std::set<std::string> addedTests; + for (ListOfTests::const_iterator it = tests.begin(); it != tests.end(); + ++it) { + const cmCTestTestProperties& p = *it; + addedTests.insert(p.Name); + } + + // These are lookups of fixture name to a list of indices into the final + // tests array for tests which require that fixture and tests which are + // setups for that fixture. They are needed at the end to populate + // dependencies of the cleanup tests in our final list of tests. + std::map<std::string, std::vector<size_t> > fixtureRequirements; + std::map<std::string, std::vector<size_t> > setupFixturesAdded; + + // Use integer index for iteration because we append to + // the tests vector as we go + size_t fixtureTestsAdded = 0; + std::set<std::string> addedFixtures; + for (size_t i = 0; i < tests.size(); ++i) { + // Skip disabled tests + if (tests[i].Disabled) { + continue; + } + + // There are two things to do for each test: + // 1. For every fixture required by this test, record that fixture as + // being required and create dependencies on that fixture's setup + // tests. + // 2. Record all setup tests in the final test list so we can later make + // cleanup tests in the test list depend on their associated setup + // tests to enforce correct ordering. + + // 1. Handle fixture requirements + // + // Must copy the set of fixtures required because we may invalidate + // the tests array by appending to it + std::set<std::string> fixtures = tests[i].FixturesRequired; + for (std::set<std::string>::const_iterator fixturesIt = fixtures.begin(); + fixturesIt != fixtures.end(); ++fixturesIt) { + + const std::string& requiredFixtureName = *fixturesIt; + if (requiredFixtureName.empty()) { + continue; + } + + fixtureRequirements[requiredFixtureName].push_back(i); + + // Add dependencies to this test for all of the setup tests + // associated with the required fixture. If any of those setup + // tests fail, this test should not run. We make the fixture's + // cleanup tests depend on this test case later. + std::pair<FixtureDepsIterator, FixtureDepsIterator> setupRange = + fixtureSetups.equal_range(requiredFixtureName); + for (FixtureDepsIterator sIt = setupRange.first; + sIt != setupRange.second; ++sIt) { + const std::string& setupTestName = sIt->second->Name; + tests[i].RequireSuccessDepends.insert(setupTestName); + if (std::find(tests[i].Depends.begin(), tests[i].Depends.end(), + setupTestName) == tests[i].Depends.end()) { + tests[i].Depends.push_back(setupTestName); + } + } + + // Append any fixture setup/cleanup tests to our test list if they + // are not already in it (they could have been in the original + // set of tests passed to us at the outset or have already been + // added from a previously checked test). A fixture isn't required + // to have setup/cleanup tests. + if (!addedFixtures.insert(requiredFixtureName).second) { + // Already seen this fixture, no need to check it again + continue; + } + + // Only add setup tests if this fixture has not been excluded + if (setupRegExp.empty() || + !excludeSetupRegex.find(requiredFixtureName)) { + std::pair<FixtureDepsIterator, FixtureDepsIterator> fixtureRange = + fixtureSetups.equal_range(requiredFixtureName); + for (FixtureDepsIterator it = fixtureRange.first; + it != fixtureRange.second; ++it) { + ListOfTests::const_iterator lotIt = it->second; + const cmCTestTestProperties& p = *lotIt; + + if (!addedTests.insert(p.Name).second) { + // Already have p in our test list + continue; + } + + // This is a test not yet in our list, so add it and + // update its index to reflect where it was in the original + // full list of all tests (needed to track individual tests + // across ctest runs for re-run failed, etc.) + tests.push_back(p); + tests.back().Index = + 1 + static_cast<int>(std::distance(this->TestList.begin(), lotIt)); + ++fixtureTestsAdded; + + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Added setup test " + << p.Name << " required by fixture " + << requiredFixtureName << std::endl, + this->Quiet); + } + } + + // Only add cleanup tests if this fixture has not been excluded + if (cleanupRegExp.empty() || + !excludeCleanupRegex.find(requiredFixtureName)) { + std::pair<FixtureDepsIterator, FixtureDepsIterator> fixtureRange = + fixtureCleanups.equal_range(requiredFixtureName); + for (FixtureDepsIterator it = fixtureRange.first; + it != fixtureRange.second; ++it) { + ListOfTests::const_iterator lotIt = it->second; + const cmCTestTestProperties& p = *lotIt; + + if (!addedTests.insert(p.Name).second) { + // Already have p in our test list + continue; + } + + // This is a test not yet in our list, so add it and + // update its index to reflect where it was in the original + // full list of all tests (needed to track individual tests + // across ctest runs for re-run failed, etc.) + tests.push_back(p); + tests.back().Index = + 1 + static_cast<int>(std::distance(this->TestList.begin(), lotIt)); + ++fixtureTestsAdded; + + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Added cleanup test " + << p.Name << " required by fixture " + << requiredFixtureName << std::endl, + this->Quiet); + } + } + } + + // 2. Record all setup fixtures included in the final list of tests + for (std::set<std::string>::const_iterator fixturesIt = + tests[i].FixturesSetup.begin(); + fixturesIt != tests[i].FixturesSetup.end(); ++fixturesIt) { + + const std::string& setupFixtureName = *fixturesIt; + if (setupFixtureName.empty()) { + continue; + } + + setupFixturesAdded[setupFixtureName].push_back(i); + } + } + + // Now that we have the final list of tests, we can update all cleanup + // tests to depend on those tests which require that fixture and on any + // setup tests for that fixture. The latter is required to handle the + // pathological case where setup and cleanup tests are in the test set + // but no other test has that fixture as a requirement. + for (ListOfTests::iterator tIt = tests.begin(); tIt != tests.end(); ++tIt) { + cmCTestTestProperties& p = *tIt; + const std::set<std::string>& cleanups = p.FixturesCleanup; + for (std::set<std::string>::const_iterator fIt = cleanups.begin(); + fIt != cleanups.end(); ++fIt) { + const std::string& fixture = *fIt; + + // This cleanup test could be part of the original test list that was + // passed in. It is then possible that no other test requires the + // fIt fixture, so we have to check for this. + std::map<std::string, std::vector<size_t> >::const_iterator cIt = + fixtureRequirements.find(fixture); + if (cIt != fixtureRequirements.end()) { + const std::vector<size_t>& indices = cIt->second; + for (std::vector<size_t>::const_iterator indexIt = indices.begin(); + indexIt != indices.end(); ++indexIt) { + const std::string& reqTestName = tests[*indexIt].Name; + if (std::find(p.Depends.begin(), p.Depends.end(), reqTestName) == + p.Depends.end()) { + p.Depends.push_back(reqTestName); + } + } + } + + // Ensure fixture cleanup tests always run after their setup tests, even + // if no other test cases require the fixture + cIt = setupFixturesAdded.find(fixture); + if (cIt != setupFixturesAdded.end()) { + const std::vector<size_t>& indices = cIt->second; + for (std::vector<size_t>::const_iterator indexIt = indices.begin(); + indexIt != indices.end(); ++indexIt) { + const std::string& setupTestName = tests[*indexIt].Name; + if (std::find(p.Depends.begin(), p.Depends.end(), setupTestName) == + p.Depends.end()) { + p.Depends.push_back(setupTestName); + } + } + } + } + } + + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Added " + << fixtureTestsAdded + << " tests to meet fixture requirements" << std::endl, + this->Quiet); +} + +void cmCTestTestHandler::UpdateMaxTestNameWidth() +{ std::string::size_type max = this->CTest->GetMaxTestNameWidth(); - for (it = this->TestList.begin(); - it != this->TestList.end(); it ++ ) - { + for (cmCTestTestHandler::ListOfTests::iterator it = this->TestList.begin(); + it != this->TestList.end(); it++) { cmCTestTestProperties& p = *it; - if(max < p.Name.size()) - { + if (max < p.Name.size()) { max = p.Name.size(); - } } - if(static_cast<std::string::size_type>(this->CTest->GetMaxTestNameWidth()) - != max) - { + } + if (static_cast<std::string::size_type>( + this->CTest->GetMaxTestNameWidth()) != max) { this->CTest->SetMaxTestNameWidth(static_cast<int>(max)); - } + } } -bool cmCTestTestHandler::GetValue(const char* tag, - int& value, - std::ifstream& fin) +bool cmCTestTestHandler::GetValue(const char* tag, int& value, + std::istream& fin) { std::string line; bool ret = true; cmSystemTools::GetLineFromStream(fin, line); - if(line == tag) - { + if (line == tag) { fin >> value; ret = cmSystemTools::GetLineFromStream(fin, line); // read blank line - } - else - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "parse error: missing tag: " - << tag << " found [" << line << "]" << std::endl); + } else { + cmCTestLog(this->CTest, ERROR_MESSAGE, "parse error: missing tag: " + << tag << " found [" << line << "]" << std::endl); ret = false; - } + } return ret; } -bool cmCTestTestHandler::GetValue(const char* tag, - double& value, - std::ifstream& fin) +bool cmCTestTestHandler::GetValue(const char* tag, double& value, + std::istream& fin) { std::string line; cmSystemTools::GetLineFromStream(fin, line); bool ret = true; - if(line == tag) - { + if (line == tag) { fin >> value; ret = cmSystemTools::GetLineFromStream(fin, line); // read blank line - } - else - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "parse error: missing tag: " - << tag << " found [" << line << "]" << std::endl); + } else { + cmCTestLog(this->CTest, ERROR_MESSAGE, "parse error: missing tag: " + << tag << " found [" << line << "]" << std::endl); ret = false; - } + } return ret; } -bool cmCTestTestHandler::GetValue(const char* tag, - bool& value, - std::ifstream& fin) +bool cmCTestTestHandler::GetValue(const char* tag, bool& value, + std::istream& fin) { std::string line; cmSystemTools::GetLineFromStream(fin, line); bool ret = true; - if(line == tag) - { + if (line == tag) { #ifdef __HAIKU__ int tmp = 0; fin >> tmp; value = false; - if(tmp) - { + if (tmp) { value = true; - } + } #else fin >> value; #endif ret = cmSystemTools::GetLineFromStream(fin, line); // read blank line - } - else - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "parse error: missing tag: " - << tag << " found [" << line << "]" << std::endl); + } else { + cmCTestLog(this->CTest, ERROR_MESSAGE, "parse error: missing tag: " + << tag << " found [" << line << "]" << std::endl); ret = false; - } + } return ret; } -bool cmCTestTestHandler::GetValue(const char* tag, - size_t& value, - std::ifstream& fin) +bool cmCTestTestHandler::GetValue(const char* tag, size_t& value, + std::istream& fin) { std::string line; cmSystemTools::GetLineFromStream(fin, line); bool ret = true; - if(line == tag) - { + if (line == tag) { fin >> value; ret = cmSystemTools::GetLineFromStream(fin, line); // read blank line - } - else - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "parse error: missing tag: " - << tag << " found [" << line.c_str() << "]" << std::endl); + } else { + cmCTestLog(this->CTest, ERROR_MESSAGE, "parse error: missing tag: " + << tag << " found [" << line << "]" << std::endl); ret = false; - } + } return ret; } -bool cmCTestTestHandler::GetValue(const char* tag, - std::string& value, - std::ifstream& fin) +bool cmCTestTestHandler::GetValue(const char* tag, std::string& value, + std::istream& fin) { std::string line; cmSystemTools::GetLineFromStream(fin, line); bool ret = true; - if(line == tag) - { - ret = cmSystemTools::GetLineFromStream(fin, value); - } - else - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "parse error: missing tag: " - << tag << " found [" << line << "]" << std::endl); + if (line == tag) { + ret = cmSystemTools::GetLineFromStream(fin, value); + } else { + cmCTestLog(this->CTest, ERROR_MESSAGE, "parse error: missing tag: " + << tag << " found [" << line << "]" << std::endl); ret = false; - } + } return ret; } -//--------------------------------------------------------------------- -void cmCTestTestHandler::ProcessDirectory(std::vector<cmStdString> &passed, - std::vector<cmStdString> &failed) +void cmCTestTestHandler::ProcessDirectory(std::vector<std::string>& passed, + std::vector<std::string>& failed) { this->ComputeTestList(); this->StartTest = this->CTest->CurrentTime(); this->StartTestTime = static_cast<unsigned int>(cmSystemTools::GetTime()); double elapsed_time_start = cmSystemTools::GetTime(); - cmCTestMultiProcessHandler* parallel = this->CTest->GetBatchJobs() ? - new cmCTestBatchTestHandler : new cmCTestMultiProcessHandler; + cmCTestMultiProcessHandler* parallel = this->CTest->GetBatchJobs() + ? new cmCTestBatchTestHandler + : new cmCTestMultiProcessHandler; parallel->SetCTest(this->CTest); parallel->SetParallelLevel(this->CTest->GetParallelLevel()); parallel->SetTestHandler(this); - - *this->LogFile << "Start testing: " - << this->CTest->CurrentTime() << std::endl + parallel->SetQuiet(this->Quiet); + if (this->TestLoad > 0) { + parallel->SetTestLoad(this->TestLoad); + } else { + parallel->SetTestLoad(this->CTest->GetTestLoad()); + } + + *this->LogFile + << "Start testing: " << this->CTest->CurrentTime() << std::endl << "----------------------------------------------------------" << std::endl; @@ -1041,320 +1214,271 @@ void cmCTestTestHandler::ProcessDirectory(std::vector<cmStdString> &passed, cmCTestMultiProcessHandler::PropertiesMap properties; bool randomSchedule = this->CTest->GetScheduleType() == "Random"; - if(randomSchedule) - { - srand((unsigned)time(0)); - } + if (randomSchedule) { + srand((unsigned)time(CM_NULLPTR)); + } for (ListOfTests::iterator it = this->TestList.begin(); - it != this->TestList.end(); ++it) - { + it != this->TestList.end(); ++it) { cmCTestTestProperties& p = *it; cmCTestMultiProcessHandler::TestSet depends; - if(randomSchedule) - { + if (randomSchedule) { p.Cost = static_cast<float>(rand()); - } + } - if(p.Timeout == 0 && this->CTest->GetGlobalTimeout() != 0) - { + if (p.Timeout == 0 && this->CTest->GetGlobalTimeout() != 0) { p.Timeout = this->CTest->GetGlobalTimeout(); - } + } - if(p.Depends.size()) - { - for(std::vector<std::string>::iterator i = p.Depends.begin(); - i != p.Depends.end(); ++i) - { - for(ListOfTests::iterator it2 = this->TestList.begin(); - it2 != this->TestList.end(); ++it2) - { - if(it2->Name == *i) - { + if (!p.Depends.empty()) { + for (std::vector<std::string>::iterator i = p.Depends.begin(); + i != p.Depends.end(); ++i) { + for (ListOfTests::iterator it2 = this->TestList.begin(); + it2 != this->TestList.end(); ++it2) { + if (it2->Name == *i) { depends.insert(it2->Index); break; // break out of test loop as name can only match 1 - } } } } + } tests[it->Index] = depends; properties[it->Index] = &*it; - } + } parallel->SetTests(tests, properties); parallel->SetPassFailVectors(&passed, &failed); this->TestResults.clear(); parallel->SetTestResults(&this->TestResults); - if(this->CTest->ShouldPrintLabels()) - { + if (this->CTest->ShouldPrintLabels()) { parallel->PrintLabels(); - } - else if(this->CTest->GetShowOnly()) - { + } else if (this->CTest->GetShowOnly()) { parallel->PrintTestList(); - } - else - { + } else { parallel->RunTests(); - } + } delete parallel; this->EndTest = this->CTest->CurrentTime(); this->EndTestTime = static_cast<unsigned int>(cmSystemTools::GetTime()); this->ElapsedTestingTime = cmSystemTools::GetTime() - elapsed_time_start; - *this->LogFile << "End testing: " - << this->CTest->CurrentTime() << std::endl; + *this->LogFile << "End testing: " << this->CTest->CurrentTime() << std::endl; } -//---------------------------------------------------------------------- -void cmCTestTestHandler::GenerateTestCommand(std::vector<std::string>&, int) +void cmCTestTestHandler::GenerateTestCommand( + std::vector<std::string>& /*unused*/, int /*unused*/) { } -//---------------------------------------------------------------------- -void cmCTestTestHandler::GenerateDartOutput(std::ostream& os) +void cmCTestTestHandler::GenerateDartOutput(cmXMLWriter& xml) { - if ( !this->CTest->GetProduceXML() ) - { + if (!this->CTest->GetProduceXML()) { return; - } + } - this->CTest->StartXML(os, this->AppendXML); - os << "<Testing>\n" - << "\t<StartDateTime>" << this->StartTest << "</StartDateTime>\n" - << "\t<StartTestTime>" << this->StartTestTime << "</StartTestTime>\n" - << "\t<TestList>\n"; + this->CTest->StartXML(xml, this->AppendXML); + xml.StartElement("Testing"); + xml.Element("StartDateTime", this->StartTest); + xml.Element("StartTestTime", this->StartTestTime); + xml.StartElement("TestList"); cmCTestTestHandler::TestResultsVector::size_type cc; - for ( cc = 0; cc < this->TestResults.size(); cc ++ ) - { - cmCTestTestResult *result = &this->TestResults[cc]; + for (cc = 0; cc < this->TestResults.size(); cc++) { + cmCTestTestResult* result = &this->TestResults[cc]; std::string testPath = result->Path + "/" + result->Name; - os << "\t\t<Test>" << cmXMLSafe( - this->CTest->GetShortPathToFile(testPath.c_str())) - << "</Test>" << std::endl; - } - os << "\t</TestList>\n"; - for ( cc = 0; cc < this->TestResults.size(); cc ++ ) - { - cmCTestTestResult *result = &this->TestResults[cc]; - this->WriteTestResultHeader(os, result); - os << "\t\t<Results>" << std::endl; - if ( result->Status != cmCTestTestHandler::NOT_RUN ) - { - if ( result->Status != cmCTestTestHandler::COMPLETED || - result->ReturnValue ) - { - os << "\t\t\t<NamedMeasurement type=\"text/string\" " - "name=\"Exit Code\"><Value>" - << cmXMLSafe(this->GetTestStatus(result->Status)) - << "</Value>" - "</NamedMeasurement>\n" - << "\t\t\t<NamedMeasurement type=\"text/string\" " - "name=\"Exit Value\"><Value>" - << result->ReturnValue - << "</Value></NamedMeasurement>" - << std::endl; - } - os << result->RegressionImages; - os << "\t\t\t<NamedMeasurement type=\"numeric/double\" " - << "name=\"Execution Time\"><Value>" - << result->ExecutionTime - << "</Value></NamedMeasurement>\n"; - if(result->Reason.size()) - { + xml.Element("Test", this->CTest->GetShortPathToFile(testPath.c_str())); + } + xml.EndElement(); // TestList + for (cc = 0; cc < this->TestResults.size(); cc++) { + cmCTestTestResult* result = &this->TestResults[cc]; + this->WriteTestResultHeader(xml, result); + xml.StartElement("Results"); + + if (result->Status != cmCTestTestHandler::NOT_RUN) { + if (result->Status != cmCTestTestHandler::COMPLETED || + result->ReturnValue) { + xml.StartElement("NamedMeasurement"); + xml.Attribute("type", "text/string"); + xml.Attribute("name", "Exit Code"); + xml.Element("Value", this->GetTestStatus(result->Status)); + xml.EndElement(); // NamedMeasurement + + xml.StartElement("NamedMeasurement"); + xml.Attribute("type", "text/string"); + xml.Attribute("name", "Exit Value"); + xml.Element("Value", result->ReturnValue); + xml.EndElement(); // NamedMeasurement + } + this->GenerateRegressionImages(xml, result->DartString); + xml.StartElement("NamedMeasurement"); + xml.Attribute("type", "numeric/double"); + xml.Attribute("name", "Execution Time"); + xml.Element("Value", result->ExecutionTime); + xml.EndElement(); // NamedMeasurement + if (!result->Reason.empty()) { const char* reasonType = "Pass Reason"; - if(result->Status != cmCTestTestHandler::COMPLETED && - result->Status != cmCTestTestHandler::NOT_RUN) - { + if (result->Status != cmCTestTestHandler::COMPLETED) { reasonType = "Fail Reason"; - } - os << "\t\t\t<NamedMeasurement type=\"text/string\" " - << "name=\"" << reasonType << "\"><Value>" - << cmXMLSafe(result->Reason) - << "</Value></NamedMeasurement>\n"; } - os - << "\t\t\t<NamedMeasurement type=\"text/string\" " - << "name=\"Completion Status\"><Value>" - << cmXMLSafe(result->CompletionStatus) - << "</Value></NamedMeasurement>\n"; - } - os - << "\t\t\t<NamedMeasurement type=\"text/string\" " - << "name=\"Command Line\"><Value>" - << cmXMLSafe(result->FullCommandLine) - << "</Value></NamedMeasurement>\n"; - std::map<cmStdString,cmStdString>::iterator measureIt; - for ( measureIt = result->Properties->Measurements.begin(); - measureIt != result->Properties->Measurements.end(); - ++ measureIt ) - { - os - << "\t\t\t<NamedMeasurement type=\"text/string\" " - << "name=\"" << measureIt->first.c_str() << "\"><Value>" - << cmXMLSafe(measureIt->second) - << "</Value></NamedMeasurement>\n"; + xml.StartElement("NamedMeasurement"); + xml.Attribute("type", "text/string"); + xml.Attribute("name", reasonType); + xml.Element("Value", result->Reason); + xml.EndElement(); // NamedMeasurement } - os - << "\t\t\t<Measurement>\n" - << "\t\t\t\t<Value" - << (result->CompressOutput ? - " encoding=\"base64\" compression=\"gzip\">" - : ">"); - os << cmXMLSafe(result->Output); - os - << "</Value>\n" - << "\t\t\t</Measurement>\n" - << "\t\t</Results>\n"; - - this->AttachFiles(os, result); - this->WriteTestResultFooter(os, result); - } - - os << "\t<EndDateTime>" << this->EndTest << "</EndDateTime>\n" - << "\t<EndTestTime>" << this->EndTestTime << "</EndTestTime>\n" - << "<ElapsedMinutes>" - << static_cast<int>(this->ElapsedTestingTime/6)/10.0 - << "</ElapsedMinutes>" - << "</Testing>" << std::endl; - this->CTest->EndXML(os); + } + + xml.StartElement("NamedMeasurement"); + xml.Attribute("type", "text/string"); + xml.Attribute("name", "Completion Status"); + xml.Element("Value", result->CompletionStatus); + xml.EndElement(); // NamedMeasurement + + xml.StartElement("NamedMeasurement"); + xml.Attribute("type", "text/string"); + xml.Attribute("name", "Command Line"); + xml.Element("Value", result->FullCommandLine); + xml.EndElement(); // NamedMeasurement + std::map<std::string, std::string>::iterator measureIt; + for (measureIt = result->Properties->Measurements.begin(); + measureIt != result->Properties->Measurements.end(); ++measureIt) { + xml.StartElement("NamedMeasurement"); + xml.Attribute("type", "text/string"); + xml.Attribute("name", measureIt->first); + xml.Element("Value", measureIt->second); + xml.EndElement(); // NamedMeasurement + } + xml.StartElement("Measurement"); + xml.StartElement("Value"); + if (result->CompressOutput) { + xml.Attribute("encoding", "base64"); + xml.Attribute("compression", "gzip"); + } + xml.Content(result->Output); + xml.EndElement(); // Value + xml.EndElement(); // Measurement + xml.EndElement(); // Results + + this->AttachFiles(xml, result); + this->WriteTestResultFooter(xml, result); + } + + xml.Element("EndDateTime", this->EndTest); + xml.Element("EndTestTime", this->EndTestTime); + xml.Element("ElapsedMinutes", + static_cast<int>(this->ElapsedTestingTime / 6) / 10.0); + xml.EndElement(); // Testing + this->CTest->EndXML(xml); } -//---------------------------------------------------------------------------- -void cmCTestTestHandler::WriteTestResultHeader(std::ostream& os, +void cmCTestTestHandler::WriteTestResultHeader(cmXMLWriter& xml, cmCTestTestResult* result) { - os << "\t<Test Status=\""; - if ( result->Status == cmCTestTestHandler::COMPLETED ) - { - os << "passed"; - } - else if ( result->Status == cmCTestTestHandler::NOT_RUN ) - { - os << "notrun"; - } - else - { - os << "failed"; - } + xml.StartElement("Test"); + if (result->Status == cmCTestTestHandler::COMPLETED) { + xml.Attribute("Status", "passed"); + } else if (result->Status == cmCTestTestHandler::NOT_RUN) { + xml.Attribute("Status", "notrun"); + } else { + xml.Attribute("Status", "failed"); + } std::string testPath = result->Path + "/" + result->Name; - os << "\">\n" - << "\t\t<Name>" << cmXMLSafe(result->Name) << "</Name>\n" - << "\t\t<Path>" << cmXMLSafe( - this->CTest->GetShortPathToFile(result->Path.c_str())) << "</Path>\n" - << "\t\t<FullName>" << cmXMLSafe( - this->CTest->GetShortPathToFile(testPath.c_str())) << "</FullName>\n" - << "\t\t<FullCommandLine>" - << cmXMLSafe(result->FullCommandLine) - << "</FullCommandLine>\n"; + xml.Element("Name", result->Name); + xml.Element("Path", this->CTest->GetShortPathToFile(result->Path.c_str())); + xml.Element("FullName", this->CTest->GetShortPathToFile(testPath.c_str())); + xml.Element("FullCommandLine", result->FullCommandLine); } -//---------------------------------------------------------------------------- -void cmCTestTestHandler::WriteTestResultFooter(std::ostream& os, +void cmCTestTestHandler::WriteTestResultFooter(cmXMLWriter& xml, cmCTestTestResult* result) { - if(!result->Properties->Labels.empty()) - { - os << "\t\t<Labels>\n"; + if (!result->Properties->Labels.empty()) { + xml.StartElement("Labels"); std::vector<std::string> const& labels = result->Properties->Labels; - for(std::vector<std::string>::const_iterator li = labels.begin(); - li != labels.end(); ++li) - { - os << "\t\t\t<Label>" << cmXMLSafe(*li) << "</Label>\n"; - } - os << "\t\t</Labels>\n"; + for (std::vector<std::string>::const_iterator li = labels.begin(); + li != labels.end(); ++li) { + xml.Element("Label", *li); } + xml.EndElement(); // Labels + } - os - << "\t</Test>" << std::endl; + xml.EndElement(); // Test } -//---------------------------------------------------------------------- -void cmCTestTestHandler::AttachFiles(std::ostream& os, +void cmCTestTestHandler::AttachFiles(cmXMLWriter& xml, cmCTestTestResult* result) { - if(result->Status != cmCTestTestHandler::COMPLETED - && result->Properties->AttachOnFail.size()) - { + if (result->Status != cmCTestTestHandler::COMPLETED && + !result->Properties->AttachOnFail.empty()) { result->Properties->AttachedFiles.insert( result->Properties->AttachedFiles.end(), result->Properties->AttachOnFail.begin(), result->Properties->AttachOnFail.end()); - } - for(std::vector<std::string>::const_iterator file = - result->Properties->AttachedFiles.begin(); - file != result->Properties->AttachedFiles.end(); ++file) - { - std::string base64 = this->CTest->Base64GzipEncodeFile(*file); + } + for (std::vector<std::string>::const_iterator file = + result->Properties->AttachedFiles.begin(); + file != result->Properties->AttachedFiles.end(); ++file) { + const std::string& base64 = this->CTest->Base64GzipEncodeFile(*file); std::string fname = cmSystemTools::GetFilenameName(*file); - os << "\t\t<NamedMeasurement name=\"Attached File\" encoding=\"base64\" " - "compression=\"tar/gzip\" filename=\"" << fname << "\" type=\"file\">" - "\n\t\t\t<Value>\n\t\t\t" - << base64 - << "\n\t\t\t</Value>\n\t\t</NamedMeasurement>\n"; - } + xml.StartElement("NamedMeasurement"); + xml.Attribute("name", "Attached File"); + xml.Attribute("encoding", "base64"); + xml.Attribute("compression", "tar/gzip"); + xml.Attribute("filename", fname); + xml.Attribute("type", "file"); + xml.Element("Value", base64); + xml.EndElement(); // NamedMeasurement + } } -//---------------------------------------------------------------------- -int cmCTestTestHandler::ExecuteCommands(std::vector<cmStdString>& vec) +int cmCTestTestHandler::ExecuteCommands(std::vector<std::string>& vec) { - std::vector<cmStdString>::iterator it; - for ( it = vec.begin(); it != vec.end(); ++it ) - { + std::vector<std::string>::iterator it; + for (it = vec.begin(); it != vec.end(); ++it) { int retVal = 0; - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Run command: " << *it - << std::endl); - if ( !cmSystemTools::RunSingleCommand(it->c_str(), 0, &retVal, 0, - cmSystemTools::OUTPUT_MERGE - /*this->Verbose*/) || retVal != 0 ) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, "Problem running command: " - << *it << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Run command: " << *it << std::endl, this->Quiet); + if (!cmSystemTools::RunSingleCommand(it->c_str(), CM_NULLPTR, CM_NULLPTR, + &retVal, CM_NULLPTR, + cmSystemTools::OUTPUT_MERGE + /*this->Verbose*/) || + retVal != 0) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Problem running command: " << *it << std::endl); return 0; - } } + } return 1; } - -//---------------------------------------------------------------------- // Find the appropriate executable to run for a test -std::string cmCTestTestHandler::FindTheExecutable(const char *exe) +std::string cmCTestTestHandler::FindTheExecutable(const char* exe) { std::string resConfig; std::vector<std::string> extraPaths; std::vector<std::string> failedPaths; - if(strcmp(exe, "NOT_AVAILABLE") == 0) - { + if (strcmp(exe, "NOT_AVAILABLE") == 0) { return exe; - } - return cmCTestTestHandler::FindExecutable(this->CTest, - exe, resConfig, - extraPaths, - failedPaths); + } + return cmCTestTestHandler::FindExecutable(this->CTest, exe, resConfig, + extraPaths, failedPaths); } // add additional configurations to the search path -void cmCTestTestHandler -::AddConfigurations(cmCTest *ctest, - std::vector<std::string> &attempted, - std::vector<std::string> &attemptedConfigs, - std::string filepath, - std::string &filename) +void cmCTestTestHandler::AddConfigurations( + cmCTest* ctest, std::vector<std::string>& attempted, + std::vector<std::string>& attemptedConfigs, std::string filepath, + std::string& filename) { std::string tempPath; - if (filepath.size() && - filepath[filepath.size()-1] != '/') - { + if (!filepath.empty() && filepath[filepath.size() - 1] != '/') { filepath += "/"; - } + } tempPath = filepath + filename; attempted.push_back(tempPath); attemptedConfigs.push_back(""); - if(ctest->GetConfigType().size()) - { + if (!ctest->GetConfigType().empty()) { tempPath = filepath; tempPath += ctest->GetConfigType(); tempPath += "/"; @@ -1369,9 +1493,7 @@ void cmCTestTestHandler tempPath += filename; attempted.push_back(tempPath); attemptedConfigs.push_back(ctest->GetConfigType()); - } - else - { + } else { // no config specified - try some options... tempPath = filepath; tempPath += "Release/"; @@ -1403,247 +1525,193 @@ void cmCTestTestHandler tempPath += filename; attempted.push_back(tempPath); attemptedConfigs.push_back("Deployment"); - } + } } - -//---------------------------------------------------------------------- // Find the appropriate executable to run for a test -std::string cmCTestTestHandler -::FindExecutable(cmCTest *ctest, - const char *testCommand, - std::string &resultingConfig, - std::vector<std::string> &extraPaths, - std::vector<std::string> &failed) +std::string cmCTestTestHandler::FindExecutable( + cmCTest* ctest, const char* testCommand, std::string& resultingConfig, + std::vector<std::string>& extraPaths, std::vector<std::string>& failed) { // now run the compiled test if we can find it std::vector<std::string> attempted; std::vector<std::string> attemptedConfigs; std::string tempPath; - std::string filepath = - cmSystemTools::GetFilenamePath(testCommand); - std::string filename = - cmSystemTools::GetFilenameName(testCommand); + std::string filepath = cmSystemTools::GetFilenamePath(testCommand); + std::string filename = cmSystemTools::GetFilenameName(testCommand); - cmCTestTestHandler::AddConfigurations(ctest, attempted, - attemptedConfigs, - filepath,filename); + cmCTestTestHandler::AddConfigurations(ctest, attempted, attemptedConfigs, + filepath, filename); // even if a fullpath was specified also try it relative to the current // directory - if (filepath.size() && filepath[0] == '/') - { - std::string localfilepath = filepath.substr(1,filepath.size()-1); - cmCTestTestHandler::AddConfigurations(ctest, attempted, - attemptedConfigs, - localfilepath,filename); - } - + if (!filepath.empty() && filepath[0] == '/') { + std::string localfilepath = filepath.substr(1, filepath.size() - 1); + cmCTestTestHandler::AddConfigurations(ctest, attempted, attemptedConfigs, + localfilepath, filename); + } // if extraPaths are provided and we were not passed a full path, try them, // try any extra paths - if (filepath.size() == 0) - { - for (unsigned int i = 0; i < extraPaths.size(); ++i) - { + if (filepath.empty()) { + for (unsigned int i = 0; i < extraPaths.size(); ++i) { std::string filepathExtra = cmSystemTools::GetFilenamePath(extraPaths[i]); std::string filenameExtra = cmSystemTools::GetFilenameName(extraPaths[i]); - cmCTestTestHandler::AddConfigurations(ctest,attempted, - attemptedConfigs, - filepathExtra, - filenameExtra); - } + cmCTestTestHandler::AddConfigurations(ctest, attempted, attemptedConfigs, + filepathExtra, filenameExtra); } + } // store the final location in fullPath std::string fullPath; // now look in the paths we specified above - for(unsigned int ai=0; - ai < attempted.size() && fullPath.size() == 0; ++ai) - { + for (unsigned int ai = 0; ai < attempted.size() && fullPath.empty(); ++ai) { // first check without exe extension - if(cmSystemTools::FileExists(attempted[ai].c_str()) - && !cmSystemTools::FileIsDirectory(attempted[ai].c_str())) - { - fullPath = cmSystemTools::CollapseFullPath(attempted[ai].c_str()); + if (cmSystemTools::FileExists(attempted[ai].c_str()) && + !cmSystemTools::FileIsDirectory(attempted[ai])) { + fullPath = cmSystemTools::CollapseFullPath(attempted[ai]); resultingConfig = attemptedConfigs[ai]; - } + } // then try with the exe extension - else - { - failed.push_back(attempted[ai].c_str()); + else { + failed.push_back(attempted[ai]); tempPath = attempted[ai]; tempPath += cmSystemTools::GetExecutableExtension(); - if(cmSystemTools::FileExists(tempPath.c_str()) - && !cmSystemTools::FileIsDirectory(tempPath.c_str())) - { - fullPath = cmSystemTools::CollapseFullPath(tempPath.c_str()); + if (cmSystemTools::FileExists(tempPath.c_str()) && + !cmSystemTools::FileIsDirectory(tempPath)) { + fullPath = cmSystemTools::CollapseFullPath(tempPath); resultingConfig = attemptedConfigs[ai]; - } - else - { - failed.push_back(tempPath.c_str()); - } + } else { + failed.push_back(tempPath); } } + } // if everything else failed, check the users path, but only if a full path // wasn't specified - if (fullPath.size() == 0 && filepath.size() == 0) - { + if (fullPath.empty() && filepath.empty()) { std::string path = cmSystemTools::FindProgram(filename.c_str()); - if (path != "") - { + if (path != "") { resultingConfig = ""; return path; - } } - if(fullPath.size() == 0) - { - cmCTestLog(ctest, HANDLER_OUTPUT, - "Could not find executable " << testCommand << "\n" - << "Looked in the following places:\n"); - for(std::vector<std::string>::iterator i = failed.begin(); - i != failed.end(); ++i) - { - cmCTestLog(ctest, HANDLER_OUTPUT, - i->c_str() << "\n"); - } + } + if (fullPath.empty()) { + cmCTestLog(ctest, HANDLER_OUTPUT, "Could not find executable " + << testCommand << "\n" + << "Looked in the following places:\n"); + for (std::vector<std::string>::iterator i = failed.begin(); + i != failed.end(); ++i) { + cmCTestLog(ctest, HANDLER_OUTPUT, *i << "\n"); } + } return fullPath; } - -//---------------------------------------------------------------------- void cmCTestTestHandler::GetListOfTests() { - if ( !this->IncludeLabelRegExp.empty() ) - { - this->IncludeLabelRegularExpression. - compile(this->IncludeLabelRegExp.c_str()); - } - if ( !this->IncludeLabelRegExp.empty() ) - { - this->ExcludeLabelRegularExpression. - compile(this->ExcludeLabelRegExp.c_str()); - } - if ( !this->IncludeRegExp.empty() ) - { + if (!this->IncludeLabelRegExp.empty()) { + this->IncludeLabelRegularExpression.compile( + this->IncludeLabelRegExp.c_str()); + } + if (!this->ExcludeLabelRegExp.empty()) { + this->ExcludeLabelRegularExpression.compile( + this->ExcludeLabelRegExp.c_str()); + } + if (!this->IncludeRegExp.empty()) { this->IncludeTestsRegularExpression.compile(this->IncludeRegExp.c_str()); - } - if ( !this->ExcludeRegExp.empty() ) - { + } + if (!this->ExcludeRegExp.empty()) { this->ExcludeTestsRegularExpression.compile(this->ExcludeRegExp.c_str()); - } - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Constructing a list of tests" << std::endl); - cmake cm; - cmGlobalGenerator gg; - gg.SetCMakeInstance(&cm); - cmsys::auto_ptr<cmLocalGenerator> lg(gg.CreateLocalGenerator()); - cmMakefile *mf = lg->GetMakefile(); + } + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Constructing a list of tests" << std::endl, this->Quiet); + cmake cm(cmake::RoleScript); + cm.SetHomeDirectory(""); + cm.SetHomeOutputDirectory(""); + cm.GetCurrentSnapshot().SetDefaultDefinitions(); + cmGlobalGenerator gg(&cm); + CM_AUTO_PTR<cmMakefile> mf(new cmMakefile(&gg, cm.GetCurrentSnapshot())); mf->AddDefinition("CTEST_CONFIGURATION_TYPE", - this->CTest->GetConfigType().c_str()); + this->CTest->GetConfigType().c_str()); // Add handler for ADD_TEST cmCTestAddTestCommand* newCom1 = new cmCTestAddTestCommand; newCom1->TestHandler = this; - cm.AddCommand(newCom1); + cm.GetState()->AddBuiltinCommand("add_test", newCom1); // Add handler for SUBDIRS - cmCTestSubdirCommand* newCom2 = - new cmCTestSubdirCommand; + cmCTestSubdirCommand* newCom2 = new cmCTestSubdirCommand; newCom2->TestHandler = this; - cm.AddCommand(newCom2); + cm.GetState()->AddBuiltinCommand("subdirs", newCom2); // Add handler for ADD_SUBDIRECTORY - cmCTestAddSubdirectoryCommand* newCom3 = - new cmCTestAddSubdirectoryCommand; + cmCTestAddSubdirectoryCommand* newCom3 = new cmCTestAddSubdirectoryCommand; newCom3->TestHandler = this; - cm.AddCommand(newCom3); + cm.GetState()->AddBuiltinCommand("add_subdirectory", newCom3); - // Add handler for SET_SOURCE_FILES_PROPERTIES - cmCTestSetTestsPropertiesCommand* newCom4 - = new cmCTestSetTestsPropertiesCommand; + // Add handler for SET_TESTS_PROPERTIES + cmCTestSetTestsPropertiesCommand* newCom4 = + new cmCTestSetTestsPropertiesCommand; newCom4->TestHandler = this; - cm.AddCommand(newCom4); + cm.GetState()->AddBuiltinCommand("set_tests_properties", newCom4); const char* testFilename; - if( cmSystemTools::FileExists("CTestTestfile.cmake") ) - { + if (cmSystemTools::FileExists("CTestTestfile.cmake")) { // does the CTestTestfile.cmake exist ? testFilename = "CTestTestfile.cmake"; - } - else if( cmSystemTools::FileExists("DartTestfile.txt") ) - { + } else if (cmSystemTools::FileExists("DartTestfile.txt")) { // does the DartTestfile.txt exist ? testFilename = "DartTestfile.txt"; - } - else - { + } else { return; - } + } - if ( !mf->ReadListFile(0, testFilename) ) - { + if (!mf->ReadListFile(testFilename)) { return; - } - if ( cmSystemTools::GetErrorOccuredFlag() ) - { + } + if (cmSystemTools::GetErrorOccuredFlag()) { return; - } - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Done constructing a list of tests" << std::endl); + } + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Done constructing a list of tests" << std::endl, + this->Quiet); } -//---------------------------------------------------------------------- void cmCTestTestHandler::UseIncludeRegExp() { this->UseIncludeRegExpFlag = true; } -//---------------------------------------------------------------------- void cmCTestTestHandler::UseExcludeRegExp() { this->UseExcludeRegExpFlag = true; - this->UseExcludeRegExpFirst = this->UseIncludeRegExpFlag ? false : true; + this->UseExcludeRegExpFirst = !this->UseIncludeRegExpFlag; } -//---------------------------------------------------------------------- const char* cmCTestTestHandler::GetTestStatus(int status) { - static const char statuses[][100] = { - "Not Run", - "Timeout", - "SEGFAULT", - "ILLEGAL", - "INTERRUPT", - "NUMERICAL", - "OTHER_FAULT", - "Failed", - "BAD_COMMAND", - "Completed" - }; - - if ( status < cmCTestTestHandler::NOT_RUN || - status > cmCTestTestHandler::COMPLETED ) - { + static const char* statuses[] = { "Not Run", "Timeout", "SEGFAULT", + "ILLEGAL", "INTERRUPT", "NUMERICAL", + "OTHER_FAULT", "Failed", "BAD_COMMAND", + "Completed" }; + + if (status < cmCTestTestHandler::NOT_RUN || + status > cmCTestTestHandler::COMPLETED) { return "No Status"; - } + } return statuses[status]; } -//---------------------------------------------------------------------- void cmCTestTestHandler::ExpandTestsToRunInformation(size_t numTests) { - if (this->TestsToRunString.empty()) - { + if (this->TestsToRunString.empty()) { return; - } + } int start; int end = -1; @@ -1651,311 +1719,317 @@ void cmCTestTestHandler::ExpandTestsToRunInformation(size_t numTests) std::string::size_type pos = 0; std::string::size_type pos2; // read start - if(GetNextNumber(this->TestsToRunString, start, pos, pos2)) - { + if (GetNextNumber(this->TestsToRunString, start, pos, pos2)) { // read end - if(GetNextNumber(this->TestsToRunString, end, pos, pos2)) - { + if (GetNextNumber(this->TestsToRunString, end, pos, pos2)) { // read stride - if(GetNextRealNumber(this->TestsToRunString, stride, pos, pos2)) - { - int val =0; + if (GetNextRealNumber(this->TestsToRunString, stride, pos, pos2)) { + int val = 0; // now read specific numbers - while(GetNextNumber(this->TestsToRunString, val, pos, pos2)) - { + while (GetNextNumber(this->TestsToRunString, val, pos, pos2)) { this->TestsToRun.push_back(val); - } - this->TestsToRun.push_back(val); } + this->TestsToRun.push_back(val); } } + } // if start is not specified then we assume we start at 1 - if(start == -1) - { + if (start == -1) { start = 1; - } + } // if end isnot specified then we assume we end with the last test - if(end == -1) - { + if (end == -1) { end = static_cast<int>(numTests); - } + } // if the stride wasn't specified then it defaults to 1 - if(stride == -1) - { + if (stride == -1) { stride = 1; - } + } // if we have a range then add it - if(end != -1 && start != -1 && stride > 0) - { + if (end != -1 && start != -1 && stride > 0) { int i = 0; - while (i*stride + start <= end) - { - this->TestsToRun.push_back(static_cast<int>(i*stride+start)); + while (i * stride + start <= end) { + this->TestsToRun.push_back(static_cast<int>(i * stride + start)); ++i; - } } + } // sort the array std::sort(this->TestsToRun.begin(), this->TestsToRun.end(), - std::less<int>()); + std::less<int>()); // remove duplicates std::vector<int>::iterator new_end = std::unique(this->TestsToRun.begin(), this->TestsToRun.end()); this->TestsToRun.erase(new_end, this->TestsToRun.end()); } -//---------------------------------------------------------------------- +void cmCTestTestHandler::ExpandTestsToRunInformationForRerunFailed() +{ + + std::string dirName = this->CTest->GetBinaryDir() + "/Testing/Temporary"; + + cmsys::Directory directory; + if (directory.Load(dirName) == 0) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Unable to read the contents of " + << dirName << std::endl); + return; + } + + int numFiles = + static_cast<int>(cmsys::Directory::GetNumberOfFilesInDirectory(dirName)); + std::string pattern = "LastTestsFailed"; + std::string logName; + + for (int i = 0; i < numFiles; ++i) { + std::string fileName = directory.GetFile(i); + // bcc crashes if we attempt a normal substring comparison, + // hence the following workaround + std::string fileNameSubstring = fileName.substr(0, pattern.length()); + if (fileNameSubstring != pattern) { + continue; + } + if (logName == "") { + logName = fileName; + } else { + // if multiple matching logs were found we use the most recently + // modified one. + int res; + cmSystemTools::FileTimeCompare(logName, fileName, &res); + if (res == -1) { + logName = fileName; + } + } + } + + std::string lastTestsFailedLog = + this->CTest->GetBinaryDir() + "/Testing/Temporary/" + logName; + + if (!cmSystemTools::FileExists(lastTestsFailedLog.c_str())) { + if (!this->CTest->GetShowOnly() && !this->CTest->ShouldPrintLabels()) { + cmCTestLog(this->CTest, ERROR_MESSAGE, lastTestsFailedLog + << " does not exist!" << std::endl); + } + return; + } + + // parse the list of tests to rerun from LastTestsFailed.log + cmsys::ifstream ifs(lastTestsFailedLog.c_str()); + if (ifs) { + std::string line; + std::string::size_type pos; + while (cmSystemTools::GetLineFromStream(ifs, line)) { + pos = line.find(':', 0); + if (pos == std::string::npos) { + continue; + } + + int val = atoi(line.substr(0, pos).c_str()); + this->TestsToRun.push_back(val); + } + ifs.close(); + } else if (!this->CTest->GetShowOnly() && + !this->CTest->ShouldPrintLabels()) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Problem reading file: " + << lastTestsFailedLog + << " while generating list of previously failed tests." + << std::endl); + } +} + // Just for convenience #define SPACE_REGEX "[ \t\r\n]" -//---------------------------------------------------------------------- -std::string cmCTestTestHandler::GenerateRegressionImages( - const std::string& xml) +void cmCTestTestHandler::GenerateRegressionImages(cmXMLWriter& xml, + const std::string& dart) { cmsys::RegularExpression twoattributes( - "<DartMeasurement" - SPACE_REGEX "*(name|type|encoding|compression)=\"([^\"]*)\"" - SPACE_REGEX "*(name|type|encoding|compression)=\"([^\"]*)\"" - SPACE_REGEX "*>([^<]*)</DartMeasurement>"); + "<DartMeasurement" SPACE_REGEX + "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX + "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX + "*>([^<]*)</DartMeasurement>"); cmsys::RegularExpression threeattributes( - "<DartMeasurement" - SPACE_REGEX "*(name|type|encoding|compression)=\"([^\"]*)\"" - SPACE_REGEX "*(name|type|encoding|compression)=\"([^\"]*)\"" - SPACE_REGEX "*(name|type|encoding|compression)=\"([^\"]*)\"" - SPACE_REGEX "*>([^<]*)</DartMeasurement>"); + "<DartMeasurement" SPACE_REGEX + "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX + "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX + "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX + "*>([^<]*)</DartMeasurement>"); cmsys::RegularExpression fourattributes( - "<DartMeasurement" - SPACE_REGEX "*(name|type|encoding|compression)=\"([^\"]*)\"" - SPACE_REGEX "*(name|type|encoding|compression)=\"([^\"]*)\"" - SPACE_REGEX "*(name|type|encoding|compression)=\"([^\"]*)\"" - SPACE_REGEX "*(name|type|encoding|compression)=\"([^\"]*)\"" - SPACE_REGEX "*>([^<]*)</DartMeasurement>"); + "<DartMeasurement" SPACE_REGEX + "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX + "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX + "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX + "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX + "*>([^<]*)</DartMeasurement>"); cmsys::RegularExpression cdatastart( - "<DartMeasurement" - SPACE_REGEX "*(name|type|encoding|compression)=\"([^\"]*)\"" - SPACE_REGEX "*(name|type|encoding|compression)=\"([^\"]*)\"" - SPACE_REGEX "*>" - SPACE_REGEX "*<!\\[CDATA\\["); - cmsys::RegularExpression cdataend( - "]]>" - SPACE_REGEX "*</DartMeasurement>"); + "<DartMeasurement" SPACE_REGEX + "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX + "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX + "*>" SPACE_REGEX "*<!\\[CDATA\\["); + cmsys::RegularExpression cdataend("]]>" SPACE_REGEX "*</DartMeasurement>"); cmsys::RegularExpression measurementfile( - "<DartMeasurementFile" - SPACE_REGEX "*(name|type|encoding|compression)=\"([^\"]*)\"" - SPACE_REGEX "*(name|type|encoding|compression)=\"([^\"]*)\"" - SPACE_REGEX "*>([^<]*)</DartMeasurementFile>"); + "<DartMeasurementFile" SPACE_REGEX + "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX + "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX + "*>([^<]*)</DartMeasurementFile>"); - cmOStringStream ostr; bool done = false; - std::string cxml = xml; - while ( ! done ) - { - if ( twoattributes.find(cxml) ) - { - ostr - << "\t\t\t<NamedMeasurement" - << " " << twoattributes.match(1) << "=\"" - << twoattributes.match(2) << "\"" - << " " << twoattributes.match(3) << "=\"" - << twoattributes.match(4) << "\"" - << "><Value>" << twoattributes.match(5) - << "</Value></NamedMeasurement>" - << std::endl; + std::string cxml = dart; + while (!done) { + if (twoattributes.find(cxml)) { + xml.StartElement("NamedMeasurement"); + xml.Attribute(twoattributes.match(1).c_str(), twoattributes.match(2)); + xml.Attribute(twoattributes.match(3).c_str(), twoattributes.match(4)); + xml.Element("Value", twoattributes.match(5)); + xml.EndElement(); cxml.erase(twoattributes.start(), - twoattributes.end() - twoattributes.start()); - } - else if ( threeattributes.find(cxml) ) - { - ostr - << "\t\t\t<NamedMeasurement" - << " " << threeattributes.match(1) << "=\"" - << threeattributes.match(2) << "\"" - << " " << threeattributes.match(3) << "=\"" - << threeattributes.match(4) << "\"" - << " " << threeattributes.match(5) << "=\"" - << threeattributes.match(6) << "\"" - << "><Value>" << threeattributes.match(7) - << "</Value></NamedMeasurement>" - << std::endl; + twoattributes.end() - twoattributes.start()); + } else if (threeattributes.find(cxml)) { + xml.StartElement("NamedMeasurement"); + xml.Attribute(threeattributes.match(1).c_str(), + threeattributes.match(2)); + xml.Attribute(threeattributes.match(3).c_str(), + threeattributes.match(4)); + xml.Attribute(threeattributes.match(5).c_str(), + threeattributes.match(6)); + xml.Element("Value", twoattributes.match(7)); + xml.EndElement(); cxml.erase(threeattributes.start(), - threeattributes.end() - threeattributes.start()); - } - else if ( fourattributes.find(cxml) ) - { - ostr - << "\t\t\t<NamedMeasurement" - << " " << fourattributes.match(1) << "=\"" - << fourattributes.match(2) << "\"" - << " " << fourattributes.match(3) << "=\"" - << fourattributes.match(4) << "\"" - << " " << fourattributes.match(5) << "=\"" - << fourattributes.match(6) << "\"" - << " " << fourattributes.match(7) << "=\"" - << fourattributes.match(8) << "\"" - << "><Value>" << fourattributes.match(9) - << "</Value></NamedMeasurement>" - << std::endl; + threeattributes.end() - threeattributes.start()); + } else if (fourattributes.find(cxml)) { + xml.StartElement("NamedMeasurement"); + xml.Attribute(fourattributes.match(1).c_str(), fourattributes.match(2)); + xml.Attribute(fourattributes.match(3).c_str(), fourattributes.match(4)); + xml.Attribute(fourattributes.match(5).c_str(), fourattributes.match(6)); + xml.Attribute(fourattributes.match(7).c_str(), fourattributes.match(8)); + xml.Element("Value", twoattributes.match(9)); + xml.EndElement(); cxml.erase(fourattributes.start(), - fourattributes.end() - fourattributes.start()); - } - else if ( cdatastart.find(cxml) && cdataend.find(cxml) ) - { - ostr - << "\t\t\t<NamedMeasurement" - << " " << cdatastart.match(1) << "=\"" - << cdatastart.match(2) << "\"" - << " " << cdatastart.match(3) << "=\"" - << cdatastart.match(4) << "\"" - << "><Value><![CDATA[" - << cxml.substr(cdatastart.end(), cdataend.start() - cdatastart.end()) - << "]]></Value></NamedMeasurement>" - << std::endl; - cxml.erase(cdatastart.start(), - cdataend.end() - cdatastart.start()); - } - else if ( measurementfile.find(cxml) ) - { + fourattributes.end() - fourattributes.start()); + } else if (cdatastart.find(cxml) && cdataend.find(cxml)) { + xml.StartElement("NamedMeasurement"); + xml.Attribute(cdatastart.match(1).c_str(), cdatastart.match(2)); + xml.Attribute(cdatastart.match(3).c_str(), cdatastart.match(4)); + xml.StartElement("Value"); + xml.CData( + cxml.substr(cdatastart.end(), cdataend.start() - cdatastart.end())); + xml.EndElement(); // Value + xml.EndElement(); // NamedMeasurement + cxml.erase(cdatastart.start(), cdataend.end() - cdatastart.start()); + } else if (measurementfile.find(cxml)) { const std::string& filename = cmCTest::CleanString(measurementfile.match(5)); - if ( cmSystemTools::FileExists(filename.c_str()) ) - { - long len = cmSystemTools::FileLength(filename.c_str()); - if ( len == 0 ) - { + if (cmSystemTools::FileExists(filename.c_str())) { + long len = cmSystemTools::FileLength(filename); + if (len == 0) { std::string k1 = measurementfile.match(1); std::string v1 = measurementfile.match(2); std::string k2 = measurementfile.match(3); std::string v2 = measurementfile.match(4); - if ( cmSystemTools::LowerCase(k1) == "type" ) - { + if (cmSystemTools::LowerCase(k1) == "type") { v1 = "text/string"; - } - if ( cmSystemTools::LowerCase(k2) == "type" ) - { + } + if (cmSystemTools::LowerCase(k2) == "type") { v2 = "text/string"; - } - - ostr - << "\t\t\t<NamedMeasurement" - << " " << k1 << "=\"" << v1 << "\"" - << " " << k2 << "=\"" << v2 << "\"" - << " encoding=\"none\"" - << "><Value>Image " << filename.c_str() - << " is empty</Value></NamedMeasurement>"; } - else - { - std::ifstream ifs(filename.c_str(), std::ios::in + + xml.StartElement("NamedMeasurement"); + xml.Attribute(k1.c_str(), v1); + xml.Attribute(k2.c_str(), v2); + xml.Attribute("encoding", "none"); + xml.Element("Value", "Image " + filename + " is empty"); + xml.EndElement(); + } else { + cmsys::ifstream ifs(filename.c_str(), std::ios::in #ifdef _WIN32 - | std::ios::binary + | std::ios::binary #endif - ); - unsigned char *file_buffer = new unsigned char [ len + 1 ]; + ); + unsigned char* file_buffer = new unsigned char[len + 1]; ifs.read(reinterpret_cast<char*>(file_buffer), len); - unsigned char *encoded_buffer - = new unsigned char [ static_cast<int>( - static_cast<double>(len) * 1.5 + 5.0) ]; - - unsigned long rlen - = cmsysBase64_Encode(file_buffer, len, encoded_buffer, 1); - unsigned long cc; - - ostr - << "\t\t\t<NamedMeasurement" - << " " << measurementfile.match(1) << "=\"" - << measurementfile.match(2) << "\"" - << " " << measurementfile.match(3) << "=\"" - << measurementfile.match(4) << "\"" - << " encoding=\"base64\"" - << ">" << std::endl << "\t\t\t\t<Value>"; - for ( cc = 0; cc < rlen; cc ++ ) - { + unsigned char* encoded_buffer = new unsigned char[static_cast<int>( + static_cast<double>(len) * 1.5 + 5.0)]; + + size_t rlen = + cmsysBase64_Encode(file_buffer, len, encoded_buffer, 1); + + xml.StartElement("NamedMeasurement"); + xml.Attribute(measurementfile.match(1).c_str(), + measurementfile.match(2)); + xml.Attribute(measurementfile.match(3).c_str(), + measurementfile.match(4)); + xml.Attribute("encoding", "base64"); + std::ostringstream ostr; + for (size_t cc = 0; cc < rlen; cc++) { ostr << encoded_buffer[cc]; - if ( cc % 60 == 0 && cc ) - { + if (cc % 60 == 0 && cc) { ostr << std::endl; - } } - ostr - << "</Value>" << std::endl << "\t\t\t</NamedMeasurement>" - << std::endl; - delete [] file_buffer; - delete [] encoded_buffer; } + xml.Element("Value", ostr.str()); + xml.EndElement(); // NamedMeasurement + delete[] file_buffer; + delete[] encoded_buffer; } - else - { + } else { int idx = 4; - if ( measurementfile.match(1) == "name" ) - { + if (measurementfile.match(1) == "name") { idx = 2; - } - ostr - << "\t\t\t<NamedMeasurement" - << " name=\"" << measurementfile.match(idx) << "\"" - << " text=\"text/string\"" - << "><Value>File " << filename.c_str() - << " not found</Value></NamedMeasurement>" - << std::endl; - cmCTestLog(this->CTest, HANDLER_OUTPUT, "File \"" << filename.c_str() - << "\" not found." << std::endl); } - cxml.erase(measurementfile.start(), - measurementfile.end() - measurementfile.start()); + xml.StartElement("NamedMeasurement"); + xml.Attribute("name", measurementfile.match(idx)); + xml.Attribute("text", "text/string"); + xml.Element("Value", "File " + filename + " not found"); + xml.EndElement(); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "File \"" + << filename << "\" not found." << std::endl, + this->Quiet); } - else - { + cxml.erase(measurementfile.start(), + measurementfile.end() - measurementfile.start()); + } else { done = true; - } } - return ostr.str(); + } } -//---------------------------------------------------------------------- -void cmCTestTestHandler::SetIncludeRegExp(const char *arg) +void cmCTestTestHandler::SetIncludeRegExp(const char* arg) { this->IncludeRegExp = arg; } -//---------------------------------------------------------------------- -void cmCTestTestHandler::SetExcludeRegExp(const char *arg) +void cmCTestTestHandler::SetExcludeRegExp(const char* arg) { this->ExcludeRegExp = arg; } -//---------------------------------------------------------------------- void cmCTestTestHandler::SetTestsToRunInformation(const char* in) { - if ( !in ) - { + if (!in) { return; - } + } this->TestsToRunString = in; // if the argument is a file, then read it and use the contents as the // string - if(cmSystemTools::FileExists(in)) - { - std::ifstream fin(in); + if (cmSystemTools::FileExists(in)) { + cmsys::ifstream fin(in); unsigned long filelen = cmSystemTools::FileLength(in); - char* buff = new char[filelen+1]; + char* buff = new char[filelen + 1]; fin.getline(buff, filelen); buff[fin.gcount()] = 0; this->TestsToRunString = buff; - delete [] buff; - } + delete[] buff; + } } -//---------------------------------------------------------------------------- bool cmCTestTestHandler::CleanTestOutput(std::string& output, size_t length) { - if(!length || length >= output.size() || - output.find("CTEST_FULL_OUTPUT") != output.npos) - { + if (!length || length >= output.size() || + output.find("CTEST_FULL_OUTPUT") != std::string::npos) { return true; - } + } // Truncate at given length but do not break in the middle of a multi-byte // UTF-8 encoding. @@ -1963,303 +2037,260 @@ bool cmCTestTestHandler::CleanTestOutput(std::string& output, size_t length) char const* const end = begin + output.size(); char const* const truncate = begin + length; char const* current = begin; - while(current < truncate) - { + while (current < truncate) { unsigned int ch; - if(const char* next = cm_utf8_decode_character(current, end, &ch)) - { - if(next > truncate) - { + if (const char* next = cm_utf8_decode_character(current, end, &ch)) { + if (next > truncate) { break; - } - current = next; } - else // Bad byte will be handled by cmXMLSafe. - { + current = next; + } else // Bad byte will be handled by cmXMLWriter. + { ++current; - } } + } output = output.substr(0, current - begin); // Append truncation message. - cmOStringStream msg; + std::ostringstream msg; msg << "...\n" - "The rest of the test output was removed since it exceeds the threshold " - "of " << length << " bytes.\n"; + "The rest of the test output was removed since it exceeds the " + "threshold " + "of " + << length << " bytes.\n"; output += msg.str(); return true; } -//---------------------------------------------------------------------- bool cmCTestTestHandler::SetTestsProperties( const std::vector<std::string>& args) { std::vector<std::string>::const_iterator it; - std::vector<cmStdString> tests; + std::vector<std::string> tests; bool found = false; - for ( it = args.begin(); it != args.end(); ++ it ) - { - if ( *it == "PROPERTIES" ) - { + for (it = args.begin(); it != args.end(); ++it) { + if (*it == "PROPERTIES") { found = true; break; - } - tests.push_back(*it); } - if ( !found ) - { + tests.push_back(*it); + } + if (!found) { return false; - } - ++ it; // skip PROPERTIES - for ( ; it != args.end(); ++ it ) - { + } + ++it; // skip PROPERTIES + for (; it != args.end(); ++it) { std::string key = *it; - ++ it; - if ( it == args.end() ) - { + ++it; + if (it == args.end()) { break; - } + } std::string val = *it; - std::vector<cmStdString>::const_iterator tit; - for ( tit = tests.begin(); tit != tests.end(); ++ tit ) - { + std::vector<std::string>::const_iterator tit; + for (tit = tests.begin(); tit != tests.end(); ++tit) { cmCTestTestHandler::ListOfTests::iterator rtit; - for ( rtit = this->TestList.begin(); - rtit != this->TestList.end(); - ++ rtit ) - { - if ( *tit == rtit->Name ) - { - if ( key == "WILL_FAIL" ) - { + for (rtit = this->TestList.begin(); rtit != this->TestList.end(); + ++rtit) { + if (*tit == rtit->Name) { + if (key == "WILL_FAIL") { rtit->WillFail = cmSystemTools::IsOn(val.c_str()); - } - if ( key == "ATTACHED_FILES" ) - { + } + if (key == "DISABLED") { + rtit->Disabled = cmSystemTools::IsOn(val.c_str()); + } + if (key == "ATTACHED_FILES") { + cmSystemTools::ExpandListArgument(val, rtit->AttachedFiles); + } + if (key == "ATTACHED_FILES_ON_FAIL") { + cmSystemTools::ExpandListArgument(val, rtit->AttachOnFail); + } + if (key == "RESOURCE_LOCK") { std::vector<std::string> lval; - cmSystemTools::ExpandListArgument(val.c_str(), lval); + cmSystemTools::ExpandListArgument(val, lval); - for(std::vector<std::string>::iterator f = lval.begin(); - f != lval.end(); ++f) - { - rtit->AttachedFiles.push_back(*f); - } - } - if ( key == "ATTACHED_FILES_ON_FAIL" ) - { + rtit->LockedResources.insert(lval.begin(), lval.end()); + } + if (key == "FIXTURES_SETUP") { std::vector<std::string> lval; - cmSystemTools::ExpandListArgument(val.c_str(), lval); + cmSystemTools::ExpandListArgument(val, lval); - for(std::vector<std::string>::iterator f = lval.begin(); - f != lval.end(); ++f) - { - rtit->AttachOnFail.push_back(*f); - } - } - if ( key == "RESOURCE_LOCK" ) - { + rtit->FixturesSetup.insert(lval.begin(), lval.end()); + } + if (key == "FIXTURES_CLEANUP") { std::vector<std::string> lval; - cmSystemTools::ExpandListArgument(val.c_str(), lval); + cmSystemTools::ExpandListArgument(val, lval); - for(std::vector<std::string>::iterator f = lval.begin(); - f != lval.end(); ++f) - { - rtit->LockedResources.insert(*f); - } - } - if ( key == "TIMEOUT" ) - { + rtit->FixturesCleanup.insert(lval.begin(), lval.end()); + } + if (key == "FIXTURES_REQUIRED") { + std::vector<std::string> lval; + cmSystemTools::ExpandListArgument(val, lval); + + rtit->FixturesRequired.insert(lval.begin(), lval.end()); + } + if (key == "TIMEOUT") { rtit->Timeout = atof(val.c_str()); rtit->ExplicitTimeout = true; - } - if ( key == "COST" ) - { + } + if (key == "COST") { rtit->Cost = static_cast<float>(atof(val.c_str())); - } - if ( key == "REQUIRED_FILES" ) - { - std::vector<std::string> lval; - cmSystemTools::ExpandListArgument(val.c_str(), lval); - - for(std::vector<std::string>::iterator f = lval.begin(); - f != lval.end(); ++f) - { - rtit->RequiredFiles.push_back(*f); - } - } - if ( key == "RUN_SERIAL" ) - { + } + if (key == "REQUIRED_FILES") { + cmSystemTools::ExpandListArgument(val, rtit->RequiredFiles); + } + if (key == "RUN_SERIAL") { rtit->RunSerial = cmSystemTools::IsOn(val.c_str()); - } - if ( key == "FAIL_REGULAR_EXPRESSION" ) - { + } + if (key == "FAIL_REGULAR_EXPRESSION") { std::vector<std::string> lval; - cmSystemTools::ExpandListArgument(val.c_str(), lval); + cmSystemTools::ExpandListArgument(val, lval); std::vector<std::string>::iterator crit; - for ( crit = lval.begin(); crit != lval.end(); ++ crit ) - { + for (crit = lval.begin(); crit != lval.end(); ++crit) { rtit->ErrorRegularExpressions.push_back( std::pair<cmsys::RegularExpression, std::string>( cmsys::RegularExpression(crit->c_str()), - std::string(crit->c_str()))); - } + std::string(*crit))); } - if ( key == "PROCESSORS" ) - { + } + if (key == "PROCESSORS") { rtit->Processors = atoi(val.c_str()); - if(rtit->Processors < 1) - { + if (rtit->Processors < 1) { rtit->Processors = 1; - } - } - if ( key == "DEPENDS" ) - { - std::vector<std::string> lval; - cmSystemTools::ExpandListArgument(val.c_str(), lval); - std::vector<std::string>::iterator crit; - for ( crit = lval.begin(); crit != lval.end(); ++ crit ) - { - rtit->Depends.push_back(*crit); - } } - if ( key == "ENVIRONMENT" ) - { - std::vector<std::string> lval; - cmSystemTools::ExpandListArgument(val.c_str(), lval); - std::vector<std::string>::iterator crit; - for ( crit = lval.begin(); crit != lval.end(); ++ crit ) - { - rtit->Environment.push_back(*crit); - } - } - if ( key == "LABELS" ) - { - std::vector<std::string> lval; - cmSystemTools::ExpandListArgument(val.c_str(), lval); - std::vector<std::string>::iterator crit; - for ( crit = lval.begin(); crit != lval.end(); ++ crit ) - { - rtit->Labels.push_back(*crit); - } + } + if (key == "SKIP_RETURN_CODE") { + rtit->SkipReturnCode = atoi(val.c_str()); + if (rtit->SkipReturnCode < 0 || rtit->SkipReturnCode > 255) { + rtit->SkipReturnCode = -1; } - if ( key == "MEASUREMENT" ) - { - size_t pos = val.find_first_of("="); - if ( pos != val.npos ) - { + } + if (key == "DEPENDS") { + cmSystemTools::ExpandListArgument(val, rtit->Depends); + } + if (key == "ENVIRONMENT") { + cmSystemTools::ExpandListArgument(val, rtit->Environment); + } + if (key == "LABELS") { + cmSystemTools::ExpandListArgument(val, rtit->Labels); + } + if (key == "MEASUREMENT") { + size_t pos = val.find_first_of('='); + if (pos != std::string::npos) { std::string mKey = val.substr(0, pos); const char* mVal = val.c_str() + pos + 1; rtit->Measurements[mKey] = mVal; - } - else - { + } else { rtit->Measurements[val] = "1"; - } } - if ( key == "PASS_REGULAR_EXPRESSION" ) - { + } + if (key == "PASS_REGULAR_EXPRESSION") { std::vector<std::string> lval; - cmSystemTools::ExpandListArgument(val.c_str(), lval); + cmSystemTools::ExpandListArgument(val, lval); std::vector<std::string>::iterator crit; - for ( crit = lval.begin(); crit != lval.end(); ++ crit ) - { + for (crit = lval.begin(); crit != lval.end(); ++crit) { rtit->RequiredRegularExpressions.push_back( std::pair<cmsys::RegularExpression, std::string>( cmsys::RegularExpression(crit->c_str()), - std::string(crit->c_str()))); - } + std::string(*crit))); } - if ( key == "WORKING_DIRECTORY" ) - { + } + if (key == "WORKING_DIRECTORY") { rtit->Directory = val; + } + if (key == "TIMEOUT_AFTER_MATCH") { + std::vector<std::string> propArgs; + cmSystemTools::ExpandListArgument(val, propArgs); + if (propArgs.size() != 2) { + cmCTestLog(this->CTest, WARNING, + "TIMEOUT_AFTER_MATCH expects two arguments, found " + << propArgs.size() << std::endl); + } else { + rtit->AlternateTimeout = atof(propArgs[0].c_str()); + std::vector<std::string> lval; + cmSystemTools::ExpandListArgument(propArgs[1], lval); + std::vector<std::string>::iterator crit; + for (crit = lval.begin(); crit != lval.end(); ++crit) { + rtit->TimeoutRegularExpressions.push_back( + std::pair<cmsys::RegularExpression, std::string>( + cmsys::RegularExpression(crit->c_str()), + std::string(*crit))); + } } } } } } + } return true; } -//---------------------------------------------------------------------- bool cmCTestTestHandler::AddTest(const std::vector<std::string>& args) { const std::string& testname = args[0]; - cmCTestLog(this->CTest, DEBUG, "Add test: " << args[0] << std::endl); + cmCTestOptionalLog(this->CTest, DEBUG, "Add test: " << args[0] << std::endl, + this->Quiet); - if (this->UseExcludeRegExpFlag && - this->UseExcludeRegExpFirst && - this->ExcludeTestsRegularExpression.find(testname.c_str())) - { + if (this->UseExcludeRegExpFlag && this->UseExcludeRegExpFirst && + this->ExcludeTestsRegularExpression.find(testname.c_str())) { return true; - } - if ( this->MemCheck ) - { - std::vector<cmStdString>::iterator it; + } + if (this->MemCheck) { + std::vector<std::string>::iterator it; bool found = false; - for ( it = this->CustomTestsIgnore.begin(); - it != this->CustomTestsIgnore.end(); ++ it ) - { - if ( *it == testname ) - { + for (it = this->CustomTestsIgnore.begin(); + it != this->CustomTestsIgnore.end(); ++it) { + if (*it == testname) { found = true; break; - } } - if ( found ) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Ignore memcheck: " - << *it << std::endl); + } + if (found) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Ignore memcheck: " << *it << std::endl, this->Quiet); return true; - } } - else - { - std::vector<cmStdString>::iterator it; + } else { + std::vector<std::string>::iterator it; bool found = false; - for ( it = this->CustomTestsIgnore.begin(); - it != this->CustomTestsIgnore.end(); ++ it ) - { - if ( *it == testname ) - { + for (it = this->CustomTestsIgnore.begin(); + it != this->CustomTestsIgnore.end(); ++it) { + if (*it == testname) { found = true; break; - } } - if ( found ) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Ignore test: " - << *it << std::endl); + } + if (found) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Ignore test: " << *it << std::endl, this->Quiet); return true; - } } + } cmCTestTestProperties test; test.Name = testname; test.Args = args; test.Directory = cmSystemTools::GetCurrentWorkingDirectory(); - cmCTestLog(this->CTest, DEBUG, "Set test directory: " - << test.Directory << std::endl); + cmCTestOptionalLog(this->CTest, DEBUG, + "Set test directory: " << test.Directory << std::endl, + this->Quiet); test.IsInBasedOnREOptions = true; test.WillFail = false; + test.Disabled = false; test.RunSerial = false; test.Timeout = 0; test.ExplicitTimeout = false; test.Cost = 0; test.Processors = 1; + test.SkipReturnCode = -1; test.PreviousRuns = 0; if (this->UseIncludeRegExpFlag && - !this->IncludeTestsRegularExpression.find(testname.c_str())) - { + !this->IncludeTestsRegularExpression.find(testname.c_str())) { test.IsInBasedOnREOptions = false; - } - else if (this->UseExcludeRegExpFlag && - !this->UseExcludeRegExpFirst && - this->ExcludeTestsRegularExpression.find(testname.c_str())) - { + } else if (this->UseExcludeRegExpFlag && !this->UseExcludeRegExpFirst && + this->ExcludeTestsRegularExpression.find(testname.c_str())) { test.IsInBasedOnREOptions = false; - } + } this->TestList.push_back(test); return true; } - diff --git a/Source/CTest/cmCTestTestHandler.h b/Source/CTest/cmCTestTestHandler.h index 93b793b20..0edcb143a 100644 --- a/Source/CTest/cmCTestTestHandler.h +++ b/Source/CTest/cmCTestTestHandler.h @@ -1,23 +1,24 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestTestHandler_h #define cmCTestTestHandler_h +#include "cmConfigure.h" #include "cmCTestGenericHandler.h" -#include <cmsys/RegularExpression.hxx> +#include "cmsys/RegularExpression.hxx" +#include <iosfwd> +#include <map> +#include <set> +#include <stddef.h> +#include <string> +#include <utility> +#include <vector> + +class cmCTest; class cmMakefile; +class cmXMLWriter; /** \class cmCTestTestHandler * \brief A class that handles ctest -S invocations @@ -28,13 +29,14 @@ class cmCTestTestHandler : public cmCTestGenericHandler friend class cmCTestRunTest; friend class cmCTestMultiProcessHandler; friend class cmCTestBatchTestHandler; + public: - cmTypeMacro(cmCTestTestHandler, cmCTestGenericHandler); + typedef cmCTestGenericHandler Superclass; /** * The main entry point for this class */ - int ProcessHandler(); + int ProcessHandler() CM_OVERRIDE; /** * When both -R and -I are used should te resulting test list be the @@ -44,19 +46,34 @@ public: void SetUseUnion(bool val) { this->UseUnion = val; } /** + * Set whether or not CTest should only execute the tests that failed + * on the previous run. By default this is false. + */ + void SetRerunFailed(bool val) { this->RerunFailed = val; } + + /** * This method is called when reading CTest custom file */ - void PopulateCustomVectors(cmMakefile *mf); + void PopulateCustomVectors(cmMakefile* mf) CM_OVERRIDE; ///! Control the use of the regular expresisons, call these methods to turn - ///them on + /// them on void UseIncludeRegExp(); void UseExcludeRegExp(); - void SetIncludeRegExp(const char *); - void SetExcludeRegExp(const char *); + void SetIncludeRegExp(const char*); + void SetExcludeRegExp(const char*); - void SetMaxIndex(int n) {this->MaxIndex = n;} - int GetMaxIndex() {return this->MaxIndex;} + void SetMaxIndex(int n) { this->MaxIndex = n; } + int GetMaxIndex() { return this->MaxIndex; } + + void SetTestOutputSizePassed(int n) + { + this->CustomMaximumPassedTestOutputSize = n; + } + void SetTestOutputSizeFailed(int n) + { + this->CustomMaximumFailedTestOutputSize = n; + } ///! pass the -I argument down void SetTestsToRunInformation(const char*); @@ -73,7 +90,7 @@ public: */ bool SetTestsProperties(const std::vector<std::string>& args); - void Initialize(); + void Initialize() CM_OVERRIDE; // NOTE: This struct is Saved/Restored // in cmCTestTestHandler, if you add to this class @@ -81,31 +98,41 @@ public: // ctest -j N will break for that feature struct cmCTestTestProperties { - cmStdString Name; - cmStdString Directory; + std::string Name; + std::string Directory; std::vector<std::string> Args; std::vector<std::string> RequiredFiles; std::vector<std::string> Depends; std::vector<std::string> AttachedFiles; std::vector<std::string> AttachOnFail; - std::vector<std::pair<cmsys::RegularExpression, - std::string> > ErrorRegularExpressions; - std::vector<std::pair<cmsys::RegularExpression, - std::string> > RequiredRegularExpressions; - std::map<cmStdString, cmStdString> Measurements; + std::vector<std::pair<cmsys::RegularExpression, std::string> > + ErrorRegularExpressions; + std::vector<std::pair<cmsys::RegularExpression, std::string> > + RequiredRegularExpressions; + std::vector<std::pair<cmsys::RegularExpression, std::string> > + TimeoutRegularExpressions; + std::map<std::string, std::string> Measurements; bool IsInBasedOnREOptions; bool WillFail; + bool Disabled; float Cost; int PreviousRuns; bool RunSerial; double Timeout; bool ExplicitTimeout; + double AlternateTimeout; int Index; - //Requested number of process slots + // Requested number of process slots int Processors; + // return code of test which will mark test as "not run" + int SkipReturnCode; std::vector<std::string> Environment; std::vector<std::string> Labels; std::set<std::string> LockedResources; + std::set<std::string> FixturesSetup; + std::set<std::string> FixturesCleanup; + std::set<std::string> FixturesRequired; + std::set<std::string> RequireSuccessDepends; }; struct cmCTestTestResult @@ -114,72 +141,73 @@ public: std::string Path; std::string Reason; std::string FullCommandLine; - double ExecutionTime; - int ReturnValue; - int Status; - bool CompressOutput; + double ExecutionTime; + int ReturnValue; + int Status; + bool CompressOutput; std::string CompletionStatus; std::string Output; - std::string RegressionImages; - int TestCount; + std::string DartString; + int TestCount; cmCTestTestProperties* Properties; }; struct cmCTestTestResultLess { - bool operator() (const cmCTestTestResult &lhs, - const cmCTestTestResult &rhs) const + bool operator()(const cmCTestTestResult& lhs, + const cmCTestTestResult& rhs) const { - return lhs.TestCount < rhs.TestCount; + return lhs.TestCount < rhs.TestCount; } }; // add configurations to a search path for an executable - static void AddConfigurations(cmCTest *ctest, - std::vector<std::string> &attempted, - std::vector<std::string> &attemptedConfigs, - std::string filepath, - std::string &filename); + static void AddConfigurations(cmCTest* ctest, + std::vector<std::string>& attempted, + std::vector<std::string>& attemptedConfigs, + std::string filepath, std::string& filename); // full signature static method to find an executable - static std::string FindExecutable(cmCTest *ctest, - const char *testCommand, - std::string &resultingConfig, - std::vector<std::string> &extraPaths, - std::vector<std::string> &failed); + static std::string FindExecutable(cmCTest* ctest, const char* testCommand, + std::string& resultingConfig, + std::vector<std::string>& extraPaths, + std::vector<std::string>& failed); typedef std::vector<cmCTestTestProperties> ListOfTests; + protected: // compute a final test list virtual int PreProcessHandler(); virtual int PostProcessHandler(); virtual void GenerateTestCommand(std::vector<std::string>& args, int test); - int ExecuteCommands(std::vector<cmStdString>& vec); + int ExecuteCommands(std::vector<std::string>& vec); - void WriteTestResultHeader(std::ostream& os, cmCTestTestResult* result); - void WriteTestResultFooter(std::ostream& os, cmCTestTestResult* result); + void WriteTestResultHeader(cmXMLWriter& xml, cmCTestTestResult* result); + void WriteTestResultFooter(cmXMLWriter& xml, cmCTestTestResult* result); // Write attached test files into the xml - void AttachFiles(std::ostream& os, cmCTestTestResult* result); + void AttachFiles(cmXMLWriter& xml, cmCTestTestResult* result); //! Clean test output to specified length bool CleanTestOutput(std::string& output, size_t length); - double ElapsedTestingTime; + double ElapsedTestingTime; typedef std::vector<cmCTestTestResult> TestResultsVector; - TestResultsVector TestResults; + TestResultsVector TestResults; - std::vector<cmStdString> CustomTestsIgnore; - std::string StartTest; - std::string EndTest; - unsigned int StartTestTime; - unsigned int EndTestTime; + std::vector<std::string> CustomTestsIgnore; + std::string StartTest; + std::string EndTest; + unsigned int StartTestTime; + unsigned int EndTestTime; bool MemCheck; int CustomMaximumPassedTestOutputSize; int CustomMaximumFailedTestOutputSize; int MaxIndex; + public: - enum { // Program statuses + enum + { // Program statuses NOT_RUN = 0, TIMEOUT, SEGFAULT, @@ -196,14 +224,14 @@ private: /** * Generate the Dart compatible output */ - virtual void GenerateDartOutput(std::ostream& os); + virtual void GenerateDartOutput(cmXMLWriter& xml); void PrintLabelSummary(); /** * Run the tests for a directory and any subdirectories */ - void ProcessDirectory(std::vector<cmStdString> &passed, - std::vector<cmStdString> &failed); + void ProcessDirectory(std::vector<std::string>& passed, + std::vector<std::string>& failed); /** * Get the list of tests in directory and subdirectories. @@ -213,33 +241,35 @@ private: // based on union regex and -I stuff void ComputeTestList(); - bool GetValue(const char* tag, - std::string& value, - std::ifstream& fin); - bool GetValue(const char* tag, - int& value, - std::ifstream& fin); - bool GetValue(const char* tag, - size_t& value, - std::ifstream& fin); - bool GetValue(const char* tag, - bool& value, - std::ifstream& fin); - bool GetValue(const char* tag, - double& value, - std::ifstream& fin); + // compute the lists of tests that will actually run + // based on LastTestFailed.log + void ComputeTestListForRerunFailed(); + + // add required setup/cleanup tests not already in the + // list of tests to be run and update dependencies between + // tests to account for fixture setup/cleanup + void UpdateForFixtures(ListOfTests& tests) const; + + void UpdateMaxTestNameWidth(); + + bool GetValue(const char* tag, std::string& value, std::istream& fin); + bool GetValue(const char* tag, int& value, std::istream& fin); + bool GetValue(const char* tag, size_t& value, std::istream& fin); + bool GetValue(const char* tag, bool& value, std::istream& fin); + bool GetValue(const char* tag, double& value, std::istream& fin); /** * Find the executable for a test */ - std::string FindTheExecutable(const char *exe); + std::string FindTheExecutable(const char* exe); const char* GetTestStatus(int status); void ExpandTestsToRunInformation(size_t numPossibleTests); + void ExpandTestsToRunInformationForRerunFailed(); - std::vector<cmStdString> CustomPreTest; - std::vector<cmStdString> CustomPostTest; + std::vector<std::string> CustomPreTest; + std::vector<std::string> CustomPostTest; - std::vector<int> TestsToRun; + std::vector<int> TestsToRun; bool UseIncludeLabelRegExpFlag; bool UseExcludeLabelRegExpFlag; @@ -250,12 +280,15 @@ private: std::string ExcludeLabelRegExp; std::string IncludeRegExp; std::string ExcludeRegExp; + std::string ExcludeFixtureRegExp; + std::string ExcludeFixtureSetupRegExp; + std::string ExcludeFixtureCleanupRegExp; cmsys::RegularExpression IncludeLabelRegularExpression; cmsys::RegularExpression ExcludeLabelRegularExpression; cmsys::RegularExpression IncludeTestsRegularExpression; cmsys::RegularExpression ExcludeTestsRegularExpression; - std::string GenerateRegressionImages(const std::string& xml); + void GenerateRegressionImages(cmXMLWriter& xml, const std::string& dart); cmsys::RegularExpression DartStuff1; void CheckLabelFilter(cmCTestTestProperties& it); void CheckLabelFilterExclude(cmCTestTestProperties& it); @@ -268,6 +301,8 @@ private: cmsys::RegularExpression DartStuff; std::ostream* LogFile; + + bool RerunFailed; }; #endif diff --git a/Source/CTest/cmCTestUpdateCommand.cxx b/Source/CTest/cmCTestUpdateCommand.cxx index 2ca9f6c86..8c037545f 100644 --- a/Source/CTest/cmCTestUpdateCommand.cxx +++ b/Source/CTest/cmCTestUpdateCommand.cxx @@ -1,80 +1,91 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestUpdateCommand.h" #include "cmCTest.h" #include "cmCTestGenericHandler.h" +#include "cmMakefile.h" +#include "cmSystemTools.h" + +#include <vector> cmCTestGenericHandler* cmCTestUpdateCommand::InitializeHandler() { - if ( this->Values[ct_SOURCE] ) - { - this->CTest->SetCTestConfiguration("SourceDirectory", + if (this->Values[ct_SOURCE]) { + this->CTest->SetCTestConfiguration( + "SourceDirectory", + cmSystemTools::CollapseFullPath(this->Values[ct_SOURCE]).c_str(), + this->Quiet); + } else { + this->CTest->SetCTestConfiguration( + "SourceDirectory", cmSystemTools::CollapseFullPath( - this->Values[ct_SOURCE]).c_str()); - } - else - { - this->CTest->SetCTestConfiguration("SourceDirectory", - cmSystemTools::CollapseFullPath( - this->Makefile->GetDefinition("CTEST_SOURCE_DIRECTORY")).c_str()); - } - std::string source_dir - = this->CTest->GetCTestConfiguration("SourceDirectory"); + this->Makefile->GetDefinition("CTEST_SOURCE_DIRECTORY")) + .c_str(), + this->Quiet); + } + std::string source_dir = + this->CTest->GetCTestConfiguration("SourceDirectory"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "UpdateCommand", "CTEST_UPDATE_COMMAND"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "UpdateOptions", "CTEST_UPDATE_OPTIONS"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "CVSCommand", "CTEST_CVS_COMMAND"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "CVSUpdateOptions", "CTEST_CVS_UPDATE_OPTIONS"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "SVNCommand", "CTEST_SVN_COMMAND"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "SVNUpdateOptions", "CTEST_SVN_UPDATE_OPTIONS"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "SVNOptions", "CTEST_SVN_OPTIONS"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "BZRCommand", "CTEST_BZR_COMMAND"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "BZRUpdateOptions", "CTEST_BZR_UPDATE_OPTIONS"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "GITCommand", "CTEST_GIT_COMMAND"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "GITUpdateOptions", "CTEST_GIT_UPDATE_OPTIONS"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "GITUpdateCustom", "CTEST_GIT_UPDATE_CUSTOM"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "HGCommand", "CTEST_HG_COMMAND"); - this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, - "HGUpdateOptions", "CTEST_HG_UPDATE_OPTIONS"); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "UpdateCommand", "CTEST_UPDATE_COMMAND", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "UpdateOptions", "CTEST_UPDATE_OPTIONS", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "CVSCommand", "CTEST_CVS_COMMAND", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "CVSUpdateOptions", "CTEST_CVS_UPDATE_OPTIONS", + this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "SVNCommand", "CTEST_SVN_COMMAND", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "SVNUpdateOptions", "CTEST_SVN_UPDATE_OPTIONS", + this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "SVNOptions", "CTEST_SVN_OPTIONS", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "BZRCommand", "CTEST_BZR_COMMAND", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "BZRUpdateOptions", "CTEST_BZR_UPDATE_OPTIONS", + this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "GITCommand", "CTEST_GIT_COMMAND", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "GITUpdateOptions", "CTEST_GIT_UPDATE_OPTIONS", + this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "GITInitSubmodules", "CTEST_GIT_INIT_SUBMODULES", + this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "GITUpdateCustom", "CTEST_GIT_UPDATE_CUSTOM", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "UpdateVersionOnly", "CTEST_UPDATE_VERSION_ONLY", + this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "HGCommand", "CTEST_HG_COMMAND", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "HGUpdateOptions", "CTEST_HG_UPDATE_OPTIONS", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "P4Command", "CTEST_P4_COMMAND", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "P4UpdateOptions", "CTEST_P4_UPDATE_OPTIONS", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "P4Client", "CTEST_P4_CLIENT", this->Quiet); + this->CTest->SetCTestConfigurationFromCMakeVariable( + this->Makefile, "P4Options", "CTEST_P4_OPTIONS", this->Quiet); - cmCTestGenericHandler* handler - = this->CTest->GetInitializedHandler("update"); - if ( !handler ) - { + cmCTestGenericHandler* handler = + this->CTest->GetInitializedHandler("update"); + if (!handler) { this->SetError("internal CTest error. Cannot instantiate update handler"); - return 0; - } + return CM_NULLPTR; + } handler->SetCommand(this); - if ( source_dir.empty() ) - { + if (source_dir.empty()) { this->SetError("source directory not specified. Please use SOURCE tag"); - return 0; - } + return CM_NULLPTR; + } handler->SetOption("SourceDirectory", source_dir.c_str()); + handler->SetQuiet(this->Quiet); return handler; } - - diff --git a/Source/CTest/cmCTestUpdateCommand.h b/Source/CTest/cmCTestUpdateCommand.h index c578fff6f..3b8f0a641 100644 --- a/Source/CTest/cmCTestUpdateCommand.h +++ b/Source/CTest/cmCTestUpdateCommand.h @@ -1,19 +1,17 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestUpdateCommand_h #define cmCTestUpdateCommand_h +#include "cmConfigure.h" + #include "cmCTestHandlerCommand.h" +#include <string> + +class cmCTestGenericHandler; +class cmCommand; + /** \class cmCTestUpdate * \brief Run a ctest script * @@ -22,52 +20,26 @@ class cmCTestUpdateCommand : public cmCTestHandlerCommand { public: - cmCTestUpdateCommand() {} /** * This is a virtual constructor for the command. */ - virtual cmCommand* Clone() - { + cmCommand* Clone() CM_OVERRIDE + { cmCTestUpdateCommand* ni = new cmCTestUpdateCommand; ni->CTest = this->CTest; ni->CTestScriptHandler = this->CTestScriptHandler; return ni; - } + } /** * The name of the command as specified in CMakeList.txt. */ - virtual const char* GetName() const { return "ctest_update";} - - /** - * Succinct documentation. - */ - virtual const char* GetTerseDocumentation() const - { - return "Update the work tree from version control."; - } - - /** - * More documentation. - */ - virtual const char* GetFullDocumentation() const - { - return - " ctest_update([SOURCE source] [RETURN_VALUE res])\n" - "Updates the given source directory and stores results in Update.xml. " - "If no SOURCE is given, the CTEST_SOURCE_DIRECTORY variable is used. " - "The RETURN_VALUE option specifies a variable in which to store the " - "result, which is the number of files updated or -1 on error." - ; - } - - cmTypeMacro(cmCTestUpdateCommand, cmCTestHandlerCommand); + std::string GetName() const CM_OVERRIDE { return "ctest_update"; } protected: - cmCTestGenericHandler* InitializeHandler(); + cmCTestGenericHandler* InitializeHandler() CM_OVERRIDE; }; - #endif diff --git a/Source/CTest/cmCTestUpdateHandler.cxx b/Source/CTest/cmCTestUpdateHandler.cxx index 9eae3f3ac..e08a9b7bd 100644 --- a/Source/CTest/cmCTestUpdateHandler.cxx +++ b/Source/CTest/cmCTestUpdateHandler.cxx @@ -1,115 +1,41 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestUpdateHandler.h" +#include "cmCLocaleEnvironmentScope.h" #include "cmCTest.h" -#include "cmake.h" -#include "cmMakefile.h" -#include "cmLocalGenerator.h" -#include "cmGlobalGenerator.h" -#include "cmVersion.h" -#include "cmGeneratedFileStream.h" -#include "cmXMLParser.h" -#include "cmXMLSafe.h" - -#include "cmCTestVC.h" -#include "cmCTestCVS.h" -#include "cmCTestSVN.h" #include "cmCTestBZR.h" +#include "cmCTestCVS.h" #include "cmCTestGIT.h" #include "cmCTestHG.h" +#include "cmCTestP4.h" +#include "cmCTestSVN.h" +#include "cmCTestVC.h" +#include "cmGeneratedFileStream.h" +#include "cmSystemTools.h" +#include "cmVersion.h" +#include "cmXMLWriter.h" -#include <cmsys/auto_ptr.hxx> - -//#include <cmsys/RegularExpression.hxx> -#include <cmsys/Process.h> - -// used for sleep -#ifdef _WIN32 -#include "windows.h" -#endif - -#include <stdlib.h> -#include <math.h> -#include <float.h> +#include "cm_auto_ptr.hxx" +#include <sstream> -//---------------------------------------------------------------------- -static const char* cmCTestUpdateHandlerUpdateStrings[] = -{ - "Unknown", - "CVS", - "SVN", - "BZR", - "GIT", - "HG" +static const char* cmCTestUpdateHandlerUpdateStrings[] = { + "Unknown", "CVS", "SVN", "BZR", "GIT", "HG", "P4" }; static const char* cmCTestUpdateHandlerUpdateToString(int type) { - if ( type < cmCTestUpdateHandler::e_UNKNOWN || - type >= cmCTestUpdateHandler::e_LAST ) - { + if (type < cmCTestUpdateHandler::e_UNKNOWN || + type >= cmCTestUpdateHandler::e_LAST) { return cmCTestUpdateHandlerUpdateStrings[cmCTestUpdateHandler::e_UNKNOWN]; - } + } return cmCTestUpdateHandlerUpdateStrings[type]; } -class cmCTestUpdateHandlerLocale -{ -public: - cmCTestUpdateHandlerLocale(); - ~cmCTestUpdateHandlerLocale(); -private: - std::string saveLCMessages; -}; - -cmCTestUpdateHandlerLocale::cmCTestUpdateHandlerLocale() -{ - const char* lcmess = cmSystemTools::GetEnv("LC_MESSAGES"); - if(lcmess) - { - saveLCMessages = lcmess; - } - // if LC_MESSAGES is not set to C, then - // set it, so that svn/cvs info will be in english ascii - if(! (lcmess && strcmp(lcmess, "C") == 0)) - { - cmSystemTools::PutEnv("LC_MESSAGES=C"); - } -} - -cmCTestUpdateHandlerLocale::~cmCTestUpdateHandlerLocale() -{ - // restore the value of LC_MESSAGES after running the version control - // commands - if(saveLCMessages.size()) - { - std::string put = "LC_MESSAGES="; - put += saveLCMessages; - cmSystemTools::PutEnv(put.c_str()); - } - else - { - cmSystemTools::UnsetEnv("LC_MESSAGES"); - } -} - -//---------------------------------------------------------------------- cmCTestUpdateHandler::cmCTestUpdateHandler() { } -//---------------------------------------------------------------------- void cmCTestUpdateHandler::Initialize() { this->Superclass::Initialize(); @@ -117,114 +43,121 @@ void cmCTestUpdateHandler::Initialize() this->UpdateType = e_CVS; } -//---------------------------------------------------------------------- int cmCTestUpdateHandler::DetermineType(const char* cmd, const char* type) { - cmCTestLog(this->CTest, DEBUG, "Determine update type from command: " << cmd - << " and type: " << type << std::endl); - if ( type && *type ) - { - cmCTestLog(this->CTest, DEBUG, "Type specified: " << type << std::endl); + cmCTestOptionalLog(this->CTest, DEBUG, "Determine update type from command: " + << cmd << " and type: " << type << std::endl, + this->Quiet); + if (type && *type) { + cmCTestOptionalLog(this->CTest, DEBUG, + "Type specified: " << type << std::endl, this->Quiet); std::string stype = cmSystemTools::LowerCase(type); - if ( stype.find("cvs") != std::string::npos ) - { + if (stype.find("cvs") != std::string::npos) { return cmCTestUpdateHandler::e_CVS; - } - if ( stype.find("svn") != std::string::npos ) - { + } + if (stype.find("svn") != std::string::npos) { return cmCTestUpdateHandler::e_SVN; - } - if ( stype.find("bzr") != std::string::npos ) - { + } + if (stype.find("bzr") != std::string::npos) { return cmCTestUpdateHandler::e_BZR; - } - if ( stype.find("git") != std::string::npos ) - { + } + if (stype.find("git") != std::string::npos) { return cmCTestUpdateHandler::e_GIT; - } - if ( stype.find("hg") != std::string::npos ) - { + } + if (stype.find("hg") != std::string::npos) { return cmCTestUpdateHandler::e_HG; - } } - else - { - cmCTestLog(this->CTest, DEBUG, "Type not specified, check command: " - << cmd << std::endl); + if (stype.find("p4") != std::string::npos) { + return cmCTestUpdateHandler::e_P4; + } + } else { + cmCTestOptionalLog( + this->CTest, DEBUG, + "Type not specified, check command: " << cmd << std::endl, this->Quiet); std::string stype = cmSystemTools::LowerCase(cmd); - if ( stype.find("cvs") != std::string::npos ) - { + if (stype.find("cvs") != std::string::npos) { return cmCTestUpdateHandler::e_CVS; - } - if ( stype.find("svn") != std::string::npos ) - { + } + if (stype.find("svn") != std::string::npos) { return cmCTestUpdateHandler::e_SVN; - } - if ( stype.find("bzr") != std::string::npos ) - { + } + if (stype.find("bzr") != std::string::npos) { return cmCTestUpdateHandler::e_BZR; - } - if ( stype.find("git") != std::string::npos ) - { + } + if (stype.find("git") != std::string::npos) { return cmCTestUpdateHandler::e_GIT; - } - if ( stype.find("hg") != std::string::npos ) - { + } + if (stype.find("hg") != std::string::npos) { return cmCTestUpdateHandler::e_HG; - } } + if (stype.find("p4") != std::string::npos) { + return cmCTestUpdateHandler::e_P4; + } + } return cmCTestUpdateHandler::e_UNKNOWN; } -//---------------------------------------------------------------------- -//clearly it would be nice if this were broken up into a few smaller -//functions and commented... +// clearly it would be nice if this were broken up into a few smaller +// functions and commented... int cmCTestUpdateHandler::ProcessHandler() { // Make sure VCS tool messages are in English so we can parse them. - cmCTestUpdateHandlerLocale fixLocale; + cmCLocaleEnvironmentScope fixLocale; static_cast<void>(fixLocale); // Get source dir const char* sourceDirectory = this->GetOption("SourceDirectory"); - if ( !sourceDirectory ) - { + if (!sourceDirectory) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot find SourceDirectory key in the DartConfiguration.tcl" - << std::endl); + "Cannot find SourceDirectory key in the DartConfiguration.tcl" + << std::endl); return -1; - } + } cmGeneratedFileStream ofs; - if ( !this->CTest->GetShowOnly() ) - { + if (!this->CTest->GetShowOnly()) { this->StartLogFile("Update", ofs); - } + } - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Updating the repository: " - << sourceDirectory << std::endl); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Updating the repository: " << sourceDirectory + << std::endl, + this->Quiet); - if(!this->SelectVCS()) - { + if (!this->SelectVCS()) { return -1; - } + } - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Use " - << cmCTestUpdateHandlerUpdateToString(this->UpdateType) - << " repository type" - << std::endl;); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " Use " + << cmCTestUpdateHandlerUpdateToString(this->UpdateType) + << " repository type" << std::endl; + , this->Quiet); // Create an object to interact with the VCS tool. - cmsys::auto_ptr<cmCTestVC> vc; - switch (this->UpdateType) - { - case e_CVS: vc.reset(new cmCTestCVS(this->CTest, ofs)); break; - case e_SVN: vc.reset(new cmCTestSVN(this->CTest, ofs)); break; - case e_BZR: vc.reset(new cmCTestBZR(this->CTest, ofs)); break; - case e_GIT: vc.reset(new cmCTestGIT(this->CTest, ofs)); break; - case e_HG: vc.reset(new cmCTestHG(this->CTest, ofs)); break; - default: vc.reset(new cmCTestVC(this->CTest, ofs)); break; - } + CM_AUTO_PTR<cmCTestVC> vc; + switch (this->UpdateType) { + case e_CVS: + vc.reset(new cmCTestCVS(this->CTest, ofs)); + break; + case e_SVN: + vc.reset(new cmCTestSVN(this->CTest, ofs)); + break; + case e_BZR: + vc.reset(new cmCTestBZR(this->CTest, ofs)); + break; + case e_GIT: + vc.reset(new cmCTestGIT(this->CTest, ofs)); + break; + case e_HG: + vc.reset(new cmCTestHG(this->CTest, ofs)); + break; + case e_P4: + vc.reset(new cmCTestP4(this->CTest, ofs)); + break; + default: + vc.reset(new cmCTestVC(this->CTest, ofs)); + break; + } vc->SetCommandLineTool(this->UpdateCommand); vc->SetSourceDirectory(sourceDirectory); @@ -235,125 +168,130 @@ int cmCTestUpdateHandler::ProcessHandler() // Now update repository and remember what files were updated // cmGeneratedFileStream os; - if(!this->StartResultingXML(cmCTest::PartUpdate, "Update", os)) - { + if (!this->StartResultingXML(cmCTest::PartUpdate, "Update", os)) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot open log file" - << std::endl); + << std::endl); return -1; - } + } std::string start_time = this->CTest->CurrentTime(); unsigned int start_time_time = static_cast<unsigned int>(cmSystemTools::GetTime()); double elapsed_time_start = cmSystemTools::GetTime(); bool updated = vc->Update(); - - os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - << "<Update mode=\"Client\" Generator=\"ctest-" - << cmVersion::GetCMakeVersion() << "\">\n" - << "\t<Site>" << this->CTest->GetCTestConfiguration("Site") << "</Site>\n" - << "\t<BuildName>" << this->CTest->GetCTestConfiguration("BuildName") - << "</BuildName>\n" - << "\t<BuildStamp>" << this->CTest->GetCurrentTag() << "-" - << this->CTest->GetTestModelString() << "</BuildStamp>" << std::endl; - os << "\t<StartDateTime>" << start_time << "</StartDateTime>\n" - << "\t<StartTime>" << start_time_time << "</StartTime>\n" - << "\t<UpdateCommand>" - << cmXMLSafe(vc->GetUpdateCommandLine()).Quotes(false) - << "</UpdateCommand>\n" - << "\t<UpdateType>" << cmXMLSafe( - cmCTestUpdateHandlerUpdateToString(this->UpdateType)) - << "</UpdateType>\n"; - - vc->WriteXML(os); + std::string buildname = + cmCTest::SafeBuildIdField(this->CTest->GetCTestConfiguration("BuildName")); + + cmXMLWriter xml(os); + xml.StartDocument(); + xml.StartElement("Update"); + xml.Attribute("mode", "Client"); + xml.Attribute("Generator", + std::string("ctest-") + cmVersion::GetCMakeVersion()); + xml.Element("Site", this->CTest->GetCTestConfiguration("Site")); + xml.Element("BuildName", buildname); + xml.Element("BuildStamp", this->CTest->GetCurrentTag() + "-" + + this->CTest->GetTestModelString()); + xml.Element("StartDateTime", start_time); + xml.Element("StartTime", start_time_time); + xml.Element("UpdateCommand", vc->GetUpdateCommandLine()); + xml.Element("UpdateType", + cmCTestUpdateHandlerUpdateToString(this->UpdateType)); + + bool loadedMods = vc->WriteXML(xml); int localModifications = 0; int numUpdated = vc->GetPathCount(cmCTestVC::PathUpdated); - if(numUpdated) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, - " Found " << numUpdated << " updated files\n"); - } - if(int numModified = vc->GetPathCount(cmCTestVC::PathModified)) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, - " Found " << numModified << " locally modified files\n"); + if (numUpdated) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Found " << numUpdated << " updated files\n", + this->Quiet); + } + if (int numModified = vc->GetPathCount(cmCTestVC::PathModified)) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " Found " + << numModified << " locally modified files\n", + this->Quiet); localModifications += numModified; - } - if(int numConflicting = vc->GetPathCount(cmCTestVC::PathConflicting)) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, - " Found " << numConflicting << " conflicting files\n"); + } + if (int numConflicting = vc->GetPathCount(cmCTestVC::PathConflicting)) { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " Found " << numConflicting << " conflicting files\n", + this->Quiet); localModifications += numConflicting; - } + } - cmCTestLog(this->CTest, DEBUG, "End" << std::endl); + cmCTestOptionalLog(this->CTest, DEBUG, "End" << std::endl, this->Quiet); std::string end_time = this->CTest->CurrentTime(); - os << "\t<EndDateTime>" << end_time << "</EndDateTime>\n" - << "\t<EndTime>" << static_cast<unsigned int>(cmSystemTools::GetTime()) - << "</EndTime>\n" - << "<ElapsedMinutes>" << - static_cast<int>((cmSystemTools::GetTime() - elapsed_time_start)/6)/10.0 - << "</ElapsedMinutes>\n" - << "\t<UpdateReturnStatus>"; - if(localModifications) - { - os << "Update error: There are modified or conflicting files in the " - "repository"; + xml.Element("EndDateTime", end_time); + xml.Element("EndTime", static_cast<unsigned int>(cmSystemTools::GetTime())); + xml.Element( + "ElapsedMinutes", + static_cast<int>((cmSystemTools::GetTime() - elapsed_time_start) / 6) / + 10.0); + + xml.StartElement("UpdateReturnStatus"); + if (localModifications) { + xml.Content("Update error: " + "There are modified or conflicting files in the repository"); cmCTestLog(this->CTest, ERROR_MESSAGE, - " There are modified or conflicting files in the repository" - << std::endl); - } - if(!updated) - { - os << "Update command failed:\n" << vc->GetUpdateCommandLine(); - cmCTestLog(this->CTest, ERROR_MESSAGE, " Update command failed: " - << vc->GetUpdateCommandLine() << "\n"); - } - os << "</UpdateReturnStatus>" << std::endl; - os << "</Update>" << std::endl; - return numUpdated; + " There are modified or conflicting files in the repository" + << std::endl); + } + if (!updated) { + xml.Content("Update command failed:\n"); + xml.Content(vc->GetUpdateCommandLine()); + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Update command failed: " + << vc->GetUpdateCommandLine() << "\n"); + } + xml.EndElement(); // UpdateReturnStatus + xml.EndElement(); // Update + xml.EndDocument(); + return updated && loadedMods ? numUpdated : -1; } -//---------------------------------------------------------------------- int cmCTestUpdateHandler::DetectVCS(const char* dir) { std::string sourceDirectory = dir; - cmCTestLog(this->CTest, DEBUG, "Check directory: " - << sourceDirectory.c_str() << std::endl); + cmCTestOptionalLog(this->CTest, DEBUG, + "Check directory: " << sourceDirectory << std::endl, + this->Quiet); sourceDirectory += "/.svn"; - if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) - { + if (cmSystemTools::FileExists(sourceDirectory.c_str())) { return cmCTestUpdateHandler::e_SVN; - } + } sourceDirectory = dir; sourceDirectory += "/CVS"; - if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) - { + if (cmSystemTools::FileExists(sourceDirectory.c_str())) { return cmCTestUpdateHandler::e_CVS; - } + } sourceDirectory = dir; sourceDirectory += "/.bzr"; - if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) - { + if (cmSystemTools::FileExists(sourceDirectory.c_str())) { return cmCTestUpdateHandler::e_BZR; - } + } sourceDirectory = dir; sourceDirectory += "/.git"; - if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) - { + if (cmSystemTools::FileExists(sourceDirectory.c_str())) { return cmCTestUpdateHandler::e_GIT; - } + } sourceDirectory = dir; sourceDirectory += "/.hg"; - if ( cmSystemTools::FileExists(sourceDirectory.c_str()) ) - { + if (cmSystemTools::FileExists(sourceDirectory.c_str())) { return cmCTestUpdateHandler::e_HG; - } + } + sourceDirectory = dir; + sourceDirectory += "/.p4"; + if (cmSystemTools::FileExists(sourceDirectory.c_str())) { + return cmCTestUpdateHandler::e_P4; + } + sourceDirectory = dir; + sourceDirectory += "/.p4config"; + if (cmSystemTools::FileExists(sourceDirectory.c_str())) { + return cmCTestUpdateHandler::e_P4; + } return cmCTestUpdateHandler::e_UNKNOWN; } -//---------------------------------------------------------------------- bool cmCTestUpdateHandler::SelectVCS() { // Get update command @@ -361,44 +299,53 @@ bool cmCTestUpdateHandler::SelectVCS() // Detect the VCS managing the source tree. this->UpdateType = this->DetectVCS(this->GetOption("SourceDirectory")); - if (this->UpdateType == e_UNKNOWN) - { + if (this->UpdateType == e_UNKNOWN) { // The source tree does not have a recognized VCS. Check the // configuration value or command name. - this->UpdateType = this->DetermineType(this->UpdateCommand.c_str(), + this->UpdateType = this->DetermineType( + this->UpdateCommand.c_str(), this->CTest->GetCTestConfiguration("UpdateType").c_str()); - } + } // If no update command was specified, lookup one for this VCS tool. - if (this->UpdateCommand.empty()) - { - const char* key = 0; - switch (this->UpdateType) - { - case e_CVS: key = "CVSCommand"; break; - case e_SVN: key = "SVNCommand"; break; - case e_BZR: key = "BZRCommand"; break; - case e_GIT: key = "GITCommand"; break; - case e_HG: key = "HGCommand"; break; - default: break; - } - if (key) - { + if (this->UpdateCommand.empty()) { + const char* key = CM_NULLPTR; + switch (this->UpdateType) { + case e_CVS: + key = "CVSCommand"; + break; + case e_SVN: + key = "SVNCommand"; + break; + case e_BZR: + key = "BZRCommand"; + break; + case e_GIT: + key = "GITCommand"; + break; + case e_HG: + key = "HGCommand"; + break; + case e_P4: + key = "P4Command"; + break; + default: + break; + } + if (key) { this->UpdateCommand = this->CTest->GetCTestConfiguration(key); - } - if (this->UpdateCommand.empty()) - { - cmOStringStream e; + } + if (this->UpdateCommand.empty()) { + std::ostringstream e; e << "Cannot find UpdateCommand "; - if (key) - { + if (key) { e << "or " << key; - } + } e << " configuration key."; cmCTestLog(this->CTest, ERROR_MESSAGE, e.str() << std::endl); return false; - } } + } return true; } diff --git a/Source/CTest/cmCTestUpdateHandler.h b/Source/CTest/cmCTestUpdateHandler.h index 55ec974dc..0cd284421 100644 --- a/Source/CTest/cmCTestUpdateHandler.h +++ b/Source/CTest/cmCTestUpdateHandler.h @@ -1,25 +1,15 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestUpdateHandler_h #define cmCTestUpdateHandler_h +#include "cmConfigure.h" #include "cmCTestGenericHandler.h" -#include "cmListFileCache.h" -#if defined(__sgi) && !defined(__GNUC__) -# pragma set woff 1375 /* base class destructor not virtual */ -#endif +#include <string> +#include <utility> +#include <vector> /** \class cmCTestUpdateHandler * \brief A class that handles ctest -S invocations @@ -28,35 +18,40 @@ class cmCTestUpdateHandler : public cmCTestGenericHandler { public: - cmTypeMacro(cmCTestUpdateHandler, cmCTestGenericHandler); + typedef cmCTestGenericHandler Superclass; /* * The main entry point for this class */ - int ProcessHandler(); + int ProcessHandler() CM_OVERRIDE; cmCTestUpdateHandler(); - enum { + enum + { e_UNKNOWN = 0, e_CVS, e_SVN, e_BZR, e_GIT, e_HG, + e_P4, e_LAST }; /** * Initialize handler */ - virtual void Initialize(); + void Initialize() CM_OVERRIDE; private: // Some structures needed for update - struct StringPair : - public std::pair<std::string, std::string>{}; - struct UpdateFiles : public std::vector<StringPair>{}; + struct StringPair : public std::pair<std::string, std::string> + { + }; + struct UpdateFiles : public std::vector<StringPair> + { + }; // Determine the type of version control int DetermineType(const char* cmd, const char* type); @@ -69,8 +64,4 @@ private: bool SelectVCS(); }; -#if defined(__sgi) && !defined(__GNUC__) -# pragma reset woff 1375 /* base class destructor not virtual */ -#endif - #endif diff --git a/Source/CTest/cmCTestUploadCommand.cxx b/Source/CTest/cmCTestUploadCommand.cxx index 731c1c7a3..717117a5f 100644 --- a/Source/CTest/cmCTestUploadCommand.cxx +++ b/Source/CTest/cmCTestUploadCommand.cxx @@ -1,68 +1,67 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestUploadCommand.h" +#include <sstream> +#include <vector> + #include "cmCTest.h" #include "cmCTestGenericHandler.h" #include "cmCTestUploadHandler.h" +#include "cmMakefile.h" +#include "cmSystemTools.h" +#include "cmake.h" cmCTestGenericHandler* cmCTestUploadCommand::InitializeHandler() { - cmCTestGenericHandler* handler - = this->CTest->GetInitializedHandler("upload"); - if ( !handler ) - { + cmCTestGenericHandler* handler = + this->CTest->GetInitializedHandler("upload"); + if (!handler) { this->SetError("internal CTest error. Cannot instantiate upload handler"); - return 0; - } + return CM_NULLPTR; + } static_cast<cmCTestUploadHandler*>(handler)->SetFiles(this->Files); + handler->SetQuiet(this->Quiet); return handler; } - -//---------------------------------------------------------------------------- bool cmCTestUploadCommand::CheckArgumentKeyword(std::string const& arg) { - if(arg == "FILES") - { + if (arg == "FILES") { this->ArgumentDoing = ArgumentDoingFiles; return true; - } + } + if (arg == "QUIET") { + this->ArgumentDoing = ArgumentDoingNone; + this->Quiet = true; + return true; + } + if (arg == "CAPTURE_CMAKE_ERROR") { + this->ArgumentDoing = ArgumentDoingCaptureCMakeError; + return true; + } return false; } - -//---------------------------------------------------------------------------- bool cmCTestUploadCommand::CheckArgumentValue(std::string const& arg) { - if(this->ArgumentDoing == ArgumentDoingFiles) - { - cmStdString filename(arg); - if(cmSystemTools::FileExists(filename.c_str())) - { - this->Files.insert(filename); + if (this->ArgumentDoing == ArgumentDoingCaptureCMakeError) { + this->Values[ct_CAPTURE_CMAKE_ERROR] = arg.c_str(); + return true; + } + if (this->ArgumentDoing == ArgumentDoingFiles) { + if (cmSystemTools::FileExists(arg.c_str())) { + this->Files.insert(arg); return true; - } - else - { - cmOStringStream e; - e << "File \"" << filename << "\" does not exist. Cannot submit " - << "a non-existent file."; - this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str()); - this->ArgumentDoing = ArgumentDoingError; - return false; - } } + std::ostringstream e; + e << "File \"" << arg << "\" does not exist. Cannot submit " + << "a non-existent file."; + this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str()); + this->ArgumentDoing = ArgumentDoingError; + return false; + } // Look for other arguments. return this->Superclass::CheckArgumentValue(arg); diff --git a/Source/CTest/cmCTestUploadCommand.h b/Source/CTest/cmCTestUploadCommand.h index 62f379f6d..6e721796a 100644 --- a/Source/CTest/cmCTestUploadCommand.h +++ b/Source/CTest/cmCTestUploadCommand.h @@ -1,19 +1,17 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestUploadCommand_h #define cmCTestUploadCommand_h -#include "cmCTestHandlerCommand.h" +#include "cmConfigure.h" + #include "cmCTest.h" +#include "cmCTestHandlerCommand.h" + +#include <string> + +class cmCTestGenericHandler; +class cmCommand; /** \class cmCTestUpload * \brief Run a ctest script @@ -24,62 +22,40 @@ class cmCTestUploadCommand : public cmCTestHandlerCommand { public: - - cmCTestUploadCommand() - { - } + cmCTestUploadCommand() {} /** * This is a virtual constructor for the command. */ - virtual cmCommand* Clone() - { + cmCommand* Clone() CM_OVERRIDE + { cmCTestUploadCommand* ni = new cmCTestUploadCommand; ni->CTest = this->CTest; ni->CTestScriptHandler = this->CTestScriptHandler; return ni; - } + } /** * The name of the command as specified in CMakeList.txt. */ - virtual const char* GetName() const { return "ctest_upload";} - - /** - * Succinct documentation. - */ - virtual const char* GetTerseDocumentation() const - { - return "Upload files to a dashboard server."; - } + std::string GetName() const CM_OVERRIDE { return "ctest_upload"; } - /** - * More documentation. - */ - virtual const char* GetFullDocumentation() const - { - return - " ctest_upload(FILES ...)\n" - "Pass a list of files to be sent along with the build results to " - "the dashboard server.\n"; - } - - cmTypeMacro(cmCTestUploadCommand, cmCTestHandlerCommand); + typedef cmCTestHandlerCommand Superclass; protected: - cmCTestGenericHandler* InitializeHandler(); + cmCTestGenericHandler* InitializeHandler() CM_OVERRIDE; - virtual bool CheckArgumentKeyword(std::string const& arg); - virtual bool CheckArgumentValue(std::string const& arg); + bool CheckArgumentKeyword(std::string const& arg) CM_OVERRIDE; + bool CheckArgumentValue(std::string const& arg) CM_OVERRIDE; enum { ArgumentDoingFiles = Superclass::ArgumentDoingLast1, + ArgumentDoingCaptureCMakeError, ArgumentDoingLast2 }; cmCTest::SetOfStrings Files; }; - #endif diff --git a/Source/CTest/cmCTestUploadHandler.cxx b/Source/CTest/cmCTestUploadHandler.cxx index caf2e5370..05a39840d 100644 --- a/Source/CTest/cmCTestUploadHandler.cxx +++ b/Source/CTest/cmCTestUploadHandler.cxx @@ -1,27 +1,20 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestUploadHandler.h" #include "cmGeneratedFileStream.h" #include "cmVersion.h" -#include "cmXMLSafe.h" +#include "cmXMLWriter.h" + +#include <ostream> +#include <set> +#include <string> -//---------------------------------------------------------------------------- cmCTestUploadHandler::cmCTestUploadHandler() { this->Initialize(); } -//---------------------------------------------------------------------------- void cmCTestUploadHandler::Initialize() { this->Superclass::Initialize(); @@ -33,45 +26,48 @@ void cmCTestUploadHandler::SetFiles(const cmCTest::SetOfStrings& files) this->Files = files; } -//---------------------------------------------------------------------------- int cmCTestUploadHandler::ProcessHandler() { cmGeneratedFileStream ofs; - if ( !this->CTest->OpenOutputFile(this->CTest->GetCurrentTag(), - "Upload.xml", ofs)) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Cannot open Upload.xml file" << std::endl); + if (!this->CTest->OpenOutputFile(this->CTest->GetCurrentTag(), "Upload.xml", + ofs)) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot open Upload.xml file" + << std::endl); return -1; - } - + } + std::string buildname = + cmCTest::SafeBuildIdField(this->CTest->GetCTestConfiguration("BuildName")); cmCTest::SetOfStrings::const_iterator it; - ofs << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - << "<?xml-stylesheet type=\"text/xsl\" " - "href=\"Dart/Source/Server/XSL/Build.xsl " - "<file:///Dart/Source/Server/XSL/Build.xsl> \"?>\n" - << "<Site BuildName=\"" - << this->CTest->GetCTestConfiguration("BuildName") - << "\" BuildStamp=\"" - << this->CTest->GetCurrentTag() << "-" - << this->CTest->GetTestModelString() << "\" Name=\"" - << this->CTest->GetCTestConfiguration("Site") << "\" Generator=\"ctest" - << cmVersion::GetCMakeVersion() - << "\">\n"; - this->CTest->AddSiteProperties(ofs); - ofs << "<Upload>\n"; - for ( it = this->Files.begin(); it != this->Files.end(); it ++ ) - { - cmCTestLog(this->CTest, OUTPUT, - "\tUpload file: " << it->c_str() << std::endl); - ofs << "<File filename=\"" << cmXMLSafe(*it) << "\">\n" - << "<Content encoding=\"base64\">\n"; - ofs << this->CTest->Base64EncodeFile(*it); - ofs << "\n</Content>\n" - << "</File>\n"; - } - ofs << "</Upload>\n" - << "</Site>\n"; + cmXMLWriter xml(ofs); + xml.StartDocument(); + xml.ProcessingInstruction("xml-stylesheet", + "type=\"text/xsl\" " + "href=\"Dart/Source/Server/XSL/Build.xsl " + "<file:///Dart/Source/Server/XSL/Build.xsl> \""); + xml.StartElement("Site"); + xml.Attribute("BuildName", buildname); + xml.Attribute("BuildStamp", this->CTest->GetCurrentTag() + "-" + + this->CTest->GetTestModelString()); + xml.Attribute("Name", this->CTest->GetCTestConfiguration("Site")); + xml.Attribute("Generator", + std::string("ctest") + cmVersion::GetCMakeVersion()); + this->CTest->AddSiteProperties(xml); + xml.StartElement("Upload"); + + for (it = this->Files.begin(); it != this->Files.end(); it++) { + cmCTestOptionalLog(this->CTest, OUTPUT, + "\tUpload file: " << *it << std::endl, this->Quiet); + xml.StartElement("File"); + xml.Attribute("filename", *it); + xml.StartElement("Content"); + xml.Attribute("encoding", "base64"); + xml.Content(this->CTest->Base64EncodeFile(*it)); + xml.EndElement(); // Content + xml.EndElement(); // File + } + xml.EndElement(); // Upload + xml.EndElement(); // Site + xml.EndDocument(); return 0; } diff --git a/Source/CTest/cmCTestUploadHandler.h b/Source/CTest/cmCTestUploadHandler.h index 23ed35a25..1e8d3c801 100644 --- a/Source/CTest/cmCTestUploadHandler.h +++ b/Source/CTest/cmCTestUploadHandler.h @@ -1,17 +1,11 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestUploadHandler_h #define cmCTestUploadHandler_h +#include "cmConfigure.h" + +#include "cmCTest.h" #include "cmCTestGenericHandler.h" /** \class cmCTestUploadHandler @@ -23,17 +17,17 @@ class cmCTestUploadHandler : public cmCTestGenericHandler { public: - cmTypeMacro(cmCTestUploadHandler, cmCTestGenericHandler); + typedef cmCTestGenericHandler Superclass; cmCTestUploadHandler(); - ~cmCTestUploadHandler() {} + ~cmCTestUploadHandler() CM_OVERRIDE {} /* * The main entry point for this class */ - int ProcessHandler(); + int ProcessHandler() CM_OVERRIDE; - void Initialize(); + void Initialize() CM_OVERRIDE; /** Specify a set of files to submit. */ void SetFiles(cmCTest::SetOfStrings const& files); diff --git a/Source/CTest/cmCTestVC.cxx b/Source/CTest/cmCTestVC.cxx index fbee2272e..fb2742e08 100644 --- a/Source/CTest/cmCTestVC.cxx +++ b/Source/CTest/cmCTestVC.cxx @@ -1,24 +1,20 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCTestVC.h" #include "cmCTest.h" #include "cmSystemTools.h" -#include "cmXMLSafe.h" +#include "cmXMLWriter.h" -#include <cmsys/Process.h> +#include "cmsys/Process.h" +#include <sstream> +#include <stdio.h> +#include <time.h> +#include <vector> -//---------------------------------------------------------------------------- -cmCTestVC::cmCTestVC(cmCTest* ct, std::ostream& log): CTest(ct), Log(log) +cmCTestVC::cmCTestVC(cmCTest* ct, std::ostream& log) + : CTest(ct) + , Log(log) { this->PathCount[PathUpdated] = 0; this->PathCount[PathModified] = 0; @@ -28,24 +24,20 @@ cmCTestVC::cmCTestVC(cmCTest* ct, std::ostream& log): CTest(ct), Log(log) this->Unknown.Rev = "Unknown"; } -//---------------------------------------------------------------------------- cmCTestVC::~cmCTestVC() { } -//---------------------------------------------------------------------------- void cmCTestVC::SetCommandLineTool(std::string const& tool) { this->CommandLineTool = tool; } -//---------------------------------------------------------------------------- void cmCTestVC::SetSourceDirectory(std::string const& dir) { this->SourceDirectory = dir; } -//---------------------------------------------------------------------------- bool cmCTestVC::InitialCheckout(const char* command) { cmCTestLog(this->CTest, HANDLER_OUTPUT, @@ -55,22 +47,20 @@ bool cmCTestVC::InitialCheckout(const char* command) std::string parent = cmSystemTools::GetFilenamePath(this->SourceDirectory); cmCTestLog(this->CTest, HANDLER_OUTPUT, " Perform checkout in directory: " << parent << "\n"); - if(!cmSystemTools::MakeDirectory(parent.c_str())) - { + if (!cmSystemTools::MakeDirectory(parent.c_str())) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot create directory: " << parent << std::endl); return false; - } + } // Construct the initial checkout command line. - std::vector<cmStdString> args = cmSystemTools::ParseArguments(command); + std::vector<std::string> args = cmSystemTools::ParseArguments(command); std::vector<char const*> vc_co; - for(std::vector<cmStdString>::const_iterator ai = args.begin(); - ai != args.end(); ++ai) - { + for (std::vector<std::string>::const_iterator ai = args.begin(); + ai != args.end(); ++ai) { vc_co.push_back(ai->c_str()); - } - vc_co.push_back(0); + } + vc_co.push_back(CM_NULLPTR); // Run the initial checkout command and log its output. this->Log << "--- Begin Initial Checkout ---\n"; @@ -78,60 +68,54 @@ bool cmCTestVC::InitialCheckout(const char* command) OutputLogger err(this->Log, "co-err> "); bool result = this->RunChild(&vc_co[0], &out, &err, parent.c_str()); this->Log << "--- End Initial Checkout ---\n"; - if(!result) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Initial checkout failed!" << std::endl); - } + if (!result) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Initial checkout failed!" + << std::endl); + } return result; } -//---------------------------------------------------------------------------- bool cmCTestVC::RunChild(char const* const* cmd, OutputParser* out, - OutputParser* err, const char* workDir) + OutputParser* err, const char* workDir, + Encoding encoding) { this->Log << this->ComputeCommandLine(cmd) << "\n"; cmsysProcess* cp = cmsysProcess_New(); cmsysProcess_SetCommand(cp, cmd); - workDir = workDir? workDir : this->SourceDirectory.c_str(); + workDir = workDir ? workDir : this->SourceDirectory.c_str(); cmsysProcess_SetWorkingDirectory(cp, workDir); - this->RunProcess(cp, out, err); + this->RunProcess(cp, out, err, encoding); int result = cmsysProcess_GetExitValue(cp); cmsysProcess_Delete(cp); return result == 0; } -//---------------------------------------------------------------------------- std::string cmCTestVC::ComputeCommandLine(char const* const* cmd) { - cmOStringStream line; + std::ostringstream line; const char* sep = ""; - for(const char* const* arg = cmd; *arg; ++arg) - { + for (const char* const* arg = cmd; *arg; ++arg) { line << sep << "\"" << *arg << "\""; sep = " "; - } + } return line.str(); } -//---------------------------------------------------------------------------- -bool cmCTestVC::RunUpdateCommand(char const* const* cmd, - OutputParser* out, OutputParser* err) +bool cmCTestVC::RunUpdateCommand(char const* const* cmd, OutputParser* out, + OutputParser* err, Encoding encoding) { // Report the command line. this->UpdateCommandLine = this->ComputeCommandLine(cmd); - if(this->CTest->GetShowOnly()) - { + if (this->CTest->GetShowOnly()) { this->Log << this->UpdateCommandLine << "\n"; return true; - } + } // Run the command. - return this->RunChild(cmd, out, err); + return this->RunChild(cmd, out, err, CM_NULLPTR, encoding); } -//---------------------------------------------------------------------------- std::string cmCTestVC::GetNightlyTime() { // Get the nightly start time corresponding to the current dau. @@ -139,17 +123,11 @@ std::string cmCTestVC::GetNightlyTime() this->CTest->GetCTestConfiguration("NightlyStartTime"), this->CTest->GetTomorrowTag()); char current_time[1024]; - sprintf(current_time, "%04d-%02d-%02d %02d:%02d:%02d", - t->tm_year + 1900, - t->tm_mon + 1, - t->tm_mday, - t->tm_hour, - t->tm_min, - t->tm_sec); + sprintf(current_time, "%04d-%02d-%02d %02d:%02d:%02d", t->tm_year + 1900, + t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); return std::string(current_time); } -//---------------------------------------------------------------------------- void cmCTestVC::Cleanup() { this->Log << "--- Begin Cleanup ---\n"; @@ -157,36 +135,39 @@ void cmCTestVC::Cleanup() this->Log << "--- End Cleanup ---\n"; } -//---------------------------------------------------------------------------- void cmCTestVC::CleanupImpl() { // We do no cleanup by default. } -//---------------------------------------------------------------------------- bool cmCTestVC::Update() { - this->NoteOldRevision(); - this->Log << "--- Begin Update ---\n"; - bool result = this->UpdateImpl(); - this->Log << "--- End Update ---\n"; - this->NoteNewRevision(); + bool result = true; + // if update version only is on then do not actually update, + // just note the current version and finish + if (!cmSystemTools::IsOn( + this->CTest->GetCTestConfiguration("UpdateVersionOnly").c_str())) { + result = this->NoteOldRevision() && result; + this->Log << "--- Begin Update ---\n"; + result = this->UpdateImpl() && result; + this->Log << "--- End Update ---\n"; + } + result = this->NoteNewRevision() && result; return result; } -//---------------------------------------------------------------------------- -void cmCTestVC::NoteOldRevision() +bool cmCTestVC::NoteOldRevision() { // We do nothing by default. + return true; } -//---------------------------------------------------------------------------- -void cmCTestVC::NoteNewRevision() +bool cmCTestVC::NoteNewRevision() { // We do nothing by default. + return true; } -//---------------------------------------------------------------------------- bool cmCTestVC::UpdateImpl() { cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, @@ -194,8 +175,7 @@ bool cmCTestVC::UpdateImpl() return true; } -//---------------------------------------------------------------------------- -bool cmCTestVC::WriteXML(std::ostream& xml) +bool cmCTestVC::WriteXML(cmXMLWriter& xml) { this->Log << "--- Begin Revisions ---\n"; bool result = this->WriteXMLUpdates(xml); @@ -203,39 +183,33 @@ bool cmCTestVC::WriteXML(std::ostream& xml) return result; } -//---------------------------------------------------------------------------- -bool cmCTestVC::WriteXMLUpdates(std::ostream&) +bool cmCTestVC::WriteXMLUpdates(cmXMLWriter& /*unused*/) { cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "* CTest cannot extract updates for this VCS tool.\n"); return true; } -//---------------------------------------------------------------------------- -void cmCTestVC::WriteXMLEntry(std::ostream& xml, - std::string const& path, - std::string const& name, - std::string const& full, +void cmCTestVC::WriteXMLEntry(cmXMLWriter& xml, std::string const& path, + std::string const& name, std::string const& full, File const& f) { - static const char* desc[3] = { "Updated", "Modified", "Conflicting"}; - Revision const& rev = f.Rev? *f.Rev : this->Unknown; - std::string prior = f.PriorRev? f.PriorRev->Rev : std::string("Unknown"); - xml << "\t\t<" << desc[f.Status] << ">\n" - << "\t\t\t<File>" << cmXMLSafe(name) << "</File>\n" - << "\t\t\t<Directory>" << cmXMLSafe(path) << "</Directory>\n" - << "\t\t\t<FullName>" << cmXMLSafe(full) << "</FullName>\n" - << "\t\t\t<CheckinDate>" << cmXMLSafe(rev.Date) << "</CheckinDate>\n" - << "\t\t\t<Author>" << cmXMLSafe(rev.Author) << "</Author>\n" - << "\t\t\t<Email>" << cmXMLSafe(rev.EMail) << "</Email>\n" - << "\t\t\t<Committer>" << cmXMLSafe(rev.Committer) << "</Committer>\n" - << "\t\t\t<CommitterEmail>" << cmXMLSafe(rev.CommitterEMail) - << "</CommitterEmail>\n" - << "\t\t\t<CommitDate>" << cmXMLSafe(rev.CommitDate) - << "</CommitDate>\n" - << "\t\t\t<Log>" << cmXMLSafe(rev.Log) << "</Log>\n" - << "\t\t\t<Revision>" << cmXMLSafe(rev.Rev) << "</Revision>\n" - << "\t\t\t<PriorRevision>" << cmXMLSafe(prior) << "</PriorRevision>\n" - << "\t\t</" << desc[f.Status] << ">\n"; + static const char* desc[3] = { "Updated", "Modified", "Conflicting" }; + Revision const& rev = f.Rev ? *f.Rev : this->Unknown; + std::string prior = f.PriorRev ? f.PriorRev->Rev : std::string("Unknown"); + xml.StartElement(desc[f.Status]); + xml.Element("File", name); + xml.Element("Directory", path); + xml.Element("FullName", full); + xml.Element("CheckinDate", rev.Date); + xml.Element("Author", rev.Author); + xml.Element("Email", rev.EMail); + xml.Element("Committer", rev.Committer); + xml.Element("CommitterEmail", rev.CommitterEMail); + xml.Element("CommitDate", rev.CommitDate); + xml.Element("Log", rev.Log); + xml.Element("Revision", rev.Rev); + xml.Element("PriorRevision", prior); + xml.EndElement(); ++this->PathCount[f.Status]; } diff --git a/Source/CTest/cmCTestVC.h b/Source/CTest/cmCTestVC.h index 9dd06515d..6400bcda3 100644 --- a/Source/CTest/cmCTestVC.h +++ b/Source/CTest/cmCTestVC.h @@ -1,26 +1,24 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmCTestVC_h #define cmCTestVC_h +#include "cmConfigure.h" + +#include <iosfwd> +#include <string> + +#include "cmProcessOutput.h" #include "cmProcessTools.h" class cmCTest; +class cmXMLWriter; /** \class cmCTestVC * \brief Base class for version control system handlers * */ -class cmCTestVC: public cmProcessTools +class cmCTestVC : public cmProcessTools { public: /** Construct with a CTest instance and update log stream. */ @@ -48,13 +46,20 @@ public: /** Get the command line used by the Update method. */ std::string const& GetUpdateCommandLine() const - { return this->UpdateCommandLine; } + { + return this->UpdateCommandLine; + } /** Write Update.xml entries for the updates found. */ - bool WriteXML(std::ostream& xml); + bool WriteXML(cmXMLWriter& xml); /** Enumerate non-trivial working tree states during update. */ - enum PathStatus { PathUpdated, PathModified, PathConflicting }; + enum PathStatus + { + PathUpdated, + PathModified, + PathConflicting + }; /** Get the number of working tree paths in each state after update. */ int GetPathCount(PathStatus s) const { return this->PathCount[s]; } @@ -62,13 +67,14 @@ public: protected: // Internal API to be implemented by subclasses. virtual void CleanupImpl(); - virtual void NoteOldRevision(); + virtual bool NoteOldRevision(); virtual bool UpdateImpl(); - virtual void NoteNewRevision(); - virtual bool WriteXMLUpdates(std::ostream& xml); + virtual bool NoteNewRevision(); + virtual bool WriteXMLUpdates(cmXMLWriter& xml); #if defined(__SUNPRO_CC) && __SUNPRO_CC <= 0x510 -public: // Sun CC 5.1 needs help to allow cmCTestSVN::Revision to see this + // Sun CC 5.1 needs help to allow cmCTestSVN::Revision to see this +public: #endif /** Basic information about one revision of a tree or file. */ struct Revision @@ -84,7 +90,6 @@ public: // Sun CC 5.1 needs help to allow cmCTestSVN::Revision to see this }; protected: - struct File; friend struct File; /** Represent change to one file. */ @@ -93,24 +98,35 @@ protected: PathStatus Status; Revision const* Rev; Revision const* PriorRev; - File(): Status(PathUpdated), Rev(0), PriorRev(0) {} - File(PathStatus status, Revision const* rev, Revision const* priorRev): - Status(status), Rev(rev), PriorRev(priorRev) {} + File() + : Status(PathUpdated) + , Rev(CM_NULLPTR) + , PriorRev(CM_NULLPTR) + { + } + File(PathStatus status, Revision const* rev, Revision const* priorRev) + : Status(status) + , Rev(rev) + , PriorRev(priorRev) + { + } }; /** Convert a list of arguments to a human-readable command line. */ static std::string ComputeCommandLine(char const* const* cmd); /** Run a command line and send output to given parsers. */ - bool RunChild(char const* const* cmd, OutputParser* out, - OutputParser* err, const char* workDir = 0); + bool RunChild(char const* const* cmd, OutputParser* out, OutputParser* err, + const char* workDir = CM_NULLPTR, + Encoding encoding = cmProcessOutput::Auto); /** Run VC update command line and send output to given parsers. */ - bool RunUpdateCommand(char const* const* cmd, - OutputParser* out, OutputParser* err = 0); + bool RunUpdateCommand(char const* const* cmd, OutputParser* out, + OutputParser* err = CM_NULLPTR, + Encoding encoding = cmProcessOutput::Auto); /** Write xml element for one file. */ - void WriteXMLEntry(std::ostream& xml, std::string const& path, + void WriteXMLEntry(cmXMLWriter& xml, std::string const& path, std::string const& name, std::string const& full, File const& f); diff --git a/Source/CTest/cmParseBlanketJSCoverage.cxx b/Source/CTest/cmParseBlanketJSCoverage.cxx new file mode 100644 index 000000000..83a7b75eb --- /dev/null +++ b/Source/CTest/cmParseBlanketJSCoverage.cxx @@ -0,0 +1,139 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmParseBlanketJSCoverage.h" + +#include "cmCTest.h" +#include "cmCTestCoverageHandler.h" +#include "cmSystemTools.h" + +#include "cmsys/FStream.hxx" +#include <stdio.h> +#include <stdlib.h> + +class cmParseBlanketJSCoverage::JSONParser +{ +public: + typedef cmCTestCoverageHandlerContainer::SingleFileCoverageVector + FileLinesType; + JSONParser(cmCTestCoverageHandlerContainer& cont) + : Coverage(cont) + { + } + + virtual ~JSONParser() {} + + std::string getValue(std::string const& line, int type) + { + size_t begIndex; + size_t endIndex; + endIndex = line.rfind(','); + begIndex = line.find_first_of(':'); + if (type == 0) { + // A unique substring to remove the extra characters + // around the files name in the JSON (extra " and ,) + std::string foundFileName = + line.substr(begIndex + 3, endIndex - (begIndex + 4)); + return foundFileName; + } + return line.substr(begIndex); + } + bool ParseFile(std::string const& file) + { + FileLinesType localCoverageVector; + std::string filename; + bool foundFile = false; + bool inSource = false; + std::string covResult; + std::string line; + + cmsys::ifstream in(file.c_str()); + if (!in) { + return false; + } + while (cmSystemTools::GetLineFromStream(in, line)) { + if (line.find("filename") != std::string::npos) { + if (foundFile) { + /* + * Upon finding a second file name, generate a + * vector within the total coverage to capture the + * information in the local vector + */ + FileLinesType& CoverageVector = + this->Coverage.TotalCoverage[filename]; + CoverageVector = localCoverageVector; + localCoverageVector.clear(); + } + foundFile = true; + inSource = false; + filename = getValue(line, 0); + } else if ((line.find("coverage") != std::string::npos) && foundFile && + inSource) { + /* + * two types of "coverage" in the JSON structure + * + * The coverage result over the file or set of files + * and the coverage for each individual line + * + * FoundFile and foundSource ensure that + * only the value of the line coverage is captured + */ + std::string result = getValue(line, 1); + result = result.substr(2); + if (result == "\"\"") { + // Empty quotation marks indicate that the + // line is not executable + localCoverageVector.push_back(-1); + } else { + // Else, it contains the number of time executed + localCoverageVector.push_back(atoi(result.c_str())); + } + } else if (line.find("source") != std::string::npos) { + inSource = true; + } + } + + // On exit, capture end of last file covered. + FileLinesType& CoverageVector = this->Coverage.TotalCoverage[filename]; + CoverageVector = localCoverageVector; + localCoverageVector.clear(); + return true; + } + +private: + cmCTestCoverageHandlerContainer& Coverage; +}; + +cmParseBlanketJSCoverage::cmParseBlanketJSCoverage( + cmCTestCoverageHandlerContainer& cont, cmCTest* ctest) + : Coverage(cont) + , CTest(ctest) +{ +} + +bool cmParseBlanketJSCoverage::LoadCoverageData(std::vector<std::string> files) +{ + size_t i = 0; + std::string path; + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Found " << files.size() << " Files" << std::endl, + this->Coverage.Quiet); + for (i = 0; i < files.size(); i++) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Reading JSON File " << files[i] << std::endl, + this->Coverage.Quiet); + + if (!this->ReadJSONFile(files[i])) { + return false; + } + } + return true; +} + +bool cmParseBlanketJSCoverage::ReadJSONFile(std::string const& file) +{ + cmParseBlanketJSCoverage::JSONParser parser(this->Coverage); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Parsing " << file << std::endl, this->Coverage.Quiet); + parser.ParseFile(file); + return true; +} diff --git a/Source/CTest/cmParseBlanketJSCoverage.h b/Source/CTest/cmParseBlanketJSCoverage.h new file mode 100644 index 000000000..696121f21 --- /dev/null +++ b/Source/CTest/cmParseBlanketJSCoverage.h @@ -0,0 +1,42 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmParseBlanketJSCoverage_h +#define cmParseBlanketJSCoverage_h + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <string> +#include <vector> + +class cmCTest; +class cmCTestCoverageHandlerContainer; + +/** \class cmParseBlanketJSCoverage + * \brief Parse BlanketJS coverage information + * + * This class is used to parse BlanketJS(Pascal) coverage information + * generated by the Blanket.js library when used in conjunction with the + * test runner mocha.js, which is used to write out the JSON format. + * + * Blanket.js: + * http://blanketjs.org/ + * + * Mocha.js + * http://visionmedia.github.io/mocha/ + */ +class cmParseBlanketJSCoverage +{ +public: + cmParseBlanketJSCoverage(cmCTestCoverageHandlerContainer& cont, + cmCTest* ctest); + bool LoadCoverageData(std::vector<std::string> files); + // Read the JSON output + bool ReadJSONFile(std::string const& file); + +protected: + class JSONParser; + + cmCTestCoverageHandlerContainer& Coverage; + cmCTest* CTest; +}; +#endif diff --git a/Source/CTest/cmParseCacheCoverage.cxx b/Source/CTest/cmParseCacheCoverage.cxx index 137f344df..629010cf1 100644 --- a/Source/CTest/cmParseCacheCoverage.cxx +++ b/Source/CTest/cmParseCacheCoverage.cxx @@ -1,49 +1,45 @@ -#include "cmStandardIncludes.h" -#include <stdio.h> -#include <stdlib.h> -#include "cmSystemTools.h" #include "cmParseCacheCoverage.h" -#include <cmsys/Directory.hxx> -#include <cmsys/Glob.hxx> +#include "cmCTest.h" +#include "cmCTestCoverageHandler.h" +#include "cmSystemTools.h" + +#include "cmsys/Directory.hxx" +#include "cmsys/FStream.hxx" +#include <map> +#include <stdio.h> +#include <stdlib.h> +#include <utility> cmParseCacheCoverage::cmParseCacheCoverage( - cmCTestCoverageHandlerContainer& cont, - cmCTest* ctest) - :cmParseMumpsCoverage(cont, ctest) + cmCTestCoverageHandlerContainer& cont, cmCTest* ctest) + : cmParseMumpsCoverage(cont, ctest) { } - bool cmParseCacheCoverage::LoadCoverageData(const char* d) { // load all the .mcov files in the specified directory cmsys::Directory dir; - if(!dir.Load(d)) - { + if (!dir.Load(d)) { return false; - } + } size_t numf; unsigned int i; numf = dir.GetNumberOfFiles(); - for (i = 0; i < numf; i++) - { + for (i = 0; i < numf; i++) { std::string file = dir.GetFile(i); - if(file != "." && file != ".." - && !cmSystemTools::FileIsDirectory(file.c_str())) - { + if (file != "." && file != ".." && !cmSystemTools::FileIsDirectory(file)) { std::string path = d; path += "/"; path += file; - if(cmSystemTools::GetFilenameLastExtension(path) == ".cmcov") - { - if(!this->ReadCMCovFile(path.c_str())) - { + if (cmSystemTools::GetFilenameLastExtension(path) == ".cmcov") { + if (!this->ReadCMCovFile(path.c_str())) { return false; - } } } } + } return true; } @@ -54,32 +50,26 @@ void cmParseCacheCoverage::RemoveUnCoveredFiles() // that only have -1 or 0 for the lines. cmCTestCoverageHandlerContainer::TotalCoverageMap::iterator ci = this->Coverage.TotalCoverage.begin(); - while(ci != this->Coverage.TotalCoverage.end()) - { - cmCTestCoverageHandlerContainer::SingleFileCoverageVector& v = - ci->second; + while (ci != this->Coverage.TotalCoverage.end()) { + cmCTestCoverageHandlerContainer::SingleFileCoverageVector& v = ci->second; bool nothing = true; - for(cmCTestCoverageHandlerContainer::SingleFileCoverageVector::iterator i= - v.begin(); i != v.end(); ++i) - { - if(*i > 0) - { + for (cmCTestCoverageHandlerContainer::SingleFileCoverageVector::iterator + i = v.begin(); + i != v.end(); ++i) { + if (*i > 0) { nothing = false; break; - } } - if(nothing) - { - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "No coverage found in: " << ci->first - << std::endl); + } + if (nothing) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "No coverage found in: " << ci->first << std::endl, + this->Coverage.Quiet); this->Coverage.TotalCoverage.erase(ci++); - } - else - { + } else { ++ci; - } } + } } bool cmParseCacheCoverage::SplitString(std::vector<std::string>& args, @@ -87,18 +77,16 @@ bool cmParseCacheCoverage::SplitString(std::vector<std::string>& args, { std::string::size_type pos1 = 0; std::string::size_type pos2 = line.find(',', 0); - if(pos2 == std::string::npos) - { + if (pos2 == std::string::npos) { return false; - } + } std::string arg; - while(pos2 != std::string::npos) - { - arg = line.substr(pos1, pos2-pos1); + while (pos2 != std::string::npos) { + arg = line.substr(pos1, pos2 - pos1); args.push_back(arg); - pos1 = pos2+1; - pos2 = line.find(',',pos1); - } + pos1 = pos2 + 1; + pos2 = line.find(',', pos1); + } arg = line.substr(pos1); args.push_back(arg); return true; @@ -106,115 +94,107 @@ bool cmParseCacheCoverage::SplitString(std::vector<std::string>& args, bool cmParseCacheCoverage::ReadCMCovFile(const char* file) { - std::ifstream in(file); - if(!in) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Can not open : " - << file << "\n"); + cmsys::ifstream in(file); + if (!in) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Can not open : " << file << "\n"); return false; - } + } std::string line; std::vector<std::string> separateLine; - if(!cmSystemTools::GetLineFromStream(in, line)) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Empty file : " - << file << " referenced in this line of cmcov data:\n" - "[" << line << "]\n"); + if (!cmSystemTools::GetLineFromStream(in, line)) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Empty file : " + << file << " referenced in this line of cmcov data:\n" + "[" + << line << "]\n"); return false; - } + } separateLine.clear(); this->SplitString(separateLine, line); - if(separateLine.size() !=4 || separateLine[0] != "Routine" - || separateLine[1] != "Line" || separateLine[2] != "RtnLine" - || separateLine[3] != "Code") - { + if (separateLine.size() != 4 || separateLine[0] != "Routine" || + separateLine[1] != "Line" || separateLine[2] != "RtnLine" || + separateLine[3] != "Code") { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Bad first line of cmcov file : " - << file << " line:\n" - "[" << line << "]\n"); - } + "Bad first line of cmcov file : " << file << " line:\n" + "[" + << line << "]\n"); + } std::string routine; std::string filepath; - while(cmSystemTools::GetLineFromStream(in, line)) - { + while (cmSystemTools::GetLineFromStream(in, line)) { // clear out line argument vector separateLine.clear(); // parse the comma separated line this->SplitString(separateLine, line); // might have more because code could have a quoted , in it // but we only care about the first 3 args anyway - if(separateLine.size() < 4) - { + if (separateLine.size() < 4) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Bad line of cmcov file expected at least 4 found: " - << separateLine.size() << " " - << file << " line:\n" - "[" << line << "]\n"); - for(std::string::size_type i = 0; i < separateLine.size(); ++i) - { - cmCTestLog(this->CTest, ERROR_MESSAGE,"" - << separateLine[1] << " "); - } + << separateLine.size() << " " << file << " line:\n" + "[" + << line << "]\n"); + for (std::string::size_type i = 0; i < separateLine.size(); ++i) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "" << separateLine[1] << " "); + } cmCTestLog(this->CTest, ERROR_MESSAGE, "\n"); return false; - } + } // if we do not have a routine yet, then it should be // the first argument in the vector - if(routine.size() == 0) - { + if (routine.empty()) { routine = separateLine[0]; // Find the full path to the file - if(!this->FindMumpsFile(routine, filepath)) - { + if (!this->FindMumpsFile(routine, filepath)) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "Could not find mumps file for routine: " - << routine << "\n"); + "Could not find mumps file for routine: " << routine + << "\n"); filepath = ""; continue; // move to next line - } } + } // if we have a routine name, check for end of routine - else - { + else { // Totals in arg 0 marks the end of a routine - if(separateLine[0].substr(0, 6) == "Totals") - { + if (separateLine[0].substr(0, 6) == "Totals") { routine = ""; // at the end of this routine filepath = ""; continue; // move to next line - } } + } // if the file path was not found for the routine // move to next line. We should have already warned // after the call to FindMumpsFile that we did not find // it, so don't report again to cut down on output - if(filepath.size() == 0) - { + if (filepath.empty()) { continue; - } + } // now we are ready to set the coverage from the line of data - cmCTestCoverageHandlerContainer::SingleFileCoverageVector& - coverageVector = this->Coverage.TotalCoverage[filepath]; - std::string::size_type linenumber = atoi(separateLine[1].c_str()) -1; + cmCTestCoverageHandlerContainer::SingleFileCoverageVector& coverageVector = + this->Coverage.TotalCoverage[filepath]; + std::string::size_type linenumber = atoi(separateLine[1].c_str()) - 1; int count = atoi(separateLine[2].c_str()); - if(linenumber > coverageVector.size()) - { + if (linenumber > coverageVector.size()) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Parse error line is greater than number of lines in file: " - << linenumber << " " << filepath << "\n"); + << linenumber << " " << filepath << "\n"); continue; // skip setting count to avoid crash - } + } // now add to count for linenumber // for some reason the cache coverage adds extra lines to the // end of the file in some cases. Since they do not exist, we will // mark them as non executable - while(linenumber >= coverageVector.size()) - { + while (linenumber >= coverageVector.size()) { coverageVector.push_back(-1); - } - coverageVector[linenumber] += count; } + // Accounts for lines that were previously marked + // as non-executable code (-1). if the parser comes back with + // a non-zero count, increase the count by 1 to push the line + // into the executable code set in addition to the count found. + if (coverageVector[linenumber] == -1 && count > 0) { + coverageVector[linenumber] += count + 1; + } else { + coverageVector[linenumber] += count; + } + } return true; } diff --git a/Source/CTest/cmParseCacheCoverage.h b/Source/CTest/cmParseCacheCoverage.h index 114eb92d3..005d27258 100644 --- a/Source/CTest/cmParseCacheCoverage.h +++ b/Source/CTest/cmParseCacheCoverage.h @@ -1,20 +1,18 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmParseCacheCoverage_h #define cmParseCacheCoverage_h +#include "cmConfigure.h" + #include "cmParseMumpsCoverage.h" +#include <string> +#include <vector> + +class cmCTest; +class cmCTestCoverageHandlerContainer; + /** \class cmParseCacheCoverage * \brief Parse Cache coverage information * @@ -24,19 +22,17 @@ class cmParseCacheCoverage : public cmParseMumpsCoverage { public: - cmParseCacheCoverage(cmCTestCoverageHandlerContainer& cont, - cmCTest* ctest); + cmParseCacheCoverage(cmCTestCoverageHandlerContainer& cont, cmCTest* ctest); + protected: // implement virtual from parent - bool LoadCoverageData(const char* dir); + bool LoadCoverageData(const char* dir) CM_OVERRIDE; // remove files with no coverage void RemoveUnCoveredFiles(); // Read a single mcov file bool ReadCMCovFile(const char* f); // split a string based on , - bool SplitString(std::vector<std::string>& args, - std::string const& line); + bool SplitString(std::vector<std::string>& args, std::string const& line); }; - #endif diff --git a/Source/CTest/cmParseCoberturaCoverage.cxx b/Source/CTest/cmParseCoberturaCoverage.cxx new file mode 100644 index 000000000..ba55cd7b2 --- /dev/null +++ b/Source/CTest/cmParseCoberturaCoverage.cxx @@ -0,0 +1,171 @@ +#include "cmParseCoberturaCoverage.h" + +#include "cmCTest.h" +#include "cmCTestCoverageHandler.h" +#include "cmSystemTools.h" +#include "cmXMLParser.h" + +#include "cmConfigure.h" +#include "cmsys/FStream.hxx" +#include <stdlib.h> +#include <string.h> + +class cmParseCoberturaCoverage::XMLParser : public cmXMLParser +{ +public: + XMLParser(cmCTest* ctest, cmCTestCoverageHandlerContainer& cont) + : CTest(ctest) + , Coverage(cont) + { + this->InSources = false; + this->InSource = false; + this->SkipThisClass = false; + this->FilePaths.push_back(this->Coverage.SourceDir); + this->FilePaths.push_back(this->Coverage.BinaryDir); + this->CurFileName = ""; + } + + ~XMLParser() CM_OVERRIDE {} + +protected: + void EndElement(const std::string& name) CM_OVERRIDE + { + if (name == "source") { + this->InSource = false; + } else if (name == "sources") { + this->InSources = false; + } else if (name == "class") { + this->SkipThisClass = false; + } + } + + void CharacterDataHandler(const char* data, int length) CM_OVERRIDE + { + std::string tmp; + tmp.insert(0, data, length); + if (this->InSources && this->InSource) { + this->FilePaths.push_back(tmp); + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Adding Source: " << tmp << std::endl, + this->Coverage.Quiet); + } + } + + void StartElement(const std::string& name, const char** atts) CM_OVERRIDE + { + std::string FoundSource; + std::string finalpath; + if (name == "source") { + this->InSource = true; + } else if (name == "sources") { + this->InSources = true; + } else if (name == "class") { + int tagCount = 0; + while (true) { + if (strcmp(atts[tagCount], "filename") == 0) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Reading file: " << atts[tagCount + 1] + << std::endl, + this->Coverage.Quiet); + std::string filename = atts[tagCount + 1]; + this->CurFileName = ""; + + // Check if this is an absolute path that falls within our + // source or binary directories. + for (size_t i = 0; i < FilePaths.size(); i++) { + if (filename.find(FilePaths[i]) == 0) { + this->CurFileName = filename; + break; + } + } + + if (this->CurFileName == "") { + // Check if this is a path that is relative to our source or + // binary directories. + for (size_t i = 0; i < FilePaths.size(); i++) { + finalpath = FilePaths[i] + "/" + filename; + if (cmSystemTools::FileExists(finalpath.c_str())) { + this->CurFileName = finalpath; + break; + } + } + } + + cmsys::ifstream fin(this->CurFileName.c_str()); + if (this->CurFileName == "" || !fin) { + this->CurFileName = + this->Coverage.BinaryDir + "/" + atts[tagCount + 1]; + fin.open(this->CurFileName.c_str()); + if (!fin) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Skipping system file " << filename + << std::endl, + this->Coverage.Quiet); + + this->SkipThisClass = true; + break; + } + } + std::string line; + FileLinesType& curFileLines = + this->Coverage.TotalCoverage[this->CurFileName]; + while (cmSystemTools::GetLineFromStream(fin, line)) { + curFileLines.push_back(-1); + } + + break; + } + ++tagCount; + } + } else if (name == "line") { + int tagCount = 0; + int curNumber = -1; + int curHits = -1; + while (true) { + if (this->SkipThisClass) { + break; + } + if (strcmp(atts[tagCount], "hits") == 0) { + curHits = atoi(atts[tagCount + 1]); + } else if (strcmp(atts[tagCount], "number") == 0) { + curNumber = atoi(atts[tagCount + 1]); + } + + if (curHits > -1 && curNumber > 0) { + FileLinesType& curFileLines = + this->Coverage.TotalCoverage[this->CurFileName]; + { + curFileLines[curNumber - 1] = curHits; + } + break; + } + ++tagCount; + } + } + } + +private: + bool InSources; + bool InSource; + bool SkipThisClass; + std::vector<std::string> FilePaths; + typedef cmCTestCoverageHandlerContainer::SingleFileCoverageVector + FileLinesType; + cmCTest* CTest; + cmCTestCoverageHandlerContainer& Coverage; + std::string CurFileName; +}; + +cmParseCoberturaCoverage::cmParseCoberturaCoverage( + cmCTestCoverageHandlerContainer& cont, cmCTest* ctest) + : Coverage(cont) + , CTest(ctest) +{ +} + +bool cmParseCoberturaCoverage::ReadCoverageXML(const char* xmlFile) +{ + cmParseCoberturaCoverage::XMLParser parser(this->CTest, this->Coverage); + parser.ParseFile(xmlFile); + return true; +} diff --git a/Source/CTest/cmParseCoberturaCoverage.h b/Source/CTest/cmParseCoberturaCoverage.h new file mode 100644 index 000000000..cb6d0976b --- /dev/null +++ b/Source/CTest/cmParseCoberturaCoverage.h @@ -0,0 +1,45 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmParseCoberturaCoverage_h +#define cmParseCoberturaCoverage_h + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <string> +#include <vector> + +class cmCTest; +class cmCTestCoverageHandlerContainer; + +/** \class cmParsePythonCoverage + * \brief Parse coverage.py Python coverage information + * + * This class is used to parse the output of the coverage.py tool that + * is currently maintained by Ned Batchelder. That tool has a command + * that produces xml output in the format typically output by the common + * Java-based Cobertura coverage application. This helper class parses + * that XML file to fill the coverage-handler container. + */ +class cmParseCoberturaCoverage +{ +public: + //! Create the coverage parser by passing in the coverage handler + //! container and the cmCTest object + cmParseCoberturaCoverage(cmCTestCoverageHandlerContainer& cont, + cmCTest* ctest); + + bool inSources; + bool inSource; + std::vector<std::string> filepaths; + //! Read the XML produced by running `coverage xml` + bool ReadCoverageXML(const char* xmlFile); + +private: + class XMLParser; + + cmCTestCoverageHandlerContainer& Coverage; + cmCTest* CTest; + std::string CurFileName; +}; + +#endif diff --git a/Source/CTest/cmParseDelphiCoverage.cxx b/Source/CTest/cmParseDelphiCoverage.cxx new file mode 100644 index 000000000..4b781a6db --- /dev/null +++ b/Source/CTest/cmParseDelphiCoverage.cxx @@ -0,0 +1,229 @@ +#include "cmParseDelphiCoverage.h" + +#include "cmCTest.h" +#include "cmCTestCoverageHandler.h" +#include "cmSystemTools.h" + +#include "cmsys/FStream.hxx" +#include "cmsys/Glob.hxx" +#include <stdio.h> +#include <stdlib.h> + +class cmParseDelphiCoverage::HTMLParser +{ +public: + typedef cmCTestCoverageHandlerContainer::SingleFileCoverageVector + FileLinesType; + HTMLParser(cmCTest* ctest, cmCTestCoverageHandlerContainer& cont) + : CTest(ctest) + , Coverage(cont) + { + } + + virtual ~HTMLParser() {} + + bool initializeDelphiFile( + std::string const& filename, + cmParseDelphiCoverage::HTMLParser::FileLinesType& coverageVector) + { + std::string line; + size_t comPos; + size_t semiPos; + bool blockComFlag = false; + bool lineComFlag = false; + std::vector<std::string> beginSet; + cmsys::ifstream in(filename.c_str()); + if (!in) { + return false; + } + while (cmSystemTools::GetLineFromStream(in, line)) { + lineComFlag = false; + // Unique cases found in lines. + size_t beginPos = line.find("begin"); + + // Check that the begin is the first non-space string on the line + if ((beginPos == line.find_first_not_of(' ')) && + beginPos != std::string::npos) { + beginSet.push_back("begin"); + coverageVector.push_back(-1); + continue; + } + if (line.find('{') != std::string::npos) { + blockComFlag = true; + } else if (line.find('}') != std::string::npos) { + blockComFlag = false; + coverageVector.push_back(-1); + continue; + } else if ((line.find("end;") != std::string::npos) && + !beginSet.empty()) { + beginSet.pop_back(); + coverageVector.push_back(-1); + continue; + } + + // This checks for comments after lines of code, finding the + // comment symbol after the ending semicolon. + comPos = line.find("//"); + if (comPos != std::string::npos) { + semiPos = line.find(';'); + if (comPos < semiPos) { + lineComFlag = true; + } + } + // Based up what was found, add a line to the coverageVector + if (!beginSet.empty() && line != "" && !blockComFlag && !lineComFlag) { + coverageVector.push_back(0); + } else { + coverageVector.push_back(-1); + } + } + return true; + } + bool ParseFile(const char* file) + { + std::string line = file; + std::string lineresult; + std::string lastroutine; + std::string filename; + std::string filelineoffset; + size_t afterLineNum = 0; + size_t lastoffset = 0; + size_t endcovpos = 0; + size_t endnamepos = 0; + size_t pos = 0; + + /* + * This first 'while' section goes through the found HTML + * file name and attempts to capture the source file name + * which is set as part of the HTML file name: the name of + * the file is found in parenthesis '()' + * + * See test HTML file name: UTCovTest(UTCovTest.pas).html. + * + * Find the text inside each pair of parenthesis and check + * to see if it ends in '.pas'. If it can't be found, + * exit the function. + */ + while (true) { + lastoffset = line.find('(', pos); + if (lastoffset == std::string::npos) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, endnamepos + << "File not found " << lastoffset << std::endl, + this->Coverage.Quiet); + return false; + } + endnamepos = line.find(')', lastoffset); + filename = line.substr(lastoffset + 1, (endnamepos - 1) - lastoffset); + if (filename.find(".pas") != std::string::npos) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Coverage found for file: " << filename + << std::endl, + this->Coverage.Quiet); + break; + } + pos = lastoffset + 1; + } + /* + * Glob through the source directory for the + * file found above + */ + cmsys::Glob gl; + gl.RecurseOn(); + gl.RecurseThroughSymlinksOff(); + std::string glob = Coverage.SourceDir + "*/" + filename; + gl.FindFiles(glob); + std::vector<std::string> const& files = gl.GetFiles(); + if (files.empty()) { + /* + * If that doesn't find any matching files + * return a failure. + */ + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Unable to find file matching" << glob << std::endl, + this->Coverage.Quiet); + return false; + } + FileLinesType& coverageVector = this->Coverage.TotalCoverage[files[0]]; + + /* + * Initialize the file to have all code between 'begin' and + * 'end' tags marked as executable + */ + + this->initializeDelphiFile(files[0], coverageVector); + + cmsys::ifstream in(file); + if (!in) { + return false; + } + + /* + * Now read the HTML file, looking for the lines that have an + * "inline" in it. Then parse out the "class" value of that + * line to determine if the line is executed or not. + * + * Sample HTML line: + * + * <tr class="covered"><td>47</td><td><pre style="display:inline;"> + * CheckEquals(1,2-1);</pre></td></tr> + * + */ + + while (cmSystemTools::GetLineFromStream(in, line)) { + if (line.find("inline") == std::string::npos) { + continue; + } + + lastoffset = line.find("class="); + endcovpos = line.find('>', lastoffset); + lineresult = line.substr(lastoffset + 7, (endcovpos - 8) - lastoffset); + + if (lineresult == "covered") { + afterLineNum = line.find('<', endcovpos + 5); + filelineoffset = + line.substr(endcovpos + 5, afterLineNum - (endcovpos + 5)); + coverageVector[atoi(filelineoffset.c_str()) - 1] = 1; + } + } + return true; + } + +private: + cmCTest* CTest; + cmCTestCoverageHandlerContainer& Coverage; +}; + +cmParseDelphiCoverage::cmParseDelphiCoverage( + cmCTestCoverageHandlerContainer& cont, cmCTest* ctest) + : Coverage(cont) + , CTest(ctest) +{ +} + +bool cmParseDelphiCoverage::LoadCoverageData( + std::vector<std::string> const& files) +{ + size_t i; + std::string path; + size_t numf = files.size(); + for (i = 0; i < numf; i++) { + path = files[i]; + + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Reading HTML File " << path << std::endl, + this->Coverage.Quiet); + if (cmSystemTools::GetFilenameLastExtension(path) == ".html") { + if (!this->ReadDelphiHTML(path.c_str())) { + return false; + } + } + } + return true; +} + +bool cmParseDelphiCoverage::ReadDelphiHTML(const char* file) +{ + cmParseDelphiCoverage::HTMLParser parser(this->CTest, this->Coverage); + parser.ParseFile(file); + return true; +} diff --git a/Source/CTest/cmParseDelphiCoverage.h b/Source/CTest/cmParseDelphiCoverage.h new file mode 100644 index 000000000..1b374050a --- /dev/null +++ b/Source/CTest/cmParseDelphiCoverage.h @@ -0,0 +1,38 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmParseDelphiCoverage_h +#define cmParseDelphiCoverage_h + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <string> +#include <vector> + +class cmCTest; +class cmCTestCoverageHandlerContainer; + +/** \class cmParseDelphiCoverage + * \brief Parse Delphi coverage information + * + * This class is used to parse Delphi(Pascal) coverage information + * generated by the Delphi-Code-Coverage tool + * + * https://code.google.com/p/delphi-code-coverage/ + */ + +class cmParseDelphiCoverage +{ +public: + cmParseDelphiCoverage(cmCTestCoverageHandlerContainer& cont, cmCTest* ctest); + bool LoadCoverageData(std::vector<std::string> const& files); + bool ReadDelphiHTML(const char* file); + // Read a single HTML file from output + bool ReadHTMLFile(const char* f); + +protected: + class HTMLParser; + + cmCTestCoverageHandlerContainer& Coverage; + cmCTest* CTest; +}; +#endif diff --git a/Source/CTest/cmParseGTMCoverage.cxx b/Source/CTest/cmParseGTMCoverage.cxx index 6b4adb4bd..e4ee699c4 100644 --- a/Source/CTest/cmParseGTMCoverage.cxx +++ b/Source/CTest/cmParseGTMCoverage.cxx @@ -1,70 +1,64 @@ -#include "cmStandardIncludes.h" -#include <stdio.h> -#include <stdlib.h> -#include "cmSystemTools.h" #include "cmParseGTMCoverage.h" -#include <cmsys/Directory.hxx> -#include <cmsys/Glob.hxx> +#include "cmCTest.h" +#include "cmCTestCoverageHandler.h" +#include "cmSystemTools.h" + +#include "cmsys/Directory.hxx" +#include "cmsys/FStream.hxx" +#include <map> +#include <stdio.h> +#include <stdlib.h> +#include <vector> cmParseGTMCoverage::cmParseGTMCoverage(cmCTestCoverageHandlerContainer& cont, cmCTest* ctest) - :cmParseMumpsCoverage(cont, ctest) + : cmParseMumpsCoverage(cont, ctest) { } - bool cmParseGTMCoverage::LoadCoverageData(const char* d) { // load all the .mcov files in the specified directory cmsys::Directory dir; - if(!dir.Load(d)) - { + if (!dir.Load(d)) { return false; - } + } size_t numf; unsigned int i; numf = dir.GetNumberOfFiles(); - for (i = 0; i < numf; i++) - { + for (i = 0; i < numf; i++) { std::string file = dir.GetFile(i); - if(file != "." && file != ".." - && !cmSystemTools::FileIsDirectory(file.c_str())) - { + if (file != "." && file != ".." && !cmSystemTools::FileIsDirectory(file)) { std::string path = d; path += "/"; path += file; - if(cmSystemTools::GetFilenameLastExtension(path) == ".mcov") - { - if(!this->ReadMCovFile(path.c_str())) - { + if (cmSystemTools::GetFilenameLastExtension(path) == ".mcov") { + if (!this->ReadMCovFile(path.c_str())) { return false; - } } } } + } return true; } bool cmParseGTMCoverage::ReadMCovFile(const char* file) { - std::ifstream in(file); - if(!in) - { + cmsys::ifstream in(file); + if (!in) { return false; - } + } std::string line; std::string lastfunction; std::string lastroutine; std::string lastpath; int lastoffset = 0; - while( cmSystemTools::GetLineFromStream(in, line)) - { + while (cmSystemTools::GetLineFromStream(in, line)) { // only look at lines that have coverage data - if(line.find("^ZZCOVERAGE") == line.npos) - { + if (line.find("^ZZCOVERAGE") == std::string::npos) { continue; - } + } std::string filepath; std::string function; std::string routine; @@ -72,54 +66,51 @@ bool cmParseGTMCoverage::ReadMCovFile(const char* file) int count = 0; this->ParseMCOVLine(line, routine, function, linenumber, count); // skip this one - if(routine == "RSEL") - { + if (routine == "RSEL") { continue; - } + } // no need to search the file if we just did it - if(function == lastfunction && lastroutine == routine) - { - if(lastpath.size()) - { - this->Coverage.TotalCoverage[lastpath][lastoffset + linenumber] - += count; - } - else - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Can not find mumps file : " - << lastroutine << - " referenced in this line of mcov data:\n" - "[" << line << "]\n"); - } - continue; + if (function == lastfunction && lastroutine == routine) { + if (!lastpath.empty()) { + this->Coverage.TotalCoverage[lastpath][lastoffset + linenumber] += + count; + } else { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Can not find mumps file : " + << lastroutine + << " referenced in this line of mcov data:\n" + "[" + << line << "]\n"); } + continue; + } // Find the full path to the file bool found = this->FindMumpsFile(routine, filepath); - if(found) - { + if (found) { int lineoffset = 0; - if(this->FindFunctionInMumpsFile(filepath, - function, - lineoffset)) - { + if (this->FindFunctionInMumpsFile(filepath, function, lineoffset)) { cmCTestCoverageHandlerContainer::SingleFileCoverageVector& coverageVector = this->Coverage.TotalCoverage[filepath]; - coverageVector[lineoffset + linenumber] += count; - lastoffset = lineoffset; + // This section accounts for lines that were previously marked + // as non-executable code (-1), if the parser comes back with + // a non-zero count, increase the count by 1 to push the line + // into the executable code set in addtion to the count found. + if (coverageVector[lineoffset + linenumber] == -1 && count > 0) { + coverageVector[lineoffset + linenumber] += count + 1; + } else { + coverageVector[lineoffset + linenumber] += count; } + lastoffset = lineoffset; } - else - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Can not find mumps file : " - << routine << " referenced in this line of mcov data:\n" - "[" << line << "]\n"); - } + } else { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Can not find mumps file : " + << routine << " referenced in this line of mcov data:\n" + "[" + << line << "]\n"); + } lastfunction = function; lastroutine = routine; lastpath = filepath; - } + } return true; } @@ -127,48 +118,40 @@ bool cmParseGTMCoverage::FindFunctionInMumpsFile(std::string const& filepath, std::string const& function, int& lineoffset) { - std::ifstream in(filepath.c_str()); - if(!in) - { + cmsys::ifstream in(filepath.c_str()); + if (!in) { return false; - } + } std::string line; int linenum = 0; - while( cmSystemTools::GetLineFromStream(in, line)) - { - std::string::size_type pos = line.find(function.c_str()); - if(pos == 0) - { + while (cmSystemTools::GetLineFromStream(in, line)) { + std::string::size_type pos = line.find(function); + if (pos == 0) { char nextchar = line[function.size()]; - if(nextchar == ' ' || nextchar == '(') - { + if (nextchar == ' ' || nextchar == '(' || nextchar == '\t') { lineoffset = linenum; return true; - } } - if(pos == 1) - { + } + if (pos == 1) { char prevchar = line[0]; - char nextchar = line[function.size()+1]; - if(prevchar == '%' && (nextchar == ' ' || nextchar == '(')) - { + char nextchar = line[function.size() + 1]; + if (prevchar == '%' && (nextchar == ' ' || nextchar == '(')) { lineoffset = linenum; return true; - } } - linenum++; // move to next line count } + linenum++; // move to next line count + } lineoffset = 0; - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Could not find entry point : " - << function << " in " << filepath << "\n"); + cmCTestLog(this->CTest, ERROR_MESSAGE, "Could not find entry point : " + << function << " in " << filepath << "\n"); return false; } bool cmParseGTMCoverage::ParseMCOVLine(std::string const& line, std::string& routine, - std::string& function, - int& linenumber, + std::string& function, int& linenumber, int& count) { // this method parses lines from the .mcov file @@ -181,63 +164,52 @@ bool cmParseGTMCoverage::ParseMCOVLine(std::string const& line, // ( file , entry ) = "number_executed:timing_info" // ^COVERAGE("%RSEL","init",8,"FOR_LOOP",1)=1 // ( file , entry, line, IGNORE ) =number_executed - std::vector<cmStdString> args; + std::vector<std::string> args; std::string::size_type pos = line.find('(', 0); // if no ( is found, then return line has no coverage - if(pos == std::string::npos) - { + if (pos == std::string::npos) { return false; - } + } std::string arg; bool done = false; // separate out all of the comma separated arguments found // in the COVERAGE(...) line - while(line[pos] && !done) - { + while (line[pos] && !done) { // save the char we are looking at char cur = line[pos]; // , or ) means end of argument - if(cur == ',' || cur == ')') - { + if (cur == ',' || cur == ')') { // save the argument into the argument vector args.push_back(arg); // start on a new argument arg = ""; // if we are at the end of the ), then finish while loop - if(cur == ')') - { + if (cur == ')') { done = true; - } } - else - { + } else { // all chars except ", (, and % get stored in the arg string - if(cur != '\"' && cur != '(' && cur != '%') - { + if (cur != '\"' && cur != '(' && cur != '%') { arg.append(1, line[pos]); - } } + } // move to next char pos++; - } + } // now parse the right hand side of the = pos = line.find('='); // no = found, this is an error - if(pos == line.npos) - { + if (pos == std::string::npos) { return false; - } + } pos++; // move past = // if the next positing is not a ", then this is a // COVERAGE(..)=count line and turn the rest of the string // past the = into an integer and set it to count - if(line[pos] != '\"') - { + if (line[pos] != '\"') { count = atoi(line.substr(pos).c_str()); - } - else - { + } else { // this means line[pos] is a ", and we have a // COVERAGE(...)="1:0:0:0" type of line pos++; // move past " @@ -245,28 +217,28 @@ bool cmParseGTMCoverage::ParseMCOVLine(std::string const& line, std::string::size_type pos2 = line.find(':', pos); // turn the string between the " and the first : into an integer // and set it to count - count = atoi(line.substr(pos, pos2-pos).c_str()); - } + count = atoi(line.substr(pos, pos2 - pos).c_str()); + } // less then two arguments is an error - if(args.size() < 2) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Error parsing mcov line: [" << line << "]\n"); + if (args.size() < 2) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Error parsing mcov line: [" + << line << "]\n"); return false; - } - routine = args[0]; // the routine is the first argument + } + routine = args[0]; // the routine is the first argument function = args[1]; // the function in the routine is the second // in the two argument only format // ^COVERAGE("%RSEL","SRC"), the line offset is 0 - if(args.size() == 2) - { - linenumber = 0; - } - else - { + if (args.size() == 2) { + // To avoid double counting of line 0 of each entry point, + // Don't count the lines that do not give an explicit line + // number. + routine = ""; + function = ""; + } else { // this is the format for this line // ^COVERAGE("%RSEL","SRC",count) linenumber = atoi(args[2].c_str()); - } + } return true; } diff --git a/Source/CTest/cmParseGTMCoverage.h b/Source/CTest/cmParseGTMCoverage.h index c6d7ef919..c4949d416 100644 --- a/Source/CTest/cmParseGTMCoverage.h +++ b/Source/CTest/cmParseGTMCoverage.h @@ -1,20 +1,17 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmParseGTMCoverage_h #define cmParseGTMCoverage_h +#include "cmConfigure.h" + #include "cmParseMumpsCoverage.h" +#include <string> + +class cmCTest; +class cmCTestCoverageHandlerContainer; + /** \class cmParseGTMCoverage * \brief Parse GTM coverage information * @@ -24,26 +21,21 @@ class cmParseGTMCoverage : public cmParseMumpsCoverage { public: - cmParseGTMCoverage(cmCTestCoverageHandlerContainer& cont, - cmCTest* ctest); + cmParseGTMCoverage(cmCTestCoverageHandlerContainer& cont, cmCTest* ctest); + protected: // implement virtual from parent - bool LoadCoverageData(const char* dir); + bool LoadCoverageData(const char* dir) CM_OVERRIDE; // Read a single mcov file bool ReadMCovFile(const char* f); // find out what line in a mumps file (filepath) the given entry point // or function is. lineoffset is set by this method. bool FindFunctionInMumpsFile(std::string const& filepath, - std::string const& function, - int& lineoffset); + std::string const& function, int& lineoffset); // parse a line from a .mcov file, and fill in the // routine, function, linenumber and coverage count - bool ParseMCOVLine(std::string const& line, - std::string& routine, - std::string& function, - int& linenumber, - int& count); + bool ParseMCOVLine(std::string const& line, std::string& routine, + std::string& function, int& linenumber, int& count); }; - #endif diff --git a/Source/CTest/cmParseJacocoCoverage.cxx b/Source/CTest/cmParseJacocoCoverage.cxx new file mode 100644 index 000000000..d15864a79 --- /dev/null +++ b/Source/CTest/cmParseJacocoCoverage.cxx @@ -0,0 +1,184 @@ +#include "cmParseJacocoCoverage.h" + +#include "cmConfigure.h" + +#include "cmCTest.h" +#include "cmCTestCoverageHandler.h" +#include "cmSystemTools.h" +#include "cmXMLParser.h" + +#include "cmsys/Directory.hxx" +#include "cmsys/FStream.hxx" +#include "cmsys/Glob.hxx" +#include <stdlib.h> +#include <string.h> + +class cmParseJacocoCoverage::XMLParser : public cmXMLParser +{ +public: + XMLParser(cmCTest* ctest, cmCTestCoverageHandlerContainer& cont) + : CTest(ctest) + , Coverage(cont) + { + this->FilePath = ""; + this->PackagePath = ""; + this->PackageName = ""; + } + + ~XMLParser() CM_OVERRIDE {} + +protected: + void EndElement(const std::string& /*name*/) CM_OVERRIDE {} + + void StartElement(const std::string& name, const char** atts) CM_OVERRIDE + { + if (name == "package") { + this->PackageName = atts[1]; + this->PackagePath = ""; + } else if (name == "sourcefile") { + std::string fileName = atts[1]; + + if (this->PackagePath == "") { + if (!this->FindPackagePath(fileName)) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot find file: " + << this->PackageName << "/" << fileName << std::endl); + this->Coverage.Error++; + return; + } + } + + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Reading file: " << fileName << std::endl, + this->Coverage.Quiet); + + this->FilePath = this->PackagePath + "/" + fileName; + cmsys::ifstream fin(this->FilePath.c_str()); + if (!fin) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Jacoco Coverage: Error opening " << this->FilePath + << std::endl); + } + std::string line; + FileLinesType& curFileLines = + this->Coverage.TotalCoverage[this->FilePath]; + if (fin) { + curFileLines.push_back(-1); + } + while (cmSystemTools::GetLineFromStream(fin, line)) { + curFileLines.push_back(-1); + } + } else if (name == "line") { + int tagCount = 0; + int nr = -1; + int ci = -1; + while (true) { + if (strcmp(atts[tagCount], "ci") == 0) { + ci = atoi(atts[tagCount + 1]); + } else if (strcmp(atts[tagCount], "nr") == 0) { + nr = atoi(atts[tagCount + 1]); + } + if (ci > -1 && nr > 0) { + FileLinesType& curFileLines = + this->Coverage.TotalCoverage[this->FilePath]; + if (!curFileLines.empty()) { + curFileLines[nr - 1] = ci; + } + break; + } + ++tagCount; + } + } + } + + virtual bool FindPackagePath(std::string const& fileName) + { + // Search for the source file in the source directory. + if (this->PackagePathFound(fileName, this->Coverage.SourceDir)) { + return true; + } + + // If not found there, check the binary directory. + if (this->PackagePathFound(fileName, this->Coverage.BinaryDir)) { + return true; + } + return false; + } + + virtual bool PackagePathFound(std::string const& fileName, + std::string const& baseDir) + { + // Search for the file in the baseDir and its subdirectories. + std::string packageGlob = baseDir; + packageGlob += "/"; + packageGlob += fileName; + cmsys::Glob gl; + gl.RecurseOn(); + gl.RecurseThroughSymlinksOn(); + gl.FindFiles(packageGlob); + std::vector<std::string> const& files = gl.GetFiles(); + if (files.empty()) { + return false; + } + + // Check if any of the locations found match our package. + for (std::vector<std::string>::const_iterator fi = files.begin(); + fi != files.end(); ++fi) { + std::string dir = cmsys::SystemTools::GetParentDirectory(*fi); + if (cmsys::SystemTools::StringEndsWith(dir, this->PackageName.c_str())) { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Found package directory for " << fileName << ": " + << dir << std::endl, + this->Coverage.Quiet); + this->PackagePath = dir; + return true; + } + } + return false; + } + +private: + std::string FilePath; + std::string PackagePath; + std::string PackageName; + typedef cmCTestCoverageHandlerContainer::SingleFileCoverageVector + FileLinesType; + cmCTest* CTest; + cmCTestCoverageHandlerContainer& Coverage; +}; + +cmParseJacocoCoverage::cmParseJacocoCoverage( + cmCTestCoverageHandlerContainer& cont, cmCTest* ctest) + : Coverage(cont) + , CTest(ctest) +{ +} + +bool cmParseJacocoCoverage::LoadCoverageData( + std::vector<std::string> const& files) +{ + // load all the jacoco.xml files in the source directory + cmsys::Directory dir; + size_t i; + std::string path; + size_t numf = files.size(); + for (i = 0; i < numf; i++) { + path = files[i]; + + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Reading XML File " << path << std::endl, + this->Coverage.Quiet); + if (cmSystemTools::GetFilenameLastExtension(path) == ".xml") { + if (!this->ReadJacocoXML(path.c_str())) { + return false; + } + } + } + return true; +} + +bool cmParseJacocoCoverage::ReadJacocoXML(const char* file) +{ + cmParseJacocoCoverage::XMLParser parser(this->CTest, this->Coverage); + parser.ParseFile(file); + return true; +} diff --git a/Source/CTest/cmParseJacocoCoverage.h b/Source/CTest/cmParseJacocoCoverage.h new file mode 100644 index 000000000..f2aec6db9 --- /dev/null +++ b/Source/CTest/cmParseJacocoCoverage.h @@ -0,0 +1,53 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmParseJacocoCoverage_h +#define cmParseJacocoCoverage_h + +#include "cmConfigure.h" // IWYU pragma: keep + +#include <map> +#include <string> +#include <vector> + +class cmCTest; +class cmCTestCoverageHandlerContainer; + +/** \class cmParseJacocoCoverage + * \brief Parse JaCoCO coverage information + * + * This class is used to parse coverage information for + * java using the JaCoCo tool: + * + * http://www.eclemma.org/jacoco/trunk/index.html + */ +class cmParseJacocoCoverage +{ +public: + cmParseJacocoCoverage(cmCTestCoverageHandlerContainer& cont, cmCTest* ctest); + bool LoadCoverageData(std::vector<std::string> const& files); + + std::string PackageName; + std::string FileName; + std::string ModuleName; + std::string CurFileName; + +private: + // implement virtual from parent + // remove files with no coverage + void RemoveUnCoveredFiles(); + // Read a single mcov file + bool ReadJacocoXML(const char* f); + // split a string based on , + bool SplitString(std::vector<std::string>& args, std::string const& line); + bool FindJavaFile(std::string const& routine, std::string& filepath); + void InitializeJavaFile(std::string& file); + bool LoadSource(std::string d); + + class XMLParser; + + std::map<std::string, std::string> RoutineToDirectory; + cmCTestCoverageHandlerContainer& Coverage; + cmCTest* CTest; +}; + +#endif diff --git a/Source/CTest/cmParseMumpsCoverage.cxx b/Source/CTest/cmParseMumpsCoverage.cxx index 37e8bd082..eb29f55c0 100644 --- a/Source/CTest/cmParseMumpsCoverage.cxx +++ b/Source/CTest/cmParseMumpsCoverage.cxx @@ -1,16 +1,21 @@ -#include "cmStandardIncludes.h" -#include <stdio.h> -#include <stdlib.h> +#include "cmParseMumpsCoverage.h" + +#include "cmCTest.h" +#include "cmCTestCoverageHandler.h" #include "cmSystemTools.h" -#include "cmParseGTMCoverage.h" -#include <cmsys/Directory.hxx> -#include <cmsys/Glob.hxx> +#include "cmConfigure.h" +#include "cmsys/FStream.hxx" +#include "cmsys/Glob.hxx" +#include <map> +#include <string> +#include <utility> +#include <vector> cmParseMumpsCoverage::cmParseMumpsCoverage( - cmCTestCoverageHandlerContainer& cont, - cmCTest* ctest) - :Coverage(cont), CTest(ctest) + cmCTestCoverageHandlerContainer& cont, cmCTest* ctest) + : Coverage(cont) + , CTest(ctest) { } @@ -23,96 +28,82 @@ bool cmParseMumpsCoverage::ReadCoverageFile(const char* file) // Read the gtm_coverage.mcov file, that has two lines of data: // packages:/full/path/to/Vista/Packages // coverage_dir:/full/path/to/dir/with/*.mcov - std::ifstream in(file); - if(!in) - { + cmsys::ifstream in(file); + if (!in) { return false; - } + } std::string line; - while(cmSystemTools::GetLineFromStream(in, line)) - { + while (cmSystemTools::GetLineFromStream(in, line)) { std::string::size_type pos = line.find(':', 0); std::string packages; - if(pos != std::string::npos) - { + if (pos != std::string::npos) { std::string type = line.substr(0, pos); - std::string path = line.substr(pos+1); - if(type == "packages") - { + std::string path = line.substr(pos + 1); + if (type == "packages") { this->LoadPackages(path.c_str()); - } - else if(type == "coverage_dir") - { + } else if (type == "coverage_dir") { this->LoadCoverageData(path.c_str()); - } - else - { + } else { cmCTestLog(this->CTest, ERROR_MESSAGE, "Parse Error in Mumps coverage file :\n" - << file << - "\ntype: [" << type << "]\npath:[" << path << "]\n" - "input line: [" << line << "]\n"); - } + << file << "\ntype: [" << type << "]\npath:[" << path + << "]\n" + "input line: [" + << line << "]\n"); } } + } return true; } void cmParseMumpsCoverage::InitializeMumpsFile(std::string& file) { // initialize the coverage information for a given mumps file - std::ifstream in(file.c_str()); - if(!in) - { + cmsys::ifstream in(file.c_str()); + if (!in) { return; - } + } std::string line; - cmCTestCoverageHandlerContainer::SingleFileCoverageVector& - coverageVector = this->Coverage.TotalCoverage[file]; - if(!cmSystemTools::GetLineFromStream(in, line)) - { + cmCTestCoverageHandlerContainer::SingleFileCoverageVector& coverageVector = + this->Coverage.TotalCoverage[file]; + if (!cmSystemTools::GetLineFromStream(in, line)) { return; - } + } // first line of a .m file can never be run coverageVector.push_back(-1); - while( cmSystemTools::GetLineFromStream(in, line) ) - { + while (cmSystemTools::GetLineFromStream(in, line)) { // putting in a 0 for a line means it is executable code // putting in a -1 for a line means it is not executable code int val = -1; // assume line is not executable bool found = false; std::string::size_type i = 0; // (1) Search for the first whitespace or semicolon character on a line. - //This will skip over labels if the line starts with one, or will simply - //be the first character on the line for non-label lines. - for(; i < line.size(); ++i) - { - if(line[i] == ' ' || line[i] == '\t' || line[i] == ';') - { + // This will skip over labels if the line starts with one, or will simply + // be the first character on the line for non-label lines. + for (; i < line.size(); ++i) { + if (line[i] == ' ' || line[i] == '\t' || line[i] == ';') { found = true; break; - } } - if(found) - { - // (2) If the first character found above is whitespace then continue the - // search for the first following non-whitespace character. - if(line[i] == ' ' || line[i] == '\t') - { - while(i < line.size() && (line[i] == ' ' || line[i] == '\t')) - { + } + if (found) { + // (2) If the first character found above is whitespace or a period + // then continue the search for the first following non-whitespace + // character. + if (line[i] == ' ' || line[i] == '\t') { + while (i < line.size() && + (line[i] == ' ' || line[i] == '\t' || line[i] == '.')) { i++; - } } + } // (3) If the character found is not a semicolon then the line counts for // coverage. - if(i < line.size() && line[i] != ';') - { + if (i < line.size() && line[i] != ';') { val = 0; - } } - coverageVector.push_back(val); } + coverageVector.push_back(val); + } } bool cmParseMumpsCoverage::LoadPackages(const char* d) @@ -121,45 +112,37 @@ bool cmParseMumpsCoverage::LoadPackages(const char* d) glob.RecurseOn(); std::string pat = d; pat += "/*.m"; - glob.FindFiles(pat.c_str()); + glob.FindFiles(pat); std::vector<std::string>& files = glob.GetFiles(); std::vector<std::string>::iterator fileIt; - for ( fileIt = files.begin(); fileIt != files.end(); - ++ fileIt ) - { + for (fileIt = files.begin(); fileIt != files.end(); ++fileIt) { std::string name = cmSystemTools::GetFilenameName(*fileIt); - this->RoutineToDirectory[name.substr(0, name.size()-2)] = *fileIt; + this->RoutineToDirectory[name.substr(0, name.size() - 2)] = *fileIt; // initialze each file, this is left out until CDash is fixed // to handle large numbers of files this->InitializeMumpsFile(*fileIt); - } + } return true; } bool cmParseMumpsCoverage::FindMumpsFile(std::string const& routine, std::string& filepath) { - std::map<cmStdString, cmStdString>::iterator i = + std::map<std::string, std::string>::iterator i = this->RoutineToDirectory.find(routine); - if(i != this->RoutineToDirectory.end()) - { + if (i != this->RoutineToDirectory.end()) { filepath = i->second; return true; + } + // try some alternate names + const char* tryname[] = { "GUX", "GTM", "ONT", CM_NULLPTR }; + for (int k = 0; tryname[k] != CM_NULLPTR; k++) { + std::string routine2 = routine + tryname[k]; + i = this->RoutineToDirectory.find(routine2); + if (i != this->RoutineToDirectory.end()) { + filepath = i->second; + return true; } - else - { - // try some alternate names - const char* tryname[] = {"GUX", "GTM", "ONT", 0}; - for(int k=0; tryname[k] != 0; k++) - { - std::string routine2 = routine + tryname[k]; - i = this->RoutineToDirectory.find(routine2); - if(i != this->RoutineToDirectory.end()) - { - filepath = i->second; - return true; - } - } - } + } return false; } diff --git a/Source/CTest/cmParseMumpsCoverage.h b/Source/CTest/cmParseMumpsCoverage.h index c1effa79b..2c544954b 100644 --- a/Source/CTest/cmParseMumpsCoverage.h +++ b/Source/CTest/cmParseMumpsCoverage.h @@ -1,20 +1,15 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmParseMumpsCoverage_h #define cmParseMumpsCoverage_h -#include "cmStandardIncludes.h" -#include "cmCTestCoverageHandler.h" +#include "cmConfigure.h" // IWYU pragma: keep + +#include <map> +#include <string> + +class cmCTest; +class cmCTestCoverageHandlerContainer; /** \class cmParseMumpsCoverage * \brief Parse Mumps coverage information @@ -25,12 +20,12 @@ class cmParseMumpsCoverage { public: - cmParseMumpsCoverage(cmCTestCoverageHandlerContainer& cont, - cmCTest* ctest); + cmParseMumpsCoverage(cmCTestCoverageHandlerContainer& cont, cmCTest* ctest); virtual ~cmParseMumpsCoverage(); // This is the toplevel coverage file locating the coverage files // and the mumps source code package tree. bool ReadCoverageFile(const char* file); + protected: // sub classes will use this to // load all coverage files found in the given directory @@ -41,10 +36,10 @@ protected: // initialize the coverage information for a single mumps file void InitializeMumpsFile(std::string& file); // Find mumps file for routine - bool FindMumpsFile(std::string const& routine, - std::string& filepath); + bool FindMumpsFile(std::string const& routine, std::string& filepath); + protected: - std::map<cmStdString, cmStdString> RoutineToDirectory; + std::map<std::string, std::string> RoutineToDirectory; cmCTestCoverageHandlerContainer& Coverage; cmCTest* CTest; }; diff --git a/Source/CTest/cmParsePHPCoverage.cxx b/Source/CTest/cmParsePHPCoverage.cxx index 593b2d1a8..761ebec65 100644 --- a/Source/CTest/cmParsePHPCoverage.cxx +++ b/Source/CTest/cmParsePHPCoverage.cxx @@ -1,7 +1,13 @@ -#include "cmStandardIncludes.h" -#include "cmSystemTools.h" #include "cmParsePHPCoverage.h" -#include <cmsys/Directory.hxx> + +#include "cmCTest.h" +#include "cmCTestCoverageHandler.h" +#include "cmSystemTools.h" + +#include "cmsys/Directory.hxx" +#include "cmsys/FStream.hxx" +#include <stdlib.h> +#include <string.h> /* To setup coverage for php. @@ -15,55 +21,45 @@ */ cmParsePHPCoverage::cmParsePHPCoverage(cmCTestCoverageHandlerContainer& cont, - cmCTest* ctest) - :Coverage(cont), CTest(ctest) + cmCTest* ctest) + : Coverage(cont) + , CTest(ctest) { } -bool cmParsePHPCoverage::ReadUntil(std::ifstream& in, char until) +bool cmParsePHPCoverage::ReadUntil(std::istream& in, char until) { char c = 0; - while(in.get(c) && c != until) - { - } - if(c != until) - { - return false; - } - return true; + while (in.get(c) && c != until) { + } + return c == until; } -bool cmParsePHPCoverage::ReadCoverageArray(std::ifstream& in, - cmStdString const& fileName) +bool cmParsePHPCoverage::ReadCoverageArray(std::istream& in, + std::string const& fileName) { - cmCTestCoverageHandlerContainer::SingleFileCoverageVector& coverageVector - = this->Coverage.TotalCoverage[fileName]; + cmCTestCoverageHandlerContainer::SingleFileCoverageVector& coverageVector = + this->Coverage.TotalCoverage[fileName]; char c; char buf[4]; in.read(buf, 3); buf[3] = 0; - if(strcmp(buf, ";a:") != 0) - { + if (strcmp(buf, ";a:") != 0) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "failed to read start of coverage array, found : " - << buf << "\n"); + "failed to read start of coverage array, found : " << buf + << "\n"); return false; - } + } int size = 0; - if(!this->ReadInt(in, size)) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "failed to read size "); + if (!this->ReadInt(in, size)) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "failed to read size "); return false; - } - if(!in.get(c) && c == '{') - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "failed to read open {\n"); + } + if (!in.get(c) && c == '{') { + cmCTestLog(this->CTest, ERROR_MESSAGE, "failed to read open {\n"); return false; - } - for(int i =0; i < size; i++) - { + } + for (int i = 0; i < size; i++) { this->ReadUntil(in, ':'); int line = 0; this->ReadInt(in, line); @@ -71,19 +67,17 @@ bool cmParsePHPCoverage::ReadCoverageArray(std::ifstream& in, // it seems to be 1 based but often times // seems to have a 0'th line. line--; - if(line < 0) - { + if (line < 0) { line = 0; - } + } this->ReadUntil(in, ':'); int value = 0; this->ReadInt(in, value); // make sure the vector is the right size and is // initialized with -1 for each line - while(coverageVector.size() <= static_cast<size_t>(line) ) - { + while (coverageVector.size() <= static_cast<size_t>(line)) { coverageVector.push_back(-1); - } + } // if value is less than 0, set it to zero // TODO figure out the difference between // -1 and -2 in xdebug coverage?? For now @@ -91,163 +85,137 @@ bool cmParsePHPCoverage::ReadCoverageArray(std::ifstream& in, // CDash expects -1 for non executable code (like comments) // and 0 for uncovered code, and a positive value // for number of times a line was executed - if(value < 0) - { + if (value < 0) { value = 0; - } + } // if unset then set it to value - if(coverageVector[line] == -1) - { + if (coverageVector[line] == -1) { coverageVector[line] = value; - } + } // otherwise increment by value - else - { + else { coverageVector[line] += value; - } } + } return true; } -bool cmParsePHPCoverage::ReadInt(std::ifstream& in, int& v) +bool cmParsePHPCoverage::ReadInt(std::istream& in, int& v) { std::string s; char c = 0; - while(in.get(c) && c != ':' && c != ';') - { + while (in.get(c) && c != ':' && c != ';') { s += c; - } + } v = atoi(s.c_str()); return true; } -bool cmParsePHPCoverage::ReadArraySize(std::ifstream& in, int& size) +bool cmParsePHPCoverage::ReadArraySize(std::istream& in, int& size) { char c = 0; in.get(c); - if(c != 'a') - { + if (c != 'a') { return false; - } - if(in.get(c) && c == ':') - { - if(this->ReadInt(in, size)) - { + } + if (in.get(c) && c == ':') { + if (this->ReadInt(in, size)) { return true; - } } + } return false; } -bool cmParsePHPCoverage::ReadFileInformation(std::ifstream& in) +bool cmParsePHPCoverage::ReadFileInformation(std::istream& in) { char buf[4]; in.read(buf, 2); buf[2] = 0; - if(strcmp(buf, "s:") != 0) - { + if (strcmp(buf, "s:") != 0) { cmCTestLog(this->CTest, ERROR_MESSAGE, "failed to read start of file info found: [" << buf << "]\n"); return false; - } + } char c; int size = 0; - if(this->ReadInt(in, size)) - { + if (this->ReadInt(in, size)) { size++; // add one for null termination - char* s = new char[size+1]; + char* s = new char[size + 1]; // read open quote - if(in.get(c) && c != '"') - { + if (in.get(c) && c != '"') { delete[] s; return false; - } + } // read the string data - in.read(s, size-1); - s[size-1] = 0; - cmStdString fileName = s; - delete [] s; + in.read(s, size - 1); + s[size - 1] = 0; + std::string fileName = s; + delete[] s; // read close quote - if(in.get(c) && c != '"') - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "failed to read close quote\n" - << "read [" << c << "]\n"); + if (in.get(c) && c != '"') { + cmCTestLog(this->CTest, ERROR_MESSAGE, "failed to read close quote\n" + << "read [" << c << "]\n"); return false; - } - if(!this->ReadCoverageArray(in, fileName) ) - { + } + if (!this->ReadCoverageArray(in, fileName)) { cmCTestLog(this->CTest, ERROR_MESSAGE, - "failed to read coverage array for file: " - << fileName << "\n"); + "failed to read coverage array for file: " << fileName + << "\n"); return false; - } - return true; } + return true; + } return false; } - bool cmParsePHPCoverage::ReadPHPData(const char* file) { - std::ifstream in(file); - if(!in) - { + cmsys::ifstream in(file); + if (!in) { return false; - } + } int size = 0; this->ReadArraySize(in, size); char c = 0; in.get(c); - if(c != '{') - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "failed to read open array\n"); + if (c != '{') { + cmCTestLog(this->CTest, ERROR_MESSAGE, "failed to read open array\n"); return false; - } - for(int i =0; i < size; i++) - { - if(!this->ReadFileInformation(in)) - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "Failed to read file #" << i << "\n"); + } + for (int i = 0; i < size; i++) { + if (!this->ReadFileInformation(in)) { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Failed to read file #" << i + << "\n"); return false; - } + } in.get(c); - if(c != '}') - { - cmCTestLog(this->CTest, ERROR_MESSAGE, - "failed to read close array\n"); + if (c != '}') { + cmCTestLog(this->CTest, ERROR_MESSAGE, "failed to read close array\n"); return false; - } } + } return true; } bool cmParsePHPCoverage::ReadPHPCoverageDirectory(const char* d) { cmsys::Directory dir; - if(!dir.Load(d)) - { + if (!dir.Load(d)) { return false; - } + } size_t numf; unsigned int i; numf = dir.GetNumberOfFiles(); - for (i = 0; i < numf; i++) - { + for (i = 0; i < numf; i++) { std::string file = dir.GetFile(i); - if(file != "." && file != ".." - && !cmSystemTools::FileIsDirectory(file.c_str())) - { + if (file != "." && file != ".." && !cmSystemTools::FileIsDirectory(file)) { std::string path = d; path += "/"; path += file; - if(!this->ReadPHPData(path.c_str())) - { + if (!this->ReadPHPData(path.c_str())) { return false; - } } } + } return true; } diff --git a/Source/CTest/cmParsePHPCoverage.h b/Source/CTest/cmParsePHPCoverage.h index d50a83c58..ff0e6366d 100644 --- a/Source/CTest/cmParsePHPCoverage.h +++ b/Source/CTest/cmParsePHPCoverage.h @@ -1,20 +1,15 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc. - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmParsePHPCoverage_h #define cmParsePHPCoverage_h -#include "cmStandardIncludes.h" -#include "cmCTestCoverageHandler.h" +#include "cmConfigure.h" // IWYU pragma: keep + +#include <iosfwd> +#include <string> + +class cmCTest; +class cmCTestCoverageHandlerContainer; /** \class cmParsePHPCoverage * \brief Parse xdebug PHP coverage information @@ -26,20 +21,19 @@ class cmParsePHPCoverage { public: - cmParsePHPCoverage(cmCTestCoverageHandlerContainer& cont, - cmCTest* ctest); + cmParsePHPCoverage(cmCTestCoverageHandlerContainer& cont, cmCTest* ctest); bool ReadPHPCoverageDirectory(const char* dir); void PrintCoverage(); + private: bool ReadPHPData(const char* file); - bool ReadArraySize(std::ifstream& in, int& size); - bool ReadFileInformation(std::ifstream& in); - bool ReadInt(std::ifstream& in, int& v); - bool ReadCoverageArray(std::ifstream& in, cmStdString const&); - bool ReadUntil(std::ifstream& in, char until); + bool ReadArraySize(std::istream& in, int& size); + bool ReadFileInformation(std::istream& in); + bool ReadInt(std::istream& in, int& v); + bool ReadCoverageArray(std::istream& in, std::string const&); + bool ReadUntil(std::istream& in, char until); cmCTestCoverageHandlerContainer& Coverage; cmCTest* CTest; }; - #endif diff --git a/Source/CTest/cmProcess.cxx b/Source/CTest/cmProcess.cxx index 167b992c9..f4ec6da29 100644 --- a/Source/CTest/cmProcess.cxx +++ b/Source/CTest/cmProcess.cxx @@ -1,21 +1,15 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmProcess.h" - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ - -#include <cmProcess.h> -#include <cmSystemTools.h> +#include "cmConfigure.h" +#include "cmProcessOutput.h" +#include "cmSystemTools.h" +#include <iostream> cmProcess::cmProcess() { - this->Process = 0; + this->Process = CM_NULLPTR; this->Timeout = 0; this->TotalTime = 0; this->ExitValue = 0; @@ -39,49 +33,43 @@ void cmProcess::SetCommandArguments(std::vector<std::string> const& args) bool cmProcess::StartProcess() { - if(this->Command.size() == 0) - { + if (this->Command.empty()) { return false; - } + } this->StartTime = cmSystemTools::GetTime(); this->ProcessArgs.clear(); // put the command as arg0 this->ProcessArgs.push_back(this->Command.c_str()); // now put the command arguments in - for(std::vector<std::string>::iterator i = this->Arguments.begin(); - i != this->Arguments.end(); ++i) - { + for (std::vector<std::string>::iterator i = this->Arguments.begin(); + i != this->Arguments.end(); ++i) { this->ProcessArgs.push_back(i->c_str()); - } - this->ProcessArgs.push_back(0); // null terminate the list + } + this->ProcessArgs.push_back(CM_NULLPTR); // null terminate the list this->Process = cmsysProcess_New(); cmsysProcess_SetCommand(this->Process, &*this->ProcessArgs.begin()); - if(this->WorkingDirectory.size()) - { + if (!this->WorkingDirectory.empty()) { cmsysProcess_SetWorkingDirectory(this->Process, this->WorkingDirectory.c_str()); - } + } cmsysProcess_SetTimeout(this->Process, this->Timeout); + cmsysProcess_SetOption(this->Process, cmsysProcess_Option_MergeOutput, 1); cmsysProcess_Execute(this->Process); - return (cmsysProcess_GetState(this->Process) - == cmsysProcess_State_Executing); + return (cmsysProcess_GetState(this->Process) == + cmsysProcess_State_Executing); } -//---------------------------------------------------------------------------- bool cmProcess::Buffer::GetLine(std::string& line) { // Scan for the next newline. - for(size_type sz = this->size(); this->Last != sz; ++this->Last) - { - if((*this)[this->Last] == '\n' || (*this)[this->Last] == '\0') - { + for (size_type sz = this->size(); this->Last != sz; ++this->Last) { + if ((*this)[this->Last] == '\n' || (*this)[this->Last] == '\0') { // Extract the range first..last as a line. const char* text = &*this->begin() + this->First; size_type length = this->Last - this->First; - while(length && text[length-1] == '\r') - { - length --; - } + while (length && text[length - 1] == '\r') { + length--; + } line.assign(text, length); // Start a new range for the next line. @@ -90,87 +78,70 @@ bool cmProcess::Buffer::GetLine(std::string& line) // Return the line extracted. return true; - } } + } // Available data have been exhausted without a newline. - if(this->First != 0) - { + if (this->First != 0) { // Move the partial line to the beginning of the buffer. this->erase(this->begin(), this->begin() + this->First); this->First = 0; this->Last = this->size(); - } + } return false; } -//---------------------------------------------------------------------------- bool cmProcess::Buffer::GetLast(std::string& line) { // Return the partial last line, if any. - if(!this->empty()) - { + if (!this->empty()) { line.assign(&*this->begin(), this->size()); this->First = this->Last = 0; this->clear(); return true; - } + } return false; } -//---------------------------------------------------------------------------- int cmProcess::GetNextOutputLine(std::string& line, double timeout) { - for(;;) - { + cmProcessOutput processOutput(cmProcessOutput::UTF8); + std::string strdata; + for (;;) { // Look for lines already buffered. - if(this->StdOut.GetLine(line)) - { + if (this->Output.GetLine(line)) { return cmsysProcess_Pipe_STDOUT; - } - else if(this->StdErr.GetLine(line)) - { - return cmsysProcess_Pipe_STDERR; - } + } // Check for more data from the process. char* data; int length; int p = cmsysProcess_WaitForData(this->Process, &data, &length, &timeout); - if(p == cmsysProcess_Pipe_Timeout) - { + if (p == cmsysProcess_Pipe_Timeout) { return cmsysProcess_Pipe_Timeout; - } - else if(p == cmsysProcess_Pipe_STDOUT) - { - this->StdOut.insert(this->StdOut.end(), data, data+length); - } - else if(p == cmsysProcess_Pipe_STDERR) - { - this->StdErr.insert(this->StdErr.end(), data, data+length); - } - else // p == cmsysProcess_Pipe_None - { + } + if (p == cmsysProcess_Pipe_STDOUT) { + processOutput.DecodeText(data, length, strdata); + this->Output.insert(this->Output.end(), strdata.begin(), strdata.end()); + } else { // p == cmsysProcess_Pipe_None // The process will provide no more data. break; - } } + } + processOutput.DecodeText(std::string(), strdata); + if (!strdata.empty()) { + this->Output.insert(this->Output.end(), strdata.begin(), strdata.end()); + } // Look for partial last lines. - if(this->StdOut.GetLast(line)) - { + if (this->Output.GetLast(line)) { return cmsysProcess_Pipe_STDOUT; - } - else if(this->StdErr.GetLast(line)) - { - return cmsysProcess_Pipe_STDERR; - } + } // No more data. Wait for process exit. - if(!cmsysProcess_WaitForExit(this->Process, &timeout)) - { + if (!cmsysProcess_WaitForExit(this->Process, &timeout)) { return cmsysProcess_Pipe_Timeout; - } + } // Record exit information. this->ExitValue = cmsysProcess_GetExitValue(this->Process); @@ -179,10 +150,9 @@ int cmProcess::GetNextOutputLine(std::string& line, double timeout) // negative. If someone changed the system clock while the process was // running this may be even more. Make sure not to report a negative // duration here. - if (this->TotalTime <= 0.0) - { + if (this->TotalTime <= 0.0) { this->TotalTime = 0.0; - } + } // std::cerr << "Time to run: " << this->TotalTime << "\n"; return cmsysProcess_Pipe_None; } @@ -190,89 +160,81 @@ int cmProcess::GetNextOutputLine(std::string& line, double timeout) // return the process status int cmProcess::GetProcessStatus() { - if(!this->Process) - { + if (!this->Process) { return cmsysProcess_State_Exited; - } + } return cmsysProcess_GetState(this->Process); } int cmProcess::ReportStatus() { int result = 1; - switch(cmsysProcess_GetState(this->Process)) - { - case cmsysProcess_State_Starting: - { - std::cerr << "cmProcess: Never started " - << this->Command << " process.\n"; - } break; - case cmsysProcess_State_Error: - { + switch (cmsysProcess_GetState(this->Process)) { + case cmsysProcess_State_Starting: { + std::cerr << "cmProcess: Never started " << this->Command + << " process.\n"; + } break; + case cmsysProcess_State_Error: { std::cerr << "cmProcess: Error executing " << this->Command - << " process: " - << cmsysProcess_GetErrorString(this->Process) + << " process: " << cmsysProcess_GetErrorString(this->Process) << "\n"; - } break; - case cmsysProcess_State_Exception: - { + } break; + case cmsysProcess_State_Exception: { std::cerr << "cmProcess: " << this->Command - << " process exited with an exception: "; - switch(cmsysProcess_GetExitException(this->Process)) - { - case cmsysProcess_Exception_None: - { + << " process exited with an exception: "; + switch (cmsysProcess_GetExitException(this->Process)) { + case cmsysProcess_Exception_None: { std::cerr << "None"; - } break; - case cmsysProcess_Exception_Fault: - { + } break; + case cmsysProcess_Exception_Fault: { std::cerr << "Segmentation fault"; - } break; - case cmsysProcess_Exception_Illegal: - { + } break; + case cmsysProcess_Exception_Illegal: { std::cerr << "Illegal instruction"; - } break; - case cmsysProcess_Exception_Interrupt: - { + } break; + case cmsysProcess_Exception_Interrupt: { std::cerr << "Interrupted by user"; - } break; - case cmsysProcess_Exception_Numerical: - { + } break; + case cmsysProcess_Exception_Numerical: { std::cerr << "Numerical exception"; - } break; - case cmsysProcess_Exception_Other: - { + } break; + case cmsysProcess_Exception_Other: { std::cerr << "Unknown"; - } break; - } + } break; + } std::cerr << "\n"; - } break; - case cmsysProcess_State_Executing: - { - std::cerr << "cmProcess: Never terminated " << - this->Command << " process.\n"; - } break; - case cmsysProcess_State_Exited: - { + } break; + case cmsysProcess_State_Executing: { + std::cerr << "cmProcess: Never terminated " << this->Command + << " process.\n"; + } break; + case cmsysProcess_State_Exited: { result = cmsysProcess_GetExitValue(this->Process); std::cerr << "cmProcess: " << this->Command - << " process exited with code " - << result << "\n"; - } break; - case cmsysProcess_State_Expired: - { + << " process exited with code " << result << "\n"; + } break; + case cmsysProcess_State_Expired: { std::cerr << "cmProcess: killed " << this->Command << " process due to timeout.\n"; - } break; - case cmsysProcess_State_Killed: - { + } break; + case cmsysProcess_State_Killed: { std::cerr << "cmProcess: killed " << this->Command << " process.\n"; - } break; - } + } break; + } return result; +} +void cmProcess::ChangeTimeout(double t) +{ + this->Timeout = t; + cmsysProcess_SetTimeout(this->Process, this->Timeout); } +void cmProcess::ResetStartTime() +{ + cmsysProcess_ResetStartTime(this->Process); + this->StartTime = cmSystemTools::GetTime(); +} int cmProcess::GetExitException() { diff --git a/Source/CTest/cmProcess.h b/Source/CTest/cmProcess.h index 1479df05f..86e905aca 100644 --- a/Source/CTest/cmProcess.h +++ b/Source/CTest/cmProcess.h @@ -1,21 +1,13 @@ -/*============================================================================ - CMake - Cross Platform Makefile Generator - Copyright 2000-2009 Kitware, Inc., Insight Software Consortium - - Distributed under the OSI-approved BSD License (the "License"); - see accompanying file Copyright.txt for details. - - This software is distributed WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the License for more information. -============================================================================*/ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ #ifndef cmProcess_h #define cmProcess_h +#include "cmConfigure.h" // IWYU pragma: keep -#include "cmStandardIncludes.h" -#include <cmsys/Process.h> - +#include "cmsys/Process.h" +#include <string> +#include <vector> /** \class cmProcess * \brief run a process with c++ @@ -27,11 +19,13 @@ class cmProcess public: cmProcess(); ~cmProcess(); - const char* GetCommand() { return this->Command.c_str();} + const char* GetCommand() { return this->Command.c_str(); } void SetCommand(const char* command); void SetCommandArguments(std::vector<std::string> const& arg); - void SetWorkingDirectory(const char* dir) { this->WorkingDirectory = dir;} - void SetTimeout(double t) { this->Timeout = t;} + void SetWorkingDirectory(const char* dir) { this->WorkingDirectory = dir; } + void SetTimeout(double t) { this->Timeout = t; } + void ChangeTimeout(double t); + void ResetStartTime(); // Return true if the process starts bool StartProcess(); @@ -40,41 +34,44 @@ public: // Report the status of the program int ReportStatus(); int GetId() { return this->Id; } - void SetId(int id) { this->Id = id;} - int GetExitValue() { return this->ExitValue;} - double GetTotalTime() { return this->TotalTime;} + void SetId(int id) { this->Id = id; } + int GetExitValue() { return this->ExitValue; } + double GetTotalTime() { return this->TotalTime; } int GetExitException(); /** * Read one line of output but block for no more than timeout. * Returns: * cmsysProcess_Pipe_None = Process terminated and all output read - * cmsysProcess_Pipe_STDOUT = Line came from stdout - * cmsysProcess_Pipe_STDOUT = Line came from stderr + * cmsysProcess_Pipe_STDOUT = Line came from stdout or stderr * cmsysProcess_Pipe_Timeout = Timeout expired while waiting */ int GetNextOutputLine(std::string& line, double timeout); + private: double Timeout; double StartTime; double TotalTime; cmsysProcess* Process; - class Buffer: public std::vector<char> + class Buffer : public std::vector<char> { // Half-open index range of partial line already scanned. size_type First; size_type Last; + public: - Buffer(): First(0), Last(0) {} + Buffer() + : First(0) + , Last(0) + { + } bool GetLine(std::string& line); bool GetLast(std::string& line); }; - Buffer StdErr; - Buffer StdOut; + Buffer Output; std::string Command; std::string WorkingDirectory; std::vector<std::string> Arguments; std::vector<const char*> ProcessArgs; - std::string Output; int Id; int ExitValue; }; |