From 230de4e06240f48715685ea184386d7314137d59 Mon Sep 17 00:00:00 2001
From: Kyle Edwards <kyle.edwards@kitware.com>
Date: Thu, 2 Apr 2020 16:16:15 -0400
Subject: [PATCH] CTest: Add CTEST_RESOURCE_SPEC_FILE variable

---
 Help/manual/cmake-variables.7.rst                  |  1 +
 Help/manual/ctest.1.rst                            |  8 +++--
 Help/variable/CTEST_RESOURCE_SPEC_FILE.rst         |  5 +++
 Source/CTest/cmCTestTestCommand.cxx                |  7 ++++
 Source/CTest/cmCTestTestHandler.cxx                | 37 +++++++++++++---------
 Source/CTest/cmCTestTestHandler.h                  |  2 +-
 Source/cmLocalGenerator.cxx                        |  7 ++++
 .../CTestResourceAllocation/RunCMakeTest.cmake     | 28 +++++++++-------
 .../checkfree1-ctest-s-res-cache-check.cmake       |  1 +
 .../checkfree1-ctest-s-res-variable-check.cmake    |  1 +
 .../notenough1-ctest-s-res-cache-check.cmake       |  3 ++
 .../notenough1-ctest-s-res-cache-result.txt        |  1 +
 .../notenough1-ctest-s-res-cache-stderr.txt        | 14 ++++++++
 .../notenough1-ctest-s-res-variable-check.cmake    |  3 ++
 .../notenough1-ctest-s-res-variable-result.txt     |  1 +
 .../notenough1-ctest-s-res-variable-stderr.txt     | 14 ++++++++
 .../RunCMake/CTestResourceAllocation/test.cmake.in | 14 +++++---
 17 files changed, 113 insertions(+), 34 deletions(-)
 create mode 100644 Help/variable/CTEST_RESOURCE_SPEC_FILE.rst
 create mode 100644 Tests/RunCMake/CTestResourceAllocation/checkfree1-ctest-s-res-cache-check.cmake
 create mode 100644 Tests/RunCMake/CTestResourceAllocation/checkfree1-ctest-s-res-variable-check.cmake
 create mode 100644 Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-cache-check.cmake
 create mode 100644 Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-cache-result.txt
 create mode 100644 Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-cache-stderr.txt
 create mode 100644 Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-variable-check.cmake
 create mode 100644 Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-variable-result.txt
 create mode 100644 Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-variable-stderr.txt

diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst
index fc27739..312346d 100644
--- a/Help/manual/cmake-variables.7.rst
+++ b/Help/manual/cmake-variables.7.rst
@@ -621,6 +621,7 @@ Variables for CTest
    /variable/CTEST_P4_COMMAND
    /variable/CTEST_P4_OPTIONS
    /variable/CTEST_P4_UPDATE_OPTIONS
+   /variable/CTEST_RESOURCE_SPEC_FILE
    /variable/CTEST_RUN_CURRENT_SCRIPT
    /variable/CTEST_SCP_COMMAND
    /variable/CTEST_SITE
diff --git a/Help/manual/ctest.1.rst b/Help/manual/ctest.1.rst
index 6503f0e..f2033b7 100644
--- a/Help/manual/ctest.1.rst
+++ b/Help/manual/ctest.1.rst
@@ -994,8 +994,12 @@ Configuration settings include:
 
 ``ResourceSpecFile``
   Specify a
-  :ref:`resource specification file <ctest-resource-specification-file>`. See
-  :ref:`ctest-resource-allocation` for more information.
+  :ref:`resource specification file <ctest-resource-specification-file>`.
+
+  * `CTest Script`_ variable: :variable:`CTEST_RESOURCE_SPEC_FILE`
+  * :module:`CTest` module variable: ``CTEST_RESOURCE_SPEC_FILE``
+
+  See :ref:`ctest-resource-allocation` for more information.
 
 ``LabelsForSubprojects``
   Specify a semicolon-separated list of labels that will be treated as
diff --git a/Help/variable/CTEST_RESOURCE_SPEC_FILE.rst b/Help/variable/CTEST_RESOURCE_SPEC_FILE.rst
new file mode 100644
index 0000000..59f365f
--- /dev/null
+++ b/Help/variable/CTEST_RESOURCE_SPEC_FILE.rst
@@ -0,0 +1,5 @@
+CTEST_RESOURCE_SPEC_FILE
+------------------------
+
+Specify the CTest ``ResourceSpecFile`` setting in a :manual:`ctest(1)`
+dashboard client script.
diff --git a/Source/CTest/cmCTestTestCommand.cxx b/Source/CTest/cmCTestTestCommand.cxx
index 0f9b695..c5f683d 100644
--- a/Source/CTest/cmCTestTestCommand.cxx
+++ b/Source/CTest/cmCTestTestCommand.cxx
@@ -52,6 +52,13 @@ cmCTestGenericHandler* cmCTestTestCommand::InitializeHandler()
     }
   }
   this->CTest->SetTimeOut(timeout);
+
+  const char* resourceSpecFile =
+    this->Makefile->GetDefinition("CTEST_RESOURCE_SPEC_FILE");
+  if (this->ResourceSpecFile.empty() && resourceSpecFile) {
+    this->ResourceSpecFile = resourceSpecFile;
+  }
+
   cmCTestGenericHandler* handler = this->InitializeActualHandler();
   if (!this->Start.empty() || !this->End.empty() || !this->Stride.empty()) {
     handler->SetOption(
diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx
index 4f324ea..25d6710 100644
--- a/Source/CTest/cmCTestTestHandler.cxx
+++ b/Source/CTest/cmCTestTestHandler.cxx
@@ -406,7 +406,9 @@ int cmCTestTestHandler::ProcessHandler()
   // start the real time clock
   auto clock_start = std::chrono::steady_clock::now();
 
-  this->ProcessDirectory(passed, failed);
+  if (!this->ProcessDirectory(passed, failed)) {
+    return -1;
+  }
 
   auto clock_finish = std::chrono::steady_clock::now();
 
@@ -543,22 +545,11 @@ bool cmCTestTestHandler::ProcessOptions()
   if (val) {
     this->ExcludeFixtureCleanupRegExp = val;
   }
-  this->SetRerunFailed(cmIsOn(this->GetOption("RerunFailed")));
-
   val = this->GetOption("ResourceSpecFile");
   if (val) {
-    this->UseResourceSpec = true;
     this->ResourceSpecFile = val;
-    auto result = this->ResourceSpec.ReadFromJSONFile(val);
-    if (result != cmCTestResourceSpec::ReadFileResult::READ_OK) {
-      cmCTestLog(this->CTest, ERROR_MESSAGE,
-                 "Could not read/parse resource spec file "
-                   << val << ": "
-                   << cmCTestResourceSpec::ResultToString(result)
-                   << std::endl);
-      return false;
-    }
   }
+  this->SetRerunFailed(cmIsOn(this->GetOption("RerunFailed")));
 
   return true;
 }
@@ -1259,7 +1250,7 @@ bool cmCTestTestHandler::GetValue(const char* tag, std::string& value,
   return ret;
 }
 
-void cmCTestTestHandler::ProcessDirectory(std::vector<std::string>& passed,
+bool cmCTestTestHandler::ProcessDirectory(std::vector<std::string>& passed,
                                           std::vector<std::string>& failed)
 {
   this->ComputeTestList();
@@ -1283,7 +1274,17 @@ void cmCTestTestHandler::ProcessDirectory(std::vector<std::string>& passed,
   } else {
     parallel->SetTestLoad(this->CTest->GetTestLoad());
   }
-  if (this->UseResourceSpec) {
+  if (!this->ResourceSpecFile.empty()) {
+    this->UseResourceSpec = true;
+    auto result = this->ResourceSpec.ReadFromJSONFile(this->ResourceSpecFile);
+    if (result != cmCTestResourceSpec::ReadFileResult::READ_OK) {
+      cmCTestLog(this->CTest, ERROR_MESSAGE,
+                 "Could not read/parse resource spec file "
+                   << this->ResourceSpecFile << ": "
+                   << cmCTestResourceSpec::ResultToString(result)
+                   << std::endl);
+      return false;
+    }
     parallel->InitResourceAllocator(this->ResourceSpec);
   }
 
@@ -1344,6 +1345,8 @@ void cmCTestTestHandler::ProcessDirectory(std::vector<std::string>& passed,
   this->ElapsedTestingTime =
     std::chrono::steady_clock::now() - elapsed_time_start;
   *this->LogFile << "End testing: " << this->CTest->CurrentTime() << std::endl;
+
+  return true;
 }
 
 void cmCTestTestHandler::GenerateTestCommand(
@@ -1742,6 +1745,10 @@ void cmCTestTestHandler::GetListOfTests()
   if (cmSystemTools::GetErrorOccuredFlag()) {
     return;
   }
+  const char* specFile = mf.GetDefinition("CTEST_RESOURCE_SPEC_FILE");
+  if (this->ResourceSpecFile.empty() && specFile) {
+    this->ResourceSpecFile = specFile;
+  }
   cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                      "Done constructing a list of tests" << std::endl,
                      this->Quiet);
diff --git a/Source/CTest/cmCTestTestHandler.h b/Source/CTest/cmCTestTestHandler.h
index b1c8755..bb41c79 100644
--- a/Source/CTest/cmCTestTestHandler.h
+++ b/Source/CTest/cmCTestTestHandler.h
@@ -278,7 +278,7 @@ private:
   /**
    * Run the tests for a directory and any subdirectories
    */
-  void ProcessDirectory(std::vector<std::string>& passed,
+  bool ProcessDirectory(std::vector<std::string>& passed,
                         std::vector<std::string>& failed);
 
   /**
diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx
index cf6802d..5e78911 100644
--- a/Source/cmLocalGenerator.cxx
+++ b/Source/cmLocalGenerator.cxx
@@ -299,6 +299,13 @@ void cmLocalGenerator::GenerateTestFiles()
        << "# testing this directory and lists subdirectories to "
        << "be tested as well." << std::endl;
 
+  std::string resourceSpecFile =
+    this->Makefile->GetSafeDefinition("CTEST_RESOURCE_SPEC_FILE");
+  if (!resourceSpecFile.empty()) {
+    fout << "set(CTEST_RESOURCE_SPEC_FILE \"" << resourceSpecFile << "\")"
+         << std::endl;
+  }
+
   const char* testIncludeFile =
     this->Makefile->GetProperty("TEST_INCLUDE_FILE");
   if (testIncludeFile) {
diff --git a/Tests/RunCMake/CTestResourceAllocation/RunCMakeTest.cmake b/Tests/RunCMake/CTestResourceAllocation/RunCMakeTest.cmake
index 8584786..777f192 100644
--- a/Tests/RunCMake/CTestResourceAllocation/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CTestResourceAllocation/RunCMakeTest.cmake
@@ -140,9 +140,13 @@ run_ctresalloc_verify(ctresalloc-verify-noend "test1")
 # Now test the resource allocation feature of CTest
 ###############################################################################
 
-function(run_ctest_resource name parallel random)
-  run_ctest("${name}-ctest-s-res" "-DCTEST_RESOURCE_ALLOC_ENABLED=1" "-DCTRESALLOC_COMMAND=${CTRESALLOC_COMMAND}" "-DCTEST_PARALLEL=${parallel}" "-DCTEST_RANDOM=${random}")
-  run_ctest("${name}-ctest-s-nores" "-DCTEST_RESOURCE_ALLOC_ENABLED=0" "-DCTRESALLOC_COMMAND=${CTRESALLOC_COMMAND}" "-DCTEST_PARALLEL=${parallel}" "-DCTEST_RANDOM=${random}")
+function(run_ctest_resource name parallel random extra)
+  run_ctest("${name}-ctest-s-res" "-DCTEST_RESOURCE_ALLOC_ENABLED=1" "-DCTEST_RESOURCE_SPEC_SOURCE=ARG" "-DCTRESALLOC_COMMAND=${CTRESALLOC_COMMAND}" "-DCTEST_PARALLEL=${parallel}" "-DCTEST_RANDOM=${random}")
+  run_ctest("${name}-ctest-s-nores" "-DCTEST_RESOURCE_ALLOC_ENABLED=0" "-DCTEST_RESOURCE_SPEC_SOURCE=NONE" "-DCTRESALLOC_COMMAND=${CTRESALLOC_COMMAND}" "-DCTEST_PARALLEL=${parallel}" "-DCTEST_RANDOM=${random}")
+  if(extra)
+    run_ctest("${name}-ctest-s-res-variable" "-DCTEST_RESOURCE_ALLOC_ENABLED=1" "-DCTEST_RESOURCE_SPEC_SOURCE=VARIABLE" "-DCTRESALLOC_COMMAND=${CTRESALLOC_COMMAND}" "-DCTEST_PARALLEL=${parallel}" "-DCTEST_RANDOM=${random}")
+    run_ctest("${name}-ctest-s-res-cache" "-DCTEST_RESOURCE_ALLOC_ENABLED=1" "-DCTEST_RESOURCE_SPEC_SOURCE=CACHE" "-DCTRESALLOC_COMMAND=${CTRESALLOC_COMMAND}" "-DCTEST_PARALLEL=${parallel}" "-DCTEST_RANDOM=${random}")
+  endif()
 endfunction()
 
 function(verify_ctest_resources)
@@ -155,15 +159,15 @@ function(verify_ctest_resources)
   endif()
 endfunction()
 
-run_ctest_resource(lotsoftests 10 1)
-run_ctest_resource(checkfree1 2 0)
-run_ctest_resource(checkfree2 1 0)
-run_ctest_resource(notenough1 1 0)
-run_ctest_resource(notenough2 1 0)
-run_ctest_resource(notenough3 1 0)
-run_ctest_resource(combine 1 0)
-run_ctest_resource(ensure_parallel 2 0)
+run_ctest_resource(lotsoftests 10 1 0)
+run_ctest_resource(checkfree1 2 0 1)
+run_ctest_resource(checkfree2 1 0 0)
+run_ctest_resource(notenough1 1 0 1)
+run_ctest_resource(notenough2 1 0 0)
+run_ctest_resource(notenough3 1 0 0)
+run_ctest_resource(combine 1 0 0)
+run_ctest_resource(ensure_parallel 2 0 0)
 
 set(ENV{CTEST_RESOURCE_GROUP_COUNT} 2)
-run_ctest_resource(process_count 1 0)
+run_ctest_resource(process_count 1 0 0)
 unset(ENV{CTEST_RESOURCE_GROUP_COUNT})
diff --git a/Tests/RunCMake/CTestResourceAllocation/checkfree1-ctest-s-res-cache-check.cmake b/Tests/RunCMake/CTestResourceAllocation/checkfree1-ctest-s-res-cache-check.cmake
new file mode 100644
index 0000000..ceda72e
--- /dev/null
+++ b/Tests/RunCMake/CTestResourceAllocation/checkfree1-ctest-s-res-cache-check.cmake
@@ -0,0 +1 @@
+verify_ctest_resources()
diff --git a/Tests/RunCMake/CTestResourceAllocation/checkfree1-ctest-s-res-variable-check.cmake b/Tests/RunCMake/CTestResourceAllocation/checkfree1-ctest-s-res-variable-check.cmake
new file mode 100644
index 0000000..ceda72e
--- /dev/null
+++ b/Tests/RunCMake/CTestResourceAllocation/checkfree1-ctest-s-res-variable-check.cmake
@@ -0,0 +1 @@
+verify_ctest_resources()
diff --git a/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-cache-check.cmake b/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-cache-check.cmake
new file mode 100644
index 0000000..321e9a2
--- /dev/null
+++ b/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-cache-check.cmake
@@ -0,0 +1,3 @@
+if(EXISTS "${RunCMake_TEST_BINARY_DIR}/ctresalloc.log")
+  set(RunCMake_TEST_FAILED "ctresalloc.log should not exist")
+endif()
diff --git a/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-cache-result.txt b/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-cache-result.txt
new file mode 100644
index 0000000..b57e2de
--- /dev/null
+++ b/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-cache-result.txt
@@ -0,0 +1 @@
+(-1|255)
diff --git a/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-cache-stderr.txt b/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-cache-stderr.txt
new file mode 100644
index 0000000..521a34b
--- /dev/null
+++ b/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-cache-stderr.txt
@@ -0,0 +1,14 @@
+^Insufficient resources for test Test1:
+
+  Test requested resources of type 'fluxcapacitors' in the following amounts:
+    200 slots
+  but only the following units were available:
+    'outatime': 121 slots
+
+Resource spec file:
+
+  [^
+]*/Tests/RunCMake/CTestResourceAllocation/resspec.json
+CMake Error at [^
+]*/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-cache/test\.cmake:[0-9]+ \(message\):
+  Tests did not pass$
diff --git a/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-variable-check.cmake b/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-variable-check.cmake
new file mode 100644
index 0000000..321e9a2
--- /dev/null
+++ b/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-variable-check.cmake
@@ -0,0 +1,3 @@
+if(EXISTS "${RunCMake_TEST_BINARY_DIR}/ctresalloc.log")
+  set(RunCMake_TEST_FAILED "ctresalloc.log should not exist")
+endif()
diff --git a/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-variable-result.txt b/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-variable-result.txt
new file mode 100644
index 0000000..b57e2de
--- /dev/null
+++ b/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-variable-result.txt
@@ -0,0 +1 @@
+(-1|255)
diff --git a/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-variable-stderr.txt b/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-variable-stderr.txt
new file mode 100644
index 0000000..dcf13e0
--- /dev/null
+++ b/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-variable-stderr.txt
@@ -0,0 +1,14 @@
+^Insufficient resources for test Test1:
+
+  Test requested resources of type 'fluxcapacitors' in the following amounts:
+    200 slots
+  but only the following units were available:
+    'outatime': 121 slots
+
+Resource spec file:
+
+  [^
+]*/Tests/RunCMake/CTestResourceAllocation/resspec.json
+CMake Error at [^
+]*/Tests/RunCMake/CTestResourceAllocation/notenough1-ctest-s-res-variable/test\.cmake:[0-9]+ \(message\):
+  Tests did not pass$
diff --git a/Tests/RunCMake/CTestResourceAllocation/test.cmake.in b/Tests/RunCMake/CTestResourceAllocation/test.cmake.in
index 4b426f1..54eb4e9 100644
--- a/Tests/RunCMake/CTestResourceAllocation/test.cmake.in
+++ b/Tests/RunCMake/CTestResourceAllocation/test.cmake.in
@@ -8,15 +8,21 @@ set(CTEST_CMAKE_GENERATOR_TOOLSET "@RunCMake_GENERATOR_TOOLSET@")
 set(CTEST_BUILD_CONFIGURATION "$ENV{CMAKE_CONFIG_TYPE}")
 set(CTEST_NIGHTLY_START_TIME "01:00:00 UTC")
 
-ctest_start(Experimental QUIET)
-ctest_configure(OPTIONS
+set(config_options
   "-DCTEST_RESOURCE_ALLOC_ENABLED=${CTEST_RESOURCE_ALLOC_ENABLED};-DCTRESALLOC_COMMAND=${CTRESALLOC_COMMAND}"
   )
-ctest_build()
 
-if(CTEST_RESOURCE_ALLOC_ENABLED)
+if(CTEST_RESOURCE_SPEC_SOURCE STREQUAL "ARG")
   set(resspec RESOURCE_SPEC_FILE "@RunCMake_SOURCE_DIR@/resspec.json")
+elseif(CTEST_RESOURCE_SPEC_SOURCE STREQUAL "VARIABLE")
+  set(CTEST_RESOURCE_SPEC_FILE "@RunCMake_SOURCE_DIR@/resspec.json")
+elseif(CTEST_RESOURCE_SPEC_SOURCE STREQUAL "CACHE")
+  list(APPEND config_options "-DCTEST_RESOURCE_SPEC_FILE=@RunCMake_SOURCE_DIR@/resspec.json")
 endif()
+
+ctest_start(Experimental QUIET)
+ctest_configure(OPTIONS "${config_options}")
+ctest_build()
 ctest_test(${resspec} RETURN_VALUE retval PARALLEL_LEVEL ${CTEST_PARALLEL} SCHEDULE_RANDOM ${CTEST_RANDOM})
 if(retval)
   message(FATAL_ERROR "Tests did not pass")
-- 
2.7.4

