Bladeren bron

Merge topic 'ctest-hardware-allocation'

e9500271a3 Help: Add documentation for CTest hardware allocation
d1f100a415 CTest: Add Json output for PROCESSES property
b741ee820d Tests: Add test for CTest hardware allocation feature
3c8a5aad46 Tests: Write tests for cthwalloc helper tool
2d74e54661 Tests: Write cthwalloc helper tool
e34de0691b CTest: Allocate hardware to tests
aee0964851 CTest: Add bin-packing algorithm
c494b2973a CTest: Add cmCTestHardwareAllocator class
...

Acked-by: Kitware Robot <[email protected]>
Merge-request: !3858
Brad King 6 jaren geleden
bovenliggende
commit
3247506662
100 gewijzigde bestanden met toevoegingen van 5783 en 12 verwijderingen
  1. 6 0
      Help/command/ctest_test.rst
  2. 1 0
      Help/manual/cmake-properties.7.rst
  3. 229 0
      Help/manual/ctest.1.rst
  4. 54 0
      Help/prop_test/PROCESSES.rst
  5. 8 0
      Help/prop_test/RESOURCE_LOCK.rst
  6. 6 0
      Help/release/dev/ctest-hardware-allocation.rst
  7. 8 0
      Source/CMakeLists.txt
  8. 201 0
      Source/CTest/cmCTestBinPacker.cxx
  9. 31 0
      Source/CTest/cmCTestBinPacker.h
  10. 86 0
      Source/CTest/cmCTestHardwareAllocator.cxx
  11. 39 0
      Source/CTest/cmCTestHardwareAllocator.h
  12. 133 0
      Source/CTest/cmCTestHardwareSpec.cxx
  13. 40 0
      Source/CTest/cmCTestHardwareSpec.h
  14. 162 4
      Source/CTest/cmCTestMultiProcessHandler.cxx
  15. 31 2
      Source/CTest/cmCTestMultiProcessHandler.h
  16. 55 0
      Source/CTest/cmCTestProcessesLexerHelper.cxx
  17. 44 0
      Source/CTest/cmCTestProcessesLexerHelper.h
  18. 43 0
      Source/CTest/cmCTestRunTest.cxx
  19. 18 1
      Source/CTest/cmCTestRunTest.h
  20. 4 0
      Source/CTest/cmCTestTestCommand.cxx
  21. 1 0
      Source/CTest/cmCTestTestCommand.h
  22. 43 0
      Source/CTest/cmCTestTestHandler.cxx
  23. 19 0
      Source/CTest/cmCTestTestHandler.h
  24. 2 0
      Source/LexerParser/.gitattributes
  25. 2224 0
      Source/LexerParser/cmCTestProcessesLexer.cxx
  26. 692 0
      Source/LexerParser/cmCTestProcessesLexer.h
  27. 102 0
      Source/LexerParser/cmCTestProcessesLexer.in.l
  28. 9 0
      Source/cmCTest.cxx
  29. 1 0
      Source/ctest.cxx
  30. 7 1
      Tests/CMakeLib/CMakeLists.txt
  31. 300 0
      Tests/CMakeLib/testCTestBinPacker.cxx
  32. 426 0
      Tests/CMakeLib/testCTestHardwareAllocator.cxx
  33. 84 0
      Tests/CMakeLib/testCTestHardwareSpec.cxx
  34. 23 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec1.json
  35. 11 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec10.json
  36. 12 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec11.json
  37. 1 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec12.json
  38. 1 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec13.json
  39. 8 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec14.json
  40. 8 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec15.json
  41. 8 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec16.json
  42. 11 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec17.json
  43. 11 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec18.json
  44. 4 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec2.json
  45. 8 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec3.json
  46. 4 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec4.json
  47. 2 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec5.json
  48. 5 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec6.json
  49. 8 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec7.json
  50. 9 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec8.json
  51. 10 0
      Tests/CMakeLib/testCTestHardwareSpec_data/spec9.json
  52. 137 0
      Tests/CMakeLib/testCTestProcesses.cxx
  53. 38 0
      Tests/RunCMake/CMakeLists.txt
  54. 1 0
      Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake
  55. 61 4
      Tests/RunCMake/CTestCommandLine/show-only_json-v1_check.py
  56. 9 0
      Tests/RunCMake/CTestHardwareAllocation/CMakeLists.txt.in
  57. 23 0
      Tests/RunCMake/CTestHardwareAllocation/HardwareCommon.cmake
  58. 167 0
      Tests/RunCMake/CTestHardwareAllocation/RunCMakeTest.cmake
  59. 1 0
      Tests/RunCMake/CTestHardwareAllocation/checkfree1-ctest-s-hw-check.cmake
  60. 7 0
      Tests/RunCMake/CTestHardwareAllocation/checkfree1.cmake
  61. 1 0
      Tests/RunCMake/CTestHardwareAllocation/checkfree2-ctest-s-hw-check.cmake
  62. 8 0
      Tests/RunCMake/CTestHardwareAllocation/checkfree2.cmake
  63. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-baddealloc-result.txt
  64. 2 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-baddealloc.log
  65. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest1-result.txt
  66. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest1.log
  67. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest2-result.txt
  68. 2 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest2.log
  69. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest3-result.txt
  70. 3 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest3.log
  71. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest4-result.txt
  72. 3 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest4.log
  73. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest5-result.txt
  74. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest5.log
  75. 14 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-good1.log
  76. 0 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-good2.log
  77. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-leak-result.txt
  78. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-leak.log
  79. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-nobegin-result.txt
  80. 0 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-nobegin.log
  81. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-noend-result.txt
  82. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-noend.log
  83. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-noid-result.txt
  84. 2 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-noid.log
  85. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-nolog-result.txt
  86. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-nores-result.txt
  87. 2 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-nores.log
  88. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-notenough-result.txt
  89. 2 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-notenough.log
  90. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-noproc-count-result.txt
  91. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badcount-result.txt
  92. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badres-result.txt
  93. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badwidgets1-result.txt
  94. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badwidgets2-result.txt
  95. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badwidgets3-result.txt
  96. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badwidgets4-result.txt
  97. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badwidgets5-result.txt
  98. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badwidgets6-result.txt
  99. 1 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badwidgets7-result.txt
  100. 20 0
      Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-good1-check.cmake

+ 6 - 0
Help/command/ctest_test.rst

@@ -17,6 +17,7 @@ Perform the :ref:`CTest Test Step` as a :ref:`Dashboard Client`.
              [EXCLUDE_FIXTURE_SETUP <regex>]
              [EXCLUDE_FIXTURE_CLEANUP <regex>]
              [PARALLEL_LEVEL <level>]
+             [HARDWARE_SPEC_FILE <file>]
              [TEST_LOAD <threshold>]
              [SCHEDULE_RANDOM <ON|OFF>]
              [STOP_TIME <time-of-day>]
@@ -82,6 +83,11 @@ The options are:
   Specify a positive number representing the number of tests to
   be run in parallel.
 
+``HARDWARE_SPEC_FILE <file>``
+  Specify a
+  :ref:`hardware specification file <ctest-hardware-specification-file>`. See
+  :ref:`ctest-hardware-allocation` for more information.
+
 ``TEST_LOAD <threshold>``
   While running tests in parallel, try not to start tests when they
   may cause the CPU load to pass above a given threshold.  If not

+ 1 - 0
Help/manual/cmake-properties.7.rst

@@ -414,6 +414,7 @@ Properties on Tests
    /prop_test/LABELS
    /prop_test/MEASUREMENT
    /prop_test/PASS_REGULAR_EXPRESSION
+   /prop_test/PROCESSES
    /prop_test/PROCESSOR_AFFINITY
    /prop_test/PROCESSORS
    /prop_test/REQUIRED_FILES

+ 229 - 0
Help/manual/ctest.1.rst

@@ -90,6 +90,15 @@ Options
 
  See `Label and Subproject Summary`_.
 
+``--hardware-spec-file <file>``
+ Run CTest with :ref:`hardware allocation <ctest-hardware-allocation>` enabled,
+ using the
+ :ref:`hardware specification file <ctest-hardware-specification-file>`
+ specified in ``<file>``.
+
+ When ``ctest`` is run as a `Dashboard Client`_ this sets the
+ ``HardwareSpecFile`` option of the `CTest Test Step`_.
+
 ``--test-load <level>``
  While running tests in parallel (e.g. with ``-j``), try not to start
  tests when they may cause the CPU load to pass above a given threshold.
@@ -958,6 +967,11 @@ Arguments to the command may specify some of the step settings.
 
 Configuration settings include:
 
+``HardwareSpecFile``
+  Specify a
+  :ref:`hardware specification file <ctest-hardware-specification-file>`. See
+  :ref:`ctest-hardware-allocation` for more information.
+
 ``LabelsForSubprojects``
   Specify a semicolon-separated list of labels that will be treated as
   subprojects. This mapping will be passed on to CDash when configure, test or
@@ -1267,6 +1281,221 @@ model is defined as follows:
     Test properties.
     Can contain keys for each of the supported test properties.
 
+.. _`ctest-hardware-allocation`:
+
+Hardware Allocation
+===================
+
+CTest provides a mechanism for tests to specify the hardware that they need and
+how much of it they need, and for users to specify the hardware availiable on
+the running machine. This allows CTest to internally keep track of which
+hardware is in use and which is free, scheduling tests in a way that prevents
+them from trying to claim hardware that is not available.
+
+A common use case for this feature is for tests that require the use of a GPU.
+Multiple tests can simultaneously allocate memory from a GPU, but if too many
+tests try to do this at once, some of them will fail to allocate, resulting in
+a failed test, even though the test would have succeeded if it had the memory
+it needed. By using the hardware allocation feature, each test can specify how
+much memory it requires from a GPU, allowing CTest to schedule tests in a way
+that running several of these tests at once does not exhaust the GPU's memory
+pool.
+
+Please note that CTest has no concept of what a GPU is or how much memory it
+has, nor does it have any way of communicating with a GPU to retrieve this
+information or perform any memory management. CTest simply keeps track of a
+list of abstract resource types, each of which has a certain number of slots
+available for tests to use. Each test specifies the number of slots that it
+requires from a certain resource, and CTest then schedules them in a way that
+prevents the total number of slots in use from exceeding the listed capacity.
+When a test is executed, and slots from a resource are allocated to that test,
+tests may assume that they have exclusive use of those slots for the duration
+of the test's process.
+
+The CTest hardware allocation feature consists of two inputs:
+
+* The :ref:`hardware specification file <ctest-hardware-specification-file>`,
+  described below, which describes the hardware resources available on the
+  system, and
+* The :prop_test:`PROCESSES` property of tests, which describes the resources
+  required by the test
+
+When CTest runs a test, the hardware allocated to that test is passed in the
+form of a set of
+:ref:`environment variables <ctest-hardware-environment-variables>` as
+described below. Using this information to decide which resource to connect to
+is left to the test writer.
+
+Please note that these processes are not spawned by CTest. The ``PROCESSES``
+property merely tells CTest what processes the test expects to launch. It is up
+to the test itself to do this process spawning, and read the :ref:`environment
+variables <ctest-hardware-environment-variables>` to determine which resources
+each process has been allocated.
+
+.. _`ctest-hardware-specification-file`:
+
+Hardware Specification File
+---------------------------
+
+The hardware specification file is a JSON file which is passed to CTest, either
+on the :manual:`ctest(1)` command line as ``--hardware-spec-file``, or as the
+``HARDWARE_SPEC_FILE`` argument of :command:`ctest_test`. The hardware
+specification file must be a JSON object. All examples in this document assume
+the following hardware specification file:
+
+.. code-block:: json
+
+  {
+    "local": [
+      {
+        "gpus": [
+          {
+            "id": "0",
+            "slots": 2
+          },
+          {
+            "id": "1",
+            "slots": 4
+          },
+          {
+            "id": "2",
+            "slots": 2
+          },
+          {
+            "id": "3"
+          }
+        ],
+        "crypto_chips": [
+          {
+            "id": "card0",
+            "slots": 4
+          }
+        ]
+      }
+    ]
+  }
+
+The members are:
+
+``local``
+  A JSON array consisting of CPU sockets present on the system. Currently, only
+  one socket is supported.
+
+  Each socket is a JSON object with members whose names are equal to the
+  desired resource types, such as ``gpu``. These names must start with a
+  lowercase letter or an underscore, and subsequent characters can be a
+  lowercase letter, a digit, or an underscore. Uppercase letters are not
+  allowed, because certain platforms have case-insensitive environment
+  variables. See the `Environment Variables`_ section below for
+  more information. It is recommended that the resource type name be the plural
+  of a noun, such as ``gpus`` or ``crypto_chips`` (and not ``gpu`` or
+  ``crypto_chip``.)
+
+  Please note that the names ``gpus`` and ``crypto_chips`` are just examples,
+  and CTest does not interpret them in any way. You are free to make up any
+  resource type you want to meet your own requirements.
+
+  The value for each resource type is a JSON array consisting of JSON objects,
+  each of which describe a specific instance of the specified resource. These
+  objects have the following members:
+
+  ``id``
+    A string consisting of an identifier for the resource. Each character in
+    the identifier can be a lowercase letter, a digit, or an underscore.
+    Uppercase letters are not allowed.
+
+    Identifiers must be unique within a resource type. However, they do not
+    have to be unique across resource types. For example, it is valid to have a
+    ``gpus`` resource named ``0`` and a ``crypto_chips`` resource named ``0``,
+    but not two ``gpus`` resources both named ``0``.
+
+    Please note that the IDs ``0``, ``1``, ``2``, ``3``, and ``card0`` are just
+    examples, and CTest does not interpret them in any way. You are free to
+    make up any IDs you want to meet your own requirements.
+
+  ``slots``
+    An optional unsigned number specifying the number of slots available on the
+    resource. For example, this could be megabytes of RAM on a GPU, or
+    cryptography units available on a cryptography chip. If ``slots`` is not
+    specified, a default value of ``1`` is assumed.
+
+In the example file above, there are four GPUs with ID's 0 through 3. GPU 0 has
+2 slots, GPU 1 has 4, GPU 2 has 2, and GPU 3 has a default of 1 slot. There is
+also one cryptography chip with 4 slots.
+
+``PROCESSES`` Property
+----------------------
+
+See :prop_test:`PROCESSES` for a description of this property.
+
+.. _`ctest-hardware-environment-variables`:
+
+Environment Variables
+---------------------
+
+Once CTest has decided which resources to allocate to a test, it passes this
+information to the test executable as a series of environment variables. For
+each example below, we will assume that the test in question has a
+:prop_test:`PROCESSES` property of ``2,gpus:2;gpus:4,gpus:1,crypto_chips:2``.
+
+The following variables are passed to the test process:
+
+.. envvar:: CTEST_PROCESS_COUNT
+
+  The total number of processes specified by the :prop_test:`PROCESSES`
+  property. For example:
+
+  * ``CTEST_PROCESS_COUNT=3``
+
+  This variable will only be defined if :manual:`ctest(1)` has been given a
+  ``--hardware-spec-file``, or if :command:`ctest_test` has been given a
+  ``HARDWARE_SPEC_FILE``. If no hardware specification file has been given,
+  this variable will not be defined.
+
+.. envvar:: CTEST_PROCESS_<num>
+
+  The list of resource types allocated to each process, with each item
+  separated by a comma. ``<num>`` is a number from zero to
+  ``CTEST_PROCESS_COUNT`` minus one. ``CTEST_PROCESS_<num>`` is defined for
+  each ``<num>`` in this range. For example:
+
+  * ``CTEST_PROCESS_0=gpus``
+  * ``CTEST_PROCESS_1=gpus``
+  * ``CTEST_PROCESS_2=crypto_chips,gpus``
+
+.. envvar:: CTEST_PROCESS_<num>_<resource-type>
+
+  The list of resource IDs and number of slots from each ID allocated to each
+  process for a given resource type. This variable consists of a series of
+  pairs, each pair separated by a semicolon, and with the two items in the pair
+  separated by a comma. The first item in each pair is ``id:`` followed by the
+  ID of a resource of type ``<resource-type>``, and the second item is
+  ``slots:`` followed by the number of slots from that resource allocated to
+  the given process. For example:
+
+  * ``CTEST_PROCESS_0_GPUS=id:0,slots:2``
+  * ``CTEST_PROCESS_1_GPUS=id:2,slots:2``
+  * ``CTEST_PROCESS_2_GPUS=id:1,slots:4;id:3,slots:1``
+  * ``CTEST_PROCESS_2_CRYPTO_CHIPS=id:card0,slots:2``
+
+  In this example, process 0 gets 2 slots from GPU ``0``, process 1 gets 2 slots
+  from GPU ``2``, and process 2 gets 4 slots from GPU ``1`` and 2 slots from
+  cryptography chip ``card0``.
+
+  ``<num>`` is a number from zero to ``CTEST_PROCESS_COUNT`` minus one.
+  ``<resource-type>`` is the name of a resource type, converted to uppercase.
+  ``CTEST_PROCESS_<num>_<resource-type>`` is defined for the product of each
+  ``<num>`` in the range listed above and each resource type listed in
+  ``CTEST_PROCESS_<num>``.
+
+  Because some platforms have case-insensitive names for environment variables,
+  the names of resource types may not clash in a case-insensitive environment.
+  Because of this, for the sake of simplicity, all resource types must be
+  listed in all lowercase in the
+  :ref:`hardware specification file <ctest-hardware-specification-file>` and in
+  the :prop_test:`PROCESSES` property, and they are converted to all uppercase
+  in the ``CTEST_PROCESS_<num>_<resource-type>`` environment variable.
+
 See Also
 ========
 

+ 54 - 0
Help/prop_test/PROCESSES.rst

@@ -0,0 +1,54 @@
+PROCESSES
+----------
+
+Set to specify the number of processes spawned by a test, and the resources
+that they require. See :ref:`hardware allocation <ctest-hardware-allocation>`
+for more information on how this property integrates into the CTest hardware
+allocation feature.
+
+The ``PROCESSES`` property is a :ref:`semicolon-separated list <CMake Language
+Lists>` of process descriptions. Each process description consists of an
+optional number of processes for the description followed by a series of
+resource requirements for those processes. These requirements (and the number
+of processes) are separated by commas. The resource requirements consist of the
+name of a resource type, followed by a colon, followed by an unsigned integer
+specifying the number of slots required on one resource of the given type.
+
+Please note that these processes are not spawned by CTest. The ``PROCESSES``
+property merely tells CTest what processes the test expects to launch. It is up
+to the test itself to do this process spawning, and read the :ref:`environment
+variables <ctest-hardware-environment-variables>` to determine which resources
+each process has been allocated.
+
+Consider the following example:
+
+.. code-block:: cmake
+
+  add_test(NAME MyTest COMMAND MyExe)
+  set_property(TEST MyTest PROPERTY PROCESSES
+    "2,gpus:2"
+    "gpus:4,crypto_chips:2")
+
+In this example, there are two process descriptions (implicitly separated by a
+semicolon.) The content of the first description is ``2,gpus:2``. This
+description spawns 2 processes, each of which requires 2 slots from a single
+GPU. The content of the second description is ``gpus:4,crypto_chips:2``. This
+description does not specify a process count, so a default of 1 is assumed.
+This single process requires 4 slots from a single GPU and 2 slots from a
+single cryptography chip. In total, 3 processes are spawned from this test,
+each with their own unique requirements.
+
+When CTest sets the :ref:`environment variables
+<ctest-hardware-environment-variables>` for a test, it assigns a process number
+based on the process description, starting at 0 on the left and the number of
+processes minus 1 on the right. For example, in the example above, the two
+processes in the first description would have IDs of 0 and 1, and the single
+process in the second description would have an ID of 2.
+
+Both the ``PROCESSES`` and :prop_test:`RESOURCE_LOCK` properties serve similar
+purposes, but they are distinct and orthogonal. Resources specified by
+``PROCESSES`` do not affect :prop_test:`RESOURCE_LOCK`, and vice versa. Whereas
+:prop_test:`RESOURCE_LOCK` is a simpler property that is used for locking one
+global resource, ``PROCESSES`` is a more advanced property that allows multiple
+tests to simultaneously use multiple resources of the same type, specifying
+their requirements in a fine-grained manner.

+ 8 - 0
Help/prop_test/RESOURCE_LOCK.rst

@@ -8,3 +8,11 @@ not to run concurrently.
 
 See also :prop_test:`FIXTURES_REQUIRED` if the resource requires any setup or
 cleanup steps.
+
+Both the :prop_test:`PROCESSES` and ``RESOURCE_LOCK`` properties serve similar
+purposes, but they are distinct and orthogonal. Resources specified by
+:prop_test:`PROCESSES` do not affect ``RESOURCE_LOCK``, and vice versa. Whereas
+``RESOURCE_LOCK`` is a simpler property that is used for locking one global
+resource, :prop_test:`PROCESSES` is a more advanced property that allows
+multiple tests to simultaneously use multiple resources of the same type,
+specifying their requirements in a fine-grained manner.

+ 6 - 0
Help/release/dev/ctest-hardware-allocation.rst

@@ -0,0 +1,6 @@
+ctest-hardware-allocation
+-------------------------
+
+* :manual:`ctest(1)` now has the ability to serialize tests based on hardware
+  requirements for each test. See :ref:`ctest-hardware-allocation` for
+  details.

+ 8 - 0
Source/CMakeLists.txt

@@ -899,6 +899,7 @@ include_directories(
 #
 set(CTEST_SRCS cmCTest.cxx
   CTest/cmProcess.cxx
+  CTest/cmCTestBinPacker.cxx
   CTest/cmCTestBuildAndTestHandler.cxx
   CTest/cmCTestBuildCommand.cxx
   CTest/cmCTestBuildHandler.cxx
@@ -918,10 +919,13 @@ set(CTEST_SRCS cmCTest.cxx
   CTest/cmCTestEmptyBinaryDirectoryCommand.cxx
   CTest/cmCTestGenericHandler.cxx
   CTest/cmCTestHandlerCommand.cxx
+  CTest/cmCTestHardwareAllocator.cxx
+  CTest/cmCTestHardwareSpec.cxx
   CTest/cmCTestLaunch.cxx
   CTest/cmCTestMemCheckCommand.cxx
   CTest/cmCTestMemCheckHandler.cxx
   CTest/cmCTestMultiProcessHandler.cxx
+  CTest/cmCTestProcessesLexerHelper.cxx
   CTest/cmCTestReadCustomFilesCommand.cxx
   CTest/cmCTestRunScriptCommand.cxx
   CTest/cmCTestRunTest.cxx
@@ -953,6 +957,10 @@ set(CTEST_SRCS cmCTest.cxx
   CTest/cmCTestHG.h
   CTest/cmCTestP4.cxx
   CTest/cmCTestP4.h
+
+  LexerParser/cmCTestProcessesLexer.cxx
+  LexerParser/cmCTestProcessesLexer.h
+  LexerParser/cmCTestProcessesLexer.in.l
   )
 
 # Build CTestLib

+ 201 - 0
Source/CTest/cmCTestBinPacker.cxx

@@ -0,0 +1,201 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmCTestBinPacker.h"
+
+#include <algorithm>
+#include <utility>
+
+bool cmCTestBinPackerAllocation::operator==(
+  const cmCTestBinPackerAllocation& other) const
+{
+  return this->ProcessIndex == other.ProcessIndex &&
+    this->SlotsNeeded == other.SlotsNeeded && this->Id == other.Id;
+}
+
+bool cmCTestBinPackerAllocation::operator!=(
+  const cmCTestBinPackerAllocation& other) const
+{
+  return !(*this == other);
+}
+
+namespace {
+
+/*
+ * The following algorithm is used to do two things:
+ *
+ * 1) Determine if a test's hardware requirements can fit within the hardware
+ *    present on the system, and
+ * 2) Do the actual allocation
+ *
+ * This algorithm performs a recursive search, looking for a bin pack that will
+ * fit the specified requirements. It has a template to specify different
+ * optimization strategies. If it ever runs out of room, it backtracks as far
+ * down the stack as it needs to and tries a different combination until no
+ * more combinations can be tried.
+ */
+template <typename AllocationStrategy>
+static bool AllocateCTestHardware(
+  const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
+  const std::vector<std::string>& hardwareSorted, std::size_t currentIndex,
+  std::vector<cmCTestBinPackerAllocation*>& allocations)
+{
+  // Iterate through all large enough resources until we find a solution
+  std::size_t hardwareIndex = 0;
+  while (hardwareIndex < hardwareSorted.size()) {
+    auto const& resource = hardware.at(hardwareSorted[hardwareIndex]);
+    if (resource.Free() >=
+        static_cast<unsigned int>(allocations[currentIndex]->SlotsNeeded)) {
+      // Preemptively allocate the resource
+      allocations[currentIndex]->Id = hardwareSorted[hardwareIndex];
+      if (currentIndex + 1 >= allocations.size()) {
+        // We have a solution
+        return true;
+      }
+
+      // Move the resource up the list until it is sorted again
+      auto hardware2 = hardware;
+      auto hardwareSorted2 = hardwareSorted;
+      hardware2[hardwareSorted2[hardwareIndex]].Locked +=
+        allocations[currentIndex]->SlotsNeeded;
+      AllocationStrategy::IncrementalSort(hardware2, hardwareSorted2,
+                                          hardwareIndex);
+
+      // Recurse one level deeper
+      if (AllocateCTestHardware<AllocationStrategy>(
+            hardware2, hardwareSorted2, currentIndex + 1, allocations)) {
+        return true;
+      }
+    }
+
+    // No solution found here, deallocate the resource and try the next one
+    allocations[currentIndex]->Id.clear();
+    auto freeSlots = hardware.at(hardwareSorted.at(hardwareIndex)).Free();
+    do {
+      ++hardwareIndex;
+    } while (hardwareIndex < hardwareSorted.size() &&
+             hardware.at(hardwareSorted.at(hardwareIndex)).Free() ==
+               freeSlots);
+  }
+
+  // No solution was found
+  return false;
+}
+
+template <typename AllocationStrategy>
+static bool AllocateCTestHardware(
+  const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
+  std::vector<cmCTestBinPackerAllocation>& allocations)
+{
+  // Sort the resource requirements in descending order by slots needed
+  std::vector<cmCTestBinPackerAllocation*> allocationsPtr;
+  allocationsPtr.reserve(allocations.size());
+  for (auto& allocation : allocations) {
+    allocationsPtr.push_back(&allocation);
+  }
+  std::stable_sort(
+    allocationsPtr.rbegin(), allocationsPtr.rend(),
+    [](cmCTestBinPackerAllocation* a1, cmCTestBinPackerAllocation* a2) {
+      return a1->SlotsNeeded < a2->SlotsNeeded;
+    });
+
+  // Sort the resources according to sort strategy
+  std::vector<std::string> hardwareSorted;
+  hardwareSorted.reserve(hardware.size());
+  for (auto const& hw : hardware) {
+    hardwareSorted.push_back(hw.first);
+  }
+  AllocationStrategy::InitialSort(hardware, hardwareSorted);
+
+  // Do the actual allocation
+  return AllocateCTestHardware<AllocationStrategy>(
+    hardware, hardwareSorted, std::size_t(0), allocationsPtr);
+}
+
+class RoundRobinAllocationStrategy
+{
+public:
+  static void InitialSort(
+    const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
+    std::vector<std::string>& hardwareSorted);
+
+  static void IncrementalSort(
+    const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
+    std::vector<std::string>& hardwareSorted, std::size_t lastAllocatedIndex);
+};
+
+void RoundRobinAllocationStrategy::InitialSort(
+  const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
+  std::vector<std::string>& hardwareSorted)
+{
+  std::stable_sort(
+    hardwareSorted.rbegin(), hardwareSorted.rend(),
+    [&hardware](const std::string& id1, const std::string& id2) {
+      return hardware.at(id1).Free() < hardware.at(id2).Free();
+    });
+}
+
+void RoundRobinAllocationStrategy::IncrementalSort(
+  const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
+  std::vector<std::string>& hardwareSorted, std::size_t lastAllocatedIndex)
+{
+  auto tmp = hardwareSorted[lastAllocatedIndex];
+  std::size_t i = lastAllocatedIndex;
+  while (i < hardwareSorted.size() - 1 &&
+         hardware.at(hardwareSorted[i + 1]).Free() > hardware.at(tmp).Free()) {
+    hardwareSorted[i] = hardwareSorted[i + 1];
+    ++i;
+  }
+  hardwareSorted[i] = tmp;
+}
+
+class BlockAllocationStrategy
+{
+public:
+  static void InitialSort(
+    const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
+    std::vector<std::string>& hardwareSorted);
+
+  static void IncrementalSort(
+    const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
+    std::vector<std::string>& hardwareSorted, std::size_t lastAllocatedIndex);
+};
+
+void BlockAllocationStrategy::InitialSort(
+  const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
+  std::vector<std::string>& hardwareSorted)
+{
+  std::stable_sort(
+    hardwareSorted.rbegin(), hardwareSorted.rend(),
+    [&hardware](const std::string& id1, const std::string& id2) {
+      return hardware.at(id1).Free() < hardware.at(id2).Free();
+    });
+}
+
+void BlockAllocationStrategy::IncrementalSort(
+  const std::map<std::string, cmCTestHardwareAllocator::Resource>&,
+  std::vector<std::string>& hardwareSorted, std::size_t lastAllocatedIndex)
+{
+  auto tmp = hardwareSorted[lastAllocatedIndex];
+  std::size_t i = lastAllocatedIndex;
+  while (i > 0) {
+    hardwareSorted[i] = hardwareSorted[i - 1];
+    --i;
+  }
+  hardwareSorted[i] = tmp;
+}
+}
+
+bool cmAllocateCTestHardwareRoundRobin(
+  const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
+  std::vector<cmCTestBinPackerAllocation>& allocations)
+{
+  return AllocateCTestHardware<RoundRobinAllocationStrategy>(hardware,
+                                                             allocations);
+}
+
+bool cmAllocateCTestHardwareBlock(
+  const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
+  std::vector<cmCTestBinPackerAllocation>& allocations)
+{
+  return AllocateCTestHardware<BlockAllocationStrategy>(hardware, allocations);
+}

+ 31 - 0
Source/CTest/cmCTestBinPacker.h

@@ -0,0 +1,31 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmCTestBinPacker_h
+#define cmCTestBinPacker_h
+
+#include <cstddef>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "cmCTestHardwareAllocator.h"
+
+struct cmCTestBinPackerAllocation
+{
+  std::size_t ProcessIndex;
+  int SlotsNeeded;
+  std::string Id;
+
+  bool operator==(const cmCTestBinPackerAllocation& other) const;
+  bool operator!=(const cmCTestBinPackerAllocation& other) const;
+};
+
+bool cmAllocateCTestHardwareRoundRobin(
+  const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
+  std::vector<cmCTestBinPackerAllocation>& allocations);
+
+bool cmAllocateCTestHardwareBlock(
+  const std::map<std::string, cmCTestHardwareAllocator::Resource>& hardware,
+  std::vector<cmCTestBinPackerAllocation>& allocations);
+
+#endif

+ 86 - 0
Source/CTest/cmCTestHardwareAllocator.cxx

@@ -0,0 +1,86 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmCTestHardwareAllocator.h"
+
+#include <utility>
+#include <vector>
+
+#include "cmCTestHardwareSpec.h"
+
+void cmCTestHardwareAllocator::InitializeFromHardwareSpec(
+  const cmCTestHardwareSpec& spec)
+{
+  this->Resources.clear();
+
+  for (auto const& it : spec.LocalSocket.Resources) {
+    auto& res = this->Resources[it.first];
+    for (auto const& specRes : it.second) {
+      res[specRes.Id].Total = specRes.Capacity;
+      res[specRes.Id].Locked = 0;
+    }
+  }
+}
+
+const std::map<std::string,
+               std::map<std::string, cmCTestHardwareAllocator::Resource>>&
+cmCTestHardwareAllocator::GetResources() const
+{
+  return this->Resources;
+}
+
+bool cmCTestHardwareAllocator::AllocateResource(const std::string& name,
+                                                const std::string& id,
+                                                unsigned int slots)
+{
+  auto it = this->Resources.find(name);
+  if (it == this->Resources.end()) {
+    return false;
+  }
+
+  auto resIt = it->second.find(id);
+  if (resIt == it->second.end()) {
+    return false;
+  }
+
+  if (resIt->second.Total < resIt->second.Locked + slots) {
+    return false;
+  }
+
+  resIt->second.Locked += slots;
+  return true;
+}
+
+bool cmCTestHardwareAllocator::DeallocateResource(const std::string& name,
+                                                  const std::string& id,
+                                                  unsigned int slots)
+{
+  auto it = this->Resources.find(name);
+  if (it == this->Resources.end()) {
+    return false;
+  }
+
+  auto resIt = it->second.find(id);
+  if (resIt == it->second.end()) {
+    return false;
+  }
+
+  if (resIt->second.Locked < slots) {
+    return false;
+  }
+
+  resIt->second.Locked -= slots;
+  return true;
+}
+
+bool cmCTestHardwareAllocator::Resource::operator==(
+  const Resource& other) const
+{
+  return this->Total == other.Total && this->Locked == other.Locked;
+}
+
+bool cmCTestHardwareAllocator::Resource::operator!=(
+  const Resource& other) const
+{
+  return !(*this == other);
+}

+ 39 - 0
Source/CTest/cmCTestHardwareAllocator.h

@@ -0,0 +1,39 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmCTestHardwareAllocator_h
+#define cmCTestHardwareAllocator_h
+
+#include <map>
+#include <string>
+
+class cmCTestHardwareSpec;
+
+class cmCTestHardwareAllocator
+{
+public:
+  struct Resource
+  {
+    unsigned int Total;
+    unsigned int Locked;
+
+    unsigned int Free() const { return this->Total - this->Locked; }
+
+    bool operator==(const Resource& other) const;
+    bool operator!=(const Resource& other) const;
+  };
+
+  void InitializeFromHardwareSpec(const cmCTestHardwareSpec& spec);
+
+  const std::map<std::string, std::map<std::string, Resource>>& GetResources()
+    const;
+
+  bool AllocateResource(const std::string& name, const std::string& id,
+                        unsigned int slots);
+  bool DeallocateResource(const std::string& name, const std::string& id,
+                          unsigned int slots);
+
+private:
+  std::map<std::string, std::map<std::string, Resource>> Resources;
+};
+
+#endif

+ 133 - 0
Source/CTest/cmCTestHardwareSpec.cxx

@@ -0,0 +1,133 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmCTestHardwareSpec.h"
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "cmsys/FStream.hxx"
+#include "cmsys/RegularExpression.hxx"
+
+#include "cm_jsoncpp_reader.h"
+#include "cm_jsoncpp_value.h"
+
+static const cmsys::RegularExpression IdentifierRegex{ "^[a-z_][a-z0-9_]*$" };
+static const cmsys::RegularExpression IdRegex{ "^[a-z0-9_]+$" };
+
+bool cmCTestHardwareSpec::ReadFromJSONFile(const std::string& filename)
+{
+  cmsys::ifstream fin(filename.c_str());
+  if (!fin) {
+    return false;
+  }
+
+  Json::Value root;
+  Json::CharReaderBuilder builder;
+  if (!Json::parseFromStream(builder, fin, &root, nullptr)) {
+    return false;
+  }
+
+  if (!root.isObject()) {
+    return false;
+  }
+
+  auto const& local = root["local"];
+  if (!local.isArray()) {
+    return false;
+  }
+  if (local.size() > 1) {
+    return false;
+  }
+
+  if (local.empty()) {
+    this->LocalSocket.Resources.clear();
+    return true;
+  }
+
+  auto const& localSocket = local[0];
+  if (!localSocket.isObject()) {
+    return false;
+  }
+  std::map<std::string, std::vector<cmCTestHardwareSpec::Resource>> resources;
+  cmsys::RegularExpressionMatch match;
+  for (auto const& key : localSocket.getMemberNames()) {
+    if (IdentifierRegex.find(key.c_str(), match)) {
+      auto const& value = localSocket[key];
+      auto& r = resources[key];
+      if (value.isArray()) {
+        for (auto const& item : value) {
+          if (item.isObject()) {
+            cmCTestHardwareSpec::Resource resource;
+
+            if (!item.isMember("id")) {
+              return false;
+            }
+            auto const& id = item["id"];
+            if (!id.isString()) {
+              return false;
+            }
+            resource.Id = id.asString();
+            if (!IdRegex.find(resource.Id.c_str(), match)) {
+              return false;
+            }
+
+            if (item.isMember("slots")) {
+              auto const& capacity = item["slots"];
+              if (!capacity.isConvertibleTo(Json::uintValue)) {
+                return false;
+              }
+              resource.Capacity = capacity.asUInt();
+            } else {
+              resource.Capacity = 1;
+            }
+
+            r.push_back(resource);
+          } else {
+            return false;
+          }
+        }
+      } else {
+        return false;
+      }
+    }
+  }
+
+  this->LocalSocket.Resources = std::move(resources);
+  return true;
+}
+
+bool cmCTestHardwareSpec::operator==(const cmCTestHardwareSpec& other) const
+{
+  return this->LocalSocket == other.LocalSocket;
+}
+
+bool cmCTestHardwareSpec::operator!=(const cmCTestHardwareSpec& other) const
+{
+  return !(*this == other);
+}
+
+bool cmCTestHardwareSpec::Socket::operator==(
+  const cmCTestHardwareSpec::Socket& other) const
+{
+  return this->Resources == other.Resources;
+}
+
+bool cmCTestHardwareSpec::Socket::operator!=(
+  const cmCTestHardwareSpec::Socket& other) const
+{
+  return !(*this == other);
+}
+
+bool cmCTestHardwareSpec::Resource::operator==(
+  const cmCTestHardwareSpec::Resource& other) const
+{
+  return this->Id == other.Id && this->Capacity == other.Capacity;
+}
+
+bool cmCTestHardwareSpec::Resource::operator!=(
+  const cmCTestHardwareSpec::Resource& other) const
+{
+  return !(*this == other);
+}

+ 40 - 0
Source/CTest/cmCTestHardwareSpec.h

@@ -0,0 +1,40 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmCTestHardwareSpec_h
+#define cmCTestHardwareSpec_h
+
+#include <map>
+#include <string>
+#include <vector>
+
+class cmCTestHardwareSpec
+{
+public:
+  class Resource
+  {
+  public:
+    std::string Id;
+    unsigned int Capacity;
+
+    bool operator==(const Resource& other) const;
+    bool operator!=(const Resource& other) const;
+  };
+
+  class Socket
+  {
+  public:
+    std::map<std::string, std::vector<Resource>> Resources;
+
+    bool operator==(const Socket& other) const;
+    bool operator!=(const Socket& other) const;
+  };
+
+  Socket LocalSocket;
+
+  bool ReadFromJSONFile(const std::string& filename);
+
+  bool operator==(const cmCTestHardwareSpec& other) const;
+  bool operator!=(const cmCTestHardwareSpec& other) const;
+};
+
+#endif

+ 162 - 4
Source/CTest/cmCTestMultiProcessHandler.cxx

@@ -3,8 +3,10 @@
 #include "cmCTestMultiProcessHandler.h"
 
 #include <algorithm>
+#include <cassert>
 #include <chrono>
 #include <cmath>
+#include <cstddef>
 #include <cstdlib>
 #include <cstring>
 #include <iomanip>
@@ -27,6 +29,7 @@
 #include "cmAffinity.h"
 #include "cmAlgorithms.h"
 #include "cmCTest.h"
+#include "cmCTestBinPacker.h"
 #include "cmCTestRunTest.h"
 #include "cmCTestTestHandler.h"
 #include "cmDuration.h"
@@ -133,6 +136,12 @@ void cmCTestMultiProcessHandler::RunTests()
   uv_run(&this->Loop, UV_RUN_DEFAULT);
   uv_loop_close(&this->Loop);
 
+  if (!this->StopTimePassed) {
+    assert(this->Completed == this->Total);
+    assert(this->Tests.empty());
+  }
+  assert(this->AllHardwareAvailable());
+
   this->MarkFinished();
   this->UpdateCostData();
 }
@@ -168,6 +177,10 @@ bool cmCTestMultiProcessHandler::StartTestProcess(int test)
   }
   testRun->SetIndex(test);
   testRun->SetTestProperties(this->Properties[test]);
+  if (this->TestHandler->UseHardwareSpec) {
+    testRun->SetUseAllocatedHardware(true);
+    testRun->SetAllocatedHardware(this->AllocatedHardware[test]);
+  }
 
   // Find any failed dependencies for this test. We assume the more common
   // scenario has no failed tests, so make it the outer loop.
@@ -179,7 +192,13 @@ bool cmCTestMultiProcessHandler::StartTestProcess(int test)
 
   // Always lock the resources we'll be using, even if we fail to set the
   // working directory because FinishTestProcess() will try to unlock them
-  this->LockResources(test);
+  this->AllocateResources(test);
+
+  if (!this->TestsHaveSufficientHardware[test]) {
+    testRun->StartFailure("Insufficient hardware");
+    this->FinishTestProcess(testRun, false);
+    return false;
+  }
 
   cmWorkingDirectory workdir(this->Properties[test]->Directory);
   if (workdir.Failed()) {
@@ -199,6 +218,110 @@ bool cmCTestMultiProcessHandler::StartTestProcess(int test)
   return false;
 }
 
+bool cmCTestMultiProcessHandler::AllocateHardware(int index)
+{
+  if (!this->TestHandler->UseHardwareSpec) {
+    return true;
+  }
+
+  std::map<std::string, std::vector<cmCTestBinPackerAllocation>> allocations;
+  if (!this->TryAllocateHardware(index, allocations)) {
+    return false;
+  }
+
+  auto& allocatedHardware = this->AllocatedHardware[index];
+  allocatedHardware.resize(this->Properties[index]->Processes.size());
+  for (auto const& it : allocations) {
+    for (auto const& alloc : it.second) {
+      bool result = this->HardwareAllocator.AllocateResource(
+        it.first, alloc.Id, alloc.SlotsNeeded);
+      (void)result;
+      assert(result);
+      allocatedHardware[alloc.ProcessIndex][it.first].push_back(
+        { alloc.Id, static_cast<unsigned int>(alloc.SlotsNeeded) });
+    }
+  }
+
+  return true;
+}
+
+bool cmCTestMultiProcessHandler::TryAllocateHardware(
+  int index,
+  std::map<std::string, std::vector<cmCTestBinPackerAllocation>>& allocations)
+{
+  allocations.clear();
+
+  std::size_t processIndex = 0;
+  for (auto const& process : this->Properties[index]->Processes) {
+    for (auto const& requirement : process) {
+      for (int i = 0; i < requirement.UnitsNeeded; ++i) {
+        allocations[requirement.ResourceType].push_back(
+          { processIndex, requirement.SlotsNeeded, "" });
+      }
+    }
+    ++processIndex;
+  }
+
+  auto const& availableHardware = this->HardwareAllocator.GetResources();
+  for (auto& it : allocations) {
+    if (!availableHardware.count(it.first)) {
+      return false;
+    }
+    if (!cmAllocateCTestHardwareRoundRobin(availableHardware.at(it.first),
+                                           it.second)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void cmCTestMultiProcessHandler::DeallocateHardware(int index)
+{
+  if (!this->TestHandler->UseHardwareSpec) {
+    return;
+  }
+
+  {
+    auto& allocatedHardware = this->AllocatedHardware[index];
+    for (auto const& processAlloc : allocatedHardware) {
+      for (auto const& it : processAlloc) {
+        auto resourceType = it.first;
+        for (auto const& it2 : it.second) {
+          bool success = this->HardwareAllocator.DeallocateResource(
+            resourceType, it2.Id, it2.Slots);
+          (void)success;
+          assert(success);
+        }
+      }
+    }
+  }
+  this->AllocatedHardware.erase(index);
+}
+
+bool cmCTestMultiProcessHandler::AllHardwareAvailable()
+{
+  for (auto const& it : this->HardwareAllocator.GetResources()) {
+    for (auto const& it2 : it.second) {
+      if (it2.second.Locked != 0) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+void cmCTestMultiProcessHandler::CheckHardwareAvailable()
+{
+  for (auto test : this->SortedTests) {
+    std::map<std::string, std::vector<cmCTestBinPackerAllocation>> allocations;
+    this->TestsHaveSufficientHardware[test] =
+      !this->TestHandler->UseHardwareSpec ||
+      this->TryAllocateHardware(test, allocations);
+  }
+}
+
 bool cmCTestMultiProcessHandler::CheckStopTimePassed()
 {
   if (!this->StopTimePassed) {
@@ -223,7 +346,7 @@ void cmCTestMultiProcessHandler::SetStopTimePassed()
   }
 }
 
-void cmCTestMultiProcessHandler::LockResources(int index)
+void cmCTestMultiProcessHandler::AllocateResources(int index)
 {
   this->LockedResources.insert(
     this->Properties[index]->LockedResources.begin(),
@@ -234,7 +357,7 @@ void cmCTestMultiProcessHandler::LockResources(int index)
   }
 }
 
-void cmCTestMultiProcessHandler::UnlockResources(int index)
+void cmCTestMultiProcessHandler::DeallocateResources(int index)
 {
   for (std::string const& i : this->Properties[index]->LockedResources) {
     this->LockedResources.erase(i);
@@ -281,12 +404,20 @@ bool cmCTestMultiProcessHandler::StartTest(int test)
     }
   }
 
+  // Allocate hardware
+  if (this->TestsHaveSufficientHardware[test] &&
+      !this->AllocateHardware(test)) {
+    this->DeallocateHardware(test);
+    return false;
+  }
+
   // if there are no depends left then run this test
   if (this->Tests[test].empty()) {
     return this->StartTestProcess(test);
   }
   // This test was not able to start because it is waiting
   // on depends to run
+  this->DeallocateHardware(test);
   return false;
 }
 
@@ -471,7 +602,8 @@ void cmCTestMultiProcessHandler::FinishTestProcess(cmCTestRunTest* runner,
   this->TestFinishMap[test] = true;
   this->TestRunningMap[test] = false;
   this->WriteCheckpoint(test);
-  this->UnlockResources(test);
+  this->DeallocateHardware(test);
+  this->DeallocateResources(test);
   this->RunningCount -= GetProcessorsUsed(test);
 
   for (auto p : properties->Affinity) {
@@ -780,6 +912,28 @@ static Json::Value DumpTimeoutAfterMatch(
   return timeoutAfterMatch;
 }
 
+static Json::Value DumpProcessesToJsonArray(
+  const std::vector<
+    std::vector<cmCTestTestHandler::cmCTestTestResourceRequirement>>&
+    processes)
+{
+  Json::Value jsonProcesses = Json::arrayValue;
+  for (auto const& it : processes) {
+    Json::Value jsonProcess = Json::objectValue;
+    Json::Value requirements = Json::arrayValue;
+    for (auto const& it2 : it) {
+      Json::Value res = Json::objectValue;
+      res[".type"] = it2.ResourceType;
+      // res[".units"] = it2.UnitsNeeded; // Intentionally commented out
+      res["slots"] = it2.SlotsNeeded;
+      requirements.append(res);
+    }
+    jsonProcess["requirements"] = requirements;
+    jsonProcesses.append(jsonProcess);
+  }
+  return jsonProcesses;
+}
+
 static Json::Value DumpCTestProperty(std::string const& name,
                                      Json::Value value)
 {
@@ -851,6 +1005,10 @@ static Json::Value DumpCTestProperties(
       "PASS_REGULAR_EXPRESSION",
       DumpRegExToJsonArray(testProperties.RequiredRegularExpressions)));
   }
+  if (!testProperties.Processes.empty()) {
+    properties.append(DumpCTestProperty(
+      "PROCESSES", DumpProcessesToJsonArray(testProperties.Processes)));
+  }
   if (testProperties.WantAffinity) {
     properties.append(
       DumpCTestProperty("PROCESSOR_AFFINITY", testProperties.WantAffinity));

+ 31 - 2
Source/CTest/cmCTestMultiProcessHandler.h

@@ -14,10 +14,13 @@
 
 #include "cm_uv.h"
 
+#include "cmCTestHardwareAllocator.h"
 #include "cmCTestTestHandler.h"
 #include "cmUVHandlePtr.h"
 
 class cmCTest;
+struct cmCTestBinPackerAllocation;
+class cmCTestHardwareSpec;
 class cmCTestRunTest;
 
 /** \class cmCTestMultiProcessHandler
@@ -44,6 +47,11 @@ public:
     : public std::map<int, cmCTestTestHandler::cmCTestTestProperties*>
   {
   };
+  struct HardwareAllocation
+  {
+    std::string Id;
+    unsigned int Slots;
+  };
 
   cmCTestMultiProcessHandler();
   virtual ~cmCTestMultiProcessHandler();
@@ -79,6 +87,13 @@ public:
 
   void SetQuiet(bool b) { this->Quiet = b; }
 
+  void InitHardwareAllocator(const cmCTestHardwareSpec& spec)
+  {
+    this->HardwareAllocator.InitializeFromHardwareSpec(spec);
+  }
+
+  void CheckHardwareAvailable();
+
 protected:
   // Start the next test or tests as many as are allowed by
   // ParallelLevel
@@ -119,8 +134,17 @@ protected:
   bool CheckStopTimePassed();
   void SetStopTimePassed();
 
-  void LockResources(int index);
-  void UnlockResources(int index);
+  void AllocateResources(int index);
+  void DeallocateResources(int index);
+
+  bool AllocateHardware(int index);
+  bool TryAllocateHardware(
+    int index,
+    std::map<std::string, std::vector<cmCTestBinPackerAllocation>>&
+      allocations);
+  void DeallocateHardware(int index);
+  bool AllHardwareAvailable();
+
   // map from test number to set of depend tests
   TestMap Tests;
   TestList SortedTests;
@@ -141,6 +165,11 @@ protected:
   std::vector<std::string>* Failed;
   std::vector<std::string> LastTestsFailed;
   std::set<std::string> LockedResources;
+  std::map<int,
+           std::vector<std::map<std::string, std::vector<HardwareAllocation>>>>
+    AllocatedHardware;
+  std::map<int, bool> TestsHaveSufficientHardware;
+  cmCTestHardwareAllocator HardwareAllocator;
   std::vector<cmCTestTestHandler::cmCTestTestResult>* TestResults;
   size_t ParallelLevel; // max number of process that can be run at once
   unsigned long TestLoad;

+ 55 - 0
Source/CTest/cmCTestProcessesLexerHelper.cxx

@@ -0,0 +1,55 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmCTestProcessesLexerHelper.h"
+
+#include "cmCTestProcessesLexer.h"
+#include "cmCTestTestHandler.h"
+
+cmCTestProcessesLexerHelper::cmCTestProcessesLexerHelper(
+  std::vector<std::vector<cmCTestTestHandler::cmCTestTestResourceRequirement>>&
+    output)
+  : Output(output)
+{
+}
+
+bool cmCTestProcessesLexerHelper::ParseString(const std::string& value)
+{
+  yyscan_t lexer;
+  cmCTestProcesses_yylex_init_extra(this, &lexer);
+
+  auto state = cmCTestProcesses_yy_scan_string(value.c_str(), lexer);
+  int retval = cmCTestProcesses_yylex(lexer);
+  cmCTestProcesses_yy_delete_buffer(state, lexer);
+
+  cmCTestProcesses_yylex_destroy(lexer);
+  return retval == 0;
+}
+
+void cmCTestProcessesLexerHelper::SetProcessCount(unsigned int count)
+{
+  this->ProcessCount = count;
+}
+
+void cmCTestProcessesLexerHelper::SetResourceType(const std::string& type)
+{
+  this->ResourceType = type;
+}
+
+void cmCTestProcessesLexerHelper::SetNeededSlots(int count)
+{
+  this->NeededSlots = count;
+}
+
+void cmCTestProcessesLexerHelper::WriteRequirement()
+{
+  this->Process.push_back({ this->ResourceType, this->NeededSlots, 1 });
+}
+
+void cmCTestProcessesLexerHelper::WriteProcess()
+{
+  for (unsigned int i = 0; i < this->ProcessCount; ++i) {
+    this->Output.push_back(this->Process);
+  }
+  this->Process.clear();
+  this->ProcessCount = 1;
+}

+ 44 - 0
Source/CTest/cmCTestProcessesLexerHelper.h

@@ -0,0 +1,44 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmCTestProcessesLexerHelper_h
+#define cmCTestProcessesLexerHelper_h
+
+#include <string>
+#include <vector>
+
+#include "cmCTestTestHandler.h"
+
+class cmCTestProcessesLexerHelper
+{
+public:
+  struct ParserType
+  {
+  };
+
+  cmCTestProcessesLexerHelper(
+    std::vector<
+      std::vector<cmCTestTestHandler::cmCTestTestResourceRequirement>>&
+      output);
+  ~cmCTestProcessesLexerHelper() = default;
+
+  bool ParseString(const std::string& value);
+
+  void SetProcessCount(unsigned int count);
+  void SetResourceType(const std::string& type);
+  void SetNeededSlots(int count);
+  void WriteRequirement();
+  void WriteProcess();
+
+private:
+  std::vector<std::vector<cmCTestTestHandler::cmCTestTestResourceRequirement>>&
+    Output;
+
+  unsigned int ProcessCount = 1;
+  std::string ResourceType;
+  int NeededSlots;
+  std::vector<cmCTestTestHandler::cmCTestTestResourceRequirement> Process;
+};
+
+#define YY_EXTRA_TYPE cmCTestProcessesLexerHelper*
+
+#endif

+ 43 - 0
Source/CTest/cmCTestRunTest.cxx

@@ -3,6 +3,7 @@
 #include "cmCTestRunTest.h"
 
 #include <chrono>
+#include <cstddef>
 #include <cstdint>
 #include <cstdio>
 #include <cstring>
@@ -689,10 +690,52 @@ bool cmCTestRunTest::ForkProcess(cmDuration testTimeOut, bool explicitTimeout,
     cmSystemTools::AppendEnv(*environment);
   }
 
+  if (this->UseAllocatedHardware) {
+    this->SetupHardwareEnvironment();
+  } else {
+    cmSystemTools::UnsetEnv("CTEST_PROCESS_COUNT");
+  }
+
   return this->TestProcess->StartProcess(this->MultiTestHandler.Loop,
                                          affinity);
 }
 
+void cmCTestRunTest::SetupHardwareEnvironment()
+{
+  std::string processCount = "CTEST_PROCESS_COUNT=";
+  processCount += std::to_string(this->AllocatedHardware.size());
+  cmSystemTools::PutEnv(processCount);
+
+  std::size_t i = 0;
+  for (auto const& process : this->AllocatedHardware) {
+    std::string prefix = "CTEST_PROCESS_";
+    prefix += std::to_string(i);
+    std::string resourceList = prefix + '=';
+    prefix += '_';
+    bool firstType = true;
+    for (auto const& it : process) {
+      if (!firstType) {
+        resourceList += ',';
+      }
+      firstType = false;
+      auto resourceType = it.first;
+      resourceList += resourceType;
+      std::string var = prefix + cmSystemTools::UpperCase(resourceType) + '=';
+      bool firstName = true;
+      for (auto const& it2 : it.second) {
+        if (!firstName) {
+          var += ';';
+        }
+        firstName = false;
+        var += "id:" + it2.Id + ",slots:" + std::to_string(it2.Slots);
+      }
+      cmSystemTools::PutEnv(var);
+    }
+    cmSystemTools::PutEnv(resourceList);
+    ++i;
+  }
+}
+
 void cmCTestRunTest::WriteLogOutputTop(size_t completed, size_t total)
 {
   std::ostringstream outputStream;

+ 18 - 1
Source/CTest/cmCTestRunTest.h

@@ -5,6 +5,7 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <map>
 #include <memory>
 #include <set>
 #include <string>
@@ -12,12 +13,12 @@
 
 #include <stddef.h>
 
+#include "cmCTestMultiProcessHandler.h"
 #include "cmCTestTestHandler.h"
 #include "cmDuration.h"
 #include "cmProcess.h"
 
 class cmCTest;
-class cmCTestMultiProcessHandler;
 
 /** \class cmRunTest
  * \brief represents a single test to be run
@@ -83,6 +84,16 @@ public:
 
   bool TimedOutForStopTime() const { return this->TimeoutIsForStopTime; }
 
+  void SetUseAllocatedHardware(bool use) { this->UseAllocatedHardware = use; }
+  void SetAllocatedHardware(
+    const std::vector<
+      std::map<std::string,
+               std::vector<cmCTestMultiProcessHandler::HardwareAllocation>>>&
+      hardware)
+  {
+    this->AllocatedHardware = hardware;
+  }
+
 private:
   bool NeedsToRerun();
   void DartProcessing();
@@ -94,6 +105,8 @@ private:
   // Run post processing of the process output for MemCheck
   void MemCheckPostProcess();
 
+  void SetupHardwareEnvironment();
+
   // Returns "completed/total Test #Index: "
   std::string GetTestPrefix(size_t completed, size_t total) const;
 
@@ -112,6 +125,10 @@ private:
   std::string StartTime;
   std::string ActualCommand;
   std::vector<std::string> Arguments;
+  bool UseAllocatedHardware = false;
+  std::vector<std::map<
+    std::string, std::vector<cmCTestMultiProcessHandler::HardwareAllocation>>>
+    AllocatedHardware;
   bool RunUntilFail;
   int NumberOfRunsLeft;
   bool RunAgain;

+ 4 - 0
Source/CTest/cmCTestTestCommand.cxx

@@ -32,6 +32,7 @@ void cmCTestTestCommand::BindArguments()
   this->Bind("SCHEDULE_RANDOM"_s, this->ScheduleRandom);
   this->Bind("STOP_TIME"_s, this->StopTime);
   this->Bind("TEST_LOAD"_s, this->TestLoad);
+  this->Bind("HARDWARE_SPEC_FILE"_s, this->HardwareSpecFile);
 }
 
 cmCTestGenericHandler* cmCTestTestCommand::InitializeHandler()
@@ -87,6 +88,9 @@ cmCTestGenericHandler* cmCTestTestCommand::InitializeHandler()
   if (!this->ScheduleRandom.empty()) {
     handler->SetOption("ScheduleRandom", this->ScheduleRandom.c_str());
   }
+  if (!this->HardwareSpecFile.empty()) {
+    handler->SetOption("HardwareSpecFile", this->HardwareSpecFile.c_str());
+  }
   if (!this->StopTime.empty()) {
     this->CTest->SetStopTime(this->StopTime);
   }

+ 1 - 0
Source/CTest/cmCTestTestCommand.h

@@ -58,6 +58,7 @@ protected:
   std::string ScheduleRandom;
   std::string StopTime;
   std::string TestLoad;
+  std::string HardwareSpecFile;
 };
 
 #endif

+ 43 - 0
Source/CTest/cmCTestTestHandler.cxx

@@ -29,6 +29,7 @@
 #include "cmAlgorithms.h"
 #include "cmCTest.h"
 #include "cmCTestMultiProcessHandler.h"
+#include "cmCTestProcessesLexerHelper.h"
 #include "cmDuration.h"
 #include "cmExecutionStatus.h"
 #include "cmGeneratedFileStream.h"
@@ -288,6 +289,7 @@ cmCTestTestHandler::cmCTestTestHandler()
   this->UseIncludeRegExpFlag = false;
   this->UseExcludeRegExpFlag = false;
   this->UseExcludeRegExpFirst = false;
+  this->UseHardwareSpec = false;
 
   this->CustomMaximumPassedTestOutputSize = 1 * 1024;
   this->CustomMaximumFailedTestOutputSize = 300 * 1024;
@@ -508,6 +510,16 @@ bool cmCTestTestHandler::ProcessOptions()
   }
   this->SetRerunFailed(cmIsOn(this->GetOption("RerunFailed")));
 
+  val = this->GetOption("HardwareSpecFile");
+  if (val) {
+    this->UseHardwareSpec = true;
+    if (!this->HardwareSpec.ReadFromJSONFile(val)) {
+      cmCTestLog(this->CTest, ERROR_MESSAGE,
+                 "Could not read hardware spec file: " << val << std::endl);
+      return false;
+    }
+  }
+
   return true;
 }
 
@@ -1225,6 +1237,9 @@ void cmCTestTestHandler::ProcessDirectory(std::vector<std::string>& passed,
   } else {
     parallel->SetTestLoad(this->CTest->GetTestLoad());
   }
+  if (this->UseHardwareSpec) {
+    parallel->InitHardwareAllocator(this->HardwareSpec);
+  }
 
   *this->LogFile
     << "Start testing: " << this->CTest->CurrentTime() << std::endl
@@ -1268,6 +1283,7 @@ void cmCTestTestHandler::ProcessDirectory(std::vector<std::string>& passed,
   parallel->SetPassFailVectors(&passed, &failed);
   this->TestResults.clear();
   parallel->SetTestResults(&this->TestResults);
+  parallel->CheckHardwareAvailable();
 
   if (this->CTest->ShouldPrintLabels()) {
     parallel->PrintLabels();
@@ -1610,6 +1626,14 @@ std::string cmCTestTestHandler::FindExecutable(
   return fullPath;
 }
 
+bool cmCTestTestHandler::ParseProcessesProperty(
+  const std::string& val,
+  std::vector<std::vector<cmCTestTestResourceRequirement>>& processes)
+{
+  cmCTestProcessesLexerHelper lexer(processes);
+  return lexer.ParseString(val);
+}
+
 void cmCTestTestHandler::GetListOfTests()
 {
   if (!this->IncludeLabelRegExp.empty()) {
@@ -2179,6 +2203,11 @@ bool cmCTestTestHandler::SetTestsProperties(
           if (key == "PROCESSOR_AFFINITY") {
             rt.WantAffinity = cmIsOn(val);
           }
+          if (key == "PROCESSES") {
+            if (!ParseProcessesProperty(val, rt.Processes)) {
+              return false;
+            }
+          }
           if (key == "SKIP_RETURN_CODE") {
             rt.SkipReturnCode = atoi(val.c_str());
             if (rt.SkipReturnCode < 0 || rt.SkipReturnCode > 255) {
@@ -2356,3 +2385,17 @@ bool cmCTestTestHandler::AddTest(const std::vector<std::string>& args)
   this->TestList.push_back(test);
   return true;
 }
+
+bool cmCTestTestHandler::cmCTestTestResourceRequirement::operator==(
+  const cmCTestTestResourceRequirement& other) const
+{
+  return this->ResourceType == other.ResourceType &&
+    this->SlotsNeeded == other.SlotsNeeded &&
+    this->UnitsNeeded == other.UnitsNeeded;
+}
+
+bool cmCTestTestHandler::cmCTestTestResourceRequirement::operator!=(
+  const cmCTestTestResourceRequirement& other) const
+{
+  return !(*this == other);
+}

+ 19 - 0
Source/CTest/cmCTestTestHandler.h

@@ -19,6 +19,7 @@
 #include "cmsys/RegularExpression.hxx"
 
 #include "cmCTestGenericHandler.h"
+#include "cmCTestHardwareSpec.h"
 #include "cmDuration.h"
 #include "cmListFileCache.h"
 
@@ -102,6 +103,16 @@ public:
 
   void Initialize() override;
 
+  struct cmCTestTestResourceRequirement
+  {
+    std::string ResourceType;
+    int SlotsNeeded;
+    int UnitsNeeded;
+
+    bool operator==(const cmCTestTestResourceRequirement& other) const;
+    bool operator!=(const cmCTestTestResourceRequirement& other) const;
+  };
+
   // NOTE: This struct is Saved/Restored
   // in cmCTestTestHandler, if you add to this class
   // then you must add the new members to that code or
@@ -147,6 +158,7 @@ public:
     std::set<std::string> FixturesCleanup;
     std::set<std::string> FixturesRequired;
     std::set<std::string> RequireSuccessDepends;
+    std::vector<std::vector<cmCTestTestResourceRequirement>> Processes;
     // Private test generator properties used to track backtraces
     cmListFileBacktrace Backtrace;
   };
@@ -190,6 +202,10 @@ public:
                                     std::vector<std::string>& extraPaths,
                                     std::vector<std::string>& failed);
 
+  static bool ParseProcessesProperty(
+    const std::string& val,
+    std::vector<std::vector<cmCTestTestResourceRequirement>>& processes);
+
   using ListOfTests = std::vector<cmCTestTestProperties>;
 
 protected:
@@ -320,6 +336,9 @@ private:
   cmsys::RegularExpression IncludeTestsRegularExpression;
   cmsys::RegularExpression ExcludeTestsRegularExpression;
 
+  bool UseHardwareSpec;
+  cmCTestHardwareSpec HardwareSpec;
+
   void GenerateRegressionImages(cmXMLWriter& xml, const std::string& dart);
   cmsys::RegularExpression DartStuff1;
   void CheckLabelFilter(cmCTestTestProperties& it);

+ 2 - 0
Source/LexerParser/.gitattributes

@@ -2,6 +2,8 @@
 /cmCommandArgumentLexer.h          generated
 /cmCommandArgumentParser.cxx       generated
 /cmCommandArgumentParserTokens.h   generated
+/cmCTestProcessesLexer.cxx         generated
+/cmCTestProcessesLexer.h           generated
 /cmDependsJavaLexer.cxx            generated
 /cmDependsJavaLexer.h              generated
 /cmDependsJavaParser.cxx           generated

+ 2224 - 0
Source/LexerParser/cmCTestProcessesLexer.cxx

@@ -0,0 +1,2224 @@
+#include "cmStandardLexer.h"
+
+#define FLEXINT_H 1
+#define  YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 6
+#define YY_FLEX_SUBMINOR_VERSION 4
+#if YY_FLEX_SUBMINOR_VERSION > 0
+#define FLEX_BETA
+#endif
+
+#ifdef yy_create_buffer
+#define cmCTestProcesses_yy_create_buffer_ALREADY_DEFINED
+#else
+#define yy_create_buffer cmCTestProcesses_yy_create_buffer
+#endif
+
+#ifdef yy_delete_buffer
+#define cmCTestProcesses_yy_delete_buffer_ALREADY_DEFINED
+#else
+#define yy_delete_buffer cmCTestProcesses_yy_delete_buffer
+#endif
+
+#ifdef yy_scan_buffer
+#define cmCTestProcesses_yy_scan_buffer_ALREADY_DEFINED
+#else
+#define yy_scan_buffer cmCTestProcesses_yy_scan_buffer
+#endif
+
+#ifdef yy_scan_string
+#define cmCTestProcesses_yy_scan_string_ALREADY_DEFINED
+#else
+#define yy_scan_string cmCTestProcesses_yy_scan_string
+#endif
+
+#ifdef yy_scan_bytes
+#define cmCTestProcesses_yy_scan_bytes_ALREADY_DEFINED
+#else
+#define yy_scan_bytes cmCTestProcesses_yy_scan_bytes
+#endif
+
+#ifdef yy_init_buffer
+#define cmCTestProcesses_yy_init_buffer_ALREADY_DEFINED
+#else
+#define yy_init_buffer cmCTestProcesses_yy_init_buffer
+#endif
+
+#ifdef yy_flush_buffer
+#define cmCTestProcesses_yy_flush_buffer_ALREADY_DEFINED
+#else
+#define yy_flush_buffer cmCTestProcesses_yy_flush_buffer
+#endif
+
+#ifdef yy_load_buffer_state
+#define cmCTestProcesses_yy_load_buffer_state_ALREADY_DEFINED
+#else
+#define yy_load_buffer_state cmCTestProcesses_yy_load_buffer_state
+#endif
+
+#ifdef yy_switch_to_buffer
+#define cmCTestProcesses_yy_switch_to_buffer_ALREADY_DEFINED
+#else
+#define yy_switch_to_buffer cmCTestProcesses_yy_switch_to_buffer
+#endif
+
+#ifdef yypush_buffer_state
+#define cmCTestProcesses_yypush_buffer_state_ALREADY_DEFINED
+#else
+#define yypush_buffer_state cmCTestProcesses_yypush_buffer_state
+#endif
+
+#ifdef yypop_buffer_state
+#define cmCTestProcesses_yypop_buffer_state_ALREADY_DEFINED
+#else
+#define yypop_buffer_state cmCTestProcesses_yypop_buffer_state
+#endif
+
+#ifdef yyensure_buffer_stack
+#define cmCTestProcesses_yyensure_buffer_stack_ALREADY_DEFINED
+#else
+#define yyensure_buffer_stack cmCTestProcesses_yyensure_buffer_stack
+#endif
+
+#ifdef yylex
+#define cmCTestProcesses_yylex_ALREADY_DEFINED
+#else
+#define yylex cmCTestProcesses_yylex
+#endif
+
+#ifdef yyrestart
+#define cmCTestProcesses_yyrestart_ALREADY_DEFINED
+#else
+#define yyrestart cmCTestProcesses_yyrestart
+#endif
+
+#ifdef yylex_init
+#define cmCTestProcesses_yylex_init_ALREADY_DEFINED
+#else
+#define yylex_init cmCTestProcesses_yylex_init
+#endif
+
+#ifdef yylex_init_extra
+#define cmCTestProcesses_yylex_init_extra_ALREADY_DEFINED
+#else
+#define yylex_init_extra cmCTestProcesses_yylex_init_extra
+#endif
+
+#ifdef yylex_destroy
+#define cmCTestProcesses_yylex_destroy_ALREADY_DEFINED
+#else
+#define yylex_destroy cmCTestProcesses_yylex_destroy
+#endif
+
+#ifdef yyget_debug
+#define cmCTestProcesses_yyget_debug_ALREADY_DEFINED
+#else
+#define yyget_debug cmCTestProcesses_yyget_debug
+#endif
+
+#ifdef yyset_debug
+#define cmCTestProcesses_yyset_debug_ALREADY_DEFINED
+#else
+#define yyset_debug cmCTestProcesses_yyset_debug
+#endif
+
+#ifdef yyget_extra
+#define cmCTestProcesses_yyget_extra_ALREADY_DEFINED
+#else
+#define yyget_extra cmCTestProcesses_yyget_extra
+#endif
+
+#ifdef yyset_extra
+#define cmCTestProcesses_yyset_extra_ALREADY_DEFINED
+#else
+#define yyset_extra cmCTestProcesses_yyset_extra
+#endif
+
+#ifdef yyget_in
+#define cmCTestProcesses_yyget_in_ALREADY_DEFINED
+#else
+#define yyget_in cmCTestProcesses_yyget_in
+#endif
+
+#ifdef yyset_in
+#define cmCTestProcesses_yyset_in_ALREADY_DEFINED
+#else
+#define yyset_in cmCTestProcesses_yyset_in
+#endif
+
+#ifdef yyget_out
+#define cmCTestProcesses_yyget_out_ALREADY_DEFINED
+#else
+#define yyget_out cmCTestProcesses_yyget_out
+#endif
+
+#ifdef yyset_out
+#define cmCTestProcesses_yyset_out_ALREADY_DEFINED
+#else
+#define yyset_out cmCTestProcesses_yyset_out
+#endif
+
+#ifdef yyget_leng
+#define cmCTestProcesses_yyget_leng_ALREADY_DEFINED
+#else
+#define yyget_leng cmCTestProcesses_yyget_leng
+#endif
+
+#ifdef yyget_text
+#define cmCTestProcesses_yyget_text_ALREADY_DEFINED
+#else
+#define yyget_text cmCTestProcesses_yyget_text
+#endif
+
+#ifdef yyget_lineno
+#define cmCTestProcesses_yyget_lineno_ALREADY_DEFINED
+#else
+#define yyget_lineno cmCTestProcesses_yyget_lineno
+#endif
+
+#ifdef yyset_lineno
+#define cmCTestProcesses_yyset_lineno_ALREADY_DEFINED
+#else
+#define yyset_lineno cmCTestProcesses_yyset_lineno
+#endif
+
+#ifdef yyget_column
+#define cmCTestProcesses_yyget_column_ALREADY_DEFINED
+#else
+#define yyget_column cmCTestProcesses_yyget_column
+#endif
+
+#ifdef yyset_column
+#define cmCTestProcesses_yyset_column_ALREADY_DEFINED
+#else
+#define yyset_column cmCTestProcesses_yyset_column
+#endif
+
+#ifdef yywrap
+#define cmCTestProcesses_yywrap_ALREADY_DEFINED
+#else
+#define yywrap cmCTestProcesses_yywrap
+#endif
+
+#ifdef yyalloc
+#define cmCTestProcesses_yyalloc_ALREADY_DEFINED
+#else
+#define yyalloc cmCTestProcesses_yyalloc
+#endif
+
+#ifdef yyrealloc
+#define cmCTestProcesses_yyrealloc_ALREADY_DEFINED
+#else
+#define yyrealloc cmCTestProcesses_yyrealloc
+#endif
+
+#ifdef yyfree
+#define cmCTestProcesses_yyfree_ALREADY_DEFINED
+#else
+#define yyfree cmCTestProcesses_yyfree
+#endif
+
+/* First, we deal with  platform-specific or compiler-specific issues. */
+
+/* begin standard C headers. */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+/* end standard C headers. */
+
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */
+
+#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+
+/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h,
+ * if you want the limit (max/min) macros for int types.
+ */
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS 1
+#endif
+
+#include <inttypes.h>
+typedef int8_t flex_int8_t;
+typedef uint8_t flex_uint8_t;
+typedef int16_t flex_int16_t;
+typedef uint16_t flex_uint16_t;
+typedef int32_t flex_int32_t;
+typedef uint32_t flex_uint32_t;
+#else
+typedef signed char flex_int8_t;
+typedef short int flex_int16_t;
+typedef int flex_int32_t;
+typedef unsigned char flex_uint8_t;
+typedef unsigned short int flex_uint16_t;
+typedef unsigned int flex_uint32_t;
+
+/* Limits of integral types. */
+#ifndef INT8_MIN
+#define INT8_MIN               (-128)
+#endif
+#ifndef INT16_MIN
+#define INT16_MIN              (-32767-1)
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN              (-2147483647-1)
+#endif
+#ifndef INT8_MAX
+#define INT8_MAX               (127)
+#endif
+#ifndef INT16_MAX
+#define INT16_MAX              (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX              (2147483647)
+#endif
+#ifndef UINT8_MAX
+#define UINT8_MAX              (255U)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX             (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX             (4294967295U)
+#endif
+
+#ifndef SIZE_MAX
+#define SIZE_MAX               (~(size_t)0)
+#endif
+
+#endif /* ! C99 */
+
+#endif /* ! FLEXINT_H */
+
+/* begin standard C++ headers. */
+
+/* TODO: this is always defined, so inline it */
+#define yyconst const
+
+#if defined(__GNUC__) && __GNUC__ >= 3
+#define yynoreturn __attribute__((__noreturn__))
+#else
+#define yynoreturn
+#endif
+
+/* Returned upon end-of-file. */
+#define YY_NULL 0
+
+/* Promotes a possibly negative, possibly signed char to an
+ *   integer in range [0..255] for use as an array index.
+ */
+#define YY_SC_TO_UI(c) ((YY_CHAR) (c))
+
+/* An opaque pointer. */
+#ifndef YY_TYPEDEF_YY_SCANNER_T
+#define YY_TYPEDEF_YY_SCANNER_T
+typedef void* yyscan_t;
+#endif
+
+/* For convenience, these vars (plus the bison vars far below)
+   are macros in the reentrant scanner. */
+#define yyin yyg->yyin_r
+#define yyout yyg->yyout_r
+#define yyextra yyg->yyextra_r
+#define yyleng yyg->yyleng_r
+#define yytext yyg->yytext_r
+#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno)
+#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column)
+#define yy_flex_debug yyg->yy_flex_debug_r
+
+/* Enter a start condition.  This macro really ought to take a parameter,
+ * but we do it the disgusting crufty way forced on us by the ()-less
+ * definition of BEGIN.
+ */
+#define BEGIN yyg->yy_start = 1 + 2 *
+/* Translate the current start state into a value that can be later handed
+ * to BEGIN to return to the state.  The YYSTATE alias is for lex
+ * compatibility.
+ */
+#define YY_START ((yyg->yy_start - 1) / 2)
+#define YYSTATE YY_START
+/* Action number for EOF rule of a given start state. */
+#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1)
+/* Special action meaning "start processing a new file". */
+#define YY_NEW_FILE yyrestart( yyin , yyscanner )
+#define YY_END_OF_BUFFER_CHAR 0
+
+/* Size of default input buffer. */
+#ifndef YY_BUF_SIZE
+#ifdef __ia64__
+/* On IA-64, the buffer size is 16k, not 8k.
+ * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case.
+ * Ditto for the __ia64__ case accordingly.
+ */
+#define YY_BUF_SIZE 32768
+#else
+#define YY_BUF_SIZE 16384
+#endif /* __ia64__ */
+#endif
+
+/* The state buf must be large enough to hold one state per character in the main buffer.
+ */
+#define YY_STATE_BUF_SIZE   ((YY_BUF_SIZE + 2) * sizeof(yy_state_type))
+
+#ifndef YY_TYPEDEF_YY_BUFFER_STATE
+#define YY_TYPEDEF_YY_BUFFER_STATE
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+#endif
+
+#ifndef YY_TYPEDEF_YY_SIZE_T
+#define YY_TYPEDEF_YY_SIZE_T
+typedef size_t yy_size_t;
+#endif
+
+#define EOB_ACT_CONTINUE_SCAN 0
+#define EOB_ACT_END_OF_FILE 1
+#define EOB_ACT_LAST_MATCH 2
+
+    #define YY_LESS_LINENO(n)
+    #define YY_LINENO_REWIND_TO(ptr)
+
+/* Return all but the first "n" matched characters back to the input stream. */
+#define yyless(n) \
+	do \
+		{ \
+		/* Undo effects of setting up yytext. */ \
+        int yyless_macro_arg = (n); \
+        YY_LESS_LINENO(yyless_macro_arg);\
+		*yy_cp = yyg->yy_hold_char; \
+		YY_RESTORE_YY_MORE_OFFSET \
+		yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \
+		YY_DO_BEFORE_ACTION; /* set up yytext again */ \
+		} \
+	while ( 0 )
+#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner )
+
+#ifndef YY_STRUCT_YY_BUFFER_STATE
+#define YY_STRUCT_YY_BUFFER_STATE
+struct yy_buffer_state
+	{
+	FILE *yy_input_file;
+
+	char *yy_ch_buf;		/* input buffer */
+	char *yy_buf_pos;		/* current position in input buffer */
+
+	/* Size of input buffer in bytes, not including room for EOB
+	 * characters.
+	 */
+	int yy_buf_size;
+
+	/* Number of characters read into yy_ch_buf, not including EOB
+	 * characters.
+	 */
+	int yy_n_chars;
+
+	/* Whether we "own" the buffer - i.e., we know we created it,
+	 * and can realloc() it to grow it, and should free() it to
+	 * delete it.
+	 */
+	int yy_is_our_buffer;
+
+	/* Whether this is an "interactive" input source; if so, and
+	 * if we're using stdio for input, then we want to use getc()
+	 * instead of fread(), to make sure we stop fetching input after
+	 * each newline.
+	 */
+	int yy_is_interactive;
+
+	/* Whether we're considered to be at the beginning of a line.
+	 * If so, '^' rules will be active on the next match, otherwise
+	 * not.
+	 */
+	int yy_at_bol;
+
+    int yy_bs_lineno; /**< The line count. */
+    int yy_bs_column; /**< The column count. */
+
+	/* Whether to try to fill the input buffer when we reach the
+	 * end of it.
+	 */
+	int yy_fill_buffer;
+
+	int yy_buffer_status;
+
+#define YY_BUFFER_NEW 0
+#define YY_BUFFER_NORMAL 1
+	/* When an EOF's been seen but there's still some text to process
+	 * then we mark the buffer as YY_EOF_PENDING, to indicate that we
+	 * shouldn't try reading from the input source any more.  We might
+	 * still have a bunch of tokens to match, though, because of
+	 * possible backing-up.
+	 *
+	 * When we actually see the EOF, we change the status to "new"
+	 * (via yyrestart()), so that the user can continue scanning by
+	 * just pointing yyin at a new input file.
+	 */
+#define YY_BUFFER_EOF_PENDING 2
+
+	};
+#endif /* !YY_STRUCT_YY_BUFFER_STATE */
+
+/* We provide macros for accessing buffer states in case in the
+ * future we want to put the buffer states in a more general
+ * "scanner state".
+ *
+ * Returns the top of the stack, or NULL.
+ */
+#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \
+                          ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \
+                          : NULL)
+/* Same as previous macro, but useful when we know that the buffer stack is not
+ * NULL or when we need an lvalue. For internal use only.
+ */
+#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top]
+
+void yyrestart ( FILE *input_file , yyscan_t yyscanner );
+void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner );
+YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner );
+void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner );
+void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner );
+void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner );
+void yypop_buffer_state ( yyscan_t yyscanner );
+
+static void yyensure_buffer_stack ( yyscan_t yyscanner );
+static void yy_load_buffer_state ( yyscan_t yyscanner );
+static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file , yyscan_t yyscanner );
+#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER , yyscanner)
+
+YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner );
+YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner );
+YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len , yyscan_t yyscanner );
+
+void *yyalloc ( yy_size_t , yyscan_t yyscanner );
+void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner );
+void yyfree ( void * , yyscan_t yyscanner );
+
+#define yy_new_buffer yy_create_buffer
+#define yy_set_interactive(is_interactive) \
+	{ \
+	if ( ! YY_CURRENT_BUFFER ){ \
+        yyensure_buffer_stack (yyscanner); \
+		YY_CURRENT_BUFFER_LVALUE =    \
+            yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \
+	} \
+	YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \
+	}
+#define yy_set_bol(at_bol) \
+	{ \
+	if ( ! YY_CURRENT_BUFFER ){\
+        yyensure_buffer_stack (yyscanner); \
+		YY_CURRENT_BUFFER_LVALUE =    \
+            yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \
+	} \
+	YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \
+	}
+#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol)
+
+/* Begin user sect3 */
+
+#define cmCTestProcesses_yywrap(yyscanner) (/*CONSTCOND*/1)
+#define YY_SKIP_YYWRAP
+typedef flex_uint8_t YY_CHAR;
+
+typedef int yy_state_type;
+
+#define yytext_ptr yytext_r
+
+static yy_state_type yy_get_previous_state ( yyscan_t yyscanner );
+static yy_state_type yy_try_NUL_trans ( yy_state_type current_state  , yyscan_t yyscanner);
+static int yy_get_next_buffer ( yyscan_t yyscanner );
+static void yynoreturn yy_fatal_error ( const char* msg , yyscan_t yyscanner );
+
+/* Done after the current pattern has been matched and before the
+ * corresponding action - sets up yytext.
+ */
+#define YY_DO_BEFORE_ACTION \
+	yyg->yytext_ptr = yy_bp; \
+	yyleng = (int) (yy_cp - yy_bp); \
+	yyg->yy_hold_char = *yy_cp; \
+	*yy_cp = '\0'; \
+	yyg->yy_c_buf_p = yy_cp;
+#define YY_NUM_RULES 8
+#define YY_END_OF_BUFFER 9
+/* This struct is not used in this scanner,
+   but its presence is necessary. */
+struct yy_trans_info
+	{
+	flex_int32_t yy_verify;
+	flex_int32_t yy_nxt;
+	};
+static const flex_int16_t yy_accept[29] =
+    {   0,
+        0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+        0,    0,    9,    7,    2,    5,    7,    4,    6,    3,
+        2,    5,    0,    1,    4,    6,    3,    0
+    } ;
+
+static const YY_CHAR yy_ec[256] =
+    {   0,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    2,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    3,    1,    1,    1,    4,    4,    4,
+        4,    4,    4,    4,    4,    4,    4,    5,    6,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    7,    1,    7,    7,    7,    7,
+
+        7,    7,    7,    7,    7,    7,    7,    7,    7,    7,
+        7,    7,    7,    7,    7,    7,    7,    7,    7,    7,
+        7,    7,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1
+    } ;
+
+static const YY_CHAR yy_meta[8] =
+    {   0,
+        1,    1,    1,    2,    2,    1,    2
+    } ;
+
+static const flex_int16_t yy_base[30] =
+    {   0,
+        0,    0,    0,    0,    7,    0,   31,   12,   17,    0,
+        0,    0,   34,   36,   29,   26,   26,   27,   23,   24,
+       23,   20,   20,   36,   21,   16,   13,   36,   14
+    } ;
+
+static const flex_int16_t yy_def[30] =
+    {   0,
+       28,    1,    1,    1,   28,    5,    1,    5,    5,    9,
+        5,    5,   28,   28,   28,   28,   29,   28,   28,   28,
+       28,   28,   29,   28,   28,   28,   28,    0,   28
+    } ;
+
+static const flex_int16_t yy_nxt[44] =
+    {   0,
+       14,   14,   14,   15,   14,   16,   17,   14,   14,   18,
+       14,   14,   19,   14,   14,   23,   27,   16,   17,   14,
+       20,   26,   14,   25,   24,   22,   21,   27,   26,   25,
+       24,   22,   21,   28,   14,   13,   28,   28,   28,   28,
+       28,   28,   28
+    } ;
+
+static const flex_int16_t yy_chk[44] =
+    {   0,
+        1,    1,    1,    1,    1,    1,    1,    5,    5,    5,
+        5,    5,    5,    5,    8,   29,   27,    8,    8,    9,
+        9,   26,    9,   25,   23,   22,   21,   20,   19,   18,
+       17,   16,   15,   13,    7,   28,   28,   28,   28,   28,
+       28,   28,   28
+    } ;
+
+/* The intent behind this definition is that it'll catch
+ * any uses of REJECT which flex missed.
+ */
+#define REJECT reject_used_but_not_detected
+#define yymore() yymore_used_but_not_detected
+#define YY_MORE_ADJ 0
+#define YY_RESTORE_YY_MORE_OFFSET
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+/*
+
+This file must be translated to C++ and modified to build everywhere.
+
+Run flex >= 2.6 like this:
+
+  flex --nounistd -DFLEXINT_H --noline --header-file=cmCTestProcessesLexer.h -ocmCTestProcessesLexer.cxx cmCTestProcessesLexer.in.l
+
+Modify cmCTestProcessesLexer.cxx:
+  - remove trailing whitespace:              sed -i 's/\s*$//' cmCTestProcessesLexer.h cmCTestProcessesLexer.cxx
+  - remove blank lines at end of file:       sed -i '${/^$/d;}' cmCTestProcessesLexer.h cmCTestProcessesLexer.cxx
+  - #include "cmStandardLexer.h" at the top: sed -i '1i#include "cmStandardLexer.h"' cmCTestProcessesLexer.cxx
+
+*/
+
+/* IWYU pragma: no_forward_declare yyguts_t */
+
+#ifndef __clang_analyzer__ /* Suppress clang scan-build warnings */
+
+#include "cmCTestProcessesLexerHelper.h"
+
+#include <string>
+
+#include <cstddef>
+
+/*--------------------------------------------------------------------------*/
+
+#define INITIAL 0
+#define PROCESSES_START 1
+#define PROCESSES_END 2
+#define RESOURCE_START 3
+#define RESOURCE_COUNT 4
+#define RESOURCE_END 5
+
+#ifndef YY_EXTRA_TYPE
+#define YY_EXTRA_TYPE void *
+#endif
+
+/* Holds the entire state of the reentrant scanner. */
+struct yyguts_t
+    {
+
+    /* User-defined. Not touched by flex. */
+    YY_EXTRA_TYPE yyextra_r;
+
+    /* The rest are the same as the globals declared in the non-reentrant scanner. */
+    FILE *yyin_r, *yyout_r;
+    size_t yy_buffer_stack_top; /**< index of top of stack. */
+    size_t yy_buffer_stack_max; /**< capacity of stack. */
+    YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */
+    char yy_hold_char;
+    int yy_n_chars;
+    int yyleng_r;
+    char *yy_c_buf_p;
+    int yy_init;
+    int yy_start;
+    int yy_did_buffer_switch_on_eof;
+    int yy_start_stack_ptr;
+    int yy_start_stack_depth;
+    int *yy_start_stack;
+    yy_state_type yy_last_accepting_state;
+    char* yy_last_accepting_cpos;
+
+    int yylineno_r;
+    int yy_flex_debug_r;
+
+    char *yytext_r;
+    int yy_more_flag;
+    int yy_more_len;
+
+    }; /* end struct yyguts_t */
+
+static int yy_init_globals ( yyscan_t yyscanner );
+
+int yylex_init (yyscan_t* scanner);
+
+int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner);
+
+/* Accessor methods to globals.
+   These are made visible to non-reentrant scanners for convenience. */
+
+int yylex_destroy ( yyscan_t yyscanner );
+
+int yyget_debug ( yyscan_t yyscanner );
+
+void yyset_debug ( int debug_flag , yyscan_t yyscanner );
+
+YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner );
+
+void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner );
+
+FILE *yyget_in ( yyscan_t yyscanner );
+
+void yyset_in  ( FILE * _in_str , yyscan_t yyscanner );
+
+FILE *yyget_out ( yyscan_t yyscanner );
+
+void yyset_out  ( FILE * _out_str , yyscan_t yyscanner );
+
+			int yyget_leng ( yyscan_t yyscanner );
+
+char *yyget_text ( yyscan_t yyscanner );
+
+int yyget_lineno ( yyscan_t yyscanner );
+
+void yyset_lineno ( int _line_number , yyscan_t yyscanner );
+
+int yyget_column  ( yyscan_t yyscanner );
+
+void yyset_column ( int _column_no , yyscan_t yyscanner );
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int yywrap ( yyscan_t yyscanner );
+#else
+extern int yywrap ( yyscan_t yyscanner );
+#endif
+#endif
+
+#ifndef YY_NO_UNPUT
+
+    static void yyunput ( int c, char *buf_ptr  , yyscan_t yyscanner);
+
+#endif
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner);
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen ( const char * , yyscan_t yyscanner);
+#endif
+
+#ifndef YY_NO_INPUT
+#ifdef __cplusplus
+static int yyinput ( yyscan_t yyscanner );
+#else
+static int input ( yyscan_t yyscanner );
+#endif
+
+#endif
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#ifdef __ia64__
+/* On IA-64, the buffer size is 16k, not 8k */
+#define YY_READ_BUF_SIZE 16384
+#else
+#define YY_READ_BUF_SIZE 8192
+#endif /* __ia64__ */
+#endif
+
+/* Copy whatever the last rule matched to the standard output. */
+#ifndef ECHO
+/* This used to be an fputs(), but since the string might contain NUL's,
+ * we now use fwrite().
+ */
+#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0)
+#endif
+
+/* Gets input and stuffs it into "buf".  number of characters read, or YY_NULL,
+ * is returned in "result".
+ */
+#ifndef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+	if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \
+		{ \
+		int c = '*'; \
+		int n; \
+		for ( n = 0; n < max_size && \
+			     (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
+			buf[n] = (char) c; \
+		if ( c == '\n' ) \
+			buf[n++] = (char) c; \
+		if ( c == EOF && ferror( yyin ) ) \
+			YY_FATAL_ERROR( "input in flex scanner failed" ); \
+		result = n; \
+		} \
+	else \
+		{ \
+		errno=0; \
+		while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \
+			{ \
+			if( errno != EINTR) \
+				{ \
+				YY_FATAL_ERROR( "input in flex scanner failed" ); \
+				break; \
+				} \
+			errno=0; \
+			clearerr(yyin); \
+			} \
+		}\
+\
+
+#endif
+
+/* No semi-colon after return; correct usage is to write "yyterminate();" -
+ * we don't want an extra ';' after the "return" because that will cause
+ * some compilers to complain about unreachable statements.
+ */
+#ifndef yyterminate
+#define yyterminate() return YY_NULL
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Report a fatal error. */
+#ifndef YY_FATAL_ERROR
+#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner)
+#endif
+
+/* end tables serialization structures and prototypes */
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL_IS_OURS 1
+
+extern int yylex (yyscan_t yyscanner);
+
+#define YY_DECL int yylex (yyscan_t yyscanner)
+#endif /* !YY_DECL */
+
+/* Code executed at the beginning of each rule, after yytext and yyleng
+ * have been set up.
+ */
+#ifndef YY_USER_ACTION
+#define YY_USER_ACTION
+#endif
+
+/* Code executed at the end of each rule. */
+#ifndef YY_BREAK
+#define YY_BREAK /*LINTED*/break;
+#endif
+
+#define YY_RULE_SETUP \
+	YY_USER_ACTION
+
+/** The main scanner function which does all the work.
+ */
+YY_DECL
+{
+	yy_state_type yy_current_state;
+	char *yy_cp, *yy_bp;
+	int yy_act;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	if ( !yyg->yy_init )
+		{
+		yyg->yy_init = 1;
+
+#ifdef YY_USER_INIT
+		YY_USER_INIT;
+#endif
+
+		if ( ! yyg->yy_start )
+			yyg->yy_start = 1;	/* first start state */
+
+		if ( ! yyin )
+			yyin = stdin;
+
+		if ( ! yyout )
+			yyout = stdout;
+
+		if ( ! YY_CURRENT_BUFFER ) {
+			yyensure_buffer_stack (yyscanner);
+			YY_CURRENT_BUFFER_LVALUE =
+				yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner);
+		}
+
+		yy_load_buffer_state( yyscanner );
+		}
+
+	{
+
+	while ( /*CONSTCOND*/1 )		/* loops until end-of-file is reached */
+		{
+		yy_cp = yyg->yy_c_buf_p;
+
+		/* Support of yytext. */
+		*yy_cp = yyg->yy_hold_char;
+
+		/* yy_bp points to the position in yy_ch_buf of the start of
+		 * the current run.
+		 */
+		yy_bp = yy_cp;
+
+		yy_current_state = yyg->yy_start;
+yy_match:
+		do
+			{
+			YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ;
+			if ( yy_accept[yy_current_state] )
+				{
+				yyg->yy_last_accepting_state = yy_current_state;
+				yyg->yy_last_accepting_cpos = yy_cp;
+				}
+			while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+				{
+				yy_current_state = (int) yy_def[yy_current_state];
+				if ( yy_current_state >= 29 )
+					yy_c = yy_meta[yy_c];
+				}
+			yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
+			++yy_cp;
+			}
+		while ( yy_base[yy_current_state] != 36 );
+
+yy_find_action:
+		yy_act = yy_accept[yy_current_state];
+		if ( yy_act == 0 )
+			{ /* have to back up */
+			yy_cp = yyg->yy_last_accepting_cpos;
+			yy_current_state = yyg->yy_last_accepting_state;
+			yy_act = yy_accept[yy_current_state];
+			}
+
+		YY_DO_BEFORE_ACTION;
+
+do_action:	/* This label is used only to access EOF actions. */
+
+		switch ( yy_act )
+	{ /* beginning of action switch */
+			case 0: /* must back up */
+			/* undo the effects of YY_DO_BEFORE_ACTION */
+			*yy_cp = yyg->yy_hold_char;
+			yy_cp = yyg->yy_last_accepting_cpos;
+			yy_current_state = yyg->yy_last_accepting_state;
+			goto yy_find_action;
+
+case 1:
+YY_RULE_SETUP
+{
+  BEGIN(RESOURCE_COUNT);
+  yyextra->SetResourceType(std::string(yytext, yyleng - 1));
+}
+	YY_BREAK
+case 2:
+YY_RULE_SETUP
+{
+  BEGIN(PROCESSES_END);
+  std::size_t len = yyleng;
+  yyextra->SetProcessCount(std::stoll(yytext, &len, 10));
+}
+	YY_BREAK
+case 3:
+YY_RULE_SETUP
+{
+  BEGIN(RESOURCE_END);
+  std::size_t len = yyleng;
+  yyextra->SetNeededSlots(std::stoll(yytext, &len, 10));
+  yyextra->WriteRequirement();
+}
+	YY_BREAK
+case 4:
+YY_RULE_SETUP
+{
+  BEGIN(RESOURCE_START);
+}
+	YY_BREAK
+case 5:
+YY_RULE_SETUP
+{
+  BEGIN(PROCESSES_START);
+}
+	YY_BREAK
+case 6:
+YY_RULE_SETUP
+{
+  BEGIN(PROCESSES_START);
+  yyextra->WriteProcess();
+}
+	YY_BREAK
+case YY_STATE_EOF(RESOURCE_START):
+case YY_STATE_EOF(PROCESSES_END):
+case YY_STATE_EOF(RESOURCE_END):
+{
+  yyextra->WriteProcess();
+  return 0;
+}
+	YY_BREAK
+case YY_STATE_EOF(INITIAL):
+case YY_STATE_EOF(PROCESSES_START):
+{
+  return 0;
+}
+	YY_BREAK
+case YY_STATE_EOF(RESOURCE_COUNT):
+{
+  return 1;
+}
+	YY_BREAK
+case 7:
+/* rule 7 can match eol */
+YY_RULE_SETUP
+{
+  return 1;
+}
+	YY_BREAK
+case 8:
+YY_RULE_SETUP
+YY_FATAL_ERROR( "flex scanner jammed" );
+	YY_BREAK
+
+	case YY_END_OF_BUFFER:
+		{
+		/* Amount of text matched not including the EOB char. */
+		int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1;
+
+		/* Undo the effects of YY_DO_BEFORE_ACTION. */
+		*yy_cp = yyg->yy_hold_char;
+		YY_RESTORE_YY_MORE_OFFSET
+
+		if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW )
+			{
+			/* We're scanning a new file or input source.  It's
+			 * possible that this happened because the user
+			 * just pointed yyin at a new source and called
+			 * yylex().  If so, then we have to assure
+			 * consistency between YY_CURRENT_BUFFER and our
+			 * globals.  Here is the right place to do so, because
+			 * this is the first action (other than possibly a
+			 * back-up) that will match for the new input source.
+			 */
+			yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+			YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin;
+			YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL;
+			}
+
+		/* Note that here we test for yy_c_buf_p "<=" to the position
+		 * of the first EOB in the buffer, since yy_c_buf_p will
+		 * already have been incremented past the NUL character
+		 * (since all states make transitions on EOB to the
+		 * end-of-buffer state).  Contrast this with the test
+		 * in input().
+		 */
+		if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] )
+			{ /* This was really a NUL. */
+			yy_state_type yy_next_state;
+
+			yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text;
+
+			yy_current_state = yy_get_previous_state( yyscanner );
+
+			/* Okay, we're now positioned to make the NUL
+			 * transition.  We couldn't have
+			 * yy_get_previous_state() go ahead and do it
+			 * for us because it doesn't know how to deal
+			 * with the possibility of jamming (and we don't
+			 * want to build jamming into it because then it
+			 * will run more slowly).
+			 */
+
+			yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner);
+
+			yy_bp = yyg->yytext_ptr + YY_MORE_ADJ;
+
+			if ( yy_next_state )
+				{
+				/* Consume the NUL. */
+				yy_cp = ++yyg->yy_c_buf_p;
+				yy_current_state = yy_next_state;
+				goto yy_match;
+				}
+
+			else
+				{
+				yy_cp = yyg->yy_c_buf_p;
+				goto yy_find_action;
+				}
+			}
+
+		else switch ( yy_get_next_buffer( yyscanner ) )
+			{
+			case EOB_ACT_END_OF_FILE:
+				{
+				yyg->yy_did_buffer_switch_on_eof = 0;
+
+				if ( yywrap( yyscanner ) )
+					{
+					/* Note: because we've taken care in
+					 * yy_get_next_buffer() to have set up
+					 * yytext, we can now set up
+					 * yy_c_buf_p so that if some total
+					 * hoser (like flex itself) wants to
+					 * call the scanner after we return the
+					 * YY_NULL, it'll still work - another
+					 * YY_NULL will get returned.
+					 */
+					yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ;
+
+					yy_act = YY_STATE_EOF(YY_START);
+					goto do_action;
+					}
+
+				else
+					{
+					if ( ! yyg->yy_did_buffer_switch_on_eof )
+						YY_NEW_FILE;
+					}
+				break;
+				}
+
+			case EOB_ACT_CONTINUE_SCAN:
+				yyg->yy_c_buf_p =
+					yyg->yytext_ptr + yy_amount_of_matched_text;
+
+				yy_current_state = yy_get_previous_state( yyscanner );
+
+				yy_cp = yyg->yy_c_buf_p;
+				yy_bp = yyg->yytext_ptr + YY_MORE_ADJ;
+				goto yy_match;
+
+			case EOB_ACT_LAST_MATCH:
+				yyg->yy_c_buf_p =
+				&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars];
+
+				yy_current_state = yy_get_previous_state( yyscanner );
+
+				yy_cp = yyg->yy_c_buf_p;
+				yy_bp = yyg->yytext_ptr + YY_MORE_ADJ;
+				goto yy_find_action;
+			}
+		break;
+		}
+
+	default:
+		YY_FATAL_ERROR(
+			"fatal flex scanner internal error--no action found" );
+	} /* end of action switch */
+		} /* end of scanning one token */
+	} /* end of user's declarations */
+} /* end of yylex */
+
+/* yy_get_next_buffer - try to read in a new buffer
+ *
+ * Returns a code representing an action:
+ *	EOB_ACT_LAST_MATCH -
+ *	EOB_ACT_CONTINUE_SCAN - continue scanning from current position
+ *	EOB_ACT_END_OF_FILE - end of file
+ */
+static int yy_get_next_buffer (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf;
+	char *source = yyg->yytext_ptr;
+	int number_to_move, i;
+	int ret_val;
+
+	if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] )
+		YY_FATAL_ERROR(
+		"fatal flex scanner internal error--end of buffer missed" );
+
+	if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 )
+		{ /* Don't try to fill the buffer, so this is an EOF. */
+		if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 )
+			{
+			/* We matched a single character, the EOB, so
+			 * treat this as a final EOF.
+			 */
+			return EOB_ACT_END_OF_FILE;
+			}
+
+		else
+			{
+			/* We matched some text prior to the EOB, first
+			 * process it.
+			 */
+			return EOB_ACT_LAST_MATCH;
+			}
+		}
+
+	/* Try to read more data. */
+
+	/* First move last chars to start of buffer. */
+	number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr - 1);
+
+	for ( i = 0; i < number_to_move; ++i )
+		*(dest++) = *(source++);
+
+	if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING )
+		/* don't do the read, it's not guaranteed to return an EOF,
+		 * just force an EOF
+		 */
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0;
+
+	else
+		{
+			int num_to_read =
+			YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1;
+
+		while ( num_to_read <= 0 )
+			{ /* Not enough room in the buffer - grow it. */
+
+			/* just a shorter name for the current buffer */
+			YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE;
+
+			int yy_c_buf_p_offset =
+				(int) (yyg->yy_c_buf_p - b->yy_ch_buf);
+
+			if ( b->yy_is_our_buffer )
+				{
+				int new_size = b->yy_buf_size * 2;
+
+				if ( new_size <= 0 )
+					b->yy_buf_size += b->yy_buf_size / 8;
+				else
+					b->yy_buf_size *= 2;
+
+				b->yy_ch_buf = (char *)
+					/* Include room in for 2 EOB chars. */
+					yyrealloc( (void *) b->yy_ch_buf,
+							 (yy_size_t) (b->yy_buf_size + 2) , yyscanner );
+				}
+			else
+				/* Can't grow it, we don't own it. */
+				b->yy_ch_buf = NULL;
+
+			if ( ! b->yy_ch_buf )
+				YY_FATAL_ERROR(
+				"fatal error - scanner input buffer overflow" );
+
+			yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset];
+
+			num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size -
+						number_to_move - 1;
+
+			}
+
+		if ( num_to_read > YY_READ_BUF_SIZE )
+			num_to_read = YY_READ_BUF_SIZE;
+
+		/* Read in more data. */
+		YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]),
+			yyg->yy_n_chars, num_to_read );
+
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars;
+		}
+
+	if ( yyg->yy_n_chars == 0 )
+		{
+		if ( number_to_move == YY_MORE_ADJ )
+			{
+			ret_val = EOB_ACT_END_OF_FILE;
+			yyrestart( yyin  , yyscanner);
+			}
+
+		else
+			{
+			ret_val = EOB_ACT_LAST_MATCH;
+			YY_CURRENT_BUFFER_LVALUE->yy_buffer_status =
+				YY_BUFFER_EOF_PENDING;
+			}
+		}
+
+	else
+		ret_val = EOB_ACT_CONTINUE_SCAN;
+
+	if ((yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) {
+		/* Extend the array by 50%, plus the number we really need. */
+		int new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1);
+		YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc(
+			(void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size , yyscanner );
+		if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf )
+			YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" );
+		/* "- 2" to take care of EOB's */
+		YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2);
+	}
+
+	yyg->yy_n_chars += number_to_move;
+	YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR;
+	YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR;
+
+	yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0];
+
+	return ret_val;
+}
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+    static yy_state_type yy_get_previous_state (yyscan_t yyscanner)
+{
+	yy_state_type yy_current_state;
+	char *yy_cp;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	yy_current_state = yyg->yy_start;
+
+	for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp )
+		{
+		YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1);
+		if ( yy_accept[yy_current_state] )
+			{
+			yyg->yy_last_accepting_state = yy_current_state;
+			yyg->yy_last_accepting_cpos = yy_cp;
+			}
+		while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+			{
+			yy_current_state = (int) yy_def[yy_current_state];
+			if ( yy_current_state >= 29 )
+				yy_c = yy_meta[yy_c];
+			}
+		yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
+		}
+
+	return yy_current_state;
+}
+
+/* yy_try_NUL_trans - try to make a transition on the NUL character
+ *
+ * synopsis
+ *	next_state = yy_try_NUL_trans( current_state );
+ */
+    static yy_state_type yy_try_NUL_trans  (yy_state_type yy_current_state , yyscan_t yyscanner)
+{
+	int yy_is_jam;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */
+	char *yy_cp = yyg->yy_c_buf_p;
+
+	YY_CHAR yy_c = 1;
+	if ( yy_accept[yy_current_state] )
+		{
+		yyg->yy_last_accepting_state = yy_current_state;
+		yyg->yy_last_accepting_cpos = yy_cp;
+		}
+	while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+		{
+		yy_current_state = (int) yy_def[yy_current_state];
+		if ( yy_current_state >= 29 )
+			yy_c = yy_meta[yy_c];
+		}
+	yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
+	yy_is_jam = (yy_current_state == 28);
+
+	(void)yyg;
+	return yy_is_jam ? 0 : yy_current_state;
+}
+
+#ifndef YY_NO_UNPUT
+
+    static void yyunput (int c, char * yy_bp , yyscan_t yyscanner)
+{
+	char *yy_cp;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+    yy_cp = yyg->yy_c_buf_p;
+
+	/* undo effects of setting up yytext */
+	*yy_cp = yyg->yy_hold_char;
+
+	if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 )
+		{ /* need to shift things up to make room */
+		/* +2 for EOB chars. */
+		int number_to_move = yyg->yy_n_chars + 2;
+		char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[
+					YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2];
+		char *source =
+				&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move];
+
+		while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf )
+			*--dest = *--source;
+
+		yy_cp += (int) (dest - source);
+		yy_bp += (int) (dest - source);
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars =
+			yyg->yy_n_chars = (int) YY_CURRENT_BUFFER_LVALUE->yy_buf_size;
+
+		if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 )
+			YY_FATAL_ERROR( "flex scanner push-back overflow" );
+		}
+
+	*--yy_cp = (char) c;
+
+	yyg->yytext_ptr = yy_bp;
+	yyg->yy_hold_char = *yy_cp;
+	yyg->yy_c_buf_p = yy_cp;
+}
+
+#endif
+
+#ifndef YY_NO_INPUT
+#ifdef __cplusplus
+    static int yyinput (yyscan_t yyscanner)
+#else
+    static int input  (yyscan_t yyscanner)
+#endif
+
+{
+	int c;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	*yyg->yy_c_buf_p = yyg->yy_hold_char;
+
+	if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR )
+		{
+		/* yy_c_buf_p now points to the character we want to return.
+		 * If this occurs *before* the EOB characters, then it's a
+		 * valid NUL; if not, then we've hit the end of the buffer.
+		 */
+		if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] )
+			/* This was really a NUL. */
+			*yyg->yy_c_buf_p = '\0';
+
+		else
+			{ /* need more input */
+			int offset = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr);
+			++yyg->yy_c_buf_p;
+
+			switch ( yy_get_next_buffer( yyscanner ) )
+				{
+				case EOB_ACT_LAST_MATCH:
+					/* This happens because yy_g_n_b()
+					 * sees that we've accumulated a
+					 * token and flags that we need to
+					 * try matching the token before
+					 * proceeding.  But for input(),
+					 * there's no matching to consider.
+					 * So convert the EOB_ACT_LAST_MATCH
+					 * to EOB_ACT_END_OF_FILE.
+					 */
+
+					/* Reset buffer status. */
+					yyrestart( yyin , yyscanner);
+
+					/*FALLTHROUGH*/
+
+				case EOB_ACT_END_OF_FILE:
+					{
+					if ( yywrap( yyscanner ) )
+						return 0;
+
+					if ( ! yyg->yy_did_buffer_switch_on_eof )
+						YY_NEW_FILE;
+#ifdef __cplusplus
+					return yyinput(yyscanner);
+#else
+					return input(yyscanner);
+#endif
+					}
+
+				case EOB_ACT_CONTINUE_SCAN:
+					yyg->yy_c_buf_p = yyg->yytext_ptr + offset;
+					break;
+				}
+			}
+		}
+
+	c = *(unsigned char *) yyg->yy_c_buf_p;	/* cast for 8-bit char's */
+	*yyg->yy_c_buf_p = '\0';	/* preserve yytext */
+	yyg->yy_hold_char = *++yyg->yy_c_buf_p;
+
+	return c;
+}
+#endif	/* ifndef YY_NO_INPUT */
+
+/** Immediately switch to a different input stream.
+ * @param input_file A readable stream.
+ * @param yyscanner The scanner object.
+ * @note This function does not reset the start condition to @c INITIAL .
+ */
+    void yyrestart  (FILE * input_file , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	if ( ! YY_CURRENT_BUFFER ){
+        yyensure_buffer_stack (yyscanner);
+		YY_CURRENT_BUFFER_LVALUE =
+            yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner);
+	}
+
+	yy_init_buffer( YY_CURRENT_BUFFER, input_file , yyscanner);
+	yy_load_buffer_state( yyscanner );
+}
+
+/** Switch to a different input buffer.
+ * @param new_buffer The new input buffer.
+ * @param yyscanner The scanner object.
+ */
+    void yy_switch_to_buffer  (YY_BUFFER_STATE  new_buffer , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	/* TODO. We should be able to replace this entire function body
+	 * with
+	 *		yypop_buffer_state();
+	 *		yypush_buffer_state(new_buffer);
+     */
+	yyensure_buffer_stack (yyscanner);
+	if ( YY_CURRENT_BUFFER == new_buffer )
+		return;
+
+	if ( YY_CURRENT_BUFFER )
+		{
+		/* Flush out information for old buffer. */
+		*yyg->yy_c_buf_p = yyg->yy_hold_char;
+		YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p;
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars;
+		}
+
+	YY_CURRENT_BUFFER_LVALUE = new_buffer;
+	yy_load_buffer_state( yyscanner );
+
+	/* We don't actually know whether we did this switch during
+	 * EOF (yywrap()) processing, but the only time this flag
+	 * is looked at is after yywrap() is called, so it's safe
+	 * to go ahead and always set it.
+	 */
+	yyg->yy_did_buffer_switch_on_eof = 1;
+}
+
+static void yy_load_buffer_state  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+	yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos;
+	yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file;
+	yyg->yy_hold_char = *yyg->yy_c_buf_p;
+}
+
+/** Allocate and initialize an input buffer state.
+ * @param file A readable stream.
+ * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE.
+ * @param yyscanner The scanner object.
+ * @return the allocated buffer state.
+ */
+    YY_BUFFER_STATE yy_create_buffer  (FILE * file, int  size , yyscan_t yyscanner)
+{
+	YY_BUFFER_STATE b;
+
+	b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner );
+	if ( ! b )
+		YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+	b->yy_buf_size = size;
+
+	/* yy_ch_buf has to be 2 characters longer than the size given because
+	 * we need to put in 2 end-of-buffer characters.
+	 */
+	b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) , yyscanner );
+	if ( ! b->yy_ch_buf )
+		YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+	b->yy_is_our_buffer = 1;
+
+	yy_init_buffer( b, file , yyscanner);
+
+	return b;
+}
+
+/** Destroy the buffer.
+ * @param b a buffer created with yy_create_buffer()
+ * @param yyscanner The scanner object.
+ */
+    void yy_delete_buffer (YY_BUFFER_STATE  b , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	if ( ! b )
+		return;
+
+	if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */
+		YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0;
+
+	if ( b->yy_is_our_buffer )
+		yyfree( (void *) b->yy_ch_buf , yyscanner );
+
+	yyfree( (void *) b , yyscanner );
+}
+
+/* Initializes or reinitializes a buffer.
+ * This function is sometimes called more than once on the same buffer,
+ * such as during a yyrestart() or at EOF.
+ */
+    static void yy_init_buffer  (YY_BUFFER_STATE  b, FILE * file , yyscan_t yyscanner)
+
+{
+	int oerrno = errno;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	yy_flush_buffer( b , yyscanner);
+
+	b->yy_input_file = file;
+	b->yy_fill_buffer = 1;
+
+    /* If b is the current buffer, then yy_init_buffer was _probably_
+     * called from yyrestart() or through yy_get_next_buffer.
+     * In that case, we don't want to reset the lineno or column.
+     */
+    if (b != YY_CURRENT_BUFFER){
+        b->yy_bs_lineno = 1;
+        b->yy_bs_column = 0;
+    }
+
+        b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0;
+
+	errno = oerrno;
+}
+
+/** Discard all buffered characters. On the next scan, YY_INPUT will be called.
+ * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER.
+ * @param yyscanner The scanner object.
+ */
+    void yy_flush_buffer (YY_BUFFER_STATE  b , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	if ( ! b )
+		return;
+
+	b->yy_n_chars = 0;
+
+	/* We always need two end-of-buffer characters.  The first causes
+	 * a transition to the end-of-buffer state.  The second causes
+	 * a jam in that state.
+	 */
+	b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR;
+	b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR;
+
+	b->yy_buf_pos = &b->yy_ch_buf[0];
+
+	b->yy_at_bol = 1;
+	b->yy_buffer_status = YY_BUFFER_NEW;
+
+	if ( b == YY_CURRENT_BUFFER )
+		yy_load_buffer_state( yyscanner );
+}
+
+/** Pushes the new state onto the stack. The new state becomes
+ *  the current state. This function will allocate the stack
+ *  if necessary.
+ *  @param new_buffer The new state.
+ *  @param yyscanner The scanner object.
+ */
+void yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	if (new_buffer == NULL)
+		return;
+
+	yyensure_buffer_stack(yyscanner);
+
+	/* This block is copied from yy_switch_to_buffer. */
+	if ( YY_CURRENT_BUFFER )
+		{
+		/* Flush out information for old buffer. */
+		*yyg->yy_c_buf_p = yyg->yy_hold_char;
+		YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p;
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars;
+		}
+
+	/* Only push if top exists. Otherwise, replace top. */
+	if (YY_CURRENT_BUFFER)
+		yyg->yy_buffer_stack_top++;
+	YY_CURRENT_BUFFER_LVALUE = new_buffer;
+
+	/* copied from yy_switch_to_buffer. */
+	yy_load_buffer_state( yyscanner );
+	yyg->yy_did_buffer_switch_on_eof = 1;
+}
+
+/** Removes and deletes the top of the stack, if present.
+ *  The next element becomes the new top.
+ *  @param yyscanner The scanner object.
+ */
+void yypop_buffer_state (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	if (!YY_CURRENT_BUFFER)
+		return;
+
+	yy_delete_buffer(YY_CURRENT_BUFFER , yyscanner);
+	YY_CURRENT_BUFFER_LVALUE = NULL;
+	if (yyg->yy_buffer_stack_top > 0)
+		--yyg->yy_buffer_stack_top;
+
+	if (YY_CURRENT_BUFFER) {
+		yy_load_buffer_state( yyscanner );
+		yyg->yy_did_buffer_switch_on_eof = 1;
+	}
+}
+
+/* Allocates the stack if it does not exist.
+ *  Guarantees space for at least one push.
+ */
+static void yyensure_buffer_stack (yyscan_t yyscanner)
+{
+	yy_size_t num_to_alloc;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	if (!yyg->yy_buffer_stack) {
+
+		/* First allocation is just for 2 elements, since we don't know if this
+		 * scanner will even need a stack. We use 2 instead of 1 to avoid an
+		 * immediate realloc on the next call.
+         */
+      num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */
+		yyg->yy_buffer_stack = (struct yy_buffer_state**)yyalloc
+								(num_to_alloc * sizeof(struct yy_buffer_state*)
+								, yyscanner);
+		if ( ! yyg->yy_buffer_stack )
+			YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" );
+
+		memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*));
+
+		yyg->yy_buffer_stack_max = num_to_alloc;
+		yyg->yy_buffer_stack_top = 0;
+		return;
+	}
+
+	if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){
+
+		/* Increase the buffer to prepare for a possible push. */
+		yy_size_t grow_size = 8 /* arbitrary grow size */;
+
+		num_to_alloc = yyg->yy_buffer_stack_max + grow_size;
+		yyg->yy_buffer_stack = (struct yy_buffer_state**)yyrealloc
+								(yyg->yy_buffer_stack,
+								num_to_alloc * sizeof(struct yy_buffer_state*)
+								, yyscanner);
+		if ( ! yyg->yy_buffer_stack )
+			YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" );
+
+		/* zero only the new slots.*/
+		memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*));
+		yyg->yy_buffer_stack_max = num_to_alloc;
+	}
+}
+
+/** Setup the input buffer state to scan directly from a user-specified character buffer.
+ * @param base the character buffer
+ * @param size the size in bytes of the character buffer
+ * @param yyscanner The scanner object.
+ * @return the newly allocated buffer state object.
+ */
+YY_BUFFER_STATE yy_scan_buffer  (char * base, yy_size_t  size , yyscan_t yyscanner)
+{
+	YY_BUFFER_STATE b;
+
+	if ( size < 2 ||
+	     base[size-2] != YY_END_OF_BUFFER_CHAR ||
+	     base[size-1] != YY_END_OF_BUFFER_CHAR )
+		/* They forgot to leave room for the EOB's. */
+		return NULL;
+
+	b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner );
+	if ( ! b )
+		YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" );
+
+	b->yy_buf_size = (int) (size - 2);	/* "- 2" to take care of EOB's */
+	b->yy_buf_pos = b->yy_ch_buf = base;
+	b->yy_is_our_buffer = 0;
+	b->yy_input_file = NULL;
+	b->yy_n_chars = b->yy_buf_size;
+	b->yy_is_interactive = 0;
+	b->yy_at_bol = 1;
+	b->yy_fill_buffer = 0;
+	b->yy_buffer_status = YY_BUFFER_NEW;
+
+	yy_switch_to_buffer( b , yyscanner );
+
+	return b;
+}
+
+/** Setup the input buffer state to scan a string. The next call to yylex() will
+ * scan from a @e copy of @a str.
+ * @param yystr a NUL-terminated string to scan
+ * @param yyscanner The scanner object.
+ * @return the newly allocated buffer state object.
+ * @note If you want to scan bytes that may contain NUL values, then use
+ *       yy_scan_bytes() instead.
+ */
+YY_BUFFER_STATE yy_scan_string (const char * yystr , yyscan_t yyscanner)
+{
+
+	return yy_scan_bytes( yystr, (int) strlen(yystr) , yyscanner);
+}
+
+/** Setup the input buffer state to scan the given bytes. The next call to yylex() will
+ * scan from a @e copy of @a bytes.
+ * @param yybytes the byte buffer to scan
+ * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes.
+ * @param yyscanner The scanner object.
+ * @return the newly allocated buffer state object.
+ */
+YY_BUFFER_STATE yy_scan_bytes  (const char * yybytes, int  _yybytes_len , yyscan_t yyscanner)
+{
+	YY_BUFFER_STATE b;
+	char *buf;
+	yy_size_t n;
+	int i;
+
+	/* Get memory for full buffer, including space for trailing EOB's. */
+	n = (yy_size_t) (_yybytes_len + 2);
+	buf = (char *) yyalloc( n , yyscanner );
+	if ( ! buf )
+		YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" );
+
+	for ( i = 0; i < _yybytes_len; ++i )
+		buf[i] = yybytes[i];
+
+	buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR;
+
+	b = yy_scan_buffer( buf, n , yyscanner);
+	if ( ! b )
+		YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" );
+
+	/* It's okay to grow etc. this buffer, and we should throw it
+	 * away when we're done.
+	 */
+	b->yy_is_our_buffer = 1;
+
+	return b;
+}
+
+#ifndef YY_EXIT_FAILURE
+#define YY_EXIT_FAILURE 2
+#endif
+
+static void yynoreturn yy_fatal_error (const char* msg , yyscan_t yyscanner)
+{
+	struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	(void)yyg;
+	fprintf( stderr, "%s\n", msg );
+	exit( YY_EXIT_FAILURE );
+}
+
+/* Redefine yyless() so it works in section 3 code. */
+
+#undef yyless
+#define yyless(n) \
+	do \
+		{ \
+		/* Undo effects of setting up yytext. */ \
+        int yyless_macro_arg = (n); \
+        YY_LESS_LINENO(yyless_macro_arg);\
+		yytext[yyleng] = yyg->yy_hold_char; \
+		yyg->yy_c_buf_p = yytext + yyless_macro_arg; \
+		yyg->yy_hold_char = *yyg->yy_c_buf_p; \
+		*yyg->yy_c_buf_p = '\0'; \
+		yyleng = yyless_macro_arg; \
+		} \
+	while ( 0 )
+
+/* Accessor  methods (get/set functions) to struct members. */
+
+/** Get the user-defined data for this scanner.
+ * @param yyscanner The scanner object.
+ */
+YY_EXTRA_TYPE yyget_extra  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yyextra;
+}
+
+/** Get the current line number.
+ * @param yyscanner The scanner object.
+ */
+int yyget_lineno  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+        if (! YY_CURRENT_BUFFER)
+            return 0;
+
+    return yylineno;
+}
+
+/** Get the current column number.
+ * @param yyscanner The scanner object.
+ */
+int yyget_column  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+        if (! YY_CURRENT_BUFFER)
+            return 0;
+
+    return yycolumn;
+}
+
+/** Get the input stream.
+ * @param yyscanner The scanner object.
+ */
+FILE *yyget_in  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yyin;
+}
+
+/** Get the output stream.
+ * @param yyscanner The scanner object.
+ */
+FILE *yyget_out  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yyout;
+}
+
+/** Get the length of the current token.
+ * @param yyscanner The scanner object.
+ */
+int yyget_leng  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yyleng;
+}
+
+/** Get the current token.
+ * @param yyscanner The scanner object.
+ */
+
+char *yyget_text  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yytext;
+}
+
+/** Set the user-defined data. This data is never touched by the scanner.
+ * @param user_defined The data to be associated with this scanner.
+ * @param yyscanner The scanner object.
+ */
+void yyset_extra (YY_EXTRA_TYPE  user_defined , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    yyextra = user_defined ;
+}
+
+/** Set the current line number.
+ * @param _line_number line number
+ * @param yyscanner The scanner object.
+ */
+void yyset_lineno (int  _line_number , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+        /* lineno is only valid if an input buffer exists. */
+        if (! YY_CURRENT_BUFFER )
+           YY_FATAL_ERROR( "yyset_lineno called with no buffer" );
+
+    yylineno = _line_number;
+}
+
+/** Set the current column.
+ * @param _column_no column number
+ * @param yyscanner The scanner object.
+ */
+void yyset_column (int  _column_no , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+        /* column is only valid if an input buffer exists. */
+        if (! YY_CURRENT_BUFFER )
+           YY_FATAL_ERROR( "yyset_column called with no buffer" );
+
+    yycolumn = _column_no;
+}
+
+/** Set the input stream. This does not discard the current
+ * input buffer.
+ * @param _in_str A readable stream.
+ * @param yyscanner The scanner object.
+ * @see yy_switch_to_buffer
+ */
+void yyset_in (FILE *  _in_str , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    yyin = _in_str ;
+}
+
+void yyset_out (FILE *  _out_str , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    yyout = _out_str ;
+}
+
+int yyget_debug  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yy_flex_debug;
+}
+
+void yyset_debug (int  _bdebug , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    yy_flex_debug = _bdebug ;
+}
+
+/* Accessor methods for yylval and yylloc */
+
+/* User-visible API */
+
+/* yylex_init is special because it creates the scanner itself, so it is
+ * the ONLY reentrant function that doesn't take the scanner as the last argument.
+ * That's why we explicitly handle the declaration, instead of using our macros.
+ */
+int yylex_init(yyscan_t* ptr_yy_globals)
+{
+    if (ptr_yy_globals == NULL){
+        errno = EINVAL;
+        return 1;
+    }
+
+    *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), NULL );
+
+    if (*ptr_yy_globals == NULL){
+        errno = ENOMEM;
+        return 1;
+    }
+
+    /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */
+    memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t));
+
+    return yy_init_globals ( *ptr_yy_globals );
+}
+
+/* yylex_init_extra has the same functionality as yylex_init, but follows the
+ * convention of taking the scanner as the last argument. Note however, that
+ * this is a *pointer* to a scanner, as it will be allocated by this call (and
+ * is the reason, too, why this function also must handle its own declaration).
+ * The user defined value in the first argument will be available to yyalloc in
+ * the yyextra field.
+ */
+int yylex_init_extra( YY_EXTRA_TYPE yy_user_defined, yyscan_t* ptr_yy_globals )
+{
+    struct yyguts_t dummy_yyguts;
+
+    yyset_extra (yy_user_defined, &dummy_yyguts);
+
+    if (ptr_yy_globals == NULL){
+        errno = EINVAL;
+        return 1;
+    }
+
+    *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts );
+
+    if (*ptr_yy_globals == NULL){
+        errno = ENOMEM;
+        return 1;
+    }
+
+    /* By setting to 0xAA, we expose bugs in
+    yy_init_globals. Leave at 0x00 for releases. */
+    memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t));
+
+    yyset_extra (yy_user_defined, *ptr_yy_globals);
+
+    return yy_init_globals ( *ptr_yy_globals );
+}
+
+static int yy_init_globals (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    /* Initialization is the same as for the non-reentrant scanner.
+     * This function is called from yylex_destroy(), so don't allocate here.
+     */
+
+    yyg->yy_buffer_stack = NULL;
+    yyg->yy_buffer_stack_top = 0;
+    yyg->yy_buffer_stack_max = 0;
+    yyg->yy_c_buf_p = NULL;
+    yyg->yy_init = 0;
+    yyg->yy_start = 0;
+
+    yyg->yy_start_stack_ptr = 0;
+    yyg->yy_start_stack_depth = 0;
+    yyg->yy_start_stack =  NULL;
+
+/* Defined in main.c */
+#ifdef YY_STDINIT
+    yyin = stdin;
+    yyout = stdout;
+#else
+    yyin = NULL;
+    yyout = NULL;
+#endif
+
+    /* For future reference: Set errno on error, since we are called by
+     * yylex_init()
+     */
+    return 0;
+}
+
+/* yylex_destroy is for both reentrant and non-reentrant scanners. */
+int yylex_destroy  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+    /* Pop the buffer stack, destroying each element. */
+	while(YY_CURRENT_BUFFER){
+		yy_delete_buffer( YY_CURRENT_BUFFER , yyscanner );
+		YY_CURRENT_BUFFER_LVALUE = NULL;
+		yypop_buffer_state(yyscanner);
+	}
+
+	/* Destroy the stack itself. */
+	yyfree(yyg->yy_buffer_stack , yyscanner);
+	yyg->yy_buffer_stack = NULL;
+
+    /* Destroy the start condition stack. */
+        yyfree( yyg->yy_start_stack , yyscanner );
+        yyg->yy_start_stack = NULL;
+
+    /* Reset the globals. This is important in a non-reentrant scanner so the next time
+     * yylex() is called, initialization will occur. */
+    yy_init_globals( yyscanner);
+
+    /* Destroy the main struct (reentrant only). */
+    yyfree ( yyscanner , yyscanner );
+    yyscanner = NULL;
+    return 0;
+}
+
+/*
+ * Internal utility routines.
+ */
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char* s1, const char * s2, int n , yyscan_t yyscanner)
+{
+	struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	(void)yyg;
+
+	int i;
+	for ( i = 0; i < n; ++i )
+		s1[i] = s2[i];
+}
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (const char * s , yyscan_t yyscanner)
+{
+	int n;
+	for ( n = 0; s[n]; ++n )
+		;
+
+	return n;
+}
+#endif
+
+void *yyalloc (yy_size_t  size , yyscan_t yyscanner)
+{
+	struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	(void)yyg;
+	return malloc(size);
+}
+
+void *yyrealloc  (void * ptr, yy_size_t  size , yyscan_t yyscanner)
+{
+	struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	(void)yyg;
+
+	/* The cast to (char *) in the following accommodates both
+	 * implementations that use char* generic pointers, and those
+	 * that use void* generic pointers.  It works with the latter
+	 * because both ANSI C and C++ allow castless assignment from
+	 * any pointer type to void*, and deal with argument conversions
+	 * as though doing an assignment.
+	 */
+	return realloc(ptr, size);
+}
+
+void yyfree (void * ptr , yyscan_t yyscanner)
+{
+	struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	(void)yyg;
+	free( (char *) ptr );	/* see yyrealloc() for (char *) cast */
+}
+
+#define YYTABLES_NAME "yytables"
+
+/*--------------------------------------------------------------------------*/
+
+#endif /* __clang_analyzer__ */

+ 692 - 0
Source/LexerParser/cmCTestProcessesLexer.h

@@ -0,0 +1,692 @@
+#ifndef cmCTestProcesses_yyHEADER_H
+#define cmCTestProcesses_yyHEADER_H 1
+#define cmCTestProcesses_yyIN_HEADER 1
+
+#define FLEXINT_H 1
+#define  YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 6
+#define YY_FLEX_SUBMINOR_VERSION 4
+#if YY_FLEX_SUBMINOR_VERSION > 0
+#define FLEX_BETA
+#endif
+
+#ifdef yy_create_buffer
+#define cmCTestProcesses_yy_create_buffer_ALREADY_DEFINED
+#else
+#define yy_create_buffer cmCTestProcesses_yy_create_buffer
+#endif
+
+#ifdef yy_delete_buffer
+#define cmCTestProcesses_yy_delete_buffer_ALREADY_DEFINED
+#else
+#define yy_delete_buffer cmCTestProcesses_yy_delete_buffer
+#endif
+
+#ifdef yy_scan_buffer
+#define cmCTestProcesses_yy_scan_buffer_ALREADY_DEFINED
+#else
+#define yy_scan_buffer cmCTestProcesses_yy_scan_buffer
+#endif
+
+#ifdef yy_scan_string
+#define cmCTestProcesses_yy_scan_string_ALREADY_DEFINED
+#else
+#define yy_scan_string cmCTestProcesses_yy_scan_string
+#endif
+
+#ifdef yy_scan_bytes
+#define cmCTestProcesses_yy_scan_bytes_ALREADY_DEFINED
+#else
+#define yy_scan_bytes cmCTestProcesses_yy_scan_bytes
+#endif
+
+#ifdef yy_init_buffer
+#define cmCTestProcesses_yy_init_buffer_ALREADY_DEFINED
+#else
+#define yy_init_buffer cmCTestProcesses_yy_init_buffer
+#endif
+
+#ifdef yy_flush_buffer
+#define cmCTestProcesses_yy_flush_buffer_ALREADY_DEFINED
+#else
+#define yy_flush_buffer cmCTestProcesses_yy_flush_buffer
+#endif
+
+#ifdef yy_load_buffer_state
+#define cmCTestProcesses_yy_load_buffer_state_ALREADY_DEFINED
+#else
+#define yy_load_buffer_state cmCTestProcesses_yy_load_buffer_state
+#endif
+
+#ifdef yy_switch_to_buffer
+#define cmCTestProcesses_yy_switch_to_buffer_ALREADY_DEFINED
+#else
+#define yy_switch_to_buffer cmCTestProcesses_yy_switch_to_buffer
+#endif
+
+#ifdef yypush_buffer_state
+#define cmCTestProcesses_yypush_buffer_state_ALREADY_DEFINED
+#else
+#define yypush_buffer_state cmCTestProcesses_yypush_buffer_state
+#endif
+
+#ifdef yypop_buffer_state
+#define cmCTestProcesses_yypop_buffer_state_ALREADY_DEFINED
+#else
+#define yypop_buffer_state cmCTestProcesses_yypop_buffer_state
+#endif
+
+#ifdef yyensure_buffer_stack
+#define cmCTestProcesses_yyensure_buffer_stack_ALREADY_DEFINED
+#else
+#define yyensure_buffer_stack cmCTestProcesses_yyensure_buffer_stack
+#endif
+
+#ifdef yylex
+#define cmCTestProcesses_yylex_ALREADY_DEFINED
+#else
+#define yylex cmCTestProcesses_yylex
+#endif
+
+#ifdef yyrestart
+#define cmCTestProcesses_yyrestart_ALREADY_DEFINED
+#else
+#define yyrestart cmCTestProcesses_yyrestart
+#endif
+
+#ifdef yylex_init
+#define cmCTestProcesses_yylex_init_ALREADY_DEFINED
+#else
+#define yylex_init cmCTestProcesses_yylex_init
+#endif
+
+#ifdef yylex_init_extra
+#define cmCTestProcesses_yylex_init_extra_ALREADY_DEFINED
+#else
+#define yylex_init_extra cmCTestProcesses_yylex_init_extra
+#endif
+
+#ifdef yylex_destroy
+#define cmCTestProcesses_yylex_destroy_ALREADY_DEFINED
+#else
+#define yylex_destroy cmCTestProcesses_yylex_destroy
+#endif
+
+#ifdef yyget_debug
+#define cmCTestProcesses_yyget_debug_ALREADY_DEFINED
+#else
+#define yyget_debug cmCTestProcesses_yyget_debug
+#endif
+
+#ifdef yyset_debug
+#define cmCTestProcesses_yyset_debug_ALREADY_DEFINED
+#else
+#define yyset_debug cmCTestProcesses_yyset_debug
+#endif
+
+#ifdef yyget_extra
+#define cmCTestProcesses_yyget_extra_ALREADY_DEFINED
+#else
+#define yyget_extra cmCTestProcesses_yyget_extra
+#endif
+
+#ifdef yyset_extra
+#define cmCTestProcesses_yyset_extra_ALREADY_DEFINED
+#else
+#define yyset_extra cmCTestProcesses_yyset_extra
+#endif
+
+#ifdef yyget_in
+#define cmCTestProcesses_yyget_in_ALREADY_DEFINED
+#else
+#define yyget_in cmCTestProcesses_yyget_in
+#endif
+
+#ifdef yyset_in
+#define cmCTestProcesses_yyset_in_ALREADY_DEFINED
+#else
+#define yyset_in cmCTestProcesses_yyset_in
+#endif
+
+#ifdef yyget_out
+#define cmCTestProcesses_yyget_out_ALREADY_DEFINED
+#else
+#define yyget_out cmCTestProcesses_yyget_out
+#endif
+
+#ifdef yyset_out
+#define cmCTestProcesses_yyset_out_ALREADY_DEFINED
+#else
+#define yyset_out cmCTestProcesses_yyset_out
+#endif
+
+#ifdef yyget_leng
+#define cmCTestProcesses_yyget_leng_ALREADY_DEFINED
+#else
+#define yyget_leng cmCTestProcesses_yyget_leng
+#endif
+
+#ifdef yyget_text
+#define cmCTestProcesses_yyget_text_ALREADY_DEFINED
+#else
+#define yyget_text cmCTestProcesses_yyget_text
+#endif
+
+#ifdef yyget_lineno
+#define cmCTestProcesses_yyget_lineno_ALREADY_DEFINED
+#else
+#define yyget_lineno cmCTestProcesses_yyget_lineno
+#endif
+
+#ifdef yyset_lineno
+#define cmCTestProcesses_yyset_lineno_ALREADY_DEFINED
+#else
+#define yyset_lineno cmCTestProcesses_yyset_lineno
+#endif
+
+#ifdef yyget_column
+#define cmCTestProcesses_yyget_column_ALREADY_DEFINED
+#else
+#define yyget_column cmCTestProcesses_yyget_column
+#endif
+
+#ifdef yyset_column
+#define cmCTestProcesses_yyset_column_ALREADY_DEFINED
+#else
+#define yyset_column cmCTestProcesses_yyset_column
+#endif
+
+#ifdef yywrap
+#define cmCTestProcesses_yywrap_ALREADY_DEFINED
+#else
+#define yywrap cmCTestProcesses_yywrap
+#endif
+
+#ifdef yyalloc
+#define cmCTestProcesses_yyalloc_ALREADY_DEFINED
+#else
+#define yyalloc cmCTestProcesses_yyalloc
+#endif
+
+#ifdef yyrealloc
+#define cmCTestProcesses_yyrealloc_ALREADY_DEFINED
+#else
+#define yyrealloc cmCTestProcesses_yyrealloc
+#endif
+
+#ifdef yyfree
+#define cmCTestProcesses_yyfree_ALREADY_DEFINED
+#else
+#define yyfree cmCTestProcesses_yyfree
+#endif
+
+/* First, we deal with  platform-specific or compiler-specific issues. */
+
+/* begin standard C headers. */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+/* end standard C headers. */
+
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */
+
+#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+
+/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h,
+ * if you want the limit (max/min) macros for int types.
+ */
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS 1
+#endif
+
+#include <inttypes.h>
+typedef int8_t flex_int8_t;
+typedef uint8_t flex_uint8_t;
+typedef int16_t flex_int16_t;
+typedef uint16_t flex_uint16_t;
+typedef int32_t flex_int32_t;
+typedef uint32_t flex_uint32_t;
+#else
+typedef signed char flex_int8_t;
+typedef short int flex_int16_t;
+typedef int flex_int32_t;
+typedef unsigned char flex_uint8_t;
+typedef unsigned short int flex_uint16_t;
+typedef unsigned int flex_uint32_t;
+
+/* Limits of integral types. */
+#ifndef INT8_MIN
+#define INT8_MIN               (-128)
+#endif
+#ifndef INT16_MIN
+#define INT16_MIN              (-32767-1)
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN              (-2147483647-1)
+#endif
+#ifndef INT8_MAX
+#define INT8_MAX               (127)
+#endif
+#ifndef INT16_MAX
+#define INT16_MAX              (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX              (2147483647)
+#endif
+#ifndef UINT8_MAX
+#define UINT8_MAX              (255U)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX             (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX             (4294967295U)
+#endif
+
+#ifndef SIZE_MAX
+#define SIZE_MAX               (~(size_t)0)
+#endif
+
+#endif /* ! C99 */
+
+#endif /* ! FLEXINT_H */
+
+/* begin standard C++ headers. */
+
+/* TODO: this is always defined, so inline it */
+#define yyconst const
+
+#if defined(__GNUC__) && __GNUC__ >= 3
+#define yynoreturn __attribute__((__noreturn__))
+#else
+#define yynoreturn
+#endif
+
+/* An opaque pointer. */
+#ifndef YY_TYPEDEF_YY_SCANNER_T
+#define YY_TYPEDEF_YY_SCANNER_T
+typedef void* yyscan_t;
+#endif
+
+/* For convenience, these vars (plus the bison vars far below)
+   are macros in the reentrant scanner. */
+#define yyin yyg->yyin_r
+#define yyout yyg->yyout_r
+#define yyextra yyg->yyextra_r
+#define yyleng yyg->yyleng_r
+#define yytext yyg->yytext_r
+#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno)
+#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column)
+#define yy_flex_debug yyg->yy_flex_debug_r
+
+/* Size of default input buffer. */
+#ifndef YY_BUF_SIZE
+#ifdef __ia64__
+/* On IA-64, the buffer size is 16k, not 8k.
+ * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case.
+ * Ditto for the __ia64__ case accordingly.
+ */
+#define YY_BUF_SIZE 32768
+#else
+#define YY_BUF_SIZE 16384
+#endif /* __ia64__ */
+#endif
+
+#ifndef YY_TYPEDEF_YY_BUFFER_STATE
+#define YY_TYPEDEF_YY_BUFFER_STATE
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+#endif
+
+#ifndef YY_TYPEDEF_YY_SIZE_T
+#define YY_TYPEDEF_YY_SIZE_T
+typedef size_t yy_size_t;
+#endif
+
+#ifndef YY_STRUCT_YY_BUFFER_STATE
+#define YY_STRUCT_YY_BUFFER_STATE
+struct yy_buffer_state
+	{
+	FILE *yy_input_file;
+
+	char *yy_ch_buf;		/* input buffer */
+	char *yy_buf_pos;		/* current position in input buffer */
+
+	/* Size of input buffer in bytes, not including room for EOB
+	 * characters.
+	 */
+	int yy_buf_size;
+
+	/* Number of characters read into yy_ch_buf, not including EOB
+	 * characters.
+	 */
+	int yy_n_chars;
+
+	/* Whether we "own" the buffer - i.e., we know we created it,
+	 * and can realloc() it to grow it, and should free() it to
+	 * delete it.
+	 */
+	int yy_is_our_buffer;
+
+	/* Whether this is an "interactive" input source; if so, and
+	 * if we're using stdio for input, then we want to use getc()
+	 * instead of fread(), to make sure we stop fetching input after
+	 * each newline.
+	 */
+	int yy_is_interactive;
+
+	/* Whether we're considered to be at the beginning of a line.
+	 * If so, '^' rules will be active on the next match, otherwise
+	 * not.
+	 */
+	int yy_at_bol;
+
+    int yy_bs_lineno; /**< The line count. */
+    int yy_bs_column; /**< The column count. */
+
+	/* Whether to try to fill the input buffer when we reach the
+	 * end of it.
+	 */
+	int yy_fill_buffer;
+
+	int yy_buffer_status;
+
+	};
+#endif /* !YY_STRUCT_YY_BUFFER_STATE */
+
+void yyrestart ( FILE *input_file , yyscan_t yyscanner );
+void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner );
+YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner );
+void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner );
+void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner );
+void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner );
+void yypop_buffer_state ( yyscan_t yyscanner );
+
+YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner );
+YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner );
+YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len , yyscan_t yyscanner );
+
+void *yyalloc ( yy_size_t , yyscan_t yyscanner );
+void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner );
+void yyfree ( void * , yyscan_t yyscanner );
+
+/* Begin user sect3 */
+
+#define cmCTestProcesses_yywrap(yyscanner) (/*CONSTCOND*/1)
+#define YY_SKIP_YYWRAP
+
+#define yytext_ptr yytext_r
+
+#ifdef YY_HEADER_EXPORT_START_CONDITIONS
+#define INITIAL 0
+#define PROCESSES_START 1
+#define PROCESSES_END 2
+#define RESOURCE_START 3
+#define RESOURCE_COUNT 4
+#define RESOURCE_END 5
+
+#endif
+
+#ifndef YY_EXTRA_TYPE
+#define YY_EXTRA_TYPE void *
+#endif
+
+int yylex_init (yyscan_t* scanner);
+
+int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner);
+
+/* Accessor methods to globals.
+   These are made visible to non-reentrant scanners for convenience. */
+
+int yylex_destroy ( yyscan_t yyscanner );
+
+int yyget_debug ( yyscan_t yyscanner );
+
+void yyset_debug ( int debug_flag , yyscan_t yyscanner );
+
+YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner );
+
+void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner );
+
+FILE *yyget_in ( yyscan_t yyscanner );
+
+void yyset_in  ( FILE * _in_str , yyscan_t yyscanner );
+
+FILE *yyget_out ( yyscan_t yyscanner );
+
+void yyset_out  ( FILE * _out_str , yyscan_t yyscanner );
+
+			int yyget_leng ( yyscan_t yyscanner );
+
+char *yyget_text ( yyscan_t yyscanner );
+
+int yyget_lineno ( yyscan_t yyscanner );
+
+void yyset_lineno ( int _line_number , yyscan_t yyscanner );
+
+int yyget_column  ( yyscan_t yyscanner );
+
+void yyset_column ( int _column_no , yyscan_t yyscanner );
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int yywrap ( yyscan_t yyscanner );
+#else
+extern int yywrap ( yyscan_t yyscanner );
+#endif
+#endif
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner);
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen ( const char * , yyscan_t yyscanner);
+#endif
+
+#ifndef YY_NO_INPUT
+
+#endif
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#ifdef __ia64__
+/* On IA-64, the buffer size is 16k, not 8k */
+#define YY_READ_BUF_SIZE 16384
+#else
+#define YY_READ_BUF_SIZE 8192
+#endif /* __ia64__ */
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL_IS_OURS 1
+
+extern int yylex (yyscan_t yyscanner);
+
+#define YY_DECL int yylex (yyscan_t yyscanner)
+#endif /* !YY_DECL */
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+#undef YY_NEW_FILE
+#undef YY_FLUSH_BUFFER
+#undef yy_set_bol
+#undef yy_new_buffer
+#undef yy_set_interactive
+#undef YY_DO_BEFORE_ACTION
+
+#ifdef YY_DECL_IS_OURS
+#undef YY_DECL_IS_OURS
+#undef YY_DECL
+#endif
+
+#ifndef cmCTestProcesses_yy_create_buffer_ALREADY_DEFINED
+#undef yy_create_buffer
+#endif
+#ifndef cmCTestProcesses_yy_delete_buffer_ALREADY_DEFINED
+#undef yy_delete_buffer
+#endif
+#ifndef cmCTestProcesses_yy_scan_buffer_ALREADY_DEFINED
+#undef yy_scan_buffer
+#endif
+#ifndef cmCTestProcesses_yy_scan_string_ALREADY_DEFINED
+#undef yy_scan_string
+#endif
+#ifndef cmCTestProcesses_yy_scan_bytes_ALREADY_DEFINED
+#undef yy_scan_bytes
+#endif
+#ifndef cmCTestProcesses_yy_init_buffer_ALREADY_DEFINED
+#undef yy_init_buffer
+#endif
+#ifndef cmCTestProcesses_yy_flush_buffer_ALREADY_DEFINED
+#undef yy_flush_buffer
+#endif
+#ifndef cmCTestProcesses_yy_load_buffer_state_ALREADY_DEFINED
+#undef yy_load_buffer_state
+#endif
+#ifndef cmCTestProcesses_yy_switch_to_buffer_ALREADY_DEFINED
+#undef yy_switch_to_buffer
+#endif
+#ifndef cmCTestProcesses_yypush_buffer_state_ALREADY_DEFINED
+#undef yypush_buffer_state
+#endif
+#ifndef cmCTestProcesses_yypop_buffer_state_ALREADY_DEFINED
+#undef yypop_buffer_state
+#endif
+#ifndef cmCTestProcesses_yyensure_buffer_stack_ALREADY_DEFINED
+#undef yyensure_buffer_stack
+#endif
+#ifndef cmCTestProcesses_yylex_ALREADY_DEFINED
+#undef yylex
+#endif
+#ifndef cmCTestProcesses_yyrestart_ALREADY_DEFINED
+#undef yyrestart
+#endif
+#ifndef cmCTestProcesses_yylex_init_ALREADY_DEFINED
+#undef yylex_init
+#endif
+#ifndef cmCTestProcesses_yylex_init_extra_ALREADY_DEFINED
+#undef yylex_init_extra
+#endif
+#ifndef cmCTestProcesses_yylex_destroy_ALREADY_DEFINED
+#undef yylex_destroy
+#endif
+#ifndef cmCTestProcesses_yyget_debug_ALREADY_DEFINED
+#undef yyget_debug
+#endif
+#ifndef cmCTestProcesses_yyset_debug_ALREADY_DEFINED
+#undef yyset_debug
+#endif
+#ifndef cmCTestProcesses_yyget_extra_ALREADY_DEFINED
+#undef yyget_extra
+#endif
+#ifndef cmCTestProcesses_yyset_extra_ALREADY_DEFINED
+#undef yyset_extra
+#endif
+#ifndef cmCTestProcesses_yyget_in_ALREADY_DEFINED
+#undef yyget_in
+#endif
+#ifndef cmCTestProcesses_yyset_in_ALREADY_DEFINED
+#undef yyset_in
+#endif
+#ifndef cmCTestProcesses_yyget_out_ALREADY_DEFINED
+#undef yyget_out
+#endif
+#ifndef cmCTestProcesses_yyset_out_ALREADY_DEFINED
+#undef yyset_out
+#endif
+#ifndef cmCTestProcesses_yyget_leng_ALREADY_DEFINED
+#undef yyget_leng
+#endif
+#ifndef cmCTestProcesses_yyget_text_ALREADY_DEFINED
+#undef yyget_text
+#endif
+#ifndef cmCTestProcesses_yyget_lineno_ALREADY_DEFINED
+#undef yyget_lineno
+#endif
+#ifndef cmCTestProcesses_yyset_lineno_ALREADY_DEFINED
+#undef yyset_lineno
+#endif
+#ifndef cmCTestProcesses_yyget_column_ALREADY_DEFINED
+#undef yyget_column
+#endif
+#ifndef cmCTestProcesses_yyset_column_ALREADY_DEFINED
+#undef yyset_column
+#endif
+#ifndef cmCTestProcesses_yywrap_ALREADY_DEFINED
+#undef yywrap
+#endif
+#ifndef cmCTestProcesses_yyget_lval_ALREADY_DEFINED
+#undef yyget_lval
+#endif
+#ifndef cmCTestProcesses_yyset_lval_ALREADY_DEFINED
+#undef yyset_lval
+#endif
+#ifndef cmCTestProcesses_yyget_lloc_ALREADY_DEFINED
+#undef yyget_lloc
+#endif
+#ifndef cmCTestProcesses_yyset_lloc_ALREADY_DEFINED
+#undef yyset_lloc
+#endif
+#ifndef cmCTestProcesses_yyalloc_ALREADY_DEFINED
+#undef yyalloc
+#endif
+#ifndef cmCTestProcesses_yyrealloc_ALREADY_DEFINED
+#undef yyrealloc
+#endif
+#ifndef cmCTestProcesses_yyfree_ALREADY_DEFINED
+#undef yyfree
+#endif
+#ifndef cmCTestProcesses_yytext_ALREADY_DEFINED
+#undef yytext
+#endif
+#ifndef cmCTestProcesses_yyleng_ALREADY_DEFINED
+#undef yyleng
+#endif
+#ifndef cmCTestProcesses_yyin_ALREADY_DEFINED
+#undef yyin
+#endif
+#ifndef cmCTestProcesses_yyout_ALREADY_DEFINED
+#undef yyout
+#endif
+#ifndef cmCTestProcesses_yy_flex_debug_ALREADY_DEFINED
+#undef yy_flex_debug
+#endif
+#ifndef cmCTestProcesses_yylineno_ALREADY_DEFINED
+#undef yylineno
+#endif
+#ifndef cmCTestProcesses_yytables_fload_ALREADY_DEFINED
+#undef yytables_fload
+#endif
+#ifndef cmCTestProcesses_yytables_destroy_ALREADY_DEFINED
+#undef yytables_destroy
+#endif
+#ifndef cmCTestProcesses_yyTABLES_NAME_ALREADY_DEFINED
+#undef yyTABLES_NAME
+#endif
+
+#undef cmCTestProcesses_yyIN_HEADER
+#endif /* cmCTestProcesses_yyHEADER_H */

+ 102 - 0
Source/LexerParser/cmCTestProcessesLexer.in.l

@@ -0,0 +1,102 @@
+%{
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+/*
+
+This file must be translated to C++ and modified to build everywhere.
+
+Run flex >= 2.6 like this:
+
+  flex --nounistd -DFLEXINT_H --noline --header-file=cmCTestProcessesLexer.h -ocmCTestProcessesLexer.cxx cmCTestProcessesLexer.in.l
+
+Modify cmCTestProcessesLexer.cxx:
+  - remove trailing whitespace:              sed -i 's/\s*$//' cmCTestProcessesLexer.h cmCTestProcessesLexer.cxx
+  - remove blank lines at end of file:       sed -i '${/^$/d;}' cmCTestProcessesLexer.h cmCTestProcessesLexer.cxx
+  - #include "cmStandardLexer.h" at the top: sed -i '1i#include "cmStandardLexer.h"' cmCTestProcessesLexer.cxx
+
+*/
+
+/* IWYU pragma: no_forward_declare yyguts_t */
+
+#ifndef __clang_analyzer__ /* Suppress clang scan-build warnings */
+
+#include "cmCTestProcessesLexerHelper.h"
+
+#include <string>
+
+#include <cstddef>
+
+/*--------------------------------------------------------------------------*/
+%}
+
+%option prefix="cmCTestProcesses_yy"
+
+%option reentrant
+%option noyywrap
+%option nodefault
+%pointer
+
+%s PROCESSES_START
+%s PROCESSES_END
+%s RESOURCE_START
+%s RESOURCE_COUNT
+%s RESOURCE_END
+
+NUMBER [0-9]+
+IDENTIFIER [a-z_][a-z0-9_]*
+
+%%
+
+<INITIAL,PROCESSES_START,RESOURCE_START>{IDENTIFIER}: {
+  BEGIN(RESOURCE_COUNT);
+  yyextra->SetResourceType(std::string(yytext, yyleng - 1));
+}
+
+<INITIAL,PROCESSES_START>{NUMBER} {
+  BEGIN(PROCESSES_END);
+  std::size_t len = yyleng;
+  yyextra->SetProcessCount(std::stoll(yytext, &len, 10));
+}
+
+<RESOURCE_COUNT>{NUMBER} {
+  BEGIN(RESOURCE_END);
+  std::size_t len = yyleng;
+  yyextra->SetNeededSlots(std::stoll(yytext, &len, 10));
+  yyextra->WriteRequirement();
+}
+
+<PROCESSES_END,RESOURCE_END>,+ {
+  BEGIN(RESOURCE_START);
+}
+
+<INITIAL,PROCESSES_START,RESOURCE_START>;+ {
+  BEGIN(PROCESSES_START);
+}
+
+<PROCESSES_END,RESOURCE_END>;+ {
+  BEGIN(PROCESSES_START);
+  yyextra->WriteProcess();
+}
+
+<RESOURCE_START,PROCESSES_END,RESOURCE_END><<EOF>> {
+  yyextra->WriteProcess();
+  return 0;
+}
+
+<INITIAL,PROCESSES_START><<EOF>> {
+  return 0;
+}
+
+<<EOF>> {
+  return 1;
+}
+
+.|\n {
+  return 1;
+}
+
+%%
+
+/*--------------------------------------------------------------------------*/
+
+#endif /* __clang_analyzer__ */

+ 9 - 0
Source/cmCTest.cxx

@@ -2090,6 +2090,15 @@ bool cmCTest::HandleCommandLineArguments(size_t& i,
       "ExcludeFixtureCleanupRegularExpression", args[i].c_str());
   }
 
+  if (this->CheckArgument(arg, "--hardware-spec-file") &&
+      i < args.size() - 1) {
+    i++;
+    this->GetTestHandler()->SetPersistentOption("HardwareSpecFile",
+                                                args[i].c_str());
+    this->GetMemCheckHandler()->SetPersistentOption("HardwareSpecFile",
+                                                    args[i].c_str());
+  }
+
   if (this->CheckArgument(arg, "--rerun-failed")) {
     this->GetTestHandler()->SetPersistentOption("RerunFailed", "true");
     this->GetMemCheckHandler()->SetPersistentOption("RerunFailed", "true");

+ 1 - 0
Source/ctest.cxx

@@ -103,6 +103,7 @@ static const char* cmDocumentationOptions[][2] = {
     "times without failing in order to pass" },
   { "--max-width <width>", "Set the max width for a test name to output" },
   { "--interactive-debug-mode [0|1]", "Set the interactive mode to 0 or 1." },
+  { "--hardware-spec-file <file>", "Set the hardware spec file to use." },
   { "--no-label-summary", "Disable timing summary information for labels." },
   { "--no-subproject-summary",
     "Disable timing summary information for "

+ 7 - 1
Tests/CMakeLib/CMakeLists.txt

@@ -2,10 +2,15 @@ include_directories(
   ${CMAKE_CURRENT_BINARY_DIR}
   ${CMake_BINARY_DIR}/Source
   ${CMake_SOURCE_DIR}/Source
+  ${CMake_SOURCE_DIR}/Source/CTest
   )
 
 set(CMakeLib_TESTS
   testArgumentParser.cxx
+  testCTestBinPacker.cxx
+  testCTestProcesses.cxx
+  testCTestHardwareAllocator.cxx
+  testCTestHardwareSpec.cxx
   testGeneratedFileStream.cxx
   testRST.cxx
   testRange.cxx
@@ -27,6 +32,7 @@ add_executable(testUVProcessChainHelper testUVProcessChainHelper.cxx)
 set(testRST_ARGS ${CMAKE_CURRENT_SOURCE_DIR})
 set(testUVProcessChain_ARGS $<TARGET_FILE:testUVProcessChainHelper>)
 set(testUVStreambuf_ARGS $<TARGET_FILE:cmake>)
+set(testCTestHardwareSpec_ARGS ${CMAKE_CURRENT_SOURCE_DIR})
 
 if(WIN32)
   list(APPEND CMakeLib_TESTS
@@ -41,7 +47,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/testXMLParser.h.in
 
 create_test_sourcelist(CMakeLib_TEST_SRCS CMakeLibTests.cxx ${CMakeLib_TESTS})
 add_executable(CMakeLibTests ${CMakeLib_TEST_SRCS})
-target_link_libraries(CMakeLibTests CMakeLib)
+target_link_libraries(CMakeLibTests CMakeLib CTestLib)
 
 set_property(TARGET CMakeLibTests PROPERTY C_CLANG_TIDY "")
 set_property(TARGET CMakeLibTests PROPERTY CXX_CLANG_TIDY "")

+ 300 - 0
Tests/CMakeLib/testCTestBinPacker.cxx

@@ -0,0 +1,300 @@
+#include <cstddef>
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "cmCTestBinPacker.h"
+#include "cmCTestHardwareAllocator.h"
+
+struct ExpectedPackResult
+{
+  std::vector<int> SlotsNeeded;
+  std::map<std::string, cmCTestHardwareAllocator::Resource> Hardware;
+  bool ExpectedReturnValue;
+  std::vector<cmCTestBinPackerAllocation> ExpectedRoundRobinAllocations;
+  std::vector<cmCTestBinPackerAllocation> ExpectedBlockAllocations;
+};
+
+static const std::vector<ExpectedPackResult> expectedResults
+{
+  /* clang-format off */
+  {
+    { 2, 2, 2, 2 },
+    { { "0", { 4, 0 } }, { "1", { 4, 0 } }, { "2", { 4, 0 } },
+      { "3", { 4, 0 } } },
+    true,
+    {
+      { 0, 2, "0" },
+      { 1, 2, "1" },
+      { 2, 2, "2" },
+      { 3, 2, "3" },
+    },
+    {
+      { 0, 2, "0" },
+      { 1, 2, "0" },
+      { 2, 2, "1" },
+      { 3, 2, "1" },
+    },
+  },
+  {
+    { 2, 3, 2 },
+    { { "0", { 5, 0 } }, { "1", { 2, 0 } } },
+    true,
+    {
+      { 0, 2, "0" },
+      { 1, 3, "0" },
+      { 2, 2, "1" },
+    },
+    {
+      { 0, 2, "0" },
+      { 1, 3, "0" },
+      { 2, 2, "1" },
+    },
+  },
+  {
+    { 1, 2, 3 },
+    { { "0", { 1, 0 } }, { "1", { 2, 0 } }, { "2", { 2, 0 } } },
+    false,
+    { },
+    { },
+  },
+  {
+    { 48, 21, 31, 10, 40 },
+    { { "0", { 81, 0 } }, { "1", { 68, 0 } }, { "2", { 20, 0 } },
+      { "3", { 13, 0 } } },
+    true,
+    {
+      { 0, 48, "0" },
+      { 1, 21, "1" },
+      { 2, 31, "0" },
+      { 3, 10, "2" },
+      { 4, 40, "1" },
+    },
+    {
+      { 0, 48, "0" },
+      { 1, 21, "1" },
+      { 2, 31, "0" },
+      { 3, 10, "2" },
+      { 4, 40, "1" },
+    },
+  },
+  {
+    { 30, 31, 39, 67 },
+    { { "0", { 16, 0 } }, { "1", { 81, 0 } }, { "2", { 97, 0 } } },
+    true,
+    {
+      { 0, 30, "2" },
+      { 1, 31, "1" },
+      { 2, 39, "1" },
+      { 3, 67, "2" },
+    },
+    {
+      { 0, 30, "2" },
+      { 1, 31, "1" },
+      { 2, 39, "1" },
+      { 3, 67, "2" },
+    },
+  },
+  {
+    { 63, 47, 1, 9 },
+    { { "0", { 18, 0 } }, { "1", { 29, 0 } }, { "2", { 9, 0 } },
+      { "3", { 52, 0 } } },
+    false,
+    { },
+    { },
+  },
+  {
+    { 22, 29, 46, 85 },
+    { { "0", { 65, 0 } }, { "1", { 85, 0 } }, { "2", { 65, 0 } },
+      { "3", { 78, 0 } } },
+    true,
+    {
+      { 0, 22, "2" },
+      { 1, 29, "0" },
+      { 2, 46, "3" },
+      { 3, 85, "1" },
+    },
+    {
+      { 0, 22, "0" },
+      { 1, 29, "3" },
+      { 2, 46, "3" },
+      { 3, 85, "1" },
+    },
+  },
+  {
+    { 66, 11, 34, 21 },
+    { { "0", { 24, 0 } }, { "1", { 57, 0 } }, { "2", { 61, 0 } },
+      { "3", { 51, 0 } } },
+    false,
+    { },
+    { },
+  },
+  {
+    { 72, 65, 67, 45 },
+    { { "0", { 29, 0 } }, { "1", { 77, 0 } }, { "2", { 98, 0 } },
+      { "3", { 58, 0 } } },
+    false,
+    { },
+    { },
+  },
+  /*
+   * The following is a contrived attack on the bin-packing algorithm that
+   * causes it to execute with n! complexity, where n is the number of
+   * resources. This case is very unrepresentative of real-world usage, and
+   * has been documented but disabled. The bin-packing problem is NP-hard, and
+   * we may not be able to fix this case at all.
+   */
+#if 0
+  {
+    { 1000, 999, 998, 997, 996, 995, 994, 993, 992, 991, 19 },
+    { { "0", { 1000, 0 } }, { "1", { 1001, 0 } }, { "2", { 1002, 0 } },
+      { "3", { 1003, 0 } }, { "4", { 1004, 0 } }, { "5", { 1005, 0 } },
+      { "6", { 1006, 0 } }, { "7", { 1007, 0 } }, { "8", { 1008, 0 } },
+      { "9", { 1009, 0 } } },
+    false,
+    { },
+    { },
+  },
+#endif
+  /*
+   * These cases are more representative of real-world usage (the resource
+   * sizes are all the same.)
+   */
+  {
+    { 1000, 999, 998, 997, 996, 995, 994, 993, 992, 991, 10 },
+    { { "0", { 1000, 0 } }, { "1", { 1000, 0 } }, { "2", { 1000, 0 } },
+      { "3", { 1000, 0 } }, { "4", { 1000, 0 } }, { "5", { 1000, 0 } },
+      { "6", { 1000, 0 } }, { "7", { 1000, 0 } }, { "8", { 1000, 0 } },
+      { "9", { 1000, 0 } } },
+    false,
+    { },
+    { },
+  },
+  {
+    { 1000, 999, 998, 997, 996, 995, 994, 993, 992, 991, 9 },
+    { { "0", { 1000, 0 } }, { "1", { 1000, 0 } }, { "2", { 1000, 0 } },
+      { "3", { 1000, 0 } }, { "4", { 1000, 0 } }, { "5", { 1000, 0 } },
+      { "6", { 1000, 0 } }, { "7", { 1000, 0 } }, { "8", { 1000, 0 } },
+      { "9", { 1000, 0 } } },
+    true,
+    {
+      { 0, 1000, "0" },
+      { 1, 999, "1" },
+      { 2, 998, "2" },
+      { 3, 997, "3" },
+      { 4, 996, "4" },
+      { 5, 995, "5" },
+      { 6, 994, "6" },
+      { 7, 993, "7" },
+      { 8, 992, "8" },
+      { 9, 991, "9" },
+      { 10, 9, "9" },
+    },
+    {
+      { 0, 1000, "0" },
+      { 1, 999, "1" },
+      { 2, 998, "2" },
+      { 3, 997, "3" },
+      { 4, 996, "4" },
+      { 5, 995, "5" },
+      { 6, 994, "6" },
+      { 7, 993, "7" },
+      { 8, 992, "8" },
+      { 9, 991, "9" },
+      { 10, 9, "9" },
+    },
+  },
+  /* clang-format on */
+};
+
+struct AllocationComparison
+{
+  cmCTestBinPackerAllocation First;
+  cmCTestBinPackerAllocation Second;
+  bool Equal;
+};
+
+static const std::vector<AllocationComparison> comparisons{
+  /* clang-format off */
+  { { 0, 1, "0" }, { 0, 1, "0" }, true },
+  { { 0, 1, "0" }, { 1, 1, "0" }, false },
+  { { 0, 1, "0" }, { 0, 2, "0" }, false },
+  { { 0, 1, "0" }, { 0, 1, "1" }, false },
+  /* clang-format on */
+};
+
+bool TestExpectedPackResult(const ExpectedPackResult& expected)
+{
+  std::vector<cmCTestBinPackerAllocation> roundRobinAllocations;
+  roundRobinAllocations.reserve(expected.SlotsNeeded.size());
+  std::size_t index = 0;
+  for (auto const& n : expected.SlotsNeeded) {
+    roundRobinAllocations.push_back({ index++, n, "" });
+  }
+
+  bool roundRobinResult = cmAllocateCTestHardwareRoundRobin(
+    expected.Hardware, roundRobinAllocations);
+  if (roundRobinResult != expected.ExpectedReturnValue) {
+    std::cout
+      << "cmAllocateCTestHardwareRoundRobin did not return expected value"
+      << std::endl;
+    return false;
+  }
+
+  if (roundRobinResult &&
+      roundRobinAllocations != expected.ExpectedRoundRobinAllocations) {
+    std::cout << "cmAllocateCTestHardwareRoundRobin did not return expected "
+                 "allocations"
+              << std::endl;
+    return false;
+  }
+
+  std::vector<cmCTestBinPackerAllocation> blockAllocations;
+  blockAllocations.reserve(expected.SlotsNeeded.size());
+  index = 0;
+  for (auto const& n : expected.SlotsNeeded) {
+    blockAllocations.push_back({ index++, n, "" });
+  }
+
+  bool blockResult =
+    cmAllocateCTestHardwareBlock(expected.Hardware, blockAllocations);
+  if (blockResult != expected.ExpectedReturnValue) {
+    std::cout << "cmAllocateCTestHardwareBlock did not return expected value"
+              << std::endl;
+    return false;
+  }
+
+  if (blockResult && blockAllocations != expected.ExpectedBlockAllocations) {
+    std::cout << "cmAllocateCTestHardwareBlock did not return expected"
+                 " allocations"
+              << std::endl;
+    return false;
+  }
+
+  return true;
+}
+
+int testCTestBinPacker(int /*unused*/, char* /*unused*/ [])
+{
+  int retval = 0;
+
+  for (auto const& comparison : comparisons) {
+    if ((comparison.First == comparison.Second) != comparison.Equal) {
+      std::cout << "Comparison did not match expected" << std::endl;
+      retval = 1;
+    }
+    if ((comparison.First != comparison.Second) == comparison.Equal) {
+      std::cout << "Comparison did not match expected" << std::endl;
+      retval = 1;
+    }
+  }
+
+  for (auto const& expected : expectedResults) {
+    if (!TestExpectedPackResult(expected)) {
+      retval = 1;
+    }
+  }
+
+  return retval;
+}

+ 426 - 0
Tests/CMakeLib/testCTestHardwareAllocator.cxx

@@ -0,0 +1,426 @@
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "cmCTestHardwareAllocator.h"
+#include "cmCTestHardwareSpec.h"
+
+static const cmCTestHardwareSpec spec{ { {
+  /* clang-format off */
+  { "gpus", { { "0", 4 }, { "1", 8 }, { "2", 0 }, { "3", 8 } } },
+  /* clang-format on */
+} } };
+
+bool testInitializeFromHardwareSpec()
+{
+  bool retval = true;
+
+  cmCTestHardwareAllocator allocator;
+  allocator.InitializeFromHardwareSpec(spec);
+
+  static const std::map<
+    std::string, std::map<std::string, cmCTestHardwareAllocator::Resource>>
+    expected{
+      /* clang-format off */
+      { "gpus", {
+        { "0", { 4, 0 } },
+        { "1", { 8, 0 } },
+        { "2", { 0, 0 } },
+        { "3", { 8, 0 } },
+      } },
+      /* clang-format on */
+    };
+  if (allocator.GetResources() != expected) {
+    std::cout << "GetResources() did not return expected value\n";
+    retval = false;
+  }
+
+  return retval;
+}
+
+bool testAllocateResource()
+{
+  bool retval = true;
+
+  cmCTestHardwareAllocator allocator;
+  allocator.InitializeFromHardwareSpec(spec);
+
+  static const std::map<
+    std::string, std::map<std::string, cmCTestHardwareAllocator::Resource>>
+    expected1{
+      /* clang-format off */
+      { "gpus", {
+        { "0", { 4, 2 } },
+        { "1", { 8, 0 } },
+        { "2", { 0, 0 } },
+        { "3", { 8, 0 } },
+      } },
+      /* clang-format on */
+    };
+  if (!allocator.AllocateResource("gpus", "0", 2)) {
+    std::cout
+      << "AllocateResource(\"gpus\", \"0\", 2) returned false, should be "
+         "true\n";
+    retval = false;
+  }
+  if (allocator.GetResources() != expected1) {
+    std::cout << "GetResources() did not return expected value\n";
+    retval = false;
+  }
+
+  static const std::map<
+    std::string, std::map<std::string, cmCTestHardwareAllocator::Resource>>
+    expected2{
+      /* clang-format off */
+      { "gpus", {
+        { "0", { 4, 4 } },
+        { "1", { 8, 0 } },
+        { "2", { 0, 0 } },
+        { "3", { 8, 0 } },
+      } },
+      /* clang-format on */
+    };
+  if (!allocator.AllocateResource("gpus", "0", 2)) {
+    std::cout
+      << "AllocateResource(\"gpus\", \"0\", 2) returned false, should be "
+         "true\n";
+    retval = false;
+  }
+  if (allocator.GetResources() != expected2) {
+    std::cout << "GetResources() did not return expected value\n";
+    retval = false;
+  }
+
+  static const std::map<
+    std::string, std::map<std::string, cmCTestHardwareAllocator::Resource>>
+    expected3{
+      /* clang-format off */
+      { "gpus", {
+        { "0", { 4, 4 } },
+        { "1", { 8, 0 } },
+        { "2", { 0, 0 } },
+        { "3", { 8, 0 } },
+      } },
+      /* clang-format on */
+    };
+  if (allocator.AllocateResource("gpus", "0", 1)) {
+    std::cout
+      << "AllocateResource(\"gpus\", \"0\", 1) returned true, should be "
+         "false\n";
+    retval = false;
+  }
+  if (allocator.GetResources() != expected3) {
+    std::cout << "GetResources() did not return expected value\n";
+    retval = false;
+  }
+
+  static const std::map<
+    std::string, std::map<std::string, cmCTestHardwareAllocator::Resource>>
+    expected4{
+      /* clang-format off */
+      { "gpus", {
+        { "0", { 4, 4 } },
+        { "1", { 8, 7 } },
+        { "2", { 0, 0 } },
+        { "3", { 8, 0 } },
+      } },
+      /* clang-format on */
+    };
+  if (!allocator.AllocateResource("gpus", "1", 7)) {
+    std::cout
+      << "AllocateResource(\"gpus\", \"1\", 7) returned false, should be "
+         "true\n";
+    retval = false;
+  }
+  if (allocator.AllocateResource("gpus", "1", 2)) {
+    std::cout
+      << "AllocateResource(\"gpus\", \"1\", 2) returned true, should be "
+         "false\n";
+    retval = false;
+  }
+  if (allocator.GetResources() != expected4) {
+    std::cout << "GetResources() did not return expected value\n";
+    retval = false;
+  }
+
+  static const std::map<
+    std::string, std::map<std::string, cmCTestHardwareAllocator::Resource>>
+    expected5{
+      /* clang-format off */
+      { "gpus", {
+        { "0", { 4, 4 } },
+        { "1", { 8, 7 } },
+        { "2", { 0, 0 } },
+        { "3", { 8, 0 } },
+      } },
+      /* clang-format on */
+    };
+  if (allocator.AllocateResource("gpus", "2", 1)) {
+    std::cout
+      << "AllocateResource(\"gpus\", \"2\", 1) returned true, should be "
+         "false\n";
+    retval = false;
+  }
+  if (allocator.GetResources() != expected5) {
+    std::cout << "GetResources() did not return expected value\n";
+    retval = false;
+  }
+
+  static const std::map<
+    std::string, std::map<std::string, cmCTestHardwareAllocator::Resource>>
+    expected6{
+      /* clang-format off */
+      { "gpus", {
+        { "0", { 4, 4 } },
+        { "1", { 8, 7 } },
+        { "2", { 0, 0 } },
+        { "3", { 8, 0 } },
+      } },
+      /* clang-format on */
+    };
+  if (allocator.AllocateResource("gpus", "4", 1)) {
+    std::cout
+      << "AllocateResource(\"gpus\", \"4\", 1) returned true, should be "
+         "false\n";
+    retval = false;
+  }
+  if (allocator.GetResources() != expected6) {
+    std::cout << "GetResources() did not return expected value\n";
+    retval = false;
+  }
+
+  static const std::map<
+    std::string, std::map<std::string, cmCTestHardwareAllocator::Resource>>
+    expected7{
+      /* clang-format off */
+      { "gpus", {
+        { "0", { 4, 4 } },
+        { "1", { 8, 7 } },
+        { "2", { 0, 0 } },
+        { "3", { 8, 0 } },
+      } },
+      /* clang-format on */
+    };
+  if (allocator.AllocateResource("threads", "0", 1)) {
+    std::cout
+      << "AllocateResource(\"threads\", \"0\", 1) returned true, should be"
+         " false\n";
+    retval = false;
+  }
+  if (allocator.GetResources() != expected7) {
+    std::cout << "GetResources() did not return expected value\n";
+    retval = false;
+  }
+
+  return retval;
+}
+
+bool testDeallocateResource()
+{
+  bool retval = true;
+
+  cmCTestHardwareAllocator allocator;
+  allocator.InitializeFromHardwareSpec(spec);
+
+  static const std::map<
+    std::string, std::map<std::string, cmCTestHardwareAllocator::Resource>>
+    expected1{
+      /* clang-format off */
+      { "gpus", {
+        { "0", { 4, 1 } },
+        { "1", { 8, 0 } },
+        { "2", { 0, 0 } },
+        { "3", { 8, 0 } },
+      } },
+      /* clang-format on */
+    };
+  if (!allocator.AllocateResource("gpus", "0", 2)) {
+    std::cout
+      << "AllocateResource(\"gpus\", \"0\", 2) returned false, should be "
+         "true\n";
+    retval = false;
+  }
+  if (!allocator.DeallocateResource("gpus", "0", 1)) {
+    std::cout
+      << "DeallocateResource(\"gpus\", \"0\", 1) returned false, should be"
+         " true\n";
+    retval = false;
+  }
+  if (allocator.GetResources() != expected1) {
+    std::cout << "GetResources() did not return expected value\n";
+    retval = false;
+  }
+
+  static const std::map<
+    std::string, std::map<std::string, cmCTestHardwareAllocator::Resource>>
+    expected2{
+      /* clang-format off */
+      { "gpus", {
+        { "0", { 4, 1 } },
+        { "1", { 8, 0 } },
+        { "2", { 0, 0 } },
+        { "3", { 8, 0 } },
+      } },
+      /* clang-format on */
+    };
+  if (allocator.DeallocateResource("gpus", "0", 2)) {
+    std::cout
+      << "DeallocateResource(\"gpus\", \"0\", 2) returned true, should be"
+         " false\n";
+    retval = false;
+  }
+  if (allocator.GetResources() != expected2) {
+    std::cout << "GetResources() did not return expected value\n";
+    retval = false;
+  }
+
+  static const std::map<
+    std::string, std::map<std::string, cmCTestHardwareAllocator::Resource>>
+    expected3{
+      /* clang-format off */
+      { "gpus", {
+        { "0", { 4, 0 } },
+        { "1", { 8, 0 } },
+        { "2", { 0, 0 } },
+        { "3", { 8, 0 } },
+      } },
+      /* clang-format on */
+    };
+  if (!allocator.DeallocateResource("gpus", "0", 1)) {
+    std::cout
+      << "DeallocateResource(\"gpus\", \"0\", 1) returned false, should be"
+         " true\n";
+    retval = false;
+  }
+  if (allocator.GetResources() != expected3) {
+    std::cout << "GetResources() did not return expected value\n";
+    retval = false;
+  }
+
+  static const std::map<
+    std::string, std::map<std::string, cmCTestHardwareAllocator::Resource>>
+    expected4{
+      /* clang-format off */
+      { "gpus", {
+        { "0", { 4, 0 } },
+        { "1", { 8, 0 } },
+        { "2", { 0, 0 } },
+        { "3", { 8, 0 } },
+      } },
+      /* clang-format on */
+    };
+  if (allocator.DeallocateResource("gpus", "0", 1)) {
+    std::cout
+      << "DeallocateResource(\"gpus\", \"0\", 1) returned true, should be"
+         " false\n";
+    retval = false;
+  }
+  if (allocator.GetResources() != expected4) {
+    std::cout << "GetResources() did not return expected value\n";
+    retval = false;
+  }
+
+  static const std::map<
+    std::string, std::map<std::string, cmCTestHardwareAllocator::Resource>>
+    expected5{
+      /* clang-format off */
+      { "gpus", {
+        { "0", { 4, 0 } },
+        { "1", { 8, 0 } },
+        { "2", { 0, 0 } },
+        { "3", { 8, 0 } },
+      } },
+      /* clang-format on */
+    };
+  if (allocator.DeallocateResource("gpus", "4", 1)) {
+    std::cout
+      << "DeallocateResource(\"gpus\", \"4\", 1) returned true, should be"
+         " false\n";
+    retval = false;
+  }
+  if (allocator.GetResources() != expected5) {
+    std::cout << "GetResources() did not return expected value\n";
+    retval = false;
+  }
+
+  static const std::map<
+    std::string, std::map<std::string, cmCTestHardwareAllocator::Resource>>
+    expected6{
+      /* clang-format off */
+      { "gpus", {
+        { "0", { 4, 0 } },
+        { "1", { 8, 0 } },
+        { "2", { 0, 0 } },
+        { "3", { 8, 0 } },
+      } },
+      /* clang-format on */
+    };
+  if (allocator.DeallocateResource("threads", "0", 1)) {
+    std::cout
+      << "DeallocateResource(\"threads\", \"0\", 1) returned true, should be"
+         " false\n";
+    retval = false;
+  }
+  if (allocator.GetResources() != expected6) {
+    std::cout << "GetResources() did not return expected value\n";
+    retval = false;
+  }
+
+  return retval;
+}
+
+bool testResourceFree()
+{
+  bool retval = true;
+
+  const cmCTestHardwareAllocator::Resource r1{ 5, 0 };
+  if (r1.Free() != 5) {
+    std::cout << "cmCTestHardwareAllocator::Resource::Free() did not return "
+                 "expected value for { 5, 0 }\n";
+    retval = false;
+  }
+
+  const cmCTestHardwareAllocator::Resource r2{ 3, 2 };
+  if (r2.Free() != 1) {
+    std::cout << "cmCTestHardwareAllocator::Resource::Free() did not return "
+                 "expected value for { 3, 2 }\n";
+    retval = false;
+  }
+
+  const cmCTestHardwareAllocator::Resource r3{ 4, 4 };
+  if (r3.Free() != 0) {
+    std::cout << "cmCTestHardwareAllocator::Resource::Free() did not return "
+                 "expected value for { 4, 4 }\n";
+    retval = false;
+  }
+
+  return retval;
+}
+
+int testCTestHardwareAllocator(int, char** const)
+{
+  int retval = 0;
+
+  if (!testInitializeFromHardwareSpec()) {
+    std::cout << "in testInitializeFromHardwareSpec()\n";
+    retval = -1;
+  }
+
+  if (!testAllocateResource()) {
+    std::cout << "in testAllocateResource()\n";
+    retval = -1;
+  }
+
+  if (!testDeallocateResource()) {
+    std::cout << "in testDeallocateResource()\n";
+    retval = -1;
+  }
+
+  if (!testResourceFree()) {
+    std::cout << "in testResourceFree()\n";
+    retval = -1;
+  }
+
+  return retval;
+}

+ 84 - 0
Tests/CMakeLib/testCTestHardwareSpec.cxx

@@ -0,0 +1,84 @@
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "cmCTestHardwareSpec.h"
+
+struct ExpectedSpec
+{
+  std::string Path;
+  bool ParseResult;
+  cmCTestHardwareSpec Expected;
+};
+
+static const std::vector<ExpectedSpec> expectedHardwareSpecs = {
+  /* clang-format off */
+  {"spec1.json", true, {{{
+    {"gpus", {
+      {"2", 4},
+      {"e", 1},
+    }},
+    {"threads", {
+    }},
+  }}}},
+  {"spec2.json", true, {{{
+  }}}},
+  {"spec3.json", false, {{{}}}},
+  {"spec4.json", false, {{{}}}},
+  {"spec5.json", false, {{{}}}},
+  {"spec6.json", false, {{{}}}},
+  {"spec7.json", false, {{{}}}},
+  {"spec8.json", false, {{{}}}},
+  {"spec9.json", false, {{{}}}},
+  {"spec10.json", false, {{{}}}},
+  {"spec11.json", false, {{{}}}},
+  {"spec12.json", false, {{{}}}},
+  {"spec13.json", false, {{{}}}},
+  {"spec14.json", true, {{{}}}},
+  {"spec15.json", true, {{{}}}},
+  {"spec16.json", true, {{{}}}},
+  {"spec17.json", false, {{{}}}},
+  {"spec18.json", false, {{{}}}},
+  {"noexist.json", false, {{{}}}},
+  /* clang-format on */
+};
+
+static bool testSpec(const std::string& path, bool expectedResult,
+                     const cmCTestHardwareSpec& expected)
+{
+  cmCTestHardwareSpec actual;
+  bool result = actual.ReadFromJSONFile(path);
+  if (result != expectedResult) {
+    std::cout << "ReadFromJSONFile(\"" << path << "\") returned " << result
+              << ", should be " << expectedResult << std::endl;
+    return false;
+  }
+
+  if (result && actual != expected) {
+    std::cout << "ReadFromJSONFile(\"" << path
+              << "\") did not give expected spec" << std::endl;
+    return false;
+  }
+
+  return true;
+}
+
+int testCTestHardwareSpec(int argc, char** const argv)
+{
+  if (argc < 2) {
+    std::cout << "Invalid arguments.\n";
+    return -1;
+  }
+
+  int retval = 0;
+  for (auto const& spec : expectedHardwareSpecs) {
+    std::string path = argv[1];
+    path += "/testCTestHardwareSpec_data/";
+    path += spec.Path;
+    if (!testSpec(path, spec.ParseResult, spec.Expected)) {
+      retval = -1;
+    }
+  }
+
+  return retval;
+}

+ 23 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec1.json

@@ -0,0 +1,23 @@
+{
+  "local": [
+    {
+      "gpus": [
+        {
+          "id": "2",
+          "slots": 4
+        },
+        {
+          "id": "e"
+        }
+      ],
+      ".reserved": [
+        {
+          "id": "a",
+          "slots": 3
+        }
+      ],
+      "threads": [
+      ]
+    }
+  ]
+}

+ 11 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec10.json

@@ -0,0 +1,11 @@
+{
+  "local": [
+    {
+      "gpus": [
+        {
+          "id": 4
+        }
+      ]
+    }
+  ]
+}

+ 12 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec11.json

@@ -0,0 +1,12 @@
+{
+  "local": [
+    {
+      "gpus": [
+        {
+          "id": "4",
+          "slots": "giraffe"
+        }
+      ]
+    }
+  ]
+}

+ 1 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec12.json

@@ -0,0 +1 @@
+[]

+ 1 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec13.json

@@ -0,0 +1 @@
+not json

+ 8 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec14.json

@@ -0,0 +1,8 @@
+{
+  "local": [
+    {
+      "0": [
+      ]
+    }
+  ]
+}

+ 8 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec15.json

@@ -0,0 +1,8 @@
+{
+  "local": [
+    {
+      "-": [
+      ]
+    }
+  ]
+}

+ 8 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec16.json

@@ -0,0 +1,8 @@
+{
+  "local": [
+    {
+      "A": [
+      ]
+    }
+  ]
+}

+ 11 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec17.json

@@ -0,0 +1,11 @@
+{
+  "local": [
+    {
+      "gpus": [
+        {
+          "id": "A"
+        }
+      ]
+    }
+  ]
+}

+ 11 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec18.json

@@ -0,0 +1,11 @@
+{
+  "local": [
+    {
+      "gpus": [
+        {
+          "id": "-"
+        }
+      ]
+    }
+  ]
+}

+ 4 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec2.json

@@ -0,0 +1,4 @@
+{
+  "local": [
+  ]
+}

+ 8 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec3.json

@@ -0,0 +1,8 @@
+{
+  "local": [
+    {
+    },
+    {
+    }
+  ]
+}

+ 4 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec4.json

@@ -0,0 +1,4 @@
+{
+  "local": {
+  }
+}

+ 2 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec5.json

@@ -0,0 +1,2 @@
+{
+}

+ 5 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec6.json

@@ -0,0 +1,5 @@
+{
+  "local": [
+    []
+  ]
+}

+ 8 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec7.json

@@ -0,0 +1,8 @@
+{
+  "local": [
+    {
+      "gpus": {
+      }
+    }
+  ]
+}

+ 9 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec8.json

@@ -0,0 +1,9 @@
+{
+  "local": [
+    {
+      "gpus": [
+        []
+      ]
+    }
+  ]
+}

+ 10 - 0
Tests/CMakeLib/testCTestHardwareSpec_data/spec9.json

@@ -0,0 +1,10 @@
+{
+  "local": [
+    {
+      "gpus": [
+        {
+        }
+      ]
+    }
+  ]
+}

+ 137 - 0
Tests/CMakeLib/testCTestProcesses.cxx

@@ -0,0 +1,137 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "cmCTestTestHandler.h"
+
+struct ExpectedParseResult
+{
+  std::string String;
+  bool ExpectedReturnValue;
+  std::vector<std::vector<cmCTestTestHandler::cmCTestTestResourceRequirement>>
+    ExpectedValue;
+};
+
+static const std::vector<ExpectedParseResult> expectedResults{
+  /* clang-format off */
+  { "threads:2", true, {
+    { { "threads", 2, 1 } },
+  } },
+  { "3,threads:2", true, {
+    { { "threads", 2, 1 } },
+    { { "threads", 2, 1 } },
+    { { "threads", 2, 1 } },
+  } },
+  { "3,threads:2,gpus:4", true, {
+    { { "threads", 2, 1 }, { "gpus", 4, 1 } },
+    { { "threads", 2, 1 }, { "gpus", 4, 1 } },
+    { { "threads", 2, 1 }, { "gpus", 4, 1 } },
+  } },
+  { "2,threads:2;gpus:4", true, {
+    { { "threads", 2, 1 } },
+    { { "threads", 2, 1 } },
+    { { "gpus", 4, 1 } },
+  } },
+  { "threads:2;2,gpus:4", true, {
+    { { "threads", 2, 1 } },
+    { { "gpus", 4, 1 } },
+    { { "gpus", 4, 1 } },
+  } },
+  { "threads:2;gpus:4", true, {
+    { { "threads", 2, 1 } },
+    { { "gpus", 4, 1 } },
+  } },
+  { "1,threads:2;0,gpus:4", true, {
+    { { "threads", 2, 1 } },
+  } },
+  { "1,_:1", true, {
+    { { "_", 1, 1 } },
+  } },
+  { "1,a:1", true, {
+    { { "a", 1, 1 } },
+  } },
+  { "2", true, {
+    {},
+    {},
+  } },
+  { "1;2,threads:1", true, {
+    {},
+    { { "threads", 1, 1 } },
+    { { "threads", 1, 1 } },
+  } },
+  { "1,,threads:1", true, {
+    { { "threads", 1, 1 } },
+  } },
+  { ";1,threads:1", true, {
+    { { "threads", 1, 1 } },
+  } },
+  { "1,threads:1;", true, {
+    { { "threads", 1, 1 } },
+  } },
+  { "1,threads:1,", true, {
+    { { "threads", 1, 1 } },
+  } },
+  { "threads:1;;threads:2", true, {
+    { { "threads", 1, 1 } },
+    { { "threads", 2, 1 } },
+  } },
+  { "1,", true, {
+    {},
+  } },
+  { ";", true, {} },
+  { "", true, {} },
+  { ",", false, {} },
+  { "1,0:1", false, {} },
+  { "1,A:1", false, {} },
+  { "1,a-b:1", false, {} },
+  { "invalid", false, {} },
+  { ",1,invalid:1", false, {} },
+  { "1,1", false, {} },
+  { "-1,invalid:1", false, {} },
+  { "1,invalid:*", false, {} },
+  { "1,invalid:-1", false, {} },
+  { "1,invalid:-", false, {} },
+  { "1,invalid:ab2", false, {} },
+  { "1,invalid :2", false, {} },
+  { "1, invalid:2", false, {} },
+  { "1,invalid:ab", false, {} },
+  /* clang-format on */
+};
+
+bool TestExpectedParseResult(const ExpectedParseResult& expected)
+{
+  std::vector<std::vector<cmCTestTestHandler::cmCTestTestResourceRequirement>>
+    result;
+  bool retval;
+  if ((retval = cmCTestTestHandler::ParseProcessesProperty(
+         expected.String, result)) != expected.ExpectedReturnValue) {
+    std::cout << "ParseProcessesProperty(\"" << expected.String
+              << "\") returned " << retval << ", should be "
+              << expected.ExpectedReturnValue << std::endl;
+    return false;
+  }
+
+  if (result != expected.ExpectedValue) {
+    std::cout << "ParseProcessesProperty(\"" << expected.String
+              << "\") did not yield expected set of processes" << std::endl;
+    return false;
+  }
+
+  return true;
+}
+
+int testCTestProcesses(int /*unused*/, char* /*unused*/ [])
+{
+  int retval = 0;
+
+  for (auto const& expected : expectedResults) {
+    if (!TestExpectedParseResult(expected)) {
+      retval = 1;
+    }
+  }
+
+  return retval;
+}

+ 38 - 0
Tests/RunCMake/CMakeLists.txt

@@ -335,6 +335,44 @@ add_RunCMake_test(no_install_prefix)
 add_RunCMake_test(configure_file)
 add_RunCMake_test(CTestTimeoutAfterMatch)
 
+# cthwalloc links against CMakeLib and CTestLib, which means it can't be built
+# if CMake_TEST_EXTERNAL_CMAKE is activated (the compiler might be different.)
+# So, it has to be provided in the original build tree.
+if(CMake_TEST_EXTERNAL_CMAKE)
+  set(no_package_root_path)
+  if(NOT CMAKE_VERSION VERSION_LESS 3.12)
+    set(no_package_root_path NO_PACKAGE_ROOT_PATH)
+  endif()
+  find_program(cthwalloc cthwalloc PATHS ${CMake_TEST_EXTERNAL_CMAKE}
+    NO_DEFAULT_PATH
+    ${no_package_root_path}
+    NO_CMAKE_PATH
+    NO_CMAKE_ENVIRONMENT_PATH
+    NO_SYSTEM_ENVIRONMENT_PATH
+    NO_CMAKE_SYSTEM_PATH
+    NO_CMAKE_FIND_ROOT_PATH
+    )
+  if(cthwalloc)
+    add_executable(cthwalloc IMPORTED)
+    set_property(TARGET cthwalloc PROPERTY IMPORTED_LOCATION ${cthwalloc})
+  endif()
+else()
+  add_executable(cthwalloc CTestHardwareAllocation/cthwalloc.cxx)
+  target_link_libraries(cthwalloc CTestLib)
+  target_include_directories(cthwalloc PRIVATE
+    ${CMake_BINARY_DIR}/Source
+    ${CMake_SOURCE_DIR}/Source
+    ${CMake_SOURCE_DIR}/Source/CTest
+    )
+  set_property(TARGET cthwalloc PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMake_BIN_DIR})
+endif()
+
+if(TARGET cthwalloc)
+  add_RunCMake_test(CTestHardwareAllocation -DCTHWALLOC_COMMAND=$<TARGET_FILE:cthwalloc>)
+else()
+  message(WARNING "Could not find or build cthwalloc")
+endif()
+
 find_package(Qt4 QUIET)
 find_package(Qt5Core QUIET)
 if (QT4_FOUND AND Qt5Core_FOUND AND NOT Qt5Core_VERSION VERSION_LESS 5.1.0)

+ 1 - 0
Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake

@@ -258,6 +258,7 @@ function(run_ShowOnly)
     add_test(ShowOnly \"${CMAKE_COMMAND}\" -E echo)
     set_tests_properties(ShowOnly PROPERTIES
       WILL_FAIL true
+      PROCESSES \"2,threads:2,gpus:4;gpus:2,threads:4\"
       REQUIRED_FILES RequiredFileDoesNotExist
       _BACKTRACE_TRIPLES \"file1;1;add_test;file0;;\"
       )

+ 61 - 4
Tests/RunCMake/CTestCommandLine/show-only_json-v1_check.py

@@ -80,6 +80,62 @@ def check_willfail_property(p):
     assert p["name"] == "WILL_FAIL"
     assert p["value"] == True
 
+def check_processes_property(p):
+    assert is_dict(p)
+    assert sorted(p.keys()) == ["name", "value"]
+    assert is_string(p["name"])
+    assert is_list(p["value"])
+    assert p["name"] == "PROCESSES"
+    assert len(p["value"]) == 3
+
+    assert is_dict(p["value"][0])
+    assert sorted(p["value"][0].keys()) == ["requirements"]
+    assert is_list(p["value"][0]["requirements"])
+    assert len(p["value"][0]["requirements"]) == 2
+    assert is_dict(p["value"][0]["requirements"][0])
+    assert sorted(p["value"][0]["requirements"][0].keys()) == \
+        [".type", "slots"]
+    assert is_string(p["value"][0]["requirements"][0][".type"])
+    assert p["value"][0]["requirements"][0][".type"] == "threads"
+    assert is_int(p["value"][0]["requirements"][0]["slots"])
+    assert p["value"][0]["requirements"][0]["slots"] == 2
+    assert is_string(p["value"][0]["requirements"][1][".type"])
+    assert p["value"][0]["requirements"][1][".type"] == "gpus"
+    assert is_int(p["value"][0]["requirements"][1]["slots"])
+    assert p["value"][0]["requirements"][1]["slots"] == 4
+
+    assert is_dict(p["value"][1])
+    assert sorted(p["value"][1].keys()) == ["requirements"]
+    assert is_list(p["value"][1]["requirements"])
+    assert len(p["value"][1]["requirements"]) == 2
+    assert is_dict(p["value"][1]["requirements"][0])
+    assert sorted(p["value"][1]["requirements"][0].keys()) == \
+        [".type", "slots"]
+    assert is_string(p["value"][1]["requirements"][0][".type"])
+    assert p["value"][1]["requirements"][0][".type"] == "threads"
+    assert is_int(p["value"][1]["requirements"][0]["slots"])
+    assert p["value"][1]["requirements"][0]["slots"] == 2
+    assert is_string(p["value"][1]["requirements"][1][".type"])
+    assert p["value"][1]["requirements"][1][".type"] == "gpus"
+    assert is_int(p["value"][1]["requirements"][1]["slots"])
+    assert p["value"][1]["requirements"][1]["slots"] == 4
+
+    assert is_dict(p["value"][2])
+    assert sorted(p["value"][2].keys()) == ["requirements"]
+    assert is_list(p["value"][2]["requirements"])
+    assert len(p["value"][2]["requirements"]) == 2
+    assert is_dict(p["value"][2]["requirements"][0])
+    assert sorted(p["value"][2]["requirements"][0].keys()) == \
+        [".type", "slots"]
+    assert is_string(p["value"][2]["requirements"][0][".type"])
+    assert p["value"][2]["requirements"][0][".type"] == "gpus"
+    assert is_int(p["value"][2]["requirements"][0]["slots"])
+    assert p["value"][2]["requirements"][0]["slots"] == 2
+    assert is_string(p["value"][2]["requirements"][1][".type"])
+    assert p["value"][2]["requirements"][1][".type"] == "threads"
+    assert is_int(p["value"][2]["requirements"][1]["slots"])
+    assert p["value"][2]["requirements"][1]["slots"] == 4
+
 def check_workingdir_property(p):
     assert is_dict(p)
     assert sorted(p.keys()) == ["name", "value"]
@@ -90,10 +146,11 @@ def check_workingdir_property(p):
 
 def check_properties(p):
     assert is_list(p)
-    assert len(p) == 3
-    check_reqfiles_property(p[0])
-    check_willfail_property(p[1])
-    check_workingdir_property(p[2])
+    assert len(p) == 4
+    check_processes_property(p[0])
+    check_reqfiles_property(p[1])
+    check_willfail_property(p[2])
+    check_workingdir_property(p[3])
 
 def check_tests(t):
     assert is_list(t)

+ 9 - 0
Tests/RunCMake/CTestHardwareAllocation/CMakeLists.txt.in

@@ -0,0 +1,9 @@
+cmake_minimum_required(VERSION 3.15)
+set(CASE_NAME "@CASE_NAME@")
+if(CASE_NAME MATCHES "^(.*)-ctest-s")
+  set(projname "${CMAKE_MATCH_1}")
+  project(${projname} NONE)
+  include(CTest)
+  include("@RunCMake_SOURCE_DIR@/HardwareCommon.cmake")
+  include("@RunCMake_SOURCE_DIR@/${projname}.cmake")
+endif()

+ 23 - 0
Tests/RunCMake/CTestHardwareAllocation/HardwareCommon.cmake

@@ -0,0 +1,23 @@
+function(setup_hardware_tests)
+  if(CTEST_HARDWARE_ALLOC_ENABLED)
+    add_test(NAME HardwareSetup COMMAND "${CMAKE_COMMAND}" -E remove -f "${CMAKE_BINARY_DIR}/cthwalloc.log")
+  endif()
+endfunction()
+
+function(add_hardware_test name sleep_time proc)
+  if(CTEST_HARDWARE_ALLOC_ENABLED)
+    add_test(NAME "${name}" COMMAND "${CTHWALLOC_COMMAND}" write "${CMAKE_BINARY_DIR}/cthwalloc.log" "${name}" "${sleep_time}" "${proc}")
+    set_property(TEST "${name}" PROPERTY DEPENDS HardwareSetup)
+  else()
+    add_test(NAME "${name}" COMMAND "${CTHWALLOC_COMMAND}" write "${CMAKE_BINARY_DIR}/cthwalloc.log" "${name}" "${sleep_time}")
+  endif()
+  set_property(TEST "${name}" PROPERTY PROCESSES "${proc}")
+  list(APPEND HARDWARE_TESTS "${name}")
+  set(HARDWARE_TESTS "${HARDWARE_TESTS}" PARENT_SCOPE)
+endfunction()
+
+function(cleanup_hardware_tests)
+  if(CTEST_HARDWARE_ALLOC_ENABLED)
+    file(WRITE "${CMAKE_BINARY_DIR}/hwtests.txt" "${HARDWARE_TESTS}")
+  endif()
+endfunction()

+ 167 - 0
Tests/RunCMake/CTestHardwareAllocation/RunCMakeTest.cmake

@@ -0,0 +1,167 @@
+include(RunCMake)
+include(RunCTest)
+
+###############################################################################
+# Test cthwalloc itself - we want to make sure it's not just rubber-stamping
+# the test results
+###############################################################################
+
+function(cthwalloc_verify_log expected_contents)
+  if(NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/cthwalloc.log")
+    string(APPEND RunCMake_TEST_FAILED "Log file was not written\n")
+    set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
+    return()
+  endif()
+  file(READ "${RunCMake_TEST_BINARY_DIR}/cthwalloc.log" actual_contents)
+  if(NOT actual_contents STREQUAL expected_contents)
+    string(APPEND RunCMake_TEST_FAILED "Actual log did not match expected log\n")
+    set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
+  endif()
+endfunction()
+
+function(run_cthwalloc_write_proc name proc)
+  file(REMOVE "${RunCMake_BINARY_DIR}/${name}-build/cthwalloc.log")
+  run_cthwalloc_write_proc_nodel("${name}" "${proc}" "${ARGN}")
+endfunction()
+
+function(run_cthwalloc_write_proc_nodel name proc)
+  string(REPLACE ";" "\\;" proc "${proc}")
+  run_cmake_command(${name} "${CMAKE_COMMAND}" -E env "${ARGN}" "${CTHWALLOC_COMMAND}" write "${RunCMake_BINARY_DIR}/${name}-build/cthwalloc.log" "${name}" 0 "${proc}")
+endfunction()
+
+function(run_cthwalloc_write_noproc name)
+  run_cmake_command(${name} "${CMAKE_COMMAND}" -E env "${ARGN}" "${CTHWALLOC_COMMAND}" write "${RunCMake_BINARY_DIR}/${name}-build/cthwalloc.log" "${name}" 0)
+endfunction()
+
+function(run_cthwalloc_verify name tests)
+  string(REPLACE ";" "\\;" tests "${tests}")
+  run_cmake_command(${name} "${CTHWALLOC_COMMAND}" verify "${RunCMake_SOURCE_DIR}/${name}.log" "${CMAKE_CURRENT_LIST_DIR}/hwspec.json" "${tests}")
+endfunction()
+
+unset(ENV{CTEST_PROCESS_COUNT})
+set(RunCMake_TEST_NO_CLEAN 1)
+file(REMOVE_RECURSE "${RunCMake_BINARY_DIR}/cthwalloc-write-proc-good1-build")
+file(MAKE_DIRECTORY "${RunCMake_BINARY_DIR}/cthwalloc-write-proc-good1-build")
+file(WRITE "${RunCMake_BINARY_DIR}/cthwalloc-write-proc-good1-build/cthwalloc.log"
+[[begin test1
+alloc widgets 0 1
+dealloc widgets 0 1
+end test1
+]])
+run_cthwalloc_write_proc_nodel(cthwalloc-write-proc-good1 "1,widgets:2,transmogrifiers:1;2,widgets:1,widgets:2"
+  CTEST_PROCESS_COUNT=3
+  CTEST_PROCESS_0=widgets,transmogrifiers
+  CTEST_PROCESS_0_WIDGETS=id:0,slots:2
+  CTEST_PROCESS_0_TRANSMOGRIFIERS=id:calvin,slots:1
+  CTEST_PROCESS_1=widgets
+  "CTEST_PROCESS_1_WIDGETS=id:0,slots:1\\;id:2,slots:2"
+  CTEST_PROCESS_2=widgets
+  "CTEST_PROCESS_2_WIDGETS=id:0,slots:1\\;id:2,slots:2"
+  )
+set(RunCMake_TEST_NO_CLEAN 0)
+run_cthwalloc_write_proc(cthwalloc-write-proc-good2 "widgets:8"
+  CTEST_PROCESS_COUNT=1
+  CTEST_PROCESS_0=widgets
+  CTEST_PROCESS_0_WIDGETS=id:3,slots:8
+  )
+run_cthwalloc_write_proc(cthwalloc-write-proc-nocount "widgets:8")
+run_cthwalloc_write_proc(cthwalloc-write-proc-badcount "widgets:8"
+  CTEST_PROCESS_COUNT=2
+  )
+run_cthwalloc_write_proc(cthwalloc-write-proc-nores "widgets:8"
+  CTEST_PROCESS_COUNT=1
+  )
+run_cthwalloc_write_proc(cthwalloc-write-proc-badres "widgets:8"
+  CTEST_PROCESS_COUNT=1
+  CTEST_PROCESS_0=widgets,transmogrifiers
+  )
+run_cthwalloc_write_proc(cthwalloc-write-proc-nowidgets "widgets:8"
+  CTEST_PROCESS_COUNT=1
+  CTEST_PROCESS_0=widgets
+  )
+run_cthwalloc_write_proc(cthwalloc-write-proc-badwidgets1 "widgets:8"
+  CTEST_PROCESS_COUNT=1
+  CTEST_PROCESS_0=widgets
+  CTEST_PROCESS_0_WIDGETS=
+  )
+run_cthwalloc_write_proc(cthwalloc-write-proc-badwidgets2 "widgets:8"
+  CTEST_PROCESS_COUNT=1
+  CTEST_PROCESS_0=widgets
+  "CTEST_PROCESS_0_WIDGETS=id:3,slots:8\\;id:0,slots:1"
+  )
+run_cthwalloc_write_proc(cthwalloc-write-proc-badwidgets3 "widgets:8"
+  CTEST_PROCESS_COUNT=1
+  CTEST_PROCESS_0=widgets
+  CTEST_PROCESS_0_WIDGETS=id:3,slots:7
+  )
+run_cthwalloc_write_proc(cthwalloc-write-proc-badwidgets4 "widgets:8"
+  CTEST_PROCESS_COUNT=1
+  CTEST_PROCESS_0=widgets
+  CTEST_PROCESS_0_WIDGETS=invalid
+  )
+run_cthwalloc_write_proc(cthwalloc-write-proc-badwidgets5 "widgets:2,widgets:2"
+  CTEST_PROCESS_COUNT=1
+  CTEST_PROCESS_0=widgets
+  "CTEST_PROCESS_0_WIDGETS=id:0,slots:2\\;id:0,slots:1"
+  )
+run_cthwalloc_write_proc(cthwalloc-write-proc-badwidgets6 "widgets:2"
+  CTEST_PROCESS_COUNT=1
+  CTEST_PROCESS_0=widgets
+  "CTEST_PROCESS_0_WIDGETS=id:0,slots:2\\;id:0,slots:1"
+  )
+run_cthwalloc_write_proc(cthwalloc-write-proc-badwidgets7 "widgets:2,widgets:2"
+  CTEST_PROCESS_COUNT=1
+  CTEST_PROCESS_0=widgets
+  CTEST_PROCESS_0_WIDGETS=id:0,slots:2
+  )
+
+run_cthwalloc_write_noproc(cthwalloc-write-noproc-good1)
+run_cthwalloc_write_noproc(cthwalloc-write-noproc-count
+  CTEST_PROCESS_COUNT=1
+  )
+
+run_cthwalloc_verify(cthwalloc-verify-good1 "test1;test2")
+run_cthwalloc_verify(cthwalloc-verify-good2 "")
+run_cthwalloc_verify(cthwalloc-verify-nolog "")
+run_cthwalloc_verify(cthwalloc-verify-nores "")
+run_cthwalloc_verify(cthwalloc-verify-noid "")
+run_cthwalloc_verify(cthwalloc-verify-notenough "")
+run_cthwalloc_verify(cthwalloc-verify-baddealloc "")
+run_cthwalloc_verify(cthwalloc-verify-leak "")
+run_cthwalloc_verify(cthwalloc-verify-badtest1 "")
+run_cthwalloc_verify(cthwalloc-verify-badtest2 "test1")
+run_cthwalloc_verify(cthwalloc-verify-badtest3 "test1")
+run_cthwalloc_verify(cthwalloc-verify-badtest4 "test1")
+run_cthwalloc_verify(cthwalloc-verify-badtest5 "test1")
+run_cthwalloc_verify(cthwalloc-verify-nobegin "test1")
+run_cthwalloc_verify(cthwalloc-verify-noend "test1")
+
+###############################################################################
+# Now test the hardware allocation feature of CTest
+###############################################################################
+
+function(run_ctest_hardware name parallel random)
+  run_ctest("${name}-ctest-s-hw" "-DCTEST_HARDWARE_ALLOC_ENABLED=1" "-DCTHWALLOC_COMMAND=${CTHWALLOC_COMMAND}" "-DCTEST_PARALLEL=${parallel}" "-DCTEST_RANDOM=${random}")
+  run_ctest("${name}-ctest-s-nohw" "-DCTEST_HARDWARE_ALLOC_ENABLED=0" "-DCTHWALLOC_COMMAND=${CTHWALLOC_COMMAND}" "-DCTEST_PARALLEL=${parallel}" "-DCTEST_RANDOM=${random}")
+endfunction()
+
+function(verify_ctest_hardware)
+  file(READ "${RunCMake_TEST_BINARY_DIR}/hwtests.txt" hwtests)
+  execute_process(COMMAND "${CTHWALLOC_COMMAND}" verify "${RunCMake_TEST_BINARY_DIR}/cthwalloc.log" "${CMAKE_CURRENT_LIST_DIR}/hwspec.json" "${hwtests}"
+    OUTPUT_VARIABLE output ERROR_QUIET RESULT_VARIABLE result)
+  if(result)
+    string(APPEND RunCMake_TEST_FAILED "${output}")
+    set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
+  endif()
+endfunction()
+
+run_ctest_hardware(lotsoftests 10 1)
+run_ctest_hardware(checkfree1 2 0)
+run_ctest_hardware(checkfree2 1 0)
+run_ctest_hardware(notenough1 1 0)
+run_ctest_hardware(notenough2 1 0)
+run_ctest_hardware(ensure_parallel 2 0)
+
+set(ENV{CTEST_PROCESS_COUNT} 2)
+run_ctest_hardware(process_count 1 0)
+unset(ENV{CTEST_PROCESS_COUNT})

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/checkfree1-ctest-s-hw-check.cmake

@@ -0,0 +1 @@
+verify_ctest_hardware()

+ 7 - 0
Tests/RunCMake/CTestHardwareAllocation/checkfree1.cmake

@@ -0,0 +1,7 @@
+setup_hardware_tests()
+
+add_hardware_test(Test1 1 "widgets:8")
+add_hardware_test(Test2 1 "fluxcapacitors:50;fluxcapacitors:50,widgets:8")
+add_hardware_test(Test3 1 "fluxcapacitors:121")
+
+cleanup_hardware_tests()

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/checkfree2-ctest-s-hw-check.cmake

@@ -0,0 +1 @@
+verify_ctest_hardware()

+ 8 - 0
Tests/RunCMake/CTestHardwareAllocation/checkfree2.cmake

@@ -0,0 +1,8 @@
+setup_hardware_tests()
+
+# This test is an attack on the hardware scheduling algorithm. It has been
+# carefully crafted to fool the algorithm into thinking there isn't sufficient
+# hardware for it.
+add_hardware_test(Test1 1 "widgets:2;4,widgets:4")
+
+cleanup_hardware_tests()

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-baddealloc-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-baddealloc.log

@@ -0,0 +1,2 @@
+alloc widgets 0 1
+dealloc widgets 0 2

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest1-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest1.log

@@ -0,0 +1 @@
+begin test1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest2-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest2.log

@@ -0,0 +1,2 @@
+begin test1
+begin test1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest3-result.txt

@@ -0,0 +1 @@
+1

+ 3 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest3.log

@@ -0,0 +1,3 @@
+begin test1
+end test1
+begin test1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest4-result.txt

@@ -0,0 +1 @@
+1

+ 3 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest4.log

@@ -0,0 +1,3 @@
+begin test1
+end test1
+end test1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest5-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-badtest5.log

@@ -0,0 +1 @@
+end test1

+ 14 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-good1.log

@@ -0,0 +1,14 @@
+begin test1
+alloc widgets 3 4
+alloc widgets 4 1
+alloc transmogrifiers calvin 2
+alloc fluxcapacitors outatime 121
+begin test2
+alloc widgets 3 4
+dealloc widgets 3 4
+dealloc widgets 4 1
+dealloc transmogrifiers calvin 2
+dealloc fluxcapacitors outatime 121
+end test1
+dealloc widgets 3 4
+end test2

+ 0 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-good2.log


+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-leak-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-leak.log

@@ -0,0 +1 @@
+alloc widgets 0 1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-nobegin-result.txt

@@ -0,0 +1 @@
+1

+ 0 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-nobegin.log


+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-noend-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-noend.log

@@ -0,0 +1 @@
+begin test1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-noid-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-noid.log

@@ -0,0 +1,2 @@
+alloc fluxcapacitors train 1
+dealloc fluxcapacitors train 1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-nolog-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-nores-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-nores.log

@@ -0,0 +1,2 @@
+alloc gpus 0 1
+dealloc gpus 0 1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-notenough-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-verify-notenough.log

@@ -0,0 +1,2 @@
+alloc widgets 0 8
+dealloc widgets 0 8

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-noproc-count-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badcount-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badres-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badwidgets1-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badwidgets2-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badwidgets3-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badwidgets4-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badwidgets5-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badwidgets6-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-badwidgets7-result.txt

@@ -0,0 +1 @@
+1

+ 20 - 0
Tests/RunCMake/CTestHardwareAllocation/cthwalloc-write-proc-good1-check.cmake

@@ -0,0 +1,20 @@
+cthwalloc_verify_log(
+[[begin test1
+alloc widgets 0 1
+dealloc widgets 0 1
+end test1
+begin cthwalloc-write-proc-good1
+alloc transmogrifiers calvin 1
+alloc widgets 0 2
+alloc widgets 0 1
+alloc widgets 2 2
+alloc widgets 0 1
+alloc widgets 2 2
+dealloc transmogrifiers calvin 1
+dealloc widgets 0 2
+dealloc widgets 0 1
+dealloc widgets 2 2
+dealloc widgets 0 1
+dealloc widgets 2 2
+end cthwalloc-write-proc-good1
+]])

Some files were not shown because too many files changed in this diff