From bb7b27e417b72d0387af5393b81ed35deab52c4b Mon Sep 17 00:00:00 2001 From: Bill Hoffman Date: Thu, 3 Jul 2008 09:31:33 -0400 Subject: [PATCH] ENH: add initial ctest -j feature --- Source/CMakeLists.txt | 3 + Source/CTest/cmCTestGenericHandler.cxx | 6 + Source/CTest/cmCTestMultiProcessHandler.cxx | 323 +++++++++++++++ Source/CTest/cmCTestMultiProcessHandler.h | 84 ++++ Source/CTest/cmCTestTestHandler.cxx | 438 +++++++++++++++++--- Source/CTest/cmCTestTestHandler.h | 35 +- Source/cmCTest.cxx | 43 +- Source/cmCTest.h | 18 + Source/cmGlobalGenerator.cxx | 2 +- Source/cmGlobalUnixMakefileGenerator3.cxx | 7 +- Tests/CMakeLists.txt | 9 + 11 files changed, 893 insertions(+), 75 deletions(-) create mode 100644 Source/CTest/cmCTestMultiProcessHandler.cxx create mode 100644 Source/CTest/cmCTestMultiProcessHandler.h diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 086fb2c0d..b4d1eb1c1 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -314,6 +314,7 @@ INCLUDE_DIRECTORIES( # Sources for CTestLib # SET(CTEST_SRCS cmCTest.cxx + CTest/cmProcess.cxx CTest/cmCTestBuildAndTestHandler.cxx CTest/cmCTestBuildCommand.cxx CTest/cmCTestBuildHandler.cxx @@ -326,6 +327,7 @@ SET(CTEST_SRCS cmCTest.cxx CTest/cmCTestHandlerCommand.cxx CTest/cmCTestMemCheckCommand.cxx CTest/cmCTestMemCheckHandler.cxx + CTest/cmCTestMultiProcessHandler.cxx CTest/cmCTestReadCustomFilesCommand.cxx CTest/cmCTestRunScriptCommand.cxx CTest/cmCTestScriptHandler.cxx @@ -458,3 +460,4 @@ IF(APPLE) ENDIF(APPLE) INSTALL_FILES(${CMAKE_DATA_DIR}/include cmCPluginAPI.h) + diff --git a/Source/CTest/cmCTestGenericHandler.cxx b/Source/CTest/cmCTestGenericHandler.cxx index 50c5a20f5..1695bc17d 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 000000000..4c9be362e --- /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 >& tests, + std::map& testNames) +{ + // set test run map to false for all + for(std::map >::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::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 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 depends = this->Tests[test]; + size_t totalDepends = depends.size(); + if(totalDepends) + { + for(std::set::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 > tests = this->Tests; + for(std::map >::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 finished; + std::string out, err; + 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"; + } + if(!p->IsRunning()) + { + finished.push_back(p); + } + } + for( std::vector::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 >::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 >::iterator i = this->Tests.begin(); + i!= this->Tests.end(); ++i) + { + std::cout << "Test " << i->first << " ("; + for(std::set::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 > tests; + std::set 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 000000000..9f7976db6 --- /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 +#include +#include +#include +class cmProcess; +#include +#include + +/** \class cmCTestMultiProcessHandler + * \brief run parallel ctest + * + * cmCTestMultiProcessHandler + */ +class cmCTestMultiProcessHandler +{ +public: + cmCTestMultiProcessHandler(); + // Set the tests + void SetTests(std::map >& tests, + std::map& 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* passed, + std::vector* failed) + { + this->Passed = passed; + this->Failed = failed; + } + void SetTestResults(std::vector* 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 > Tests; + std::map TestNames; + std::map TestRunningMap; + std::map TestFinishMap; + std::map TestOutput; + 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 +}; + +#endif diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx index fe3c3ae8a..2db4d6d88 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 passed; std::vector 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(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(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::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::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::const_iterator j = args.begin(); - ++j; - ++j; + ++j; // skip test name + ++j; // skip command as it is in actualCommand std::vector arguments; this->GenerateTestCommand(arguments); arguments.push_back(actualCommand.c_str()); @@ -917,23 +923,20 @@ void cmCTestTestHandler::ProcessOneTest(cmCTestTestProperties *it, } //---------------------------------------------------------------------- -void cmCTestTestHandler::ProcessDirectory(std::vector &passed, - std::vector &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(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 &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 &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 &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::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::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 &passed, + std::vector &failed) +{ + this->ComputeTestList(); + cmCTestMultiProcessHandler parallel; + parallel.SetCTest(this->CTest); + parallel.SetParallelLevel(this->CTest->GetParallelLevel()); + std::set depends; + std::map > tests; + std::map 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::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 &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 - this->ProcessOneTest(&(*it), passed, failed, cnt, - static_cast(tmsize)); + this->ProcessOneTest(&(*it), passed, failed, it->Index, + static_cast(this->TotalNumberOfTests)); } this->EndTest = this->CTest->CurrentTime(); @@ -1890,6 +2189,17 @@ bool cmCTestTestHandler::SetTestsProperties( std::string(crit->c_str()))); } } + if ( key == "DEPENDS" ) + { + std::vector lval; + cmSystemTools::ExpandListArgument(val.c_str(), lval); + std::vector::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 395ad3b3f..1b9283141 100644 --- a/Source/CTest/cmCTestTestHandler.h +++ b/Source/CTest/cmCTestTestHandler.h @@ -80,6 +80,7 @@ public: cmStdString Name; cmStdString Directory; std::vector Args; + std::vector Depends; std::vector > ErrorRegularExpressions; std::vector &failed); protected: + // comput a final test list virtual int PreProcessHandler(); virtual int PostProcessHandler(); virtual void GenerateTestCommand(std::vector& 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 &passed, + std::vector &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; diff --git a/Source/cmCTest.cxx b/Source/cmCTest.cxx index de9ddac1f..39da6cc36 100644 --- a/Source/cmCTest.cxx +++ b/Source/cmCTest.cxx @@ -229,10 +229,13 @@ std::string cmCTest::MakeURLSafe(const std::string& str) //---------------------------------------------------------------------- cmCTest::cmCTest() { + this->ParallelSubprocess = false; + this->ParallelLevel = 0; this->SubmitIndex = 0; this->ForceNewCTestProcess = false; this->TomorrowTag = false; this->Verbose = false; + this->Debug = false; this->ShowLineNumbers = false; this->Quiet = false; @@ -802,7 +805,11 @@ int cmCTest::ProcessTests() int cc; int update_count = 0; - cmCTestLog(this, OUTPUT, "Start processing tests" << std::endl); + // do not output startup if this is a sub-process for parallel tests + if(!this->GetParallelSubprocess()) + { + cmCTestLog(this, OUTPUT, "Start processing tests" << std::endl); + } for ( cc = 0; cc < LAST_TEST; cc ++ ) { @@ -915,8 +922,11 @@ int cmCTest::ProcessTests() } if ( res != 0 ) { - cmCTestLog(this, ERROR_MESSAGE, "Errors while running CTest" - << std::endl); + if(!this->GetParallelSubprocess()) + { + cmCTestLog(this, ERROR_MESSAGE, "Errors while running CTest" + << std::endl); + } } return res; } @@ -1626,10 +1636,31 @@ void cmCTest::HandleCommandLineArguments(size_t &i, std::vector &args) { std::string arg = args[i]; - if(this->CheckArgument(arg, "--ctest-config") && i < args.size() - 1) + + if(this->CheckArgument(arg, "-j", "--parallel") && i < args.size() - 1) { i++; - this->CTestConfigFile= args[i]; + int plevel = atoi(args[i].c_str()); + this->SetParallelLevel(plevel); + } + else if(arg.find("-j") == 0) + { + int plevel = atoi(arg.substr(2).c_str()); + this->SetParallelLevel(plevel); + } + if(this->CheckArgument(arg, "--internal-ctest-parallel") && i < args.size() - 1) + { + i++; + int pid = atoi(args[i].c_str()); + this->SetParallelSubprocessId(pid); + this->SetParallelSubprocess(); + } + + if(this->CheckArgument(arg, "--parallel-cache") && i < args.size() - 1) + { + i++; + int plevel = atoi(args[i].c_str()); + this->SetParallelCacheFile(args[i].c_str()); } if(this->CheckArgument(arg, "-C", "--build-config") && @@ -1966,6 +1997,8 @@ int cmCTest::Run(std::vector &args, std::string* output) } else { + // What is this? -V seems to be the same as -VV, + // and Verbose is always on in this case this->ExtraVerbose = this->Verbose; this->Verbose = true; cmCTest::t_TestingHandlers::iterator it; diff --git a/Source/cmCTest.h b/Source/cmCTest.h index 7dfaa9243..01d3b424c 100644 --- a/Source/cmCTest.h +++ b/Source/cmCTest.h @@ -95,6 +95,18 @@ public: std::string const& GetConfigType(); double GetTimeOut() { return this->TimeOut; } 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; } + + bool GetParallelSubprocess() { return this->ParallelSubprocess; } + void SetParallelSubprocess() { this->ParallelSubprocess = true; } + + void SetParallelSubprocessId(int id) { this->ParallelSubprocessId = id;} + int GetParallelSubprocessId() { return this->ParallelSubprocessId;} + const char* GetParallelCacheFile() + { return this->ParallelCacheFile.c_str();} + void SetParallelCacheFile(const char* c) { this->ParallelCacheFile = c; } /** * Check if CTest file exists @@ -310,6 +322,8 @@ public: void SetSpecificTrack(const char* track); const char* GetSpecificTrack(); + bool GetVerbose() { return this->Verbose;} + bool GetExtraVerbose() { return this->ExtraVerbose;} private: std::string ConfigType; bool Verbose; @@ -359,6 +373,10 @@ private: double TimeOut; + std::string ParallelCacheFile; + int ParallelLevel; + int ParallelSubprocessId; + bool ParallelSubprocess; int CompatibilityMode; // information for the --build-and-test options diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index e6436e419..f57089acf 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -196,7 +196,7 @@ void cmGlobalGenerator::FindMakeProgram(cmMakefile* mf) void cmGlobalGenerator::EnableLanguage(std::vectorconst& languages, - cmMakefile *mf, bool) + cmMakefile *mf, bool optional) { if(languages.size() == 0) { diff --git a/Source/cmGlobalUnixMakefileGenerator3.cxx b/Source/cmGlobalUnixMakefileGenerator3.cxx index ebd228d24..c72ff184a 100644 --- a/Source/cmGlobalUnixMakefileGenerator3.cxx +++ b/Source/cmGlobalUnixMakefileGenerator3.cxx @@ -59,8 +59,11 @@ void cmGlobalUnixMakefileGenerator3 if(!mf->GetDefinition(langComp.c_str())) { - cmSystemTools::Error(langComp.c_str(), - " not set, after EnableLanguage"); + if(!optional) + { + cmSystemTools::Error(langComp.c_str(), + " not set, after EnableLanguage"); + } continue; } const char* name = mf->GetRequiredDefinition(langComp.c_str()); diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index febb46af9..88e9acafb 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -580,6 +580,15 @@ ${CMake_BINARY_DIR}/bin/cmake -DVERSION=CVS -P ${CMake_SOURCE_DIR}/Utilities/Rel --build-project LinkLineOrder --test-command Exec2 ) + IF(COMMAND SET_TESTS_PROPERTIES AND COMMAND GET_TEST_PROPERTY) + SET_TESTS_PROPERTIES ( qtwrapping PROPERTIES DEPENDS wrapping) + SET_TESTS_PROPERTIES ( testdriver1 PROPERTIES DEPENDS qtwrapping) + SET_TESTS_PROPERTIES ( testdriver2 PROPERTIES DEPENDS testdriver1) + SET_TESTS_PROPERTIES ( testdriver3 PROPERTIES DEPENDS testdriver2) + SET_TESTS_PROPERTIES ( linkorder2 PROPERTIES DEPENDS linkorder1) + SET_TESTS_PROPERTIES ( SubProject-Stage2 PROPERTIES DEPENDS SubProject) + SET_TESTS_PROPERTIES ( SimpleInstall-Stage2 PROPERTIES DEPENDS SimpleInstall) + ENDIF(COMMAND SET_TESTS_PROPERTIES AND COMMAND GET_TEST_PROPERTY) IF(NOT CMAKE_TEST_DIFFERENT_GENERATOR) ADD_TEST(kwsys ${CMAKE_CTEST_COMMAND}