Browse Source

Merge topic 'ctest-cleanup'

5d2e93f9e8 cmCTestMultiProcessHandler: Simplify logic on unavailable resources
a4b061a035 cmCTestMultiProcessHandler: Clarify resource availability error member names
1487e540aa cmCTestMultiProcessHandler: Reduce repeat test property map lookups
b02b628ad9 cmCTestMultiProcessHandler: Simplify loop termination on serial test
8f1e8af0cc cmCTestMultiProcessHandler: Stop searching for tests when limit is reached
bd0b4ca867 cmCTestMultiProcessHandler: Invert spare load condition
9b548139fd cmCTestMultiProcessHandler: Clarify search for tests <= concurrency limit
ee321dc85f cmCTestMultiProcessHandler: Clarify search for tests <= spare load
...

Acked-by: Kitware Robot <[email protected]>
Acked-by: buildbot <[email protected]>
Merge-request: !8981
Brad King 1 year ago
parent
commit
6818925b9a

+ 111 - 109
Source/CTest/cmCTestMultiProcessHandler.cxx

@@ -82,17 +82,12 @@ cmCTestMultiProcessHandler::cmCTestMultiProcessHandler()
 cmCTestMultiProcessHandler::~cmCTestMultiProcessHandler() = default;
 
 // Set the tests
-void cmCTestMultiProcessHandler::SetTests(TestMap& tests,
-                                          PropertiesMap& properties)
+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 (auto const& t : this->Tests) {
-    this->TestRunningMap[t.first] = false;
-    this->TestFinishMap[t.first] = false;
-  }
+  this->PendingTests = std::move(tests);
+  this->Properties = std::move(properties);
+  this->Total = this->PendingTests.size();
   if (!this->CTest->GetShowOnly()) {
     this->ReadCostData();
     this->HasCycles = !this->CheckCycles();
@@ -125,6 +120,11 @@ void cmCTestMultiProcessHandler::SetTestLoad(unsigned long load)
   }
 }
 
+bool cmCTestMultiProcessHandler::Complete()
+{
+  return this->Completed == this->Total;
+}
+
 void cmCTestMultiProcessHandler::RunTests()
 {
   this->CheckResume();
@@ -133,14 +133,14 @@ void cmCTestMultiProcessHandler::RunTests()
   }
   this->TestHandler->SetMaxIndex(this->FindMaxIndex());
 
-  uv_loop_init(&this->Loop);
+  this->Loop.init();
   this->StartNextTests();
-  uv_run(&this->Loop, UV_RUN_DEFAULT);
-  uv_loop_close(&this->Loop);
+  uv_run(this->Loop, UV_RUN_DEFAULT);
+  this->Loop.reset();
 
   if (!this->StopTimePassed && !this->CheckStopOnFailure()) {
-    assert(this->Completed == this->Total);
-    assert(this->Tests.empty());
+    assert(this->Complete());
+    assert(this->PendingTests.empty());
   }
   assert(this->AllResourcesAvailable());
 
@@ -152,9 +152,7 @@ bool cmCTestMultiProcessHandler::StartTestProcess(int test)
 {
   if (this->HaveAffinity && this->Properties[test]->WantAffinity) {
     size_t needProcessors = this->GetProcessorsUsed(test);
-    if (needProcessors > this->ProcessorsAvailable.size()) {
-      return false;
-    }
+    assert(needProcessors <= this->ProcessorsAvailable.size());
     std::vector<size_t> affinity;
     affinity.reserve(needProcessors);
     for (size_t i = 0; i < needProcessors; ++i) {
@@ -167,19 +165,16 @@ bool 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->ErasePendingTest(test);
   this->RunningCount += this->GetProcessorsUsed(test);
 
-  auto testRun = cm::make_unique<cmCTestRunTest>(*this);
+  auto testRun = cm::make_unique<cmCTestRunTest>(*this, test);
 
   if (this->RepeatMode != cmCTest::Repeat::Never) {
     testRun->SetRepeatMode(this->RepeatMode);
     testRun->SetNumberOfRuns(this->RepeatCount);
   }
-  testRun->SetIndex(test);
-  testRun->SetTestProperties(this->Properties[test]);
   if (this->UseResourceSpec) {
     testRun->SetUseAllocatedResources(true);
     testRun->SetAllocatedResources(this->AllocatedResources[test]);
@@ -197,18 +192,18 @@ bool cmCTestMultiProcessHandler::StartTestProcess(int test)
   // working directory because FinishTestProcess() will try to unlock them
   this->LockResources(test);
 
-  if (!this->ResourceAllocationErrors[test].empty()) {
+  if (!this->ResourceAvailabilityErrors[test].empty()) {
     std::ostringstream e;
     e << "Insufficient resources for test " << this->Properties[test]->Name
       << ":\n\n";
-    for (auto const& it : this->ResourceAllocationErrors[test]) {
+    for (auto const& it : this->ResourceAvailabilityErrors[test]) {
       switch (it.second) {
-        case ResourceAllocationError::NoResourceType:
+        case ResourceAvailabilityError::NoResourceType:
           e << "  Test requested resources of type '" << it.first
             << "' which does not exist\n";
           break;
 
-        case ResourceAllocationError::InsufficientResources:
+        case ResourceAvailabilityError::InsufficientResources:
           e << "  Test requested resources of type '" << it.first
             << "' in the following amounts:\n";
           for (auto const& group : this->Properties[test]->ResourceGroups) {
@@ -257,6 +252,12 @@ bool cmCTestMultiProcessHandler::AllocateResources(int index)
     return true;
   }
 
+  // If the test needs unavailable resources then do not allocate anything
+  // because it will never run.  We will issue the recorded errors instead.
+  if (!this->ResourceAvailabilityErrors[index].empty()) {
+    return true;
+  }
+
   std::map<std::string, std::vector<cmCTestBinPackerAllocation>> allocations;
   if (!this->TryAllocateResources(index, allocations)) {
     return false;
@@ -281,7 +282,7 @@ bool cmCTestMultiProcessHandler::AllocateResources(int index)
 bool cmCTestMultiProcessHandler::TryAllocateResources(
   int index,
   std::map<std::string, std::vector<cmCTestBinPackerAllocation>>& allocations,
-  std::map<std::string, ResourceAllocationError>* errors)
+  std::map<std::string, ResourceAvailabilityError>* errors)
 {
   allocations.clear();
 
@@ -301,7 +302,7 @@ bool cmCTestMultiProcessHandler::TryAllocateResources(
   for (auto& it : allocations) {
     if (!availableResources.count(it.first)) {
       if (errors) {
-        (*errors)[it.first] = ResourceAllocationError::NoResourceType;
+        (*errors)[it.first] = ResourceAvailabilityError::NoResourceType;
         result = false;
       } else {
         return false;
@@ -309,7 +310,7 @@ bool cmCTestMultiProcessHandler::TryAllocateResources(
     } else if (!cmAllocateCTestResourcesRoundRobin(
                  availableResources.at(it.first), it.second)) {
       if (errors) {
-        (*errors)[it.first] = ResourceAllocationError::InsufficientResources;
+        (*errors)[it.first] = ResourceAvailabilityError::InsufficientResources;
         result = false;
       } else {
         return false;
@@ -356,14 +357,14 @@ bool cmCTestMultiProcessHandler::AllResourcesAvailable()
   return true;
 }
 
-void cmCTestMultiProcessHandler::CheckResourcesAvailable()
+void cmCTestMultiProcessHandler::CheckResourceAvailability()
 {
   if (this->UseResourceSpec) {
-    for (auto test : this->SortedTests) {
+    for (auto const& t : this->PendingTests) {
       std::map<std::string, std::vector<cmCTestBinPackerAllocation>>
         allocations;
-      this->TryAllocateResources(test, allocations,
-                                 &this->ResourceAllocationErrors[test]);
+      this->TryAllocateResources(t.first, allocations,
+                                 &this->ResourceAvailabilityErrors[t.first]);
     }
   }
 }
@@ -399,30 +400,30 @@ void cmCTestMultiProcessHandler::SetStopTimePassed()
 
 void cmCTestMultiProcessHandler::LockResources(int index)
 {
-  this->LockedResources.insert(
-    this->Properties[index]->LockedResources.begin(),
-    this->Properties[index]->LockedResources.end());
-
-  if (this->Properties[index]->RunSerial) {
+  auto* properties = this->Properties[index];
+  this->LockedResources.insert(properties->LockedResources.begin(),
+                               properties->LockedResources.end());
+  if (properties->RunSerial) {
     this->SerialTestRunning = true;
   }
 }
 
 void cmCTestMultiProcessHandler::UnlockResources(int index)
 {
-  for (std::string const& i : this->Properties[index]->LockedResources) {
+  auto* properties = this->Properties[index];
+  for (std::string const& i : properties->LockedResources) {
     this->LockedResources.erase(i);
   }
-  if (this->Properties[index]->RunSerial) {
+  if (properties->RunSerial) {
     this->SerialTestRunning = false;
   }
 }
 
-void cmCTestMultiProcessHandler::EraseTest(int test)
+void cmCTestMultiProcessHandler::ErasePendingTest(int test)
 {
-  this->Tests.erase(test);
-  this->SortedTests.erase(
-    std::find(this->SortedTests.begin(), this->SortedTests.end(), test));
+  this->PendingTests.erase(test);
+  this->OrderedTests.erase(
+    std::find(this->OrderedTests.begin(), this->OrderedTests.end(), test));
 }
 
 inline size_t cmCTestMultiProcessHandler::GetProcessorsUsed(int test)
@@ -455,15 +456,12 @@ bool cmCTestMultiProcessHandler::StartTest(int test)
     }
   }
 
-  // Allocate resources
-  if (this->ResourceAllocationErrors[test].empty() &&
-      !this->AllocateResources(test)) {
-    this->DeallocateResources(test);
+  if (!this->AllocateResources(test)) {
     return false;
   }
 
   // if there are no depends left then run this test
-  if (this->Tests[test].empty()) {
+  if (this->PendingTests[test].Depends.empty()) {
     return this->StartTestProcess(test);
   }
   // This test was not able to start because it is waiting
@@ -480,7 +478,7 @@ void cmCTestMultiProcessHandler::StartNextTests()
     uv_timer_stop(this->TestLoadRetryTimer);
   }
 
-  if (this->Tests.empty()) {
+  if (this->PendingTests.empty()) {
     this->TestLoadRetryTimer.reset();
     return;
   }
@@ -541,12 +539,14 @@ void cmCTestMultiProcessHandler::StartNextTests()
     }
   }
 
-  TestList copy = this->SortedTests;
-  for (auto const& test : copy) {
-    // Take a nap if we're currently performing a RUN_SERIAL test.
-    if (this->SerialTestRunning) {
-      break;
-    }
+  // Start tests in the preferred order, each subject to readiness checks.
+  auto ti = this->OrderedTests.begin();
+  while (numToStart > 0 && !this->SerialTestRunning &&
+         ti != this->OrderedTests.end()) {
+    // Increment the test iterator now because the current list
+    // entry may be deleted below.
+    int test = *ti++;
+
     // We can only start a RUN_SERIAL test if no other tests are also
     // running.
     if (this->Properties[test]->RunSerial && this->RunningCount > 0) {
@@ -554,28 +554,32 @@ void cmCTestMultiProcessHandler::StartNextTests()
     }
 
     size_t processors = this->GetProcessorsUsed(test);
-    bool testLoadOk = true;
     if (this->TestLoad > 0) {
-      if (processors <= spareLoad) {
-        cmCTestLog(this->CTest, DEBUG,
-                   "OK to run " << this->GetName(test) << ", it requires "
-                                << processors << " procs & system load is: "
-                                << systemLoad << std::endl);
-        allTestsFailedTestLoadCheck = false;
-      } else {
-        testLoadOk = false;
+      // Exclude tests that are too big to fit in the spare load.
+      if (processors > spareLoad) {
+        // Keep track of the smallest excluded test to report in message below.
+        if (processors <= minProcessorsRequired) {
+          minProcessorsRequired = processors;
+          testWithMinProcessors = this->GetName(test);
+        }
+        continue;
       }
+
+      // We found a test that fits in the spare load.
+      allTestsFailedTestLoadCheck = false;
+      cmCTestLog(this->CTest, DEBUG,
+                 "OK to run "
+                   << this->GetName(test) << ", it requires " << processors
+                   << " procs & system load is: " << systemLoad << std::endl);
     }
 
-    if (processors <= minProcessorsRequired) {
-      minProcessorsRequired = processors;
-      testWithMinProcessors = this->GetName(test);
+    // Exclude tests that are too big to fit in the concurrency limit.
+    if (processors > numToStart) {
+      continue;
     }
 
-    if (testLoadOk && processors <= numToStart && this->StartTest(test)) {
+    if (this->StartTest(test)) {
       numToStart -= processors;
-    } else if (numToStart == 0) {
-      break;
     }
   }
 
@@ -583,8 +587,8 @@ void cmCTestMultiProcessHandler::StartNextTests()
     // Find out whether there are any non RUN_SERIAL tests left, so that the
     // correct warning may be displayed.
     bool onlyRunSerialTestsLeft = true;
-    for (auto const& test : copy) {
-      if (!this->Properties[test]->RunSerial) {
+    for (auto const& t : this->PendingTests) {
+      if (!this->Properties[t.first]->RunSerial) {
         onlyRunSerialTestsLeft = false;
       }
     }
@@ -596,7 +600,7 @@ void cmCTestMultiProcessHandler::StartNextTests()
     } else if (onlyRunSerialTestsLeft) {
       cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                  "Only RUN_SERIAL tests remain, awaiting available slot.");
-    } else {
+    } else if (!testWithMinProcessors.empty()) {
       /* clang-format off */
       cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                  "System Load: " << systemLoad << ", "
@@ -604,6 +608,12 @@ void cmCTestMultiProcessHandler::StartNextTests()
                  "Smallest test " << testWithMinProcessors <<
                  " requires " << minProcessorsRequired);
       /* clang-format on */
+    } else {
+      /* clang-format off */
+      cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+                 "System Load: " << systemLoad << ", "
+                 "Max Allowed Load: " << this->TestLoad);
+      /* clang-format on */
     }
     cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "*****" << std::endl);
 
@@ -613,7 +623,7 @@ void cmCTestMultiProcessHandler::StartNextTests()
       milliseconds = 10;
     }
     if (this->TestLoadRetryTimer.get() == nullptr) {
-      this->TestLoadRetryTimer.init(this->Loop, this);
+      this->TestLoadRetryTimer.init(*this->Loop, this);
     }
     this->TestLoadRetryTimer.start(
       &cmCTestMultiProcessHandler::OnTestLoadRetryCB, milliseconds, 0);
@@ -653,12 +663,10 @@ void cmCTestMultiProcessHandler::FinishTestProcess(
     this->Failed->push_back(properties->Name);
   }
 
-  for (auto& t : this->Tests) {
-    t.second.erase(test);
+  for (auto& t : this->PendingTests) {
+    t.second.Depends.erase(test);
   }
 
-  this->TestFinishMap[test] = true;
-  this->TestRunningMap[test] = false;
   this->WriteCheckpoint(test);
   this->DeallocateResources(test);
   this->UnlockResources(test);
@@ -803,7 +811,7 @@ void cmCTestMultiProcessHandler::CreateTestCostList()
 
 void cmCTestMultiProcessHandler::CreateParallelTestCostList()
 {
-  TestSet alreadySortedTests;
+  TestSet alreadyOrderedTests;
 
   std::list<TestSet> priorityStack;
   priorityStack.emplace_back();
@@ -811,11 +819,11 @@ void cmCTestMultiProcessHandler::CreateParallelTestCostList()
 
   // In parallel test runs add previously failed tests to the front
   // of the cost list and queue other tests for further sorting
-  for (auto const& t : this->Tests) {
+  for (auto const& t : this->PendingTests) {
     if (cm::contains(this->LastTestsFailed, this->Properties[t.first]->Name)) {
       // If the test failed last time, it should be run first.
-      this->SortedTests.push_back(t.first);
-      alreadySortedTests.insert(t.first);
+      this->OrderedTests.push_back(t.first);
+      alreadyOrderedTests.insert(t.first);
     } else {
       topLevel.insert(t.first);
     }
@@ -830,7 +838,7 @@ void cmCTestMultiProcessHandler::CreateParallelTestCostList()
     TestSet& currentSet = priorityStack.back();
 
     for (auto const& i : previousSet) {
-      TestSet const& dependencies = this->Tests[i];
+      TestSet const& dependencies = this->PendingTests[i].Depends;
       currentSet.insert(dependencies.begin(), dependencies.end());
     }
 
@@ -851,9 +859,9 @@ void cmCTestMultiProcessHandler::CreateParallelTestCostList()
                      TestComparator(this));
 
     for (auto const& j : sortedCopy) {
-      if (!cm::contains(alreadySortedTests, j)) {
-        this->SortedTests.push_back(j);
-        alreadySortedTests.insert(j);
+      if (!cm::contains(alreadyOrderedTests, j)) {
+        this->OrderedTests.push_back(j);
+        alreadyOrderedTests.insert(j);
       }
     }
   }
@@ -862,7 +870,7 @@ void cmCTestMultiProcessHandler::CreateParallelTestCostList()
 void cmCTestMultiProcessHandler::GetAllTestDependencies(int test,
                                                         TestList& dependencies)
 {
-  TestSet const& dependencySet = this->Tests[test];
+  TestSet const& dependencySet = this->PendingTests[test].Depends;
   for (int i : dependencySet) {
     this->GetAllTestDependencies(i, dependencies);
     dependencies.push_back(i);
@@ -873,17 +881,17 @@ void cmCTestMultiProcessHandler::CreateSerialTestCostList()
 {
   TestList presortedList;
 
-  for (auto const& i : this->Tests) {
+  for (auto const& i : this->PendingTests) {
     presortedList.push_back(i.first);
   }
 
   std::stable_sort(presortedList.begin(), presortedList.end(),
                    TestComparator(this));
 
-  TestSet alreadySortedTests;
+  TestSet alreadyOrderedTests;
 
   for (int test : presortedList) {
-    if (cm::contains(alreadySortedTests, test)) {
+    if (cm::contains(alreadyOrderedTests, test)) {
       continue;
     }
 
@@ -891,14 +899,14 @@ void cmCTestMultiProcessHandler::CreateSerialTestCostList()
     this->GetAllTestDependencies(test, dependencies);
 
     for (int testDependency : dependencies) {
-      if (!cm::contains(alreadySortedTests, testDependency)) {
-        alreadySortedTests.insert(testDependency);
-        this->SortedTests.push_back(testDependency);
+      if (!cm::contains(alreadyOrderedTests, testDependency)) {
+        alreadyOrderedTests.insert(testDependency);
+        this->OrderedTests.push_back(testDependency);
       }
     }
 
-    alreadySortedTests.insert(test);
-    this->SortedTests.push_back(test);
+    alreadyOrderedTests.insert(test);
+    this->OrderedTests.push_back(test);
   }
 }
 
@@ -1255,9 +1263,7 @@ void cmCTestMultiProcessHandler::PrintOutputAsJson()
     // Don't worry if this fails, we are only showing the test list, not
     // running the tests
     cmWorkingDirectory workdir(p.Directory);
-    cmCTestRunTest testRun(*this);
-    testRun.SetIndex(p.Index);
-    testRun.SetTestProperties(&p);
+    cmCTestRunTest testRun(*this, p.Index);
     testRun.ComputeArguments();
 
     // Skip tests not available in this configuration.
@@ -1294,9 +1300,7 @@ void cmCTestMultiProcessHandler::PrintTestList()
     // running the tests
     cmWorkingDirectory workdir(p.Directory);
 
-    cmCTestRunTest testRun(*this);
-    testRun.SetIndex(p.Index);
-    testRun.SetTestProperties(&p);
+    cmCTestRunTest testRun(*this, p.Index);
     testRun.ComputeArguments(); // logs the command in verbose mode
 
     if (!p.Labels.empty()) // print the labels
@@ -1390,17 +1394,15 @@ void cmCTestMultiProcessHandler::CheckResume()
 
 void cmCTestMultiProcessHandler::RemoveTest(int index)
 {
-  this->EraseTest(index);
+  this->ErasePendingTest(index);
   this->Properties.erase(index);
-  this->TestRunningMap[index] = false;
-  this->TestFinishMap[index] = true;
   this->Completed++;
 }
 
 int cmCTestMultiProcessHandler::FindMaxIndex()
 {
   int max = 0;
-  for (auto const& i : this->Tests) {
+  for (auto const& i : this->PendingTests) {
     if (i.first > max) {
       max = i.first;
     }
@@ -1414,7 +1416,7 @@ bool cmCTestMultiProcessHandler::CheckCycles()
   cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                      "Checking test dependency graph..." << std::endl,
                      this->Quiet);
-  for (auto const& it : this->Tests) {
+  for (auto const& it : this->PendingTests) {
     // DFS from each element to itself
     int root = it.first;
     std::set<int> visited;
@@ -1424,7 +1426,7 @@ bool cmCTestMultiProcessHandler::CheckCycles()
       int test = s.top();
       s.pop();
       if (visited.insert(test).second) {
-        for (auto const& d : this->Tests[test]) {
+        for (auto const& d : this->PendingTests[test].Depends) {
           if (d == root) {
             // cycle exists
             cmCTestLog(

+ 19 - 14
Source/CTest/cmCTestMultiProcessHandler.h

@@ -5,6 +5,7 @@
 #include "cmConfigure.h" // IWYU pragma: keep
 
 #include <cstddef>
+#include <list>
 #include <map>
 #include <memory>
 #include <set>
@@ -38,7 +39,11 @@ public:
   struct TestSet : public std::set<int>
   {
   };
-  struct TestMap : public std::map<int, TestSet>
+  struct TestInfo
+  {
+    TestSet Depends;
+  };
+  struct TestMap : public std::map<int, TestInfo>
   {
   };
   struct TestList : public std::vector<int>
@@ -57,7 +62,7 @@ public:
   cmCTestMultiProcessHandler();
   virtual ~cmCTestMultiProcessHandler();
   // Set the tests
-  void SetTests(TestMap& tests, PropertiesMap& properties);
+  void SetTests(TestMap tests, PropertiesMap properties);
   // Set the max number of tests that can be run at the same time.
   void SetParallelLevel(size_t);
   void SetTestLoad(unsigned long load);
@@ -99,7 +104,7 @@ public:
 
   void SetQuiet(bool b) { this->Quiet = b; }
 
-  void CheckResourcesAvailable();
+  void CheckResourceAvailability();
 
 protected:
   // Start the next test or tests as many as are allowed by
@@ -124,7 +129,7 @@ protected:
 
   // Removes the checkpoint file
   void MarkFinished();
-  void EraseTest(int index);
+  void ErasePendingTest(int index);
   void FinishTestProcess(std::unique_ptr<cmCTestRunTest> runner, bool started);
 
   static void OnTestLoadRetryCB(uv_timer_t* timer);
@@ -146,18 +151,19 @@ protected:
   void LockResources(int index);
   void UnlockResources(int index);
 
-  enum class ResourceAllocationError
+  enum class ResourceAvailabilityError
   {
     NoResourceType,
     InsufficientResources,
   };
 
+  bool Complete();
   bool AllocateResources(int index);
   bool TryAllocateResources(
     int index,
     std::map<std::string, std::vector<cmCTestBinPackerAllocation>>&
       allocations,
-    std::map<std::string, ResourceAllocationError>* errors = nullptr);
+    std::map<std::string, ResourceAvailabilityError>* errors = nullptr);
   void DeallocateResources(int index);
   bool AllResourcesAvailable();
   bool InitResourceAllocator(std::string& error);
@@ -170,9 +176,10 @@ protected:
   cm::optional<std::size_t> ResourceSpecSetupTest;
   bool HasInvalidGeneratedResourceSpec;
 
-  // map from test number to set of depend tests
-  TestMap Tests;
-  TestList SortedTests;
+  // Tests pending selection to start.  They may have dependencies.
+  TestMap PendingTests;
+  // List of pending test indexes, ordered by cost.
+  std::list<int> OrderedTests;
   // Total number of tests we'll be running
   size_t Total;
   // Number of tests that are complete
@@ -183,8 +190,6 @@ protected:
   bool StopTimePassed = false;
   // list of test properties (indices concurrent to the test map)
   PropertiesMap Properties;
-  std::map<int, bool> TestRunningMap;
-  std::map<int, bool> TestFinishMap;
   std::map<int, std::string> TestOutput;
   std::vector<std::string>* Passed;
   std::vector<std::string>* Failed;
@@ -193,14 +198,14 @@ protected:
   std::map<int,
            std::vector<std::map<std::string, std::vector<ResourceAllocation>>>>
     AllocatedResources;
-  std::map<int, std::map<std::string, ResourceAllocationError>>
-    ResourceAllocationErrors;
+  std::map<int, std::map<std::string, ResourceAvailabilityError>>
+    ResourceAvailabilityErrors;
   cmCTestResourceAllocator ResourceAllocator;
   std::vector<cmCTestTestHandler::cmCTestTestResult>* TestResults;
   size_t ParallelLevel; // max number of process that can be run at once
   unsigned long TestLoad;
   unsigned long FakeLoadForTesting;
-  uv_loop_t Loop;
+  cm::uv_loop_ptr Loop;
   cm::uv_timer_ptr TestLoadRetryTimer;
   cmCTestTestHandler* TestHandler;
   cmCTest* CTest;

+ 9 - 5
Source/CTest/cmCTestRunTest.cxx

@@ -25,13 +25,17 @@
 #include "cmProcess.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
+#include "cmUVHandlePtr.h"
 #include "cmWorkingDirectory.h"
 
-cmCTestRunTest::cmCTestRunTest(cmCTestMultiProcessHandler& multiHandler)
+cmCTestRunTest::cmCTestRunTest(cmCTestMultiProcessHandler& multiHandler,
+                               int index)
   : MultiTestHandler(multiHandler)
+  , Index(index)
+  , CTest(MultiTestHandler.CTest)
+  , TestHandler(MultiTestHandler.TestHandler)
+  , TestProperties(MultiTestHandler.Properties[Index])
 {
-  this->CTest = multiHandler.CTest;
-  this->TestHandler = multiHandler.TestHandler;
 }
 
 void cmCTestRunTest::CheckOutput(std::string const& line)
@@ -161,7 +165,7 @@ cmCTestRunTest::EndTestResult cmCTestRunTest::EndTest(size_t completed,
       reason = "Invalid resource spec file";
       forceFail = true;
     } else {
-      this->MultiTestHandler.CheckResourcesAvailable();
+      this->MultiTestHandler.CheckResourceAvailability();
     }
   }
   std::ostringstream outputStream;
@@ -887,7 +891,7 @@ bool cmCTestRunTest::ForkProcess()
   this->TestResult.Environment.erase(this->TestResult.Environment.length() -
                                      1);
 
-  return this->TestProcess->StartProcess(this->MultiTestHandler.Loop,
+  return this->TestProcess->StartProcess(*this->MultiTestHandler.Loop,
                                          &this->TestProperties->Affinity);
 }
 

+ 6 - 13
Source/CTest/cmCTestRunTest.h

@@ -24,7 +24,7 @@
 class cmCTestRunTest
 {
 public:
-  explicit cmCTestRunTest(cmCTestMultiProcessHandler& multiHandler);
+  explicit cmCTestRunTest(cmCTestMultiProcessHandler& multiHandler, int index);
 
   void SetNumberOfRuns(int n)
   {
@@ -33,18 +33,12 @@ public:
   }
 
   void SetRepeatMode(cmCTest::Repeat r) { this->RepeatMode = r; }
-  void SetTestProperties(cmCTestTestHandler::cmCTestTestProperties* prop)
-  {
-    this->TestProperties = prop;
-  }
 
   cmCTestTestHandler::cmCTestTestProperties* GetTestProperties()
   {
     return this->TestProperties;
   }
 
-  void SetIndex(int i) { this->Index = i; }
-
   int GetIndex() { return this->Index; }
 
   void AddFailedDependency(const std::string& failedTest)
@@ -124,16 +118,15 @@ private:
   // Returns "completed/total Test #Index: "
   std::string GetTestPrefix(size_t completed, size_t total) const;
 
-  cmCTestTestHandler::cmCTestTestProperties* TestProperties;
-  // Pointer back to the "parent"; the handler that invoked this test run
-  cmCTestTestHandler* TestHandler;
+  cmCTestMultiProcessHandler& MultiTestHandler;
+  int Index;
   cmCTest* CTest;
+  cmCTestTestHandler* TestHandler;
+  cmCTestTestHandler::cmCTestTestProperties* TestProperties;
+
   std::unique_ptr<cmProcess> TestProcess;
   std::string ProcessOutput;
-  // The test results
   cmCTestTestHandler::cmCTestTestResult TestResult;
-  cmCTestMultiProcessHandler& MultiTestHandler;
-  int Index;
   std::set<std::string> FailedDependencies;
   std::string StartTime;
   std::string ActualCommand;

+ 3 - 3
Source/CTest/cmCTestTestHandler.cxx

@@ -1381,15 +1381,15 @@ bool cmCTestTestHandler::ProcessDirectory(std::vector<std::string>& passed,
         }
       }
     }
-    tests[p.Index] = depends;
+    tests[p.Index].Depends = depends;
     properties[p.Index] = &p;
   }
   parallel->SetResourceSpecFile(this->ResourceSpecFile);
-  parallel->SetTests(tests, properties);
+  parallel->SetTests(std::move(tests), std::move(properties));
   parallel->SetPassFailVectors(&passed, &failed);
   this->TestResults.clear();
   parallel->SetTestResults(&this->TestResults);
-  parallel->CheckResourcesAvailable();
+  parallel->CheckResourceAvailability();
 
   if (this->CTest->ShouldPrintLabels()) {
     parallel->PrintLabels();

+ 7 - 4
Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake

@@ -229,19 +229,22 @@ function(run_TestLoad name load)
   file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
   file(WRITE "${RunCMake_TEST_BINARY_DIR}/CTestTestfile.cmake" "
   add_test(TestLoad1 \"${CMAKE_COMMAND}\" -E echo \"test of --test-load\")
+  set_tests_properties(TestLoad1 PROPERTIES PROCESSORS 2)
   add_test(TestLoad2 \"${CMAKE_COMMAND}\" -E echo \"test of --test-load\")
+  set_tests_properties(TestLoad2 PROPERTIES PROCESSORS 2)
 ")
-  run_cmake_command(${name} ${CMAKE_CTEST_COMMAND} -VV -j2 --test-load ${load})
+  run_cmake_command(${name} ${CMAKE_CTEST_COMMAND} -VV -j8 --test-load ${load})
 endfunction()
 
 # Tests for the --test-load feature of ctest
 #
 # Spoof a load average value to make these tests more reliable.
-set(ENV{__CTEST_FAKE_LOAD_AVERAGE_FOR_TESTING} 5)
+set(ENV{__CTEST_FAKE_LOAD_AVERAGE_FOR_TESTING} 7)
 
 # Verify that new tests are not started when the load average exceeds
 # our threshold and that they then run once the load average drops.
-run_TestLoad(test-load-wait 3)
+run_TestLoad(test-load-wait0 5)
+run_TestLoad(test-load-wait1 8)
 
 # Verify that warning message is displayed but tests still start when
 # an invalid argument is given.
@@ -249,7 +252,7 @@ run_TestLoad(test-load-invalid 'two')
 
 # Verify that new tests are started when the load average falls below
 # our threshold.
-run_TestLoad(test-load-pass 10)
+run_TestLoad(test-load-pass 12)
 
 unset(ENV{__CTEST_FAKE_LOAD_AVERAGE_FOR_TESTING})
 

+ 1 - 1
Tests/RunCMake/CTestCommandLine/test-load-wait-stdout.txt → Tests/RunCMake/CTestCommandLine/test-load-wait0-stdout.txt

@@ -2,7 +2,7 @@ Test project [^
 ]*/Tests/RunCMake/CTestCommandLine/TestLoad(
 [^*][^
 ]*)*
-\*\*\*\*\* WAITING, System Load: 5, Max Allowed Load: 3, Smallest test TestLoad[1-2] requires 1\*\*\*\*\*
+\*\*\*\*\* WAITING, System Load: 7, Max Allowed Load: 5\*\*\*\*\*
 test 1
     Start 1: TestLoad1
 +(

+ 21 - 0
Tests/RunCMake/CTestCommandLine/test-load-wait1-stdout.txt

@@ -0,0 +1,21 @@
+Test project [^
+]*/Tests/RunCMake/CTestCommandLine/TestLoad(
+[^*][^
+]*)*
+\*\*\*\*\* WAITING, System Load: 7, Max Allowed Load: 8, Smallest test TestLoad[1-2] requires 2\*\*\*\*\*
+test 1
+    Start 1: TestLoad1
++(
+[^*][^
+]*)*
+test 2
+    Start 2: TestLoad2
++(
+[^*][^
+]*)*
+1/2 Test #[1-2]: TestLoad[1-2] ........................   Passed +[0-9.]+ sec(
+[^*][^
+]*)*
+2/2 Test #[1-2]: TestLoad[1-2] ........................   Passed +[0-9.]+ sec
++
+100% tests passed, 0 tests failed out of 2

+ 2 - 2
Tests/RunCMake/ctest_test/TestLoadWait-stdout.txt → Tests/RunCMake/ctest_test/CTestTestLoadWait0-stdout.txt

@@ -1,8 +1,8 @@
 Test project [^
-]*/Tests/RunCMake/ctest_test/TestLoadWait-build(
+]*/Tests/RunCMake/ctest_test/CTestTestLoadWait0-build(
 [^*][^
 ]*)*
-\*\*\*\*\* WAITING, System Load: 5, Max Allowed Load: 2, Smallest test RunCMakeVersion requires 1\*\*\*\*\*
+\*\*\*\*\* WAITING, System Load: 7, Max Allowed Load: 6\*\*\*\*\*
 test 1
     Start 1: RunCMakeVersion
 +(

+ 15 - 0
Tests/RunCMake/ctest_test/CTestTestLoadWait1-stdout.txt

@@ -0,0 +1,15 @@
+Test project [^
+]*/Tests/RunCMake/ctest_test/CTestTestLoadWait1-build(
+[^*][^
+]*)*
+\*\*\*\*\* WAITING, System Load: 7, Max Allowed Load: 8, Smallest test RunCMakeVersion requires 2\*\*\*\*\*
+test 1
+    Start 1: RunCMakeVersion
++(
+[^*][^
+]*)*
+1/1 Test #1: RunCMakeVersion ..................   Passed +[0-9.]+ sec
++
+100% tests passed, 0 tests failed out of 1
++
+Total Test time \(real\) = +[0-9.]+ sec$

+ 13 - 6
Tests/RunCMake/ctest_test/RunCMakeTest.cmake

@@ -14,16 +14,20 @@ run_ctest_test(TestQuiet QUIET)
 # Tests for the 'Test Load' feature of ctest
 #
 # Spoof a load average value to make these tests more reliable.
-set(ENV{__CTEST_FAKE_LOAD_AVERAGE_FOR_TESTING} 5)
+set(ENV{__CTEST_FAKE_LOAD_AVERAGE_FOR_TESTING} 7)
 set(RunCTest_VERBOSE_FLAG -VV)
+set(CASE_CMAKELISTS_SUFFIX_CODE [[
+set_property(TEST RunCMakeVersion PROPERTY PROCESSORS 2)
+]])
 
 # Verify that new tests are started when the load average falls below
 # our threshold.
-run_ctest_test(TestLoadPass TEST_LOAD 6)
+run_ctest_test(TestLoadPass TEST_LOAD 8)
 
 # Verify that new tests are not started when the load average exceeds
 # our threshold and that they then run once the load average drops.
-run_ctest_test(TestLoadWait TEST_LOAD 2)
+run_ctest_test(TestLoadWait0 TEST_LOAD 4 PARALLEL_LEVEL 8)
+run_ctest_test(TestLoadWait1 TEST_LOAD 8 PARALLEL_LEVEL 8)
 
 # Verify that when an invalid "TEST_LOAD" value is given, a warning
 # message is displayed and the value is ignored.
@@ -31,13 +35,15 @@ run_ctest_test(TestLoadInvalid TEST_LOAD "ERR1")
 
 # Verify that new tests are started when the load average falls below
 # our threshold.
-set(CASE_CTEST_TEST_LOAD 7)
+set(CASE_CTEST_TEST_LOAD 9)
 run_ctest_test(CTestTestLoadPass)
 
 # Verify that new tests are not started when the load average exceeds
 # our threshold and that they then run once the load average drops.
-set(CASE_CTEST_TEST_LOAD 4)
-run_ctest_test(CTestTestLoadWait)
+set(CASE_CTEST_TEST_LOAD 6)
+run_ctest_test(CTestTestLoadWait0 PARALLEL_LEVEL 8)
+set(CASE_CTEST_TEST_LOAD 8)
+run_ctest_test(CTestTestLoadWait1 PARALLEL_LEVEL 8)
 
 # Verify that when an invalid "CTEST_TEST_LOAD" value is given,
 # a warning message is displayed and the value is ignored.
@@ -51,6 +57,7 @@ run_ctest_test(TestLoadOrder TEST_LOAD "ERR4")
 
 unset(ENV{__CTEST_FAKE_LOAD_AVERAGE_FOR_TESTING})
 unset(CASE_CTEST_TEST_LOAD)
+unset(CASE_CMAKELISTS_SUFFIX_CODE)
 unset(RunCTest_VERBOSE_FLAG)
 
 block()

+ 2 - 2
Tests/RunCMake/ctest_test/CTestTestLoadWait-stdout.txt → Tests/RunCMake/ctest_test/TestLoadWait0-stdout.txt

@@ -1,8 +1,8 @@
 Test project [^
-]*/Tests/RunCMake/ctest_test/CTestTestLoadWait-build(
+]*/Tests/RunCMake/ctest_test/TestLoadWait0-build(
 [^*][^
 ]*)*
-\*\*\*\*\* WAITING, System Load: 5, Max Allowed Load: 4, Smallest test RunCMakeVersion requires 1\*\*\*\*\*
+\*\*\*\*\* WAITING, System Load: 7, Max Allowed Load: 4\*\*\*\*\*
 test 1
     Start 1: RunCMakeVersion
 +(

+ 15 - 0
Tests/RunCMake/ctest_test/TestLoadWait1-stdout.txt

@@ -0,0 +1,15 @@
+Test project [^
+]*/Tests/RunCMake/ctest_test/TestLoadWait1-build(
+[^*][^
+]*)*
+\*\*\*\*\* WAITING, System Load: 7, Max Allowed Load: 8, Smallest test RunCMakeVersion requires 2\*\*\*\*\*
+test 1
+    Start 1: RunCMakeVersion
++(
+[^*][^
+]*)*
+1/1 Test #1: RunCMakeVersion ..................   Passed +[0-9.]+ sec
++
+100% tests passed, 0 tests failed out of 1
++
+Total Test time \(real\) = +[0-9.]+ sec$