diff options
author | Bill Hoffman <bill.hoffman@kitware.com> | 2008-07-03 13:31:33 (GMT) |
---|---|---|
committer | Bill Hoffman <bill.hoffman@kitware.com> | 2008-07-03 13:31:33 (GMT) |
commit | bb7b27e417b72d0387af5393b81ed35deab52c4b (patch) | |
tree | 42d2efd6a2131935eefbe540cd15e8bcb0ed2ac7 /Source/CTest | |
parent | ea71721b805708f82898c32e007aa3e29e6f458f (diff) | |
download | CMake-bb7b27e417b72d0387af5393b81ed35deab52c4b.zip CMake-bb7b27e417b72d0387af5393b81ed35deab52c4b.tar.gz CMake-bb7b27e417b72d0387af5393b81ed35deab52c4b.tar.bz2 |
ENH: add initial ctest -j feature
Diffstat (limited to 'Source/CTest')
-rw-r--r-- | Source/CTest/cmCTestGenericHandler.cxx | 6 | ||||
-rw-r--r-- | Source/CTest/cmCTestMultiProcessHandler.cxx | 323 | ||||
-rw-r--r-- | Source/CTest/cmCTestMultiProcessHandler.h | 84 | ||||
-rw-r--r-- | Source/CTest/cmCTestTestHandler.cxx | 438 | ||||
-rw-r--r-- | Source/CTest/cmCTestTestHandler.h | 35 |
5 files changed, 819 insertions, 67 deletions
diff --git a/Source/CTest/cmCTestGenericHandler.cxx b/Source/CTest/cmCTestGenericHandler.cxx index 50c5a20..1695bc1 100644 --- a/Source/CTest/cmCTestGenericHandler.cxx +++ b/Source/CTest/cmCTestGenericHandler.cxx @@ -161,6 +161,12 @@ bool cmCTestGenericHandler::StartLogFile(const char* name, ostr << "_" << this->CTest->GetCurrentTag(); } ostr << ".log"; + // if this is a parallel subprocess then add the id to the + // file so they don't clobber each other + if(this->CTest->GetParallelSubprocess()) + { + ostr << "." << this->CTest->GetParallelSubprocessId(); + } if( !this->CTest->OpenOutputFile("Temporary", ostr.str().c_str(), xofs) ) { cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot create log file: " diff --git a/Source/CTest/cmCTestMultiProcessHandler.cxx b/Source/CTest/cmCTestMultiProcessHandler.cxx new file mode 100644 index 0000000..4c9be36 --- /dev/null +++ b/Source/CTest/cmCTestMultiProcessHandler.cxx @@ -0,0 +1,323 @@ +/*========================================================================= + + Program: CMake - Cross-Platform Makefile Generator + Module: $RCSfile$ + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. + See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ +#include "cmCTestMultiProcessHandler.h" +#include "cmProcess.h" +#include "cmStandardIncludes.h" +#include "cmCTest.h" + + +cmCTestMultiProcessHandler::cmCTestMultiProcessHandler() +{ + this->ParallelLevel = 1; + this->ProcessId = 0; +} + // Set the tests +void +cmCTestMultiProcessHandler::SetTests(std::map<int, std::set<int> >& tests, + std::map<int,cmStdString>& testNames) +{ + // set test run map to false for all + for(std::map<int, std::set<int> >::iterator i = this->Tests.begin(); + i != this->Tests.end(); ++i) + { + this->TestRunningMap[i->first] = false; + this->TestFinishMap[i->first] = false; + } + this->Tests = tests; + this->TestNames = testNames; +} + // Set the max number of tests that can be run at the same time. +void cmCTestMultiProcessHandler::SetParallelLevel(size_t l) +{ + this->ParallelLevel = l; +} + + +void cmCTestMultiProcessHandler::RunTests() +{ + this->StartNextTests(); + while(this->Tests.size() != 0) + { + this->CheckOutput(); + this->StartNextTests(); + } + // let all running tests finish + 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"); + 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; + 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); + } + else + { + this->RunningTests.insert(newp); + } + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "ctest -I " << test << "\n"); + this->ProcessId++; +} + +bool cmCTestMultiProcessHandler::StartTest(int test) +{ + // 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 + std::set<int> depends = this->Tests[test]; + size_t totalDepends = depends.size(); + if(totalDepends) + { + for(std::set<int>::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) + { + // Start this test it has no depends + 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() +{ + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl + << "Number of running tests : " << this->RunningTests.size() + << "\n"); + size_t numToStart = this->ParallelLevel - this->RunningTests.size(); + if(numToStart == 0) + { + return; + } + std::map<int, std::set<int> > tests = this->Tests; + for(std::map<int, std::set<int> >::iterator i = tests.begin(); + i != tests.end(); ++i) + { + // start test should start only one test + if(this->StartTest(i->first)) + { + numToStart--; + } + else + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl + << "Test did not start waiting on depends to finish: " + << i->first << "\n"); + } + if(numToStart == 0 ) + { + return; + } + } +} + + +bool cmCTestMultiProcessHandler::CheckOutput() +{ + // no more output we are done + if(this->RunningTests.size() == 0) + { + return false; + } + std::vector<cmProcess*> finished; + std::string out, err; + for(std::set<cmProcess*>::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"; + } + if(!p->IsRunning()) + { + finished.push_back(p); + } + } + for( std::vector<cmProcess*>::iterator i = finished.begin(); + i != finished.end(); ++i) + { + cmProcess* p = *i; + this->EndTest(p); + } + return true; +} + +void cmCTestMultiProcessHandler::EndTest(cmProcess* p) +{ + int test = p->GetId(); + int exitVal = p->GetExitValue(); + cmCTestTestHandler::cmCTestTestResult cres; + cres.Properties = 0; + cres.ExecutionTime = 0;// ??? + cres.ReturnValue = exitVal; + cres.Status = cmCTestTestHandler::COMPLETED; + cres.TestCount = test; + cres.Name = this->TestNames[test]; + cres.Path.clear(); + 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( std::map<int, std::set<int> >::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 + for( std::map<int, std::set<int> >::iterator i = this->Tests.begin(); + i!= this->Tests.end(); ++i) + { + std::cout << "Test " << i->first << " ("; + for(std::set<int>::iterator j = i->second.begin(); + j != i->second.end(); ++j) + { + std::cout << *j << " "; + } + std::cout << ")\n"; + } +} + +#if 0 +int main() +{ + cmCTestMultiProcessHandler h; + h.SetParallelLevel(4); + std::map<int, std::set<int> > tests; + std::set<int> depends; + for(int i =1; i < 92; i++) + { + tests[i] = depends; + } + depends.clear(); + depends.insert(45); subprject + tests[46] = depends; subproject-stage2 + depends.clear(); + depends.insert(55); simpleinstall simpleinstall-s2 + tests[56] = depends; + depends.clear(); + depends.insert(70); wrapping + tests[71] = depends; qtwrapping + depends.clear(); + depends.insert(71); qtwrapping + tests[72] = depends; testdriver1 + depends.clear(); + depends.insert(72) testdriver1 + tests[73] = depends; testdriver2 + depends.clear(); + depends.insert(73); testdriver2 + tests[74] = depends; testdriver3 + depends.clear(); + depends.insert(79); linkorder1 + tests[80] = depends; linkorder2 + h.SetTests(tests); + h.PrintTests(); + h.RunTests(); +} +#endif diff --git a/Source/CTest/cmCTestMultiProcessHandler.h b/Source/CTest/cmCTestMultiProcessHandler.h new file mode 100644 index 0000000..9f7976d --- /dev/null +++ b/Source/CTest/cmCTestMultiProcessHandler.h @@ -0,0 +1,84 @@ +/*========================================================================= + + Program: CMake - Cross-Platform Makefile Generator + Module: $RCSfile$ + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. + See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ +#ifndef cmCTestMultiProcessHandler_h +#define cmCTestMultiProcessHandler_h + +#include <set> +#include <map> +#include <string> +#include <vector> +class cmProcess; +#include <cmStandardIncludes.h> +#include <cmCTestTestHandler.h> + +/** \class cmCTestMultiProcessHandler + * \brief run parallel ctest + * + * cmCTestMultiProcessHandler + */ +class cmCTestMultiProcessHandler +{ +public: + cmCTestMultiProcessHandler(); + // Set the tests + void SetTests(std::map<int, std::set<int> >& tests, + std::map<int, cmStdString>& testNames); + // 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 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;} +protected: + cmCTest* CTest; + // Start the next test or tests as many as are allowed by + // ParallelLevel + void StartNextTests(); + void StartTestProcess(int test); + bool StartTest(int test); + 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 + std::map<int, std::set<int> > Tests; + std::map<int, cmStdString> TestNames; + std::map<int, bool> TestRunningMap; + std::map<int, bool> TestFinishMap; + std::map<int, cmStdString> TestOutput; + 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 +}; + +#endif diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx index fe3c3ae..2db4d6d 100644 --- a/Source/CTest/cmCTestTestHandler.cxx +++ b/Source/CTest/cmCTestTestHandler.cxx @@ -16,7 +16,7 @@ =========================================================================*/ #include "cmCTestTestHandler.h" - +#include "cmCTestMultiProcessHandler.h" #include "cmCTest.h" #include "cmake.h" #include "cmGeneratedFileStream.h" @@ -500,11 +500,14 @@ int cmCTestTestHandler::ProcessHandler() } this->TestResults.clear(); - - cmCTestLog(this->CTest, HANDLER_OUTPUT, - (this->MemCheck ? "Memory check" : "Test") - << " project " << cmSystemTools::GetCurrentWorkingDirectory() - << std::endl); + // do not output startup if this is a sub-process for parallel tests + if(!this->CTest->GetParallelSubprocess()) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, + (this->MemCheck ? "Memory check" : "Test") + << " project " << cmSystemTools::GetCurrentWorkingDirectory() + << std::endl); + } if ( ! this->PreProcessHandler() ) { return -1; @@ -517,7 +520,6 @@ int cmCTestTestHandler::ProcessHandler() std::vector<cmStdString> passed; std::vector<cmStdString> failed; int total; - this->ProcessDirectory(passed, failed); total = int(passed.size()) + int(failed.size()); @@ -550,33 +552,37 @@ int cmCTestTestHandler::ProcessHandler() { 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); - //fprintf(stderr,"\n%.0f%% tests passed, %i tests failed out of %i\n", - // percent, int(failed.size()), total); + if(!this->CTest->GetParallelSubprocess()) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl + << static_cast<int>(percent + .5) << "% tests passed, " + << failed.size() << " tests failed out of " + << total << std::endl); + } if (failed.size()) { cmGeneratedFileStream ofs; - - cmCTestLog(this->CTest, ERROR_MESSAGE, std::endl - << "The following tests FAILED:" << std::endl); - this->StartLogFile("TestsFailed", ofs); - - std::vector<cmCTestTestHandler::cmCTestTestResult>::iterator ftit; - for(ftit = this->TestResults.begin(); - ftit != this->TestResults.end(); ++ftit) + if(!this->CTest->GetParallelSubprocess()) { - if ( ftit->Status != cmCTestTestHandler::COMPLETED ) + cmCTestLog(this->CTest, ERROR_MESSAGE, std::endl + << "The following tests FAILED:" << std::endl); + this->StartLogFile("TestsFailed", ofs); + + std::vector<cmCTestTestHandler::cmCTestTestResult>::iterator ftit; + for(ftit = this->TestResults.begin(); + ftit != this->TestResults.end(); ++ftit) { - 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 ( 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); + } } + } - } } @@ -677,8 +683,8 @@ void cmCTestTestHandler::ProcessOneTest(cmCTestTestProperties *it, // add the arguments std::vector<std::string>::const_iterator j = args.begin(); - ++j; - ++j; + ++j; // skip test name + ++j; // skip command as it is in actualCommand std::vector<const char*> arguments; this->GenerateTestCommand(arguments); arguments.push_back(actualCommand.c_str()); @@ -917,23 +923,20 @@ void cmCTestTestHandler::ProcessOneTest(cmCTestTestProperties *it, } //---------------------------------------------------------------------- -void cmCTestTestHandler::ProcessDirectory(std::vector<cmStdString> &passed, - std::vector<cmStdString> &failed) +void cmCTestTestHandler::ComputeTestList() { - std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory(); - this->TestList.clear(); - - this->GetListOfTests(); + this->TestList.clear(); // clear list of test + if(this->CTest->GetParallelSubprocess()) + { + this->LoadTestList(); + return; + } + else + { + this->GetListOfTests(); + } cmCTestTestHandler::ListOfTests::size_type tmsize = this->TestList.size(); - 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; - // how many tests are in based on RegExp? int inREcnt = 0; cmCTestTestHandler::ListOfTests::iterator it; @@ -953,10 +956,11 @@ void cmCTestTestHandler::ProcessDirectory(std::vector<cmStdString> &passed, { this->ExpandTestsToRunInformation(inREcnt); } - + // Now create a final list of tests to run int cnt = 0; inREcnt = 0; - std::string last_directory = ""; + std::string last_directory = ""; + ListOfTests finalList; for ( it = this->TestList.begin(); it != this->TestList.end(); it ++ ) { cnt ++; @@ -965,23 +969,6 @@ void cmCTestTestHandler::ProcessDirectory(std::vector<cmStdString> &passed, inREcnt++; } - // if we are out of time then skip this test, we leave two minutes - // to submit results - if (this->CTest->GetRemainingTimeAllowed() - 120 <= 0) - { - continue; - } - - 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()); - } - if (this->UseUnion) { // if it is not in the list and not in the regexp then skip @@ -1003,10 +990,322 @@ void cmCTestTestHandler::ProcessDirectory(std::vector<cmStdString> &passed, 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; +} + +bool cmCTestTestHandler::GetValue(const char* tag, + int& value, + std::ifstream& fin) +{ + std::string line; + cmSystemTools::GetLineFromStream(fin, line); + if(line == tag) + { + fin >> value; + cmSystemTools::GetLineFromStream(fin, line); // read blank line + } + else + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "parse error: missing tag: " + << tag << " found [" << line << "]" << std::endl); + return false; + } + return true; +} + +bool cmCTestTestHandler::GetValue(const char* tag, + double& value, + std::ifstream& fin) +{ + std::string line; + cmSystemTools::GetLineFromStream(fin, line); + if(line == tag) + { + fin >> value; + cmSystemTools::GetLineFromStream(fin, line); // read blank line + } + else + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "parse error: missing tag: " + << tag << " found [" << line << "]" << std::endl); + return false; + } + return true; +} + +bool cmCTestTestHandler::GetValue(const char* tag, + bool& value, + std::ifstream& fin) +{ + std::string line; + cmSystemTools::GetLineFromStream(fin, line); + if(line == tag) + { + fin >> value; + cmSystemTools::GetLineFromStream(fin, line); // read blank line + } + else + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "parse error: missing tag: " + << tag << " found [" << line << "]" << std::endl); + return false; + } + return true; +} + +bool cmCTestTestHandler::GetValue(const char* tag, + size_t& value, + std::ifstream& fin) +{ + std::string line; + cmSystemTools::GetLineFromStream(fin, line); + if(line == tag) + { + fin >> value; + cmSystemTools::GetLineFromStream(fin, line); // read blank line + } + else + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "parse error: missing tag: " + << tag << " found [" << line.c_str() << "]" << std::endl); + return false; + } + return true; +} + +bool cmCTestTestHandler::GetValue(const char* tag, + std::string& value, + std::ifstream& fin) +{ + std::string line; + cmSystemTools::GetLineFromStream(fin, line); + if(line == tag) + { + return cmSystemTools::GetLineFromStream(fin, value); + } + else + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "parse error: missing tag: " + << tag << " found [" << line << "]" << std::endl); + return false; + } + return true; +} + + +// This should load only one test and is used in -j N mode. +// it is used by the sub-process ctest runs which should have +// only one -I N test to run. +void cmCTestTestHandler::LoadTestList() +{ + this->TestList.clear(); + std::string fname = this->CTest->GetBinaryDir() + + "/Testing/Temporary/PCache.txt"; + std::ifstream fin(fname.c_str()); + std::string line; + if(!fin) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Could not load PCache.txt file: " + << fname.c_str() << std::endl); + return; + } + bool ok = true; + int numTestsToRun; + ok = ok && this->GetValue("TotalNumberOfTests:", + this->TotalNumberOfTests, fin); + ok = ok && this->GetValue("NumberOfTestsToRun:", numTestsToRun, fin); + this->ExpandTestsToRunInformation(this->TotalNumberOfTests); + if(this->TestsToRun.size() != 1) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Error when in parallel mode only one test should be run: " + << this->TestsToRun.size() << std::endl); + } + int testIndexToRun = this->TestsToRun[0]; + this->CTest->SetParallelSubprocessId(testIndexToRun); + if(!ok) + { + return; + } + for(int i =0; i < numTestsToRun; i++) + { + cmCTestTestProperties p; + int numArgs; + bool ok = this->GetValue("Name:", p.Name, fin); + ok = ok && this->GetValue("Directory:", p.Directory, fin); + ok = ok && this->GetValue("Args:", numArgs, fin); + for(int j =0; j < numArgs; ++j) + { + cmSystemTools::GetLineFromStream(fin, line); + p.Args.push_back(line); + } + int numDep = 0; + ok = ok && this->GetValue("Depends:", numDep, fin); + for(int j =0; j < numDep; ++j) + { + cmSystemTools::GetLineFromStream(fin, line); + p.Depends.push_back(line); + } + int isinre; + ok = ok && this->GetValue("IsInBasedOnREOptions:", isinre, fin); + ok = ok && this->GetValue("WillFail:", p.WillFail, fin); + ok = ok && this->GetValue("TimeOut:", p.Timeout, fin); + ok = ok && this->GetValue("Index:", p.Index, fin); + if(!ok) + { + return; + } + if(p.Index == testIndexToRun) + { + // add the one test and stop reading + this->TestList.push_back(p); + return; + } + } +} +std::string cmCTestTestHandler::SaveTestList() +{ + std::string fname = this->CTest->GetBinaryDir() + + "/Testing/Temporary/PCache.txt"; + cmGeneratedFileStream fout(fname.c_str()); + if(!fout) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, std::endl + << "Could not open PCache.txt for write:" + << fname.c_str() + << std::endl); + } + fout << "TotalNumberOfTests:\n"; + fout << this->TotalNumberOfTests << "\n"; + fout << "NumberOfTestsToRun:\n"; + fout << this->TestList.size() << "\n"; + for (ListOfTests::iterator it = this->TestList.begin(); + it != this->TestList.end(); it ++ ) + { + cmCTestTestProperties& p = *it; + fout << "Name:\n" + << p.Name.c_str() + << "\nDirectory:\n" + << p.Directory.c_str() + << "\nArgs:\n" + << p.Args.size() << "\n"; + for(std::vector<std::string>::iterator i = p.Args.begin(); + i != p.Args.end(); ++i) + { + fout << i->c_str() << "\n"; + } + fout << "Depends:\n" << p.Depends.size() << "\n"; + for(std::vector<std::string>::iterator i = p.Depends.begin(); + i != p.Depends.end(); ++i) + { + fout << i->c_str() << "\n"; + } + fout << "IsInBasedOnREOptions:\n" + << p.IsInBasedOnREOptions + << "\nWillFail:\n" + << p.WillFail + << "\nTimeOut:\n" + << p.Timeout + << "\nIndex:\n" + << p.Index << "\n"; + } + fout.close(); + return fname; +} + +void cmCTestTestHandler::ProcessParallel(std::vector<cmStdString> &passed, + std::vector<cmStdString> &failed) +{ + this->ComputeTestList(); + cmCTestMultiProcessHandler parallel; + parallel.SetCTest(this->CTest); + parallel.SetParallelLevel(this->CTest->GetParallelLevel()); + std::set<int> depends; + std::map<int, std::set<int> > tests; + std::map<int, cmStdString> testnames; + 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(); + 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; + } + parallel.SetCTestCommand(this->CTest->GetCTestExecutable()); + parallel.SetTests(tests, testnames); + std::string fname = this->SaveTestList(); + parallel.SetTestCacheFile(fname.c_str()); + 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 - this->ProcessOneTest(&(*it), passed, failed, cnt, - static_cast<int>(tmsize)); + this->ProcessOneTest(&(*it), passed, failed, it->Index, + static_cast<int>(this->TotalNumberOfTests)); } this->EndTest = this->CTest->CurrentTime(); @@ -1890,6 +2189,17 @@ bool cmCTestTestHandler::SetTestsProperties( std::string(crit->c_str()))); } } + 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 ) + { + cmCTestTestProperties* tp = &(*rtit); + rtit->Depends.push_back(*crit); + } + } if ( key == "MEASUREMENT" ) { size_t pos = val.find_first_of("="); diff --git a/Source/CTest/cmCTestTestHandler.h b/Source/CTest/cmCTestTestHandler.h index 395ad3b..1b92831 100644 --- a/Source/CTest/cmCTestTestHandler.h +++ b/Source/CTest/cmCTestTestHandler.h @@ -80,6 +80,7 @@ public: cmStdString Name; cmStdString Directory; std::vector<std::string> Args; + std::vector<std::string> Depends; std::vector<std::pair<cmsys::RegularExpression, std::string> > ErrorRegularExpressions; std::vector<std::pair<cmsys::RegularExpression, @@ -88,6 +89,7 @@ public: bool IsInBasedOnREOptions; bool WillFail; double Timeout; + int Index; }; struct cmCTestTestResult @@ -120,6 +122,7 @@ public: std::vector<std::string> &failed); protected: + // comput a final test list virtual int PreProcessHandler(); virtual int PostProcessHandler(); virtual void GenerateTestCommand(std::vector<const char*>& args); @@ -152,7 +155,7 @@ protected: -private: +public: enum { // Program statuses NOT_RUN = 0, TIMEOUT, @@ -166,7 +169,7 @@ private: COMPLETED }; - +private: /** * Generate the Dart compatible output */ @@ -183,7 +186,32 @@ private: * 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(); + + // Save the state of the test list and return the file + // name it was saved to + std::string SaveTestList(); + void LoadTestList(); + 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); + // run in -j N mode + void ProcessParallel(std::vector<cmStdString> &passed, + std::vector<cmStdString> &failed); /** * Find the executable for a test */ @@ -210,6 +238,7 @@ private: std::string TestsToRunString; bool UseUnion; ListOfTests TestList; + size_t TotalNumberOfTests; cmsys::RegularExpression DartStuff; std::ostream* LogFile; |