ENH: refactored ctest. All testing is now parallel. If no -j option is specified, defaults to a MP level of 1 (non parallel)

This commit is contained in:
Zach Mullen 2009-08-26 12:09:06 -04:00
parent 1171bcfc69
commit 8ffd8d0a03
8 changed files with 347 additions and 524 deletions

View File

@ -23,12 +23,11 @@
cmCTestMultiProcessHandler::cmCTestMultiProcessHandler()
{
this->ParallelLevel = 1;
this->ProcessId = 0;
}
// Set the tests
void
cmCTestMultiProcessHandler::SetTests(TestMap& tests,
std::map<int,cmStdString>& testNames)
PropertiesMap& properties)
{
// set test run map to false for all
for(TestMap::iterator i = this->Tests.begin();
@ -38,15 +37,14 @@ cmCTestMultiProcessHandler::SetTests(TestMap& tests,
this->TestFinishMap[i->first] = false;
}
this->Tests = tests;
this->TestNames = testNames;
this->Properties = properties;
}
// Set the max number of tests that can be run at the same time.
void cmCTestMultiProcessHandler::SetParallelLevel(size_t l)
void cmCTestMultiProcessHandler::SetParallelLevel(size_t level)
{
this->ParallelLevel = l;
this->ParallelLevel = level < 1 ? 1 : level;
}
void cmCTestMultiProcessHandler::RunTests()
{
this->StartNextTests();
@ -59,65 +57,28 @@ void cmCTestMultiProcessHandler::RunTests()
while(this->CheckOutput())
{
}
for(std::map<int, cmStdString>::iterator i =
this->TestOutput.begin();
i != this->TestOutput.end(); ++i)
{
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
i->second << std::endl);
}
}
void cmCTestMultiProcessHandler::StartTestProcess(int test)
{
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
" test " << test << "\n");
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, test << ": "
<< " test " << test << "\n");
this->TestRunningMap[test] = true; // mark the test as running
// now remove the test itself
this->Tests.erase(test);
// now run the test
cmProcess* newp = new cmProcess;
newp->SetId(this->ProcessId);
newp->SetId(test);
newp->SetCommand(this->CTestCommand.c_str());
std::vector<std::string> args;
cmOStringStream width;
if(this->CTest->GetMaxTestNameWidth())
cmCTestRunTest* testRun = new cmCTestRunTest;
testRun->SetCTest(this->CTest);
testRun->SetTestHandler(this->TestHandler);
testRun->SetIndex(test);
testRun->SetTestProperties(this->Properties[test]);
if(testRun->StartTest())
{
args.push_back("-W");
width << this->CTest->GetMaxTestNameWidth();
args.push_back(width.str().c_str());
}
args.push_back("-I");
cmOStringStream strm;
strm << test << "," << test;
args.push_back(strm.str());
args.push_back("--parallel-cache");
args.push_back(this->CTestCacheFile.c_str());
args.push_back("--internal-ctest-parallel");
cmOStringStream strm2;
strm2 << test;
args.push_back(strm2.str());
if(this->CTest->GetExtraVerbose())
{
args.push_back("-VV");
}
newp->SetCommandArguments(args);
if(!newp->StartProcess())
{
cmCTestLog(this->CTest, ERROR_MESSAGE,
"Error starting " << newp->GetCommand() << "\n");
this->EndTest(newp);
this->RunningTests.insert(testRun);
}
else
{
this->RunningTests.insert(newp);
testRun->EndTest();
}
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
"ctest -I " << test << "\n");
this->ProcessId++;
}
bool cmCTestMultiProcessHandler::StartTest(int test)
@ -160,7 +121,7 @@ bool cmCTestMultiProcessHandler::StartTest(int test)
}
// This test was not able to start because it is waiting
// on depends to run
return false;
return false;
}
void cmCTestMultiProcessHandler::StartNextTests()
@ -195,7 +156,6 @@ void cmCTestMultiProcessHandler::StartNextTests()
}
}
bool cmCTestMultiProcessHandler::CheckOutput()
{
// no more output we are done
@ -203,82 +163,48 @@ bool cmCTestMultiProcessHandler::CheckOutput()
{
return false;
}
std::vector<cmProcess*> finished;
std::vector<cmCTestRunTest*> finished;
std::string out, err;
for(std::set<cmProcess*>::const_iterator i = this->RunningTests.begin();
for(std::set<cmCTestRunTest*>::const_iterator i = this->RunningTests.begin();
i != this->RunningTests.end(); ++i)
{
cmProcess* p = *i;
int pipe = p->CheckOutput(.1, out, err);
if(pipe == cmsysProcess_Pipe_STDOUT)
{
cmCTestLog(this->CTest, HANDLER_OUTPUT,
p->GetId() << ": " << out << std::endl);
this->TestOutput[ p->GetId() ] += out;
this->TestOutput[ p->GetId() ] += "\n";
}
else if(pipe == cmsysProcess_Pipe_STDERR)
{
cmCTestLog(this->CTest, HANDLER_OUTPUT,
p->GetId() << ": " << err << std::endl);
this->TestOutput[ p->GetId() ] += err;
this->TestOutput[ p->GetId() ] += "\n";
}
cmCTestRunTest* p = *i;
p->CheckOutput(); //reads and stores the process output
if(!p->IsRunning())
{
finished.push_back(p);
}
}
for( std::vector<cmProcess*>::iterator i = finished.begin();
for( std::vector<cmCTestRunTest*>::iterator i = finished.begin();
i != finished.end(); ++i)
{
cmProcess* p = *i;
this->EndTest(p);
cmCTestRunTest* p = *i;
int test = p->GetIndex();
if(p->EndTest())
{
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);
delete p;
}
return true;
}
void cmCTestMultiProcessHandler::EndTest(cmProcess* p)
{
// Should have a way of getting this stuff from the
// launched ctest, maybe a temp file or some extra xml
// stuff in the stdout
// Need things like Reason and ExecutionTime, Path, etc.
int test = p->GetId();
int exitVal = p->GetExitValue();
cmCTestTestHandler::cmCTestTestResult cres;
cres.Properties = 0;
cres.ExecutionTime = p->GetTotalTime();
cres.ReturnValue = exitVal;
cres.Status = cmCTestTestHandler::COMPLETED;
cres.TestCount = test;
cres.Name = this->TestNames[test];
cres.Path = "";
if(exitVal)
{
cres.Status = cmCTestTestHandler::FAILED;
this->Failed->push_back(this->TestNames[test]);
}
else
{
this->Passed->push_back(this->TestNames[test]);
}
this->TestResults->push_back(cres);
// remove test from depend of all other tests
for(TestMap::iterator i = this->Tests.begin();
i!= this->Tests.end(); ++i)
{
i->second.erase(test);
}
this->TestFinishMap[test] = true;
this->TestRunningMap[test] = false;
this->RunningTests.erase(p);
delete p;
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
"finish test " << test << "\n");
}
void cmCTestMultiProcessHandler::PrintTests()
{
#undef cout

View File

@ -17,9 +17,9 @@
#ifndef cmCTestMultiProcessHandler_h
#define cmCTestMultiProcessHandler_h
class cmProcess;
#include <cmStandardIncludes.h>
#include <cmCTestTestHandler.h>
#include <cmCTestRunTest.h>
/** \class cmCTestMultiProcessHandler
* \brief run parallel ctest
@ -31,16 +31,18 @@ class cmCTestMultiProcessHandler
public:
struct TestSet : public std::set<int> {};
struct TestMap : public std::map<int, TestSet> {};
struct PropertiesMap : public
std::map<int, cmCTestTestHandler::cmCTestTestProperties*> {};
cmCTestMultiProcessHandler();
// Set the tests
void SetTests(TestMap& tests,
std::map<int, cmStdString>& testNames);
void SetTests(TestMap& tests, PropertiesMap& properties);
// Set the max number of tests that can be run at the same time.
void SetParallelLevel(size_t);
void RunTests();
void PrintTests();
void SetCTestCommand(const char* c) { this->CTestCommand = c;}
void SetTestCacheFile(const char* c) { this->CTestCacheFile = c;}
//void SetCTestCommand(const char* c) { this->CTestCommand = c;}
//void SetTestCacheFile(const char* c) { this->CTestCacheFile = c;}
void SetPassFailVectors(std::vector<cmStdString>* passed,
std::vector<cmStdString>* failed)
{
@ -51,7 +53,14 @@ public:
{
this->TestResults = r;
}
void SetCTest(cmCTest* ctest) { this->CTest = ctest;}
void SetTestHandler(cmCTestTestHandler * handler)
{ this->TestHandler = handler; }
cmCTestTestHandler * GetTestHandler()
{ return this->TestHandler; }
protected:
cmCTest* CTest;
// Start the next test or tests as many as are allowed by
@ -59,24 +68,25 @@ protected:
void StartNextTests();
void StartTestProcess(int test);
bool StartTest(int test);
void EndTest(cmProcess*);
//void EndTest(cmProcess*);
// Return true if there are still tests running
// check all running processes for output and exit case
bool CheckOutput();
// map from test number to set of depend tests
TestMap Tests;
std::map<int, cmStdString> TestNames;
//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::string CTestCommand;
std::string CTestCacheFile;
//std::string CTestCommand;
//std::string CTestCacheFile;
std::vector<cmStdString>* Passed;
std::vector<cmStdString>* Failed;
std::vector<cmCTestTestHandler::cmCTestTestResult>* TestResults;
int ProcessId;
size_t ParallelLevel; // max number of process that can be run at once
std::set<cmProcess*> RunningTests; // current running tests
std::set<cmCTestRunTest*> RunningTests; // current running tests
cmCTestTestHandler * TestHandler;
};
#endif

View File

@ -21,150 +21,52 @@
cmCTestRunTest::cmCTestRunTest()
{
this->OptimizeForCTest = true;
cmCTestTestHandler::cmCTestTestResult result;
this->TestResult = result;
}
cmCTestRunTest::~cmCTestRunTest()
{
}
void cmCTestRunTest::SetTestHandler(cmCTestTestHandler * handler)
bool cmCTestRunTest::IsRunning()
{
this->TestHandler = handler;
this->CTest = handler->CTest;
return this->TestProcess->IsRunning();
}
//----------------------------------------------------------------------
// Executes a test. Returns whether it passed or failed
bool cmCTestRunTest::Execute()
//---------------------------------------------------------
//waits .1 sec for output from this process.
void cmCTestRunTest::CheckOutput()
{
const std::string& testname = this->TestProperties->Name;
std::vector<std::string>& args = this->TestProperties->Args;
this->TestResult.Properties = this->TestProperties;
this->TestResult.ExecutionTime = 0;
this->TestResult.ReturnValue = -1;
this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
this->TestResult.TestCount = this->TestProperties->Index;
this->TestResult.Name = testname;
this->TestResult.Path = this->TestProperties->Directory.c_str();
cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(3)
<< this->TestProperties->Index << "/");
cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(3)
<< this->TestHandler->TotalNumberOfTests << " ");
if ( this->TestHandler->MemCheck )
std::string out, err;
int pipe = this->TestProcess->CheckOutput(.1, out, err);
if(pipe == cmsysProcess_Pipe_STDOUT)
{
cmCTestLog(this->CTest, HANDLER_OUTPUT, "Memory Check");
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
this->GetIndex() << ": " << out << std::endl);
this->ProcessOutput += out;
this->ProcessOutput += "\n";
}
else
else if(pipe == cmsysProcess_Pipe_STDERR)
{
cmCTestLog(this->CTest, HANDLER_OUTPUT, "Testing");
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
this->GetIndex() << ": " << err << std::endl);
this->ProcessOutput += err;
this->ProcessOutput += "\n";
}
cmCTestLog(this->CTest, HANDLER_OUTPUT, " ");
const int maxTestNameWidth = this->CTest->GetMaxTestNameWidth();
std::string outname = testname + " ";
outname.resize(maxTestNameWidth, '.');
*this->TestHandler->LogFile << this->TestProperties->Index << "/"
<< this->TestHandler->TotalNumberOfTests << " Testing: "
<< testname << std::endl;
}
if ( this->CTest->GetShowOnly() )
//---------------------------------------------------------
bool cmCTestRunTest::EndTest()
{
//restore the old environment
if (this->ModifyEnv)
{
cmCTestLog(this->CTest, HANDLER_OUTPUT, outname.c_str() << std::endl);
cmSystemTools::RestoreEnv(this->OrigEnv);
}
else
{
cmCTestLog(this->CTest, HANDLER_OUTPUT, outname.c_str());
}
cmCTestLog(this->CTest, DEBUG, "Testing " << args[0].c_str() << " ... ");
// find the test executable
std::string actualCommand
= this->TestHandler->FindTheExecutable(args[1].c_str());
std::string testCommand
= cmSystemTools::ConvertToOutputPath(actualCommand.c_str());
// continue if we did not find the executable
if (testCommand == "")
{
*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];
if ( !this->CTest->GetShowOnly() )
{
this->TestResult.FullCommandLine = actualCommand;
this->TestHandler->TestResults.push_back( this->TestResult );
return false;
}
}
// add the arguments
std::vector<std::string>::const_iterator j = args.begin();
++j; // skip test name
++j; // skip command as it is in actualCommand
std::vector<const char*> arguments;
this->TestHandler->GenerateTestCommand(arguments);
arguments.push_back(actualCommand.c_str());
for(;j != args.end(); ++j)
{
testCommand += " ";
testCommand += cmSystemTools::EscapeSpaces(j->c_str());
arguments.push_back(j->c_str());
}
arguments.push_back(0);
/**
* Run an executable command and put the stdout in output.
*/
std::string output;
int retVal = 0;
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl
<< (this->TestHandler->MemCheck?"MemCheck":"Test")
<< " command: " << testCommand
<< std::endl);
*this->TestHandler->LogFile << this->TestProperties->Index << "/"
<< this->TestHandler->TotalNumberOfTests
<< " Test: " << testname.c_str() << std::endl;
*this->TestHandler->LogFile << "Command: ";
std::vector<cmStdString>::size_type ll;
for ( ll = 0; ll < arguments.size()-1; ll ++ )
{
*this->TestHandler->LogFile << "\"" << arguments[ll] << "\" ";
}
*this->TestHandler->LogFile
<< std::endl
<< "Directory: " << this->TestProperties->Directory << std::endl
<< "\"" << testname.c_str() << "\" start time: "
<< this->CTest->CurrentTime() << std::endl
<< "Output:" << std::endl
<< "----------------------------------------------------------"
<< std::endl;
int res = 0;
double clock_start, clock_finish;
clock_start = cmSystemTools::GetTime();
if ( !this->CTest->GetShowOnly() )
{
res = this->RunTestProcess(arguments, &output, &retVal,
this->TestHandler->LogFile,
this->TestProperties->Timeout,
&this->TestProperties->Environment);
this->ProcessOutput = output; //save process output in the object
}
clock_finish = cmSystemTools::GetTime();
this->TestResult.ExecutionTime = (double)(clock_finish - clock_start);
this->TestResult.FullCommandLine = testCommand;
this->WriteLogOutputTop();
std::string reason;
bool passed = true;
int res = this->TestProcess->GetProcessStatus();
int retVal = this->TestProcess->GetExitValue();
if ( !this->CTest->GetShowOnly() )
{
std::vector<std::pair<cmsys::RegularExpression,
@ -177,7 +79,7 @@ bool cmCTestRunTest::Execute()
passIt != this->TestProperties->RequiredRegularExpressions.end();
++ passIt )
{
if ( passIt->first.find(output.c_str()) )
if ( passIt->first.find(this->ProcessOutput.c_str()) )
{
found = true;
reason = "Required regular expression found.";
@ -204,7 +106,7 @@ bool cmCTestRunTest::Execute()
passIt != this->TestProperties->ErrorRegularExpressions.end();
++ passIt )
{
if ( passIt->first.find(output.c_str()) )
if ( passIt->first.find(this->ProcessOutput.c_str()) )
{
reason = "Error regular expression found in output.";
reason += " Regex=[";
@ -214,7 +116,6 @@ bool cmCTestRunTest::Execute()
}
}
}
if (res == cmsysProcess_State_Exited)
{
bool success =
@ -224,13 +125,12 @@ bool cmCTestRunTest::Execute()
|| (!success && this->TestProperties->WillFail))
{
this->TestResult.Status = cmCTestTestHandler::COMPLETED;
cmCTestLog(this->CTest, HANDLER_OUTPUT, " Passed " );
cmCTestLog(this->CTest, HANDLER_OUTPUT, " Passed " );
}
else
{
this->TestResult.Status = cmCTestTestHandler::FAILED;
cmCTestLog(this->CTest, HANDLER_OUTPUT,
"***Failed " << reason );
cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Failed " << reason );
}
}
else if ( res == cmsysProcess_State_Expired )
@ -273,28 +173,29 @@ bool cmCTestRunTest::Execute()
passed = this->TestResult.Status == cmCTestTestHandler::COMPLETED;
char buf[1024];
sprintf(buf, "%6.2f sec", this->TestResult.ExecutionTime);
sprintf(buf, "%6.2f sec", this->TestProcess->GetTotalTime());
cmCTestLog(this->CTest, HANDLER_OUTPUT, buf << "\n" );
if ( this->TestHandler->LogFile )
{
*this->TestHandler->LogFile << "\nTest time = " << buf << std::endl;
*this->TestHandler->LogFile << "Test time = " << buf << std::endl;
}
this->DartProcessing(output);
this->DartProcessing();
}
// if this is doing MemCheck then all the output needs to be put into
// Output since that is what is parsed by cmCTestMemCheckHandler
if(!this->TestHandler->MemCheck)
{
if ( this->TestResult.Status == cmCTestTestHandler::COMPLETED )
{
this->TestHandler->CleanTestOutput(output, static_cast<size_t>
(this->TestHandler->CustomMaximumPassedTestOutputSize));
this->TestHandler->CleanTestOutput(this->ProcessOutput,
static_cast<size_t>
(this->TestHandler->CustomMaximumPassedTestOutputSize));
}
else
{
this->TestHandler->CleanTestOutput(output, static_cast<size_t>
(this->TestHandler->CustomMaximumFailedTestOutputSize));
this->TestHandler->CleanTestOutput(this->ProcessOutput,
static_cast<size_t>
(this->TestHandler->CustomMaximumFailedTestOutputSize));
}
}
this->TestResult.Reason = reason;
@ -308,7 +209,7 @@ bool cmCTestRunTest::Execute()
reasonType = "Test Fail Reason";
pass = false;
}
double ttime = clock_finish - clock_start;
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;
@ -333,34 +234,99 @@ bool cmCTestRunTest::Execute()
*this->TestHandler->LogFile << "Test Failed.\n";
}
}
*this->TestHandler->LogFile << "\"" << testname.c_str() << "\" end time: "
<< this->CTest->CurrentTime() << std::endl
<< "\"" << testname.c_str() << "\" time elapsed: "
*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;
}
this->TestResult.Output = output;
this->TestResult.ReturnValue = retVal;
this->TestResult.Output = this->ProcessOutput;
this->TestResult.ReturnValue = this->TestProcess->GetExitValue();
this->TestResult.CompletionStatus = "Completed";
this->TestResult.ExecutionTime = this->TestProcess->GetTotalTime();
this->TestHandler->TestResults.push_back( this->TestResult );
delete this->TestProcess;
return passed;
}
//----------------------------------------------------------------------
void cmCTestRunTest::DartProcessing(std::string& output)
void cmCTestRunTest::SetTestHandler(cmCTestTestHandler * handler)
{
if (!output.empty() && output.find("<DartMeasurement") != output.npos)
this->TestHandler = handler;
this->CTest = handler->CTest;
}
//----------------------------------------------------------------------
// Starts the execution of a test. Returns once it has started
bool cmCTestRunTest::StartTest()
{
std::vector<std::string>& args = this->TestProperties->Args;
this->TestResult.Properties = this->TestProperties;
this->TestResult.ExecutionTime = 0;
this->TestResult.ReturnValue = -1;
this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
this->TestResult.TestCount = this->TestProperties->Index;
this->TestResult.Name = this->TestProperties->Name;
this->TestResult.Path = this->TestProperties->Directory.c_str();
// find the test executable
this->ActualCommand
= this->TestHandler->FindTheExecutable(args[1].c_str());
this->TestCommand
= cmSystemTools::ConvertToOutputPath(this->ActualCommand.c_str());
// continue if we did not find the executable
if (this->TestCommand == "")
{
if (this->TestHandler->DartStuff.find(output.c_str()))
*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];
if ( !this->CTest->GetShowOnly() )
{
this->TestResult.FullCommandLine = this->ActualCommand;
this->TestHandler->TestResults.push_back( this->TestResult );
return false;
}
}
/**
* Run an executable command and put the stdout in output.
*/
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl
<< this->Index << ": "
<< (this->TestHandler->MemCheck?"MemCheck":"Test")
<< " command: " << this->TestCommand
<< std::endl);
this->StartTime = this->CTest->CurrentTime();
if ( !this->CTest->GetShowOnly() )
{
return this->CreateProcess(this->ActualCommand,
this->TestProperties->Args,
this->TestProperties->Timeout,
&this->TestProperties->Environment);
}
return true;
}
//----------------------------------------------------------------------
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(output.c_str()))
while (this->TestHandler->DartStuff1.find(this->ProcessOutput.c_str()))
{
// replace the exact match for the string
cmSystemTools::ReplaceString(output,
cmSystemTools::ReplaceString(this->ProcessOutput,
this->TestHandler->DartStuff1.match(1).c_str(), "");
}
this->TestResult.RegressionImages
@ -370,13 +336,27 @@ void cmCTestRunTest::DartProcessing(std::string& output)
}
//----------------------------------------------------------------------
int cmCTestRunTest::RunTestProcess(std::vector<const char*> argv,
std::string* output, int *retVal,
std::ostream* log, double testTimeOut,
bool cmCTestRunTest::CreateProcess(std::string command,
std::vector<std::string> args,
double testTimeOut,
std::vector<std::string>* environment)
{
std::vector<std::string> commandArgs;
std::vector<std::string>::iterator i = args.begin();
++i; //skip test name
++i; //skip executable name
for(; i != args.end(); ++i)
{
commandArgs.push_back(*i);
}
this->TestProcess = new cmProcess;
this->TestProcess->SetId(this->Index);
this->TestProcess->SetCommand(command.c_str());
this->TestProcess->SetCommandArguments(commandArgs);
std::vector<std::string> origEnv;
bool modifyEnv = (environment && environment->size()>0);
this->ModifyEnv = (environment && environment->size()>0);
// determine how much time we have
double timeout = this->CTest->GetRemainingTimeAllowed() - 120;
@ -395,154 +375,89 @@ int cmCTestRunTest::RunTestProcess(std::vector<const char*> argv,
{
timeout = 1;
}
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
"Test timeout computed to be: " << timeout << "\n");
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->Index << ": "
<< "Test timeout computed to be: " << timeout << "\n");
if(cmSystemTools::SameFile(argv[0], this->CTest->CTestSelf.c_str()) &&
!this->CTest->ForceNewCTestProcess &&
this->OptimizeForCTest)
if (this->ModifyEnv)
{
cmCTest inst;
inst.ConfigType = this->CTest->ConfigType;
inst.TimeOut = timeout;
// Capture output of the child ctest.
cmOStringStream oss;
inst.SetStreams(&oss, &oss);
std::vector<std::string> args;
for(unsigned int i =0; i < argv.size(); ++i)
{
if(argv[i])
{
// make sure we pass the timeout in for any build and test
// invocations. Since --build-generator is required this is a
// good place to check for it, and to add the arguments in
if (strcmp(argv[i],"--build-generator") == 0 && timeout)
{
args.push_back("--test-timeout");
cmOStringStream msg;
msg << timeout;
args.push_back(msg.str());
}
args.push_back(argv[i]);
}
}
if ( log )
{
*log << "* Run internal CTest" << std::endl;
}
std::string oldpath = cmSystemTools::GetCurrentWorkingDirectory();
if (modifyEnv)
{
origEnv = cmSystemTools::AppendEnv(environment);
}
*retVal = inst.Run(args, output);
*output += oss.str();
if ( log )
{
*log << output->c_str();
}
cmSystemTools::ChangeDirectory(oldpath.c_str());
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
"Internal cmCTest object used to run test." << std::endl
<< *output << std::endl);
if (modifyEnv)
{
cmSystemTools::RestoreEnv(origEnv);
}
return cmsysProcess_State_Exited;
}
std::vector<char> tempOutput;
if ( output )
{
*output = "";
this->OrigEnv = cmSystemTools::AppendEnv(environment);
}
if (modifyEnv)
{
origEnv = cmSystemTools::AppendEnv(environment);
}
cmsysProcess* cp = cmsysProcess_New();
cmsysProcess_SetCommand(cp, &*argv.begin());
cmCTestLog(this->CTest, DEBUG, "Command is: " << argv[0] << std::endl);
if(cmSystemTools::GetRunCommandHideConsole())
{
cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1);
}
cmsysProcess_SetTimeout(cp, timeout);
cmsysProcess_Execute(cp);
char* data;
int length;
while(cmsysProcess_WaitForData(cp, &data, &length, 0))
{
if ( output )
{
tempOutput.insert(tempOutput.end(), data, data+length);
}
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
cmCTestLogWrite(data, length));
if ( log )
{
log->write(data, length);
}
}
cmsysProcess_WaitForExit(cp, 0);
if(output && tempOutput.begin() != tempOutput.end())
{
//We are waiting for exit before finally appending to the output
output->append(&*tempOutput.begin(), tempOutput.size());
}
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "-- Process completed"
<< std::endl);
int result = cmsysProcess_GetState(cp);
if(result == cmsysProcess_State_Exited)
{
*retVal = cmsysProcess_GetExitValue(cp);
if(*retVal != 0 && this->CTest->OutputTestOutputOnTestFailure)
{
this->CTest->OutputTestErrors(tempOutput);
}
}
else if(result == cmsysProcess_State_Exception)
{
if(this->CTest->OutputTestOutputOnTestFailure)
{
this->CTest->OutputTestErrors(tempOutput);
}
*retVal = cmsysProcess_GetExitException(cp);
std::string outerr = "\n*** Exception executing: ";
outerr += cmsysProcess_GetExceptionString(cp);
*output += outerr;
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, outerr.c_str()
<< std::endl << std::flush);
}
else if(result == cmsysProcess_State_Error)
{
std::string outerr = "\n*** ERROR executing: ";
outerr += cmsysProcess_GetErrorString(cp);
*output += outerr;
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, outerr.c_str()
<< std::endl << std::flush);
}
cmsysProcess_Delete(cp);
if (modifyEnv)
{
cmSystemTools::RestoreEnv(origEnv);
}
return result;
return this->TestProcess->StartProcess();
}
void cmCTestRunTest::WriteLogOutputTop()
{
/* Not sure whether we want to prepend the test index anymore
cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(3)
<< this->Index << ": ");*/
cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(3)
<< this->TestProperties->Index << "/");
cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(3)
<< this->TestHandler->TotalNumberOfTests << " ");
if ( this->TestHandler->MemCheck )
{
cmCTestLog(this->CTest, HANDLER_OUTPUT, "Memory Check");
}
else
{
cmCTestLog(this->CTest, HANDLER_OUTPUT, "Testing");
}
cmCTestLog(this->CTest, HANDLER_OUTPUT, " ");
const int maxTestNameWidth = this->CTest->GetMaxTestNameWidth();
std::string outname = this->TestProperties->Name + " ";
outname.resize(maxTestNameWidth, '.');
// add the arguments
std::vector<std::string>::const_iterator j =
this->TestProperties->Args.begin();
++j; // skip test name
++j; // skip command as it is in actualCommand
std::vector<const char*> arguments;
this->TestHandler->GenerateTestCommand(arguments);
arguments.push_back(this->ActualCommand.c_str());
for(;j != this->TestProperties->Args.end(); ++j)
{
this->TestCommand += " ";
this->TestCommand += cmSystemTools::EscapeSpaces(j->c_str());
arguments.push_back(j->c_str());
}
arguments.push_back(0);
this->TestResult.FullCommandLine = this->TestCommand;
*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: ";
std::vector<cmStdString>::size_type ll;
for ( ll = 0; ll < arguments.size()-1; ll ++ )
{
*this->TestHandler->LogFile << "\"" << arguments[ll] << "\" ";
}
*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;
if ( this->CTest->GetShowOnly() )
{
cmCTestLog(this->CTest, HANDLER_OUTPUT, outname.c_str() << std::endl);
}
else
{
cmCTestLog(this->CTest, HANDLER_OUTPUT, outname.c_str());
}
cmCTestLog(this->CTest, DEBUG, "Testing "
<< this->TestProperties->Name.c_str() << " ... ");
}

View File

@ -19,6 +19,7 @@
#include <cmStandardIncludes.h>
#include <cmCTestTestHandler.h>
#include <cmProcess.h>
/** \class cmRunTest
* \brief represents a single test to be run
@ -38,44 +39,53 @@ public:
{ return this->TestProperties; }
void SetTestHandler(cmCTestTestHandler * handler);
void SetOptimizeForCTest(bool optimize)
{ this->OptimizeForCTest = optimize; }
bool GetOptimizeForCTest()
{ return this->OptimizeForCTest; }
void SetIndex(int i) { this->Index = i; }
std::string GetProcessOutput()
{ return this->ProcessOutput; }
void SetCTest(cmCTest * ct) { this->CTest = ct; }
//Provides a handle to the log stream in case someone wants
// to asynchronously process the log
std::ostream * GetLogStream()
{ return this->TestHandler->LogFile; }
int GetIndex() { return this->Index; }
std::string GetProcessOutput() { return this->ProcessOutput; }
cmCTestTestHandler::cmCTestTestResult GetTestResults()
{ return this->TestResult; }
//Runs the test
bool Execute();
bool IsRunning();
void CheckOutput();
//launch the test process, return whether it started correctly
bool StartTest();
//capture the test results and send them back to the test handler
bool EndTest();
protected:
void DartProcessing(std::string& output);
int RunTestProcess(std::vector<const char*> argv,
std::string* output, int *retVal,
std::ostream* log, double testTimeOut,
void DartProcessing();
bool CreateProcess(std::string executable,
std::vector<std::string> args,
double testTimeOut,
std::vector<std::string>* environment);
private:
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 OptimizeForCTest;
//flag for whether the env was modified for this run
bool ModifyEnv;
//stores the original environment if we are modifying it
std::vector<std::string> OrigEnv;
std::string ProcessOutput;
//The test results
cmCTestTestHandler::cmCTestTestResult TestResult;
int Index;
std::string StartTime;
std::string TestCommand;
std::string ActualCommand;
void WriteLogOutputTop();
};
#endif

View File

@ -543,8 +543,15 @@ int cmCTestTestHandler::ProcessHandler()
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)
@ -591,10 +598,15 @@ int cmCTestTestHandler::ProcessHandler()
totalTestTime += result->ExecutionTime;
}
char buf[1024];
sprintf(buf, "%6.2f sec", totalTestTime);
cmCTestLog(this->CTest, HANDLER_OUTPUT, "\nTotal Test time = "
<< buf << "\n" );
char realBuf[1024];
sprintf(realBuf, "%6.2f sec", (double)(clock_finish - clock_start));
cmCTestLog(this->CTest, HANDLER_OUTPUT, "\nTotal Test time (real) = "
<< realBuf << "\n" );
char totalBuf[1024];
sprintf(totalBuf, "%6.2f sec", totalTestTime);
cmCTestLog(this->CTest, HANDLER_OUTPUT, "\nTotal Test time (parallel) = "
<< totalBuf << "\n" );
}
@ -1528,21 +1540,28 @@ std::string cmCTestTestHandler::SaveTestList()
return fname;
}
void cmCTestTestHandler::ProcessParallel(std::vector<cmStdString> &passed,
void cmCTestTestHandler::ProcessDirectory(std::vector<cmStdString> &passed,
std::vector<cmStdString> &failed)
{
this->ComputeTestList();
cmCTestMultiProcessHandler parallel;
parallel.SetCTest(this->CTest);
parallel.SetParallelLevel(this->CTest->GetParallelLevel());
parallel.SetParallelLevel(this->CTest->GetParallelLevel());
parallel.SetTestHandler(this);
*this->LogFile << "Start testing: "
<< this->CTest->CurrentTime() << std::endl
<< "----------------------------------------------------------"
<< std::endl;
cmCTestMultiProcessHandler::TestSet depends;
cmCTestMultiProcessHandler::TestMap tests;
std::map<int, cmStdString> testnames;
cmCTestMultiProcessHandler::PropertiesMap properties;
for (ListOfTests::iterator it = this->TestList.begin();
it != this->TestList.end(); it ++ )
{
cmCTestTestProperties& p = *it;
testnames[p.Index] = p.Name;
if(p.Depends.size())
{
for(std::vector<std::string>::iterator i = p.Depends.begin();
@ -1560,76 +1579,16 @@ void cmCTestTestHandler::ProcessParallel(std::vector<cmStdString> &passed,
}
}
tests[it->Index] = depends;
properties[it->Index] = &*it;
}
parallel.SetCTestCommand(this->CTest->GetCTestExecutable());
parallel.SetTests(tests, testnames);
std::string fname = this->SaveTestList();
parallel.SetTestCacheFile(fname.c_str());
parallel.SetTests(tests, properties);
parallel.SetPassFailVectors(&passed, &failed);
this->TestResults.clear();
parallel.SetTestResults(&this->TestResults);
parallel.RunTests();
cmSystemTools::RemoveFile(fname.c_str());
}
//----------------------------------------------------------------------
void cmCTestTestHandler::ProcessDirectory(std::vector<cmStdString> &passed,
std::vector<cmStdString> &failed)
{
if(this->CTest->GetParallelLevel() > 0)
{
this->ProcessParallel(passed, failed);
return;
}
// save the current working directory
std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory();
// compute the list of tests to run
this->ComputeTestList();
this->StartTest = this->CTest->CurrentTime();
this->StartTestTime = static_cast<unsigned int>(cmSystemTools::GetTime());
double elapsed_time_start = cmSystemTools::GetTime();
*this->LogFile << "Start testing: " << this->StartTest << std::endl
<< "----------------------------------------------------------"
<< std::endl;
std::string last_directory = "";
// run each test
for (ListOfTests::iterator it = this->TestList.begin();
it != this->TestList.end(); it ++ )
{
if (!(last_directory == it->Directory))
{
cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
"Changing directory into " << it->Directory.c_str() << "\n");
*this->LogFile << "Changing directory into: " << it->Directory.c_str()
<< std::endl;
last_directory = it->Directory;
cmSystemTools::ChangeDirectory(it->Directory.c_str());
}
// process this one test
cmCTestRunTest testRun;
testRun.SetTestProperties(&(*it));
testRun.SetTestHandler(this);
bool testPassed = testRun.Execute(); //run the test
if(testPassed && !this->CTest->GetShowOnly())
{
passed.push_back(it->Name);
}
else if(!testPassed)
{
failed.push_back(it->Name);
}
}
this->EndTest = this->CTest->CurrentTime();
this->EndTestTime = static_cast<unsigned int>(cmSystemTools::GetTime());
this->ElapsedTestingTime = cmSystemTools::GetTime() - elapsed_time_start;
if ( this->LogFile )
{
*this->LogFile << "End testing: " << this->EndTest << std::endl;
}
cmSystemTools::ChangeDirectory(current_dir.c_str());
*this->LogFile << "End testing: "
<< this->CTest->CurrentTime() << std::endl;
}
//----------------------------------------------------------------------

View File

@ -31,6 +31,7 @@ class cmMakefile;
class cmCTestTestHandler : public cmCTestGenericHandler
{
friend class cmCTestRunTest;
friend class cmCTestMultiProcessHandler;
public:
cmTypeMacro(cmCTestTestHandler, cmCTestGenericHandler);
@ -129,6 +130,7 @@ public:
std::vector<std::string> &extraPaths,
std::vector<std::string> &failed);
typedef std::vector<cmCTestTestProperties> ListOfTests;
protected:
// comput a final test list
virtual int PreProcessHandler();
@ -193,7 +195,6 @@ private:
void ProcessDirectory(std::vector<cmStdString> &passed,
std::vector<cmStdString> &failed);
typedef std::vector<cmCTestTestProperties> ListOfTests;
/**
* Get the list of tests in directory and subdirectories.
*/
@ -221,9 +222,6 @@ private:
bool GetValue(const char* tag,
double& value,
std::ifstream& fin);
// run in -j N mode
void ProcessParallel(std::vector<cmStdString> &passed,
std::vector<cmStdString> &failed);
/**
* Find the executable for a test
*/

View File

@ -209,7 +209,7 @@ std::string cmCTest::DecodeURL(const std::string& in)
cmCTest::cmCTest()
{
this->ParallelSubprocess = false;
this->ParallelLevel = 0;
this->ParallelLevel = 1;
this->SubmitIndex = 0;
this->ForceNewCTestProcess = false;
this->TomorrowTag = false;
@ -292,6 +292,11 @@ cmCTest::~cmCTest()
this->SetOutputLogFileName(0);
}
void cmCTest::SetParallelLevel(int level)
{
this->ParallelLevel = level < 1 ? 1 : level;
}
//----------------------------------------------------------------------------
cmCTest::Part cmCTest::GetPartFromName(const char* name)
{

View File

@ -136,7 +136,7 @@ public:
void SetTimeOut(double t) { this->TimeOut = t; }
// how many test to run at the same time
int GetParallelLevel() { return this->ParallelLevel; }
void SetParallelLevel(int t) { this->ParallelLevel = t; }
void SetParallelLevel(int);
bool GetParallelSubprocess() { return this->ParallelSubprocess; }
void SetParallelSubprocess() { this->ParallelSubprocess = true; }