diff --git a/Source/CTest/cmCTestMultiProcessHandler.cxx b/Source/CTest/cmCTestMultiProcessHandler.cxx index 70ca4c0e8..328e2e5a2 100644 --- a/Source/CTest/cmCTestMultiProcessHandler.cxx +++ b/Source/CTest/cmCTestMultiProcessHandler.cxx @@ -23,12 +23,11 @@ cmCTestMultiProcessHandler::cmCTestMultiProcessHandler() { this->ParallelLevel = 1; - this->ProcessId = 0; } // Set the tests void cmCTestMultiProcessHandler::SetTests(TestMap& tests, - std::map& 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::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 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 finished; + std::vector finished; std::string out, err; - for(std::set::const_iterator i = this->RunningTests.begin(); + for(std::set::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::iterator i = finished.begin(); + + for( std::vector::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 diff --git a/Source/CTest/cmCTestMultiProcessHandler.h b/Source/CTest/cmCTestMultiProcessHandler.h index 75be5426c..413bff2cc 100644 --- a/Source/CTest/cmCTestMultiProcessHandler.h +++ b/Source/CTest/cmCTestMultiProcessHandler.h @@ -17,9 +17,9 @@ #ifndef cmCTestMultiProcessHandler_h #define cmCTestMultiProcessHandler_h -class cmProcess; #include #include +#include /** \class cmCTestMultiProcessHandler * \brief run parallel ctest @@ -31,16 +31,18 @@ class cmCTestMultiProcessHandler public: struct TestSet : public std::set {}; struct TestMap : public std::map {}; + struct PropertiesMap : public + std::map {}; + cmCTestMultiProcessHandler(); // Set the tests - void SetTests(TestMap& tests, - std::map& 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* passed, std::vector* 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 TestNames; + //list of test properties (indices concurrent to the test map) + PropertiesMap Properties; std::map TestRunningMap; std::map TestFinishMap; std::map TestOutput; - std::string CTestCommand; - std::string CTestCacheFile; + //std::string CTestCommand; + //std::string CTestCacheFile; std::vector* Passed; std::vector* Failed; std::vector* TestResults; - int ProcessId; size_t ParallelLevel; // max number of process that can be run at once - std::set RunningTests; // current running tests + std::set RunningTests; // current running tests + cmCTestTestHandler * TestHandler; }; #endif diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx index cd6112d60..d444249e5 100644 --- a/Source/CTest/cmCTestRunTest.cxx +++ b/Source/CTest/cmCTestRunTest.cxx @@ -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& 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::const_iterator j = args.begin(); - ++j; // skip test name - ++j; // skip command as it is in actualCommand - std::vector 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::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::vectorTestProperties->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 - (this->TestHandler->CustomMaximumPassedTestOutputSize)); + this->TestHandler->CleanTestOutput(this->ProcessOutput, + static_cast + (this->TestHandler->CustomMaximumPassedTestOutputSize)); } else { - this->TestHandler->CleanTestOutput(output, static_cast - (this->TestHandler->CustomMaximumFailedTestOutputSize)); + this->TestHandler->CleanTestOutput(this->ProcessOutput, + static_cast + (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(ttime / (60 * 60)); int minutes = static_cast(ttime / 60) % 60; int seconds = static_cast(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("TestHandler = handler; + this->CTest = handler->CTest; +} + +//---------------------------------------------------------------------- +// Starts the execution of a test. Returns once it has started +bool cmCTestRunTest::StartTest() +{ + std::vector& 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("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 argv, - std::string* output, int *retVal, - std::ostream* log, double testTimeOut, +bool cmCTestRunTest::CreateProcess(std::string command, + std::vector args, + double testTimeOut, std::vector* environment) { + std::vector commandArgs; + std::vector::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 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 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 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 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::const_iterator j = + this->TestProperties->Args.begin(); + ++j; // skip test name + ++j; // skip command as it is in actualCommand + std::vector 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::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() << "" << 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() << " ... "); +} diff --git a/Source/CTest/cmCTestRunTest.h b/Source/CTest/cmCTestRunTest.h index 1438a60fb..72a108a10 100644 --- a/Source/CTest/cmCTestRunTest.h +++ b/Source/CTest/cmCTestRunTest.h @@ -19,6 +19,7 @@ #include #include +#include /** \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 argv, - std::string* output, int *retVal, - std::ostream* log, double testTimeOut, + void DartProcessing(); + bool CreateProcess(std::string executable, + std::vector args, + double testTimeOut, std::vector* 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 OrigEnv; std::string ProcessOutput; //The test results cmCTestTestHandler::cmCTestTestResult TestResult; + int Index; + std::string StartTime; + std::string TestCommand; + std::string ActualCommand; + void WriteLogOutputTop(); }; #endif diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx index 62b111f41..6df925cfe 100644 --- a/Source/CTest/cmCTestTestHandler.cxx +++ b/Source/CTest/cmCTestTestHandler.cxx @@ -543,8 +543,15 @@ int cmCTestTestHandler::ProcessHandler() std::vector passed; std::vector 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 &passed, +void cmCTestTestHandler::ProcessDirectory(std::vector &passed, std::vector &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 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::iterator i = p.Depends.begin(); @@ -1560,76 +1579,16 @@ void cmCTestTestHandler::ProcessParallel(std::vector &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 &passed, - std::vector &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(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(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; } //---------------------------------------------------------------------- diff --git a/Source/CTest/cmCTestTestHandler.h b/Source/CTest/cmCTestTestHandler.h index 03ce285ca..a6b8e8ec4 100644 --- a/Source/CTest/cmCTestTestHandler.h +++ b/Source/CTest/cmCTestTestHandler.h @@ -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 &extraPaths, std::vector &failed); + typedef std::vector ListOfTests; protected: // comput a final test list virtual int PreProcessHandler(); @@ -193,7 +195,6 @@ private: void ProcessDirectory(std::vector &passed, std::vector &failed); - typedef std::vector 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 &passed, - std::vector &failed); /** * Find the executable for a test */ diff --git a/Source/cmCTest.cxx b/Source/cmCTest.cxx index abf47a21c..759077200 100644 --- a/Source/cmCTest.cxx +++ b/Source/cmCTest.cxx @@ -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) { diff --git a/Source/cmCTest.h b/Source/cmCTest.h index 2a0d4bfcc..91a0fc66c 100644 --- a/Source/cmCTest.h +++ b/Source/cmCTest.h @@ -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; }