CMake/Source/CTest/cmCTestMultiProcessHandler.cxx
Brad King 86578eccf2 Simplify CMake per-source license notices
Per-source copyright/license notice headers that spell out copyright holder
names and years are hard to maintain and often out-of-date or plain wrong.
Precise contributor information is already maintained automatically by the
version control tool.  Ultimately it is the receiver of a file who is
responsible for determining its licensing status, and per-source notices are
merely a convenience.  Therefore it is simpler and more accurate for
each source to have a generic notice of the license name and references to
more detailed information on copyright holders and full license terms.

Our `Copyright.txt` file now contains a list of Contributors whose names
appeared source-level copyright notices.  It also references version control
history for more precise information.  Therefore we no longer need to spell
out the list of Contributors in each source file notice.

Replace CMake per-source copyright/license notice headers with a short
description of the license and links to `Copyright.txt` and online information
available from "https://cmake.org/licensing".  The online URL also handles
cases of modules being copied out of our source into other projects, so we
can drop our notices about replacing links with full license text.

Run the `Utilities/Scripts/filter-notices.bash` script to perform the majority
of the replacements mechanically.  Manually fix up shebang lines and trailing
newlines in a few files.  Manually update the notices in a few files that the
script does not handle.
2016-09-27 15:14:44 -04:00

844 lines
25 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmCTestMultiProcessHandler.h"
#include "cmCTest.h"
#include "cmCTestRunTest.h"
#include "cmCTestScriptHandler.h"
#include "cmCTestTestHandler.h"
#include "cmSystemTools.h"
#include <algorithm>
#include <cmsys/FStream.hxx>
#include <cmsys/String.hxx>
#include <cmsys/SystemInformation.hxx>
#include <iomanip>
#include <list>
#include <math.h>
#include <sstream>
#include <stack>
#include <stdlib.h>
#include <utility>
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]);
// Find any failed dependencies for this test. We assume the more common
// scenario has no failed tests, so make it the outer loop.
for (std::vector<std::string>::const_iterator it = this->Failed->begin();
it != this->Failed->end(); ++it) {
if (this->Properties[test]->RequireSuccessDepends.find(*it) !=
this->Properties[test]->RequireSuccessDepends.end()) {
testRun->AddFailedDependency(*it);
}
}
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.
std::string fake_load_value;
if (cmSystemTools::GetEnv("__CTEST_FAKE_LOAD_AVERAGE_FOR_TESTING",
fake_load_value)) {
usedFakeLoadForTesting = true;
if (!cmSystemTools::StringToULong(fake_load_value.c_str(),
&systemLoad)) {
cmSystemTools::Error("Failed to parse fake load value: ",
fake_load_value.c_str());
}
}
// 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;
}
s.push(*d);
}
}
}
}
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
"Checking test dependency graph end" << std::endl,
this->Quiet);
return true;
}