diff options
Diffstat (limited to 'Source/CTest/cmCTestMultiProcessHandler.cxx')
-rw-r--r-- | Source/CTest/cmCTestMultiProcessHandler.cxx | 957 |
1 files changed, 957 insertions, 0 deletions
diff --git a/Source/CTest/cmCTestMultiProcessHandler.cxx b/Source/CTest/cmCTestMultiProcessHandler.cxx new file mode 100644 index 0000000..4832186 --- /dev/null +++ b/Source/CTest/cmCTestMultiProcessHandler.cxx @@ -0,0 +1,957 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2000-2009 Kitware, Inc., Insight Software Consortium + + Distributed under the OSI-approved BSD License (the "License"); + see accompanying file Copyright.txt for details. + + This software is distributed WITHOUT ANY WARRANTY; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the License for more information. +============================================================================*/ +#include "cmCTestMultiProcessHandler.h" +#include "cmProcess.h" +#include "cmStandardIncludes.h" +#include "cmCTest.h" +#include "cmCTestScriptHandler.h" +#include "cmSystemTools.h" +#include <stdlib.h> +#include <stack> +#include <list> +#include <float.h> +#include <math.h> +#include <cmsys/FStream.hxx> +#include <cmsys/SystemInformation.hxx> + +class TestComparator +{ +public: + TestComparator(cmCTestMultiProcessHandler* handler) : Handler(handler) {} + ~TestComparator() {} + + // Sorts tests in descending order of cost + bool operator() (int index1, int index2) const + { + return Handler->Properties[index1]->Cost > + Handler->Properties[index2]->Cost; + } + +private: + cmCTestMultiProcessHandler* Handler; +}; + +cmCTestMultiProcessHandler::cmCTestMultiProcessHandler() +{ + this->ParallelLevel = 1; + this->TestLoad = 0; + this->Completed = 0; + this->RunningCount = 0; + this->StopTimePassed = false; + this->HasCycles = false; + this->SerialTestRunning = false; +} + +cmCTestMultiProcessHandler::~cmCTestMultiProcessHandler() +{ +} + + // Set the tests +void +cmCTestMultiProcessHandler::SetTests(TestMap& tests, + PropertiesMap& properties) +{ + this->Tests = tests; + this->Properties = properties; + this->Total = this->Tests.size(); + // set test run map to false for all + for(TestMap::iterator i = this->Tests.begin(); + i != this->Tests.end(); ++i) + { + this->TestRunningMap[i->first] = false; + this->TestFinishMap[i->first] = false; + } + if(!this->CTest->GetShowOnly()) + { + this->ReadCostData(); + this->HasCycles = !this->CheckCycles(); + if(this->HasCycles) + { + return; + } + this->CreateTestCostList(); + } +} + + // Set the max number of tests that can be run at the same time. +void cmCTestMultiProcessHandler::SetParallelLevel(size_t level) +{ + this->ParallelLevel = level < 1 ? 1 : level; +} + +void cmCTestMultiProcessHandler::SetTestLoad(unsigned long load) +{ + this->TestLoad = load; +} + +//--------------------------------------------------------- +void cmCTestMultiProcessHandler::RunTests() +{ + this->CheckResume(); + if(this->HasCycles) + { + return; + } + this->TestHandler->SetMaxIndex(this->FindMaxIndex()); + this->StartNextTests(); + while(!this->Tests.empty()) + { + if(this->StopTimePassed) + { + return; + } + this->CheckOutput(); + this->StartNextTests(); + } + // let all running tests finish + while(this->CheckOutput()) + { + } + this->MarkFinished(); + this->UpdateCostData(); +} + +//--------------------------------------------------------- +void cmCTestMultiProcessHandler::StartTestProcess(int test) +{ + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "test " << test << "\n", this->Quiet); + this->TestRunningMap[test] = true; // mark the test as running + // now remove the test itself + this->EraseTest(test); + this->RunningCount += GetProcessorsUsed(test); + + cmCTestRunTest* testRun = new cmCTestRunTest(this->TestHandler); + if(this->CTest->GetRepeatUntilFail()) + { + testRun->SetRunUntilFailOn(); + testRun->SetNumberOfRuns(this->CTest->GetTestRepeat()); + } + testRun->SetIndex(test); + testRun->SetTestProperties(this->Properties[test]); + + std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory(); + cmSystemTools::ChangeDirectory(this->Properties[test]->Directory); + + // Lock the resources we'll be using + this->LockResources(test); + + if(testRun->StartTest(this->Total)) + { + this->RunningTests.insert(testRun); + } + else if(testRun->IsStopTimePassed()) + { + this->StopTimePassed = true; + delete testRun; + return; + } + else + { + + for(TestMap::iterator j = this->Tests.begin(); + j != this->Tests.end(); ++j) + { + j->second.erase(test); + } + + this->UnlockResources(test); + this->Completed++; + this->TestFinishMap[test] = true; + this->TestRunningMap[test] = false; + this->RunningCount -= GetProcessorsUsed(test); + testRun->EndTest(this->Completed, this->Total, false); + this->Failed->push_back(this->Properties[test]->Name); + delete testRun; + } + cmSystemTools::ChangeDirectory(current_dir); +} + +//--------------------------------------------------------- +void cmCTestMultiProcessHandler::LockResources(int index) +{ + this->LockedResources.insert( + this->Properties[index]->LockedResources.begin(), + this->Properties[index]->LockedResources.end()); + + if (this->Properties[index]->RunSerial) + { + this->SerialTestRunning = true; + } +} + +//--------------------------------------------------------- +void cmCTestMultiProcessHandler::UnlockResources(int index) +{ + for(std::set<std::string>::iterator i = + this->Properties[index]->LockedResources.begin(); + i != this->Properties[index]->LockedResources.end(); ++i) + { + this->LockedResources.erase(*i); + } +} + +//--------------------------------------------------------- +void cmCTestMultiProcessHandler::EraseTest(int test) +{ + this->Tests.erase(test); + this->SortedTests.erase( + std::find(this->SortedTests.begin(), this->SortedTests.end(), test)); +} + +//--------------------------------------------------------- +inline size_t cmCTestMultiProcessHandler::GetProcessorsUsed(int test) +{ + size_t processors = + static_cast<int>(this->Properties[test]->Processors); + //If processors setting is set higher than the -j + //setting, we default to using all of the process slots. + if (processors > this->ParallelLevel) + { + processors = this->ParallelLevel; + } + return processors; +} + +std::string cmCTestMultiProcessHandler::GetName(int test) +{ + return this->Properties[test]->Name; +} + +//--------------------------------------------------------- +bool cmCTestMultiProcessHandler::StartTest(int test) +{ + //Check for locked resources + for(std::set<std::string>::iterator i = + this->Properties[test]->LockedResources.begin(); + i != this->Properties[test]->LockedResources.end(); ++i) + { + if(this->LockedResources.find(*i) != this->LockedResources.end()) + { + return false; + } + } + + // if there are no depends left then run this test + if(this->Tests[test].empty()) + { + this->StartTestProcess(test); + return true; + } + // This test was not able to start because it is waiting + // on depends to run + return false; +} + +//--------------------------------------------------------- +void cmCTestMultiProcessHandler::StartNextTests() +{ + size_t numToStart = 0; + if(this->RunningCount < this->ParallelLevel) + { + numToStart = this->ParallelLevel - this->RunningCount; + } + + if(numToStart == 0) + { + return; + } + + // Don't start any new tests if one with the RUN_SERIAL property + // is already running. + if (this->SerialTestRunning) + { + return; + } + + bool allTestsFailedTestLoadCheck = false; + bool usedFakeLoadForTesting = false; + size_t minProcessorsRequired = this->ParallelLevel; + std::string testWithMinProcessors = ""; + + cmsys::SystemInformation info; + + unsigned long systemLoad = 0; + size_t spareLoad = 0; + if (this->TestLoad > 0) + { + // Activate possible wait. + allTestsFailedTestLoadCheck = true; + + // Check for a fake load average value used in testing. + if (const char* fake_load_value = + cmSystemTools::GetEnv("__CTEST_FAKE_LOAD_AVERAGE_FOR_TESTING")) + { + usedFakeLoadForTesting = true; + if (!cmSystemTools::StringToULong(fake_load_value, &systemLoad)) + { + cmSystemTools::Error("Failed to parse fake load value: ", + fake_load_value); + } + } + // If it's not set, look up the true load average. + else + { + systemLoad = static_cast<unsigned long>(ceil(info.GetLoadAverage())); + } + spareLoad = (this->TestLoad > systemLoad ? + this->TestLoad - systemLoad : 0); + + // Don't start more tests than the spare load can support. + if (numToStart > spareLoad) + { + numToStart = spareLoad; + } + } + + TestList copy = this->SortedTests; + for(TestList::iterator test = copy.begin(); test != copy.end(); ++test) + { + // Take a nap if we're currently performing a RUN_SERIAL test. + if (this->SerialTestRunning) + { + break; + } + // We can only start a RUN_SERIAL test if no other tests are also running. + if (this->Properties[*test]->RunSerial && this->RunningCount > 0) + { + continue; + } + + size_t processors = GetProcessorsUsed(*test); + bool testLoadOk = true; + if (this->TestLoad > 0) + { + if (processors <= spareLoad) + { + cmCTestLog(this->CTest, DEBUG, + "OK to run " << GetName(*test) << + ", it requires " << processors << + " procs & system load is: " << + systemLoad << std::endl); + allTestsFailedTestLoadCheck = false; + } + else + { + testLoadOk = false; + } + } + + if (processors <= minProcessorsRequired) + { + minProcessorsRequired = processors; + testWithMinProcessors = GetName(*test); + } + + if(testLoadOk && processors <= numToStart && this->StartTest(*test)) + { + if(this->StopTimePassed) + { + return; + } + + numToStart -= processors; + } + else if(numToStart == 0) + { + break; + } + } + + if (allTestsFailedTestLoadCheck) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, "***** WAITING, "); + if (this->SerialTestRunning) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, + "Waiting for RUN_SERIAL test to finish."); + } + else + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, + "System Load: " << systemLoad << ", " + "Max Allowed Load: " << this->TestLoad << ", " + "Smallest test " << testWithMinProcessors << + " requires " << minProcessorsRequired); + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, "*****" << std::endl); + + if (usedFakeLoadForTesting) + { + // Break out of the infinite loop of waiting for our fake load + // to come down. + this->StopTimePassed = true; + } + else + { + // Wait between 1 and 5 seconds before trying again. + cmCTestScriptHandler::SleepInSeconds( + cmSystemTools::RandomSeed() % 5 + 1); + } + } +} + +//--------------------------------------------------------- +bool cmCTestMultiProcessHandler::CheckOutput() +{ + // no more output we are done + if(this->RunningTests.empty()) + { + return false; + } + std::vector<cmCTestRunTest*> finished; + std::string out, err; + for(std::set<cmCTestRunTest*>::const_iterator i = this->RunningTests.begin(); + i != this->RunningTests.end(); ++i) + { + cmCTestRunTest* p = *i; + if(!p->CheckOutput()) + { + finished.push_back(p); + } + } + for( std::vector<cmCTestRunTest*>::iterator i = finished.begin(); + i != finished.end(); ++i) + { + this->Completed++; + cmCTestRunTest* p = *i; + int test = p->GetIndex(); + + bool testResult = p->EndTest(this->Completed, this->Total, true); + if(p->StartAgain()) + { + this->Completed--; // remove the completed test because run again + continue; + } + if(testResult) + { + this->Passed->push_back(p->GetTestProperties()->Name); + } + else + { + this->Failed->push_back(p->GetTestProperties()->Name); + } + for(TestMap::iterator j = this->Tests.begin(); + j != this->Tests.end(); ++j) + { + j->second.erase(test); + } + this->TestFinishMap[test] = true; + this->TestRunningMap[test] = false; + this->RunningTests.erase(p); + this->WriteCheckpoint(test); + this->UnlockResources(test); + this->RunningCount -= GetProcessorsUsed(test); + if (this->Properties[test]->RunSerial) + { + this->SerialTestRunning = false; + } + + delete p; + } + return true; +} + +//--------------------------------------------------------- +void cmCTestMultiProcessHandler::UpdateCostData() +{ + std::string fname = this->CTest->GetCostDataFile(); + std::string tmpout = fname + ".tmp"; + cmsys::ofstream fout; + fout.open(tmpout.c_str()); + + PropertiesMap temp = this->Properties; + + if(cmSystemTools::FileExists(fname.c_str())) + { + cmsys::ifstream fin; + fin.open(fname.c_str()); + + std::string line; + while(std::getline(fin, line)) + { + if(line == "---") break; + std::vector<cmsys::String> parts = + cmSystemTools::SplitString(line, ' '); + //Format: <name> <previous_runs> <avg_cost> + if(parts.size() < 3) break; + + std::string name = parts[0]; + int prev = atoi(parts[1].c_str()); + float cost = static_cast<float>(atof(parts[2].c_str())); + + int index = this->SearchByName(name); + if(index == -1) + { + // This test is not in memory. We just rewrite the entry + fout << name << " " << prev << " " << cost << "\n"; + } + else + { + // Update with our new average cost + fout << name << " " << this->Properties[index]->PreviousRuns << " " + << this->Properties[index]->Cost << "\n"; + temp.erase(index); + } + } + fin.close(); + cmSystemTools::RemoveFile(fname); + } + + // Add all tests not previously listed in the file + for(PropertiesMap::iterator i = temp.begin(); i != temp.end(); ++i) + { + fout << i->second->Name << " " << i->second->PreviousRuns << " " + << i->second->Cost << "\n"; + } + + // Write list of failed tests + fout << "---\n"; + for(std::vector<std::string>::iterator i = this->Failed->begin(); + i != this->Failed->end(); ++i) + { + fout << i->c_str() << "\n"; + } + fout.close(); + cmSystemTools::RenameFile(tmpout.c_str(), fname.c_str()); +} + +//--------------------------------------------------------- +void cmCTestMultiProcessHandler::ReadCostData() +{ + std::string fname = this->CTest->GetCostDataFile(); + + if(cmSystemTools::FileExists(fname.c_str(), true)) + { + cmsys::ifstream fin; + fin.open(fname.c_str()); + std::string line; + while(std::getline(fin, line)) + { + if(line == "---") break; + + std::vector<cmsys::String> parts = + cmSystemTools::SplitString(line, ' '); + + // Probably an older version of the file, will be fixed next run + if(parts.size() < 3) + { + fin.close(); + return; + } + + std::string name = parts[0]; + int prev = atoi(parts[1].c_str()); + float cost = static_cast<float>(atof(parts[2].c_str())); + + int index = this->SearchByName(name); + if(index == -1) continue; + + this->Properties[index]->PreviousRuns = prev; + // When not running in parallel mode, don't use cost data + if(this->ParallelLevel > 1 && + this->Properties[index] && + this->Properties[index]->Cost == 0) + { + this->Properties[index]->Cost = cost; + } + } + // Next part of the file is the failed tests + while(std::getline(fin, line)) + { + if(line != "") + { + this->LastTestsFailed.push_back(line); + } + } + fin.close(); + } +} + +//--------------------------------------------------------- +int cmCTestMultiProcessHandler::SearchByName(std::string name) +{ + int index = -1; + + for(PropertiesMap::iterator i = this->Properties.begin(); + i != this->Properties.end(); ++i) + { + if(i->second->Name == name) + { + index = i->first; + } + } + return index; +} + +//--------------------------------------------------------- +void cmCTestMultiProcessHandler::CreateTestCostList() +{ + if(this->ParallelLevel > 1) + { + CreateParallelTestCostList(); + } + else + { + CreateSerialTestCostList(); + } +} + +//--------------------------------------------------------- +void cmCTestMultiProcessHandler::CreateParallelTestCostList() +{ + TestSet alreadySortedTests; + + std::list<TestSet> priorityStack; + priorityStack.push_back(TestSet()); + TestSet &topLevel = priorityStack.back(); + + // In parallel test runs add previously failed tests to the front + // of the cost list and queue other tests for further sorting + for(TestMap::const_iterator i = this->Tests.begin(); + i != this->Tests.end(); ++i) + { + if(std::find(this->LastTestsFailed.begin(), this->LastTestsFailed.end(), + this->Properties[i->first]->Name) != this->LastTestsFailed.end()) + { + //If the test failed last time, it should be run first. + this->SortedTests.push_back(i->first); + alreadySortedTests.insert(i->first); + } + else + { + topLevel.insert(i->first); + } + } + + // In parallel test runs repeatedly move dependencies of the tests on + // the current dependency level to the next level until no + // further dependencies exist. + while(priorityStack.back().size()) + { + TestSet &previousSet = priorityStack.back(); + priorityStack.push_back(TestSet()); + TestSet ¤tSet = priorityStack.back(); + + for(TestSet::const_iterator i = previousSet.begin(); + i != previousSet.end(); ++i) + { + TestSet const& dependencies = this->Tests[*i]; + currentSet.insert(dependencies.begin(), dependencies.end()); + } + + for(TestSet::const_iterator i = currentSet.begin(); + i != currentSet.end(); ++i) + { + previousSet.erase(*i); + } + } + + // Remove the empty dependency level + priorityStack.pop_back(); + + // Reverse iterate over the different dependency levels (deepest first). + // Sort tests within each level by COST and append them to the cost list. + for(std::list<TestSet>::reverse_iterator i = priorityStack.rbegin(); + i != priorityStack.rend(); ++i) + { + TestSet const& currentSet = *i; + TestComparator comp(this); + + TestList sortedCopy; + + sortedCopy.insert(sortedCopy.end(), + currentSet.begin(), currentSet.end()); + + std::stable_sort(sortedCopy.begin(), sortedCopy.end(), comp); + + for(TestList::const_iterator j = sortedCopy.begin(); + j != sortedCopy.end(); ++j) + { + if(alreadySortedTests.find(*j) == alreadySortedTests.end()) + { + this->SortedTests.push_back(*j); + alreadySortedTests.insert(*j); + } + } + } +} + +//--------------------------------------------------------- +void cmCTestMultiProcessHandler::GetAllTestDependencies( + int test, TestList& dependencies) +{ + TestSet const& dependencySet = this->Tests[test]; + for(TestSet::const_iterator i = dependencySet.begin(); + i != dependencySet.end(); ++i) + { + GetAllTestDependencies(*i, dependencies); + dependencies.push_back(*i); + } +} + +//--------------------------------------------------------- +void cmCTestMultiProcessHandler::CreateSerialTestCostList() +{ + TestList presortedList; + + for(TestMap::iterator i = this->Tests.begin(); + i != this->Tests.end(); ++i) + { + presortedList.push_back(i->first); + } + + TestComparator comp(this); + std::stable_sort(presortedList.begin(), presortedList.end(), comp); + + TestSet alreadySortedTests; + + for(TestList::const_iterator i = presortedList.begin(); + i != presortedList.end(); ++i) + { + int test = *i; + + if(alreadySortedTests.find(test) != alreadySortedTests.end()) + { + continue; + } + + TestList dependencies; + GetAllTestDependencies(test, dependencies); + + for(TestList::const_iterator j = dependencies.begin(); + j != dependencies.end(); ++j) + { + int testDependency = *j; + + if(alreadySortedTests.find(testDependency) == alreadySortedTests.end()) + { + alreadySortedTests.insert(testDependency); + this->SortedTests.push_back(testDependency); + } + } + + alreadySortedTests.insert(test); + this->SortedTests.push_back(test); + } +} + +//--------------------------------------------------------- +void cmCTestMultiProcessHandler::WriteCheckpoint(int index) +{ + std::string fname = this->CTest->GetBinaryDir() + + "/Testing/Temporary/CTestCheckpoint.txt"; + cmsys::ofstream fout; + fout.open(fname.c_str(), std::ios::app); + fout << index << "\n"; + fout.close(); +} + +//--------------------------------------------------------- +void cmCTestMultiProcessHandler::MarkFinished() +{ + std::string fname = this->CTest->GetBinaryDir() + + "/Testing/Temporary/CTestCheckpoint.txt"; + cmSystemTools::RemoveFile(fname); +} + +//--------------------------------------------------------- +//For ShowOnly mode +void cmCTestMultiProcessHandler::PrintTestList() +{ + this->TestHandler->SetMaxIndex(this->FindMaxIndex()); + int count = 0; + + for (PropertiesMap::iterator it = this->Properties.begin(); + it != this->Properties.end(); ++it) + { + count++; + cmCTestTestHandler::cmCTestTestProperties& p = *it->second; + + //push working dir + std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory(); + cmSystemTools::ChangeDirectory(p.Directory); + + cmCTestRunTest testRun(this->TestHandler); + testRun.SetIndex(p.Index); + testRun.SetTestProperties(&p); + testRun.ComputeArguments(); //logs the command in verbose mode + + if(!p.Labels.empty()) //print the labels + { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Labels:", + this->Quiet); + } + for(std::vector<std::string>::iterator label = p.Labels.begin(); + label != p.Labels.end(); ++label) + { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " " << *label, + this->Quiet); + } + if(!p.Labels.empty()) //print the labels + { + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl, + this->Quiet); + } + + if (this->TestHandler->MemCheck) + { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " Memory Check", + this->Quiet); + } + else + { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " Test", this->Quiet); + } + std::ostringstream indexStr; + indexStr << " #" << p.Index << ":"; + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + std::setw(3 + getNumWidth(this->TestHandler->GetMaxIndex())) + << indexStr.str(), this->Quiet); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " ", this->Quiet); + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + p.Name.c_str() << std::endl, this->Quiet); + //pop working dir + cmSystemTools::ChangeDirectory(current_dir); + } + + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, std::endl << "Total Tests: " + << this->Total << std::endl, this->Quiet); +} + +void cmCTestMultiProcessHandler::PrintLabels() +{ + std::set<std::string> allLabels; + for (PropertiesMap::iterator it = this->Properties.begin(); + it != this->Properties.end(); ++it) + { + cmCTestTestHandler::cmCTestTestProperties& p = *it->second; + allLabels.insert(p.Labels.begin(), p.Labels.end()); + } + + if(!allLabels.empty()) + { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + "All Labels:" << std::endl, this->Quiet); + } + else + { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + "No Labels Exist" << std::endl, this->Quiet); + } + for(std::set<std::string>::iterator label = allLabels.begin(); + label != allLabels.end(); ++label) + { + cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, + " " << *label << std::endl, this->Quiet); + } +} + +//--------------------------------------------------------- +void cmCTestMultiProcessHandler::CheckResume() +{ + std::string fname = this->CTest->GetBinaryDir() + + "/Testing/Temporary/CTestCheckpoint.txt"; + if(this->CTest->GetFailover()) + { + if(cmSystemTools::FileExists(fname.c_str(), true)) + { + *this->TestHandler->LogFile << "Resuming previously interrupted test set" + << std::endl + << "----------------------------------------------------------" + << std::endl; + + cmsys::ifstream fin; + fin.open(fname.c_str()); + std::string line; + while(std::getline(fin, line)) + { + int index = atoi(line.c_str()); + this->RemoveTest(index); + } + fin.close(); + } + } + else if(cmSystemTools::FileExists(fname.c_str(), true)) + { + cmSystemTools::RemoveFile(fname); + } +} + +//--------------------------------------------------------- +void cmCTestMultiProcessHandler::RemoveTest(int index) +{ + this->EraseTest(index); + this->Properties.erase(index); + this->TestRunningMap[index] = false; + this->TestFinishMap[index] = true; + this->Completed++; +} + +//--------------------------------------------------------- +int cmCTestMultiProcessHandler::FindMaxIndex() +{ + int max = 0; + cmCTestMultiProcessHandler::TestMap::iterator i = this->Tests.begin(); + for(; i != this->Tests.end(); ++i) + { + if(i->first > max) + { + max = i->first; + } + } + return max; +} + +//Returns true if no cycles exist in the dependency graph +bool cmCTestMultiProcessHandler::CheckCycles() +{ + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Checking test dependency graph..." << std::endl, this->Quiet); + for(TestMap::iterator it = this->Tests.begin(); + it != this->Tests.end(); ++it) + { + //DFS from each element to itself + int root = it->first; + std::set<int> visited; + std::stack<int> s; + s.push(root); + while(!s.empty()) + { + int test = s.top(); + s.pop(); + if(visited.insert(test).second) + { + for(TestSet::iterator d = this->Tests[test].begin(); + d != this->Tests[test].end(); ++d) + { + if(*d == root) + { + //cycle exists + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Error: a cycle exists in the test dependency graph " + "for the test \"" << this->Properties[root]->Name << + "\".\nPlease fix the cycle and run ctest again.\n"); + return false; + } + else + { + s.push(*d); + } + } + } + } + } + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Checking test dependency graph end" << std::endl, this->Quiet); + return true; +} |