summaryrefslogtreecommitdiff
path: root/Source/CTest
diff options
context:
space:
mode:
Diffstat (limited to 'Source/CTest')
-rw-r--r--Source/CTest/cmCTestBZR.cxx523
-rw-r--r--Source/CTest/cmCTestBZR.h55
-rw-r--r--Source/CTest/cmCTestBatchTestHandler.cxx143
-rw-r--r--Source/CTest/cmCTestBatchTestHandler.h41
-rw-r--r--Source/CTest/cmCTestBuildAndTestHandler.cxx526
-rw-r--r--Source/CTest/cmCTestBuildAndTestHandler.h78
-rw-r--r--Source/CTest/cmCTestBuildCommand.cxx193
-rw-r--r--Source/CTest/cmCTestBuildCommand.h98
-rw-r--r--Source/CTest/cmCTestBuildHandler.cxx1310
-rw-r--r--Source/CTest/cmCTestBuildHandler.h152
-rw-r--r--Source/CTest/cmCTestCVS.cxx319
-rw-r--r--Source/CTest/cmCTestCVS.h51
-rw-r--r--Source/CTest/cmCTestCommand.h40
-rw-r--r--Source/CTest/cmCTestConfigureCommand.cxx172
-rw-r--r--Source/CTest/cmCTestConfigureCommand.h84
-rw-r--r--Source/CTest/cmCTestConfigureHandler.cxx132
-rw-r--r--Source/CTest/cmCTestConfigureHandler.h39
-rw-r--r--Source/CTest/cmCTestCoverageCommand.cxx73
-rw-r--r--Source/CTest/cmCTestCoverageCommand.h90
-rw-r--r--Source/CTest/cmCTestCoverageHandler.cxx2144
-rw-r--r--Source/CTest/cmCTestCoverageHandler.h139
-rw-r--r--Source/CTest/cmCTestEmptyBinaryDirectoryCommand.cxx36
-rw-r--r--Source/CTest/cmCTestEmptyBinaryDirectoryCommand.h78
-rw-r--r--Source/CTest/cmCTestGIT.cxx652
-rw-r--r--Source/CTest/cmCTestGIT.h57
-rw-r--r--Source/CTest/cmCTestGenericHandler.cxx171
-rw-r--r--Source/CTest/cmCTestGenericHandler.h107
-rw-r--r--Source/CTest/cmCTestGlobalVC.cxx142
-rw-r--r--Source/CTest/cmCTestGlobalVC.h68
-rw-r--r--Source/CTest/cmCTestHG.cxx338
-rw-r--r--Source/CTest/cmCTestHG.h47
-rw-r--r--Source/CTest/cmCTestHandlerCommand.cxx197
-rw-r--r--Source/CTest/cmCTestHandlerCommand.h77
-rw-r--r--Source/CTest/cmCTestLaunch.cxx719
-rw-r--r--Source/CTest/cmCTestLaunch.h104
-rw-r--r--Source/CTest/cmCTestMemCheckCommand.cxx32
-rw-r--r--Source/CTest/cmCTestMemCheckCommand.h88
-rw-r--r--Source/CTest/cmCTestMemCheckHandler.cxx955
-rw-r--r--Source/CTest/cmCTestMemCheckHandler.h125
-rw-r--r--Source/CTest/cmCTestMultiProcessHandler.cxx691
-rw-r--r--Source/CTest/cmCTestMultiProcessHandler.h116
-rw-r--r--Source/CTest/cmCTestReadCustomFilesCommand.cxx35
-rw-r--r--Source/CTest/cmCTestReadCustomFilesCommand.h75
-rw-r--r--Source/CTest/cmCTestRunScriptCommand.cxx65
-rw-r--r--Source/CTest/cmCTestRunScriptCommand.h80
-rw-r--r--Source/CTest/cmCTestRunTest.cxx735
-rw-r--r--Source/CTest/cmCTestRunTest.h112
-rw-r--r--Source/CTest/cmCTestSVN.cxx450
-rw-r--r--Source/CTest/cmCTestSVN.h68
-rw-r--r--Source/CTest/cmCTestScriptHandler.cxx1095
-rw-r--r--Source/CTest/cmCTestScriptHandler.h172
-rw-r--r--Source/CTest/cmCTestSleepCommand.cxx55
-rw-r--r--Source/CTest/cmCTestSleepCommand.h77
-rw-r--r--Source/CTest/cmCTestStartCommand.cxx170
-rw-r--r--Source/CTest/cmCTestStartCommand.h92
-rw-r--r--Source/CTest/cmCTestSubmitCommand.cxx255
-rw-r--r--Source/CTest/cmCTestSubmitCommand.h118
-rw-r--r--Source/CTest/cmCTestSubmitHandler.cxx1508
-rw-r--r--Source/CTest/cmCTestSubmitHandler.h98
-rw-r--r--Source/CTest/cmCTestTestCommand.cxx112
-rw-r--r--Source/CTest/cmCTestTestCommand.h107
-rw-r--r--Source/CTest/cmCTestTestHandler.cxx2267
-rw-r--r--Source/CTest/cmCTestTestHandler.h273
-rw-r--r--Source/CTest/cmCTestUpdateCommand.cxx78
-rw-r--r--Source/CTest/cmCTestUpdateCommand.h73
-rw-r--r--Source/CTest/cmCTestUpdateHandler.cxx404
-rw-r--r--Source/CTest/cmCTestUpdateHandler.h76
-rw-r--r--Source/CTest/cmCTestUploadCommand.cxx69
-rw-r--r--Source/CTest/cmCTestUploadCommand.h85
-rw-r--r--Source/CTest/cmCTestUploadHandler.cxx77
-rw-r--r--Source/CTest/cmCTestUploadHandler.h45
-rw-r--r--Source/CTest/cmCTestVC.cxx241
-rw-r--r--Source/CTest/cmCTestVC.h133
-rw-r--r--Source/CTest/cmParseCacheCoverage.cxx220
-rw-r--r--Source/CTest/cmParseCacheCoverage.h42
-rw-r--r--Source/CTest/cmParseGTMCoverage.cxx272
-rw-r--r--Source/CTest/cmParseGTMCoverage.h49
-rw-r--r--Source/CTest/cmParseMumpsCoverage.cxx165
-rw-r--r--Source/CTest/cmParseMumpsCoverage.h52
-rw-r--r--Source/CTest/cmParsePHPCoverage.cxx253
-rw-r--r--Source/CTest/cmParsePHPCoverage.h45
-rw-r--r--Source/CTest/cmProcess.cxx272
-rw-r--r--Source/CTest/cmProcess.h82
83 files changed, 21954 insertions, 0 deletions
diff --git a/Source/CTest/cmCTestBZR.cxx b/Source/CTest/cmCTestBZR.cxx
new file mode 100644
index 000000000..381c70ce5
--- /dev/null
+++ b/Source/CTest/cmCTestBZR.cxx
@@ -0,0 +1,523 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestBZR.h"
+
+#include "cmCTest.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)
+{
+ 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;
+ }
+
+ return 0;
+}
+
+//----------------------------------------------------------------------------
+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
+ // 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
+{
+public:
+ 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))
+ {
+ this->BZR->URL = this->RegexCheckOut.match(1);
+ CheckOutFound = true;
+ }
+ else if(!CheckOutFound && this->RegexParent.find(this->Line))
+ {
+ this->BZR->URL = this->RegexParent.match(1);
+ }
+ return true;
+ }
+};
+
+//----------------------------------------------------------------------------
+class cmCTestBZR::RevnoParser: public cmCTestVC::LineParser
+{
+public:
+ 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))
+ {
+ this->Rev = this->RegexRevno.match(1);
+ }
+ 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};
+ 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};
+ std::string rev;
+ RevnoParser rout(this, "revno-out> ", rev);
+ OutputLogger rerr(this->Log, "revno-err> ");
+ this->RunChild(bzr_revno, &rout, &rerr);
+
+ return rev;
+}
+
+void 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->PriorRev.Rev = this->OldRevision;
+}
+
+//----------------------------------------------------------------------------
+void 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->Log << "URL = " << this->URL << "\n";
+}
+
+//----------------------------------------------------------------------------
+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;
+ }
+private:
+ cmCTestBZR* BZR;
+
+ typedef cmCTestBZR::Revision Revision;
+ typedef cmCTestBZR::Change Change;
+ Revision Rev;
+ std::vector<Change> Changes;
+ Change CurChange;
+ std::vector<char> CData;
+
+ cmsys::RegularExpression EmailRegex;
+
+ virtual bool ProcessChunk(const char* data, int length)
+ {
+ this->OutputLogger::ProcessChunk(data, length);
+ this->ParseChunk(data, length);
+ return true;
+ }
+
+ virtual void StartElement(const char* name, const char**)
+ {
+ this->CData.clear();
+ if(strcmp(name, "log") == 0)
+ {
+ 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)
+ {
+ this->CurChange = Change();
+ this->CurChange.Action = 'M';
+ }
+ else if(strcmp(name, "added") == 0)
+ {
+ this->CurChange = Change();
+ this->CurChange = 'A';
+ }
+ else if(strcmp(name, "removed") == 0)
+ {
+ this->CurChange = Change();
+ this->CurChange = 'D';
+ }
+ else if(strcmp(name, "unknown") == 0
+ || strcmp(name, "conflicts") == 0)
+ {
+ // Should not happen here
+ this->CurChange = Change();
+ }
+ }
+
+ virtual void CharacterDataHandler(const char* data, int length)
+ {
+ this->CData.insert(this->CData.end(), data, data+length);
+ }
+
+ virtual void EndElement(const char* name)
+ {
+ if(strcmp(name, "log") == 0)
+ {
+ this->BZR->DoRevision(this->Rev, this->Changes);
+ }
+ else if((strcmp(name, "file") == 0 || strcmp(name, "directory") == 0)
+ && !this->CData.empty())
+ {
+ 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())
+ {
+ // symlinks have an arobase at the end in the log
+ 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())
+ {
+ this->Rev.Author.assign(&this->CData[0], this->CData.size());
+ 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())
+ {
+ this->Rev.Date.assign(&this->CData[0], this->CData.size());
+ }
+ else if(strcmp(name, "message") == 0 && !this->CData.empty())
+ {
+ this->Rev.Log.assign(&this->CData[0], this->CData.size());
+ }
+ else if(strcmp(name, "revno") == 0 && !this->CData.empty())
+ {
+ this->Rev.Rev.assign(&this->CData[0], this->CData.size());
+ }
+ this->CData.clear();
+ }
+
+ virtual void ReportError(int, int, const char* msg)
+ {
+ this->BZR->Log << "Error parsing bzr log xml: " << msg << "\n";
+ }
+};
+
+//----------------------------------------------------------------------------
+class cmCTestBZR::UpdateParser: public cmCTestVC::LineParser
+{
+public:
+ 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 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)
+ {
+ // Log this line.
+ if(this->Log && this->Prefix)
+ {
+ *this->Log << this->Prefix << this->Line << "\n";
+ }
+
+ // Hand this line to the subclass implementation.
+ if(!this->ProcessLine())
+ {
+ this->Line = "";
+ return false;
+ }
+
+ this->Line = "";
+ last_is_new_line = true;
+ }
+ }
+ else
+ {
+ // Append this character to the line under construction.
+ this->Line.append(1, *c);
+ last_is_new_line = false;
+ }
+ }
+ return true;
+ }
+
+ bool ProcessLine()
+ {
+ 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;
+ }
+
+ void DoPath(char c0, char c1, char c2, std::string path)
+ {
+ if(path.empty()) return;
+ cmSystemTools::ConvertToUnixSlashes(path);
+
+ const std::string dir = cmSystemTools::GetFilenamePath(path);
+ const std::string name = cmSystemTools::GetFilenameName(path);
+
+ if ( c0=='C' )
+ {
+ this->BZR->Dirs[dir][name].Status = PathConflicting;
+ return;
+ }
+
+ 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())
+ {
+ opts = this->CTest->GetCTestConfiguration("BZRUpdateOptions");
+ }
+ std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str());
+
+ // TODO: if(this->CTest->GetTestModel() == cmCTest::NIGHTLY)
+
+ // Use "bzr pull" to update the working tree.
+ std::vector<char const*> bzr_update;
+ 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)
+ {
+ bzr_update.push_back(ai->c_str());
+ }
+
+ bzr_update.push_back(this->URL.c_str());
+
+ bzr_update.push_back(0);
+
+ // For some reason bzr uses stderr to display the update status.
+ OutputLogger out(this->Log, "pull-out> ");
+ UpdateParser err(this, "pull-err> ");
+ return this->RunUpdateCommand(&bzr_update[0], &out, &err);
+}
+
+//----------------------------------------------------------------------------
+void cmCTestBZR::LoadRevisions()
+{
+ cmCTestLog(this->CTest, HANDLER_OUTPUT,
+ " Gathering version information (one . per revision):\n"
+ " " << 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()))
+ {
+ // DoRevision takes care of discarding the information about OldRevision
+ revs = this->OldRevision + ".." + this->NewRevision;
+ }
+ else
+ {
+ return;
+ }
+
+ // 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};
+ {
+ LogParser out(this, "log-out> ");
+ OutputLogger err(this->Log, "log-err> ");
+ this->RunChild(bzr_log, &out, &err);
+ }
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl);
+}
+
+//----------------------------------------------------------------------------
+class cmCTestBZR::StatusParser: public cmCTestVC::LineParser
+{
+public:
+ 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))
+ {
+ this->DoPath(this->RegexStatus.match(1)[0],
+ this->RegexStatus.match(2)[0],
+ 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;
+ cmSystemTools::ConvertToUnixSlashes(path);
+
+ if ( c0=='C' )
+ {
+ this->BZR->DoModification(PathConflicting, path);
+ return;
+ }
+
+ if ( c0 == '+' || c0 == 'R' || c0 == 'P'
+ || c1=='M' || c1=='K' || c1=='N' || c1=='D'
+ || c2 =='*' )
+ {
+ this->BZR->DoModification(PathModified, path);
+ return;
+ }
+ }
+};
+
+//----------------------------------------------------------------------------
+void cmCTestBZR::LoadModifications()
+{
+ // Run "bzr status" which reports local modifications.
+ const char* bzr = this->CommandLineTool.c_str();
+ const char* bzr_status[] = {bzr, "status", "-SV", 0};
+ StatusParser out(this, "status-out> ");
+ OutputLogger err(this->Log, "status-err> ");
+ this->RunChild(bzr_status, &out, &err);
+}
diff --git a/Source/CTest/cmCTestBZR.h b/Source/CTest/cmCTestBZR.h
new file mode 100644
index 000000000..df688e1ee
--- /dev/null
+++ b/Source/CTest/cmCTestBZR.h
@@ -0,0 +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.
+============================================================================*/
+#ifndef cmCTestBZR_h
+#define cmCTestBZR_h
+
+#include "cmCTestGlobalVC.h"
+
+/** \class cmCTestBZR
+ * \brief Interaction with bzr command-line tool
+ *
+ */
+class cmCTestBZR: public cmCTestGlobalVC
+{
+public:
+ /** Construct with a CTest instance and update log stream. */
+ cmCTestBZR(cmCTest* ctest, std::ostream& log);
+
+ virtual ~cmCTestBZR();
+
+private:
+ // Implement cmCTestVC internal API.
+ virtual void NoteOldRevision();
+ virtual void NoteNewRevision();
+ virtual bool UpdateImpl();
+
+ // URL of repository directory checked out in the working tree.
+ std::string URL;
+
+ std::string LoadInfo();
+ void LoadModifications();
+ void LoadRevisions();
+
+ // Parsing helper classes.
+ class InfoParser;
+ class RevnoParser;
+ class LogParser;
+ class UpdateParser;
+ class StatusParser;
+ friend class InfoParser;
+ friend class RevnoParser;
+ friend class LogParser;
+ friend class UpdateParser;
+ friend class StatusParser;
+};
+
+#endif
diff --git a/Source/CTest/cmCTestBatchTestHandler.cxx b/Source/CTest/cmCTestBatchTestHandler.cxx
new file mode 100644
index 000000000..00bb6fa70
--- /dev/null
+++ b/Source/CTest/cmCTestBatchTestHandler.cxx
@@ -0,0 +1,143 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#include "cmCTestBatchTestHandler.h"
+#include "cmProcess.h"
+#include "cmStandardIncludes.h"
+#include "cmCTest.h"
+#include "cmSystemTools.h"
+#include <stdlib.h>
+
+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);
+ fout << "#!/bin/sh\n";
+
+ 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)
+{
+ cmCTestTestHandler::cmCTestTestProperties* properties =
+ this->Properties[test];
+
+ fout << "srun ";
+ //fout << "--jobid=" << test << " ";
+ fout << "-J=" << properties->Name << " ";
+
+ //Write dependency information
+ /*if(this->Tests[test].size() > 0)
+ {
+ fout << "-P=afterany";
+ for(TestSet::iterator i = this->Tests[test].begin();
+ i != this->Tests[test].end(); ++i)
+ {
+ fout << ":" << *i;
+ }
+ fout << " ";
+ }*/
+ if(properties->RunSerial)
+ {
+ fout << "--exclusive ";
+ }
+ if(properties->Processors > 1)
+ {
+ fout << "-n" << properties->Processors << " ";
+ }
+}
+
+//---------------------------------------------------------
+void cmCTestBatchTestHandler::WriteTestCommand(int test, std::fstream& fout)
+{
+ std::vector<std::string> args = this->Properties[test]->Args;
+ std::vector<std::string> processArgs;
+ std::string command;
+
+ 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
+ this->TestHandler->GenerateTestCommand(processArgs);
+ processArgs.push_back(command);
+
+ 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)
+ {
+ fout << "'";
+ }
+ while(i != args.end())
+ {
+ fout << "\"" << *i << "\""; //args to the test executable
+ ++i;
+
+ if(i == args.end() && args.size() > 2)
+ {
+ fout << "'";
+ }
+ 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");
+
+ sbatch.SetCommand("sbatch");
+ sbatch.SetCommandArguments(args);
+ /*if(sbatch.StartProcess())
+ {
+ //success condition
+ }
+ else
+ {
+ //fail condition
+ }*/
+}
diff --git a/Source/CTest/cmCTestBatchTestHandler.h b/Source/CTest/cmCTestBatchTestHandler.h
new file mode 100644
index 000000000..fdfd1be29
--- /dev/null
+++ b/Source/CTest/cmCTestBatchTestHandler.h
@@ -0,0 +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.
+============================================================================*/
+
+#ifndef cmCTestBatchTestHandler_h
+#define cmCTestBatchTestHandler_h
+
+#include <cmStandardIncludes.h>
+#include <cmCTestTestHandler.h>
+#include <cmCTestMultiProcessHandler.h>
+#include <cmCTestRunTest.h>
+
+/** \class cmCTestBatchTestHandler
+ * \brief run parallel ctest
+ *
+ * cmCTestBatchTestHandler
+ */
+class cmCTestBatchTestHandler : public cmCTestMultiProcessHandler
+{
+public:
+ ~cmCTestBatchTestHandler();
+ virtual void RunTests();
+protected:
+ void WriteBatchScript();
+ void WriteSrunArgs(int test, std::fstream& fout);
+ void WriteTestCommand(int test, std::fstream& fout);
+
+ void SubmitBatchScript();
+
+ std::string Script;
+};
+
+#endif
diff --git a/Source/CTest/cmCTestBuildAndTestHandler.cxx b/Source/CTest/cmCTestBuildAndTestHandler.cxx
new file mode 100644
index 000000000..8a2d65a7b
--- /dev/null
+++ b/Source/CTest/cmCTestBuildAndTestHandler.cxx
@@ -0,0 +1,526 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#include "cmCTestBuildAndTestHandler.h"
+
+#include "cmSystemTools.h"
+#include "cmCTest.h"
+#include "cmake.h"
+#include "cmGlobalGenerator.h"
+#include <cmsys/Process.h>
+#include "cmCTestTestHandler.h"
+
+//----------------------------------------------------------------------
+cmCTestBuildAndTestHandler::cmCTestBuildAndTestHandler()
+{
+ 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->Superclass::Initialize();
+}
+
+//----------------------------------------------------------------------
+const char* cmCTestBuildAndTestHandler::GetOutput()
+{
+ return this->Output.c_str();
+}
+//----------------------------------------------------------------------
+int cmCTestBuildAndTestHandler::ProcessHandler()
+{
+ this->Output = "";
+ std::string output;
+ cmSystemTools::ResetErrorOccuredFlag();
+ int retv = this->RunCMakeAndTest(&this->Output);
+ cmSystemTools::ResetErrorOccuredFlag();
+ return retv;
+}
+
+//----------------------------------------------------------------------
+int cmCTestBuildAndTestHandler::RunCMake(std::string* outstring,
+ cmOStringStream &out, std::string &cmakeOutString, std::string &cwd,
+ cmake *cm)
+{
+ unsigned int k;
+ std::vector<std::string> args;
+ args.push_back(this->CTest->GetCMakeExecutable());
+ args.push_back(this->SourceDir);
+ if(this->BuildGenerator.size())
+ {
+ std::string generator = "-G";
+ generator += this->BuildGenerator;
+ args.push_back(generator);
+ }
+
+ const char* config = 0;
+ if ( this->CTest->GetConfigType().size() > 0 )
+ {
+ config = this->CTest->GetConfigType().c_str();
+ }
+#ifdef CMAKE_INTDIR
+ if(!config)
+ {
+ config = CMAKE_INTDIR;
+ }
+#endif
+
+ if ( config )
+ {
+ std::string btype
+ = "-DCMAKE_BUILD_TYPE:STRING=" + std::string(config);
+ args.push_back(btype);
+ }
+
+ for(k=0; k < this->BuildOptions.size(); ++k)
+ {
+ args.push_back(this->BuildOptions[k]);
+ }
+ 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)
+ {
+ *outstring = out.str();
+ }
+ else
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, out.str() << std::endl);
+ }
+ return 1;
+ }
+ // do another config?
+ 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)
+ {
+ *outstring = out.str();
+ }
+ else
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, out.str() << std::endl);
+ }
+ 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)
+{
+ std::string* out = (std::string*)s;
+ *out += m;
+ *out += "\n";
+}
+
+void CMakeProgressCallback(const char*msg, float , void * s)
+{
+ std::string* out = (std::string*)s;
+ *out += msg;
+ *out += "\n";
+}
+
+//----------------------------------------------------------------------
+void CMakeStdoutCallback(const char* m, int len, void* s)
+{
+ std::string* out = (std::string*)s;
+ out->append(m, len);
+}
+struct cmSetupOutputCaptureCleanup
+{
+ ~cmSetupOutputCaptureCleanup()
+ {
+ cmSystemTools::SetErrorCallback(0, 0);
+ cmSystemTools::SetStdoutCallback(0, 0);
+ }
+};
+
+//----------------------------------------------------------------------
+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->CTest->GetConfigType().size() == 0 &&
+ this->ConfigSample.size())
+ {
+ // 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())
+ {
+ this->CTest->SetConfigType(resultingConfig.c_str());
+ }
+ 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()))
+ {
+ cmSystemTools::MakeDirectory(this->BinaryDir.c_str());
+ }
+ cmSystemTools::ChangeDirectory(this->BinaryDir.c_str());
+
+ // should we cmake?
+ cmake cm;
+ cm.SetProgressCallback(CMakeProgressCallback, &cmakeOutString);
+ cm.SetGlobalGenerator(cm.CreateGlobalGenerator(
+ this->BuildGenerator.c_str()));
+
+ if(!this->BuildNoCMake)
+ {
+ // do the cmake step, no timeout here since it is not a sub process
+ if (this->RunCMake(outstring,out,cmakeOutString,cwd,&cm))
+ {
+ return 1;
+ }
+ }
+
+ // do the build
+ std::vector<std::string>::iterator tarIt;
+ if ( this->BuildTargets.size() == 0 )
+ {
+ this->BuildTargets.push_back("");
+ }
+ for ( tarIt = this->BuildTargets.begin();
+ tarIt != this->BuildTargets.end(); ++ tarIt )
+ {
+ double remainingTime = 0;
+ if (this->Timeout > 0)
+ {
+ remainingTime = this->Timeout - cmSystemTools::GetTime() + clock_start;
+ if (remainingTime <= 0)
+ {
+ if(outstring)
+ {
+ *outstring = "--build-and-test timeout exceeded. ";
+ }
+ return 1;
+ }
+ }
+ std::string output;
+ const char* config = 0;
+ if ( this->CTest->GetConfigType().size() > 0 )
+ {
+ config = this->CTest->GetConfigType().c_str();
+ }
+#ifdef CMAKE_INTDIR
+ if(!config)
+ {
+ config = CMAKE_INTDIR;
+ }
+#endif
+ 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);
+ out << output;
+ // if the build failed then return
+ if (retVal)
+ {
+ if(outstring)
+ {
+ *outstring = out.str();
+ }
+ return 1;
+ }
+ }
+ if(outstring)
+ {
+ *outstring = out.str();
+ }
+
+ // if no test was specified then we are done
+ if (!this->TestCommand.size())
+ {
+ return 0;
+ }
+
+ // now run the compiled test if we can find it
+ // store the final location in fullPath
+ std::string fullPath;
+ std::string resultingConfig;
+ std::vector<std::string> extraPaths;
+ // if this->ExecutableDirectory is set try that as well
+ if (this->ExecutableDirectory.size())
+ {
+ 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);
+
+ if(!cmSystemTools::FileExists(fullPath.c_str()))
+ {
+ out << "Could not find path to executable, perhaps it was not built: "
+ << 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 << failed[i] << "\n";
+ }
+ 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;
+ }
+
+ std::vector<const char*> testCommand;
+ testCommand.push_back(fullPath.c_str());
+ for(k=0; k < this->TestCommandArgs.size(); ++k)
+ {
+ testCommand.push_back(this->TestCommandArgs[k].c_str());
+ }
+ testCommand.push_back(0);
+ std::string outs;
+ int retval = 0;
+ // run the test from the this->BuildRunDir if set
+ if(this->BuildRunDir.size())
+ {
+ out << "Run test in directory: " << this->BuildRunDir << "\n";
+ cmSystemTools::ChangeDirectory(this->BuildRunDir.c_str());
+ }
+ out << "Running test command: \"" << fullPath << "\"";
+ for(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)
+ {
+ remainingTime = this->Timeout - cmSystemTools::GetTime() + clock_start;
+ if (remainingTime <= 0)
+ {
+ if(outstring)
+ {
+ *outstring = "--build-and-test timeout exceeded. ";
+ }
+ return 1;
+ }
+ }
+
+ int runTestRes = this->CTest->RunTest(testCommand, &outs, &retval, 0,
+ remainingTime, 0);
+
+ if(runTestRes != cmsysProcess_State_Exited || retval != 0)
+ {
+ out << "Test command failed: " << testCommand[0] << "\n";
+ retval = 1;
+ }
+
+ out << outs << "\n";
+ if(outstring)
+ {
+ *outstring = out.str();
+ }
+ 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())
+ {
+ 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
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "--build-and-test must have source and binary dir" << std::endl);
+ return 0;
+ }
+ }
+ 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)
+ {
+ this->BuildNoCMake = true;
+ }
+ 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)
+ {
+ this->BuildTwoConfig = true;
+ }
+ 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)
+ {
+ idx++;
+ this->Timeout = atof(allArgs[idx].c_str());
+ }
+ if(currentArg.find("--build-generator",0) == 0 && idx < allArgs.size() - 1)
+ {
+ idx++;
+ this->BuildGenerator = allArgs[idx];
+ }
+ 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)
+ {
+ idx++;
+ this->BuildMakeProgram = allArgs[idx];
+ }
+ if(currentArg.find("--build-config-sample",0) == 0 &&
+ idx < allArgs.size() - 1)
+ {
+ idx++;
+ this->ConfigSample = allArgs[idx];
+ }
+ 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)
+ {
+ 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)
+ {
+ ++idx;
+ this->TestCommand = allArgs[idx];
+ 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
new file mode 100644
index 000000000..6e5f7a6ab
--- /dev/null
+++ b/Source/CTest/cmCTestBuildAndTestHandler.h
@@ -0,0 +1,78 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#ifndef cmCTestBuildAndTestHandler_h
+#define cmCTestBuildAndTestHandler_h
+
+
+#include "cmCTestGenericHandler.h"
+#include "cmListFileCache.h"
+
+class cmake;
+
+/** \class cmCTestBuildAndTestHandler
+ * \brief A class that handles ctest -S invocations
+ *
+ */
+class cmCTestBuildAndTestHandler : public cmCTestGenericHandler
+{
+public:
+ cmTypeMacro(cmCTestBuildAndTestHandler, cmCTestGenericHandler);
+
+ /*
+ * The main entry point for this class
+ */
+ int ProcessHandler();
+
+ //! Set all the build and test arguments
+ virtual int ProcessCommandLineArguments(
+ const std::string& currentArg, size_t& idx,
+ const std::vector<std::string>& allArgs);
+
+ /*
+ * Get the output variable
+ */
+ const char* GetOutput();
+
+ cmCTestBuildAndTestHandler();
+
+ virtual void Initialize();
+
+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);
+
+ cmStdString Output;
+
+ std::string BuildGenerator;
+ 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;
+ std::vector<std::string> TestCommandArgs;
+ std::vector<std::string> BuildTargets;
+ bool BuildNoCMake;
+ double Timeout;
+};
+
+#endif
+
diff --git a/Source/CTest/cmCTestBuildCommand.cxx b/Source/CTest/cmCTestBuildCommand.cxx
new file mode 100644
index 000000000..b984e850f
--- /dev/null
+++ b/Source/CTest/cmCTestBuildCommand.cxx
@@ -0,0 +1,193 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestBuildCommand.h"
+
+#include "cmCTest.h"
+#include "cmCTestGenericHandler.h"
+#include "cmCTestBuildHandler.h"
+#include "cmake.h"
+#include "cmGlobalGenerator.h"
+
+
+//----------------------------------------------------------------------------
+cmCTestBuildCommand::cmCTestBuildCommand()
+{
+ this->GlobalGenerator = 0;
+ 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->Last = ctb_LAST;
+}
+
+//----------------------------------------------------------------------------
+cmCTestBuildCommand::~cmCTestBuildCommand()
+{
+ if ( this->GlobalGenerator )
+ {
+ delete this->GlobalGenerator;
+ this->GlobalGenerator = 0;
+ }
+}
+
+//----------------------------------------------------------------------------
+cmCTestGenericHandler* cmCTestBuildCommand::InitializeHandler()
+{
+ 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])
+ ? this->Values[ctb_PROJECT_NAME]
+ : this->Makefile->GetDefinition("CTEST_PROJECT_NAME");
+
+ // Build configuration is determined by: CONFIGURATION argument,
+ // or CTEST_BUILD_CONFIGURATION script variable, or
+ // 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])
+ ? this->Values[ctb_CONFIGURATION]
+ : ((ctestBuildConfiguration && *ctestBuildConfiguration)
+ ? ctestBuildConfiguration
+ : this->CTest->GetConfigType().c_str());
+
+ 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])
+ ? this->Values[ctb_TARGET]
+ : this->Makefile->GetDefinition("CTEST_BUILD_TARGET");
+
+ if ( cmakeGeneratorName && *cmakeGeneratorName &&
+ cmakeProjectName && *cmakeProjectName )
+ {
+ if ( !cmakeBuildConfiguration )
+ {
+ cmakeBuildConfiguration = "Release";
+ }
+ if ( this->GlobalGenerator )
+ {
+ if ( strcmp(this->GlobalGenerator->GetName(),
+ cmakeGeneratorName) != 0 )
+ {
+ delete this->GlobalGenerator;
+ this->GlobalGenerator = 0;
+ }
+ }
+ if ( !this->GlobalGenerator )
+ {
+ this->GlobalGenerator =
+ this->Makefile->GetCMakeInstance()->CreateGlobalGenerator(
+ cmakeGeneratorName);
+ }
+ this->GlobalGenerator->FindMakeProgram(this->Makefile);
+ const char* cmakeMakeProgram
+ = this->Makefile->GetDefinition("CMAKE_MAKE_PROGRAM");
+ if(strlen(cmakeBuildConfiguration) == 0)
+ {
+ const char* config = 0;
+#ifdef CMAKE_INTDIR
+ config = CMAKE_INTDIR;
+#endif
+ if(!config)
+ {
+ config = "Debug";
+ }
+ cmakeBuildConfiguration = config;
+ }
+
+ std::string buildCommand
+ = this->GlobalGenerator->
+ GenerateBuildCommand(cmakeMakeProgram,
+ cmakeProjectName,
+ 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;
+ 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."
+ "\n"
+ "CTEST_PROJECT_NAME is usually set in CTestConfig.cmake. Verify "
+ "that CTestConfig.cmake exists, or CTEST_PROJECT_NAME "
+ "is set in the script, or PROJECT_NAME is passed as an argument "
+ "to ctest_build."
+ "\n"
+ "Alternatively, set CTEST_BUILD_COMMAND to build the project "
+ "with a custom command line.";
+ this->SetError(ostr.str().c_str());
+ return 0;
+ }
+ }
+
+ if(const char* useLaunchers =
+ this->Makefile->GetDefinition("CTEST_USE_LAUNCHERS"))
+ {
+ this->CTest->SetCTestConfiguration("UseLaunchers", useLaunchers);
+ }
+
+ return handler;
+}
+
+
+bool cmCTestBuildCommand::InitialPass(std::vector<std::string> const& args,
+ cmExecutionStatus &status)
+{
+ bool ret = cmCTestHandlerCommand::InitialPass(args, status);
+ if ( this->Values[ctb_NUMBER_ERRORS] && *this->Values[ctb_NUMBER_ERRORS])
+ {
+ cmOStringStream 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;
+ str << this->Handler->GetTotalWarnings();
+ 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
new file mode 100644
index 000000000..cabc39b32
--- /dev/null
+++ b/Source/CTest/cmCTestBuildCommand.h
@@ -0,0 +1,98 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestBuildCommand_h
+#define cmCTestBuildCommand_h
+
+#include "cmCTestHandlerCommand.h"
+
+class cmGlobalGenerator;
+class cmCTestBuildHandler;
+
+/** \class cmCTestBuild
+ * \brief Run a ctest script
+ *
+ * cmCTestBuildCommand defineds the command to build the project.
+ */
+class cmCTestBuildCommand : public cmCTestHandlerCommand
+{
+public:
+
+ cmCTestBuildCommand();
+ ~cmCTestBuildCommand();
+
+ /**
+ * This is a virtual constructor for the command.
+ */
+ virtual cmCommand* Clone()
+ {
+ 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";}
+
+ /**
+ * 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);
+
+ cmGlobalGenerator* GlobalGenerator;
+
+protected:
+ cmCTestBuildHandler* Handler;
+ enum {
+ ctb_BUILD = ct_LAST,
+ ctb_NUMBER_ERRORS,
+ ctb_NUMBER_WARNINGS,
+ ctb_TARGET,
+ ctb_CONFIGURATION,
+ ctb_FLAGS,
+ ctb_PROJECT_NAME,
+ ctb_LAST
+ };
+
+ cmCTestGenericHandler* InitializeHandler();
+};
+
+
+#endif
diff --git a/Source/CTest/cmCTestBuildHandler.cxx b/Source/CTest/cmCTestBuildHandler.cxx
new file mode 100644
index 000000000..27bb06c13
--- /dev/null
+++ b/Source/CTest/cmCTestBuildHandler.cxx
@@ -0,0 +1,1310 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#include "cmCTestBuildHandler.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 <stdlib.h>
+#include <time.h>
+#include <math.h>
+#include <float.h>
+
+#if defined(__BORLANDC__)
+# pragma warn -8060 /* possibly incorrect assignment */
+#endif
+
+static const char* cmCTestErrorMatches[] = {
+ "^[Bb]us [Ee]rror",
+ "^[Ss]egmentation [Vv]iolation",
+ "^[Ss]egmentation [Ff]ault",
+ ":.*[Pp]ermission [Dd]enied",
+ "([^ :]+):([0-9]+): ([^ \\t])",
+ "([^:]+): error[ \\t]*[0-9]+[ \\t]*:",
+ "^Error ([0-9]+):",
+ "^Fatal",
+ "^Error: ",
+ "^Error ",
+ "[0-9] ERROR: ",
+ "^\"[^\"]+\", line [0-9]+: [^Ww]",
+ "^cc[^C]*CC: ERROR File = ([^,]+), Line = ([0-9]+)",
+ "^ld([^:])*:([ \\t])*ERROR([^:])*:",
+ "^ild:([ \\t])*\\(undefined symbol\\)",
+ "([^ :]+) : (error|fatal error|catastrophic error)",
+ "([^:]+): (Error:|error|undefined reference|multiply defined)",
+ "([^:]+)\\(([^\\)]+)\\) ?: (error|fatal error|catastrophic error)",
+ "^fatal error C[0-9]+:",
+ ": syntax error ",
+ "^collect2: ld returned 1 exit status",
+ "ld terminated with signal",
+ "Unsatisfied symbol",
+ "^Unresolved:",
+ "Undefined symbol",
+ "^Undefined[ \\t]+first referenced",
+ "^CMake Error.*:",
+ ":[ \\t]cannot find",
+ ":[ \\t]can't find",
+ ": \\*\\*\\* 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 \\`.*\\'",
+ "\", 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 ::",
+ "^could not be found and will not be loaded.",
+ "s:616 string too big",
+ "make: Fatal error: ",
+ "ld: 0711-993 Error occurred while writing to the output file:",
+ "ld: fatal: ",
+ "final link failed:",
+ "make: \\*\\*\\*.*Error",
+ "make\\[.*\\]: \\*\\*\\*.*Error",
+ "\\*\\*\\* Error code",
+ "nternal error:",
+ "Makefile:[0-9]+: \\*\\*\\* .* Stop\\.",
+ ": No such file or directory",
+ ": Invalid argument",
+ "^The project cannot be built\\.",
+ "^\\[ERROR\\]",
+ "^Command .* failed with exit code",
+ 0
+};
+
+static const char* cmCTestErrorExceptions[] = {
+ "instantiated from ",
+ "candidates are:",
+ ": warning",
+ ": \\(Warning\\)",
+ ": note",
+ "makefile:",
+ "Makefile:",
+ ":[ \\t]+Where:",
+ "([^ :]+):([0-9]+): Warning",
+ "------ Build started: .* ------",
+ 0
+};
+
+static const char* cmCTestWarningMatches[] = {
+ "([^ :]+):([0-9]+): warning:",
+ "([^ :]+):([0-9]+): note:",
+ "^cc[^C]*CC: WARNING File = ([^,]+), Line = ([0-9]+)",
+ "^ld([^:])*:([ \\t])*WARNING([^:])*:",
+ "([^:]+): warning ([0-9]+):",
+ "^\"[^\"]+\", line [0-9]+: [Ww](arning|arnung)",
+ "([^:]+): warning[ \\t]*[0-9]+[ \\t]*:",
+ "^(Warning|Warnung) ([0-9]+):",
+ "^(Warning|Warnung)[ :]",
+ "WARNING: ",
+ "([^ :]+) : warning",
+ "([^:]+): warning",
+ "\", line [0-9]+\\.[0-9]+: [0-9]+-[0-9]+ \\([WI]\\)",
+ "^cxx: Warning:",
+ ".*file: .* has no symbols",
+ "([^ :]+):([0-9]+): (Warning|Warnung)",
+ "\\([0-9]*\\): remark #[0-9]*",
+ "\".*\", line [0-9]+: remark\\([0-9]*\\):",
+ "cc-[0-9]* CC: REMARK File = .*, Line = [0-9]*",
+ "^CMake Warning.*:",
+ "^\\[WARNING\\]",
+ 0
+};
+
+static const char* cmCTestWarningExceptions[] = {
+ "/usr/.*/X11/Xlib\\.h:[0-9]+: war.*: ANSI C\\+\\+ forbids declaration",
+ "/usr/.*/X11/Xutil\\.h:[0-9]+: war.*: ANSI C\\+\\+ forbids declaration",
+ "/usr/.*/X11/XResource\\.h:[0-9]+: war.*: ANSI C\\+\\+ forbids declaration",
+ "WARNING 84 :",
+ "WARNING 47 :",
+ "makefile:",
+ "Makefile:",
+ "warning: Clock skew detected. Your build may be incomplete.",
+ "/usr/openwin/include/GL/[^:]+:",
+ "bind_at_load",
+ "XrmQGetResource",
+ "IceFlush",
+ "warning LNK4089: all references to [^ \\t]+ discarded by .OPT:REF",
+ "ld32: WARNING 85: definition of dataKey in",
+ "cc: warning 422: Unknown option \"\\+b",
+ "_with_warning_C",
+ 0
+};
+
+struct cmCTestBuildCompileErrorWarningRex
+{
+ const char* RegularExpressionString;
+ int FileIndex;
+ 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 }
+};
+
+//----------------------------------------------------------------------
+cmCTestBuildHandler::cmCTestBuildHandler()
+{
+ this->MaxPreContext = 10;
+ this->MaxPostContext = 10;
+
+ this->MaxErrors = 50;
+ this->MaxWarnings = 50;
+
+ this->LastErrorOrWarning = this->ErrorsAndWarnings.end();
+
+ this->UseCTestLaunch = false;
+}
+
+//----------------------------------------------------------------------
+void cmCTestBuildHandler::Initialize()
+{
+ this->Superclass::Initialize();
+ this->StartBuild = "";
+ this->EndBuild = "";
+ this->CustomErrorMatches.clear();
+ this->CustomErrorExceptions.clear();
+ this->CustomWarningMatches.clear();
+ this->CustomWarningExceptions.clear();
+ this->ReallyCustomWarningMatches.clear();
+ this->ReallyCustomWarningExceptions.clear();
+ this->ErrorWarningFileLineRegex.clear();
+
+ this->ErrorMatchRegex.clear();
+ this->ErrorExceptionRegex.clear();
+ this->WarningMatchRegex.clear();
+ this->WarningExceptionRegex.clear();
+ this->BuildProcessingQueue.clear();
+ this->BuildProcessingErrorQueue.clear();
+ this->BuildOutputLogSize = 0;
+ this->CurrentProcessingLine.clear();
+
+ this->SimplifySourceDir = "";
+ this->SimplifyBuildDir = "";
+ this->OutputLineCounter = 0;
+ this->ErrorsAndWarnings.clear();
+ this->LastErrorOrWarning = this->ErrorsAndWarnings.end();
+ this->PostContextCount = 0;
+ this->MaxPreContext = 10;
+ this->MaxPostContext = 10;
+ this->PreContext.clear();
+
+ this->TotalErrors = 0;
+ this->TotalWarnings = 0;
+ this->LastTickChar = 0;
+
+ this->ErrorQuotaReached = false;
+ this->WarningQuotaReached = false;
+
+ this->MaxErrors = 50;
+ this->MaxWarnings = 50;
+
+ this->UseCTestLaunch = false;
+}
+
+//----------------------------------------------------------------------
+void cmCTestBuildHandler::PopulateCustomVectors(cmMakefile *mf)
+{
+ this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_ERROR_MATCH",
+ this->CustomErrorMatches);
+ this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_ERROR_EXCEPTION",
+ this->CustomErrorExceptions);
+ this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_WARNING_MATCH",
+ 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);
+
+ int n = -1;
+ this->CTest->PopulateCustomInteger(mf, "CTEST_CUSTOM_ERROR_PRE_CONTEXT", n);
+ 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)
+ {
+ this->MaxPostContext = static_cast<size_t>(n);
+ }
+
+ // Record the user-specified custom warning rules.
+ 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"))
+ {
+ 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 configType = this->CTest->GetConfigType();
+ if (configType == "")
+ {
+ configType
+ = this->CTest->GetCTestConfiguration("DefaultCTestConfigurationType");
+ }
+ if (configType == "")
+ {
+ configType = "Release";
+ }
+
+ 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...
+int cmCTestBuildHandler::ProcessHandler()
+{
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, "Build project" << std::endl);
+
+ // do we have time for this
+ if (this->CTest->GetRemainingTimeAllowed() < 120)
+ {
+ return 0;
+ }
+
+ int entry;
+ for ( entry = 0;
+ cmCTestWarningErrorFileLine[entry].RegularExpressionString;
+ ++ entry )
+ {
+ cmCTestBuildHandler::cmCTestCompileErrorWarningRex r;
+ 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);
+ }
+ }
+
+ // Determine build command and build directory
+ std::string makeCommand = this->GetMakeCommand();
+ if ( makeCommand.size() == 0 )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Cannot find MakeCommand key in the DartConfiguration.tcl"
+ << std::endl);
+ return -1;
+ }
+
+ const std::string &buildDirectory
+ = this->CTest->GetCTestConfiguration("BuildDirectory");
+ if ( buildDirectory.size() == 0 )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Cannot find BuildDirectory key in the DartConfiguration.tcl"
+ << std::endl);
+ return -1;
+ }
+
+ std::string const& useLaunchers =
+ this->CTest->GetCTestConfiguration("UseLaunchers");
+ this->UseCTestLaunch = cmSystemTools::IsOn(useLaunchers.c_str());
+
+ // Create a last build log
+ cmGeneratedFileStream ofs;
+ double elapsed_time_start = cmSystemTools::GetTime();
+ if ( !this->StartLogFile("Build", ofs) )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot create build log file"
+ << 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 ++ )
+ {
+ this->CustomErrorMatches.push_back(cmCTestErrorMatches[cc]);
+ }
+ for ( cc = 0; cmCTestErrorExceptions[cc]; cc ++ )
+ {
+ this->CustomErrorExceptions.push_back(cmCTestErrorExceptions[cc]);
+ }
+ for ( cc = 0; cmCTestWarningMatches[cc]; cc ++ )
+ {
+ this->CustomWarningMatches.push_back(cmCTestWarningMatches[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);
+
+
+ // 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") + "/";
+ std::string srcdirrep;
+ for ( cc = srcdir.size()-2; cc > 0; cc -- )
+ {
+ if ( srcdir[cc] == '/' )
+ {
+ srcdirrep = srcdir.c_str() + cc;
+ srcdirrep = "/..." + srcdirrep;
+ srcdir = srcdir.substr(0, cc+1);
+ break;
+ }
+ }
+ 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] == '/' )
+ {
+ bindirrep = bindir.c_str() + cc;
+ bindirrep = "/..." + bindirrep;
+ bindir = bindir.substr(0, cc+1);
+ break;
+ }
+ }
+ this->SimplifyBuildDir = bindir;
+ }
+
+
+ // Ok, let's do the build
+
+ // Remember start build time
+ this->StartBuild = this->CTest->CurrentTime();
+ this->StartBuildTime = cmSystemTools::GetTime();
+ int retVal = 0;
+ int res = cmsysProcess_State_Exited;
+ 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);
+ }
+
+ // Remember end build time and calculate elapsed time
+ this->EndBuild = this->CTest->CurrentTime();
+ this->EndBuildTime = cmSystemTools::GetTime();
+ double elapsed_build_time = cmSystemTools::GetTime() - elapsed_time_start;
+
+ // 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->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))
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot create build XML file"
+ << 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)
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Error(s) when building project"
+ << 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);
+
+ return retVal;
+}
+
+//----------------------------------------------------------------------------
+void cmCTestBuildHandler::GenerateXMLHeader(std::ostream& os)
+{
+ 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;
+}
+
+//----------------------------------------------------------------------------
+class cmCTestBuildHandler::FragmentCompare
+{
+public:
+ FragmentCompare(cmFileTimeComparison* ftc): FTC(ftc) {}
+ FragmentCompare(): FTC(0) {}
+ bool operator()(std::string const& l, std::string const& r)
+ {
+ // 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)
+ {
+ return result < 0;
+ }
+ else
+ {
+ return l < r;
+ }
+ }
+private:
+ cmFileTimeComparison* FTC;
+};
+
+//----------------------------------------------------------------------------
+void cmCTestBuildHandler::GenerateXMLLaunched(std::ostream& os)
+{
+ if(this->CTestLaunchDir.empty())
+ {
+ return;
+ }
+
+ // Sort XML fragments in chronological order.
+ cmFileTimeComparison ftc;
+ FragmentCompare fragmentCompare(&ftc);
+ typedef std::set<cmStdString, FragmentCompare> Fragments;
+ Fragments fragments(fragmentCompare);
+
+ // Identify fragments on disk.
+ cmsys::Directory launchDir;
+ launchDir.Load(this->CTestLaunchDir.c_str());
+ unsigned long n = launchDir.GetNumberOfFiles();
+ for(unsigned long i=0; i < n; ++i)
+ {
+ const char* fname = launchDir.GetFile(i);
+ if(this->IsLaunchedErrorFile(fname))
+ {
+ fragments.insert(this->CTestLaunchDir + "/" + fname);
+ ++this->TotalErrors;
+ }
+ else if(this->IsLaunchedWarningFile(fname))
+ {
+ 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());
+ }
+}
+
+//----------------------------------------------------------------------------
+void cmCTestBuildHandler::GenerateXMLLogScraped(std::ostream& os)
+{
+ std::vector<cmCTestBuildErrorWarning>& ew = this->ErrorsAndWarnings;
+ std::vector<cmCTestBuildErrorWarning>::iterator it;
+
+ // only report the first 50 warnings and first 50 errors
+ int numErrorsAllowed = this->MaxErrors;
+ int numWarningsAllowed = this->MaxWarnings;
+ 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 += "/";
+ for ( it = ew.begin();
+ it != ew.end() && (numErrorsAllowed || numWarningsAllowed); it++ )
+ {
+ cmCTestBuildErrorWarning *cm = &(*it);
+ if ((cm->Error && numErrorsAllowed) ||
+ (!cm->Error && numWarningsAllowed))
+ {
+ if (cm->Error)
+ {
+ numErrorsAllowed--;
+ }
+ 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;
+ std::vector<cmCTestCompileErrorWarningRex>::iterator rit;
+ for ( rit = this->ErrorWarningFileLineRegex.begin();
+ rit != this->ErrorWarningFileLineRegex.end(); ++ rit )
+ {
+ cmsys::RegularExpression* re = &rit->RegularExpression;
+ 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)
+ {
+ 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);
+ }
+ }
+ 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->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;
+ }
+ }
+ os << "\t\t<PreContext>" << cmXMLSafe(cm->PreContext).Quotes(false)
+ << "</PreContext>\n"
+ << "\t\t<PostContext>" << cmXMLSafe(cm->PostContext).Quotes(false);
+ // 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;
+ }
+ }
+}
+
+//----------------------------------------------------------------------------
+void cmCTestBuildHandler::GenerateXMLFooter(std::ostream& os,
+ 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";
+ }
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestBuildHandler::IsLaunchedErrorFile(const char* fname)
+{
+ // error-{hash}.xml
+ return (strncmp(fname, "error-", 6) == 0 &&
+ 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);
+}
+
+//######################################################################
+//######################################################################
+//######################################################################
+//######################################################################
+
+//----------------------------------------------------------------------------
+class cmCTestBuildHandler::LaunchHelper
+{
+public:
+ LaunchHelper(cmCTestBuildHandler* handler);
+ ~LaunchHelper();
+private:
+ cmCTestBuildHandler* Handler;
+ cmCTest* CTest;
+
+ void WriteLauncherConfig();
+ void WriteScrapeMatchers(const char* purpose,
+ std::vector<std::string> const& matchers);
+};
+
+//----------------------------------------------------------------------------
+cmCTestBuildHandler::LaunchHelper::LaunchHelper(cmCTestBuildHandler* handler):
+ Handler(handler), CTest(handler->CTest)
+{
+ std::string tag = this->CTest->GetCurrentTag();
+ if(tag.empty())
+ {
+ // This is not for a dashboard submission, so there is no XML.
+ // Skip enabling the launchers.
+ this->Handler->UseCTestLaunch = false;
+ }
+ else
+ {
+ // Compute a directory in which to store launcher fragments.
+ std::string& launchDir = this->Handler->CTestLaunchDir;
+ launchDir = this->CTest->GetBinaryDir();
+ launchDir += "/Testing/";
+ launchDir += tag;
+ launchDir += "/Build";
+
+ // Clean out any existing launcher fragments.
+ cmSystemTools::RemoveADirectory(launchDir.c_str());
+
+ 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());
+ }
+ }
+
+ // If not using launchers, make sure they passthru.
+ if(!this->Handler->UseCTestLaunch)
+ {
+ cmSystemTools::UnsetEnv("CTEST_LAUNCH_LOGS");
+ }
+}
+
+//----------------------------------------------------------------------------
+cmCTestBuildHandler::LaunchHelper::~LaunchHelper()
+{
+ if(this->Handler->UseCTestLaunch)
+ {
+ cmSystemTools::UnsetEnv("CTEST_LAUNCH_LOGS");
+ }
+}
+
+//----------------------------------------------------------------------------
+void cmCTestBuildHandler::LaunchHelper::WriteLauncherConfig()
+{
+ this->WriteScrapeMatchers("Warning",
+ this->Handler->ReallyCustomWarningMatches);
+ this->WriteScrapeMatchers("WarningSuppress",
+ this->Handler->ReallyCustomWarningExceptions);
+
+ // Give some testing configuration information to the launcher.
+ std::string fname = this->Handler->CTestLaunchDir;
+ fname += "/CTestLaunchConfig.cmake";
+ cmGeneratedFileStream fout(fname.c_str());
+ std::string srcdir = this->CTest->GetCTestConfiguration("SourceDirectory");
+ fout << "set(CTEST_SOURCE_DIRECTORY \"" << srcdir << "\")\n";
+}
+
+//----------------------------------------------------------------------------
+void
+cmCTestBuildHandler::LaunchHelper
+::WriteScrapeMatchers(const char* purpose,
+ std::vector<std::string> const& matchers)
+{
+ 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)
+ {
+ fout << *mi << "\n";
+ }
+}
+
+//----------------------------------------------------------------------
+int cmCTestBuildHandler::RunMakeCommand(const char* command,
+ int* retVal, const char* dir, int timeout, std::ofstream& ofs)
+{
+ // First generate the command and arguments
+ std::vector<cmStdString> args = cmSystemTools::ParseArguments(command);
+
+ if(args.size() < 1)
+ {
+ return false;
+ }
+
+ std::vector<const char*> argv;
+ for(std::vector<cmStdString>::const_iterator a = args.begin();
+ a != args.end(); ++a)
+ {
+ argv.push_back(a->c_str());
+ }
+ argv.push_back(0);
+
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Run command:");
+ 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);
+
+ // Optionally use make rule launchers to record errors and warnings.
+ LaunchHelper launchHelper(this);
+ static_cast<void>(launchHelper);
+
+ // Now create process object
+ cmsysProcess* cp = cmsysProcess_New();
+ cmsysProcess_SetCommand(cp, &*argv.begin());
+ cmsysProcess_SetWorkingDirectory(cp, dir);
+ cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
+ cmsysProcess_SetTimeout(cp, timeout);
+ cmsysProcess_Execute(cp);
+
+ // Initialize tick's
+ std::string::size_type tick = 0;
+ const std::string::size_type tick_len = 1024;
+
+ 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);
+
+ // Initialize building structures
+ this->BuildProcessingQueue.clear();
+ this->OutputLineCounter = 0;
+ this->ErrorsAndWarnings.clear();
+ this->TotalErrors = 0;
+ this->TotalWarnings = 0;
+ this->BuildOutputLogSize = 0;
+ this->LastTickChar = '.';
+ this->WarningQuotaReached = false;
+ this->ErrorQuotaReached = false;
+
+ // For every chunk of data
+ int res;
+ while((res = cmsysProcess_WaitForData(cp, &data, &length, 0)))
+ {
+ // 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)
+ {
+ 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);
+ }
+ }
+
+ 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);
+
+ // Properly handle output of the build command
+ cmsysProcess_WaitForExit(cp, 0);
+ int result = cmsysProcess_GetState(cp);
+
+ 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);
+ // if a non zero return value
+ 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.PostContext = "";
+ errorwarning.Error = false;
+ this->ErrorsAndWarnings.push_back(errorwarning);
+ this->TotalWarnings ++;
+ }
+ }
+ }
+ 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);
+ }
+ 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.PostContext = "";
+ errorwarning.Error = true;
+ this->ErrorsAndWarnings.push_back(errorwarning);
+ this->TotalErrors ++;
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "There was an error: "
+ << cmsysProcess_GetErrorString(cp) << std::endl);
+ }
+
+ cmsysProcess_Delete(cp);
+ return result;
+}
+
+//######################################################################
+//######################################################################
+//######################################################################
+//######################################################################
+
+//----------------------------------------------------------------------
+void cmCTestBuildHandler::ProcessBuffer(const char* data, int length,
+ size_t& tick, size_t tick_len, std::ofstream& ofs,
+ t_BuildProcessingQueueType* queue)
+{
+ const std::string::size_type tick_line_len = 50;
+ const char* 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 )
+ {
+ // Find the end of line
+ t_BuildProcessingQueueType::iterator it;
+ 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 )
+ {
+ this->WarningQuotaReached = true;
+ }
+ if ( this->TotalErrors >= this->MaxErrors )
+ {
+ this->ErrorQuotaReached = true;
+ }
+
+ // If the end of line was found
+ 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.push_back(0);
+ const char* line = &*this->CurrentProcessingLine.begin();
+
+ // Process the line
+ int lineType = this->ProcessSingleLine(line);
+
+ // Erase the line from the queue
+ 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 )
+ {
+ // This is an error or warning, so generate report
+ 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 )
+ {
+ errorwarning.PreContext += *pcit + "\n";
+ }
+ this->PreContext.clear();
+
+ // Store report
+ this->ErrorsAndWarnings.push_back(errorwarning);
+ this->LastErrorOrWarning = this->ErrorsAndWarnings.end()-1;
+ this->PostContextCount = 0;
+ }
+ 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 ++;
+ this->LastErrorOrWarning->PostContext += line;
+ if ( this->PostContextCount < this->MaxPostContext )
+ {
+ this->LastErrorOrWarning->PostContext += "\n";
+ }
+ }
+ else
+ {
+ // Otherwise store pre-context for the next error
+ this->PreContext.push_back(line);
+ if ( this->PreContext.size() > this->MaxPreContext )
+ {
+ this->PreContext.erase(this->PreContext.begin(),
+ this->PreContext.end()-this->MaxPreContext);
+ }
+ }
+ }
+ 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);
+ tickDisplayed = true;
+ if ( tick % tick_line_len == 0 && tick > 0 )
+ {
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, " Size: "
+ << ((this->BuildOutputLogSize + 512) / 1024) << "K" << std::endl
+ << " ");
+ }
+ }
+ 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));
+
+ // Always store the chunk to the file
+ ofs << cmCTestLogWrite(data, length);
+}
+
+//----------------------------------------------------------------------
+int cmCTestBuildHandler::ProcessSingleLine(const char* data)
+{
+ if(this->UseCTestLaunch)
+ {
+ // No log scraping when using launchers.
+ return b_REGULAR_LINE;
+ }
+
+ cmCTestLog(this->CTest, DEBUG, "Line: [" << data << "]" << std::endl);
+
+ std::vector<cmsys::RegularExpression>::iterator it;
+
+ int warningLine = 0;
+ int errorLine = 0;
+
+ // Check for regular expressions
+
+ if ( !this->ErrorQuotaReached )
+ {
+ // Errors
+ int wrxCnt = 0;
+ 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);
+ break;
+ }
+ wrxCnt ++;
+ }
+ // Error exceptions
+ wrxCnt = 0;
+ 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);
+ break;
+ }
+ wrxCnt ++;
+ }
+ }
+ if ( !this->WarningQuotaReached )
+ {
+ // Warnings
+ int wrxCnt = 0;
+ 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);
+ break;
+ }
+ wrxCnt ++;
+ }
+
+ wrxCnt = 0;
+ // Warning exceptions
+ 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);
+ break;
+ }
+ wrxCnt ++;
+ }
+ }
+ if ( errorLine )
+ {
+ return b_ERROR_LINE;
+ }
+ if ( warningLine )
+ {
+ return b_WARNING_LINE;
+ }
+ return b_REGULAR_LINE;
+}
+
diff --git a/Source/CTest/cmCTestBuildHandler.h b/Source/CTest/cmCTestBuildHandler.h
new file mode 100644
index 000000000..439efd604
--- /dev/null
+++ b/Source/CTest/cmCTestBuildHandler.h
@@ -0,0 +1,152 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#ifndef cmCTestBuildHandler_h
+#define cmCTestBuildHandler_h
+
+
+#include "cmCTestGenericHandler.h"
+#include "cmListFileCache.h"
+
+#include <cmsys/RegularExpression.hxx>
+
+class cmMakefile;
+
+/** \class cmCTestBuildHandler
+ * \brief A class that handles ctest -S invocations
+ *
+ */
+class cmCTestBuildHandler : public cmCTestGenericHandler
+{
+public:
+ cmTypeMacro(cmCTestBuildHandler, cmCTestGenericHandler);
+
+ /*
+ * The main entry point for this class
+ */
+ int ProcessHandler();
+
+ cmCTestBuildHandler();
+
+ void PopulateCustomVectors(cmMakefile *mf);
+
+ /**
+ * Initialize handler
+ */
+ virtual void Initialize();
+
+ 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);
+
+ 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;
+ std::string Text;
+ std::string SourceFile;
+ std::string SourceFileTail;
+ 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);
+ bool IsLaunchedErrorFile(const char* fname);
+ bool IsLaunchedWarningFile(const char* fname);
+
+ 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> ReallyCustomWarningMatches;
+ std::vector<std::string> ReallyCustomWarningExceptions;
+ std::vector<cmCTestCompileErrorWarningRex> ErrorWarningFileLineRegex;
+
+ std::vector<cmsys::RegularExpression> ErrorMatchRegex;
+ std::vector<cmsys::RegularExpression> ErrorExceptionRegex;
+ std::vector<cmsys::RegularExpression> WarningMatchRegex;
+ std::vector<cmsys::RegularExpression> WarningExceptionRegex;
+
+ 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);
+ int ProcessSingleLine(const char* data);
+
+ t_BuildProcessingQueueType BuildProcessingQueue;
+ t_BuildProcessingQueueType BuildProcessingErrorQueue;
+ size_t BuildOutputLogSize;
+ std::vector<char> CurrentProcessingLine;
+
+ cmStdString SimplifySourceDir;
+ cmStdString 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;
+
+ int TotalErrors;
+ int TotalWarnings;
+ char LastTickChar;
+
+ bool ErrorQuotaReached;
+ bool WarningQuotaReached;
+
+ int MaxErrors;
+ int MaxWarnings;
+
+ bool UseCTestLaunch;
+ std::string CTestLaunchDir;
+ class LaunchHelper;
+ friend class LaunchHelper;
+ class FragmentCompare;
+};
+
+#endif
diff --git a/Source/CTest/cmCTestCVS.cxx b/Source/CTest/cmCTestCVS.cxx
new file mode 100644
index 000000000..726950704
--- /dev/null
+++ b/Source/CTest/cmCTestCVS.cxx
@@ -0,0 +1,319 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestCVS.h"
+
+#include "cmCTest.h"
+#include "cmSystemTools.h"
+#include "cmXMLSafe.h"
+
+#include <cmsys/RegularExpression.hxx>
+
+//----------------------------------------------------------------------------
+cmCTestCVS::cmCTestCVS(cmCTest* ct, std::ostream& log): cmCTestVC(ct, log)
+{
+}
+
+//----------------------------------------------------------------------------
+cmCTestCVS::~cmCTestCVS()
+{
+}
+
+//----------------------------------------------------------------------------
+class cmCTestCVS::UpdateParser: public cmCTestVC::LineParser
+{
+public:
+ UpdateParser(cmCTestCVS* cvs, const char* prefix): CVS(cvs)
+ {
+ this->SetLog(&cvs->Log, prefix);
+ // See "man cvs", section "update output".
+ this->RegexFileUpdated.compile("^([UP]) *(.*)");
+ this->RegexFileModified.compile("^([MRA]) *(.*)");
+ this->RegexFileConflicting.compile("^([C]) *(.*)");
+ this->RegexFileRemoved1.compile(
+ "cvs[^ ]* update: `?([^']*)'? is no longer in the repository");
+ this->RegexFileRemoved2.compile(
+ "cvs[^ ]* update: "
+ "warning: `?([^']*)'? is not \\(any longer\\) pertinent");
+ }
+private:
+ cmCTestCVS* CVS;
+ cmsys::RegularExpression RegexFileUpdated;
+ cmsys::RegularExpression RegexFileModified;
+ cmsys::RegularExpression RegexFileConflicting;
+ cmsys::RegularExpression RegexFileRemoved1;
+ cmsys::RegularExpression RegexFileRemoved2;
+
+ virtual bool ProcessLine()
+ {
+ if(this->RegexFileUpdated.find(this->Line))
+ {
+ this->DoFile(PathUpdated, this->RegexFileUpdated.match(2));
+ }
+ else if(this->RegexFileModified.find(this->Line))
+ {
+ this->DoFile(PathModified, this->RegexFileModified.match(2));
+ }
+ else if(this->RegexFileConflicting.find(this->Line))
+ {
+ this->DoFile(PathConflicting, this->RegexFileConflicting.match(2));
+ }
+ else if(this->RegexFileRemoved1.find(this->Line))
+ {
+ this->DoFile(PathUpdated, this->RegexFileRemoved1.match(1));
+ }
+ else if(this->RegexFileRemoved2.find(this->Line))
+ {
+ this->DoFile(PathUpdated, this->RegexFileRemoved2.match(1));
+ }
+ 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())
+ {
+ opts = this->CTest->GetCTestConfiguration("CVSUpdateOptions");
+ if(opts.empty())
+ {
+ opts = "-dP";
+ }
+ }
+ std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str());
+
+ // Specify the start time for nightly testing.
+ 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)
+ {
+ cvs_update.push_back(ai->c_str());
+ }
+ cvs_update.push_back(0);
+
+ UpdateParser out(this, "up-out> ");
+ UpdateParser err(this, "up-err> ");
+ return this->RunUpdateCommand(&cvs_update[0], &out, &err);
+}
+
+//----------------------------------------------------------------------------
+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)
+ {
+ this->SetLog(&cvs->Log, prefix),
+ 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 };
+ SectionType Section;
+ Revision Rev;
+
+ virtual bool ProcessLine()
+ {
+ if(this->Line == ("======================================="
+ "======================================"))
+ {
+ // This line ends the revision list.
+ if(this->Section == SectionRevisions)
+ {
+ this->FinishRevision();
+ }
+ this->Section = SectionEnd;
+ }
+ else if(this->Line == "----------------------------")
+ {
+ // This line divides revisions from the header and each other.
+ if(this->Section == SectionHeader)
+ {
+ this->Section = SectionRevisions;
+ }
+ else if(this->Section == SectionRevisions)
+ {
+ this->FinishRevision();
+ }
+ }
+ 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))
+ {
+ this->Rev.Rev = this->RegexRevision.match(1);
+ }
+ 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))
+ {
+ // Start the log.
+ this->Rev.Log += this->Line;
+ this->Rev.Log += "\n";
+ }
+ }
+ return this->Section != SectionEnd;
+ }
+
+ void FinishRevision()
+ {
+ if(!this->Rev.Rev.empty())
+ {
+ // Record this revision.
+ this->CVS->Log << "Found revision " << this->Rev.Rev << "\n"
+ << " author = " << this->Rev.Author << "\n"
+ << " date = " << this->Rev.Date << "\n";
+ this->Revisions.push_back(this->Rev);
+
+ // We only need two revisions.
+ if(this->Revisions.size() >= 2)
+ {
+ this->Section = SectionEnd;
+ }
+ }
+ 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())
+ {
+ 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')
+ {
+ // 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";
+ }
+}
+
+//----------------------------------------------------------------------------
+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};
+
+ 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,
+ Directory const& dir)
+{
+ const char* slash = path.empty()? "":"/";
+ xml << "\t<Directory>\n"
+ << "\t\t<Name>" << cmXMLSafe(path) << "</Name>\n";
+
+ // 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)
+ {
+ std::string full = path + slash + fi->first;
+
+ // Load two real or unknown revisions.
+ revisions.clear();
+ 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";
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestCVS::WriteXMLUpdates(std::ostream& xml)
+{
+ cmCTestLog(this->CTest, HANDLER_OUTPUT,
+ " Gathering version information (one . per updated file):\n"
+ " " << std::flush);
+
+ for(std::map<cmStdString, 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);
+
+ return true;
+}
diff --git a/Source/CTest/cmCTestCVS.h b/Source/CTest/cmCTestCVS.h
new file mode 100644
index 000000000..b7fe567d8
--- /dev/null
+++ b/Source/CTest/cmCTestCVS.h
@@ -0,0 +1,51 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestCVS_h
+#define cmCTestCVS_h
+
+#include "cmCTestVC.h"
+
+/** \class cmCTestCVS
+ * \brief Interaction with cvs command-line tool
+ *
+ */
+class cmCTestCVS: public cmCTestVC
+{
+public:
+ /** Construct with a CTest instance and update log stream. */
+ cmCTestCVS(cmCTest* ctest, std::ostream& log);
+
+ virtual ~cmCTestCVS();
+
+private:
+ // Implement cmCTestVC internal API.
+ virtual bool UpdateImpl();
+ virtual bool WriteXMLUpdates(std::ostream& xml);
+
+ // Update status for files in each directory.
+ class Directory: public std::map<cmStdString, PathStatus> {};
+ std::map<cmStdString, 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,
+ Directory const& dir);
+
+ // Parsing helper classes.
+ class UpdateParser;
+ class LogParser;
+ friend class UpdateParser;
+ friend class LogParser;
+};
+
+#endif
diff --git a/Source/CTest/cmCTestCommand.h b/Source/CTest/cmCTestCommand.h
new file mode 100644
index 000000000..e2ebba8fd
--- /dev/null
+++ b/Source/CTest/cmCTestCommand.h
@@ -0,0 +1,40 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestCommand_h
+#define cmCTestCommand_h
+
+#include "cmCommand.h"
+
+class cmCTest;
+class cmCTestScriptHandler;
+
+/** \class cmCTestCommand
+ * \brief A superclass for all commands added to the CTestScriptHandler
+ *
+ * cmCTestCommand is the superclass for all commands that will be added to
+ * the ctest script handlers parser.
+ *
+ */
+class cmCTestCommand : public cmCommand
+{
+public:
+
+ cmCTestCommand() {this->CTest = 0; this->CTestScriptHandler = 0;}
+
+ cmCTest *CTest;
+ cmCTestScriptHandler *CTestScriptHandler;
+
+ cmTypeMacro(cmCTestCommand, cmCommand);
+};
+
+
+#endif
diff --git a/Source/CTest/cmCTestConfigureCommand.cxx b/Source/CTest/cmCTestConfigureCommand.cxx
new file mode 100644
index 000000000..7a99ddfa0
--- /dev/null
+++ b/Source/CTest/cmCTestConfigureCommand.cxx
@@ -0,0 +1,172 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestConfigureCommand.h"
+
+#include "cmGlobalGenerator.h"
+#include "cmCTest.h"
+#include "cmCTestGenericHandler.h"
+
+cmCTestConfigureCommand::cmCTestConfigureCommand()
+{
+ this->Arguments[ctc_OPTIONS] = "OPTIONS";
+ this->Arguments[ctc_LAST] = 0;
+ this->Last = ctc_LAST;
+}
+
+cmCTestGenericHandler* cmCTestConfigureCommand::InitializeHandler()
+{
+ std::vector<std::string> options;
+
+ if (this->Values[ctc_OPTIONS])
+ {
+ cmSystemTools::ExpandListArgument(this->Values[ctc_OPTIONS], options);
+ }
+
+ if ( this->Values[ct_BUILD] )
+ {
+ this->CTest->SetCTestConfiguration("BuildDirectory",
+ cmSystemTools::CollapseFullPath(
+ this->Values[ct_BUILD]).c_str());
+ }
+ else
+ {
+ this->CTest->SetCTestConfiguration("BuildDirectory",
+ cmSystemTools::CollapseFullPath(
+ this->Makefile->GetSafeDefinition("CTEST_BINARY_DIRECTORY")).c_str());
+ }
+
+ if ( this->Values[ct_SOURCE] )
+ {
+ this->CTest->SetCTestConfiguration("SourceDirectory",
+ cmSystemTools::CollapseFullPath(
+ this->Values[ct_SOURCE]).c_str());
+ }
+ else
+ {
+ this->CTest->SetCTestConfiguration("SourceDirectory",
+ cmSystemTools::CollapseFullPath(
+ this->Makefile->GetSafeDefinition("CTEST_SOURCE_DIRECTORY")).c_str());
+ }
+
+ 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;
+ }
+
+ const char* ctestConfigureCommand
+ = this->Makefile->GetDefinition("CTEST_CONFIGURE_COMMAND");
+
+ 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 "
+ "argument to CTEST_CONFIGURE command or set CTEST_SOURCE_DIRECTORY "
+ "variable");
+ return 0;
+ }
+
+ 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;
+ }
+
+ bool multiConfig = false;
+ bool cmakeBuildTypeInOptions = false;
+
+ cmGlobalGenerator *gg =
+ this->Makefile->GetCMakeInstance()->CreateGlobalGenerator(
+ cmakeGeneratorName);
+ if(gg)
+ {
+ multiConfig = gg->IsMultiConfig();
+ delete gg;
+ }
+
+ std::string cmakeConfigureCommand = "\"";
+ cmakeConfigureCommand += this->CTest->GetCMakeExecutable();
+ cmakeConfigureCommand += "\"";
+
+ std::vector<std::string>::const_iterator it;
+ std::string option;
+ 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=")))
+ {
+ cmakeBuildTypeInOptions = true;
+ }
+ }
+
+ if (!multiConfig && !cmakeBuildTypeInOptions &&
+ !this->CTest->GetConfigType().empty())
+ {
+ cmakeConfigureCommand += " \"-DCMAKE_BUILD_TYPE:STRING=";
+ cmakeConfigureCommand += this->CTest->GetConfigType();
+ cmakeConfigureCommand += "\"";
+ }
+
+ cmakeConfigureCommand += " \"-G";
+ cmakeConfigureCommand += cmakeGeneratorName;
+ 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 "
+ "\"built with CMake\" project, set CTEST_CMAKE_GENERATOR. If not, "
+ "set CTEST_CONFIGURE_COMMAND.");
+ return 0;
+ }
+ }
+
+ cmCTestGenericHandler* handler
+ = this->CTest->GetInitializedHandler("configure");
+ if ( !handler )
+ {
+ this->SetError(
+ "internal CTest error. Cannot instantiate configure handler");
+ return 0;
+ }
+ return handler;
+}
diff --git a/Source/CTest/cmCTestConfigureCommand.h b/Source/CTest/cmCTestConfigureCommand.h
new file mode 100644
index 000000000..b343fc1e9
--- /dev/null
+++ b/Source/CTest/cmCTestConfigureCommand.h
@@ -0,0 +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.
+============================================================================*/
+#ifndef cmCTestConfigureCommand_h
+#define cmCTestConfigureCommand_h
+
+#include "cmCTestHandlerCommand.h"
+
+/** \class cmCTestConfigure
+ * \brief Run a ctest script
+ *
+ * cmCTestConfigureCommand defineds the command to configures the project.
+ */
+class cmCTestConfigureCommand : public cmCTestHandlerCommand
+{
+public:
+ cmCTestConfigureCommand();
+
+ /**
+ * This is a virtual constructor for the command.
+ */
+ virtual cmCommand* Clone()
+ {
+ 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);
+
+protected:
+ cmCTestGenericHandler* InitializeHandler();
+
+ enum {
+ ctc_FIRST = ct_LAST,
+ ctc_OPTIONS,
+ ctc_LAST
+ };
+};
+
+
+#endif
diff --git a/Source/CTest/cmCTestConfigureHandler.cxx b/Source/CTest/cmCTestConfigureHandler.cxx
new file mode 100644
index 000000000..33297564e
--- /dev/null
+++ b/Source/CTest/cmCTestConfigureHandler.cxx
@@ -0,0 +1,132 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#include "cmCTestConfigureHandler.h"
+
+#include "cmCTest.h"
+#include "cmGeneratedFileStream.h"
+#include "cmake.h"
+#include "cmXMLSafe.h"
+#include <cmsys/Process.h>
+
+
+//----------------------------------------------------------------------
+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...
+int cmCTestConfigureHandler::ProcessHandler()
+{
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, "Configure project" << std::endl);
+ std::string cCommand
+ = this->CTest->GetCTestConfiguration("ConfigureCommand");
+ if ( cCommand.size() == 0 )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Cannot find ConfigureCommand key in the DartConfiguration.tcl"
+ << std::endl);
+ return -1;
+ }
+
+ std::string buildDirectory
+ = this->CTest->GetCTestConfiguration("BuildDirectory");
+ if ( buildDirectory.size() == 0 )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "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() )
+ {
+ cmGeneratedFileStream os;
+ if(!this->StartResultingXML(cmCTest::PartConfigure, "Configure", os))
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot open configure file"
+ << std::endl);
+ return 1;
+ }
+ std::string start_time = this->CTest->CurrentTime();
+ 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);
+
+ 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 (! res || retVal )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Error(s) when configuring the project" << std::endl);
+ return -1;
+ }
+ return 0;
+}
diff --git a/Source/CTest/cmCTestConfigureHandler.h b/Source/CTest/cmCTestConfigureHandler.h
new file mode 100644
index 000000000..d880bd7df
--- /dev/null
+++ b/Source/CTest/cmCTestConfigureHandler.h
@@ -0,0 +1,39 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#ifndef cmCTestConfigureHandler_h
+#define cmCTestConfigureHandler_h
+
+
+#include "cmCTestGenericHandler.h"
+#include "cmListFileCache.h"
+
+/** \class cmCTestConfigureHandler
+ * \brief A class that handles ctest -S invocations
+ *
+ */
+class cmCTestConfigureHandler : public cmCTestGenericHandler
+{
+public:
+ cmTypeMacro(cmCTestConfigureHandler, cmCTestGenericHandler);
+
+ /*
+ * The main entry point for this class
+ */
+ int ProcessHandler();
+
+ cmCTestConfigureHandler();
+
+ void Initialize();
+};
+
+#endif
diff --git a/Source/CTest/cmCTestCoverageCommand.cxx b/Source/CTest/cmCTestCoverageCommand.cxx
new file mode 100644
index 000000000..72ff720d3
--- /dev/null
+++ b/Source/CTest/cmCTestCoverageCommand.cxx
@@ -0,0 +1,73 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestCoverageCommand.h"
+
+#include "cmCTest.h"
+#include "cmCTestCoverageHandler.h"
+
+//----------------------------------------------------------------------------
+cmCTestCoverageCommand::cmCTestCoverageCommand()
+{
+ this->LabelsMentioned = false;
+}
+
+//----------------------------------------------------------------------------
+cmCTestGenericHandler* cmCTestCoverageCommand::InitializeHandler()
+{
+ this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile,
+ "CoverageCommand", "CTEST_COVERAGE_COMMAND");
+
+ cmCTestCoverageHandler* handler = static_cast<cmCTestCoverageHandler*>(
+ this->CTest->GetInitializedHandler("coverage"));
+ if ( !handler )
+ {
+ this->SetError("internal CTest error. Cannot instantiate test handler");
+ return 0;
+ }
+
+ // If a LABELS option was given, select only files with the labels.
+ if(this->LabelsMentioned)
+ {
+ handler->SetLabelFilter(this->Labels);
+ }
+
+ return handler;
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestCoverageCommand::CheckArgumentKeyword(std::string const& arg)
+{
+ // Look for arguments specific to this command.
+ 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)
+ {
+ 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
new file mode 100644
index 000000000..2fe762c45
--- /dev/null
+++ b/Source/CTest/cmCTestCoverageCommand.h
@@ -0,0 +1,90 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestCoverageCommand_h
+#define cmCTestCoverageCommand_h
+
+#include "cmCTestHandlerCommand.h"
+
+/** \class cmCTestCoverage
+ * \brief Run a ctest script
+ *
+ * cmCTestCoverageCommand defineds the command to test the project.
+ */
+class cmCTestCoverageCommand : public cmCTestHandlerCommand
+{
+public:
+
+ cmCTestCoverageCommand();
+
+ /**
+ * This is a virtual constructor for the command.
+ */
+ virtual cmCommand* Clone()
+ {
+ 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";}
+
+ /**
+ * 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);
+
+protected:
+ cmCTestGenericHandler* InitializeHandler();
+
+ virtual bool CheckArgumentKeyword(std::string const& arg);
+ virtual bool CheckArgumentValue(std::string const& arg);
+
+ enum
+ {
+ ArgumentDoingLabels = Superclass::ArgumentDoingLast1,
+ ArgumentDoingLast2
+ };
+
+ bool LabelsMentioned;
+ std::set<cmStdString> Labels;
+};
+
+
+#endif
+
diff --git a/Source/CTest/cmCTestCoverageHandler.cxx b/Source/CTest/cmCTestCoverageHandler.cxx
new file mode 100644
index 000000000..81d366937
--- /dev/null
+++ b/Source/CTest/cmCTestCoverageHandler.cxx
@@ -0,0 +1,2144 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#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 <stdlib.h>
+#include <math.h>
+#include <float.h>
+
+#define SAFEDIV(x,y) (((y)!=0)?((x)/(y)):(0))
+
+class cmCTestRunProcess
+{
+public:
+ cmCTestRunProcess()
+ {
+ 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);
+ }
+ void SetCommand(const char* 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;
+ }
+ 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;
+ }
+ 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;}
+private:
+ int PipeState;
+ cmsysProcess* Process;
+ std::vector<std::string> CommandLineStrings;
+ std::string WorkingDirectory;
+ double TimeOut;
+};
+
+
+//----------------------------------------------------------------------
+
+//----------------------------------------------------------------------
+cmCTestCoverageHandler::cmCTestCoverageHandler()
+{
+}
+
+//----------------------------------------------------------------------
+void cmCTestCoverageHandler::Initialize()
+{
+ this->Superclass::Initialize();
+ this->CustomCoverageExclude.clear();
+ this->SourceLabels.clear();
+ this->LabelIdMap.clear();
+ this->Labels.clear();
+ this->LabelFilter.clear();
+}
+
+//----------------------------------------------------------------------------
+void cmCTestCoverageHandler::CleanCoverageLogFiles(std::ostream& log)
+{
+ std::string logGlob = this->CTest->GetCTestConfiguration("BuildDirectory");
+ logGlob += "/Testing/";
+ logGlob += this->CTest->GetCurrentTag();
+ logGlob += "/CoverageLog*";
+ cmsys::Glob gl;
+ gl.FindFiles(logGlob.c_str());
+ std::vector<std::string> const& files = gl.GetFiles();
+ 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());
+ }
+}
+
+//----------------------------------------------------------------------
+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);
+ 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)
+{
+ 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);
+ ostr.Close();
+}
+
+//----------------------------------------------------------------------
+bool cmCTestCoverageHandler::ShouldIDoCoverage(const char* file,
+ const char* srcDir,
+ const char* binDir)
+{
+ 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;);
+ 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());
+ // Always check parent directory of the file.
+ std::string fileDir = cmSystemTools::GetFilenamePath(fFile.c_str());
+ std::string checkDir;
+
+ // We also need to check the binary/source directory pair.
+ if ( sourceSubDir && buildSubDir )
+ {
+ if ( fSrcDir.size() > fBinDir.size() )
+ {
+ checkDir = fSrcDir;
+ }
+ else
+ {
+ checkDir = fBinDir;
+ }
+ }
+ else if ( sourceSubDir )
+ {
+ checkDir = fSrcDir;
+ }
+ 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);
+ 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
+ {
+ relPath = fFile;
+ }
+ if ( checkDir == fSrcDir )
+ {
+ checkDir = fBinDir;
+ }
+ else
+ {
+ checkDir = fSrcDir;
+ }
+ fFile = checkDir + "/" + relPath;
+ fFile = cmSystemTools::GetFilenamePath(fFile.c_str());
+
+ 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);
+ 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...
+int cmCTestCoverageHandler::ProcessHandler()
+{
+ this->CTest->ClearSubmitFiles(cmCTest::PartCoverage);
+ int error = 0;
+ // do we have time for this
+ 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");
+
+ this->LoadLabels();
+
+ cmGeneratedFileStream ofs;
+ double elapsed_time_start = cmSystemTools::GetTime();
+ if ( !this->StartLogFile("Coverage", ofs) )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Cannot create LastCoverage.log file" << std::endl);
+ }
+
+ ofs << "Performing coverage: " << elapsed_time_start << std::endl;
+ this->CleanCoverageLogFiles(ofs);
+
+ cmSystemTools::ConvertToUnixSlashes(sourceDir);
+ cmSystemTools::ConvertToUnixSlashes(binaryDir);
+
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, "Performing coverage" << std::endl);
+
+ cmCTestCoverageHandlerContainer cont;
+ cont.Error = error;
+ cont.SourceDir = sourceDir;
+ cont.BinaryDir = binaryDir;
+ cont.OFS = &ofs;
+
+ // setup the regex exclude stuff
+ this->CustomCoverageExcludeRegex.clear();
+ std::vector<cmStdString>::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))
+ {
+ return cont.Error;
+ }
+ int file_count = 0;
+ file_count += this->HandleGCovCoverage(&cont);
+ error = cont.Error;
+ if ( file_count < 0 )
+ {
+ return error;
+ }
+ file_count += this->HandleTracePyCoverage(&cont);
+ error = cont.Error;
+ if ( file_count < 0 )
+ {
+ return error;
+ }
+ file_count += this->HandlePHPCoverage(&cont);
+ error = cont.Error;
+ if ( file_count < 0 )
+ {
+ return error;
+ }
+ file_count += this->HandleMumpsCoverage(&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,
+ " Cannot find any coverage files. Ignoring Coverage request."
+ << std::endl);
+ return error;
+ }
+ cmGeneratedFileStream covSumFile;
+ cmGeneratedFileStream covLogFile;
+
+ if(!this->StartResultingXML(cmCTest::PartCoverage, "Coverage", covSumFile))
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Cannot open coverage summary file." << std::endl);
+ return -1;
+ }
+
+ this->CTest->StartXML(covSumFile, 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;
+ int logFileCount = 0;
+ if ( !this->StartCoverageLogFile(covLogFile, logFileCount) )
+ {
+ return -1;
+ }
+ 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::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, " ");
+ }
+
+ 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);
+ continue;
+ }
+
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "Process file: " << fullFileName << std::endl);
+
+ if ( !cmSystemTools::FileExists(fullFileName.c_str()) )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot find file: "
+ << fullFileName.c_str() << std::endl);
+ continue;
+ }
+
+ if ( ++cnt % 100 == 0 )
+ {
+ this->EndCoverageLogFile(covLogFile, logFileCount);
+ logFileCount ++;
+ if ( !this->StartCoverageLogFile(covLogFile, logFileCount) )
+ {
+ return -1;
+ }
+ }
+
+ const std::string fileName
+ = cmSystemTools::GetFilenameName(fullFileName.c_str());
+ 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();
+ errorsWhileAccumulating.push_back(ostr.str());
+ 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;
+ errorsWhileAccumulating.push_back(ostr.str());
+ 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());
+ }
+ float cper = 0;
+ float cmet = 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)));
+ }
+ 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)
+ {
+ 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;
+
+ std::ifstream ifs(fullPath.c_str());
+ if (!ifs)
+ {
+ cmOStringStream ostr;
+ ostr << "Cannot open source file: " << fullPath.c_str();
+ errorsWhileAccumulating.push_back(ostr.str());
+ error ++;
+ 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;
+
+ 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;
+ }
+
+ this->EndCoverageLogFile(covLogFile, logFileCount);
+
+ if ( errorsWhileAccumulating.size() > 0 )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, std::endl);
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "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);
+ }
+ }
+
+ 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 )
+ {
+ 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);
+
+ 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;
+
+
+ if ( error )
+ {
+ return -1;
+ }
+ return 0;
+}
+
+//----------------------------------------------------------------------
+void cmCTestCoverageHandler::PopulateCustomVectors(cmMakefile *mf)
+{
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ " Add coverage exclude regular expressions." << std::endl);
+ this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_COVERAGE_EXCLUDE",
+ 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);
+ }
+}
+
+//----------------------------------------------------------------------
+// Fix for issue #4971 where the case of the drive letter component of
+// the filenames might be different when analyzing gcov output.
+//
+// Compare file names: fnc(fn1) == fnc(fn2) // fnc == file name compare
+//
+#ifdef _WIN32
+#define fnc(s) cmSystemTools::LowerCase(s)
+#else
+#define fnc(s) s
+#endif
+
+//----------------------------------------------------------------------
+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;
+ }
+
+ return false;
+}
+
+//----------------------------------------------------------------------
+int cmCTestCoverageHandler::HandlePHPCoverage(
+ cmCTestCoverageHandlerContainer* cont)
+{
+ cmParsePHPCoverage cov(*cont, this->CTest);
+ std::string coverageDir = this->CTest->GetBinaryDir() + "/xdebugCoverage";
+ if(cmSystemTools::FileIsDirectory(coverageDir.c_str()))
+ {
+ cov.ReadPHPCoverageDirectory(coverageDir.c_str());
+ }
+ 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);
+ 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);
+ }
+ 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);
+ ccov.ReadCoverageFile(coverageFile.c_str());
+ }
+ else
+ {
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ " Cannot find Cache coverage file: " << coverageFile
+ << std::endl);
+ }
+ return static_cast<int>(cont->TotalCoverage.size());
+}
+
+struct cmCTestCoverageHandlerLocale
+{
+ cmCTestCoverageHandlerLocale()
+ {
+ if(const char* l = cmSystemTools::GetEnv("LC_ALL"))
+ {
+ lc_all = l;
+ }
+ if(lc_all != "C")
+ {
+ cmSystemTools::PutEnv("LC_ALL=C");
+ }
+ }
+ ~cmCTestCoverageHandlerLocale()
+ {
+ if(!lc_all.empty())
+ {
+ cmSystemTools::PutEnv(("LC_ALL=" + lc_all).c_str());
+ }
+ else
+ {
+ cmSystemTools::UnsetEnv("LC_ALL");
+ }
+ }
+ std::string lc_all;
+};
+
+//----------------------------------------------------------------------
+int cmCTestCoverageHandler::HandleGCovCoverage(
+ cmCTestCoverageHandlerContainer* cont)
+{
+ std::string gcovCommand
+ = this->CTest->GetCTestConfiguration("CoverageCommand");
+ std::string gcovExtraFlags
+ = this->CTest->GetCTestConfiguration("CoverageExtraFlags");
+
+ // Style 1
+ 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 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 `(.*)'$";
+ cmsys::RegularExpression st2re1(st2gcovOutputRex1.c_str());
+ cmsys::RegularExpression st2re2(st2gcovOutputRex2.c_str());
+ cmsys::RegularExpression st2re3(st2gcovOutputRex3.c_str());
+ cmsys::RegularExpression st2re4(st2gcovOutputRex4.c_str());
+ cmsys::RegularExpression st2re5(st2gcovOutputRex5.c_str());
+ cmsys::RegularExpression st2re6(st2gcovOutputRex6.c_str());
+
+ std::vector<std::string> files;
+ 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);
+ // 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());
+
+ 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, " ");
+ int file_count = 0;
+
+ // make sure output from gcov is in English!
+ cmCTestCoverageHandlerLocale locale_C;
+ static_cast<void>(locale_C);
+
+ // files is a list of *.da and *.gcda files with coverage data in them.
+ // 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);
+
+ // 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 + "\"";
+
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, command.c_str()
+ << std::endl);
+
+ 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 )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Problem running coverage on file: " << it->c_str() << 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->c_str() << std::endl);
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Command produced error: " << cont->Error << std::endl);
+ }
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "--------------------------------------------------------------"
+ << std::endl
+ << output << std::endl
+ << "--------------------------------------------------------------"
+ << std::endl);
+
+ std::vector<cmStdString> lines;
+ std::vector<cmStdString>::iterator line;
+
+ cmSystemTools::Split(output.c_str(), lines);
+
+ for ( line = lines.begin(); line != lines.end(); ++line)
+ {
+ std::string sourceFile;
+ std::string gcovFile;
+
+ cmCTestLog(this->CTest, DEBUG, "Line: [" << line->c_str() << "]"
+ << std::endl);
+
+ if ( line->size() == 0 )
+ {
+ // Ignore empty line; probably style 2
+ }
+ else if ( st1re1.find(line->c_str()) )
+ {
+ if ( gcovStyle == 0 )
+ {
+ gcovStyle = 1;
+ }
+ if ( gcovStyle != 1 )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e1"
+ << std::endl);
+ cont->Error ++;
+ break;
+ }
+
+ actualSourceFile = "";
+ sourceFile = st1re1.match(2);
+ }
+ else if ( st1re2.find(line->c_str() ) )
+ {
+ if ( gcovStyle == 0 )
+ {
+ gcovStyle = 1;
+ }
+ if ( gcovStyle != 1 )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e2"
+ << std::endl);
+ cont->Error ++;
+ break;
+ }
+
+ gcovFile = st1re2.match(1);
+ }
+ else if ( st2re1.find(line->c_str() ) )
+ {
+ if ( gcovStyle == 0 )
+ {
+ gcovStyle = 2;
+ }
+ if ( gcovStyle != 2 )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e3"
+ << std::endl);
+ cont->Error ++;
+ break;
+ }
+
+ actualSourceFile = "";
+ sourceFile = st2re1.match(1);
+ }
+ else if ( st2re2.find(line->c_str() ) )
+ {
+ if ( gcovStyle == 0 )
+ {
+ gcovStyle = 2;
+ }
+ if ( gcovStyle != 2 )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e4"
+ << std::endl);
+ cont->Error ++;
+ break;
+ }
+ }
+ else if ( st2re3.find(line->c_str() ) )
+ {
+ if ( gcovStyle == 0 )
+ {
+ gcovStyle = 2;
+ }
+ if ( gcovStyle != 2 )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e5"
+ << std::endl);
+ cont->Error ++;
+ break;
+ }
+
+ gcovFile = st2re3.match(2);
+ }
+ else if ( st2re4.find(line->c_str() ) )
+ {
+ if ( gcovStyle == 0 )
+ {
+ gcovStyle = 2;
+ }
+ if ( gcovStyle != 2 )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e6"
+ << 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 )
+ {
+ gcovStyle = 2;
+ }
+ if ( gcovStyle != 2 )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e7"
+ << 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 )
+ {
+ gcovStyle = 2;
+ }
+ if ( gcovStyle != 2 )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e8"
+ << std::endl);
+ cont->Error ++;
+ break;
+ }
+
+ cmCTestLog(this->CTest, WARNING, "Warning: File: " << st2re6.match(1)
+ << " is newer than " << st2re6.match(2) << std::endl);
+ }
+ else
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Unknown gcov output line: [" << line->c_str() << "]" << 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
+ {
+ long cnt = -1;
+ std::string nl;
+ while ( cmSystemTools::GetLineFromStream(ifile, nl) )
+ {
+ cnt ++;
+
+ //TODO: Handle gcov 3.0 non-coverage lines
+
+ // Skip empty lines
+ if ( !nl.size() )
+ {
+ continue;
+ }
+
+ // Skip unused lines
+ if ( nl.size() < 12 )
+ {
+ continue;
+ }
+
+ // Read the coverage count from the beginning of the gcov output
+ // line
+ std::string prefix = nl.substr(0, 12);
+ int cov = atoi(prefix.c_str());
+
+ // Read the line number starting at the 10th character of the gcov
+ // 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) )
+ {
+ 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 )
+ {
+ vec[lineIdx] = 0;
+ }
+ }
+
+ vec[lineIdx] += cov;
+ }
+ }
+ }
+
+ actualSourceFile = "";
+ }
+
+
+ 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 ( 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);
+ *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;
+
+ 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, " ");
+ }
+ }
+
+ cmSystemTools::ChangeDirectory(currentDirectory.c_str());
+ 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)
+ {
+ // Skip targets containing no interesting labels.
+ 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);
+ std::string daGlob = lmi->first;
+ daGlob += "/*.da";
+ gl.FindFiles(daGlob);
+ files.insert(files.end(), gl.GetFiles().begin(), gl.GetFiles().end());
+ daGlob = lmi->first;
+ daGlob += "/*.gcda";
+ gl.FindFiles(daGlob);
+ files.insert(files.end(), gl.GetFiles().begin(), gl.GetFiles().end());
+ }
+}
+
+//----------------------------------------------------------------------
+int cmCTestCoverageHandler::HandleTracePyCoverage(
+ cmCTestCoverageHandlerContainer* cont)
+{
+ cmsys::Glob gl;
+ gl.RecurseOn();
+ gl.RecurseThroughSymlinksOff();
+ std::string daGlob = cont->BinaryDir + "/*.cover";
+ 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);
+ // 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 )
+ {
+ std::string fileName = this->FindFile(cont, *fileIt);
+ if ( fileName.empty() )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Cannot find source Python file corresponding to: "
+ << fileIt->c_str() << 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
+ {
+ long cnt = -1;
+ std::string nl;
+ while ( cmSystemTools::GetLineFromStream(ifile, nl) )
+ {
+ cnt ++;
+
+ // Skip empty lines
+ if ( !nl.size() )
+ {
+ continue;
+ }
+
+ // Skip unused lines
+ 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] != ':' )
+ {
+ // This is a hack. We should really do something more elaborate
+ prefix = nl.substr(0, 7);
+ if ( prefix[6] != ' ' && prefix[6] != ':' )
+ {
+ prefix = nl.substr(0, 8);
+ if ( prefix[7] != ' ' && prefix[7] != ':' )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Currently the limit is maximum coverage of 999999"
+ << std::endl);
+ }
+ }
+ }
+ int cov = atoi(prefix.c_str());
+ 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);
+ // 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) )
+ {
+ 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 )
+ {
+ (*vec)[lineIdx] = 0;
+ }
+ }
+ (*vec)[lineIdx] += cov;
+ }
+ }
+ }
+ ++ file_count;
+ }
+ cmSystemTools::ChangeDirectory(currentDirectory.c_str());
+ return file_count;
+}
+
+//----------------------------------------------------------------------
+std::string cmCTestCoverageHandler::FindFile(
+ cmCTestCoverageHandlerContainer* cont,
+ std::string 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()) )
+ {
+ return fullName;
+ }
+ fullName = cont->BinaryDir + "/" + fileNameNoE + ".py";
+ 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};
+}
+
+//----------------------------------------------------------------------
+int cmCTestCoverageHandler::RunBullseyeCoverageBranch(
+ cmCTestCoverageHandlerContainer* cont,
+ std::set<cmStdString>& coveredFileNames,
+ std::vector<std::string>& files,
+ std::vector<std::string>& filesFullPath)
+{
+ 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;
+ int logFileCount = 0;
+ if ( !this->StartCoverageLogFile(covLogFile, logFileCount) )
+ {
+ return -1;
+ }
+ // 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);
+
+ if(!this->RunBullseyeCommand(cont, "covbr", 0, 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);
+ // open the output file
+ std::ifstream fin(outputFile.c_str());
+ if(!fin)
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Cannot open coverage file: " <<
+ outputFile.c_str() << std::endl);
+ return 0;
+ }
+ std::map<cmStdString, cmStdString> fileMap;
+ std::vector<std::string>::iterator fp = filesFullPath.begin();
+ 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
+ // 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))
+ {
+ bool startFile = false;
+ 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 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;
+ }
+ // 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);
+ this->EndCoverageLogFile(covLogFile, logFileCount);
+ logFileCount ++;
+ if ( !this->StartCoverageLogFile(covLogFile, logFileCount) )
+ {
+ return -1;
+ }
+ count++; // move on one
+ }
+ std::map<cmStdString, cmStdString>::iterator
+ i = fileMap.find(file);
+ // if the file should be covered write out the header for that file
+ 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);
+ // 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;
+ // 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++;
+ }
+ 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;
+ 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;
+ }
+ this->EndCoverageLogFile(covLogFile, logFileCount);
+ return 1;
+}
+
+//----------------------------------------------------------------------
+int cmCTestCoverageHandler::RunBullseyeCommand(
+ cmCTestCoverageHandlerContainer* cont,
+ const char* cmd,
+ const char* arg,
+ std::string& outputFile)
+{
+ std::string program = cmSystemTools::FindProgram(cmd);
+ if(program.size() == 0)
+ {
+ 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");
+ }
+ // create a process object and start it
+ cmCTestRunProcess runCoverageSrc;
+ runCoverageSrc.SetCommand(program.c_str());
+ runCoverageSrc.AddArgument(arg);
+ std::string stdoutFile = cont->BinaryDir + "/Testing/Temporary/";
+ stdoutFile += this->GetCTestInstance()->GetCurrentTag();
+ stdoutFile += "-";
+ stdoutFile += cmd;
+ std::string stderrFile = stdoutFile;
+ stdoutFile += ".stdout";
+ 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());
+ 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))
+ {
+ 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);
+ return 0;
+ }
+ this->CTest->StartXML(covSumFile, 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;
+ std::string stdline;
+ std::string errline;
+ // expected output:
+ // first line is:
+ // "Source","Function Coverage","out of","%","C/D Coverage","out of","%"
+ // after that data follows in that format
+ std::string sourceFile;
+ int functionsCalled = 0;
+ int totalFunctions = 0;
+ int percentFunction = 0;
+ int branchCovered = 0;
+ int totalBranches = 0;
+ int percentBranch = 0;
+ double total_tested = 0;
+ double total_untested = 0;
+ double total_functions = 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)
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Cannot open coverage summary file: " <<
+ outputFile.c_str() << std::endl);
+ return 0;
+ }
+ std::set<cmStdString> coveredFileNames;
+ while(cmSystemTools::GetLineFromStream(fin, stdline))
+ {
+ // if we have a line of output from stdout
+ if(stdline.size())
+ {
+ // parse the comma separated output
+ this->ParseBullsEyeCovsrcLine(stdline,
+ sourceFile,
+ functionsCalled,
+ totalFunctions,
+ percentFunction,
+ branchCovered,
+ totalBranches,
+ percentBranch);
+ // The first line is the header
+ if(sourceFile == "Source" || sourceFile == "Total")
+ {
+ continue;
+ }
+ std::string file = sourceFile;
+ coveredFileNames.insert(file);
+ 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);
+ continue;
+ }
+
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "Doing coverage for: "
+ << file.c_str()
+ << std::endl);
+
+ coveredFiles.push_back(sourceFile);
+ coveredFilesFullPath.push_back(file);
+
+ number_files++;
+ total_functions += totalFunctions;
+ total_tested += functionsCalled;
+ total_untested += (totalFunctions - functionsCalled);
+
+ std::string fileName = cmSystemTools::GetFilenameName(file.c_str());
+ std::string shortFileName =
+ this->CTest->GetShortPathToFile(file.c_str());
+
+ float cper = static_cast<float>(percentBranch + percentFunction);
+ if(totalBranches > 0)
+ {
+ cper /= 2.0f;
+ }
+ percent_coverage += cper;
+ float cmet = static_cast<float>(percentFunction + percentBranch);
+ if(totalBranches > 0)
+ {
+ cmet /= 2.0f;
+ }
+ cmet /= 100.0f;
+ tmpLog << stdline.c_str() << "\n";
+ tmpLog << fileName << "\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;
+ }
+ }
+ 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);
+
+ // Now create the coverage information for each file
+ 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");
+ return 0;
+ }
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ " run covsrc with COVFILE=["
+ << covfile
+ << "]" << std::endl);
+ 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);
+ return 1;
+}
+
+bool cmCTestCoverageHandler::GetNextInt(std::string const& inputLine,
+ std::string::size_type& pos,
+ int& value)
+{
+ std::string::size_type start = pos;
+ pos = inputLine.find(',', start);
+ value = atoi(inputLine.substr(start, pos).c_str());
+ if(pos == inputLine.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)
+{
+ // 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");
+ return false;
+ }
+ // the source file has "" around it so extract out the file name
+ sourceFile = inputLine.substr(1,pos-2);
+ pos++;
+ if(!this->GetNextInt(inputLine, pos, functionsCalled))
+ {
+ return false;
+ }
+ if(!this->GetNextInt(inputLine, pos, totalFunctions))
+ {
+ return false;
+ }
+ if(!this->GetNextInt(inputLine, pos, percentFunction))
+ {
+ return false;
+ }
+ if(!this->GetNextInt(inputLine, pos, branchCovered))
+ {
+ return false;
+ }
+ if(!this->GetNextInt(inputLine, pos, totalBranches))
+ {
+ return false;
+ }
+ if(!this->GetNextInt(inputLine, pos, percentBranch))
+ {
+ return false;
+ }
+ // should be at the end now
+ if(pos != inputLine.npos)
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Error parsing input : "
+ << inputLine.c_str() << " 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())
+ {
+ 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());
+ std::string 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)
+ {
+ return;
+ }
+
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ " loading labels from [" << fname << "]\n");
+ bool inTarget = true;
+ std::string source;
+ std::string line;
+ std::vector<int> targetLabels;
+ while(cmSystemTools::GetLineFromStream(fin, line))
+ {
+ if(line.empty() || line[0] == '#')
+ {
+ // Ignore blank and comment lines.
+ continue;
+ }
+ else 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)
+ {
+ targetLabels.push_back(id);
+ }
+ else
+ {
+ this->SourceLabels[source].insert(id);
+ }
+ }
+ else
+ {
+ // Non-indented lines specify a source file name. The first one
+ // is the end of the target-wide labels.
+ inTarget = false;
+
+ source = this->CTest->GetShortPathToFile(line.c_str());
+
+ // 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);
+ }
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+void cmCTestCoverageHandler::WriteXMLLabels(std::ofstream& os,
+ 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";
+ }
+}
+
+//----------------------------------------------------------------------------
+void
+cmCTestCoverageHandler::SetLabelFilter(std::set<cmStdString> const& labels)
+{
+ this->LabelFilter.clear();
+ for(std::set<cmStdString>::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())
+ {
+ 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));
+ 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())
+ {
+ 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())
+ {
+ 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)
+ {
+ 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()));
+ }
+ }
+ }
+
+ 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());
+ extraMatches.erase(shortPath);
+ }
+ }
+ return extraMatches;
+}
diff --git a/Source/CTest/cmCTestCoverageHandler.h b/Source/CTest/cmCTestCoverageHandler.h
new file mode 100644
index 000000000..92b0b2285
--- /dev/null
+++ b/Source/CTest/cmCTestCoverageHandler.h
@@ -0,0 +1,139 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#ifndef cmCTestCoverageHandler_h
+#define cmCTestCoverageHandler_h
+
+
+#include "cmCTestGenericHandler.h"
+#include "cmListFileCache.h"
+
+#include <cmsys/RegularExpression.hxx>
+
+class cmGeneratedFileStream;
+class cmCTestCoverageHandlerContainer
+{
+public:
+ int Error;
+ std::string SourceDir;
+ std::string BinaryDir;
+ typedef std::vector<int> SingleFileCoverageVector;
+ typedef std::map<std::string, SingleFileCoverageVector> TotalCoverageMap;
+ TotalCoverageMap TotalCoverage;
+ std::ostream* OFS;
+};
+/** \class cmCTestCoverageHandler
+ * \brief A class that handles coverage computaiton for ctest
+ *
+ */
+class cmCTestCoverageHandler : public cmCTestGenericHandler
+{
+public:
+ cmTypeMacro(cmCTestCoverageHandler, cmCTestGenericHandler);
+
+ /*
+ * The main entry point for this class
+ */
+ int ProcessHandler();
+
+ cmCTestCoverageHandler();
+
+ virtual void Initialize();
+
+ /**
+ * This method is called when reading CTest custom file
+ */
+ void PopulateCustomVectors(cmMakefile *mf);
+
+ /** Report coverage only for sources with these labels. */
+ void SetLabelFilter(std::set<cmStdString> const& labels);
+
+private:
+ bool ShouldIDoCoverage(const char* file, const char* srcDir,
+ const char* binDir);
+ void CleanCoverageLogFiles(std::ostream& log);
+ bool StartCoverageLogFile(cmGeneratedFileStream& ostr, int logFileCount);
+ void EndCoverageLogFile(cmGeneratedFileStream& ostr, int logFileCount);
+
+ //! Handle coverage using GCC's GCov
+ int HandleGCovCoverage(cmCTestCoverageHandlerContainer* cont);
+ void FindGCovFiles(std::vector<std::string>& files);
+
+ //! Handle coverage using xdebug php coverage
+ int HandlePHPCoverage(cmCTestCoverageHandlerContainer* cont);
+ //! Handle coverage for mumps
+ int HandleMumpsCoverage(cmCTestCoverageHandlerContainer* cont);
+
+ //! Handle coverage using Bullseye
+ int HandleBullseyeCoverage(cmCTestCoverageHandlerContainer* cont);
+ int RunBullseyeSourceSummary(cmCTestCoverageHandlerContainer* cont);
+ int RunBullseyeCoverageBranch(cmCTestCoverageHandlerContainer* cont,
+ std::set<cmStdString>& 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& value);
+ //! Handle Python coverage using Python's Trace.py
+ int HandleTracePyCoverage(cmCTestCoverageHandlerContainer* cont);
+
+ // 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::set<std::string> FindUncoveredFiles(
+ cmCTestCoverageHandlerContainer* cont);
+ std::vector<cmStdString> CustomCoverageExclude;
+ std::vector<cmsys::RegularExpression> CustomCoverageExcludeRegex;
+ std::vector<cmStdString> ExtraCoverageGlobs;
+
+
+ // Map from source file to label ids.
+ class LabelSet: public std::set<int> {};
+ typedef std::map<cmStdString, LabelSet> LabelMapType;
+ LabelMapType SourceLabels;
+ LabelMapType TargetDirs;
+
+ // Map from label name to label id.
+ typedef std::map<cmStdString, int> LabelIdMapType;
+ LabelIdMapType LabelIdMap;
+ std::vector<std::string> Labels;
+ int GetLabelId(std::string const& label);
+
+ // Label reading and writing methods.
+ void LoadLabels();
+ void LoadLabels(const char* dir);
+ void WriteXMLLabels(std::ofstream& os, std::string const& source);
+
+ // Label-based filtering.
+ std::set<int> LabelFilter;
+ bool IntersectsFilter(LabelSet const& labels);
+ bool IsFilteredOut(std::string const& source);
+};
+
+#endif
diff --git a/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.cxx b/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.cxx
new file mode 100644
index 000000000..56312c0fb
--- /dev/null
+++ b/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.cxx
@@ -0,0 +1,36 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestEmptyBinaryDirectoryCommand.h"
+
+#include "cmCTestScriptHandler.h"
+
+bool cmCTestEmptyBinaryDirectoryCommand
+::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
+{
+ 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());
+ return false;
+ }
+
+ return true;
+}
+
+
diff --git a/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.h b/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.h
new file mode 100644
index 000000000..a763fe9e9
--- /dev/null
+++ b/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.h
@@ -0,0 +1,78 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestEmptyBinaryDirectoryCommand_h
+#define cmCTestEmptyBinaryDirectoryCommand_h
+
+#include "cmCTestCommand.h"
+
+/** \class cmCTestEmptyBinaryDirectory
+ * \brief Run a ctest script
+ *
+ * cmLibrarysCommand defines a list of executable (i.e., test)
+ * programs to create.
+ */
+class cmCTestEmptyBinaryDirectoryCommand : public cmCTestCommand
+{
+public:
+
+ cmCTestEmptyBinaryDirectoryCommand() {}
+
+ /**
+ * This is a virtual constructor for the command.
+ */
+ virtual cmCommand* Clone()
+ {
+ 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);
+
+};
+
+
+#endif
diff --git a/Source/CTest/cmCTestGIT.cxx b/Source/CTest/cmCTestGIT.cxx
new file mode 100644
index 000000000..2c1a0afd9
--- /dev/null
+++ b/Source/CTest/cmCTestGIT.cxx
@@ -0,0 +1,652 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestGIT.h"
+
+#include "cmCTest.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;
+}
+
+//----------------------------------------------------------------------------
+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
+{
+public:
+ OneLineParser(cmCTestGIT* git, const char* prefix,
+ std::string& l): Line1(l)
+ {
+ this->SetLog(&git->Log, prefix);
+ }
+private:
+ std::string& Line1;
+ virtual bool ProcessLine()
+ {
+ // 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};
+ std::string rev;
+ OneLineParser out(this, "rl-out> ", rev);
+ OutputLogger err(this->Log, "rl-err> ");
+ this->RunChild(git_rev_list, &out, &err);
+ return rev;
+}
+
+//----------------------------------------------------------------------------
+void cmCTestGIT::NoteOldRevision()
+{
+ this->OldRevision = this->GetWorkingRevision();
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: "
+ << this->OldRevision << "\n");
+ this->PriorRev.Rev = this->OldRevision;
+}
+
+//----------------------------------------------------------------------------
+void cmCTestGIT::NoteNewRevision()
+{
+ this->NewRevision = this->GetWorkingRevision();
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: "
+ << this->NewRevision << "\n");
+}
+
+//----------------------------------------------------------------------------
+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};
+ 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))
+ {
+ git_dir = git_dir_line;
+ }
+ 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] == '.')
+ {
+ git_dir = this->SourceDirectory + "/" + git_dir;
+ }
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ 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};
+ OneLineParser cygpath_out(this, "cygpath-out> ", git_dir_line);
+ OutputLogger cygpath_err(this->Log, "cygpath-err> ");
+ if(this->RunChild(cygpath, &cygpath_out, &cygpath_err))
+ {
+ 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};
+ 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())
+ {
+ top_dir += "/";
+ top_dir += cdup;
+ top_dir = cmSystemTools::CollapseFullPath(top_dir.c_str());
+ }
+ return top_dir;
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestGIT::UpdateByFetchAndReset()
+{
+ const char* git = this->CommandLineTool.c_str();
+
+ // Use "git fetch" to get remote commits.
+ std::vector<char const*> git_fetch;
+ git_fetch.push_back(git);
+ git_fetch.push_back("fetch");
+
+ // Add user-specified update options.
+ std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
+ 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)
+ {
+ git_fetch.push_back(ai->c_str());
+ }
+
+ // Sentinel argument.
+ git_fetch.push_back(0);
+
+ // 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))
+ {
+ 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 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);
+ }
+ }
+ }
+ 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};
+ 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)
+ {
+ git_custom.push_back(i->c_str());
+ }
+ git_custom.push_back(0);
+
+ 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())
+ {
+ return this->UpdateByCustom(custom);
+ }
+ return this->UpdateByFetchAndReset();
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestGIT::UpdateImpl()
+{
+ if(!this->UpdateInternal())
+ {
+ return false;
+ }
+
+ std::string top_dir = this->FindTopDir();
+ const char* git = this->CommandLineTool.c_str();
+ const char* recursive = "--recursive";
+
+ // 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";
+ }
+ }
+
+ char const* git_submodule[] = {git, "submodule", "update", recursive, 0};
+ OutputLogger submodule_out(this->Log, "submodule-out> ");
+ OutputLogger submodule_err(this->Log, "submodule-err> ");
+ return this->RunChild(git_submodule, &submodule_out, &submodule_err,
+ top_dir.c_str());
+}
+
+//----------------------------------------------------------------------------
+unsigned int cmCTestGIT::GetGitVersion()
+{
+ if(!this->CurrentGitVersion)
+ {
+ const char* git = this->CommandLineTool.c_str();
+ char const* git_version[] = {git, "--version", 0};
+ 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)
+ {
+ 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
+ src-path\0
+ [dst-path\0]
+
+ The format is repeated for every file changed. The [dst-path\0]
+ line appears only for lines with status 'C' or 'R'. See 'git help
+ diff-tree' for details.
+*/
+class cmCTestGIT::DiffParser: public cmCTestVC::LineParser
+{
+public:
+ 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 };
+ DiffFieldType DiffField;
+ Change CurChange;
+
+ void DiffReset()
+ {
+ this->DiffField = DiffFieldNone;
+ this->Changes.clear();
+ }
+
+ virtual bool ProcessLine()
+ {
+ if(this->Line[0] == ':')
+ {
+ this->DiffField = DiffFieldChange;
+ this->CurChange = Change();
+ }
+ if(this->DiffField == DiffFieldChange)
+ {
+ // :src-mode dst-mode src-sha1 dst-sha1 status
+ 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* dst_mode_first = this->ConsumeSpace(src_mode_last);
+ 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* 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)
+ {
+ this->CurChange.Action = *status_first;
+ this->DiffField = DiffFieldSrc;
+ }
+ else
+ {
+ this->DiffField = DiffFieldNone;
+ }
+ }
+ else if(this->DiffField == DiffFieldSrc)
+ {
+ // src-path
+ if(this->CurChange.Action == 'C')
+ {
+ // Convert copy to addition of destination.
+ this->CurChange.Action = 'A';
+ this->DiffField = DiffFieldDst;
+ }
+ 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;
+ this->Changes.push_back(this->CurChange);
+
+ this->CurChange = Change('A');
+ this->DiffField = DiffFieldDst;
+ }
+ else
+ {
+ this->CurChange.Path = this->Line;
+ this->Changes.push_back(this->CurChange);
+ this->DiffField = this->DiffFieldNone;
+ }
+ }
+ else if(this->DiffField == DiffFieldDst)
+ {
+ // dst-path
+ this->CurChange.Path = this->Line;
+ this->Changes.push_back(this->CurChange);
+ this->DiffField = this->DiffFieldNone;
+ }
+ return true;
+ }
+
+ const char* ConsumeSpace(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
+ tree ...\n
+ parent ...\n
+ author ...\n
+ committer ...\n
+ \n
+ Log message indented by (4) spaces\n
+ (even blank lines have the spaces)\n
+ [[
+ \n
+ [Diff format]
+ OR
+ \0
+ ]]
+
+ The header may have more fields. See 'git help diff-tree'.
+*/
+class cmCTestGIT::CommitParser: public cmCTestGIT::DiffParser
+{
+public:
+ 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 };
+ static char const SectionSep[SectionCount];
+ SectionType Section;
+ Revision Rev;
+
+ struct Person
+ {
+ std::string Name;
+ std::string EMail;
+ unsigned long Time;
+ long TimeZone;
+ 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; }
+
+ const char* name_first = 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);
+
+ 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')
+ {
+ // 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
+ }
+ }
+ return true;
+ }
+
+ void NextSection()
+ {
+ this->Section = SectionType((this->Section+1) % SectionCount);
+ this->Separator = SectionSep[this->Section];
+ 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)
+ {
+ Person 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)
+ {
+ Person 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)
+ {
+ this->Rev.Log += this->Line.substr(4);
+ }
+ 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);
+ std::string out = dt;
+
+ // Add the time-zone field "+zone" or "-zone".
+ char tz[32];
+ if(person.TimeZone >= 0)
+ {
+ sprintf(tz, " +%04ld", person.TimeZone);
+ }
+ else
+ {
+ sprintf(tz, " -%04ld", -person.TimeZone);
+ }
+ out += tz;
+ return out;
+ }
+};
+
+char const cmCTestGIT::CommitParser::SectionSep[SectionCount] =
+{'\n', '\n', '\0'};
+
+//----------------------------------------------------------------------------
+void 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};
+ this->Log << this->ComputeCommandLine(git_rev_list) << " | "
+ << this->ComputeCommandLine(git_diff_tree) << "\n";
+
+ cmsysProcess* cp = cmsysProcess_New();
+ cmsysProcess_AddCommand(cp, git_rev_list);
+ cmsysProcess_AddCommand(cp, git_diff_tree);
+ cmsysProcess_SetWorkingDirectory(cp, this->SourceDirectory.c_str());
+
+ CommitParser out(this, "dt-out> ");
+ OutputLogger err(this->Log, "dt-err> ");
+ this->RunProcess(cp, &out, &err);
+
+ // Send one extra zero-byte to terminate the last record.
+ out.Process("", 1);
+
+ cmsysProcess_Delete(cp);
+}
+
+//----------------------------------------------------------------------------
+void 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};
+ OutputLogger ui_out(this->Log, "ui-out> ");
+ OutputLogger ui_err(this->Log, "ui-err> ");
+ this->RunChild(git_update_index, &ui_out, &ui_err);
+
+ // Use 'git diff-index' to get modified files.
+ const char* git_diff_index[] = {git, "diff-index", "-z", "HEAD", 0};
+ DiffParser out(this, "di-out> ");
+ OutputLogger err(this->Log, "di-err> ");
+ this->RunChild(git_diff_index, &out, &err);
+
+ for(std::vector<Change>::const_iterator ci = out.Changes.begin();
+ ci != out.Changes.end(); ++ci)
+ {
+ this->DoModification(PathModified, ci->Path);
+ }
+}
diff --git a/Source/CTest/cmCTestGIT.h b/Source/CTest/cmCTestGIT.h
new file mode 100644
index 000000000..f4fae8f9d
--- /dev/null
+++ b/Source/CTest/cmCTestGIT.h
@@ -0,0 +1,57 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestGIT_h
+#define cmCTestGIT_h
+
+#include "cmCTestGlobalVC.h"
+
+/** \class cmCTestGIT
+ * \brief Interaction with git command-line tool
+ *
+ */
+class cmCTestGIT: public cmCTestGlobalVC
+{
+public:
+ /** Construct with a CTest instance and update log stream. */
+ cmCTestGIT(cmCTest* ctest, std::ostream& log);
+
+ virtual ~cmCTestGIT();
+
+private:
+ unsigned int CurrentGitVersion;
+ unsigned int GetGitVersion();
+ std::string GetWorkingRevision();
+ virtual void NoteOldRevision();
+ virtual void NoteNewRevision();
+ virtual bool UpdateImpl();
+
+ std::string FindGitDir();
+ std::string FindTopDir();
+
+ bool UpdateByFetchAndReset();
+ bool UpdateByCustom(std::string const& custom);
+ bool UpdateInternal();
+
+ void LoadRevisions();
+ void LoadModifications();
+
+public: // needed by older Sun compilers
+ // Parsing helper classes.
+ class OneLineParser;
+ class DiffParser;
+ class CommitParser;
+ friend class OneLineParser;
+ friend class DiffParser;
+ friend class CommitParser;
+};
+
+#endif
diff --git a/Source/CTest/cmCTestGenericHandler.cxx b/Source/CTest/cmCTestGenericHandler.cxx
new file mode 100644
index 000000000..fd75e45e8
--- /dev/null
+++ b/Source/CTest/cmCTestGenericHandler.cxx
@@ -0,0 +1,171 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#include "cmCTestGenericHandler.h"
+#include "cmSystemTools.h"
+
+#include "cmCTest.h"
+
+//----------------------------------------------------------------------
+cmCTestGenericHandler::cmCTestGenericHandler()
+{
+ this->HandlerVerbose = cmSystemTools::OUTPUT_NONE;
+ this->CTest = 0;
+ this->SubmitIndex = 0;
+ this->AppendXML = false;
+}
+
+//----------------------------------------------------------------------
+cmCTestGenericHandler::~cmCTestGenericHandler()
+{
+}
+
+//----------------------------------------------------------------------
+void cmCTestGenericHandler::SetOption(const char* op, const char* value)
+{
+ if ( !op )
+ {
+ return;
+ }
+ if ( !value )
+ {
+ cmCTestGenericHandler::t_StringToString::iterator remit
+ = this->Options.find(op);
+ if ( remit != this->Options.end() )
+ {
+ this->Options.erase(remit);
+ }
+ return;
+ }
+
+ this->Options[op] = value;
+}
+
+//----------------------------------------------------------------------
+void cmCTestGenericHandler::SetPersistentOption(const char* 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() )
+ {
+ this->PersistentOptions.erase(remit);
+ }
+ return;
+ }
+
+ this->PersistentOptions[op] = value;
+}
+
+//----------------------------------------------------------------------
+void cmCTestGenericHandler::Initialize()
+{
+ this->AppendXML = false;
+ 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();
+ }
+}
+
+//----------------------------------------------------------------------
+const char* cmCTestGenericHandler::GetOption(const char* op)
+{
+ cmCTestGenericHandler::t_StringToString::iterator remit
+ = this->Options.find(op);
+ if ( remit == this->Options.end() )
+ {
+ return 0;
+ }
+ return remit->second.c_str();
+}
+
+//----------------------------------------------------------------------
+bool cmCTestGenericHandler::StartResultingXML(cmCTest::Part part,
+ const char* name,
+ cmGeneratedFileStream& xofs)
+{
+ if ( !name )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Cannot create resulting XML file without providing the name"
+ << std::endl;);
+ return false;
+ }
+ cmOStringStream ostr;
+ ostr << name;
+ if ( this->SubmitIndex > 0 )
+ {
+ ostr << "_" << this->SubmitIndex;
+ }
+ ostr << ".xml";
+ 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);
+ 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);
+ return false;
+ }
+ this->CTest->AddSubmitFile(part, ostr.str().c_str());
+ return true;
+}
+
+//----------------------------------------------------------------------
+bool cmCTestGenericHandler::StartLogFile(const char* name,
+ cmGeneratedFileStream& xofs)
+{
+ if ( !name )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Cannot create log file without providing the name" << std::endl;);
+ return false;
+ }
+ cmOStringStream ostr;
+ ostr << "Last" << name;
+ if ( this->SubmitIndex > 0 )
+ {
+ ostr << "_" << this->SubmitIndex;
+ }
+ 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);
+ return false;
+ }
+ return true;
+}
diff --git a/Source/CTest/cmCTestGenericHandler.h b/Source/CTest/cmCTestGenericHandler.h
new file mode 100644
index 000000000..18189ec2b
--- /dev/null
+++ b/Source/CTest/cmCTestGenericHandler.h
@@ -0,0 +1,107 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#ifndef cmCTestGenericHandler_h
+#define cmCTestGenericHandler_h
+
+
+#include "cmObject.h"
+#include "cmCTest.h"
+#include "cmSystemTools.h" //OutputOption
+
+class cmMakefile;
+class cmCTestCommand;
+class cmGeneratedFileStream;
+
+/** \class cmCTestGenericHandler
+ * \brief A superclass of all CTest Handlers
+ *
+ */
+class cmCTestGenericHandler : public cmObject
+{
+public:
+ /**
+ * If verbose then more informaiton is printed out
+ */
+ void SetVerbose(bool val)
+ { this->HandlerVerbose = val ?
+ cmSystemTools::OUTPUT_MERGE : cmSystemTools::OUTPUT_NONE; }
+
+ /**
+ * Populate internals from CTest custom scripts
+ */
+ virtual void PopulateCustomVectors(cmMakefile *) {}
+
+ /**
+ * Do the actual processing. Subclass has to override it.
+ * Return < 0 if error.
+ */
+ virtual int ProcessHandler() = 0;
+
+ /**
+ * Process command line arguments that are applicable for the handler
+ */
+ virtual int ProcessCommandLineArguments(
+ const std::string& /*currentArg*/, size_t& /*idx*/,
+ const std::vector<std::string>& /*allArgs*/) { return 1; }
+
+ /**
+ * Initialize handler
+ */
+ virtual void Initialize();
+
+ /**
+ * Set the CTest instance
+ */
+ void SetCTestInstance(cmCTest* ctest) { this->CTest = ctest; }
+ cmCTest* GetCTestInstance() { return this->CTest; }
+
+ /**
+ * Construct handler
+ */
+ cmCTestGenericHandler();
+ virtual ~cmCTestGenericHandler();
+
+ typedef std::map<cmStdString,cmStdString> t_StringToString;
+
+
+ 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 SetSubmitIndex(int idx) { this->SubmitIndex = idx; }
+ int GetSubmitIndex() { return this->SubmitIndex; }
+
+ void SetAppendXML(bool b) { this->AppendXML = b; }
+
+protected:
+ bool StartResultingXML(cmCTest::Part part,
+ const char* name, cmGeneratedFileStream& xofs);
+ bool StartLogFile(const char* name, cmGeneratedFileStream& xofs);
+
+ bool AppendXML;
+ cmSystemTools::OutputOption HandlerVerbose;
+ cmCTest *CTest;
+ t_StringToString Options;
+ t_StringToString PersistentOptions;
+
+ cmCTestCommand* Command;
+ int SubmitIndex;
+};
+
+#endif
+
diff --git a/Source/CTest/cmCTestGlobalVC.cxx b/Source/CTest/cmCTestGlobalVC.cxx
new file mode 100644
index 000000000..8c51102e5
--- /dev/null
+++ b/Source/CTest/cmCTestGlobalVC.cxx
@@ -0,0 +1,142 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestGlobalVC.h"
+
+#include "cmCTest.h"
+#include "cmSystemTools.h"
+#include "cmXMLSafe.h"
+
+#include <cmsys/RegularExpression.hxx>
+
+//----------------------------------------------------------------------------
+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)
+ {
+ this->PriorRev = revision;
+ return;
+ }
+
+ // Indicate we found a revision.
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush);
+
+ // Store the revision.
+ this->Revisions.push_back(revision);
+
+ // Report this revision.
+ Revision const& rev = this->Revisions.back();
+ this->Log << "Found revision " << rev.Rev << "\n"
+ << " author = " << rev.Author << "\n"
+ << " date = " << rev.Date << "\n";
+
+ // 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))
+ {
+ 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.Rev = &rev;
+ this->Log << " " << ci->Action << " " << local << " " << "\n";
+ }
+ }
+}
+
+//----------------------------------------------------------------------------
+void cmCTestGlobalVC::DoModification(PathStatus status,
+ std::string const& path)
+{
+ std::string dir = cmSystemTools::GetFilenamePath(path);
+ std::string name = cmSystemTools::GetFilenameName(path);
+ File& file = this->Dirs[dir][name];
+ 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)
+ {
+ file.PriorRev = &this->PriorRev;
+ }
+}
+
+//----------------------------------------------------------------------------
+void cmCTestGlobalVC::WriteXMLDirectory(std::ostream& 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)
+ {
+ std::string full = path + slash + fi->first;
+ this->WriteXMLEntry(xml, path, fi->first, full, fi->second);
+ }
+ xml << "\t</Directory>\n";
+}
+
+//----------------------------------------------------------------------------
+void cmCTestGlobalVC::WriteXMLGlobal(std::ostream& 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";
+ }
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestGlobalVC::WriteXMLUpdates(std::ostream& xml)
+{
+ cmCTestLog(this->CTest, HANDLER_OUTPUT,
+ " Gathering version information (one . per revision):\n"
+ " " << std::flush);
+ this->LoadRevisions();
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl);
+
+ this->LoadModifications();
+
+ this->WriteXMLGlobal(xml);
+
+ for(std::map<cmStdString, Directory>::const_iterator
+ di = this->Dirs.begin(); di != this->Dirs.end(); ++di)
+ {
+ this->WriteXMLDirectory(xml, di->first, di->second);
+ }
+
+ return true;
+}
diff --git a/Source/CTest/cmCTestGlobalVC.h b/Source/CTest/cmCTestGlobalVC.h
new file mode 100644
index 000000000..a648a5982
--- /dev/null
+++ b/Source/CTest/cmCTestGlobalVC.h
@@ -0,0 +1,68 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestGlobalVC_h
+#define cmCTestGlobalVC_h
+
+#include "cmCTestVC.h"
+
+/** \class cmCTestGlobalVC
+ * \brief Base class for handling globally-versioned trees
+ *
+ */
+class cmCTestGlobalVC: public cmCTestVC
+{
+public:
+ /** Construct with a CTest instance and update log stream. */
+ cmCTestGlobalVC(cmCTest* ctest, std::ostream& log);
+
+ virtual ~cmCTestGlobalVC();
+
+protected:
+ // Implement cmCTestVC internal API.
+ virtual bool WriteXMLUpdates(std::ostream& xml);
+
+ /** Represent a vcs-reported action for one path in a revision. */
+ struct Change
+ {
+ char Action;
+ std::string Path;
+ Change(char a = '?'): Action(a) {}
+ };
+
+ // Update status for files in each directory.
+ class Directory: public std::map<cmStdString, File> {};
+ std::map<cmStdString, Directory> Dirs;
+
+ // Old and new repository revisions.
+ std::string OldRevision;
+ std::string NewRevision;
+
+ // Information known about old revision.
+ Revision PriorRev;
+
+ // Information about revisions from a svn log.
+ std::list<Revision> Revisions;
+
+ virtual const char* LocalPath(std::string const& path);
+
+ 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 void WriteXMLGlobal(std::ostream& xml);
+ void WriteXMLDirectory(std::ostream& xml, std::string const& path,
+ Directory const& dir);
+};
+
+#endif
diff --git a/Source/CTest/cmCTestHG.cxx b/Source/CTest/cmCTestHG.cxx
new file mode 100644
index 000000000..86a7617f1
--- /dev/null
+++ b/Source/CTest/cmCTestHG.cxx
@@ -0,0 +1,338 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestHG.h"
+
+#include "cmCTest.h"
+#include "cmSystemTools.h"
+#include "cmXMLParser.h"
+
+#include <cmsys/RegularExpression.hxx>
+
+//----------------------------------------------------------------------------
+cmCTestHG::cmCTestHG(cmCTest* ct, std::ostream& log):
+ cmCTestGlobalVC(ct, log)
+{
+ this->PriorRev = this->Unknown;
+}
+
+//----------------------------------------------------------------------------
+cmCTestHG::~cmCTestHG()
+{
+}
+
+//----------------------------------------------------------------------------
+class cmCTestHG::IdentifyParser: public cmCTestVC::LineParser
+{
+public:
+ 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))
+ {
+ this->Rev = this->RegexIdentify.match(1);
+ return false;
+ }
+ return true;
+ }
+};
+
+//----------------------------------------------------------------------------
+class cmCTestHG::StatusParser: public cmCTestVC::LineParser
+{
+public:
+ 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;
+ }
+
+ void DoPath(char status, std::string const& path)
+ {
+ if(path.empty()) return;
+
+ // See "hg help status". Note that there is no 'conflict' status.
+ switch(status)
+ {
+ case 'M': case 'A': case '!': case 'R':
+ this->HG->DoModification(PathModified, path);
+ break;
+ 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};
+ std::string rev;
+ IdentifyParser out(this, "rev-out> ", rev);
+ OutputLogger err(this->Log, "rev-err> ");
+ this->RunChild(hg_identify, &out, &err);
+ return rev;
+}
+
+//----------------------------------------------------------------------------
+void cmCTestHG::NoteOldRevision()
+{
+ this->OldRevision = this->GetWorkingRevision();
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, " Old revision of repository is: "
+ << this->OldRevision << "\n");
+ this->PriorRev.Rev = this->OldRevision;
+}
+
+//----------------------------------------------------------------------------
+void cmCTestHG::NoteNewRevision()
+{
+ this->NewRevision = this->GetWorkingRevision();
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, " New revision of repository is: "
+ << this->NewRevision << "\n");
+}
+
+//----------------------------------------------------------------------------
+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);
+ }
+
+ // TODO: if(this->CTest->GetTestModel() == cmCTest::NIGHTLY)
+
+ std::vector<char const*> hg_update;
+ hg_update.push_back(this->CommandLineTool.c_str());
+ hg_update.push_back("update");
+ hg_update.push_back("-v");
+
+ // Add user-specified update options.
+ std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
+ 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)
+ {
+ hg_update.push_back(ai->c_str());
+ }
+
+ // Sentinel argument.
+ hg_update.push_back(0);
+
+ 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
+{
+public:
+ LogParser(cmCTestHG* hg, const char* prefix):
+ OutputLogger(hg->Log, prefix), HG(hg) { this->InitializeParser(); }
+ ~LogParser() { this->CleanupParser(); }
+private:
+ cmCTestHG* HG;
+
+ typedef cmCTestHG::Revision Revision;
+ typedef cmCTestHG::Change Change;
+ Revision Rev;
+ std::vector<Change> Changes;
+ Change CurChange;
+ std::vector<char> CData;
+
+ virtual bool ProcessChunk(const char* data, int length)
+ {
+ this->OutputLogger::ProcessChunk(data, length);
+ this->ParseChunk(data, length);
+ return true;
+ }
+
+ virtual void StartElement(const char* name, const char** atts)
+ {
+ this->CData.clear();
+ if(strcmp(name, "logentry") == 0)
+ {
+ this->Rev = Revision();
+ if(const char* rev = this->FindAttribute(atts, "revision"))
+ {
+ this->Rev.Rev = rev;
+ }
+ this->Changes.clear();
+ }
+ }
+
+ virtual void CharacterDataHandler(const char* data, int length)
+ {
+ this->CData.insert(this->CData.end(), data, data+length);
+ }
+
+ virtual void EndElement(const char* name)
+ {
+ if(strcmp(name, "logentry") == 0)
+ {
+ this->HG->DoRevision(this->Rev, this->Changes);
+ }
+ else if(strcmp(name, "author") == 0 && !this->CData.empty())
+ {
+ this->Rev.Author.assign(&this->CData[0], this->CData.size());
+ }
+ else if ( strcmp(name, "email") == 0 && !this->CData.empty())
+ {
+ this->Rev.EMail.assign(&this->CData[0], this->CData.size());
+ }
+ else if(strcmp(name, "date") == 0 && !this->CData.empty())
+ {
+ this->Rev.Date.assign(&this->CData[0], this->CData.size());
+ }
+ else if(strcmp(name, "msg") == 0 && !this->CData.empty())
+ {
+ this->Rev.Log.assign(&this->CData[0], this->CData.size());
+ }
+ else if(strcmp(name, "files") == 0 && !this->CData.empty())
+ {
+ std::vector<std::string> paths = this->SplitCData();
+ 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())
+ {
+ 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)
+ {
+ this->Changes[i].Action = 'A';
+ }
+ }
+ }
+ else if(strcmp(name, "file_dels") == 0 && !this->CData.empty())
+ {
+ 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)
+ {
+ this->Changes[i].Action = 'D';
+ }
+ }
+ }
+ 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] != ' ')
+ {
+ currPath += this->CData[i];
+ }
+ else
+ {
+ output.push_back(currPath);
+ currPath = "";
+ }
+ }
+ output.push_back(currPath);
+ return output;
+ }
+
+ virtual void ReportError(int, int, const char* msg)
+ {
+ this->HG->Log << "Error parsing hg log xml: " << msg << "\n";
+ }
+};
+
+//----------------------------------------------------------------------------
+void cmCTestHG::LoadRevisions()
+{
+ // Use 'hg log' to get revisions in a xml format.
+ //
+ // TODO: This should use plumbing or python code to be more precise.
+ // The "list of strings" templates like {files} will not work when
+ // the project has spaces in the path. Also, they may not have
+ // 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};
+
+ LogParser out(this, "log-out> ");
+ out.Process("<?xml version=\"1.0\"?>\n"
+ "<log>\n");
+ OutputLogger err(this->Log, "log-err> ");
+ this->RunChild(hg_log, &out, &err);
+ out.Process("</log>\n");
+}
+
+//----------------------------------------------------------------------------
+void cmCTestHG::LoadModifications()
+{
+ // Use 'hg status' to get modified files.
+ const char* hg = this->CommandLineTool.c_str();
+ const char* hg_status[] = {hg, "status", 0};
+ StatusParser out(this, "status-out> ");
+ OutputLogger err(this->Log, "status-err> ");
+ this->RunChild(hg_status, &out, &err);
+}
diff --git a/Source/CTest/cmCTestHG.h b/Source/CTest/cmCTestHG.h
new file mode 100644
index 000000000..1eaf9337e
--- /dev/null
+++ b/Source/CTest/cmCTestHG.h
@@ -0,0 +1,47 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestHG_h
+#define cmCTestHG_h
+
+#include "cmCTestGlobalVC.h"
+
+/** \class cmCTestHG
+ * \brief Interaction with Mercurial command-line tool
+ *
+ */
+class cmCTestHG: public cmCTestGlobalVC
+{
+public:
+ /** Construct with a CTest instance and update log stream. */
+ cmCTestHG(cmCTest* ctest, std::ostream& log);
+
+ virtual ~cmCTestHG();
+
+private:
+ std::string GetWorkingRevision();
+ virtual void NoteOldRevision();
+ virtual void NoteNewRevision();
+ virtual bool UpdateImpl();
+
+ void LoadRevisions();
+ void LoadModifications();
+
+ // Parsing helper classes.
+ class IdentifyParser;
+ class StatusParser;
+ class LogParser;
+ friend class IdentifyParser;
+ friend class StatusParser;
+ friend class LogParser;
+};
+
+#endif
diff --git a/Source/CTest/cmCTestHandlerCommand.cxx b/Source/CTest/cmCTestHandlerCommand.cxx
new file mode 100644
index 000000000..1957e04e3
--- /dev/null
+++ b/Source/CTest/cmCTestHandlerCommand.cxx
@@ -0,0 +1,197 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestHandlerCommand.h"
+
+#include "cmCTest.h"
+#include "cmCTestGenericHandler.h"
+
+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);
+ }
+ this->Arguments[ct_RETURN_VALUE] = "RETURN_VALUE";
+ this->Arguments[ct_SOURCE] = "SOURCE";
+ this->Arguments[ct_BUILD] = "BUILD";
+ this->Arguments[ct_SUBMIT_INDEX] = "SUBMIT_INDEX";
+ this->Last = ct_LAST;
+ this->AppendXML = false;
+}
+
+bool cmCTestHandlerCommand
+::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
+{
+ // Allocate space for argument values.
+ this->Values.clear();
+ this->Values.resize(this->Last, 0);
+
+ // Process input arguments.
+ this->ArgumentDoing = ArgumentDoingNone;
+ for(unsigned int i=0; i < args.size(); ++i)
+ {
+ // Check this argument.
+ if(!this->CheckArgumentKeyword(args[i]) &&
+ !this->CheckArgumentValue(args[i]))
+ {
+ cmOStringStream 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;
+ }
+ }
+
+ // Set the config type of this ctest to the current value of the
+ // CTEST_CONFIGURATION_TYPE script variable if it is defined.
+ // The current script value trumps the -C argument on the command
+ // line.
+ const char* ctestConfigType =
+ this->Makefile->GetDefinition("CTEST_CONFIGURATION_TYPE");
+ if (ctestConfigType)
+ {
+ this->CTest->SetConfigType(ctestConfigType);
+ }
+
+ 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;
+ }
+
+ handler->SetAppendXML(this->AppendXML);
+
+ handler->PopulateCustomVectors(this->Makefile);
+ if ( this->Values[ct_BUILD] )
+ {
+ this->CTest->SetCTestConfiguration("BuildDirectory",
+ cmSystemTools::CollapseFullPath(
+ this->Values[ct_BUILD]).c_str());
+ }
+ 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());
+ }
+ else
+ {
+ this->CTest->SetCTestConfiguration("SourceDirectory",
+ cmSystemTools::CollapseFullPath(
+ this->Makefile->GetSafeDefinition("CTEST_SOURCE_DIRECTORY")).c_str());
+ }
+ if ( this->Values[ct_SUBMIT_INDEX] )
+ {
+ if ( 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
+ {
+ handler->SetSubmitIndex(atoi(this->Values[ct_SUBMIT_INDEX]));
+ }
+ }
+ std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory();
+ cmSystemTools::ChangeDirectory(
+ this->CTest->GetCTestConfiguration("BuildDirectory").c_str());
+ int res = handler->ProcessHandler();
+ if ( this->Values[ct_RETURN_VALUE] && *this->Values[ct_RETURN_VALUE])
+ {
+ cmOStringStream str;
+ str << res;
+ this->Makefile->AddDefinition(
+ this->Values[ct_RETURN_VALUE], str.str().c_str());
+ }
+ cmSystemTools::ChangeDirectory(current_dir.c_str());
+ return true;
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestHandlerCommand::CheckArgumentKeyword(std::string const& arg)
+{
+ // Look for non-value arguments common to all commands.
+ if(arg == "APPEND")
+ {
+ this->ArgumentDoing = ArgumentDoingNone;
+ this->AppendXML = 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])
+ {
+ this->ArgumentDoing = ArgumentDoingKeyword;
+ this->ArgumentIndex = k;
+ return true;
+ }
+ }
+ return false;
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestHandlerCommand::CheckArgumentValue(std::string const& arg)
+{
+ if(this->ArgumentDoing == ArgumentDoingKeyword)
+ {
+ this->ArgumentDoing = ArgumentDoingNone;
+ unsigned int k = this->ArgumentIndex;
+ if(this->Values[k])
+ {
+ cmOStringStream 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");
+ return true;
+ }
+ return false;
+}
diff --git a/Source/CTest/cmCTestHandlerCommand.h b/Source/CTest/cmCTestHandlerCommand.h
new file mode 100644
index 000000000..8ef2b8028
--- /dev/null
+++ b/Source/CTest/cmCTestHandlerCommand.h
@@ -0,0 +1,77 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestHandlerCommand_h
+#define cmCTestHandlerCommand_h
+
+#include "cmCTestCommand.h"
+
+class cmCTestGenericHandler;
+
+/** \class cmCTestHandler
+ * \brief Run a ctest script
+ *
+ * cmCTestHandlerCommand defineds the command to test the project.
+ */
+class cmCTestHandlerCommand : public cmCTestCommand
+{
+public:
+ cmCTestHandlerCommand();
+
+ /**
+ * 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);
+
+ enum
+ {
+ ct_NONE,
+ ct_RETURN_VALUE,
+ ct_BUILD,
+ ct_SOURCE,
+ ct_SUBMIT_INDEX,
+ ct_LAST
+ };
+
+protected:
+ virtual cmCTestGenericHandler* InitializeHandler() = 0;
+
+ // Command argument handling.
+ virtual bool CheckArgumentKeyword(std::string const& arg);
+ virtual bool CheckArgumentValue(std::string const& arg);
+ enum
+ {
+ ArgumentDoingNone,
+ ArgumentDoingError,
+ ArgumentDoingKeyword,
+ ArgumentDoingLast1
+ };
+ int ArgumentDoing;
+ unsigned int ArgumentIndex;
+
+ bool AppendXML;
+
+ std::string ReturnVariable;
+ std::vector<const char*> Arguments;
+ std::vector<const char*> Values;
+ 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. " \
+ "Append semantics are defined by the dashboard server in use."
+
+#endif
diff --git a/Source/CTest/cmCTestLaunch.cxx b/Source/CTest/cmCTestLaunch.cxx
new file mode 100644
index 000000000..9831d0200
--- /dev/null
+++ b/Source/CTest/cmCTestLaunch.cxx
@@ -0,0 +1,719 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestLaunch.h"
+
+#include "cmGeneratedFileStream.h"
+#include "cmSystemTools.h"
+#include "cmXMLSafe.h"
+#include "cmake.h"
+
+#include <cmsys/MD5.h>
+#include <cmsys/Process.h>
+#include <cmsys/RegularExpression.hxx>
+
+//----------------------------------------------------------------------------
+cmCTestLaunch::cmCTestLaunch(int argc, const char* const* argv)
+{
+ this->Passthru = true;
+ this->Process = 0;
+ this->ExitCode = 1;
+ this->CWD = cmSystemTools::GetCurrentWorkingDirectory();
+
+ if(!this->ParseArguments(argc, argv))
+ {
+ return;
+ }
+
+ this->ComputeFileNames();
+
+ this->ScrapeRulesLoaded = false;
+ this->HaveOut = false;
+ this->HaveErr = false;
+ 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());
+ }
+}
+
+//----------------------------------------------------------------------------
+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 };
+ Doing doing = DoingNone;
+ int arg0 = 0;
+ 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)
+ {
+ doing = DoingOutput;
+ }
+ else if(strcmp(arg, "--source") == 0)
+ {
+ doing = DoingSource;
+ }
+ else if(strcmp(arg, "--language") == 0)
+ {
+ doing = DoingLanguage;
+ }
+ else if(strcmp(arg, "--target-name") == 0)
+ {
+ doing = DoingTargetName;
+ }
+ else if(strcmp(arg, "--target-type") == 0)
+ {
+ doing = DoingTargetType;
+ }
+ else if(strcmp(arg, "--build-dir") == 0)
+ {
+ doing = DoingBuildDir;
+ }
+ else if(doing == DoingOutput)
+ {
+ this->OptionOutput = arg;
+ doing = DoingNone;
+ }
+ else if(doing == DoingSource)
+ {
+ this->OptionSource = arg;
+ doing = DoingNone;
+ }
+ else if(doing == DoingLanguage)
+ {
+ this->OptionLanguage = arg;
+ if(this->OptionLanguage == "CXX")
+ {
+ this->OptionLanguage = "C++";
+ }
+ doing = DoingNone;
+ }
+ else if(doing == DoingTargetName)
+ {
+ this->OptionTargetName = arg;
+ doing = DoingNone;
+ }
+ else if(doing == DoingTargetType)
+ {
+ this->OptionTargetType = arg;
+ doing = DoingNone;
+ }
+ else if(doing == DoingBuildDir)
+ {
+ this->OptionBuildDir = arg;
+ doing = DoingNone;
+ }
+ }
+
+ // Extract the real command line.
+ if(arg0)
+ {
+ this->RealArgC = argc - arg0;
+ this->RealArgV = argv + arg0;
+ 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;
+ }
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::HandleRealArg(const char* arg)
+{
+#ifdef _WIN32
+ // Expand response file arguments.
+ if(arg[0] == '@' && cmSystemTools::FileExists(arg+1))
+ {
+ std::ifstream fin(arg+1);
+ std::string line;
+ while(cmSystemTools::GetLineFromStream(fin, line))
+ {
+ cmSystemTools::ParseWindowsCommandLine(line.c_str(), this->RealArgs);
+ }
+ 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))
+ {
+ return;
+ }
+ this->Passthru = false;
+
+ // The environment variable specifies the directory into which we
+ // generate build logs.
+ this->LogDir = d;
+ cmSystemTools::ConvertToUnixSlashes(this->LogDir);
+ this->LogDir += "/";
+
+ // 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);
+
+ // We store stdout and stderr in temporary log files.
+ this->LogOut = this->LogDir;
+ this->LogOut += "launch-";
+ this->LogOut += this->LogHash;
+ this->LogOut += "-out.txt";
+ this->LogErr = this->LogDir;
+ this->LogErr += "launch-";
+ this->LogErr += this->LogHash;
+ this->LogErr += "-err.txt";
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::RunChild()
+{
+ // Ignore noopt make rules
+ 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)
+ {
+ // In passthru mode we just share the output pipes.
+ cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDOUT, 1);
+ cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDERR, 1);
+ }
+ 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);
+ }
+
+ // Run the real command.
+ cmsysProcess_Execute(cp);
+
+ // Record child stdout and stderr if necessary.
+ if(!this->Passthru)
+ {
+ char* data = 0;
+ 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);
+ this->HaveOut = true;
+ }
+ else if(p == cmsysProcess_Pipe_STDERR)
+ {
+ ferr.write(data, length);
+ std::cerr.write(data, length);
+ this->HaveErr = true;
+ }
+ }
+ }
+
+ // Wait for the real command to finish.
+ cmsysProcess_WaitForExit(cp, 0);
+ this->ExitCode = cmsysProcess_GetExitValue(cp);
+}
+
+//----------------------------------------------------------------------------
+int cmCTestLaunch::Run()
+{
+ if(!this->Process)
+ {
+ std::cerr << "Could not allocate cmsysProcess instance!\n";
+ return -1;
+ }
+
+ this->RunChild();
+
+ if(this->CheckResults())
+ {
+ return this->ExitCode;
+ }
+
+ this->LoadConfig();
+ this->WriteXML();
+
+ return this->ExitCode;
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::LoadLabels()
+{
+ if(this->OptionBuildDir.empty() || this->OptionTargetName.empty())
+ {
+ return;
+ }
+
+ // Labels are listed in per-target files.
+ std::string fname = this->OptionBuildDir;
+ fname += cmake::GetCMakeFilesDirectory();
+ fname += "/";
+ fname += this->OptionTargetName;
+ fname += ".dir/Labels.txt";
+
+ // We are interested in per-target labels for this source file.
+ std::string source = this->OptionSource;
+ cmSystemTools::ConvertToUnixSlashes(source);
+
+ // Load the labels file.
+ std::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] == '#')
+ {
+ // Ignore blank and comment lines.
+ continue;
+ }
+ else if(line[0] == ' ')
+ {
+ // Label lines appear indented by one space.
+ if(inTarget || inSource)
+ {
+ this->Labels.insert(line.c_str()+1);
+ }
+ }
+ 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
+ {
+ return;
+ }
+ }
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestLaunch::SourceMatches(std::string const& lhs,
+ std::string const& rhs)
+{
+ // TODO: Case sensitivity, UseRelativePaths, etc. Note that both
+ // paths in the comparison get generated by CMake. This is done for
+ // every source in the target, so it should be efficient (cannot use
+ // cmSystemTools::IsSameFile).
+ 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->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";
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::WriteXMLAction(std::ostream& fxml)
+{
+ fxml << "\t\t<!-- Meta-information about the build action -->\n";
+ fxml << "\t\t<Action>\n";
+
+ // TargetName
+ if(!this->OptionTargetName.empty())
+ {
+ fxml << "\t\t\t<TargetName>"
+ << cmXMLSafe(this->OptionTargetName)
+ << "</TargetName>\n";
+ }
+
+ // Language
+ if(!this->OptionLanguage.empty())
+ {
+ fxml << "\t\t\t<Language>"
+ << cmXMLSafe(this->OptionLanguage)
+ << "</Language>\n";
+ }
+
+ // SourceFile
+ 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";
+ }
+
+ // OutputFile
+ if(!this->OptionOutput.empty())
+ {
+ fxml << "\t\t\t<OutputFile>"
+ << cmXMLSafe(this->OptionOutput)
+ << "</OutputFile>\n";
+ }
+
+ // OutputType
+ const char* outputType = 0;
+ if(!this->OptionTargetType.empty())
+ {
+ if(this->OptionTargetType == "EXECUTABLE")
+ {
+ outputType = "executable";
+ }
+ else if(this->OptionTargetType == "SHARED_LIBRARY")
+ {
+ outputType = "shared library";
+ }
+ else if(this->OptionTargetType == "MODULE_LIBRARY")
+ {
+ outputType = "module library";
+ }
+ else if(this->OptionTargetType == "STATIC_LIBRARY")
+ {
+ outputType = "static library";
+ }
+ }
+ else if(!this->OptionSource.empty())
+ {
+ outputType = "object file";
+ }
+ if(outputType)
+ {
+ fxml << "\t\t\t<OutputType>"
+ << cmXMLSafe(outputType)
+ << "</OutputType>\n";
+ }
+
+ fxml << "\t\t</Action>\n";
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::WriteXMLCommand(std::ostream& fxml)
+{
+ 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";
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::WriteXMLResult(std::ostream& fxml)
+{
+ fxml << "\n";
+ fxml << "\t\t<!-- Result of command -->\n";
+ fxml << "\t\t<Result>\n";
+
+ // StdOut
+ fxml << "\t\t\t<StdOut>";
+ this->DumpFileToXML(fxml, this->LogOut);
+ fxml << "</StdOut>\n";
+
+ // StdErr
+ fxml << "\t\t\t<StdErr>";
+ this->DumpFileToXML(fxml, this->LogErr);
+ fxml << "</StdErr>\n";
+
+ // ExitCondition
+ fxml << "\t\t\t<ExitCondition>";
+ cmsysProcess* cp = this->Process;
+ switch (cmsysProcess_GetState(cp))
+ {
+ case cmsysProcess_State_Starting:
+ fxml << "No process has been executed"; break;
+ case cmsysProcess_State_Executing:
+ fxml << "The process is still executing"; break;
+ case cmsysProcess_State_Disowned:
+ fxml << "Disowned"; break;
+ case cmsysProcess_State_Killed:
+ fxml << "Killed by parent"; break;
+
+ case cmsysProcess_State_Expired:
+ fxml << "Killed when timeout expired"; break;
+ case cmsysProcess_State_Exited:
+ fxml << this->ExitCode; break;
+ case cmsysProcess_State_Exception:
+ fxml << "Terminated abnormally: "
+ << cmXMLSafe(cmsysProcess_GetExceptionString(cp)); break;
+ case cmsysProcess_State_Error:
+ fxml << "Error administrating child process: "
+ << cmXMLSafe(cmsysProcess_GetErrorString(cp)); break;
+ };
+ fxml << "</ExitCondition>\n";
+
+ fxml << "\t\t</Result>\n";
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::WriteXMLLabels(std::ostream& fxml)
+{
+ 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";
+ }
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::DumpFileToXML(std::ostream& fxml,
+ std::string const& fname)
+{
+ std::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";
+ }
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestLaunch::CheckResults()
+{
+ // Skip XML in passthru mode.
+ if(this->Passthru)
+ {
+ return true;
+ }
+
+ // We always report failure for error conditions.
+ 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)))
+ {
+ return false;
+ }
+ return true;
+}
+
+//----------------------------------------------------------------------------
+void cmCTestLaunch::LoadScrapeRules()
+{
+ if(this->ScrapeRulesLoaded)
+ {
+ return;
+ }
+ this->ScrapeRulesLoaded = true;
+
+ // Common compiler warning formats. These are much simpler than the
+ // full log-scraping expressions because we do not need to extract
+ // file and line information.
+ this->RegexWarning.push_back("(^|[ :])[Ww][Aa][Rr][Nn][Ii][Nn][Gg]");
+ this->RegexWarning.push_back("(^|[ :])[Rr][Ee][Mm][Aa][Rr][Kk]");
+ this->RegexWarning.push_back("(^|[ :])[Nn][Oo][Tt][Ee]");
+
+ // Load custom match rules given to us by CTest.
+ this->LoadScrapeRules("Warning", this->RegexWarning);
+ this->LoadScrapeRules("WarningSuppress", this->RegexWarningSuppress);
+}
+
+//----------------------------------------------------------------------------
+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);
+ std::string line;
+ cmsys::RegularExpression rex;
+ 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);
+ std::string line;
+ while(cmSystemTools::GetLineFromStream(fin, line))
+ {
+ if(this->Match(line.c_str(), this->RegexWarning) &&
+ !this->Match(line.c_str(), 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()))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+//----------------------------------------------------------------------------
+int cmCTestLaunch::Main(int argc, const char* const argv[])
+{
+ 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();
+ std::string fname = this->LogDir;
+ fname += "CTestLaunchConfig.cmake";
+ if(cmSystemTools::FileExists(fname.c_str()) &&
+ mf->ReadListFile(0, 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
new file mode 100644
index 000000000..7457e8357
--- /dev/null
+++ b/Source/CTest/cmCTestLaunch.h
@@ -0,0 +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.
+============================================================================*/
+#ifndef cmCTestLaunch_h
+#define cmCTestLaunch_h
+
+#include "cmStandardIncludes.h"
+#include <cmsys/RegularExpression.hxx>
+
+/** \class cmCTestLaunch
+ * \brief Launcher for make rules to report results for ctest
+ *
+ * This implements the 'ctest --launch' tool.
+ */
+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);
+ ~cmCTestLaunch();
+
+ // Run the real command.
+ int Run();
+ void RunChild();
+
+ // Methods to check the result of the real command.
+ bool IsError() const;
+ bool CheckResults();
+
+ // Launcher options specified before the real command.
+ std::string OptionOutput;
+ std::string OptionSource;
+ std::string OptionLanguage;
+ std::string OptionTargetName;
+ std::string OptionTargetType;
+ std::string OptionBuildDir;
+ bool ParseArguments(int argc, const char* const* argv);
+
+ // The real command line appearing after launcher arguments.
+ int RealArgC;
+ const char* const* RealArgV;
+ std::string CWD;
+
+ // The real command line after response file expansion.
+ std::vector<std::string> RealArgs;
+ void HandleRealArg(const char* arg);
+
+ // A hash of the real command line is unique and unlikely to collide.
+ std::string LogHash;
+ void ComputeFileNames();
+
+ bool Passthru;
+ struct cmsysProcess_s* Process;
+ int ExitCode;
+
+ // Temporary log files for stdout and stderr of real command.
+ std::string LogDir;
+ std::string LogOut;
+ std::string LogErr;
+ bool HaveOut;
+ bool HaveErr;
+
+ // Labels associated with the build rule.
+ std::set<cmStdString> Labels;
+ void LoadLabels();
+ bool SourceMatches(std::string const& lhs,
+ std::string const& rhs);
+
+ // Regular expressions to match warnings and their exceptions.
+ bool ScrapeRulesLoaded;
+ std::vector<cmsys::RegularExpression> RegexWarning;
+ std::vector<cmsys::RegularExpression> RegexWarningSuppress;
+ void LoadScrapeRules();
+ void LoadScrapeRules(const char* purpose,
+ std::vector<cmsys::RegularExpression>& regexps);
+ bool ScrapeLog(std::string const& fname);
+ bool Match(std::string const& line,
+ std::vector<cmsys::RegularExpression>& regexps);
+
+ // 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);
+
+ // Configuration
+ void LoadConfig();
+ std::string SourceDir;
+};
+
+#endif
diff --git a/Source/CTest/cmCTestMemCheckCommand.cxx b/Source/CTest/cmCTestMemCheckCommand.cxx
new file mode 100644
index 000000000..535c9934f
--- /dev/null
+++ b/Source/CTest/cmCTestMemCheckCommand.cxx
@@ -0,0 +1,32 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestMemCheckCommand.h"
+
+#include "cmCTest.h"
+#include "cmCTestGenericHandler.h"
+
+
+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");
+
+ return handler;
+}
+
diff --git a/Source/CTest/cmCTestMemCheckCommand.h b/Source/CTest/cmCTestMemCheckCommand.h
new file mode 100644
index 000000000..399fe8b22
--- /dev/null
+++ b/Source/CTest/cmCTestMemCheckCommand.h
@@ -0,0 +1,88 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestMemCheckCommand_h
+#define cmCTestMemCheckCommand_h
+
+#include "cmCTestTestCommand.h"
+
+class cmCTestGenericHandler;
+
+/** \class cmCTestMemCheck
+ * \brief Run a ctest script
+ *
+ * cmCTestMemCheckCommand defineds the command to test the project.
+ */
+class cmCTestMemCheckCommand : public cmCTestTestCommand
+{
+public:
+
+ cmCTestMemCheckCommand() {}
+
+ /**
+ * This is a virtual constructor for the command.
+ */
+ virtual cmCommand* Clone()
+ {
+ 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;
+ }
+
+ cmTypeMacro(cmCTestMemCheckCommand, cmCTestTestCommand);
+
+protected:
+ cmCTestGenericHandler* InitializeActualHandler();
+};
+
+
+#endif
+
diff --git a/Source/CTest/cmCTestMemCheckHandler.cxx b/Source/CTest/cmCTestMemCheckHandler.cxx
new file mode 100644
index 000000000..3e4ecddaa
--- /dev/null
+++ b/Source/CTest/cmCTestMemCheckHandler.cxx
@@ -0,0 +1,955 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#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>
+
+struct CatToErrorType
+{
+ const char* ErrorCategory;
+ 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}
+};
+
+// parse the xml file storing the installed version of Xcode on
+// the machine
+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();
+ }
+ void EndElement(const char* )
+ {
+ }
+
+ 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;
+ }
+ 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);
+ }
+ }
+ 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
+};
+
+
+//----------------------------------------------------------------------
+cmCTestMemCheckHandler::cmCTestMemCheckHandler()
+{
+ this->MemCheck = true;
+ this->CustomMaximumPassedTestOutputSize = 0;
+ this->CustomMaximumFailedTestOutputSize = 0;
+}
+
+//----------------------------------------------------------------------
+void cmCTestMemCheckHandler::Initialize()
+{
+ this->Superclass::Initialize();
+ this->CustomMaximumPassedTestOutputSize = 0;
+ this->CustomMaximumFailedTestOutputSize = 0;
+ this->MemoryTester = "";
+ this->MemoryTesterOptions.clear();
+ this->MemoryTesterStyle = UNKNOWN;
+ this->MemoryTesterOutputFile = "";
+ int cc;
+ for ( cc = 0; cc < NO_MEMORY_FAULT; cc ++ )
+ {
+ this->MemoryTesterGlobalResults[cc] = 0;
+ }
+
+}
+
+//----------------------------------------------------------------------
+int cmCTestMemCheckHandler::PreProcessHandler()
+{
+ if ( !this->InitializeMemoryChecking() )
+ {
+ return 0;
+ }
+
+ if ( !this->ExecuteCommands(this->CustomPreMemCheck) )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Problem executing pre-memcheck command(s)." << std::endl);
+ return 0;
+ }
+ return 1;
+}
+
+//----------------------------------------------------------------------
+int cmCTestMemCheckHandler::PostProcessHandler()
+{
+ if ( !this->ExecuteCommands(this->CustomPostMemCheck) )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Problem executing post-memcheck command(s)." << std::endl);
+ return 0;
+ }
+ return 1;
+}
+
+//----------------------------------------------------------------------
+void cmCTestMemCheckHandler::GenerateTestCommand(
+ std::vector<std::string>& args)
+{
+ std::vector<cmStdString>::size_type pp;
+ std::string memcheckcommand = "";
+ memcheckcommand = this->MemoryTester;
+ for ( pp = 0; pp < this->MemoryTesterOptions.size(); pp ++ )
+ {
+ args.push_back(this->MemoryTesterOptions[pp]);
+ memcheckcommand += " \"";
+ memcheckcommand += this->MemoryTesterOptions[pp];
+ memcheckcommand += "\"";
+ }
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Memory check command: "
+ << memcheckcommand << std::endl);
+}
+
+//----------------------------------------------------------------------
+void cmCTestMemCheckHandler::PopulateCustomVectors(cmMakefile *mf)
+{
+ this->cmCTestTestHandler::PopulateCustomVectors(mf);
+ this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_PRE_MEMCHECK",
+ this->CustomPreMemCheck);
+ this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_POST_MEMCHECK",
+ this->CustomPostMemCheck);
+
+ this->CTest->PopulateCustomVector(mf,
+ "CTEST_CUSTOM_MEMCHECK_IGNORE",
+ this->CustomTestsIgnore);
+}
+
+//----------------------------------------------------------------------
+void cmCTestMemCheckHandler::GenerateDartOutput(std::ostream& os)
+{
+ if ( !this->CTest->GetProduceXML() )
+ {
+ return;
+ }
+
+ this->CTest->StartXML(os, this->AppendXML);
+ os << "<DynamicAnalysis Checker=\"";
+ switch ( this->MemoryTesterStyle )
+ {
+ case cmCTestMemCheckHandler::VALGRIND:
+ os << "Valgrind";
+ break;
+ case cmCTestMemCheckHandler::PURIFY:
+ os << "Purify";
+ break;
+ case cmCTestMemCheckHandler::BOUNDS_CHECKER:
+ os << "BoundsChecker";
+ break;
+ default:
+ os << "Unknown";
+ }
+ os << "\">" << std::endl;
+
+ os << "\t<StartDateTime>" << this->StartTest << "</StartDateTime>\n"
+ << "\t<StartTestTime>" << this->StartTestTime << "</StartTestTime>\n"
+ << "\t<TestList>\n";
+ cmCTestMemCheckHandler::TestResultsVector::size_type 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: ");
+ 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];
+ std::string memcheckstr;
+ int memcheckresults[cmCTestMemCheckHandler::NO_MEMORY_FAULT];
+ int kk;
+ bool res = this->ProcessMemCheckOutput(result->Output, memcheckstr,
+ memcheckresults);
+ if ( res && result->Status == cmCTestMemCheckHandler::COMPLETED )
+ {
+ continue;
+ }
+ 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];
+ }
+
+ std::string logTag;
+ if(this->CTest->ShouldCompressMemCheckOutput())
+ {
+ 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;
+ }
+ }
+ 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
+ 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;
+ }
+ }
+ 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);
+
+
+}
+
+//----------------------------------------------------------------------
+bool cmCTestMemCheckHandler::InitializeMemoryChecking()
+{
+ // Setup the command
+ if ( cmSystemTools::FileExists(this->CTest->GetCTestConfiguration(
+ "MemoryCheckCommand").c_str()) )
+ {
+ this->MemoryTester
+ = cmSystemTools::ConvertToOutputPath(this->CTest->GetCTestConfiguration(
+ "MemoryCheckCommand").c_str());
+ }
+ else if ( cmSystemTools::FileExists(this->CTest->GetCTestConfiguration(
+ "PurifyCommand").c_str()) )
+ {
+ this->MemoryTester
+ = cmSystemTools::ConvertToOutputPath(this->CTest->GetCTestConfiguration(
+ "PurifyCommand").c_str());
+ }
+ else if ( cmSystemTools::FileExists(this->CTest->GetCTestConfiguration(
+ "ValgrindCommand").c_str()) )
+ {
+ this->MemoryTester
+ = cmSystemTools::ConvertToOutputPath(this->CTest->GetCTestConfiguration(
+ "ValgrindCommand").c_str());
+ }
+ else if ( cmSystemTools::FileExists(this->CTest->GetCTestConfiguration(
+ "BoundsCheckerCommand").c_str()) )
+ {
+ this->MemoryTester
+ = cmSystemTools::ConvertToOutputPath(this->CTest->GetCTestConfiguration(
+ "BoundsCheckerCommand").c_str());
+ }
+ else
+ {
+ cmCTestLog(this->CTest, WARNING,
+ "Memory checker (MemoryCheckCommand) "
+ "not set, or cannot find the specified program."
+ << std::endl);
+ return false;
+ }
+
+ if ( this->MemoryTester[0] == '\"' &&
+ this->MemoryTester[this->MemoryTester.size()-1] == '\"' )
+ {
+ this->MemoryTester
+ = this->MemoryTester.substr(1, this->MemoryTester.size()-2);
+ }
+
+ // 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";
+
+ if ( this->MemoryTester.find("valgrind") != std::string::npos )
+ {
+ this->MemoryTesterStyle = 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("--workaround-gcc296-bugs=yes");
+ this->MemoryTesterOptions.push_back("--num-callers=50");
+ }
+ 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 suppressions = "--suppressions="
+ + this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile");
+ this->MemoryTesterOptions.push_back(suppressions);
+ }
+ }
+ else if ( this->MemoryTester.find("purify") != std::string::npos )
+ {
+ this->MemoryTesterStyle = 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);
+ return false;
+ }
+ std::string filterFiles = "/FilterFiles="
+ + this->CTest->GetCTestConfiguration("MemoryCheckSuppressionFile");
+ this->MemoryTesterOptions.push_back(filterFiles);
+ }
+ outputFile = "/SAVETEXTDATA=";
+#else
+ outputFile = "-log-file=";
+#endif
+ outputFile += this->MemoryTesterOutputFile;
+ this->MemoryTesterOptions.push_back(outputFile);
+ }
+ else if ( this->MemoryTester.find("BC") != std::string::npos )
+ {
+ this->BoundsCheckerXMLFile = this->MemoryTesterOutputFile;
+ std::string dpbdFile = this->CTest->GetBinaryDir()
+ + "/Testing/Temporary/MemoryChecker.DPbd";
+ this->BoundsCheckerDPBDFile = dpbdFile;
+ this->MemoryTesterStyle = cmCTestMemCheckHandler::BOUNDS_CHECKER;
+ this->MemoryTesterOptions.push_back("/B");
+ this->MemoryTesterOptions.push_back(dpbdFile);
+ this->MemoryTesterOptions.push_back("/X");
+ this->MemoryTesterOptions.push_back(this->MemoryTesterOutputFile);
+ this->MemoryTesterOptions.push_back("/M");
+ }
+ else
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Do not understand memory checker: " << this->MemoryTester.c_str()
+ << std::endl);
+ return false;
+ }
+
+ std::vector<cmStdString>::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::size_type cc;
+ for ( cc = 0; cc < cmCTestMemCheckHandler::NO_MEMORY_FAULT; cc ++ )
+ {
+ results[cc] = 0;
+ }
+
+ if ( this->MemoryTesterStyle == cmCTestMemCheckHandler::VALGRIND )
+ {
+ return this->ProcessMemCheckValgrindOutput(str, log, results);
+ }
+ else if ( this->MemoryTesterStyle == cmCTestMemCheckHandler::PURIFY )
+ {
+ return this->ProcessMemCheckPurifyOutput(str, log, results);
+ }
+ else if ( this->MemoryTesterStyle ==
+ cmCTestMemCheckHandler::BOUNDS_CHECKER )
+ {
+ return this->ProcessMemCheckBoundsCheckerOutput(str, log, results);
+ }
+ else
+ {
+ log.append("\nMemory checking style used was: ");
+ log.append("None that I know");
+ log = str;
+ }
+
+
+ return true;
+}
+
+//----------------------------------------------------------------------
+bool cmCTestMemCheckHandler::ProcessMemCheckPurifyOutput(
+ const std::string& str, std::string& log,
+ int* results)
+{
+ std::vector<cmStdString> lines;
+ cmSystemTools::Split(str.c_str(), lines);
+ cmOStringStream 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] )
+ {
+ failure = cc;
+ break;
+ }
+ }
+ if ( cc == cmCTestMemCheckHandler::NO_MEMORY_FAULT )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown Purify memory fault: "
+ << 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 ++;
+ }
+ ostr << cmXMLSafe(*i) << std::endl;
+ }
+
+ log = ostr.str();
+ if ( defects )
+ {
+ return false;
+ }
+ return true;
+}
+
+//----------------------------------------------------------------------
+bool cmCTestMemCheckHandler::ProcessMemCheckValgrindOutput(
+ const std::string& str, std::string& log,
+ int* results)
+{
+ std::vector<cmStdString> lines;
+ cmSystemTools::Split(str.c_str(), lines);
+ bool unlimitedOutput = false;
+ if(str.find("CTEST_FULL_OUTPUT") != str.npos ||
+ this->CustomMaximumFailedTestOutputSize == 0)
+ {
+ unlimitedOutput = true;
+ }
+
+ std::string::size_type cc;
+
+ cmOStringStream ostr;
+ log = "";
+
+ int defects = 0;
+
+ cmsys::RegularExpression valgrindLine("^==[0-9][0-9]*==");
+
+ cmsys::RegularExpression vgFIM(
+ "== .*Invalid free\\(\\) / delete / delete\\[\\]");
+ cmsys::RegularExpression vgFMM(
+ "== .*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,]+");
+ cmsys::RegularExpression vgMLK2(
+ "== .*[0-9,]+ \\([0-9,]+ direct, [0-9,]+ indirect\\)"
+ " bytes in [0-9,]+ blocks are definitely lost"
+ " in loss record [0-9,]+ of [0-9,]+");
+ cmsys::RegularExpression vgPAR(
+ "== .*Syscall param .* (contains|points to) unaddressable byte\\(s\\)");
+ cmsys::RegularExpression vgMPK1(
+ "== .*[0-9,]+ bytes in [0-9,]+ blocks are possibly lost in"
+ " loss record [0-9,]+ of [0-9,]+");
+ cmsys::RegularExpression vgMPK2(
+ "== .*[0-9,]+ bytes in [0-9,]+ blocks are still reachable"
+ " in loss record [0-9,]+ of [0-9,]+");
+ cmsys::RegularExpression vgUMC(
+ "== .*Conditional jump or move depends on uninitialised value\\(s\\)");
+ cmsys::RegularExpression vgUMR1(
+ "== .*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 "
+ "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");
+ std::vector<std::string::size_type> nonValGrindOutput;
+ double sttime = cmSystemTools::GetTime();
+ cmCTestLog(this->CTest, DEBUG, "Start test: " << lines.size() << std::endl);
+ 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);
+ int failure = cmCTestMemCheckHandler::NO_MEMORY_FAULT;
+ if ( vgFIM.find(lines[cc]) )
+ {
+ failure = cmCTestMemCheckHandler::FIM;
+ }
+ else if ( vgFMM.find(lines[cc]) )
+ {
+ failure = cmCTestMemCheckHandler::FMM;
+ }
+ else if ( vgMLK1.find(lines[cc]) )
+ {
+ failure = cmCTestMemCheckHandler::MLK;
+ }
+ else if ( vgMLK2.find(lines[cc]) )
+ {
+ failure = cmCTestMemCheckHandler::MLK;
+ }
+ else if ( vgPAR.find(lines[cc]) )
+ {
+ failure = cmCTestMemCheckHandler::PAR;
+ }
+ else if ( vgMPK1.find(lines[cc]) )
+ {
+ failure = cmCTestMemCheckHandler::MPK;
+ }
+ else if ( vgMPK2.find(lines[cc]) )
+ {
+ failure = cmCTestMemCheckHandler::MPK;
+ }
+ else if ( vgUMC.find(lines[cc]) )
+ {
+ failure = cmCTestMemCheckHandler::UMC;
+ }
+ else if ( vgUMR1.find(lines[cc]) )
+ {
+ failure = cmCTestMemCheckHandler::UMR;
+ }
+ else if ( vgUMR2.find(lines[cc]) )
+ {
+ failure = cmCTestMemCheckHandler::UMR;
+ }
+ else if ( vgUMR3.find(lines[cc]) )
+ {
+ failure = cmCTestMemCheckHandler::UMR;
+ }
+ else if ( vgUMR4.find(lines[cc]) )
+ {
+ failure = cmCTestMemCheckHandler::UMR;
+ }
+ else if ( vgUMR5.find(lines[cc]) )
+ {
+ failure = cmCTestMemCheckHandler::UMR;
+ }
+ else if ( vgIPW.find(lines[cc]) )
+ {
+ failure = cmCTestMemCheckHandler::IPW;
+ }
+ 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;
+ }
+ 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";
+ }
+ }
+ }
+ cmCTestLog(this->CTest, DEBUG, "End test (elapsed: "
+ << (cmSystemTools::GetTime() - sttime) << std::endl);
+ log = ostr.str();
+ if ( defects )
+ {
+ return false;
+ }
+ return true;
+}
+
+
+
+//----------------------------------------------------------------------
+bool cmCTestMemCheckHandler::ProcessMemCheckBoundsCheckerOutput(
+ const std::string& str, std::string& log,
+ int* results)
+{
+ log = "";
+ double sttime = cmSystemTools::GetTime();
+ std::vector<cmStdString> 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)
+ {
+ break;
+ }
+ }
+ cmBoundsCheckerParser parser(this->CTest);
+ parser.InitializeParser();
+ 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)
+ {
+ // skip this because BC gets it wrong and we can't parse it
+ }
+ else if(!parser.ParseChunk(theLine.c_str(), theLine.size()))
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Error in ParseChunk: " << theLine.c_str()
+ << std::endl);
+ }
+ }
+ }
+ int defects = 0;
+ 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)
+ {
+ // only put the output of Bounds Checker if there were
+ // errors or leaks detected
+ log = parser.Log;
+ return false;
+ }
+ return true;
+}
+
+// This method puts the bounds checker output file into the output
+// for the test
+void
+cmCTestMemCheckHandler::PostProcessBoundsCheckerTest(cmCTestTestResult& res)
+{
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "PostProcessBoundsCheckerTest for : "
+ << res.Name.c_str() << std::endl);
+ if ( !cmSystemTools::FileExists(this->MemoryTesterOutputFile.c_str()) )
+ {
+ std::string log = "Cannot find memory tester output file: "
+ + this->MemoryTesterOutputFile;
+ cmCTestLog(this->CTest, ERROR_MESSAGE, log.c_str() << std::endl);
+ return;
+ }
+ // put a scope around this to close ifs so the file can be removed
+ {
+ std::ifstream ifs(this->MemoryTesterOutputFile.c_str());
+ if ( !ifs )
+ {
+ std::string log = "Cannot read memory tester output file: "
+ + this->MemoryTesterOutputFile;
+ cmCTestLog(this->CTest, ERROR_MESSAGE, log.c_str() << std::endl);
+ return;
+ }
+ 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);
+}
+
+void
+cmCTestMemCheckHandler::PostProcessPurifyTest(cmCTestTestResult& res)
+{
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "PostProcessPurifyTest for : "
+ << res.Name.c_str() << std::endl);
+ if ( !cmSystemTools::FileExists(this->MemoryTesterOutputFile.c_str()) )
+ {
+ std::string log = "Cannot find memory tester output file: "
+ + this->MemoryTesterOutputFile;
+ cmCTestLog(this->CTest, ERROR_MESSAGE, log.c_str() << std::endl);
+ return;
+ }
+ std::ifstream ifs(this->MemoryTesterOutputFile.c_str());
+ if ( !ifs )
+ {
+ std::string log = "Cannot read memory tester output file: "
+ + this->MemoryTesterOutputFile;
+ cmCTestLog(this->CTest, ERROR_MESSAGE, log.c_str() << std::endl);
+ return;
+ }
+ std::string line;
+ while ( cmSystemTools::GetLineFromStream(ifs, line) )
+ {
+ res.Output += line;
+ res.Output += "\n";
+ }
+}
diff --git a/Source/CTest/cmCTestMemCheckHandler.h b/Source/CTest/cmCTestMemCheckHandler.h
new file mode 100644
index 000000000..427d4717d
--- /dev/null
+++ b/Source/CTest/cmCTestMemCheckHandler.h
@@ -0,0 +1,125 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#ifndef cmCTestMemCheckHandler_h
+#define cmCTestMemCheckHandler_h
+
+
+#include "cmCTestTestHandler.h"
+#include "cmListFileCache.h"
+
+class cmMakefile;
+
+/** \class cmCTestMemCheckHandler
+ * \brief A class that handles ctest -S invocations
+ *
+ */
+class cmCTestMemCheckHandler : public cmCTestTestHandler
+{
+ friend class cmCTestRunTest;
+public:
+ cmTypeMacro(cmCTestMemCheckHandler, cmCTestTestHandler);
+
+ void PopulateCustomVectors(cmMakefile *mf);
+
+ cmCTestMemCheckHandler();
+
+ void Initialize();
+protected:
+ virtual int PreProcessHandler();
+ virtual int PostProcessHandler();
+ virtual void GenerateTestCommand(std::vector<std::string>& args);
+
+private:
+
+ enum { // Memory checkers
+ UNKNOWN = 0,
+ VALGRIND,
+ PURIFY,
+ BOUNDS_CHECKER
+ };
+public:
+ enum { // Memory faults
+ ABR = 0,
+ ABW,
+ ABWL,
+ COR,
+ EXU,
+ FFM,
+ FIM,
+ FMM,
+ FMR,
+ FMW,
+ FUM,
+ IPR,
+ IPW,
+ MAF,
+ MLK,
+ MPK,
+ NPR,
+ ODS,
+ PAR,
+ PLK,
+ UMC,
+ UMR,
+ NO_MEMORY_FAULT
+ };
+private:
+ enum { // Program statuses
+ NOT_RUN = 0,
+ TIMEOUT,
+ SEGFAULT,
+ ILLEGAL,
+ INTERRUPT,
+ NUMERICAL,
+ OTHER_FAULT,
+ FAILED,
+ BAD_COMMAND,
+ COMPLETED
+ };
+ std::string BoundsCheckerDPBDFile;
+ std::string BoundsCheckerXMLFile;
+ std::string MemoryTester;
+ std::vector<cmStdString> MemoryTesterOptions;
+ int MemoryTesterStyle;
+ std::string MemoryTesterOutputFile;
+ int MemoryTesterGlobalResults[NO_MEMORY_FAULT];
+
+ ///! Initialize memory checking subsystem.
+ bool InitializeMemoryChecking();
+
+ /**
+ * Generate the Dart compatible output
+ */
+ void GenerateDartOutput(std::ostream& os);
+
+ std::vector<cmStdString> CustomPreMemCheck;
+ std::vector<cmStdString> 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);
+ bool ProcessMemCheckBoundsCheckerOutput(const std::string& str,
+ std::string& log, int* results);
+
+ void PostProcessPurifyTest(cmCTestTestResult& res);
+ void PostProcessBoundsCheckerTest(cmCTestTestResult& res);
+};
+
+#endif
+
diff --git a/Source/CTest/cmCTestMultiProcessHandler.cxx b/Source/CTest/cmCTestMultiProcessHandler.cxx
new file mode 100644
index 000000000..2cae80215
--- /dev/null
+++ b/Source/CTest/cmCTestMultiProcessHandler.cxx
@@ -0,0 +1,691 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestMultiProcessHandler.h"
+#include "cmProcess.h"
+#include "cmStandardIncludes.h"
+#include "cmCTest.h"
+#include "cmSystemTools.h"
+#include <stdlib.h>
+#include <stack>
+#include <float.h>
+
+class TestComparator
+{
+public:
+ TestComparator(cmCTestMultiProcessHandler* handler) : Handler(handler) {}
+ ~TestComparator() {}
+
+ // Sorts tests in descending order of cost
+ bool operator() (int index1, int index2) const
+ {
+ return Handler->Properties[index1]->Cost >
+ Handler->Properties[index2]->Cost;
+ }
+
+private:
+ cmCTestMultiProcessHandler* Handler;
+};
+
+cmCTestMultiProcessHandler::cmCTestMultiProcessHandler()
+{
+ this->ParallelLevel = 1;
+ this->Completed = 0;
+ this->RunningCount = 0;
+ this->StopTimePassed = false;
+}
+
+cmCTestMultiProcessHandler::~cmCTestMultiProcessHandler()
+{
+}
+
+ // 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)
+ {
+ this->TestRunningMap[i->first] = false;
+ this->TestFinishMap[i->first] = false;
+ }
+ if(!this->CTest->GetShowOnly())
+ {
+ this->ReadCostData();
+ this->CreateTestCostList();
+ }
+}
+
+ // 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::RunTests()
+{
+ this->CheckResume();
+ if(!this->CheckCycles())
+ {
+ return;
+ }
+ this->TestHandler->SetMaxIndex(this->FindMaxIndex());
+ this->StartNextTests();
+ while(this->Tests.size() != 0)
+ {
+ if(this->StopTimePassed)
+ {
+ return;
+ }
+ this->CheckOutput();
+ this->StartNextTests();
+ }
+ // let all running tests finish
+ while(this->CheckOutput())
+ {
+ }
+ this->MarkFinished();
+ this->UpdateCostData();
+}
+
+//---------------------------------------------------------
+void cmCTestMultiProcessHandler::StartTestProcess(int test)
+{
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "test " << test << "\n");
+ 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);
+ testRun->SetIndex(test);
+ testRun->SetTestProperties(this->Properties[test]);
+
+ std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory();
+ cmSystemTools::ChangeDirectory(this->Properties[test]->Directory.c_str());
+
+ // Lock the resources we'll be using
+ this->LockResources(test);
+
+ if(testRun->StartTest(this->Total))
+ {
+ this->RunningTests.insert(testRun);
+ }
+ else if(testRun->IsStopTimePassed())
+ {
+ this->StopTimePassed = true;
+ delete testRun;
+ return;
+ }
+ 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;
+ }
+ cmSystemTools::ChangeDirectory(current_dir.c_str());
+}
+
+//---------------------------------------------------------
+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);
+ }
+}
+
+//---------------------------------------------------------
+void cmCTestMultiProcessHandler::UnlockResources(int index)
+{
+ for(std::set<std::string>::iterator i =
+ this->Properties[index]->LockedResources.begin();
+ i != this->Properties[index]->LockedResources.end(); ++i)
+ {
+ this->LockedResources.erase(*i);
+ }
+}
+
+//---------------------------------------------------------
+void cmCTestMultiProcessHandler::EraseTest(int test)
+{
+ this->Tests.erase(test);
+ this->SortedTests.erase(
+ 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)
+ {
+ processors = this->ParallelLevel;
+ }
+ return processors;
+}
+
+//---------------------------------------------------------
+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())
+ {
+ 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)
+ {
+ 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 = this->ParallelLevel - this->RunningCount;
+ if(numToStart == 0)
+ {
+ return;
+ }
+
+ 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])
+ {
+ continue;
+ }
+ size_t processors = GetProcessorsUsed(*test);
+ if(processors > numToStart)
+ {
+ return;
+ }
+ if(this->StartTest(*test))
+ {
+ if(this->StopTimePassed)
+ {
+ return;
+ }
+ numToStart -= processors;
+ }
+ if(numToStart == 0)
+ {
+ return;
+ }
+ }
+}
+
+//---------------------------------------------------------
+bool cmCTestMultiProcessHandler::CheckOutput()
+{
+ // no more output we are done
+ if(this->RunningTests.size() == 0)
+ {
+ 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)
+ {
+ cmCTestRunTest* p = *i;
+ if(!p->CheckOutput())
+ {
+ finished.push_back(p);
+ }
+ }
+ 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))
+ {
+ this->Passed->push_back(p->GetTestProperties()->Name);
+ }
+ else
+ {
+ this->Failed->push_back(p->GetTestProperties()->Name);
+ }
+ 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);
+ this->WriteCheckpoint(test);
+ 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);
+
+ PropertiesMap temp = this->Properties;
+
+ if(cmSystemTools::FileExists(fname.c_str()))
+ {
+ std::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;
+
+ 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)
+ {
+ // This test is not in memory. We just rewrite the entry
+ fout << name << " " << prev << " " << cost << "\n";
+ }
+ else
+ {
+ // Update with our new average cost
+ fout << name << " " << this->Properties[index]->PreviousRuns << " "
+ << this->Properties[index]->Cost << "\n";
+ temp.erase(index);
+ }
+ }
+ fin.close();
+ cmSystemTools::RemoveFile(fname.c_str());
+ }
+
+ // Add all tests not previously listed in the file
+ for(PropertiesMap::iterator i = temp.begin(); i != temp.end(); ++i)
+ {
+ fout << i->second->Name << " " << i->second->PreviousRuns << " "
+ << 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";
+ }
+ 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;
+ 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(), ' ');
+
+ // Probably an older version of the file, will be fixed next run
+ 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;
+
+ 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)
+ {
+ this->Properties[index]->Cost = cost;
+ }
+ }
+ // Next part of the file is the failed tests
+ while(std::getline(fin, line))
+ {
+ if(line != "")
+ {
+ this->LastTestsFailed.push_back(line);
+ }
+ }
+ fin.close();
+ }
+}
+
+//---------------------------------------------------------
+int cmCTestMultiProcessHandler::SearchByName(std::string name)
+{
+ int index = -1;
+
+ 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;
+ }
+ }
+
+ TestComparator comp(this);
+ std::stable_sort(SortedTests.begin(), SortedTests.end(), comp);
+}
+
+//---------------------------------------------------------
+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);
+ fout << index << "\n";
+ fout.close();
+}
+
+//---------------------------------------------------------
+void cmCTestMultiProcessHandler::MarkFinished()
+{
+ std::string fname = this->CTest->GetBinaryDir()
+ + "/Testing/Temporary/CTestCheckpoint.txt";
+ cmSystemTools::RemoveFile(fname.c_str());
+}
+
+//---------------------------------------------------------
+//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)
+ {
+ count++;
+ cmCTestTestHandler::cmCTestTestProperties& p = *it->second;
+
+ //push working dir
+ std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory();
+ cmSystemTools::ChangeDirectory(p.Directory.c_str());
+
+ cmCTestRunTest testRun(this->TestHandler);
+ testRun.SetIndex(p.Index);
+ testRun.SetTestProperties(&p);
+ 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 (this->TestHandler->MemCheck)
+ {
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, " Memory Check");
+ }
+ else
+ {
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, " Test");
+ }
+ cmOStringStream indexStr;
+ indexStr << " #" << p.Index << ":";
+ cmCTestLog(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);
+}
+
+void cmCTestMultiProcessHandler::PrintLabels()
+{
+ std::set<std::string> allLabels;
+ for (PropertiesMap::iterator it = this->Properties.begin();
+ 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);
+ }
+}
+
+//---------------------------------------------------------
+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::endl;
+
+ std::ifstream fin;
+ fin.open(fname.c_str());
+ std::string line;
+ while(std::getline(fin, line))
+ {
+ int index = atoi(line.c_str());
+ this->RemoveTest(index);
+ }
+ fin.close();
+ }
+ }
+ else if(cmSystemTools::FileExists(fname.c_str(), true))
+ {
+ cmSystemTools::RemoveFile(fname.c_str());
+ }
+}
+
+//---------------------------------------------------------
+void cmCTestMultiProcessHandler::RemoveTest(int index)
+{
+ this->EraseTest(index);
+ this->Properties.erase(index);
+ this->TestRunningMap[index] = false;
+ this->TestFinishMap[index] = true;
+ 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)
+ {
+ max = i->first;
+ }
+ }
+ return max;
+}
+
+//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
+ int root = it->first;
+ std::set<int> visited;
+ std::stack<int> s;
+ s.push(root);
+ 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");
+ return false;
+ }
+ else
+ {
+ s.push(*d);
+ }
+ }
+ }
+ }
+ }
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "Checking test dependency graph end" << std::endl);
+ return true;
+}
diff --git a/Source/CTest/cmCTestMultiProcessHandler.h b/Source/CTest/cmCTestMultiProcessHandler.h
new file mode 100644
index 000000000..1483440c6
--- /dev/null
+++ b/Source/CTest/cmCTestMultiProcessHandler.h
@@ -0,0 +1,116 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestMultiProcessHandler_h
+#define cmCTestMultiProcessHandler_h
+
+#include <cmStandardIncludes.h>
+#include <cmCTestTestHandler.h>
+#include <cmCTestRunTest.h>
+
+/** \class cmCTestMultiProcessHandler
+ * \brief run parallel ctest
+ *
+ * cmCTestMultiProcessHandler
+ */
+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*> {};
+
+ cmCTestMultiProcessHandler();
+ virtual ~cmCTestMultiProcessHandler();
+ // Set the tests
+ void SetTests(TestMap& tests, PropertiesMap& properties);
+ // Set the max number of tests that can be run at the same time.
+ void SetParallelLevel(size_t);
+ virtual void RunTests();
+ void PrintTestList();
+ void PrintLabels();
+
+ void SetPassFailVectors(std::vector<cmStdString>* passed,
+ std::vector<cmStdString>* failed)
+ {
+ this->Passed = passed;
+ this->Failed = failed;
+ }
+ void SetTestResults(std::vector<cmCTestTestHandler::cmCTestTestResult>* r)
+ { this->TestResults = r; }
+
+ void SetCTest(cmCTest* ctest) { this->CTest = ctest;}
+
+ void SetTestHandler(cmCTestTestHandler * handler)
+ { this->TestHandler = handler; }
+
+ cmCTestTestHandler * GetTestHandler()
+ { return this->TestHandler; }
+protected:
+ // Start the next test or tests as many as are allowed by
+ // ParallelLevel
+ void StartNextTests();
+ void StartTestProcess(int test);
+ bool StartTest(int test);
+ // Mark the checkpoint for the given test
+ void WriteCheckpoint(int index);
+
+ void UpdateCostData();
+ void ReadCostData();
+ // Return index of a test based on its name
+ int SearchByName(std::string name);
+
+ void CreateTestCostList();
+ // Removes the checkpoint file
+ void MarkFinished();
+ void EraseTest(int index);
+ // Return true if there are still tests running
+ // 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
+ void CheckResume();
+ //Check if there are any circular dependencies
+ bool CheckCycles();
+ int FindMaxIndex();
+ inline size_t GetProcessorsUsed(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
+ size_t Total;
+ //Number of tests that are complete
+ size_t Completed;
+ size_t RunningCount;
+ bool StopTimePassed;
+ //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::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;
+ cmCTest* CTest;
+};
+
+#endif
diff --git a/Source/CTest/cmCTestReadCustomFilesCommand.cxx b/Source/CTest/cmCTestReadCustomFilesCommand.cxx
new file mode 100644
index 000000000..5db01f98d
--- /dev/null
+++ b/Source/CTest/cmCTestReadCustomFilesCommand.cxx
@@ -0,0 +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.
+============================================================================*/
+#include "cmCTestReadCustomFilesCommand.h"
+
+#include "cmCTest.h"
+
+bool cmCTestReadCustomFilesCommand
+::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
+{
+ if (args.size() < 1)
+ {
+ 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);
+ }
+
+ return true;
+}
+
+
diff --git a/Source/CTest/cmCTestReadCustomFilesCommand.h b/Source/CTest/cmCTestReadCustomFilesCommand.h
new file mode 100644
index 000000000..f382b0f2d
--- /dev/null
+++ b/Source/CTest/cmCTestReadCustomFilesCommand.h
@@ -0,0 +1,75 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestReadCustomFilesCommand_h
+#define cmCTestReadCustomFilesCommand_h
+
+#include "cmCTestCommand.h"
+
+/** \class cmCTestReadCustomFiles
+ * \brief Run a ctest script
+ *
+ * cmLibrarysCommand defines a list of executable (i.e., test)
+ * programs to create.
+ */
+class cmCTestReadCustomFilesCommand : public cmCTestCommand
+{
+public:
+
+ cmCTestReadCustomFilesCommand() {}
+
+ /**
+ * This is a virtual constructor for the command.
+ */
+ virtual cmCommand* Clone()
+ {
+ 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);
+
+};
+
+
+#endif
diff --git a/Source/CTest/cmCTestRunScriptCommand.cxx b/Source/CTest/cmCTestRunScriptCommand.cxx
new file mode 100644
index 000000000..fe429bd9d
--- /dev/null
+++ b/Source/CTest/cmCTestRunScriptCommand.cxx
@@ -0,0 +1,65 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestRunScriptCommand.h"
+
+#include "cmCTestScriptHandler.h"
+
+bool cmCTestRunScriptCommand
+::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
+{
+ if(args.size() < 1 )
+ {
+ this->CTestScriptHandler->RunCurrentScript();
+ return true;
+ }
+
+ bool np = false;
+ unsigned int i = 0;
+ 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")
+ {
+ ++i;
+ if(i < args.size())
+ {
+ returnVariable = args[i];
+ }
+ }
+ }
+ for (i = start; i < args.size(); ++i)
+ {
+ if(args[i] == "RETURN_VALUE")
+ {
+ ++i;
+ }
+ else
+ {
+ int ret;
+ cmCTestScriptHandler::RunScript(this->CTest, args[i].c_str(), !np,
+ &ret);
+ cmOStringStream str;
+ str << ret;
+ this->Makefile->AddDefinition(returnVariable.c_str(), str.str().c_str());
+ }
+ }
+ return true;
+}
+
+
diff --git a/Source/CTest/cmCTestRunScriptCommand.h b/Source/CTest/cmCTestRunScriptCommand.h
new file mode 100644
index 000000000..6df69afa6
--- /dev/null
+++ b/Source/CTest/cmCTestRunScriptCommand.h
@@ -0,0 +1,80 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestRunScriptCommand_h
+#define cmCTestRunScriptCommand_h
+
+#include "cmCTestCommand.h"
+
+/** \class cmCTestRunScript
+ * \brief Run a ctest script
+ *
+ * cmLibrarysCommand defines a list of executable (i.e., test)
+ * programs to create.
+ */
+class cmCTestRunScriptCommand : public cmCTestCommand
+{
+public:
+
+ cmCTestRunScriptCommand() {}
+
+ /**
+ * This is a virtual constructor for the command.
+ */
+ virtual cmCommand* Clone()
+ {
+ 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);
+};
+
+
+#endif
diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx
new file mode 100644
index 000000000..c3de5dcf6
--- /dev/null
+++ b/Source/CTest/cmCTestRunTest.cxx
@@ -0,0 +1,735 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#include "cmCTestRunTest.h"
+#include "cmCTestMemCheckHandler.h"
+#include "cmCTest.h"
+#include "cmSystemTools.h"
+#include "cm_curl.h"
+
+#include <cm_zlib.h>
+#include <cmsys/Base64.h>
+
+cmCTestRunTest::cmCTestRunTest(cmCTestTestHandler* handler)
+{
+ this->CTest = handler->CTest;
+ this->TestHandler = handler;
+ this->TestProcess = 0;
+ this->TestResult.ExecutionTime =0;
+ this->TestResult.ReturnValue = 0;
+ this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
+ this->TestResult.TestCount = 0;
+ this->TestResult.Properties = 0;
+ this->ProcessOutput = "";
+ this->CompressedOutput = "";
+ this->CompressionRatio = 2;
+ this->StopTimePassed = false;
+}
+
+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))
+ {
+ int p = this->TestProcess->GetNextOutputLine(line, timeout);
+ if(p == cmsysProcess_Pipe_None)
+ {
+ // Process has terminated and all output read.
+ return false;
+ }
+ else if(p == cmsysProcess_Pipe_STDOUT ||
+ p == cmsysProcess_Pipe_STDERR)
+ {
+ // Store this line of output.
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ this->GetIndex() << ": " << line << std::endl);
+ this->ProcessOutput += line;
+ this->ProcessOutput += "\n";
+ }
+ 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()
+{
+ int ret;
+ z_stream strm;
+
+ 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
+ int outSize = static_cast<int>(
+ static_cast<double>(this->ProcessOutput.size()) * 1.001 + 13.0);
+ unsigned char* out = new unsigned char[outSize];
+
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ 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;
+ strm.avail_out = outSize;
+ 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);
+ delete[] out;
+ return;
+ }
+
+ (void)deflateEnd(&strm);
+
+ 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);
+
+ for(unsigned long 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);
+ }
+
+ delete [] encoded_buffer;
+ delete [] out;
+}
+
+//---------------------------------------------------------
+bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started)
+{
+ if ((!this->TestHandler->MemCheck &&
+ this->CTest->ShouldCompressTestOutput()) ||
+ (this->TestHandler->MemCheck &&
+ this->CTest->ShouldCompressMemCheckOutput()))
+ {
+ this->CompressOutput();
+ }
+
+ this->WriteLogOutputTop(completed, total);
+ std::string reason;
+ bool passed = true;
+ int res = started ? this->TestProcess->GetProcessStatus()
+ : cmsysProcess_State_Error;
+ int retVal = this->TestProcess->GetExitValue();
+ std::vector<std::pair<cmsys::RegularExpression,
+ std::string> >::iterator passIt;
+ bool forceFail = false;
+ bool outputTestErrorsToConsole = false;
+ if ( this->TestProperties->RequiredRegularExpressions.size() > 0 )
+ {
+ bool found = false;
+ 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.";
+ }
+ }
+ 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 += 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 = "Error regular expression found in output.";
+ reason += " Regex=[";
+ reason += passIt->second;
+ reason += "]";
+ forceFail = true;
+ }
+ }
+ }
+ if (res == cmsysProcess_State_Exited)
+ {
+ bool success =
+ !forceFail && (retVal == 0 ||
+ this->TestProperties->RequiredRegularExpressions.size());
+ if((success && !this->TestProperties->WillFail)
+ || (!success && this->TestProperties->WillFail))
+ {
+ this->TestResult.Status = cmCTestTestHandler::COMPLETED;
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, " Passed " );
+ }
+ else
+ {
+ this->TestResult.Status = cmCTestTestHandler::FAILED;
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Failed " << reason );
+ outputTestErrorsToConsole = this->CTest->OutputTestOutputOnTestFailure;
+ }
+ }
+ 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 )
+ {
+ outputTestErrorsToConsole = this->CTest->OutputTestOutputOnTestFailure;
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Exception: ");
+ switch(this->TestProcess->GetExitException())
+ {
+ case cmsysProcess_Exception_Fault:
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, "SegFault");
+ this->TestResult.Status = cmCTestTestHandler::SEGFAULT;
+ break;
+ case cmsysProcess_Exception_Illegal:
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, "Illegal");
+ this->TestResult.Status = cmCTestTestHandler::ILLEGAL;
+ break;
+ case cmsysProcess_Exception_Interrupt:
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, "Interrupt");
+ this->TestResult.Status = cmCTestTestHandler::INTERRUPT;
+ break;
+ case cmsysProcess_Exception_Numerical:
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, "Numerical");
+ this->TestResult.Status = cmCTestTestHandler::NUMERICAL;
+ break;
+ default:
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, "Other");
+ this->TestResult.Status = cmCTestTestHandler::OTHER_FAULT;
+ }
+ }
+ 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" );
+
+ if ( outputTestErrorsToConsole )
+ {
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, this->ProcessOutput << std::endl );
+ }
+
+ 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());
+
+
+ // 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));
+ }
+ this->TestResult.Reason = reason;
+ 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)
+ {
+ 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;
+ int seconds = static_cast<int>(ttime) % 60;
+ char buffer[100];
+ sprintf(buffer, "%02d:%02d:%02d", hours, minutes, seconds);
+ *this->TestHandler->LogFile
+ << "----------------------------------------------------------"
+ << std::endl;
+ if(this->TestResult.Reason.size())
+ {
+ *this->TestHandler->LogFile << reasonType << ":\n"
+ << this->TestResult.Reason << "\n";
+ }
+ else
+ {
+ if(pass)
+ {
+ *this->TestHandler->LogFile << "Test Passed.\n";
+ }
+ else
+ {
+ *this->TestHandler->LogFile << "Test Failed.\n";
+ }
+ }
+ *this->TestHandler->LogFile << "\"" << this->TestProperties->Name.c_str()
+ << "\" end time: " << this->CTest->CurrentTime() << std::endl
+ << "\"" << this->TestProperties->Name.c_str() << "\" time elapsed: "
+ << buffer << std::endl
+ << "----------------------------------------------------------"
+ << std::endl << std::endl;
+ }
+ // if the test actually started and ran
+ // record the results in TestResult
+ if(started)
+ {
+ bool compress = !this->TestHandler->MemCheck &&
+ 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";
+ this->TestResult.ExecutionTime = this->TestProcess->GetTotalTime();
+ this->MemCheckPostProcess();
+ this->ComputeWeightedCost();
+ }
+ // Always push the current TestResult onto the
+ // TestHandler vector
+ this->TestHandler->TestResults.push_back(this->TestResult);
+ delete this->TestProcess;
+ return passed;
+}
+
+//----------------------------------------------------------------------
+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)
+ {
+ this->TestProperties->Cost =
+ static_cast<float>(((prev * avgcost) + current) / (prev + 1.0));
+ this->TestProperties->PreviousRuns++;
+ }
+}
+
+//----------------------------------------------------------------------
+void cmCTestRunTest::MemCheckPostProcess()
+{
+ 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);
+ if(handler->MemoryTesterStyle == cmCTestMemCheckHandler::BOUNDS_CHECKER)
+ {
+ handler->PostProcessBoundsCheckerTest(this->TestResult);
+ }
+ else if(handler->MemoryTesterStyle == cmCTestMemCheckHandler::PURIFY)
+ {
+ handler->PostProcessPurifyTest(this->TestResult);
+ }
+}
+
+//----------------------------------------------------------------------
+// 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->ComputeArguments();
+ std::vector<std::string>& args = this->TestProperties->Args;
+ this->TestResult.Properties = this->TestProperties;
+ this->TestResult.ExecutionTime = 0;
+ this->TestResult.CompressOutput = false;
+ this->TestResult.ReturnValue = -1;
+ this->TestResult.CompletionStatus = "Failed to start";
+ 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();
+
+ if(args.size() >= 2 && args[1] == "NOT_AVAILABLE")
+ {
+ this->TestProcess = new cmProcess;
+ std::string msg;
+ if(this->CTest->GetConfigType().empty())
+ {
+ msg = "Test not available without configuration.";
+ msg += " (Missing \"-C <config>\"?)";
+ }
+ 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.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)
+ {
+ std::string file = *i;
+
+ 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->TestResult.Output = "Unable to find required file: " + file;
+ this->TestResult.FullCommandLine = "";
+ this->TestResult.CompletionStatus = "Not Run";
+ this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
+ return false;
+ }
+ }
+ // log and return if we did not find the executable
+ 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->TestResult.Output = "Unable to find executable: " + args[1];
+ this->TestResult.FullCommandLine = "";
+ this->TestResult.CompletionStatus = "Not Run";
+ this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
+ return false;
+ }
+ this->StartTime = this->CTest->CurrentTime();
+
+ double timeout = this->ResolveTimeout();
+
+ if(this->StopTimePassed)
+ {
+ return false;
+ }
+ return this->ForkProcess(timeout, this->TestProperties->ExplicitTimeout,
+ &this->TestProperties->Environment);
+}
+
+//----------------------------------------------------------------------
+void cmCTestRunTest::ComputeArguments()
+{
+ 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();
+ this->TestProperties->Args[1] = this->TestHandler->FindTheExecutable(
+ this->TestProperties->Args[1].c_str());
+ }
+ 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());
+
+ //Prepends memcheck args to our command string
+ this->TestHandler->GenerateTestCommand(this->Arguments);
+ 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)
+ {
+ testCommand += " \"";
+ testCommand += *j;
+ testCommand += "\"";
+ this->Arguments.push_back(*j);
+ }
+ this->TestResult.FullCommandLine = testCommand;
+
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl
+ << this->Index << ": "
+ << (this->TestHandler->MemCheck?"MemCheck":"Test")
+ << " command: " << testCommand
+ << 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);
+ // keep searching and replacing until none are left
+ 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);
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+double cmCTestRunTest::ResolveTimeout()
+{
+ double timeout = this->TestProperties->Timeout;
+
+ if(this->CTest->GetStopTime() == "")
+ {
+ return timeout;
+ }
+ struct tm* lctime;
+ time_t current_time = time(0);
+ lctime = gmtime(&current_time);
+ int gm_hour = lctime->tm_hour;
+ time_t gm_time = mktime(lctime);
+ lctime = localtime(&current_time);
+ int local_hour = lctime->tm_hour;
+
+ int tzone_offset = local_hour - gm_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)
+ {
+ // 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);
+
+ time_t stop_time = curl_getdate(buf, &current_time);
+ 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);
+ this->CTest->LastStopTimeout = stop_timeout;
+
+ 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);
+ this->StopTimePassed = true;
+ return 0;
+ }
+ return timeout == 0 ? stop_timeout :
+ (timeout < stop_timeout ? timeout : stop_timeout);
+}
+
+//----------------------------------------------------------------------
+bool cmCTestRunTest::ForkProcess(double testTimeOut, bool explicitTimeout,
+ std::vector<std::string>* environment)
+{
+ this->TestProcess = new cmProcess;
+ this->TestProcess->SetId(this->Index);
+ this->TestProcess->SetWorkingDirectory(
+ 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)
+ {
+ timeout = this->CTest->GetTimeOut();
+ }
+ if (testTimeOut > 0
+ && testTimeOut < this->CTest->GetRemainingTimeAllowed())
+ {
+ timeout = testTimeOut;
+ }
+ // always have at least 1 second if we got to here
+ if (timeout <= 0)
+ {
+ timeout = 1;
+ }
+ // handle timeout explicitly set to 0
+ if (testTimeOut == 0 && explicitTimeout)
+ {
+ timeout = 0;
+ }
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->Index << ": "
+ << "Test timeout computed to be: " << timeout << "\n");
+
+ this->TestProcess->SetTimeout(timeout);
+
+#ifdef CMAKE_BUILD_WITH_CMAKE
+ cmSystemTools::SaveRestoreEnvironment sre;
+#endif
+
+ if (environment && environment->size()>0)
+ {
+ 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 )
+ {
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, "MemCheck");
+ }
+ else
+ {
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, "Test");
+ }
+
+ cmOStringStream indexStr;
+ indexStr << " #" << this->Index << ":";
+ cmCTestLog(this->CTest, HANDLER_OUTPUT,
+ std::setw(3 + getNumWidth(this->TestHandler->GetMaxIndex()))
+ << indexStr.str().c_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->LogFile << this->TestProperties->Index << "/"
+ << this->TestHandler->TotalNumberOfTests
+ << " Test: " << this->TestProperties->Name.c_str() << 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
+ << "Directory: " << this->TestProperties->Directory << std::endl
+ << "\"" << this->TestProperties->Name.c_str() << "\" 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;
+
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, outname.c_str());
+ cmCTestLog(this->CTest, DEBUG, "Testing "
+ << this->TestProperties->Name.c_str() << " ... ");
+}
diff --git a/Source/CTest/cmCTestRunTest.h b/Source/CTest/cmCTestRunTest.h
new file mode 100644
index 000000000..89456d505
--- /dev/null
+++ b/Source/CTest/cmCTestRunTest.h
@@ -0,0 +1,112 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestRunTest_h
+#define cmCTestRunTest_h
+
+#include <cmStandardIncludes.h>
+#include <cmCTestTestHandler.h>
+#include <cmProcess.h>
+
+/** \class cmRunTest
+ * \brief represents a single test to be run
+ *
+ * cmRunTest contains the information related to running a single test
+ */
+class cmCTestRunTest
+{
+public:
+ cmCTestRunTest(cmCTestTestHandler* handler);
+ ~cmCTestRunTest();
+
+ void SetTestProperties(cmCTestTestHandler::cmCTestTestProperties * prop)
+ { this->TestProperties = prop; }
+
+ cmCTestTestHandler::cmCTestTestProperties * GetTestProperties()
+ { return this->TestProperties; }
+
+ void SetIndex(int i) { this->Index = i; }
+
+ int GetIndex() { return this->Index; }
+
+ std::string GetProcessOutput() { return this->ProcessOutput; }
+
+ bool IsStopTimePassed() { return this->StopTimePassed; }
+
+ cmCTestTestHandler::cmCTestTestResult GetTestResults()
+ { return this->TestResult; }
+
+ // Read and store output. Returns true if it must be called again.
+ bool CheckOutput();
+
+ // Compresses the output, writing to CompressedOutput
+ void CompressOutput();
+
+ //launch the test process, return whether it started correctly
+ bool StartTest(size_t total);
+ //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
+ void ComputeArguments();
+
+ void ComputeWeightedCost();
+private:
+ void DartProcessing();
+ void ExeNotFound(std::string exe);
+ // Figures out a final timeout which is min(STOP_TIME, NOW+TIMEOUT)
+ double ResolveTimeout();
+ 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
+ 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;
+
+ bool UsePrefixCommand;
+ std::string PrefixCommand;
+
+ std::string ProcessOutput;
+ std::string CompressedOutput;
+ double CompressionRatio;
+ //The test results
+ cmCTestTestHandler::cmCTestTestResult TestResult;
+ int Index;
+ std::string StartTime;
+ std::string ActualCommand;
+ std::vector<std::string> Arguments;
+ bool StopTimePassed;
+};
+
+inline int getNumWidth(size_t n)
+{
+ int numWidth = 1;
+ if(n >= 10)
+ {
+ numWidth = 2;
+ }
+ if(n >= 100)
+ {
+ numWidth = 3;
+ }
+ return numWidth;
+}
+
+#endif
+
diff --git a/Source/CTest/cmCTestSVN.cxx b/Source/CTest/cmCTestSVN.cxx
new file mode 100644
index 000000000..fab9a8cba
--- /dev/null
+++ b/Source/CTest/cmCTestSVN.cxx
@@ -0,0 +1,450 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestSVN.h"
+
+#include "cmCTest.h"
+#include "cmSystemTools.h"
+#include "cmXMLParser.h"
+#include "cmXMLSafe.h"
+
+#include <cmsys/RegularExpression.hxx>
+
+//----------------------------------------------------------------------------
+cmCTestSVN::cmCTestSVN(cmCTest* ct, std::ostream& log):
+ cmCTestGlobalVC(ct, log)
+{
+ this->PriorRev = this->Unknown;
+}
+
+//----------------------------------------------------------------------------
+cmCTestSVN::~cmCTestSVN()
+{
+}
+
+//----------------------------------------------------------------------------
+void cmCTestSVN::CleanupImpl()
+{
+ const char* svn = this->CommandLineTool.c_str();
+ const char* svn_cleanup[] = {svn, "cleanup", 0};
+ OutputLogger out(this->Log, "cleanup-out> ");
+ OutputLogger err(this->Log, "cleanup-err> ");
+ this->RunChild(svn_cleanup, &out, &err);
+}
+
+//----------------------------------------------------------------------------
+class cmCTestSVN::InfoParser: public cmCTestVC::LineParser
+{
+public:
+ InfoParser(cmCTestSVN* svn, const char* prefix, std::string& rev):
+ SVN(svn), Rev(rev)
+ {
+ this->SetLog(&svn->Log, prefix);
+ this->RegexRev.compile("^Revision: ([0-9]+)");
+ this->RegexURL.compile("^URL: +([^ ]+) *$");
+ this->RegexRoot.compile("^Repository Root: +([^ ]+) *$");
+ }
+private:
+ cmCTestSVN* SVN;
+ std::string& Rev;
+ cmsys::RegularExpression RegexRev;
+ cmsys::RegularExpression RegexURL;
+ cmsys::RegularExpression RegexRoot;
+ virtual bool ProcessLine()
+ {
+ if(this->RegexRev.find(this->Line))
+ {
+ this->Rev = this->RegexRev.match(1);
+ }
+ else if(this->RegexURL.find(this->Line))
+ {
+ this->SVN->URL = this->RegexURL.match(1);
+ }
+ else if(this->RegexRoot.find(this->Line))
+ {
+ this->SVN->Root = this->RegexRoot.match(1);
+ }
+ 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())
+ {
+ return p1 == p2;
+ }
+ else if(p1.size() > p2.size() && p1[p2.size()] == '/')
+ {
+ return strncmp(p1.c_str(), p2.c_str(), p2.size()) == 0;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+//----------------------------------------------------------------------------
+std::string cmCTestSVN::LoadInfo()
+{
+ // Run "svn info" to get the repository info from the work tree.
+ const char* svn = this->CommandLineTool.c_str();
+ const char* svn_info[] = {svn, "info", 0};
+ std::string rev;
+ InfoParser out(this, "info-out> ", rev);
+ OutputLogger err(this->Log, "info-err> ");
+ this->RunChild(svn_info, &out, &err);
+ return rev;
+}
+
+//----------------------------------------------------------------------------
+void cmCTestSVN::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->PriorRev.Rev = this->OldRevision;
+}
+
+//----------------------------------------------------------------------------
+void cmCTestSVN::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->Root = ""; // uncomment to test GuessBase
+ this->Log << "URL = " << this->URL << "\n";
+ this->Log << "Root = " << this->Root << "\n";
+
+ // Compute the base path the working tree has checked out under
+ // the repository root.
+ if(!this->Root.empty() && cmCTestSVNPathStarts(this->URL, this->Root))
+ {
+ this->Base = cmCTest::DecodeURL(this->URL.substr(this->Root.size()));
+ this->Base += "/";
+ }
+ this->Log << "Base = " << this->Base << "\n";
+}
+
+//----------------------------------------------------------------------------
+void cmCTestSVN::GuessBase(std::vector<Change> const& changes)
+{
+ // Subversion did not give us a good repository root so we need to
+ // guess the base path from the URL and the paths in a revision with
+ // changes under it.
+
+ // Consider each possible URL suffix from longest to shortest.
+ for(std::string::size_type slash = this->URL.find('/');
+ this->Base.empty() && slash != std::string::npos;
+ slash = this->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(this->URL.substr(slash));
+ for(std::vector<Change>::const_iterator ci = changes.begin();
+ this->Base.empty() && ci != changes.end(); ++ci)
+ {
+ if(cmCTestSVNPathStarts(ci->Path, base))
+ {
+ this->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
+ // tree must be a checkout of the entire repo and this will match
+ // the leading slash in all paths.
+ this->Base += "/";
+
+ this->Log << "Guessed Base = " << this->Base << "\n";
+}
+
+//----------------------------------------------------------------------------
+const char* cmCTestSVN::LocalPath(std::string const& path)
+{
+ if(path.size() > this->Base.size() &&
+ strncmp(path.c_str(), this->Base.c_str(), this->Base.size()) == 0)
+ {
+ // This path lies under the base, so return a relative path.
+ return path.c_str() + this->Base.size();
+ }
+ else
+ {
+ // This path does not lie under the base, so ignore it.
+ return 0;
+ }
+}
+
+//----------------------------------------------------------------------------
+class cmCTestSVN::UpdateParser: public cmCTestVC::LineParser
+{
+public:
+ 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))
+ {
+ this->DoPath(this->RegexUpdate.match(1)[0],
+ 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;
+ std::string dir = cmSystemTools::GetFilenamePath(path);
+ std::string name = cmSystemTools::GetFilenameName(path);
+ // See "svn help update".
+ 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':
+ this->SVN->Dirs[dir][name].Status = PathUpdated;
+ break;
+ case 'E': // TODO?
+ case '?': case ' ': default:
+ break;
+ }
+ }
+};
+
+//----------------------------------------------------------------------------
+bool cmCTestSVN::UpdateImpl()
+{
+ // Get user-specified update options.
+ std::string opts = this->CTest->GetCTestConfiguration("UpdateOptions");
+ if(opts.empty())
+ {
+ opts = this->CTest->GetCTestConfiguration("SVNUpdateOptions");
+ }
+ std::vector<cmStdString> args = cmSystemTools::ParseArguments(opts.c_str());
+
+ // Specify the start time for nightly testing.
+ if(this->CTest->GetTestModel() == cmCTest::NIGHTLY)
+ {
+ args.push_back("-r{" + this->GetNightlyTime() + " +0000}");
+ }
+
+ std::vector<char const*> svn_update;
+ svn_update.push_back(this->CommandLineTool.c_str());
+ svn_update.push_back("update");
+ svn_update.push_back("--non-interactive");
+ for(std::vector<cmStdString>::const_iterator ai = args.begin();
+ ai != args.end(); ++ai)
+ {
+ svn_update.push_back(ai->c_str());
+ }
+ svn_update.push_back(0);
+
+ UpdateParser out(this, "up-out> ");
+ OutputLogger err(this->Log, "up-err> ");
+ return this->RunUpdateCommand(&svn_update[0], &out, &err);
+}
+
+//----------------------------------------------------------------------------
+class cmCTestSVN::LogParser: public cmCTestVC::OutputLogger,
+ private cmXMLParser
+{
+public:
+ LogParser(cmCTestSVN* svn, const char* prefix):
+ OutputLogger(svn->Log, prefix), SVN(svn) { this->InitializeParser(); }
+ ~LogParser() { this->CleanupParser(); }
+private:
+ cmCTestSVN* SVN;
+
+ typedef cmCTestSVN::Revision Revision;
+ typedef cmCTestSVN::Change Change;
+ Revision Rev;
+ std::vector<Change> Changes;
+ Change CurChange;
+ std::vector<char> CData;
+
+ virtual bool ProcessChunk(const char* data, int length)
+ {
+ this->OutputLogger::ProcessChunk(data, length);
+ this->ParseChunk(data, length);
+ return true;
+ }
+
+ virtual void StartElement(const char* name, const char** atts)
+ {
+ this->CData.clear();
+ if(strcmp(name, "logentry") == 0)
+ {
+ this->Rev = Revision();
+ if(const char* rev = this->FindAttribute(atts, "revision"))
+ {
+ this->Rev.Rev = rev;
+ }
+ this->Changes.clear();
+ }
+ else if(strcmp(name, "path") == 0)
+ {
+ this->CurChange = Change();
+ 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);
+ }
+
+ virtual void EndElement(const char* name)
+ {
+ if(strcmp(name, "logentry") == 0)
+ {
+ this->SVN->DoRevision(this->Rev, this->Changes);
+ }
+ else if(strcmp(name, "path") == 0 && !this->CData.empty())
+ {
+ this->CurChange.Path.assign(&this->CData[0], this->CData.size());
+ this->Changes.push_back(this->CurChange);
+ }
+ else if(strcmp(name, "author") == 0 && !this->CData.empty())
+ {
+ this->Rev.Author.assign(&this->CData[0], this->CData.size());
+ }
+ else if(strcmp(name, "date") == 0 && !this->CData.empty())
+ {
+ this->Rev.Date.assign(&this->CData[0], this->CData.size());
+ }
+ else if(strcmp(name, "msg") == 0 && !this->CData.empty())
+ {
+ this->Rev.Log.assign(&this->CData[0], this->CData.size());
+ }
+ this->CData.clear();
+ }
+
+ virtual void ReportError(int, int, const char* msg)
+ {
+ this->SVN->Log << "Error parsing svn log xml: " << msg << "\n";
+ }
+};
+
+//----------------------------------------------------------------------------
+void cmCTestSVN::LoadRevisions()
+{
+ // We are interested in every revision included in the update.
+ std::string revs;
+ if(atoi(this->OldRevision.c_str()) < atoi(this->NewRevision.c_str()))
+ {
+ revs = "-r" + this->OldRevision + ":" + this->NewRevision;
+ }
+ else
+ {
+ revs = "-r" + this->NewRevision;
+ }
+
+ // Run "svn log" to get all global revisions of interest.
+ const char* svn = this->CommandLineTool.c_str();
+ const char* svn_log[] = {svn, "log", "--xml", "-v", revs.c_str(), 0};
+ {
+ LogParser out(this, "log-out> ");
+ OutputLogger err(this->Log, "log-err> ");
+ this->RunChild(svn_log, &out, &err);
+ }
+}
+
+//----------------------------------------------------------------------------
+void cmCTestSVN::DoRevision(Revision const& revision,
+ std::vector<Change> const& changes)
+{
+ // Guess the base checkout path from the changes if necessary.
+ if(this->Base.empty() && !changes.empty())
+ {
+ this->GuessBase(changes);
+ }
+ this->cmCTestGlobalVC::DoRevision(revision, changes);
+}
+
+//----------------------------------------------------------------------------
+class cmCTestSVN::StatusParser: public cmCTestVC::LineParser
+{
+public:
+ 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))
+ {
+ this->DoPath(this->RegexStatus.match(1)[0],
+ 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;
+ // See "svn help status".
+ switch(status)
+ {
+ case 'M': case '!': case 'A': case 'D': case 'R':
+ this->SVN->DoModification(PathModified, path);
+ break;
+ case 'C': case '~':
+ this->SVN->DoModification(PathConflicting, path);
+ break;
+ case 'X': case 'I': case '?': case ' ': default:
+ break;
+ }
+ }
+};
+
+//----------------------------------------------------------------------------
+void cmCTestSVN::LoadModifications()
+{
+ // Run "svn status" which reports local modifications.
+ const char* svn = this->CommandLineTool.c_str();
+ const char* svn_status[] = {svn, "status", "--non-interactive", 0};
+ StatusParser out(this, "status-out> ");
+ OutputLogger err(this->Log, "status-err> ");
+ this->RunChild(svn_status, &out, &err);
+}
+
+//----------------------------------------------------------------------------
+void cmCTestSVN::WriteXMLGlobal(std::ostream& xml)
+{
+ this->cmCTestGlobalVC::WriteXMLGlobal(xml);
+
+ xml << "\t<SVNPath>" << this->Base << "</SVNPath>\n";
+}
diff --git a/Source/CTest/cmCTestSVN.h b/Source/CTest/cmCTestSVN.h
new file mode 100644
index 000000000..f72c58fb0
--- /dev/null
+++ b/Source/CTest/cmCTestSVN.h
@@ -0,0 +1,68 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestSVN_h
+#define cmCTestSVN_h
+
+#include "cmCTestGlobalVC.h"
+
+/** \class cmCTestSVN
+ * \brief Interaction with subversion command-line tool
+ *
+ */
+class cmCTestSVN: public cmCTestGlobalVC
+{
+public:
+ /** Construct with a CTest instance and update log stream. */
+ cmCTestSVN(cmCTest* ctest, std::ostream& log);
+
+ virtual ~cmCTestSVN();
+
+private:
+ // Implement cmCTestVC internal API.
+ virtual void CleanupImpl();
+ virtual void NoteOldRevision();
+ virtual void NoteNewRevision();
+ virtual bool UpdateImpl();
+
+ // URL of repository directory checked out in the working tree.
+ std::string URL;
+
+ // URL of repository root directory.
+ std::string Root;
+
+ // Directory under repository root checked out in working tree.
+ std::string Base;
+
+ std::string LoadInfo();
+ void LoadModifications();
+ void LoadRevisions();
+
+ void GuessBase(std::vector<Change> const& changes);
+ const char* LocalPath(std::string const& path);
+
+ void DoRevision(Revision const& revision,
+ std::vector<Change> const& changes);
+
+ void WriteXMLGlobal(std::ostream& xml);
+
+ // Parsing helper classes.
+ class InfoParser;
+ class LogParser;
+ class StatusParser;
+ class UpdateParser;
+ friend class InfoParser;
+ friend class LogParser;
+ friend class StatusParser;
+ friend class UpdateParser;
+};
+
+#endif
diff --git a/Source/CTest/cmCTestScriptHandler.cxx b/Source/CTest/cmCTestScriptHandler.cxx
new file mode 100644
index 000000000..8643cb3f7
--- /dev/null
+++ b/Source/CTest/cmCTestScriptHandler.cxx
@@ -0,0 +1,1095 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#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 <stdlib.h>
+#include <time.h>
+#include <math.h>
+#include <float.h>
+
+// needed for sleep
+#if !defined(_WIN32)
+# include <unistd.h>
+#endif
+
+#include "cmCTestBuildCommand.h"
+#include "cmCTestConfigureCommand.h"
+#include "cmCTestCoverageCommand.h"
+#include "cmCTestEmptyBinaryDirectoryCommand.h"
+#include "cmCTestMemCheckCommand.h"
+#include "cmCTestReadCustomFilesCommand.h"
+#include "cmCTestRunScriptCommand.h"
+#include "cmCTestSleepCommand.h"
+#include "cmCTestStartCommand.h"
+#include "cmCTestSubmitCommand.h"
+#include "cmCTestTestCommand.h"
+#include "cmCTestUpdateCommand.h"
+#include "cmCTestUploadCommand.h"
+
+#define CTEST_INITIAL_CMAKE_OUTPUT_FILE_NAME "CTestInitialCMakeOutput.log"
+
+// used to keep elapsed time up to date
+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);
+
+ cmCTestScriptHandler* CTestScriptHandler;
+};
+
+// simply update the time and don't block anything
+bool cmCTestScriptFunctionBlocker::
+IsFunctionBlocked(const cmListFileFunction& , cmMakefile &,
+ cmExecutionStatus &)
+{
+ 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->ScriptStartTime = 0;
+
+ // the *60 is becuase the settings are in minutes but GetTime is seconds
+ this->MinimumInterval = 30*60;
+ this->ContinuousDuration = -1;
+}
+
+//----------------------------------------------------------------------
+void cmCTestScriptHandler::Initialize()
+{
+ this->Superclass::Initialize();
+ this->Backup = false;
+ this->EmptyBinDir = false;
+ this->EmptyBinDirOnce = false;
+
+ this->SourceDir = "";
+ this->BinaryDir = "";
+ this->BackupSourceDir = "";
+ this->BackupBinaryDir = "";
+ this->CTestRoot = "";
+ this->CVSCheckOut = "";
+ this->CTestCmd = "";
+ this->UpdateCmd = "";
+ this->CTestEnv = "";
+ this->InitialCache = "";
+ this->CMakeCmd = "";
+ this->CMOutFile = "";
+ this->ExtraUpdates.clear();
+
+ 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;
+ }
+}
+
+//----------------------------------------------------------------------
+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;
+ }
+}
+
+
+//----------------------------------------------------------------------
+// just adds an argument to the vector
+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 each script run it
+ res += this->RunConfigurationScript
+ (cmSystemTools::CollapseFullPath(this->ConfigurationScripts[i].c_str()),
+ this->ScriptProcessScope[i]);
+ }
+ if ( res )
+ {
+ return -1;
+ }
+ return 0;
+}
+
+void cmCTestScriptHandler::UpdateElapsedTime()
+{
+ if (this->LocalGenerator)
+ {
+ // 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);
+ }
+}
+
+//----------------------------------------------------------------------
+void cmCTestScriptHandler::AddCTestCommand(cmCTestCommand* command)
+{
+ cmCTestCommand* newCom = command;
+ newCom->CTest = this->CTest;
+ newCom->CTestScriptHandler = this;
+ this->CMake->AddCommand(newCom);
+}
+
+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("-SR");
+ argv.push_back(total_script_arg.c_str());
+
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "Executable for CTest is: " <<
+ this->CTest->GetCTestExecutable() << "\n");
+
+ // now pass through all the other arguments
+ std::vector<cmStdString> &initArgs =
+ this->CTest->GetInitialCommandLineArguments();
+ //*** need to make sure this does not have the current script ***
+ for(size_t i=1; i < initArgs.size(); ++i)
+ {
+ argv.push_back(initArgs[i].c_str());
+ }
+ argv.push_back(0);
+
+ // Now create process object
+ cmsysProcess* cp = cmsysProcess_New();
+ cmsysProcess_SetCommand(cp, &*argv.begin());
+ //cmsysProcess_SetWorkingDirectory(cp, dir);
+ cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
+ //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)
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, line << "\n");
+ }
+ else if(pipe == cmsysProcess_Pipe_STDOUT)
+ {
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, line << "\n");
+ }
+ pipe = cmSystemTools::WaitForLine(cp, line, 100, out, err);
+ }
+
+ // Properly handle output of the build command
+ cmsysProcess_WaitForExit(cp, 0);
+ int result = cmsysProcess_GetState(cp);
+ int retVal = 0;
+ bool failed = false;
+ if(result == cmsysProcess_State_Exited)
+ {
+ retVal = cmsysProcess_GetExitValue(cp);
+ }
+ 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);
+ failed = true;
+ }
+ else if(result == cmsysProcess_State_Expired)
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "\tThere was a timeout"
+ << std::endl);
+ failed = true;
+ }
+ else if(result == cmsysProcess_State_Error)
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "\tError executing ctest: "
+ << cmsysProcess_GetErrorString(cp) << std::endl);
+ failed = true;
+ }
+ cmsysProcess_Delete(cp);
+ if(failed)
+ {
+ cmOStringStream message;
+ message << "Error running command: [";
+ message << result << "] ";
+ 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;
+ }
+ return retVal;
+}
+
+void cmCTestScriptHandler::CreateCMake()
+{
+ // create a cmake instance to read the configuration script
+ if (this->CMake)
+ {
+ delete this->CMake;
+ delete this->GlobalGenerator;
+ delete this->LocalGenerator;
+ }
+ this->CMake = new cmake;
+ this->CMake->AddCMakePaths();
+ this->GlobalGenerator = new cmGlobalGenerator;
+ this->GlobalGenerator->SetCMakeInstance(this->CMake);
+
+ this->LocalGenerator = this->GlobalGenerator->CreateLocalGenerator();
+ this->Makefile = this->LocalGenerator->GetMakefile();
+
+ // Set CMAKE_CURRENT_SOURCE_DIR and CMAKE_CURRENT_BINARY_DIR.
+ // Also, some commands need Makefile->GetCurrentDirectory().
+ 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);
+}
+
+//----------------------------------------------------------------------
+// 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)
+{
+ // Reset the error flag so that the script is read in no matter what
+ cmSystemTools::ResetErrorOccuredFlag();
+
+ // if the argument has a , in it then it needs to be broken into the fist
+ // argument (which is the script) and the second argument which will be
+ // 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);
+ }
+ // make sure the file exists
+ 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_EXECUTABLE_NAME",
+ this->CTest->GetCTestExecutable());
+ this->Makefile->AddDefinition("CMAKE_EXECUTABLE_NAME",
+ this->CTest->GetCMakeExecutable());
+ this->Makefile->AddDefinition("CTEST_RUN_CURRENT_SCRIPT", true);
+ this->UpdateElapsedTime();
+
+ // add the script arg if defined
+ if (script_arg.size())
+ {
+ this->Makefile->AddDefinition("CTEST_SCRIPT_ARG", script_arg.c_str());
+ }
+
+ // always add a function blocker to update the elapsed time
+ 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");
+ return 2;
+ }
+
+ // Add definitions of variables passed in on the command line:
+ 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());
+ }
+
+ // 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);
+ // Reset the error flag so that it can run more than
+ // 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()
+{
+ // Temporary variables
+ const char* minInterval;
+ const char* contDuration;
+
+ 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");
+
+ 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.");
+ return 12;
+ }
+ this->ExtraUpdates.push_back(updateVal);
+ }
+ }
+
+ // in order to backup and restore we also must have the cvs root
+ 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())
+ {
+ std::string msg = "CTEST_SOURCE_DIRECTORY = ";
+ msg += (!this->SourceDir.empty()) ? this->SourceDir.c_str() : "(Null)";
+ msg += "\nCTEST_BINARY_DIRECTORY = ";
+ msg += (!this->BinaryDir.empty()) ? this->BinaryDir.c_str() : "(Null)";
+ msg += "\nCTEST_COMMAND = ";
+ msg += (!this->CTestCmd.empty()) ? this->CTestCmd.c_str() : "(Null)";
+ cmSystemTools::Error(
+ "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();
+ }
+
+ // the script may override the minimum continuous interval
+ if (minInterval)
+ {
+ this->MinimumInterval = 60 * atof(minInterval);
+ }
+ if (contDuration)
+ {
+ this->ContinuousDuration = 60.0 * atof(contDuration);
+ }
+
+
+ this->UpdateElapsedTime();
+
+ return 0;
+}
+
+//----------------------------------------------------------------------
+void cmCTestScriptHandler::SleepInSeconds(unsigned int secondsToWait)
+{
+#if defined(_WIN32)
+ Sleep(1000*secondsToWait);
+#else
+ sleep(secondsToWait);
+#endif
+}
+
+//----------------------------------------------------------------------
+// run a specific script
+int cmCTestScriptHandler::RunConfigurationScript
+(const std::string& total_script_arg, bool pscope)
+{
+#ifdef CMAKE_BUILD_WITH_CMAKE
+ cmSystemTools::SaveRestoreEnvironment sre;
+#endif
+
+ int result;
+
+ this->ScriptStartTime =
+ cmSystemTools::GetTime();
+
+ // read in the script
+ if (pscope)
+ {
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "Reading Script: " << total_script_arg << std::endl);
+ result = this->ReadInScript(total_script_arg);
+ }
+ else
+ {
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "Executing Script: " << total_script_arg << std::endl);
+ result = this->ExecuteScript(total_script_arg);
+ }
+ if (result)
+ {
+ return result;
+ }
+
+ // only run the curent script if we should
+ if (this->Makefile && this->Makefile->IsOn("CTEST_RUN_CURRENT_SCRIPT"))
+ {
+ return this->RunCurrentScript();
+ }
+ return result;
+}
+
+//----------------------------------------------------------------------
+int cmCTestScriptHandler::RunCurrentScript()
+{
+ int result;
+
+ // do not run twice
+ this->Makefile->AddDefinition("CTEST_RUN_CURRENT_SCRIPT", false);
+
+ // no popup widows
+ cmSystemTools::SetRunCommandHideConsole(true);
+
+ // extract the vars from the cache and store in ivars
+ result = this->ExtractVariables();
+ if (result)
+ {
+ return result;
+ }
+
+ // set any environment variables
+ if (!this->CTestEnv.empty())
+ {
+ std::vector<std::string> envArgs;
+ cmSystemTools::ExpandListArgument(this->CTestEnv.c_str(),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 )
+ {
+ this->UpdateElapsedTime();
+ double ending_time = cmSystemTools::GetTime() + this->ContinuousDuration;
+ if (this->EmptyBinDirOnce)
+ {
+ this->EmptyBinDir = true;
+ }
+ do
+ {
+ double interval = cmSystemTools::GetTime();
+ result = this->RunConfigurationDashboard();
+ interval = cmSystemTools::GetTime() - interval;
+ if (interval < this->MinimumInterval)
+ {
+ this->SleepInSeconds(
+ static_cast<unsigned int>(this->MinimumInterval - interval));
+ }
+ if (this->EmptyBinDirOnce)
+ {
+ this->EmptyBinDir = false;
+ }
+ }
+ while (cmSystemTools::GetTime() < ending_time);
+ }
+ // otherwise just run it once
+ else
+ {
+ result = this->RunConfigurationDashboard();
+ }
+
+ return result;
+}
+
+//----------------------------------------------------------------------
+int cmCTestScriptHandler::CheckOutSourceDir()
+{
+ std::string command;
+ std::string output;
+ int retVal;
+ bool res;
+
+ if (!cmSystemTools::FileExists(this->SourceDir.c_str()) &&
+ !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)
+ {
+ cmSystemTools::Error("Unable to perform cvs checkout:\n",
+ output.c_str());
+ return 6;
+ }
+ }
+ return 0;
+}
+
+//----------------------------------------------------------------------
+int cmCTestScriptHandler::BackupDirectories()
+{
+ int retVal;
+
+ // compute the backup names
+ this->BackupSourceDir = this->SourceDir;
+ this->BackupSourceDir += "_CMakeBackup";
+ this->BackupBinaryDir = this->BinaryDir;
+ this->BackupBinaryDir += "_CMakeBackup";
+
+ // backup the binary and src directories if requested
+ 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());
+ }
+
+ // first rename the src and binary directories
+ rename(this->SourceDir.c_str(), this->BackupSourceDir.c_str());
+ rename(this->BinaryDir.c_str(), this->BackupBinaryDir.c_str());
+
+ // we must now checkout the src dir
+ retVal = this->CheckOutSourceDir();
+ if (retVal)
+ {
+ this->RestoreBackupDirectories();
+ return retVal;
+ }
+ }
+
+ return 0;
+}
+
+
+//----------------------------------------------------------------------
+int cmCTestScriptHandler::PerformExtraUpdates()
+{
+ std::string command;
+ std::string output;
+ int retVal;
+ bool res;
+
+ // 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> cvsArgs;
+ cmSystemTools::ExpandListArgument(it->c_str(),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(),
+ 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());
+ return 0;
+ }
+ }
+ }
+ return 0;
+}
+
+
+//----------------------------------------------------------------------
+// run a single dashboard entry
+int cmCTestScriptHandler::RunConfigurationDashboard()
+{
+ // local variables
+ std::string command;
+ std::string output;
+ int retVal;
+ bool res;
+
+ // 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)
+ {
+ return retVal;
+ }
+
+ // backup the dirs if requested
+ retVal = this->BackupDirectories();
+ if (retVal)
+ {
+ return retVal;
+ }
+
+ // clear the binary directory?
+ if (this->EmptyBinDir)
+ {
+ if ( !cmCTestScriptHandler::EmptyBinaryDirectory(
+ this->BinaryDir.c_str()) )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "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()))
+ {
+ 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)
+ {
+ // make sure we have the required info
+ 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)
+ {
+ this->RestoreBackupDirectories();
+ return retVal;
+ }
+ }
+
+ // backup the dirs if requested
+ retVal = this->PerformExtraUpdates();
+ if (retVal)
+ {
+ return retVal;
+ }
+
+ // put the initial cache into the bin dir
+ if (!this->InitialCache.empty())
+ {
+ if (!this->WriteInitialCache(this->BinaryDir.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())
+ {
+ 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(),
+ this->HandlerVerbose, 0 /*this->TimeOut*/);
+
+ if ( !this->CMOutFile.empty() )
+ {
+ std::string cmakeOutputFile = this->CMOutFile;
+ 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);
+ cmGeneratedFileStream fout(cmakeOutputFile.c_str());
+ if ( fout )
+ {
+ fout << output.c_str();
+ }
+ else
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Cannot open CMake output file: "
+ << cmakeOutputFile.c_str() << " for writing" << std::endl);
+ }
+ }
+ 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);
+ // for each variable/argument do a putenv
+ 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*/);
+
+ // did something critical fail in ctest
+ if (!res || cmakeFailed ||
+ retVal & cmCTest::BUILD_ERRORS)
+ {
+ this->RestoreBackupDirectories();
+ if (cmakeFailed)
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Unable to run cmake:" << std::endl
+ << cmakeFailedOuput.c_str() << 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)
+ {
+ return 11;
+ }
+ 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());
+ }
+
+ 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)
+ {
+ return false;
+ }
+
+ if (text!=0)
+ {
+ 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
+ // the check below.
+ fout.flush();
+ fout.close();
+ 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 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());
+ }
+ // 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 InProcess, int* returnValue)
+{
+ cmCTestScriptHandler* sh = new cmCTestScriptHandler();
+ sh->SetCTestInstance(ctest);
+ sh->AddConfigurationScript(sname,InProcess);
+ int res = sh->ProcessHandler();
+ if(returnValue)
+ {
+ *returnValue = res;
+ }
+ delete sh;
+ return true;
+}
+
+bool cmCTestScriptHandler::EmptyBinaryDirectory(const char *sname)
+{
+ // try to avoid deleting root
+ if (!sname || strlen(sname) < 2)
+ {
+ return false;
+ }
+
+ // 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))
+ {
+ return false;
+ }
+ return true;
+}
+
+//-------------------------------------------------------------------------
+double cmCTestScriptHandler::GetRemainingTimeAllowed()
+{
+ if (!this->Makefile)
+ {
+ return 1.0e7;
+ }
+
+ const char *timelimitS
+ = this->Makefile->GetDefinition("CTEST_TIME_LIMIT");
+
+ if (!timelimitS)
+ {
+ return 1.0e7;
+ }
+
+ double timelimit = atof(timelimitS);
+
+ return timelimit - cmSystemTools::GetTime() + this->ScriptStartTime;
+}
diff --git a/Source/CTest/cmCTestScriptHandler.h b/Source/CTest/cmCTestScriptHandler.h
new file mode 100644
index 000000000..a7cf6ba65
--- /dev/null
+++ b/Source/CTest/cmCTestScriptHandler.h
@@ -0,0 +1,172 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#ifndef cmCTestScriptHandler_h
+#define cmCTestScriptHandler_h
+
+
+#include "cmCTestGenericHandler.h"
+#include "cmListFileCache.h"
+
+class cmMakefile;
+class cmLocalGenerator;
+class cmGlobalGenerator;
+class cmake;
+class cmCTestCommand;
+
+/** \class cmCTestScriptHandler
+ * \brief A class that handles ctest -S invocations
+ *
+ * CTest script is controlled using several variables that script has to
+ * specify and some optional ones. Required ones are:
+ * CTEST_SOURCE_DIRECTORY - Source directory of the project
+ * CTEST_BINARY_DIRECTORY - Binary directory of the project
+ * CTEST_COMMAND - Testing commands
+ *
+ * Optional variables are:
+ * CTEST_BACKUP_AND_RESTORE
+ * CTEST_CMAKE_COMMAND
+ * CTEST_CMAKE_OUTPUT_FILE_NAME
+ * CTEST_CONTINUOUS_DURATION
+ * CTEST_CONTINUOUS_MINIMUM_INTERVAL
+ * CTEST_CVS_CHECKOUT
+ * CTEST_CVS_COMMAND
+ * CTEST_UPDATE_COMMAND
+ * CTEST_DASHBOARD_ROOT
+ * CTEST_ENVIRONMENT
+ * CTEST_INITIAL_CACHE
+ * CTEST_START_WITH_EMPTY_BINARY_DIRECTORY
+ * CTEST_START_WITH_EMPTY_BINARY_DIRECTORY_ONCE
+ *
+ * In addition the following variables can be used. The number can be 1-10.
+ * CTEST_EXTRA_UPDATES_1
+ * CTEST_EXTRA_UPDATES_2
+ * ...
+ * CTEST_EXTRA_UPDATES_10
+ *
+ * CTest script can use the following arguments CTest provides:
+ * CTEST_SCRIPT_ARG
+ * CTEST_SCRIPT_DIRECTORY
+ * CTEST_SCRIPT_NAME
+ *
+ */
+class cmCTestScriptHandler : public cmCTestGenericHandler
+{
+public:
+ cmTypeMacro(cmCTestScriptHandler, cmCTestGenericHandler);
+
+ /**
+ * Add a script to run, and if is should run in the current process
+ */
+ void AddConfigurationScript(const char *, bool pscope);
+
+ /**
+ * Run a dashboard using a specified confiuration script
+ */
+ int ProcessHandler();
+
+ /*
+ * Run a script
+ */
+ static bool RunScript(cmCTest* ctest, const char *script, bool InProcess,
+ int* returnValue);
+ int RunCurrentScript();
+
+ /*
+ * Empty Binary Directory
+ */
+ static bool EmptyBinaryDirectory(const char *dir);
+
+ /*
+ * Write an initial CMakeCache.txt from the given contents.
+ */
+ static bool WriteInitialCache(const char* directory, const char* text);
+
+ /*
+ * Some elapsed time handling functions
+ */
+ static void SleepInSeconds(unsigned int secondsToWait);
+ void UpdateElapsedTime();
+
+ /**
+ * Return the time remaianing that the script is allowed to run in
+ * seconds if the user has set the variable CTEST_TIME_LIMIT. If that has
+ * not been set it returns 1e7 seconds
+ */
+ double GetRemainingTimeAllowed();
+
+ cmCTestScriptHandler();
+ ~cmCTestScriptHandler();
+
+ void Initialize();
+
+ void CreateCMake();
+ void GetCommandDocumentation(std::vector<cmDocumentationEntry>& v) const;
+ cmake* GetCMake() { return this->CMake;}
+private:
+ // reads in a script
+ int ReadInScript(const std::string& total_script_arg);
+ int ExecuteScript(const std::string& total_script_arg);
+
+ // extract vars from the script to set ivars
+ int ExtractVariables();
+
+ // perform a CVS checkout of the source dir
+ int CheckOutSourceDir();
+
+ // perform any extra cvs updates that were requested
+ int PerformExtraUpdates();
+
+ // backup and restore dirs
+ int BackupDirectories();
+ void RestoreBackupDirectories();
+
+ int RunConfigurationScript(const std::string& script, bool pscope);
+ int RunConfigurationDashboard();
+
+ // Add ctest command
+ void AddCTestCommand(cmCTestCommand* command);
+
+ std::vector<cmStdString> 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;
+
+ double MinimumInterval;
+ double ContinuousDuration;
+
+ // what time in seconds did this script start running
+ double ScriptStartTime;
+
+ cmMakefile *Makefile;
+ cmLocalGenerator *LocalGenerator;
+ cmGlobalGenerator *GlobalGenerator;
+ cmake *CMake;
+};
+
+#endif
diff --git a/Source/CTest/cmCTestSleepCommand.cxx b/Source/CTest/cmCTestSleepCommand.cxx
new file mode 100644
index 000000000..5b0697219
--- /dev/null
+++ b/Source/CTest/cmCTestSleepCommand.cxx
@@ -0,0 +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.
+============================================================================*/
+#include "cmCTestSleepCommand.h"
+
+#include "cmCTestScriptHandler.h"
+#include <stdlib.h> // required for atoi
+
+bool cmCTestSleepCommand
+::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
+{
+ if (args.size() < 1)
+ {
+ 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 )
+ {
+ 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 )
+ {
+ unsigned int duration = atoi(args[1].c_str());
+ unsigned int time2 = atoi(args[2].c_str());
+ 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;
+ }
+
+ this->SetError("called with incorrect number of arguments");
+ return false;
+}
+
+
diff --git a/Source/CTest/cmCTestSleepCommand.h b/Source/CTest/cmCTestSleepCommand.h
new file mode 100644
index 000000000..80fd6afb7
--- /dev/null
+++ b/Source/CTest/cmCTestSleepCommand.h
@@ -0,0 +1,77 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestSleepCommand_h
+#define cmCTestSleepCommand_h
+
+#include "cmCTestCommand.h"
+
+/** \class cmCTestSleep
+ * \brief Run a ctest script
+ *
+ * cmLibrarysCommand defines a list of executable (i.e., test)
+ * programs to create.
+ */
+class cmCTestSleepCommand : public cmCTestCommand
+{
+public:
+
+ cmCTestSleepCommand() {}
+
+ /**
+ * This is a virtual constructor for the command.
+ */
+ virtual cmCommand* Clone()
+ {
+ 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);
+
+};
+
+
+#endif
diff --git a/Source/CTest/cmCTestStartCommand.cxx b/Source/CTest/cmCTestStartCommand.cxx
new file mode 100644
index 000000000..228a17300
--- /dev/null
+++ b/Source/CTest/cmCTestStartCommand.cxx
@@ -0,0 +1,170 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestStartCommand.h"
+
+#include "cmCTest.h"
+#include "cmLocalGenerator.h"
+#include "cmGlobalGenerator.h"
+#include "cmCTestVC.h"
+#include "cmGeneratedFileStream.h"
+
+cmCTestStartCommand::cmCTestStartCommand()
+{
+ this->CreateNewTag = true;
+}
+
+bool cmCTestStartCommand
+::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
+{
+ if (args.size() < 1)
+ {
+ 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;
+
+ cnt++;
+
+ this->CTest->SetSpecificTrack(0);
+ if ( cnt < args.size() -1 )
+ {
+ if ( args[cnt] == "TRACK" )
+ {
+ cnt ++;
+ this->CTest->SetSpecificTrack(args[cnt].c_str());
+ cnt ++;
+ }
+ }
+
+ if (cnt < args.size())
+ {
+ if (args[cnt] == "APPEND")
+ {
+ cnt ++;
+ this->CreateNewTag = false;
+ }
+ }
+
+ if ( cnt < args.size() )
+ {
+ src_dir = args[cnt].c_str();
+ cnt ++;
+ if ( cnt < args.size() )
+ {
+ bld_dir = args[cnt].c_str();
+ }
+ }
+ if ( !src_dir )
+ {
+ src_dir = this->Makefile->GetDefinition("CTEST_SOURCE_DIRECTORY");
+ }
+ if ( !bld_dir)
+ {
+ bld_dir = this->Makefile->GetDefinition("CTEST_BINARY_DIRECTORY");
+ }
+ if ( !src_dir )
+ {
+ this->SetError("source directory not specified. Specify source directory "
+ "as an argument or set CTEST_SOURCE_DIRECTORY");
+ return false;
+ }
+ if ( !bld_dir)
+ {
+ this->SetError("binary directory not specified. Specify binary directory "
+ "as an argument or set CTEST_BINARY_DIRECTORY");
+ return false;
+ }
+
+ cmSystemTools::AddKeepPath(src_dir);
+ cmSystemTools::AddKeepPath(bld_dir);
+
+ this->CTest->EmptyCTestConfiguration();
+
+ 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);
+ const char* track = this->CTest->GetSpecificTrack();
+ if ( track )
+ {
+ cmCTestLog(this->CTest, HANDLER_OUTPUT,
+ " Track: " << track << std::endl);
+ }
+
+ // Log startup actions.
+ std::string startLogFile = binaryDir + "/Testing/Temporary/LastStart.log";
+ cmGeneratedFileStream ofs(startLogFile.c_str());
+ 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))
+ {
+ return false;
+ }
+ if(!cmSystemTools::FileIsDirectory(sourceDir.c_str()))
+ {
+ cmOStringStream 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());
+ return false;
+ }
+
+ this->Makefile->AddDefinition("CTEST_RUN_CURRENT_SCRIPT", "OFF");
+ this->CTest->SetSuppressUpdatingCTestConfiguration(true);
+ int model = this->CTest->GetTestModelFromString(smodel);
+ this->CTest->SetTestModel(model);
+ this->CTest->SetProduceXML(true);
+
+ return this->CTest->InitializeFromCommand(this);
+}
+
+//----------------------------------------------------------------------------
+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)
+ {
+ initialCheckoutCommand =
+ this->Makefile->GetDefinition("CTEST_CVS_CHECKOUT");
+ }
+ 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))
+ {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/Source/CTest/cmCTestStartCommand.h b/Source/CTest/cmCTestStartCommand.h
new file mode 100644
index 000000000..6be47703e
--- /dev/null
+++ b/Source/CTest/cmCTestStartCommand.h
@@ -0,0 +1,92 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestStartCommand_h
+#define cmCTestStartCommand_h
+
+#include "cmCTestCommand.h"
+
+/** \class cmCTestStart
+ * \brief Run a ctest script
+ *
+ * cmCTestStartCommand defineds the command to start the nightly testing.
+ */
+class cmCTestStartCommand : public cmCTestCommand
+{
+public:
+
+ cmCTestStartCommand();
+
+ /**
+ * This is a virtual constructor for the command.
+ */
+ virtual cmCommand* Clone()
+ {
+ cmCTestStartCommand* ni = new cmCTestStartCommand;
+ ni->CTest = this->CTest;
+ ni->CTestScriptHandler = this->CTestScriptHandler;
+ ni->CreateNewTag = this->CreateNewTag;
+ 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);
+
+ /**
+ * 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";}
+
+ /**
+ * Succinct documentation.
+ */
+ 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);
+
+private:
+ bool InitialCheckout(std::ostream& ofs, std::string const& sourceDir);
+ bool CreateNewTag;
+};
+
+
+#endif
diff --git a/Source/CTest/cmCTestSubmitCommand.cxx b/Source/CTest/cmCTestSubmitCommand.cxx
new file mode 100644
index 000000000..24974e3bc
--- /dev/null
+++ b/Source/CTest/cmCTestSubmitCommand.cxx
@@ -0,0 +1,255 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestSubmitCommand.h"
+
+#include "cmCTest.h"
+#include "cmCTestGenericHandler.h"
+#include "cmCTestSubmitHandler.h"
+
+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 )
+ {
+ ctestDropMethod = "http";
+ }
+
+ if ( !ctestDropSite )
+ {
+ // error: CDash requires CTEST_DROP_SITE definition
+ // in CTestConfig.cmake
+ }
+ 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");
+
+ // 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)
+ {
+ 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);
+ }
+ this->CTest->GenerateNotesFile(newNotesFiles);
+ }
+
+ 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))
+ {
+ this->SetError("problem submitting extra files.");
+ return 0;
+ }
+ }
+
+ cmCTestGenericHandler* handler
+ = this->CTest->GetInitializedHandler("submit");
+ if ( !handler )
+ {
+ this->SetError("internal CTest error. Cannot instantiate submit handler");
+ return 0;
+ }
+
+ // If no FILES or PARTS given, *all* PARTS are submitted by default.
+ //
+ // If FILES are given, but not PARTS, only the FILES are submitted
+ // and *no* PARTS are submitted.
+ // (This is why we select the empty "noParts" set in the
+ // FilesMentioned block below...)
+ //
+ // If PARTS are given, only the selected PARTS are submitted.
+ //
+ // If both PARTS and FILES are given, only the selected PARTS *and*
+ // all the given FILES are submitted.
+
+ // If given explicit FILES to submit, pass them to the handler.
+ //
+ 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
+ // without any of the default parts.
+ //
+ std::set<cmCTest::Part> noParts;
+ 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)
+ {
+ 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");
+
+ return handler;
+}
+
+
+//----------------------------------------------------------------------------
+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(arg == "FILES")
+ {
+ this->ArgumentDoing = ArgumentDoingFiles;
+ this->FilesMentioned = true;
+ return true;
+ }
+
+ if(arg == "RETRY_COUNT")
+ {
+ this->ArgumentDoing = ArgumentDoingRetryCount;
+ return true;
+ }
+
+ if(arg == "RETRY_DELAY")
+ {
+ this->ArgumentDoing = ArgumentDoingRetryDelay;
+ return true;
+ }
+
+ 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)
+ {
+ cmCTest::Part p = this->CTest->GetPartFromName(arg.c_str());
+ if(p != cmCTest::PartCount)
+ {
+ this->Parts.insert(p);
+ }
+ else
+ {
+ cmOStringStream 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.";
+ this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str());
+ this->ArgumentDoing = ArgumentDoingError;
+ }
+ return true;
+ }
+
+ if(this->ArgumentDoing == ArgumentDoingRetryCount)
+ {
+ this->RetryCount = arg;
+ return true;
+ }
+
+ if(this->ArgumentDoing == ArgumentDoingRetryDelay)
+ {
+ this->RetryDelay = arg;
+ return true;
+ }
+
+ // Look for other arguments.
+ return this->Superclass::CheckArgumentValue(arg);
+}
diff --git a/Source/CTest/cmCTestSubmitCommand.h b/Source/CTest/cmCTestSubmitCommand.h
new file mode 100644
index 000000000..53ee8754e
--- /dev/null
+++ b/Source/CTest/cmCTestSubmitCommand.h
@@ -0,0 +1,118 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestSubmitCommand_h
+#define cmCTestSubmitCommand_h
+
+#include "cmCTestHandlerCommand.h"
+#include "cmCTest.h"
+
+/** \class cmCTestSubmit
+ * \brief Run a ctest script
+ *
+ * cmCTestSubmitCommand defineds the command to submit the test results for
+ * the project.
+ */
+class cmCTestSubmitCommand : public cmCTestHandlerCommand
+{
+public:
+
+ cmCTestSubmitCommand()
+ {
+ this->PartsMentioned = false;
+ this->FilesMentioned = false;
+ this->InternalTest = false;
+ this->RetryCount = "";
+ this->RetryDelay = "";
+ }
+
+ /**
+ * This is a virtual constructor for the command.
+ */
+ virtual cmCommand* Clone()
+ {
+ 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";}
+
+ /**
+ * Succinct documentation.
+ */
+ virtual const char* GetTerseDocumentation() const
+ {
+ return "Submit results to a dashboard server.";
+ }
+
+ /**
+ * More documentation.
+ */
+ 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";
+ }
+
+ cmTypeMacro(cmCTestSubmitCommand, cmCTestHandlerCommand);
+
+protected:
+ cmCTestGenericHandler* InitializeHandler();
+
+ virtual bool CheckArgumentKeyword(std::string const& arg);
+ virtual bool CheckArgumentValue(std::string const& arg);
+
+ enum
+ {
+ ArgumentDoingParts = Superclass::ArgumentDoingLast1,
+ ArgumentDoingFiles,
+ ArgumentDoingRetryDelay,
+ ArgumentDoingRetryCount,
+ ArgumentDoingLast2
+ };
+
+ bool PartsMentioned;
+ std::set<cmCTest::Part> Parts;
+ bool FilesMentioned;
+ bool InternalTest;
+ cmCTest::SetOfStrings Files;
+ std::string RetryCount;
+ std::string RetryDelay;
+};
+
+
+#endif
diff --git a/Source/CTest/cmCTestSubmitHandler.cxx b/Source/CTest/cmCTestSubmitHandler.cxx
new file mode 100644
index 000000000..142bb4613
--- /dev/null
+++ b/Source/CTest/cmCTestSubmitHandler.cxx
@@ -0,0 +1,1508 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestSubmitHandler.h"
+
+#include "cmSystemTools.h"
+#include "cmVersion.h"
+#include "cmGeneratedFileStream.h"
+#include "cmCTest.h"
+#include "cmXMLParser.h"
+
+#include <cmsys/Process.h>
+#include <cmsys/Base64.h>
+
+// For XML-RPC submission
+#include "cm_xmlrpc.h"
+
+// For curl submission
+#include "cm_curl.h"
+
+#include <sys/stat.h>
+
+#define SUBMIT_TIMEOUT_IN_SECONDS_DEFAULT 120
+
+typedef std::vector<char> cmCTestSubmitHandlerVectorOfChar;
+
+//----------------------------------------------------------------------------
+class cmCTestSubmitHandler::ResponseParser: public cmXMLParser
+{
+public:
+ ResponseParser() { this->Status = STATUS_OK; }
+ ~ResponseParser() {}
+
+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())
+ {
+ val.assign(&this->CurrentValue[0], this->CurrentValue.size());
+ }
+ return val;
+ }
+
+ virtual void StartElement(const char* name, const char** atts)
+ {
+ 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);
+ }
+
+ virtual void EndElement(const char* name)
+ {
+ if(strcmp(name, "status") == 0)
+ {
+ std::string status = cmSystemTools::UpperCase(this->GetCurrentValue());
+ if(status == "OK" || status == "SUCCESS")
+ {
+ this->Status = STATUS_OK;
+ }
+ else if(status == "WARNING")
+ {
+ this->Status = STATUS_WARNING;
+ }
+ else
+ {
+ this->Status = STATUS_ERROR;
+ }
+ }
+ else if(strcmp(name, "filename") == 0)
+ {
+ this->Filename = this->GetCurrentValue();
+ }
+ else if(strcmp(name, "md5") == 0)
+ {
+ this->MD5 = this->GetCurrentValue();
+ }
+ else if(strcmp(name, "message") == 0)
+ {
+ this->Message = this->GetCurrentValue();
+ }
+ }
+};
+
+
+static size_t
+cmCTestSubmitHandlerWriteMemoryCallback(void *ptr, size_t size, size_t nmemb,
+ void *data)
+{
+ register int realsize = (int)(size * nmemb);
+
+ cmCTestSubmitHandlerVectorOfChar *vec
+ = static_cast<cmCTestSubmitHandlerVectorOfChar*>(data);
+ const char* chPtr = static_cast<char*>(ptr);
+ vec->insert(vec->end(), chPtr, chPtr + realsize);
+
+ return realsize;
+}
+
+static size_t
+cmCTestSubmitHandlerCurlDebugCallback(CURL *, curl_infotype, char *chPtr,
+ size_t size, void *data)
+{
+ cmCTestSubmitHandlerVectorOfChar *vec
+ = static_cast<cmCTestSubmitHandlerVectorOfChar*>(data);
+ vec->insert(vec->end(), chPtr, chPtr + size);
+
+ return size;
+}
+
+//----------------------------------------------------------------------------
+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))
+ {
+ this->SubmitPart[p] = true;
+ }
+ this->CDash = false;
+ this->HasWarnings = false;
+ this->HasErrors = false;
+ this->Superclass::Initialize();
+ this->HTTPProxy = "";
+ this->HTTPProxyType = 0;
+ this->HTTPProxyAuth = "";
+ this->FTPProxy = "";
+ this->FTPProxyType = 0;
+ this->LogFile = 0;
+ this->Files.clear();
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestSubmitHandler::SubmitUsingFTP(const cmStdString& localprefix,
+ const std::set<cmStdString>& files,
+ const cmStdString& remoteprefix,
+ const cmStdString& url)
+{
+ CURL *curl;
+ CURLcode res;
+ FILE* ftpfile;
+ 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 )
+ {
+ /* get a curl handle */
+ curl = curl_easy_init();
+ if(curl)
+ {
+ // Using proxy
+ 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);
+ }
+ }
+
+ // enable uploading
+ ::curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
+
+ // if there is little to no activity for too long stop submitting
+ ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
+ ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME,
+ SUBMIT_TIMEOUT_IN_SECONDS_DEFAULT);
+
+ ::curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
+
+ cmStdString 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);
+ ::curl_easy_cleanup(curl);
+ ::curl_global_cleanup();
+ return false;
+ }
+
+ 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);
+
+ ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
+
+ // specify target
+ ::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));
+
+ // and give curl the buffer for errors
+ ::curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error_buffer);
+
+ // specify handler for output
+ ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
+ cmCTestSubmitHandlerWriteMemoryCallback);
+ ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION,
+ cmCTestSubmitHandlerCurlDebugCallback);
+
+ /* we pass our 'chunk' struct to the callback function */
+ cmCTestSubmitHandlerVectorOfChar chunk;
+ cmCTestSubmitHandlerVectorOfChar chunkDebug;
+ ::curl_easy_setopt(curl, CURLOPT_FILE, (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);
+ }
+
+ fclose(ftpfile);
+ if ( res )
+ {
+ 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()
+ << std::endl
+ << " Error message was: "
+ << error_buffer << std::endl
+ << " Curl output was: ";
+ // avoid dereference of empty vector
+ if(chunk.size())
+ {
+ *this->LogFile << cmCTestLogWrite(&*chunk.begin(), chunk.size());
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "CURL output: ["
+ << 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);
+ }
+ }
+ ::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)
+{
+ CURL *curl;
+ CURLcode res;
+ FILE* ftpfile;
+ char error_buffer[1024];
+
+ /* 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::vector<std::string> args;
+ cmSystemTools::ExpandListArgument(curlopt.c_str(), 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")
+ {
+ verifyPeerOff = true;
+ }
+ if(*i == "CURLOPT_SSL_VERIFYHOST_OFF")
+ {
+ verifyHostOff = true;
+ }
+ }
+ cmStdString::size_type kk;
+ cmCTest::SetOfStrings::const_iterator 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");
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
+ }
+ if(verifyHostOff)
+ {
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ " Set CURLOPT_SSL_VERIFYHOST to off\n");
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
+ }
+
+ // Using proxy
+ if ( this->HTTPProxyType > 0 )
+ {
+ curl_easy_setopt(curl, CURLOPT_PROXY, this->HTTPProxy.c_str());
+ switch (this->HTTPProxyType)
+ {
+ case 2:
+ curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
+ break;
+ case 3:
+ curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
+ break;
+ default:
+ curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
+ if (this->HTTPProxyAuth.size() > 0)
+ {
+ curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD,
+ this->HTTPProxyAuth.c_str());
+ }
+ }
+ }
+ if(this->CTest->ShouldUseHTTP10())
+ {
+ curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+ }
+ // enable HTTP ERROR parsing
+ curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
+ /* enable uploading */
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
+
+ // if there is little to no activity for too long stop submitting
+ ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
+ ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME,
+ SUBMIT_TIMEOUT_IN_SECONDS_DEFAULT);
+
+ /* HTTP PUT please */
+ ::curl_easy_setopt(curl, CURLOPT_PUT, 1);
+ ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
+
+ cmStdString local_file = *file;
+ if ( !cmSystemTools::FileExists(local_file.c_str()) )
+ {
+ local_file = localprefix + "/" + *file;
+ }
+ cmStdString remote_file
+ = remoteprefix + cmSystemTools::GetFilenameName(*file);
+
+ *this->LogFile << "\tUpload file: " << local_file.c_str() << " to "
+ << remote_file.c_str() << std::endl;
+
+ cmStdString 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);
+ }
+ }
+ cmStdString upload_as
+ = url + ((url.find("?",0) == cmStdString::npos) ? "?" : "&")
+ + "FileName=" + ofile;
+
+ upload_as += "&MD5=";
+
+ if(cmSystemTools::IsOn(this->GetOption("InternalTest")))
+ {
+ upload_as += "bad_md5sum";
+ }
+ else
+ {
+ char md5[33];
+ cmSystemTools::ComputeFileMD5(local_file.c_str(), 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);
+ ::curl_easy_cleanup(curl);
+ ::curl_global_cleanup();
+ return false;
+ }
+
+ 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);
+
+ // specify target
+ ::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));
+
+ // and give curl the buffer for errors
+ ::curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error_buffer);
+
+ // specify handler for output
+ ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
+ cmCTestSubmitHandlerWriteMemoryCallback);
+ ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION,
+ cmCTestSubmitHandlerCurlDebugCallback);
+
+ /* we pass our 'chunk' struct to the callback function */
+ cmCTestSubmitHandlerVectorOfChar chunk;
+ cmCTestSubmitHandlerVectorOfChar chunkDebug;
+ ::curl_easy_setopt(curl, CURLOPT_FILE, (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);
+ this->ParseResponse(chunk);
+ }
+ if ( chunkDebug.size() > 0 )
+ {
+ cmCTestLog(this->CTest, DEBUG, "CURL debug output: ["
+ << cmCTestLogWrite(&*chunkDebug.begin(), chunkDebug.size()) << "]"
+ << std::endl);
+ }
+
+ // 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");
+
+ double stop = cmSystemTools::GetTime() + delay;
+ while(cmSystemTools::GetTime() < stop)
+ {
+ cmSystemTools::Delay(100);
+ }
+
+ cmCTestLog(this->CTest, HANDLER_OUTPUT,
+ " Retry submission: Attempt " << (i + 1) << " of "
+ << count << std::endl);
+
+ ::fclose(ftpfile);
+ ftpfile = ::fopen(local_file.c_str(), "rb");
+ ::curl_easy_setopt(curl, CURLOPT_INFILE, ftpfile);
+
+ chunk.clear();
+ chunkDebug.clear();
+ this->HasErrors = false;
+
+ res = ::curl_easy_perform(curl);
+
+ if ( chunk.size() > 0 )
+ {
+ cmCTestLog(this->CTest, DEBUG, "CURL output: ["
+ << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "]"
+ << std::endl);
+ this->ParseResponse(chunk);
+ }
+
+ if(res == CURLE_OK && !this->HasErrors)
+ {
+ break;
+ }
+ }
+ }
+
+ fclose(ftpfile);
+ if ( res )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ " Error when uploading file: "
+ << local_file.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()
+ << std::endl
+ << " Error message was: " << error_buffer
+ << std::endl;
+ // avoid deref of begin for zero size array
+ if(chunk.size())
+ {
+ *this->LogFile << " Curl output was: "
+ << cmCTestLogWrite(&*chunk.begin(), chunk.size())
+ << std::endl;
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "CURL output: ["
+ << cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "]"
+ << std::endl);
+ }
+ ::curl_easy_cleanup(curl);
+ ::curl_global_cleanup();
+ return false;
+ }
+ // always cleanup
+ ::curl_easy_cleanup(curl);
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, " Uploaded: " + local_file
+ << std::endl);
+ }
+ }
+ ::curl_global_cleanup();
+ return true;
+}
+
+//----------------------------------------------------------------------------
+void cmCTestSubmitHandler
+::ParseResponse(cmCTestSubmitHandlerVectorOfChar chunk)
+{
+ std::string output = "";
+ output.append(chunk.begin(), chunk.end());
+
+ if(output.find("<cdash") != output.npos)
+ {
+ ResponseParser parser;
+ parser.Parse(output.c_str());
+
+ if(parser.Status != ResponseParser::STATUS_OK)
+ {
+ this->HasErrors = true;
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, " Submission failed: " <<
+ parser.Message << std::endl);
+ return;
+ }
+ }
+ output = cmSystemTools::UpperCase(output);
+ if(output.find("WARNING") != std::string::npos)
+ {
+ this->HasWarnings = true;
+ }
+ if(output.find("ERROR") != std::string::npos)
+ {
+ this->HasErrors = true;
+ }
+
+ if(this->HasWarnings || this->HasErrors)
+ {
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, " Server Response:\n" <<
+ cmCTestLogWrite(&*chunk.begin(), chunk.size()) << "\n");
+ }
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestSubmitHandler::TriggerUsingHTTP(
+ const std::set<cmStdString>& files,
+ const cmStdString& remoteprefix,
+ const cmStdString& url)
+{
+ 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 )
+ {
+ /* get a curl handle */
+ curl = curl_easy_init();
+ if(curl)
+ {
+ // Using proxy
+ if ( this->HTTPProxyType > 0 )
+ {
+ curl_easy_setopt(curl, CURLOPT_PROXY, this->HTTPProxy.c_str());
+ switch (this->HTTPProxyType)
+ {
+ case 2:
+ curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
+ break;
+ case 3:
+ curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
+ break;
+ default:
+ curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
+ if (this->HTTPProxyAuth.size() > 0)
+ {
+ curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD,
+ this->HTTPProxyAuth.c_str());
+ }
+ }
+ }
+
+ ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
+
+ // and give curl the buffer for errors
+ ::curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error_buffer);
+
+ // specify handler for output
+ ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
+ cmCTestSubmitHandlerWriteMemoryCallback);
+ ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION,
+ cmCTestSubmitHandlerCurlDebugCallback);
+
+ /* we pass our 'chunk' struct to the callback function */
+ cmCTestSubmitHandlerVectorOfChar chunk;
+ cmCTestSubmitHandlerVectorOfChar chunkDebug;
+ ::curl_easy_setopt(curl, CURLOPT_FILE, (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)
+ {
+ 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);
+ }
+ }
+ 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);
+ 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);
+ *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;
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "CURL output: ["
+ << 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);
+ }
+
+ // always cleanup
+ ::curl_easy_cleanup(curl);
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl);
+ }
+ }
+ ::curl_global_cleanup();
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, " Dart server triggered..."
+ << std::endl);
+ return true;
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestSubmitHandler::SubmitUsingSCP(
+ const cmStdString& scp_command,
+ const cmStdString& localprefix,
+ const std::set<cmStdString>& files,
+ const cmStdString& remoteprefix,
+ const cmStdString& url)
+{
+ if ( !scp_command.size() || !localprefix.size() ||
+ !files.size() || !remoteprefix.size() || !url.size() )
+ {
+ return 0;
+ }
+ 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);
+
+ cmsysProcess* cp = cmsysProcess_New();
+ cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
+ //cmsysProcess_SetTimeout(cp, timeout);
+
+ int problems = 0;
+
+ cmCTest::SetOfStrings::const_iterator file;
+ for ( file = files.begin(); file != files.end(); ++file )
+ {
+ int retVal;
+
+ std::string lfname = localprefix;
+ cmSystemTools::ConvertToUnixSlashes(lfname);
+ lfname += "/" + *file;
+ lfname = cmSystemTools::ConvertToOutputPath(lfname.c_str());
+ 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);
+ *this->LogFile << "Execute \"" << argv[0] << "\" \"" << argv[1] << "\" \""
+ << argv[2] << "\"" << std::endl;
+
+ cmsysProcess_SetCommand(cp, &*argv.begin());
+ cmsysProcess_Execute(cp);
+ char* data;
+ int length;
+
+ while(cmsysProcess_WaitForData(cp, &data, &length, 0))
+ {
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ cmCTestLogWrite(data, length));
+ }
+
+ cmsysProcess_WaitForExit(cp, 0);
+
+ int result = cmsysProcess_GetState(cp);
+
+ if(result == cmsysProcess_State_Exited)
+ {
+ retVal = cmsysProcess_GetExitValue(cp);
+ if ( retVal != 0 )
+ {
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "\tSCP returned: "
+ << retVal << std::endl);
+ *this->LogFile << "\tSCP returned: " << retVal << std::endl;
+ problems ++;
+ }
+ }
+ else if(result == cmsysProcess_State_Exception)
+ {
+ retVal = cmsysProcess_GetExitException(cp);
+ 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)
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "\tThere was a timeout"
+ << std::endl);
+ *this->LogFile << "\tThere was a timeout" << std::endl;
+ problems ++;
+ }
+ else if(result == cmsysProcess_State_Error)
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "\tError executing SCP: "
+ << cmsysProcess_GetErrorString(cp) << std::endl);
+ *this->LogFile << "\tError executing SCP: "
+ << cmsysProcess_GetErrorString(cp) << std::endl;
+ problems ++;
+ }
+ }
+ cmsysProcess_Delete(cp);
+ if ( problems )
+ {
+ return false;
+ }
+ return true;
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestSubmitHandler::SubmitUsingCP(
+ const cmStdString& localprefix,
+ const std::set<cmStdString>& files,
+ const cmStdString& remoteprefix,
+ const cmStdString& destination)
+{
+ if ( !localprefix.size() ||
+ !files.size() || !remoteprefix.size() || !destination.size() )
+ {
+ 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;
+ }
+ cmCTest::SetOfStrings::const_iterator file;
+ bool problems = false;
+ 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);
+ }
+ std::string tagDoneFile = destination + "/" + remoteprefix + "DONE";
+ cmSystemTools::Touch(tagDoneFile.c_str(), true);
+ if ( problems )
+ {
+ return false;
+ }
+ return true;
+}
+
+
+//----------------------------------------------------------------------------
+#if defined(CTEST_USE_XMLRPC)
+bool cmCTestSubmitHandler::SubmitUsingXMLRPC(const cmStdString& localprefix,
+ const std::set<cmStdString>& files,
+ const cmStdString& remoteprefix,
+ const cmStdString& 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/";
+
+ /* Start up our XML-RPC client library. */
+ xmlrpc_client_init(XMLRPC_CLIENT_NO_FLAGS, ctestString, ctestVersion);
+
+ /* Initialize our error-handling environment. */
+ 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);
+ cmCTest::SetOfStrings::const_iterator file;
+ for ( file = files.begin(); file != files.end(); ++file )
+ {
+ xmlrpc_value *result;
+
+ cmStdString 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);
+ struct stat st;
+ if ( ::stat(local_file.c_str(), &st) )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, " Cannot find file: "
+ << local_file.c_str() << 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);
+ 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);
+ return false;
+ }
+
+ 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);
+ 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 );
+
+ delete [] fileBuffer;
+
+ if ( env.fault_occurred )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, " Submission problem: "
+ << 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);
+
+ /* Shutdown our XML-RPC client library. */
+ xmlrpc_client_cleanup();
+ return true;
+}
+#else
+bool cmCTestSubmitHandler::SubmitUsingXMLRPC(cmStdString const&,
+ std::set<cmStdString> const&,
+ cmStdString const&,
+ cmStdString const&)
+{
+ return false;
+}
+#endif
+
+//----------------------------------------------------------------------------
+int cmCTestSubmitHandler::ProcessHandler()
+{
+ std::string iscdash = this->CTest->GetCTestConfiguration("IsCDash");
+ // cdash does not need to trigger so just return true
+ if(iscdash.size())
+ {
+ this->CDash = true;
+ }
+
+ const std::string &buildDirectory
+ = this->CTest->GetCTestConfiguration("BuildDirectory");
+ if ( buildDirectory.size() == 0 )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Cannot find BuildDirectory key in the DartConfiguration.tcl"
+ << std::endl);
+ return -1;
+ }
+
+ if ( getenv("HTTP_PROXY") )
+ {
+ this->HTTPProxyType = 1;
+ this->HTTPProxy = getenv("HTTP_PROXY");
+ if ( getenv("HTTP_PROXY_PORT") )
+ {
+ this->HTTPProxy += ":";
+ this->HTTPProxy += getenv("HTTP_PROXY_PORT");
+ }
+ if ( getenv("HTTP_PROXY_TYPE") )
+ {
+ cmStdString type = getenv("HTTP_PROXY_TYPE");
+ // HTTP/SOCKS4/SOCKS5
+ if ( type == "HTTP" )
+ {
+ this->HTTPProxyType = 1;
+ }
+ else if ( type == "SOCKS4" )
+ {
+ this->HTTPProxyType = 2;
+ }
+ else if ( type == "SOCKS5" )
+ {
+ this->HTTPProxyType = 3;
+ }
+ }
+ if ( getenv("HTTP_PROXY_USER") )
+ {
+ this->HTTPProxyAuth = getenv("HTTP_PROXY_USER");
+ }
+ if ( getenv("HTTP_PROXY_PASSWD") )
+ {
+ this->HTTPProxyAuth += ":";
+ this->HTTPProxyAuth += getenv("HTTP_PROXY_PASSWD");
+ }
+ }
+
+ if ( getenv("FTP_PROXY") )
+ {
+ this->FTPProxyType = 1;
+ this->FTPProxy = getenv("FTP_PROXY");
+ if ( getenv("FTP_PROXY_PORT") )
+ {
+ this->FTPProxy += ":";
+ this->FTPProxy += getenv("FTP_PROXY_PORT");
+ }
+ if ( getenv("FTP_PROXY_TYPE") )
+ {
+ cmStdString type = getenv("FTP_PROXY_TYPE");
+ // HTTP/SOCKS4/SOCKS5
+ if ( type == "HTTP" )
+ {
+ this->FTPProxyType = 1;
+ }
+ else if ( type == "SOCKS4" )
+ {
+ this->FTPProxyType = 2;
+ }
+ 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);
+ }
+ cmGeneratedFileStream ofs;
+ this->StartLogFile("Submit", ofs);
+
+ cmCTest::SetOfStrings files;
+ std::string prefix = this->GetSubmitResultsPrefix();
+
+ 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);
+ }
+ }
+
+ // Add to the list of files to submit from any selected, existing parts:
+ //
+
+ // TODO:
+ // Check if test is enabled
+
+ this->CTest->AddIfExists(cmCTest::PartUpdate, "Update.xml");
+ this->CTest->AddIfExists(cmCTest::PartConfigure, "Configure.xml");
+ this->CTest->AddIfExists(cmCTest::PartBuild, "Build.xml");
+ this->CTest->AddIfExists(cmCTest::PartTest, "Test.xml");
+ if(this->CTest->AddIfExists(cmCTest::PartCoverage, "Coverage.xml"))
+ {
+ cmCTest::VectorOfStrings 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) )
+ {
+ size_t 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);
+ this->CTest->AddSubmitFile(cmCTest::PartCoverage, gfiles[cc].c_str());
+ }
+ }
+ else
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Problem globbing" << std::endl);
+ }
+ }
+ this->CTest->AddIfExists(cmCTest::PartMemCheck, "DynamicAnalysis.xml");
+ this->CTest->AddIfExists(cmCTest::PartMemCheck, "Purify.xml");
+ this->CTest->AddIfExists(cmCTest::PartNotes, "Notes.xml");
+ this->CTest->AddIfExists(cmCTest::PartUpload, "Upload.xml");
+
+ // Query parts for files to submit.
+ for(cmCTest::Part p = cmCTest::PartStart;
+ p != cmCTest::PartCount; p = cmCTest::Part(p+1))
+ {
+ // Skip parts we are not submitting.
+ if(!this->SubmitPart[p])
+ {
+ continue;
+ }
+
+ // Submit files from this part.
+ std::vector<std::string> const& pfiles = this->CTest->GetSubmitFiles(p);
+ for(std::vector<std::string>::const_iterator pi = pfiles.begin();
+ pi != pfiles.end(); ++pi)
+ {
+ files.insert(*pi);
+ }
+ }
+
+ 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 ++;
+ }
+ }
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, "Submit files (using "
+ << this->CTest->GetCTestConfiguration("DropMethod") << ")"
+ << std::endl);
+ const char* specificTrack = this->CTest->GetSpecificTrack();
+ if ( specificTrack )
+ {
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, " Send to track: "
+ << specificTrack << std::endl);
+ }
+ this->SetLogFile(&ofs);
+
+ cmStdString dropMethod(this->CTest->GetCTestConfiguration("DropMethod"));
+
+ 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://");
+ 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, "@");
+ }
+ 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) )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ " 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")))
+ {
+ 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);
+ ofs << " Submission successful" << std::endl;
+ return 0;
+ }
+ }
+ 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 )
+ {
+ url += this->CTest->GetCTestConfiguration("DropSiteUser");
+ cmCTestLog(this->CTest, HANDLER_OUTPUT,
+ this->CTest->GetCTestConfiguration("DropSiteUser").c_str());
+ if ( this->CTest->GetCTestConfiguration("DropSitePassword").size() > 0 )
+ {
+ url += ":" + this->CTest->GetCTestConfiguration("DropSitePassword");
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, ":******");
+ }
+ url += "@";
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, "@");
+ }
+ 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) )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ " 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")))
+ {
+ 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)
+ {
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, " Errors occurred during "
+ "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;
+ }
+
+ return 0;
+ }
+ 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);
+ std::string url = this->CTest->GetCTestConfiguration("DropSite");
+ prefix = this->CTest->GetCTestConfiguration("DropLocation");
+ if ( !this->SubmitUsingXMLRPC(buildDirectory + "/Testing/" +
+ this->CTest->GetCurrentTag(), files, prefix, url) )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ " 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);
+ ofs << " Submission successful" << std::endl;
+ return 0;
+#else
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ " Submission method \"xmlrpc\" not compiled into CTest!"
+ << std::endl);
+ return -1;
+#endif
+ }
+ else if ( dropMethod == "scp" )
+ {
+ std::string url;
+ std::string oldWorkingDirectory;
+ if ( this->CTest->GetCTestConfiguration("DropSiteUser").size() > 0 )
+ {
+ 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());
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ " 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);
+ ofs << " Submission successful" << std::endl;
+ return 0;
+ }
+ 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());
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ " 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);
+ ofs << " Submission successful" << std::endl;
+ return 0;
+ }
+
+ cmCTestLog(this->CTest, ERROR_MESSAGE, " Unknown submission method: \""
+ << dropMethod << "\"" << std::endl);
+ return -1;
+}
+
+//----------------------------------------------------------------------------
+std::string cmCTestSubmitHandler::GetSubmitResultsPrefix()
+{
+ std::string name = this->CTest->GetCTestConfiguration("Site") +
+ "___" + this->CTest->GetCTestConfiguration("BuildName") +
+ "___" + this->CTest->GetCurrentTag() + "-" +
+ this->CTest->GetTestModelString() + "___XML___";
+ return name;
+}
+
+//----------------------------------------------------------------------------
+void cmCTestSubmitHandler::SelectParts(std::set<cmCTest::Part> const& parts)
+{
+ // Check whether each part is selected.
+ for(cmCTest::Part p = cmCTest::PartStart;
+ p != cmCTest::PartCount; p = cmCTest::Part(p+1))
+ {
+ this->SubmitPart[p] =
+ (std::set<cmCTest::Part>::const_iterator(parts.find(p)) != parts.end());
+ }
+}
+
+//----------------------------------------------------------------------------
+void cmCTestSubmitHandler::SelectFiles(cmCTest::SetOfStrings const& files)
+{
+ cmCTest::SetOfStrings::const_iterator it;
+ for (it = files.begin(); it != files.end(); ++it)
+ {
+ this->Files.insert(*it);
+ }
+}
diff --git a/Source/CTest/cmCTestSubmitHandler.h b/Source/CTest/cmCTestSubmitHandler.h
new file mode 100644
index 000000000..14eac80b5
--- /dev/null
+++ b/Source/CTest/cmCTestSubmitHandler.h
@@ -0,0 +1,98 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestSubmitHandler_h
+#define cmCTestSubmitHandler_h
+
+#include "cmCTestGenericHandler.h"
+
+/** \class cmCTestSubmitHandler
+ * \brief Helper class for CTest
+ *
+ * Submit testing results
+ *
+ */
+class cmCTestSubmitHandler : public cmCTestGenericHandler
+{
+public:
+ cmTypeMacro(cmCTestSubmitHandler, cmCTestGenericHandler);
+
+ cmCTestSubmitHandler();
+ ~cmCTestSubmitHandler() { this->LogFile = 0; }
+
+ /*
+ * The main entry point for this class
+ */
+ int ProcessHandler();
+
+ void Initialize();
+
+ /** Specify a set of parts (by name) to submit. */
+ void SelectParts(std::set<cmCTest::Part> const& parts);
+
+ /** Specify a set of files to submit. */
+ void SelectFiles(cmCTest::SetOfStrings const& files);
+
+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);
+
+ typedef std::vector<char> cmCTestSubmitHandlerVectorOfChar;
+
+ void ParseResponse(cmCTestSubmitHandlerVectorOfChar chunk);
+
+ std::string GetSubmitResultsPrefix();
+
+ class ResponseParser;
+ cmStdString HTTPProxy;
+ int HTTPProxyType;
+ cmStdString HTTPProxyAuth;
+ cmStdString FTPProxy;
+ int FTPProxyType;
+ std::ostream* LogFile;
+ bool SubmitPart[cmCTest::PartCount];
+ bool CDash;
+ bool HasWarnings;
+ bool HasErrors;
+ cmCTest::SetOfStrings Files;
+};
+
+#endif
diff --git a/Source/CTest/cmCTestTestCommand.cxx b/Source/CTest/cmCTestTestCommand.cxx
new file mode 100644
index 000000000..5aee035c7
--- /dev/null
+++ b/Source/CTest/cmCTestTestCommand.cxx
@@ -0,0 +1,112 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestTestCommand.h"
+
+#include "cmCTest.h"
+#include "cmCTestGenericHandler.h"
+
+cmCTestTestCommand::cmCTestTestCommand()
+{
+ this->Arguments[ctt_START] = "START";
+ this->Arguments[ctt_END] = "END";
+ this->Arguments[ctt_STRIDE] = "STRIDE";
+ this->Arguments[ctt_EXCLUDE] = "EXCLUDE";
+ this->Arguments[ctt_INCLUDE] = "INCLUDE";
+ this->Arguments[ctt_EXCLUDE_LABEL] = "EXCLUDE_LABEL";
+ this->Arguments[ctt_INCLUDE_LABEL] = "INCLUDE_LABEL";
+ 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->Last = ctt_LAST;
+}
+
+cmCTestGenericHandler* cmCTestTestCommand::InitializeHandler()
+{
+ const char* ctestTimeout =
+ this->Makefile->GetDefinition("CTEST_TEST_TIMEOUT");
+
+ double timeout = this->CTest->GetTimeOut();
+ if ( ctestTimeout )
+ {
+ timeout = atof(ctestTimeout);
+ }
+ else
+ {
+ 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] )
+ {
+ testsToRunString << this->Values[ctt_START];
+ }
+ testsToRunString << ",";
+ if ( this->Values[ctt_END] )
+ {
+ testsToRunString << this->Values[ctt_END];
+ }
+ testsToRunString << ",";
+ if ( this->Values[ctt_STRIDE] )
+ {
+ testsToRunString << this->Values[ctt_STRIDE];
+ }
+ handler->SetOption("TestsToRunInformation",
+ testsToRunString.str().c_str());
+ }
+ if(this->Values[ctt_EXCLUDE])
+ {
+ handler->SetOption("ExcludeRegularExpression", this->Values[ctt_EXCLUDE]);
+ }
+ if(this->Values[ctt_INCLUDE])
+ {
+ handler->SetOption("IncludeRegularExpression", this->Values[ctt_INCLUDE]);
+ }
+ if(this->Values[ctt_EXCLUDE_LABEL])
+ {
+ handler->SetOption("ExcludeLabelRegularExpression",
+ this->Values[ctt_EXCLUDE_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])
+ {
+ this->CTest->SetStopTime(this->Values[ctt_STOP_TIME]);
+ }
+ return handler;
+}
+
+cmCTestGenericHandler* cmCTestTestCommand::InitializeActualHandler()
+{
+ return this->CTest->GetInitializedHandler("test");
+}
diff --git a/Source/CTest/cmCTestTestCommand.h b/Source/CTest/cmCTestTestCommand.h
new file mode 100644
index 000000000..d184ff2a0
--- /dev/null
+++ b/Source/CTest/cmCTestTestCommand.h
@@ -0,0 +1,107 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestTestCommand_h
+#define cmCTestTestCommand_h
+
+#include "cmCTestHandlerCommand.h"
+
+/** \class cmCTestTest
+ * \brief Run a ctest script
+ *
+ * cmCTestTestCommand defineds the command to test the project.
+ */
+class cmCTestTestCommand : public cmCTestHandlerCommand
+{
+public:
+
+ cmCTestTestCommand();
+
+ /**
+ * This is a virtual constructor for the command.
+ */
+ virtual cmCommand* Clone()
+ {
+ 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);
+
+protected:
+ virtual cmCTestGenericHandler* InitializeActualHandler();
+ cmCTestGenericHandler* InitializeHandler();
+
+ enum {
+ ctt_BUILD = ct_LAST,
+ ctt_RETURN_VALUE,
+ ctt_START,
+ ctt_END,
+ ctt_STRIDE,
+ ctt_EXCLUDE,
+ ctt_INCLUDE,
+ ctt_EXCLUDE_LABEL,
+ ctt_INCLUDE_LABEL,
+ ctt_PARALLEL_LEVEL,
+ ctt_SCHEDULE_RANDOM,
+ ctt_STOP_TIME,
+ ctt_LAST
+ };
+};
+
+
+#endif
diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx
new file mode 100644
index 000000000..ead449edd
--- /dev/null
+++ b/Source/CTest/cmCTestTestHandler.cxx
@@ -0,0 +1,2267 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#include "cmCTestTestHandler.h"
+#include "cmCTestMultiProcessHandler.h"
+#include "cmCTestBatchTestHandler.h"
+#include "cmCTest.h"
+#include "cmCTestRunTest.h"
+#include "cmake.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 "cmSystemTools.h"
+#include "cmXMLSafe.h"
+#include "cm_utf8.h"
+
+#include <stdlib.h>
+#include <math.h>
+#include <float.h>
+
+#include <memory> // auto_ptr
+#include <set>
+
+//----------------------------------------------------------------------
+class cmCTestSubdirCommand : public cmCommand
+{
+public:
+ /**
+ * This is a virtual constructor for the command.
+ */
+ virtual cmCommand* Clone()
+ {
+ 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);
+
+ cmCTestTestHandler* TestHandler;
+};
+
+//----------------------------------------------------------------------
+bool cmCTestSubdirCommand
+::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
+{
+ if(args.size() < 1 )
+ {
+ 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 )
+ {
+ cmSystemTools::ChangeDirectory(cwd.c_str());
+ std::string fname;
+
+ if(cmSystemTools::FileIsFullPath(it->c_str()))
+ {
+ fname = *it;
+ }
+ else
+ {
+ fname = cwd;
+ fname += "/";
+ fname += *it;
+ }
+
+ if ( !cmSystemTools::FileIsDirectory(fname.c_str()) )
+ {
+ // No subdirectory? So what...
+ 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...
+ cmSystemTools::ChangeDirectory(cwd.c_str());
+ continue;
+ }
+ fname += "/";
+ fname += testFilename;
+ bool readit =
+ this->Makefile->ReadListFile(this->Makefile->GetCurrentListFile(),
+ fname.c_str());
+ cmSystemTools::ChangeDirectory(cwd.c_str());
+ if(!readit)
+ {
+ std::string m = "Could not find include file: ";
+ m += fname;
+ this->SetError(m.c_str());
+ return false;
+ }
+ }
+ return true;
+}
+
+//----------------------------------------------------------------------
+class cmCTestAddSubdirectoryCommand : public cmCommand
+{
+public:
+ /**
+ * This is a virtual constructor for the command.
+ */
+ virtual cmCommand* Clone()
+ {
+ 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);
+
+ cmCTestTestHandler* TestHandler;
+};
+
+//----------------------------------------------------------------------
+bool cmCTestAddSubdirectoryCommand
+::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
+{
+ if(args.size() < 1 )
+ {
+ this->SetError("called with incorrect number of arguments");
+ return false;
+ }
+
+ std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
+ cmSystemTools::ChangeDirectory(cwd.c_str());
+ std::string fname = cwd;
+ fname += "/";
+ fname += args[1];
+
+ if ( !cmSystemTools::FileExists(fname.c_str()) )
+ {
+ // No subdirectory? So what...
+ 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)
+ {
+ std::string m = "Could not find include file: ";
+ m += fname;
+ this->SetError(m.c_str());
+ return false;
+ }
+ return true;
+}
+
+//----------------------------------------------------------------------
+class cmCTestAddTestCommand : public cmCommand
+{
+public:
+ /**
+ * This is a virtual constructor for the command.
+ */
+ virtual cmCommand* Clone()
+ {
+ 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);
+
+ cmCTestTestHandler* TestHandler;
+};
+
+//----------------------------------------------------------------------
+bool cmCTestAddTestCommand
+::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
+{
+ 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;
+ 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);
+
+ cmCTestTestHandler* TestHandler;
+};
+
+//----------------------------------------------------------------------
+bool cmCTestSetTestsPropertiesCommand
+::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
+{
+ 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,
+ std::string::size_type& pos,
+ std::string::size_type& pos2)
+{
+ pos2 = in.find(',', pos);
+ if(pos2 != in.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;
+ }
+}
+
+//----------------------------------------------------------------------
+// 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,
+ std::string::size_type& pos,
+ std::string::size_type& pos2)
+{
+ pos2 = in.find(',', pos);
+ if(pos2 != in.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;
+ }
+}
+
+
+//----------------------------------------------------------------------
+cmCTestTestHandler::cmCTestTestHandler()
+{
+ this->UseUnion = 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;
+
+ // regex to detect <DartMeasurement>...</DartMeasurement>
+ 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();
+
+ this->ElapsedTestingTime = -1;
+
+ this->TestResults.clear();
+
+ this->CustomTestsIgnore.clear();
+ this->StartTest = "";
+ this->EndTest = "";
+
+ this->CustomPreTest.clear();
+ this->CustomPostTest.clear();
+ this->CustomMaximumPassedTestOutputSize = 1 * 1024;
+ this->CustomMaximumFailedTestOutputSize = 300 * 1024;
+
+ this->TestsToRun.clear();
+
+ this->UseIncludeLabelRegExpFlag = false;
+ this->UseExcludeLabelRegExpFlag = false;
+ this->UseIncludeRegExpFlag = false;
+ this->UseExcludeRegExpFlag = false;
+ this->UseExcludeRegExpFirst = false;
+ this->IncludeLabelRegularExpression = "";
+ this->ExcludeLabelRegularExpression = "";
+ this->IncludeRegExp = "";
+ this->ExcludeRegExp = "";
+
+ TestsToRunString = "";
+ this->UseUnion = false;
+ this->TestList.clear();
+}
+
+//----------------------------------------------------------------------
+void cmCTestTestHandler::PopulateCustomVectors(cmMakefile *mf)
+{
+ this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_PRE_TEST",
+ 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);
+}
+
+//----------------------------------------------------------------------
+int cmCTestTestHandler::PreProcessHandler()
+{
+ if ( !this->ExecuteCommands(this->CustomPreTest) )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Problem executing pre-test command(s)." << std::endl);
+ return 0;
+ }
+ return 1;
+}
+
+//----------------------------------------------------------------------
+int cmCTestTestHandler::PostProcessHandler()
+{
+ if ( !this->ExecuteCommands(this->CustomPostTest) )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "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...
+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")))
+ {
+ this->CTest->SetScheduleType("Random");
+ }
+ if(this->GetOption("ParallelLevel"))
+ {
+ this->CTest->SetParallelLevel(atoi(this->GetOption("ParallelLevel")));
+ }
+
+ const char* val;
+ val = this->GetOption("LabelRegularExpression");
+ if ( val )
+ {
+ this->UseIncludeLabelRegExpFlag = true;
+ this->IncludeLabelRegExp = val;
+ }
+ val = this->GetOption("ExcludeLabelRegularExpression");
+ if ( val )
+ {
+ this->UseExcludeLabelRegExpFlag = true;
+ this->ExcludeLabelRegularExpression = val;
+ }
+ val = this->GetOption("IncludeRegularExpression");
+ if ( val )
+ {
+ this->UseIncludeRegExp();
+ this->SetIncludeRegExp(val);
+ }
+ val = this->GetOption("ExcludeRegularExpression");
+ if ( val )
+ {
+ this->UseExcludeRegExp();
+ this->SetExcludeRegExp(val);
+ }
+
+ this->TestResults.clear();
+
+ cmCTestLog(this->CTest, HANDLER_OUTPUT,
+ (this->MemCheck ? "Memory check" : "Test")
+ << " project " << cmSystemTools::GetCurrentWorkingDirectory()
+ << std::endl);
+ 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;
+ int total;
+
+ //start the real time clock
+ double clock_start, clock_finish;
+ clock_start = cmSystemTools::GetTime();
+
+ this->ProcessDirectory(passed, failed);
+
+ clock_finish = cmSystemTools::GetTime();
+
+ total = int(passed.size()) + int(failed.size());
+
+ if (total == 0)
+ {
+ if ( !this->CTest->GetShowOnly() && !this->CTest->ShouldPrintLabels() )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "No tests were found!!!"
+ << std::endl);
+ }
+ }
+ 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);
+ }
+ }
+
+ float percent = float(passed.size()) * 100.0f / float(total);
+ if ( failed.size() > 0 && 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())
+ {
+ 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" );
+
+ if (failed.size())
+ {
+ cmGeneratedFileStream ofs;
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl
+ << "The following tests FAILED:" << std::endl);
+ this->StartLogFile("TestsFailed", ofs);
+
+ typedef std::set<cmCTestTestHandler::cmCTestTestResult,
+ cmCTestTestResultLess> SetOfTests;
+ SetOfTests resultsSet(this->TestResults.begin(),
+ this->TestResults.end());
+
+ for(SetOfTests::iterator ftit = resultsSet.begin();
+ ftit != resultsSet.end(); ++ftit)
+ {
+ if ( ftit->Status != cmCTestTestHandler::COMPLETED )
+ {
+ 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);
+ }
+ }
+ }
+ }
+
+ if ( this->CTest->GetProduceXML() )
+ {
+ cmGeneratedFileStream xmlfile;
+ if( !this->StartResultingXML(
+ (this->MemCheck ? cmCTest::PartMemCheck : cmCTest::PartTest),
+ (this->MemCheck ? "DynamicAnalysis" : "Test"), xmlfile) )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot create "
+ << (this->MemCheck ? "memory check" : "testing")
+ << " XML file" << std::endl);
+ this->LogFile = 0;
+ return 1;
+ }
+ this->GenerateDartOutput(xmlfile);
+ }
+
+ if ( ! this->PostProcessHandler() )
+ {
+ this->LogFile = 0;
+ return -1;
+ }
+
+ if ( !failed.empty() )
+ {
+ this->LogFile = 0;
+ return -1;
+ }
+ this->LogFile = 0;
+ 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;
+ // initialize maps
+ std::string::size_type maxlen = 0;
+ 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)
+ {
+ maxlen = (*l).size();
+ }
+ labels.insert(*l);
+ labelTimes[*l] = 0;
+ }
+ }
+ }
+ ri = this->TestResults.begin();
+ // fill maps
+ 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)
+ {
+ labelTimes[*l] += result.ExecutionTime;
+ }
+ }
+ }
+ // 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)
+ {
+ std::string label = *i;
+ 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";
+ }
+ }
+ if(labels.size())
+ {
+ if(this->LogFile)
+ {
+ *this->LogFile << "\n";
+ }
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, "\n");
+ }
+
+}
+
+//----------------------------------------------------------------------
+void cmCTestTestHandler::CheckLabelFilterInclude(cmCTestTestProperties& it)
+{
+ // if not using Labels to filter then return
+ 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 )
+ {
+ it.IsInBasedOnREOptions = false;
+ return;
+ }
+ // check to see if the label regular expression matches
+ 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))
+ {
+ found = true;
+ }
+ }
+ // if no match was found, exclude the test
+ if(!found)
+ {
+ it.IsInBasedOnREOptions = false;
+ }
+}
+
+
+//----------------------------------------------------------------------
+void cmCTestTestHandler::CheckLabelFilterExclude(cmCTestTestProperties& it)
+{
+ // if not using Labels to filter then return
+ 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 )
+ {
+ return;
+ }
+ // check to see if the label regular expression matches
+ 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))
+ {
+ found = true;
+ }
+ }
+ // if match was found, exclude the test
+ 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();
+ 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 ++ )
+ {
+ this->CheckLabelFilter(*it);
+ if (it->IsInBasedOnREOptions)
+ {
+ inREcnt ++;
+ }
+ }
+ // expand the test list based on the union flag
+ if (this->UseUnion)
+ {
+ this->ExpandTestsToRunInformation((int)tmsize);
+ }
+ else
+ {
+ this->ExpandTestsToRunInformation(inREcnt);
+ }
+ // Now create a final list of tests to run
+ int cnt = 0;
+ inREcnt = 0;
+ std::string last_directory = "";
+ ListOfTests finalList;
+ for ( it = this->TestList.begin(); it != this->TestList.end(); it ++ )
+ {
+ cnt ++;
+ if (it->IsInBasedOnREOptions)
+ {
+ inREcnt++;
+ }
+
+ 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)
+ {
+ continue;
+ }
+ }
+ 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)
+ {
+ continue;
+ }
+ }
+ it->Index = cnt; // save the index into the test list for this test
+ finalList.push_back(*it);
+ }
+ // 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;
+ std::string::size_type max = this->CTest->GetMaxTestNameWidth();
+ for (it = this->TestList.begin();
+ it != this->TestList.end(); it ++ )
+ {
+ cmCTestTestProperties& p = *it;
+ if(max < p.Name.size())
+ {
+ max = p.Name.size();
+ }
+ }
+ 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)
+{
+ std::string line;
+ bool ret = true;
+ cmSystemTools::GetLineFromStream(fin, line);
+ 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);
+ ret = false;
+ }
+ return ret;
+}
+
+bool cmCTestTestHandler::GetValue(const char* tag,
+ double& value,
+ std::ifstream& fin)
+{
+ std::string line;
+ cmSystemTools::GetLineFromStream(fin, line);
+ bool ret = true;
+ 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);
+ ret = false;
+ }
+ return ret;
+}
+
+bool cmCTestTestHandler::GetValue(const char* tag,
+ bool& value,
+ std::ifstream& fin)
+{
+ std::string line;
+ cmSystemTools::GetLineFromStream(fin, line);
+ bool ret = true;
+ if(line == tag)
+ {
+#ifdef __HAIKU__
+ int tmp = 0;
+ fin >> tmp;
+ value = false;
+ 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);
+ ret = false;
+ }
+ return ret;
+}
+
+bool cmCTestTestHandler::GetValue(const char* tag,
+ size_t& value,
+ std::ifstream& fin)
+{
+ std::string line;
+ cmSystemTools::GetLineFromStream(fin, line);
+ bool ret = true;
+ 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);
+ ret = false;
+ }
+ return ret;
+}
+
+bool cmCTestTestHandler::GetValue(const char* tag,
+ std::string& value,
+ std::ifstream& 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);
+ ret = false;
+ }
+ return ret;
+}
+
+//---------------------------------------------------------------------
+void cmCTestTestHandler::ProcessDirectory(std::vector<cmStdString> &passed,
+ std::vector<cmStdString> &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;
+ parallel->SetCTest(this->CTest);
+ parallel->SetParallelLevel(this->CTest->GetParallelLevel());
+ parallel->SetTestHandler(this);
+
+ *this->LogFile << "Start testing: "
+ << this->CTest->CurrentTime() << std::endl
+ << "----------------------------------------------------------"
+ << std::endl;
+
+ cmCTestMultiProcessHandler::TestMap tests;
+ cmCTestMultiProcessHandler::PropertiesMap properties;
+
+ bool randomSchedule = this->CTest->GetScheduleType() == "Random";
+ if(randomSchedule)
+ {
+ srand((unsigned)time(0));
+ }
+
+ for (ListOfTests::iterator it = this->TestList.begin();
+ it != this->TestList.end(); ++it)
+ {
+ cmCTestTestProperties& p = *it;
+ cmCTestMultiProcessHandler::TestSet depends;
+
+ if(randomSchedule)
+ {
+ p.Cost = static_cast<float>(rand());
+ }
+
+ 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)
+ {
+ 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())
+ {
+ parallel->PrintLabels();
+ }
+ else if(this->CTest->GetShowOnly())
+ {
+ parallel->PrintTestList();
+ }
+ 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;
+}
+
+//----------------------------------------------------------------------
+void cmCTestTestHandler::GenerateTestCommand(std::vector<std::string>&)
+{
+}
+
+//----------------------------------------------------------------------
+void cmCTestTestHandler::GenerateDartOutput(std::ostream& os)
+{
+ 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";
+ cmCTestTestHandler::TestResultsVector::size_type 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())
+ {
+ const char* reasonType = "Pass Reason";
+ if(result->Status != cmCTestTestHandler::COMPLETED &&
+ result->Status != cmCTestTestHandler::NOT_RUN)
+ {
+ 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";
+ }
+ 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);
+}
+
+//----------------------------------------------------------------------------
+void cmCTestTestHandler::WriteTestResultHeader(std::ostream& os,
+ 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";
+ }
+ 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";
+}
+
+//----------------------------------------------------------------------------
+void cmCTestTestHandler::WriteTestResultFooter(std::ostream& os,
+ cmCTestTestResult* result)
+{
+ if(!result->Properties->Labels.empty())
+ {
+ os << "\t\t<Labels>\n";
+ 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";
+ }
+
+ os
+ << "\t</Test>" << std::endl;
+}
+
+//----------------------------------------------------------------------
+void cmCTestTestHandler::AttachFiles(std::ostream& os,
+ cmCTestTestResult* result)
+{
+ if(result->Status != cmCTestTestHandler::COMPLETED
+ && result->Properties->AttachOnFail.size())
+ {
+ 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);
+ 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";
+ }
+}
+
+//----------------------------------------------------------------------
+int cmCTestTestHandler::ExecuteCommands(std::vector<cmStdString>& vec)
+{
+ std::vector<cmStdString>::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);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+
+//----------------------------------------------------------------------
+// Find the appropriate executable to run for a test
+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)
+ {
+ return exe;
+ }
+ 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)
+{
+ std::string tempPath;
+
+ if (filepath.size() &&
+ filepath[filepath.size()-1] != '/')
+ {
+ filepath += "/";
+ }
+ tempPath = filepath + filename;
+ attempted.push_back(tempPath);
+ attemptedConfigs.push_back("");
+
+ if(ctest->GetConfigType().size())
+ {
+ tempPath = filepath;
+ tempPath += ctest->GetConfigType();
+ tempPath += "/";
+ tempPath += filename;
+ attempted.push_back(tempPath);
+ attemptedConfigs.push_back(ctest->GetConfigType());
+ // If the file is an OSX bundle then the configtyp
+ // will be at the start of the path
+ tempPath = ctest->GetConfigType();
+ tempPath += "/";
+ tempPath += filepath;
+ tempPath += filename;
+ attempted.push_back(tempPath);
+ attemptedConfigs.push_back(ctest->GetConfigType());
+ }
+ else
+ {
+ // no config specified to try some options
+ tempPath = filepath;
+ tempPath += "Release/";
+ tempPath += filename;
+ attempted.push_back(tempPath);
+ attemptedConfigs.push_back("Release");
+ tempPath = filepath;
+ tempPath += "Debug/";
+ tempPath += filename;
+ attempted.push_back(tempPath);
+ attemptedConfigs.push_back("Debug");
+ tempPath = filepath;
+ tempPath += "MinSizeRel/";
+ tempPath += filename;
+ attempted.push_back(tempPath);
+ attemptedConfigs.push_back("MinSizeRel");
+ tempPath = filepath;
+ tempPath += "RelWithDebInfo/";
+ tempPath += filename;
+ attempted.push_back(tempPath);
+ attemptedConfigs.push_back("RelWithDebInfo");
+ tempPath = filepath;
+ tempPath += "Deployment/";
+ tempPath += filename;
+ attempted.push_back(tempPath);
+ attemptedConfigs.push_back("Deployment");
+ tempPath = filepath;
+ tempPath += "Development/";
+ 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)
+{
+ // 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);
+
+ 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 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)
+ {
+ std::string filepathExtra =
+ cmSystemTools::GetFilenamePath(extraPaths[i]);
+ std::string filenameExtra =
+ cmSystemTools::GetFilenameName(extraPaths[i]);
+ 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)
+ {
+ // 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());
+ resultingConfig = attemptedConfigs[ai];
+ }
+ // then try with the exe extension
+ else
+ {
+ failed.push_back(attempted[ai].c_str());
+ tempPath = attempted[ai];
+ tempPath += cmSystemTools::GetExecutableExtension();
+ if(cmSystemTools::FileExists(tempPath.c_str())
+ && !cmSystemTools::FileIsDirectory(tempPath.c_str()))
+ {
+ fullPath = cmSystemTools::CollapseFullPath(tempPath.c_str());
+ resultingConfig = attemptedConfigs[ai];
+ }
+ else
+ {
+ failed.push_back(tempPath.c_str());
+ }
+ }
+ }
+
+ // if everything else failed, check the users path, but only if a full path
+ // wasn't specified
+ if (fullPath.size() == 0 && filepath.size() == 0)
+ {
+ std::string path = cmSystemTools::FindProgram(filename.c_str());
+ 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");
+ }
+ }
+
+ 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() )
+ {
+ this->IncludeTestsRegularExpression.compile(this->IncludeRegExp.c_str());
+ }
+ 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);
+ std::auto_ptr<cmLocalGenerator> lg(gg.CreateLocalGenerator());
+ cmMakefile *mf = lg->GetMakefile();
+ mf->AddDefinition("CTEST_CONFIGURATION_TYPE",
+ this->CTest->GetConfigType().c_str());
+
+ // Add handler for ADD_TEST
+ cmCTestAddTestCommand* newCom1 = new cmCTestAddTestCommand;
+ newCom1->TestHandler = this;
+ cm.AddCommand(newCom1);
+
+ // Add handler for SUBDIRS
+ cmCTestSubdirCommand* newCom2 =
+ new cmCTestSubdirCommand;
+ newCom2->TestHandler = this;
+ cm.AddCommand(newCom2);
+
+ // Add handler for ADD_SUBDIRECTORY
+ cmCTestAddSubdirectoryCommand* newCom3 =
+ new cmCTestAddSubdirectoryCommand;
+ newCom3->TestHandler = this;
+ cm.AddCommand(newCom3);
+
+ // Add handler for SET_SOURCE_FILES_PROPERTIES
+ cmCTestSetTestsPropertiesCommand* newCom4
+ = new cmCTestSetTestsPropertiesCommand;
+ newCom4->TestHandler = this;
+ cm.AddCommand(newCom4);
+
+ 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
+ {
+ return;
+ }
+
+ if ( !mf->ReadListFile(0, testFilename) )
+ {
+ return;
+ }
+ if ( cmSystemTools::GetErrorOccuredFlag() )
+ {
+ return;
+ }
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "Done constructing a list of tests" << std::endl);
+}
+
+//----------------------------------------------------------------------
+void cmCTestTestHandler::UseIncludeRegExp()
+{
+ this->UseIncludeRegExpFlag = true;
+}
+
+//----------------------------------------------------------------------
+void cmCTestTestHandler::UseExcludeRegExp()
+{
+ this->UseExcludeRegExpFlag = true;
+ this->UseExcludeRegExpFirst = this->UseIncludeRegExpFlag ? false : true;
+}
+
+//----------------------------------------------------------------------
+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 )
+ {
+ return "No Status";
+ }
+ return statuses[status];
+}
+
+//----------------------------------------------------------------------
+void cmCTestTestHandler::ExpandTestsToRunInformation(size_t numTests)
+{
+ if (this->TestsToRunString.empty())
+ {
+ return;
+ }
+
+ int start;
+ int end = -1;
+ double stride = -1;
+ std::string::size_type pos = 0;
+ std::string::size_type pos2;
+ // read start
+ if(GetNextNumber(this->TestsToRunString, start, pos, pos2))
+ {
+ // read end
+ if(GetNextNumber(this->TestsToRunString, end, pos, pos2))
+ {
+ // read stride
+ if(GetNextRealNumber(this->TestsToRunString, stride, pos, pos2))
+ {
+ int val =0;
+ // now read specific numbers
+ while(GetNextNumber(this->TestsToRunString, val, pos, pos2))
+ {
+ 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)
+ {
+ start = 1;
+ }
+
+ // if end isnot specified then we assume we end with the last test
+ if(end == -1)
+ {
+ end = static_cast<int>(numTests);
+ }
+
+ // if the stride wasn't specified then it defaults to 1
+ if(stride == -1)
+ {
+ stride = 1;
+ }
+
+ // if we have a range then add it
+ 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));
+ ++i;
+ }
+ }
+
+ // sort the array
+ std::sort(this->TestsToRun.begin(), this->TestsToRun.end(),
+ 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());
+}
+
+//----------------------------------------------------------------------
+// Just for convenience
+#define SPACE_REGEX "[ \t\r\n]"
+//----------------------------------------------------------------------
+std::string cmCTestTestHandler::GenerateRegressionImages(
+ const std::string& xml)
+{
+ cmsys::RegularExpression twoattributes(
+ "<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>");
+ 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>");
+ 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>");
+ cmsys::RegularExpression measurementfile(
+ "<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;
+ 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;
+ 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;
+ 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) )
+ {
+ 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 )
+ {
+ 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" )
+ {
+ v1 = "text/string";
+ }
+ 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
+#ifdef _WIN32
+ | std::ios::binary
+#endif
+ );
+ 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 ++ )
+ {
+ ostr << encoded_buffer[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;
+ }
+ }
+ else
+ {
+ int idx = 4;
+ 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());
+ }
+ else
+ {
+ done = true;
+ }
+ }
+ return ostr.str();
+}
+
+//----------------------------------------------------------------------
+void cmCTestTestHandler::SetIncludeRegExp(const char *arg)
+{
+ this->IncludeRegExp = arg;
+}
+
+//----------------------------------------------------------------------
+void cmCTestTestHandler::SetExcludeRegExp(const char *arg)
+{
+ this->ExcludeRegExp = arg;
+}
+
+//----------------------------------------------------------------------
+void cmCTestTestHandler::SetTestsToRunInformation(const char* 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);
+ unsigned long filelen = cmSystemTools::FileLength(in);
+ char* buff = new char[filelen+1];
+ fin.getline(buff, filelen);
+ buff[fin.gcount()] = 0;
+ this->TestsToRunString = buff;
+ delete [] buff;
+ }
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestTestHandler::CleanTestOutput(std::string& output, size_t length)
+{
+ if(!length || length >= output.size() ||
+ output.find("CTEST_FULL_OUTPUT") != output.npos)
+ {
+ return true;
+ }
+
+ // Truncate at given length but do not break in the middle of a multi-byte
+ // UTF-8 encoding.
+ char const* const begin = output.c_str();
+ char const* const end = begin + output.size();
+ char const* const truncate = begin + length;
+ char const* current = begin;
+ while(current < truncate)
+ {
+ unsigned int ch;
+ 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;
+ }
+ }
+ output = output.substr(0, current - begin);
+
+ // Append truncation message.
+ cmOStringStream msg;
+ msg << "...\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;
+ bool found = false;
+ for ( it = args.begin(); it != args.end(); ++ it )
+ {
+ if ( *it == "PROPERTIES" )
+ {
+ found = true;
+ break;
+ }
+ tests.push_back(*it);
+ }
+ if ( !found )
+ {
+ return false;
+ }
+ ++ it; // skip PROPERTIES
+ for ( ; it != args.end(); ++ it )
+ {
+ std::string key = *it;
+ ++ it;
+ if ( it == args.end() )
+ {
+ break;
+ }
+ std::string val = *it;
+ std::vector<cmStdString>::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" )
+ {
+ rtit->WillFail = cmSystemTools::IsOn(val.c_str());
+ }
+ if ( key == "ATTACHED_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->AttachedFiles.push_back(*f);
+ }
+ }
+ if ( key == "ATTACHED_FILES_ON_FAIL" )
+ {
+ 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->AttachOnFail.push_back(*f);
+ }
+ }
+ if ( key == "RESOURCE_LOCK" )
+ {
+ 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->LockedResources.insert(*f);
+ }
+ }
+ if ( key == "TIMEOUT" )
+ {
+ rtit->Timeout = atof(val.c_str());
+ rtit->ExplicitTimeout = true;
+ }
+ 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" )
+ {
+ rtit->RunSerial = cmSystemTools::IsOn(val.c_str());
+ }
+ if ( key == "FAIL_REGULAR_EXPRESSION" )
+ {
+ 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->ErrorRegularExpressions.push_back(
+ std::pair<cmsys::RegularExpression, std::string>(
+ cmsys::RegularExpression(crit->c_str()),
+ std::string(crit->c_str())));
+ }
+ }
+ if ( key == "PROCESSORS" )
+ {
+ rtit->Processors = atoi(val.c_str());
+ 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 == "MEASUREMENT" )
+ {
+ size_t pos = val.find_first_of("=");
+ if ( pos != val.npos )
+ {
+ std::string mKey = val.substr(0, pos);
+ const char* mVal = val.c_str() + pos + 1;
+ rtit->Measurements[mKey] = mVal;
+ }
+ else
+ {
+ rtit->Measurements[val] = "1";
+ }
+ }
+ if ( key == "PASS_REGULAR_EXPRESSION" )
+ {
+ 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->RequiredRegularExpressions.push_back(
+ std::pair<cmsys::RegularExpression, std::string>(
+ cmsys::RegularExpression(crit->c_str()),
+ std::string(crit->c_str())));
+ }
+ }
+ if ( key == "WORKING_DIRECTORY" )
+ {
+ rtit->Directory = val;
+ }
+ }
+ }
+ }
+ }
+ 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);
+
+ if (this->UseExcludeRegExpFlag &&
+ this->UseExcludeRegExpFirst &&
+ this->ExcludeTestsRegularExpression.find(testname.c_str()))
+ {
+ return true;
+ }
+ if ( this->MemCheck )
+ {
+ std::vector<cmStdString>::iterator it;
+ bool found = false;
+ 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);
+ return true;
+ }
+ }
+ else
+ {
+ std::vector<cmStdString>::iterator it;
+ bool found = false;
+ 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);
+ 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);
+
+ test.IsInBasedOnREOptions = true;
+ test.WillFail = false;
+ test.RunSerial = false;
+ test.Timeout = 0;
+ test.ExplicitTimeout = false;
+ test.Cost = 0;
+ test.Processors = 1;
+ test.PreviousRuns = 0;
+ if (this->UseIncludeRegExpFlag &&
+ !this->IncludeTestsRegularExpression.find(testname.c_str()))
+ {
+ test.IsInBasedOnREOptions = false;
+ }
+ 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
new file mode 100644
index 000000000..8e59e5915
--- /dev/null
+++ b/Source/CTest/cmCTestTestHandler.h
@@ -0,0 +1,273 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#ifndef cmCTestTestHandler_h
+#define cmCTestTestHandler_h
+
+
+#include "cmCTestGenericHandler.h"
+#include <cmsys/RegularExpression.hxx>
+
+class cmMakefile;
+
+/** \class cmCTestTestHandler
+ * \brief A class that handles ctest -S invocations
+ *
+ */
+class cmCTestTestHandler : public cmCTestGenericHandler
+{
+ friend class cmCTestRunTest;
+ friend class cmCTestMultiProcessHandler;
+ friend class cmCTestBatchTestHandler;
+public:
+ cmTypeMacro(cmCTestTestHandler, cmCTestGenericHandler);
+
+ /**
+ * The main entry point for this class
+ */
+ int ProcessHandler();
+
+ /**
+ * When both -R and -I are used should te resulting test list be the
+ * intersection or the union of the lists. By default it is the
+ * intersection.
+ */
+ void SetUseUnion(bool val) { this->UseUnion = val; }
+
+ /**
+ * This method is called when reading CTest custom file
+ */
+ void PopulateCustomVectors(cmMakefile *mf);
+
+ ///! Control the use of the regular expresisons, call these methods to turn
+ ///them on
+ void UseIncludeRegExp();
+ void UseExcludeRegExp();
+ void SetIncludeRegExp(const char *);
+ void SetExcludeRegExp(const char *);
+
+ void SetMaxIndex(int n) {this->MaxIndex = n;}
+ int GetMaxIndex() {return this->MaxIndex;}
+
+ ///! pass the -I argument down
+ void SetTestsToRunInformation(const char*);
+
+ cmCTestTestHandler();
+
+ /*
+ * Add the test to the list of tests to be executed
+ */
+ bool AddTest(const std::vector<std::string>& args);
+
+ /*
+ * Set tests properties
+ */
+ bool SetTestsProperties(const std::vector<std::string>& args);
+
+ void Initialize();
+
+ // NOTE: This struct is Saved/Restored
+ // in cmCTestTestHandler, if you add to this class
+ // then you must add the new members to that code or
+ // ctest -j N will break for that feature
+ struct cmCTestTestProperties
+ {
+ cmStdString Name;
+ cmStdString 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;
+ bool IsInBasedOnREOptions;
+ bool WillFail;
+ float Cost;
+ int PreviousRuns;
+ bool RunSerial;
+ double Timeout;
+ bool ExplicitTimeout;
+ int Index;
+ //Requested number of process slots
+ int Processors;
+ std::vector<std::string> Environment;
+ std::vector<std::string> Labels;
+ std::set<std::string> LockedResources;
+ };
+
+ struct cmCTestTestResult
+ {
+ std::string Name;
+ std::string Path;
+ std::string Reason;
+ std::string FullCommandLine;
+ double ExecutionTime;
+ int ReturnValue;
+ int Status;
+ bool CompressOutput;
+ std::string CompletionStatus;
+ std::string Output;
+ std::string RegressionImages;
+ int TestCount;
+ cmCTestTestProperties* Properties;
+ };
+
+ struct cmCTestTestResultLess
+ {
+ bool operator() (const cmCTestTestResult &lhs,
+ const cmCTestTestResult &rhs) const
+ {
+ 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);
+
+ // 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);
+
+ 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 ExecuteCommands(std::vector<cmStdString>& vec);
+
+ void WriteTestResultHeader(std::ostream& os, cmCTestTestResult* result);
+ void WriteTestResultFooter(std::ostream& os, cmCTestTestResult* result);
+ // Write attached test files into the xml
+ void AttachFiles(std::ostream& os, cmCTestTestResult* result);
+
+ //! Clean test output to specified length
+ bool CleanTestOutput(std::string& output, size_t length);
+
+ double ElapsedTestingTime;
+
+ typedef std::vector<cmCTestTestResult> TestResultsVector;
+ TestResultsVector TestResults;
+
+ std::vector<cmStdString> 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
+ NOT_RUN = 0,
+ TIMEOUT,
+ SEGFAULT,
+ ILLEGAL,
+ INTERRUPT,
+ NUMERICAL,
+ OTHER_FAULT,
+ FAILED,
+ BAD_COMMAND,
+ COMPLETED
+ };
+
+private:
+ /**
+ * Generate the Dart compatible output
+ */
+ virtual void GenerateDartOutput(std::ostream& os);
+
+ void PrintLabelSummary();
+ /**
+ * Run the tests for a directory and any subdirectories
+ */
+ void ProcessDirectory(std::vector<cmStdString> &passed,
+ std::vector<cmStdString> &failed);
+
+ /**
+ * Get the list of tests in directory and subdirectories.
+ */
+ void GetListOfTests();
+ // compute the lists of tests that will actually run
+ // 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);
+ /**
+ * Find the executable for a test
+ */
+ std::string FindTheExecutable(const char *exe);
+
+ const char* GetTestStatus(int status);
+ void ExpandTestsToRunInformation(size_t numPossibleTests);
+
+ std::vector<cmStdString> CustomPreTest;
+ std::vector<cmStdString> CustomPostTest;
+
+ std::vector<int> TestsToRun;
+
+ bool UseIncludeLabelRegExpFlag;
+ bool UseExcludeLabelRegExpFlag;
+ bool UseIncludeRegExpFlag;
+ bool UseExcludeRegExpFlag;
+ bool UseExcludeRegExpFirst;
+ std::string IncludeLabelRegExp;
+ std::string ExcludeLabelRegExp;
+ std::string IncludeRegExp;
+ std::string ExcludeRegExp;
+ cmsys::RegularExpression IncludeLabelRegularExpression;
+ cmsys::RegularExpression ExcludeLabelRegularExpression;
+ cmsys::RegularExpression IncludeTestsRegularExpression;
+ cmsys::RegularExpression ExcludeTestsRegularExpression;
+
+ std::string GenerateRegressionImages(const std::string& xml);
+ cmsys::RegularExpression DartStuff1;
+ void CheckLabelFilter(cmCTestTestProperties& it);
+ void CheckLabelFilterExclude(cmCTestTestProperties& it);
+ void CheckLabelFilterInclude(cmCTestTestProperties& it);
+
+ std::string TestsToRunString;
+ bool UseUnion;
+ ListOfTests TestList;
+ size_t TotalNumberOfTests;
+ cmsys::RegularExpression DartStuff;
+
+ std::ostream* LogFile;
+};
+
+#endif
diff --git a/Source/CTest/cmCTestUpdateCommand.cxx b/Source/CTest/cmCTestUpdateCommand.cxx
new file mode 100644
index 000000000..841434911
--- /dev/null
+++ b/Source/CTest/cmCTestUpdateCommand.cxx
@@ -0,0 +1,78 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestUpdateCommand.h"
+
+#include "cmCTest.h"
+#include "cmCTestGenericHandler.h"
+
+cmCTestGenericHandler* cmCTestUpdateCommand::InitializeHandler()
+{
+ if ( this->Values[ct_SOURCE] )
+ {
+ 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->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,
+ "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");
+
+ cmCTestGenericHandler* handler
+ = this->CTest->GetInitializedHandler("update");
+ if ( !handler )
+ {
+ this->SetError("internal CTest error. Cannot instantiate update handler");
+ return 0;
+ }
+ handler->SetCommand(this);
+ if ( source_dir.empty() )
+ {
+ this->SetError("source directory not specified. Please use SOURCE tag");
+ return 0;
+ }
+ handler->SetOption("SourceDirectory", source_dir.c_str());
+ return handler;
+}
+
+
diff --git a/Source/CTest/cmCTestUpdateCommand.h b/Source/CTest/cmCTestUpdateCommand.h
new file mode 100644
index 000000000..c578fff6f
--- /dev/null
+++ b/Source/CTest/cmCTestUpdateCommand.h
@@ -0,0 +1,73 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestUpdateCommand_h
+#define cmCTestUpdateCommand_h
+
+#include "cmCTestHandlerCommand.h"
+
+/** \class cmCTestUpdate
+ * \brief Run a ctest script
+ *
+ * cmCTestUpdateCommand defineds the command to updates the repository.
+ */
+class cmCTestUpdateCommand : public cmCTestHandlerCommand
+{
+public:
+
+ cmCTestUpdateCommand() {}
+
+ /**
+ * This is a virtual constructor for the command.
+ */
+ virtual cmCommand* Clone()
+ {
+ 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);
+
+protected:
+ cmCTestGenericHandler* InitializeHandler();
+};
+
+
+#endif
diff --git a/Source/CTest/cmCTestUpdateHandler.cxx b/Source/CTest/cmCTestUpdateHandler.cxx
new file mode 100644
index 000000000..9eae3f3ac
--- /dev/null
+++ b/Source/CTest/cmCTestUpdateHandler.cxx
@@ -0,0 +1,404 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#include "cmCTestUpdateHandler.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 "cmCTestGIT.h"
+#include "cmCTestHG.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>
+
+//----------------------------------------------------------------------
+static const char* cmCTestUpdateHandlerUpdateStrings[] =
+{
+ "Unknown",
+ "CVS",
+ "SVN",
+ "BZR",
+ "GIT",
+ "HG"
+};
+
+static const char* cmCTestUpdateHandlerUpdateToString(int type)
+{
+ 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();
+ this->UpdateCommand = "";
+ 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);
+ std::string stype = cmSystemTools::LowerCase(type);
+ if ( stype.find("cvs") != std::string::npos )
+ {
+ return cmCTestUpdateHandler::e_CVS;
+ }
+ if ( stype.find("svn") != std::string::npos )
+ {
+ return cmCTestUpdateHandler::e_SVN;
+ }
+ if ( stype.find("bzr") != std::string::npos )
+ {
+ return cmCTestUpdateHandler::e_BZR;
+ }
+ if ( stype.find("git") != std::string::npos )
+ {
+ return cmCTestUpdateHandler::e_GIT;
+ }
+ if ( stype.find("hg") != std::string::npos )
+ {
+ return cmCTestUpdateHandler::e_HG;
+ }
+ }
+ else
+ {
+ cmCTestLog(this->CTest, DEBUG, "Type not specified, check command: "
+ << cmd << std::endl);
+ std::string stype = cmSystemTools::LowerCase(cmd);
+ if ( stype.find("cvs") != std::string::npos )
+ {
+ return cmCTestUpdateHandler::e_CVS;
+ }
+ if ( stype.find("svn") != std::string::npos )
+ {
+ return cmCTestUpdateHandler::e_SVN;
+ }
+ if ( stype.find("bzr") != std::string::npos )
+ {
+ return cmCTestUpdateHandler::e_BZR;
+ }
+ if ( stype.find("git") != std::string::npos )
+ {
+ return cmCTestUpdateHandler::e_GIT;
+ }
+ if ( stype.find("hg") != std::string::npos )
+ {
+ return cmCTestUpdateHandler::e_HG;
+ }
+ }
+ return cmCTestUpdateHandler::e_UNKNOWN;
+}
+
+//----------------------------------------------------------------------
+//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;
+ static_cast<void>(fixLocale);
+
+ // Get source dir
+ const char* sourceDirectory = this->GetOption("SourceDirectory");
+ if ( !sourceDirectory )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Cannot find SourceDirectory key in the DartConfiguration.tcl"
+ << std::endl);
+ return -1;
+ }
+
+ cmGeneratedFileStream ofs;
+ if ( !this->CTest->GetShowOnly() )
+ {
+ this->StartLogFile("Update", ofs);
+ }
+
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, " Updating the repository: "
+ << sourceDirectory << std::endl);
+
+ if(!this->SelectVCS())
+ {
+ return -1;
+ }
+
+ cmCTestLog(this->CTest, HANDLER_OUTPUT, " Use "
+ << cmCTestUpdateHandlerUpdateToString(this->UpdateType)
+ << " repository type"
+ << std::endl;);
+
+ // 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;
+ }
+ vc->SetCommandLineTool(this->UpdateCommand);
+ vc->SetSourceDirectory(sourceDirectory);
+
+ // Cleanup the working tree.
+ vc->Cleanup();
+
+ //
+ // Now update repository and remember what files were updated
+ //
+ cmGeneratedFileStream os;
+ if(!this->StartResultingXML(cmCTest::PartUpdate, "Update", os))
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot open log file"
+ << 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);
+
+ 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");
+ localModifications += numModified;
+ }
+ if(int numConflicting = vc->GetPathCount(cmCTestVC::PathConflicting))
+ {
+ cmCTestLog(this->CTest, HANDLER_OUTPUT,
+ " Found " << numConflicting << " conflicting files\n");
+ localModifications += numConflicting;
+ }
+
+ cmCTestLog(this->CTest, DEBUG, "End" << std::endl);
+ 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";
+ 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;
+}
+
+//----------------------------------------------------------------------
+int cmCTestUpdateHandler::DetectVCS(const char* dir)
+{
+ std::string sourceDirectory = dir;
+ cmCTestLog(this->CTest, DEBUG, "Check directory: "
+ << sourceDirectory.c_str() << std::endl);
+ sourceDirectory += "/.svn";
+ if ( cmSystemTools::FileExists(sourceDirectory.c_str()) )
+ {
+ return cmCTestUpdateHandler::e_SVN;
+ }
+ sourceDirectory = dir;
+ sourceDirectory += "/CVS";
+ if ( cmSystemTools::FileExists(sourceDirectory.c_str()) )
+ {
+ return cmCTestUpdateHandler::e_CVS;
+ }
+ sourceDirectory = dir;
+ sourceDirectory += "/.bzr";
+ if ( cmSystemTools::FileExists(sourceDirectory.c_str()) )
+ {
+ return cmCTestUpdateHandler::e_BZR;
+ }
+ sourceDirectory = dir;
+ sourceDirectory += "/.git";
+ if ( cmSystemTools::FileExists(sourceDirectory.c_str()) )
+ {
+ return cmCTestUpdateHandler::e_GIT;
+ }
+ sourceDirectory = dir;
+ sourceDirectory += "/.hg";
+ if ( cmSystemTools::FileExists(sourceDirectory.c_str()) )
+ {
+ return cmCTestUpdateHandler::e_HG;
+ }
+ return cmCTestUpdateHandler::e_UNKNOWN;
+}
+
+//----------------------------------------------------------------------
+bool cmCTestUpdateHandler::SelectVCS()
+{
+ // Get update command
+ this->UpdateCommand = this->CTest->GetCTestConfiguration("UpdateCommand");
+
+ // Detect the VCS managing the source tree.
+ this->UpdateType = this->DetectVCS(this->GetOption("SourceDirectory"));
+ 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->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)
+ {
+ this->UpdateCommand = this->CTest->GetCTestConfiguration(key);
+ }
+ if (this->UpdateCommand.empty())
+ {
+ cmOStringStream e;
+ e << "Cannot find UpdateCommand ";
+ 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
new file mode 100644
index 000000000..55ec974dc
--- /dev/null
+++ b/Source/CTest/cmCTestUpdateHandler.h
@@ -0,0 +1,76 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#ifndef cmCTestUpdateHandler_h
+#define cmCTestUpdateHandler_h
+
+
+#include "cmCTestGenericHandler.h"
+#include "cmListFileCache.h"
+
+#if defined(__sgi) && !defined(__GNUC__)
+# pragma set woff 1375 /* base class destructor not virtual */
+#endif
+
+/** \class cmCTestUpdateHandler
+ * \brief A class that handles ctest -S invocations
+ *
+ */
+class cmCTestUpdateHandler : public cmCTestGenericHandler
+{
+public:
+ cmTypeMacro(cmCTestUpdateHandler, cmCTestGenericHandler);
+
+ /*
+ * The main entry point for this class
+ */
+ int ProcessHandler();
+
+ cmCTestUpdateHandler();
+
+ enum {
+ e_UNKNOWN = 0,
+ e_CVS,
+ e_SVN,
+ e_BZR,
+ e_GIT,
+ e_HG,
+ e_LAST
+ };
+
+ /**
+ * Initialize handler
+ */
+ virtual void Initialize();
+
+private:
+ // Some structures needed for update
+ 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);
+
+ // The VCS command to update the working tree.
+ std::string UpdateCommand;
+ int UpdateType;
+
+ int DetectVCS(const char* dir);
+ 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
new file mode 100644
index 000000000..731c1c7a3
--- /dev/null
+++ b/Source/CTest/cmCTestUploadCommand.cxx
@@ -0,0 +1,69 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestUploadCommand.h"
+
+#include "cmCTest.h"
+#include "cmCTestGenericHandler.h"
+#include "cmCTestUploadHandler.h"
+
+cmCTestGenericHandler* cmCTestUploadCommand::InitializeHandler()
+{
+ cmCTestGenericHandler* handler
+ = this->CTest->GetInitializedHandler("upload");
+ if ( !handler )
+ {
+ this->SetError("internal CTest error. Cannot instantiate upload handler");
+ return 0;
+ }
+ static_cast<cmCTestUploadHandler*>(handler)->SetFiles(this->Files);
+
+ return handler;
+}
+
+
+//----------------------------------------------------------------------------
+bool cmCTestUploadCommand::CheckArgumentKeyword(std::string const& arg)
+{
+ if(arg == "FILES")
+ {
+ this->ArgumentDoing = ArgumentDoingFiles;
+ 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);
+ 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;
+ }
+ }
+
+ // Look for other arguments.
+ return this->Superclass::CheckArgumentValue(arg);
+}
diff --git a/Source/CTest/cmCTestUploadCommand.h b/Source/CTest/cmCTestUploadCommand.h
new file mode 100644
index 000000000..62f379f6d
--- /dev/null
+++ b/Source/CTest/cmCTestUploadCommand.h
@@ -0,0 +1,85 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestUploadCommand_h
+#define cmCTestUploadCommand_h
+
+#include "cmCTestHandlerCommand.h"
+#include "cmCTest.h"
+
+/** \class cmCTestUpload
+ * \brief Run a ctest script
+ *
+ * cmCTestUploadCommand defines the command to upload result files for
+ * the project.
+ */
+class cmCTestUploadCommand : public cmCTestHandlerCommand
+{
+public:
+
+ cmCTestUploadCommand()
+ {
+ }
+
+ /**
+ * This is a virtual constructor for the command.
+ */
+ virtual cmCommand* Clone()
+ {
+ 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.";
+ }
+
+ /**
+ * 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);
+
+protected:
+ cmCTestGenericHandler* InitializeHandler();
+
+ virtual bool CheckArgumentKeyword(std::string const& arg);
+ virtual bool CheckArgumentValue(std::string const& arg);
+
+ enum
+ {
+ ArgumentDoingFiles = Superclass::ArgumentDoingLast1,
+ ArgumentDoingLast2
+ };
+
+ cmCTest::SetOfStrings Files;
+};
+
+
+#endif
diff --git a/Source/CTest/cmCTestUploadHandler.cxx b/Source/CTest/cmCTestUploadHandler.cxx
new file mode 100644
index 000000000..caf2e5370
--- /dev/null
+++ b/Source/CTest/cmCTestUploadHandler.cxx
@@ -0,0 +1,77 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestUploadHandler.h"
+
+#include "cmGeneratedFileStream.h"
+#include "cmVersion.h"
+#include "cmXMLSafe.h"
+
+//----------------------------------------------------------------------------
+cmCTestUploadHandler::cmCTestUploadHandler()
+{
+ this->Initialize();
+}
+
+//----------------------------------------------------------------------------
+void cmCTestUploadHandler::Initialize()
+{
+ this->Superclass::Initialize();
+ this->Files.clear();
+}
+
+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);
+ return -1;
+ }
+
+ 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";
+ return 0;
+}
diff --git a/Source/CTest/cmCTestUploadHandler.h b/Source/CTest/cmCTestUploadHandler.h
new file mode 100644
index 000000000..23ed35a25
--- /dev/null
+++ b/Source/CTest/cmCTestUploadHandler.h
@@ -0,0 +1,45 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestUploadHandler_h
+#define cmCTestUploadHandler_h
+
+#include "cmCTestGenericHandler.h"
+
+/** \class cmCTestUploadHandler
+ * \brief Helper class for CTest
+ *
+ * Submit arbitrary files
+ *
+ */
+class cmCTestUploadHandler : public cmCTestGenericHandler
+{
+public:
+ cmTypeMacro(cmCTestUploadHandler, cmCTestGenericHandler);
+
+ cmCTestUploadHandler();
+ ~cmCTestUploadHandler() {}
+
+ /*
+ * The main entry point for this class
+ */
+ int ProcessHandler();
+
+ void Initialize();
+
+ /** Specify a set of files to submit. */
+ void SetFiles(cmCTest::SetOfStrings const& files);
+
+private:
+ cmCTest::SetOfStrings Files;
+};
+
+#endif
diff --git a/Source/CTest/cmCTestVC.cxx b/Source/CTest/cmCTestVC.cxx
new file mode 100644
index 000000000..fbee2272e
--- /dev/null
+++ b/Source/CTest/cmCTestVC.cxx
@@ -0,0 +1,241 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#include "cmCTestVC.h"
+
+#include "cmCTest.h"
+#include "cmSystemTools.h"
+#include "cmXMLSafe.h"
+
+#include <cmsys/Process.h>
+
+//----------------------------------------------------------------------------
+cmCTestVC::cmCTestVC(cmCTest* ct, std::ostream& log): CTest(ct), Log(log)
+{
+ this->PathCount[PathUpdated] = 0;
+ this->PathCount[PathModified] = 0;
+ this->PathCount[PathConflicting] = 0;
+ this->Unknown.Date = "Unknown";
+ this->Unknown.Author = "Unknown";
+ 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,
+ " First perform the initial checkout: " << command << "\n");
+
+ // Make the parent directory in which to perform the checkout.
+ std::string parent = cmSystemTools::GetFilenamePath(this->SourceDirectory);
+ cmCTestLog(this->CTest, HANDLER_OUTPUT,
+ " Perform checkout in directory: " << parent << "\n");
+ 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<char const*> vc_co;
+ for(std::vector<cmStdString>::const_iterator ai = args.begin();
+ ai != args.end(); ++ai)
+ {
+ vc_co.push_back(ai->c_str());
+ }
+ vc_co.push_back(0);
+
+ // Run the initial checkout command and log its output.
+ this->Log << "--- Begin Initial Checkout ---\n";
+ OutputLogger out(this->Log, "co-out> ");
+ 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);
+ }
+ return result;
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestVC::RunChild(char const* const* cmd, OutputParser* out,
+ OutputParser* err, const char* workDir)
+{
+ this->Log << this->ComputeCommandLine(cmd) << "\n";
+
+ cmsysProcess* cp = cmsysProcess_New();
+ cmsysProcess_SetCommand(cp, cmd);
+ workDir = workDir? workDir : this->SourceDirectory.c_str();
+ cmsysProcess_SetWorkingDirectory(cp, workDir);
+ this->RunProcess(cp, out, err);
+ int result = cmsysProcess_GetExitValue(cp);
+ cmsysProcess_Delete(cp);
+ return result == 0;
+}
+
+//----------------------------------------------------------------------------
+std::string cmCTestVC::ComputeCommandLine(char const* const* cmd)
+{
+ cmOStringStream line;
+ const char* sep = "";
+ 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)
+{
+ // Report the command line.
+ this->UpdateCommandLine = this->ComputeCommandLine(cmd);
+ if(this->CTest->GetShowOnly())
+ {
+ this->Log << this->UpdateCommandLine << "\n";
+ return true;
+ }
+
+ // Run the command.
+ return this->RunChild(cmd, out, err);
+}
+
+//----------------------------------------------------------------------------
+std::string cmCTestVC::GetNightlyTime()
+{
+ // Get the nightly start time corresponding to the current dau.
+ struct tm* t = this->CTest->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);
+ return std::string(current_time);
+}
+
+//----------------------------------------------------------------------------
+void cmCTestVC::Cleanup()
+{
+ this->Log << "--- Begin Cleanup ---\n";
+ this->CleanupImpl();
+ 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();
+ return result;
+}
+
+//----------------------------------------------------------------------------
+void cmCTestVC::NoteOldRevision()
+{
+ // We do nothing by default.
+}
+
+//----------------------------------------------------------------------------
+void cmCTestVC::NoteNewRevision()
+{
+ // We do nothing by default.
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestVC::UpdateImpl()
+{
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "* Unknown VCS tool, not updating!" << std::endl);
+ return true;
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestVC::WriteXML(std::ostream& xml)
+{
+ this->Log << "--- Begin Revisions ---\n";
+ bool result = this->WriteXMLUpdates(xml);
+ this->Log << "--- End Revisions ---\n";
+ return result;
+}
+
+//----------------------------------------------------------------------------
+bool cmCTestVC::WriteXMLUpdates(std::ostream&)
+{
+ 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,
+ 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";
+ ++this->PathCount[f.Status];
+}
diff --git a/Source/CTest/cmCTestVC.h b/Source/CTest/cmCTestVC.h
new file mode 100644
index 000000000..44e1dacbb
--- /dev/null
+++ b/Source/CTest/cmCTestVC.h
@@ -0,0 +1,133 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmCTestVC_h
+#define cmCTestVC_h
+
+#include "cmProcessTools.h"
+
+class cmCTest;
+
+/** \class cmCTestVC
+ * \brief Base class for version control system handlers
+ *
+ */
+class cmCTestVC: public cmProcessTools
+{
+public:
+ /** Construct with a CTest instance and update log stream. */
+ cmCTestVC(cmCTest* ctest, std::ostream& log);
+
+ virtual ~cmCTestVC();
+
+ /** Command line tool to invoke. */
+ void SetCommandLineTool(std::string const& tool);
+
+ /** Top-level source directory. */
+ void SetSourceDirectory(std::string const& dir);
+
+ /** Get the date/time specification for the current nightly start time. */
+ std::string GetNightlyTime();
+
+ /** Prepare the work tree. */
+ bool InitialCheckout(const char* command);
+
+ /** Perform cleanup operations on the work tree. */
+ void Cleanup();
+
+ /** Update the working tree to the new revision. */
+ bool Update();
+
+ /** Get the command line used by the Update method. */
+ std::string const& GetUpdateCommandLine() const
+ { return this->UpdateCommandLine; }
+
+ /** Write Update.xml entries for the updates found. */
+ bool WriteXML(std::ostream& xml);
+
+ /** Enumerate non-trivial working tree states during update. */
+ 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]; }
+
+protected:
+ // Internal API to be implemented by subclasses.
+ virtual void CleanupImpl();
+ virtual void NoteOldRevision();
+ virtual bool UpdateImpl();
+ virtual void NoteNewRevision();
+ virtual bool WriteXMLUpdates(std::ostream& xml);
+
+ /** Basic information about one revision of a tree or file. */
+ struct Revision
+ {
+ std::string Rev;
+ std::string Date;
+ std::string Author;
+ std::string EMail;
+ std::string Committer;
+ std::string CommitterEMail;
+ std::string CommitDate;
+ std::string Log;
+ };
+
+ struct File;
+ friend struct File;
+
+ /** Represent change to one file. */
+ struct File
+ {
+ 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) {}
+ };
+
+ /** 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);
+
+ /** Run VC update command line and send output to given parsers. */
+ bool RunUpdateCommand(char const* const* cmd,
+ OutputParser* out, OutputParser* err = 0);
+
+ /** Write xml element for one file. */
+ void WriteXMLEntry(std::ostream& xml, std::string const& path,
+ std::string const& name, std::string const& full,
+ File const& f);
+
+ // Instance of cmCTest running the script.
+ cmCTest* CTest;
+
+ // A stream to which we write log information.
+ std::ostream& Log;
+
+ // Basic information about the working tree.
+ std::string CommandLineTool;
+ std::string SourceDirectory;
+
+ // Record update command info.
+ std::string UpdateCommandLine;
+
+ // Placeholder for unknown revisions.
+ Revision Unknown;
+
+ // Count paths reported with each PathStatus value.
+ int PathCount[3];
+};
+
+#endif
diff --git a/Source/CTest/cmParseCacheCoverage.cxx b/Source/CTest/cmParseCacheCoverage.cxx
new file mode 100644
index 000000000..137f344df
--- /dev/null
+++ b/Source/CTest/cmParseCacheCoverage.cxx
@@ -0,0 +1,220 @@
+#include "cmStandardIncludes.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include "cmSystemTools.h"
+#include "cmParseCacheCoverage.h"
+#include <cmsys/Directory.hxx>
+#include <cmsys/Glob.hxx>
+
+
+cmParseCacheCoverage::cmParseCacheCoverage(
+ 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))
+ {
+ return false;
+ }
+ size_t numf;
+ unsigned int i;
+ numf = dir.GetNumberOfFiles();
+ for (i = 0; i < numf; i++)
+ {
+ std::string file = dir.GetFile(i);
+ if(file != "." && file != ".."
+ && !cmSystemTools::FileIsDirectory(file.c_str()))
+ {
+ std::string path = d;
+ path += "/";
+ path += file;
+ if(cmSystemTools::GetFilenameLastExtension(path) == ".cmcov")
+ {
+ if(!this->ReadCMCovFile(path.c_str()))
+ {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+// not currently used, but leave it in case we want it in the future
+void cmParseCacheCoverage::RemoveUnCoveredFiles()
+{
+ // loop over the coverage data computed and remove all files
+ // 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;
+ bool nothing = true;
+ 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);
+ this->Coverage.TotalCoverage.erase(ci++);
+ }
+ else
+ {
+ ++ci;
+ }
+ }
+}
+
+bool cmParseCacheCoverage::SplitString(std::vector<std::string>& args,
+ std::string const& line)
+{
+ std::string::size_type pos1 = 0;
+ std::string::size_type pos2 = line.find(',', 0);
+ if(pos2 == std::string::npos)
+ {
+ return false;
+ }
+ std::string arg;
+ while(pos2 != std::string::npos)
+ {
+ arg = line.substr(pos1, pos2-pos1);
+ args.push_back(arg);
+ pos1 = pos2+1;
+ pos2 = line.find(',',pos1);
+ }
+ arg = line.substr(pos1);
+ args.push_back(arg);
+ return true;
+}
+
+bool cmParseCacheCoverage::ReadCMCovFile(const char* file)
+{
+ std::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");
+ return false;
+ }
+ separateLine.clear();
+ this->SplitString(separateLine, line);
+ 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");
+ }
+ std::string routine;
+ std::string filepath;
+ 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)
+ {
+ 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] << " ");
+ }
+ 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)
+ {
+ routine = separateLine[0];
+ // Find the full path to the file
+ if(!this->FindMumpsFile(routine, filepath))
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "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
+ {
+ // Totals in arg 0 marks the end of a routine
+ 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)
+ {
+ 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;
+ int count = atoi(separateLine[2].c_str());
+ if(linenumber > coverageVector.size())
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Parse error line is greater than number of lines in file: "
+ << 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())
+ {
+ coverageVector.push_back(-1);
+ }
+ coverageVector[linenumber] += count;
+ }
+ return true;
+}
diff --git a/Source/CTest/cmParseCacheCoverage.h b/Source/CTest/cmParseCacheCoverage.h
new file mode 100644
index 000000000..114eb92d3
--- /dev/null
+++ b/Source/CTest/cmParseCacheCoverage.h
@@ -0,0 +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.
+============================================================================*/
+
+#ifndef cmParseCacheCoverage_h
+#define cmParseCacheCoverage_h
+
+#include "cmParseMumpsCoverage.h"
+
+/** \class cmParseCacheCoverage
+ * \brief Parse Cache coverage information
+ *
+ * This class is used to parse Cache coverage information for
+ * mumps.
+ */
+class cmParseCacheCoverage : public cmParseMumpsCoverage
+{
+public:
+ cmParseCacheCoverage(cmCTestCoverageHandlerContainer& cont,
+ cmCTest* ctest);
+protected:
+ // implement virtual from parent
+ bool LoadCoverageData(const char* dir);
+ // 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);
+};
+
+
+#endif
diff --git a/Source/CTest/cmParseGTMCoverage.cxx b/Source/CTest/cmParseGTMCoverage.cxx
new file mode 100644
index 000000000..5dfcfe50a
--- /dev/null
+++ b/Source/CTest/cmParseGTMCoverage.cxx
@@ -0,0 +1,272 @@
+#include "cmStandardIncludes.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include "cmSystemTools.h"
+#include "cmParseGTMCoverage.h"
+#include <cmsys/Directory.hxx>
+#include <cmsys/Glob.hxx>
+
+
+cmParseGTMCoverage::cmParseGTMCoverage(cmCTestCoverageHandlerContainer& cont,
+ cmCTest* 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))
+ {
+ return false;
+ }
+ size_t numf;
+ unsigned int i;
+ numf = dir.GetNumberOfFiles();
+ for (i = 0; i < numf; i++)
+ {
+ std::string file = dir.GetFile(i);
+ if(file != "." && file != ".."
+ && !cmSystemTools::FileIsDirectory(file.c_str()))
+ {
+ std::string path = d;
+ path += "/";
+ path += file;
+ 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)
+ {
+ return false;
+ }
+ std::string line;
+ std::string lastfunction;
+ std::string lastroutine;
+ std::string lastpath;
+ int lastoffset = 0;
+ while( cmSystemTools::GetLineFromStream(in, line))
+ {
+ // only look at lines that have coverage data
+ if(line.find("^ZZCOVERAGE") == line.npos)
+ {
+ continue;
+ }
+ std::string filepath;
+ std::string function;
+ std::string routine;
+ int linenumber = 0;
+ int count = 0;
+ this->ParseMCOVLine(line, routine, function, linenumber, count);
+ // skip this one
+ 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;
+ }
+ // Find the full path to the file
+ bool found = this->FindMumpsFile(routine, filepath);
+ if(found)
+ {
+ int lineoffset;
+ if(this->FindFunctionInMumpsFile(filepath,
+ function,
+ lineoffset))
+ {
+ cmCTestCoverageHandlerContainer::SingleFileCoverageVector&
+ coverageVector = this->Coverage.TotalCoverage[filepath];
+ 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");
+ }
+ lastfunction = function;
+ lastroutine = routine;
+ lastpath = filepath;
+ }
+ return true;
+}
+
+bool cmParseGTMCoverage::FindFunctionInMumpsFile(std::string const& filepath,
+ std::string const& function,
+ int& lineoffset)
+{
+ std::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)
+ {
+ char nextchar = line[function.size()];
+ if(nextchar == ' ' || nextchar == '(')
+ {
+ lineoffset = linenum;
+ return true;
+ }
+ }
+ if(pos == 1)
+ {
+ char prevchar = line[0];
+ char nextchar = line[function.size()+1];
+ if(prevchar == '%' && (nextchar == ' ' || nextchar == '('))
+ {
+ lineoffset = linenum;
+ return true;
+ }
+ }
+ linenum++; // move to next line count
+ }
+ lineoffset = 0;
+ 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,
+ int& count)
+{
+ // this method parses lines from the .mcov file
+ // each line has ^COVERAGE(...) in it, and there
+ // are several varients of coverage lines:
+ //
+ // ^COVERAGE("DIC11","PR1",0)="2:0:0:0"
+ // ( file , entry, line ) = "number_executed:timing_info"
+ // ^COVERAGE("%RSEL","SRC")="1:0:0:0"
+ // ( 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::string::size_type pos = line.find('(', 0);
+ // if no ( is found, then return line has no coverage
+ 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)
+ {
+ // save the char we are looking at
+ char cur = line[pos];
+ // , or ) means end of argument
+ 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 == ')')
+ {
+ done = true;
+ }
+ }
+ else
+ {
+ // all chars except ", (, and % get stored in the arg string
+ 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)
+ {
+ 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] != '\"')
+ {
+ count = atoi(line.substr(pos).c_str());
+ }
+ else
+ {
+ // this means line[pos] is a ", and we have a
+ // COVERAGE(...)="1:0:0:0" type of line
+ pos++; // move past "
+ // find the first : past the "
+ 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());
+ }
+ // less then two arguments is an error
+ 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
+ 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
+ {
+ // 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
new file mode 100644
index 000000000..c6d7ef919
--- /dev/null
+++ b/Source/CTest/cmParseGTMCoverage.h
@@ -0,0 +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.
+============================================================================*/
+
+#ifndef cmParseGTMCoverage_h
+#define cmParseGTMCoverage_h
+
+#include "cmParseMumpsCoverage.h"
+
+/** \class cmParseGTMCoverage
+ * \brief Parse GTM coverage information
+ *
+ * This class is used to parse GTM coverage information for
+ * mumps.
+ */
+class cmParseGTMCoverage : public cmParseMumpsCoverage
+{
+public:
+ cmParseGTMCoverage(cmCTestCoverageHandlerContainer& cont,
+ cmCTest* ctest);
+protected:
+ // implement virtual from parent
+ bool LoadCoverageData(const char* dir);
+ // 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);
+ // 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);
+};
+
+
+#endif
diff --git a/Source/CTest/cmParseMumpsCoverage.cxx b/Source/CTest/cmParseMumpsCoverage.cxx
new file mode 100644
index 000000000..37e8bd082
--- /dev/null
+++ b/Source/CTest/cmParseMumpsCoverage.cxx
@@ -0,0 +1,165 @@
+#include "cmStandardIncludes.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include "cmSystemTools.h"
+#include "cmParseGTMCoverage.h"
+#include <cmsys/Directory.hxx>
+#include <cmsys/Glob.hxx>
+
+
+cmParseMumpsCoverage::cmParseMumpsCoverage(
+ cmCTestCoverageHandlerContainer& cont,
+ cmCTest* ctest)
+ :Coverage(cont), CTest(ctest)
+{
+}
+
+cmParseMumpsCoverage::~cmParseMumpsCoverage()
+{
+}
+
+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)
+ {
+ return false;
+ }
+ std::string line;
+ while(cmSystemTools::GetLineFromStream(in, line))
+ {
+ std::string::size_type pos = line.find(':', 0);
+ std::string packages;
+ if(pos != std::string::npos)
+ {
+ std::string type = line.substr(0, pos);
+ std::string path = line.substr(pos+1);
+ if(type == "packages")
+ {
+ this->LoadPackages(path.c_str());
+ }
+ else if(type == "coverage_dir")
+ {
+ this->LoadCoverageData(path.c_str());
+ }
+ else
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Parse Error in Mumps coverage file :\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)
+ {
+ return;
+ }
+ std::string 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) )
+ {
+ // 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] == ';')
+ {
+ 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'))
+ {
+ i++;
+ }
+ }
+ // (3) If the character found is not a semicolon then the line counts for
+ // coverage.
+ if(i < line.size() && line[i] != ';')
+ {
+ val = 0;
+ }
+ }
+ coverageVector.push_back(val);
+ }
+}
+
+bool cmParseMumpsCoverage::LoadPackages(const char* d)
+{
+ cmsys::Glob glob;
+ glob.RecurseOn();
+ std::string pat = d;
+ pat += "/*.m";
+ glob.FindFiles(pat.c_str());
+ std::vector<std::string>& files = glob.GetFiles();
+ std::vector<std::string>::iterator fileIt;
+ for ( fileIt = files.begin(); fileIt != files.end();
+ ++ fileIt )
+ {
+ std::string name = cmSystemTools::GetFilenameName(*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 =
+ this->RoutineToDirectory.find(routine);
+ 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
new file mode 100644
index 000000000..c1effa79b
--- /dev/null
+++ b/Source/CTest/cmParseMumpsCoverage.h
@@ -0,0 +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.
+============================================================================*/
+
+#ifndef cmParseMumpsCoverage_h
+#define cmParseMumpsCoverage_h
+
+#include "cmStandardIncludes.h"
+#include "cmCTestCoverageHandler.h"
+
+/** \class cmParseMumpsCoverage
+ * \brief Parse Mumps coverage information
+ *
+ * This class is used as the base class for Mumps coverage
+ * parsing.
+ */
+class cmParseMumpsCoverage
+{
+public:
+ 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
+ virtual bool LoadCoverageData(const char* d) = 0;
+ // search the package directory for mumps files and fill
+ // in the RoutineToDirectory map
+ bool LoadPackages(const char* dir);
+ // 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);
+protected:
+ std::map<cmStdString, cmStdString> RoutineToDirectory;
+ cmCTestCoverageHandlerContainer& Coverage;
+ cmCTest* CTest;
+};
+
+#endif
diff --git a/Source/CTest/cmParsePHPCoverage.cxx b/Source/CTest/cmParsePHPCoverage.cxx
new file mode 100644
index 000000000..593b2d1a8
--- /dev/null
+++ b/Source/CTest/cmParsePHPCoverage.cxx
@@ -0,0 +1,253 @@
+#include "cmStandardIncludes.h"
+#include "cmSystemTools.h"
+#include "cmParsePHPCoverage.h"
+#include <cmsys/Directory.hxx>
+
+/*
+ To setup coverage for php.
+
+ - edit php.ini to add auto prepend and append php files from phpunit
+ auto_prepend_file =
+ auto_append_file =
+ - run the tests
+ - run this program on all the files in c:/tmp
+
+*/
+
+cmParsePHPCoverage::cmParsePHPCoverage(cmCTestCoverageHandlerContainer& cont,
+ cmCTest* ctest)
+ :Coverage(cont), CTest(ctest)
+{
+}
+
+bool cmParsePHPCoverage::ReadUntil(std::ifstream& in, char until)
+{
+ char c = 0;
+ while(in.get(c) && c != until)
+ {
+ }
+ if(c != until)
+ {
+ return false;
+ }
+ return true;
+}
+bool cmParsePHPCoverage::ReadCoverageArray(std::ifstream& in,
+ cmStdString const& 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)
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "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 ");
+ return false;
+ }
+ if(!in.get(c) && c == '{')
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "failed to read open {\n");
+ return false;
+ }
+ for(int i =0; i < size; i++)
+ {
+ this->ReadUntil(in, ':');
+ int line = 0;
+ this->ReadInt(in, line);
+ // ok xdebug may have a bug here
+ // it seems to be 1 based but often times
+ // seems to have a 0'th line.
+ line--;
+ 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) )
+ {
+ 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
+ // assume less than 0 is just not covered
+ // 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)
+ {
+ value = 0;
+ }
+ // if unset then set it to value
+ if(coverageVector[line] == -1)
+ {
+ coverageVector[line] = value;
+ }
+ // otherwise increment by value
+ else
+ {
+ coverageVector[line] += value;
+ }
+ }
+ return true;
+}
+
+bool cmParsePHPCoverage::ReadInt(std::ifstream& in, int& v)
+{
+ std::string s;
+ char c = 0;
+ while(in.get(c) && c != ':' && c != ';')
+ {
+ s += c;
+ }
+ v = atoi(s.c_str());
+ return true;
+}
+
+bool cmParsePHPCoverage::ReadArraySize(std::ifstream& in, int& size)
+{
+ char c = 0;
+ in.get(c);
+ if(c != 'a')
+ {
+ return false;
+ }
+ if(in.get(c) && c == ':')
+ {
+ if(this->ReadInt(in, size))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool cmParsePHPCoverage::ReadFileInformation(std::ifstream& in)
+{
+ char buf[4];
+ in.read(buf, 2);
+ buf[2] = 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))
+ {
+ size++; // add one for null termination
+ char* s = new char[size+1];
+ // read open quote
+ 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;
+ // read close quote
+ 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) )
+ {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "failed to read coverage array for file: "
+ << fileName << "\n");
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+
+bool cmParsePHPCoverage::ReadPHPData(const char* file)
+{
+ std::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");
+ return false;
+ }
+ 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");
+ return false;
+ }
+ }
+ return true;
+}
+
+bool cmParsePHPCoverage::ReadPHPCoverageDirectory(const char* d)
+{
+ cmsys::Directory dir;
+ if(!dir.Load(d))
+ {
+ return false;
+ }
+ size_t numf;
+ unsigned int i;
+ numf = dir.GetNumberOfFiles();
+ for (i = 0; i < numf; i++)
+ {
+ std::string file = dir.GetFile(i);
+ if(file != "." && file != ".."
+ && !cmSystemTools::FileIsDirectory(file.c_str()))
+ {
+ std::string path = d;
+ path += "/";
+ path += file;
+ if(!this->ReadPHPData(path.c_str()))
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+}
diff --git a/Source/CTest/cmParsePHPCoverage.h b/Source/CTest/cmParsePHPCoverage.h
new file mode 100644
index 000000000..d50a83c58
--- /dev/null
+++ b/Source/CTest/cmParsePHPCoverage.h
@@ -0,0 +1,45 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#ifndef cmParsePHPCoverage_h
+#define cmParsePHPCoverage_h
+
+#include "cmStandardIncludes.h"
+#include "cmCTestCoverageHandler.h"
+
+/** \class cmParsePHPCoverage
+ * \brief Parse xdebug PHP coverage information
+ *
+ * This class is used to parse php coverage information produced
+ * by xdebug. The data is stored as a php dump of the array
+ * return by xdebug coverage. It is an array of arrays.
+ */
+class cmParsePHPCoverage
+{
+public:
+ 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);
+ cmCTestCoverageHandlerContainer& Coverage;
+ cmCTest* CTest;
+};
+
+
+#endif
diff --git a/Source/CTest/cmProcess.cxx b/Source/CTest/cmProcess.cxx
new file mode 100644
index 000000000..0ee631fe9
--- /dev/null
+++ b/Source/CTest/cmProcess.cxx
@@ -0,0 +1,272 @@
+/*============================================================================
+ 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.
+============================================================================*/
+
+#include <cmProcess.h>
+#include <cmSystemTools.h>
+
+cmProcess::cmProcess()
+{
+ this->Process = 0;
+ this->Timeout = 0;
+ this->TotalTime = 0;
+ this->ExitValue = 0;
+ this->Id = 0;
+ this->StartTime = 0;
+}
+
+cmProcess::~cmProcess()
+{
+ cmsysProcess_Delete(this->Process);
+}
+void cmProcess::SetCommand(const char* command)
+{
+ this->Command = command;
+}
+
+void cmProcess::SetCommandArguments(std::vector<std::string> const& args)
+{
+ this->Arguments = args;
+}
+
+bool cmProcess::StartProcess()
+{
+ if(this->Command.size() == 0)
+ {
+ 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)
+ {
+ this->ProcessArgs.push_back(i->c_str());
+ }
+ this->ProcessArgs.push_back(0); // null terminate the list
+ this->Process = cmsysProcess_New();
+ cmsysProcess_SetCommand(this->Process, &*this->ProcessArgs.begin());
+ if(this->WorkingDirectory.size())
+ {
+ cmsysProcess_SetWorkingDirectory(this->Process,
+ this->WorkingDirectory.c_str());
+ }
+ cmsysProcess_SetTimeout(this->Process, this->Timeout);
+ cmsysProcess_Execute(this->Process);
+ 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')
+ {
+ // 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 --;
+ }
+ line.assign(text, length);
+
+ // Start a new range for the next line.
+ ++this->Last;
+ this->First = Last;
+
+ // Return the line extracted.
+ return true;
+ }
+ }
+
+ // Available data have been exhausted without a newline.
+ 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())
+ {
+ 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(;;)
+ {
+ // Look for lines already buffered.
+ if(this->StdOut.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)
+ {
+ 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
+ {
+ // The process will provide no more data.
+ break;
+ }
+ }
+
+ // Look for partial last lines.
+ if(this->StdOut.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))
+ {
+ return cmsysProcess_Pipe_Timeout;
+ }
+
+ // Record exit information.
+ this->ExitValue = cmsysProcess_GetExitValue(this->Process);
+ this->TotalTime = cmSystemTools::GetTime() - this->StartTime;
+ // std::cerr << "Time to run: " << this->TotalTime << "\n";
+ return cmsysProcess_Pipe_None;
+}
+
+// return the process status
+int cmProcess::GetProcessStatus()
+{
+ 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:
+ {
+ std::cerr << "cmProcess: Error executing " << this->Command
+ << " process: "
+ << cmsysProcess_GetErrorString(this->Process)
+ << "\n";
+ } break;
+ case cmsysProcess_State_Exception:
+ {
+ std::cerr << "cmProcess: " << this->Command
+ << " process exited with an exception: ";
+ switch(cmsysProcess_GetExitException(this->Process))
+ {
+ case cmsysProcess_Exception_None:
+ {
+ std::cerr << "None";
+ } break;
+ case cmsysProcess_Exception_Fault:
+ {
+ std::cerr << "Segmentation fault";
+ } break;
+ case cmsysProcess_Exception_Illegal:
+ {
+ std::cerr << "Illegal instruction";
+ } break;
+ case cmsysProcess_Exception_Interrupt:
+ {
+ std::cerr << "Interrupted by user";
+ } break;
+ case cmsysProcess_Exception_Numerical:
+ {
+ std::cerr << "Numerical exception";
+ } break;
+ case cmsysProcess_Exception_Other:
+ {
+ std::cerr << "Unknown";
+ } break;
+ }
+ std::cerr << "\n";
+ } 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:
+ {
+ std::cerr << "cmProcess: killed " << this->Command
+ << " process due to timeout.\n";
+ } break;
+ case cmsysProcess_State_Killed:
+ {
+ std::cerr << "cmProcess: killed " << this->Command << " process.\n";
+ } break;
+ }
+ return result;
+
+}
+
+
+int cmProcess::GetExitException()
+{
+ return cmsysProcess_GetExitException(this->Process);
+}
diff --git a/Source/CTest/cmProcess.h b/Source/CTest/cmProcess.h
new file mode 100644
index 000000000..ff99ca23d
--- /dev/null
+++ b/Source/CTest/cmProcess.h
@@ -0,0 +1,82 @@
+/*============================================================================
+ 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.
+============================================================================*/
+#ifndef cmProcess_h
+#define cmProcess_h
+
+
+#include "cmStandardIncludes.h"
+#include <cmsys/Process.h>
+
+
+/** \class cmProcess
+ * \brief run a process with c++
+ *
+ * cmProcess wraps the kwsys process stuff in a c++ class.
+ */
+class cmProcess
+{
+public:
+ cmProcess();
+ ~cmProcess();
+ 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;}
+ // Return true if the process starts
+ bool StartProcess();
+
+ // return the process status
+ int GetProcessStatus();
+ // 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;}
+ 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_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>
+ {
+ // Half-open index range of partial line already scanned.
+ size_type First;
+ size_type Last;
+ public:
+ Buffer(): First(0), Last(0) {}
+ bool GetLine(std::string& line);
+ bool GetLast(std::string& line);
+ };
+ Buffer StdErr;
+ Buffer StdOut;
+ std::string Command;
+ std::string WorkingDirectory;
+ std::vector<std::string> Arguments;
+ std::vector<const char*> ProcessArgs;
+ std::string Output;
+ int Id;
+ int ExitValue;
+};
+
+#endif