diff options
Diffstat (limited to 'Source/CTest/cmCTestMultiProcessHandler.cxx')
-rw-r--r-- | Source/CTest/cmCTestMultiProcessHandler.cxx | 836 |
1 files changed, 836 insertions, 0 deletions
diff --git a/Source/CTest/cmCTestMultiProcessHandler.cxx b/Source/CTest/cmCTestMultiProcessHandler.cxx new file mode 100644 index 0000000..ae97d32 --- /dev/null +++ b/Source/CTest/cmCTestMultiProcessHandler.cxx @@ -0,0 +1,836 @@ +/*============================================================================ + 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 "cmCTest.h" +#include "cmCTestScriptHandler.h" +#include "cmProcess.h" +#include "cmStandardIncludes.h" +#include "cmSystemTools.h" +#include <cmsys/FStream.hxx> +#include <cmsys/SystemInformation.hxx> +#include <float.h> +#include <list> +#include <math.h> +#include <stack> +#include <stdlib.h> + +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); + } + if (this->Properties[index]->RunSerial) { + this->SerialTestRunning = false; + } +} + +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 { + /* clang-format off */ + cmCTestLog(this->CTest, HANDLER_OUTPUT, + "System Load: " << systemLoad << ", " + "Max Allowed Load: " << this->TestLoad << ", " + "Smallest test " << testWithMinProcessors << + " requires " << minProcessorsRequired); + /* clang-format on */ + } + 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); + 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 << "\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 const& 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& currentSet = 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 << 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; +} |