Browse Source

Merge topic 'cmake-test-launcher'

88863d83d6 fileapi: Add test launcher to codemodel-v2
1ec0372ed4 add_test: Optionally use a launcher for tests running in-project targets
478a5f4e04 fileapi: Make launcher attribute 'arguments' optional
b44e38a397 cmFileAPICodemodel: Add missing std::move()

Acked-by: Kitware Robot <[email protected]>
Tested-by: buildbot <[email protected]>
Merge-request: !8963
Brad King 1 year ago
parent
commit
6ba3bb0563
37 changed files with 503 additions and 22 deletions
  1. 25 3
      Help/command/add_test.rst
  2. 11 0
      Help/envvar/CMAKE_TEST_LAUNCHER.rst
  3. 1 0
      Help/manual/cmake-env-variables.7.rst
  4. 4 0
      Help/manual/cmake-file-api.7.rst
  5. 1 0
      Help/manual/cmake-properties.7.rst
  6. 1 0
      Help/manual/cmake-variables.7.rst
  7. 2 1
      Help/prop_test/PASS_REGULAR_EXPRESSION.rst
  8. 2 1
      Help/prop_test/SKIP_REGULAR_EXPRESSION.rst
  9. 2 1
      Help/prop_test/SKIP_RETURN_CODE.rst
  10. 2 1
      Help/prop_test/WILL_FAIL.rst
  11. 20 0
      Help/prop_tgt/TEST_LAUNCHER.rst
  12. 7 0
      Help/release/dev/cmake-test-launcher.rst
  13. 16 0
      Help/variable/CMAKE_TEST_LAUNCHER.rst
  14. 12 7
      Source/cmFileAPICodemodel.cxx
  15. 1 0
      Source/cmTarget.cxx
  16. 12 0
      Source/cmTestGenerator.cxx
  17. 10 0
      Source/cmake.cxx
  18. 4 0
      Tests/RunCMake/CrosscompilingEmulator/AddTest-check.cmake
  19. 6 0
      Tests/RunCMake/CrosscompilingEmulator/AddTest.cmake
  20. 11 6
      Tests/RunCMake/FileAPI/codemodel-v2-check.py
  21. 2 1
      Tests/RunCMake/FileAPI/codemodel-v2-data/directories/cxx.cross.json
  22. 1 0
      Tests/RunCMake/FileAPI/codemodel-v2-data/directories/cxx.json
  23. 2 0
      Tests/RunCMake/FileAPI/codemodel-v2-data/projects/cxx.json
  24. 8 0
      Tests/RunCMake/FileAPI/codemodel-v2-data/targets/all_build_cxx.json
  25. 8 0
      Tests/RunCMake/FileAPI/codemodel-v2-data/targets/all_build_top.json
  26. 0 1
      Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_cross_emulator.json
  27. 105 0
      Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_test_launcher.json
  28. 109 0
      Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_test_launcher_and_cross_emulator.json
  29. 3 0
      Tests/RunCMake/FileAPI/cxx/CMakeLists.txt
  30. 4 0
      Tests/RunCMake/FileAPI/cxx/cross/CMakeLists.txt
  31. 6 0
      Tests/RunCMake/add_test/RunCMakeTest.cmake
  32. 28 0
      Tests/RunCMake/add_test/TestLauncher-check.cmake
  33. 22 0
      Tests/RunCMake/add_test/TestLauncher.cmake
  34. 9 0
      Tests/RunCMake/add_test/TestLauncher/CMakeLists.txt
  35. 41 0
      Tests/RunCMake/add_test/TestLauncherProperty.cmake
  36. 4 0
      Tests/RunCMake/add_test/simple_src_exiterror.cxx
  37. 1 0
      Tests/RunCMake/property_init/Executable.cmake

+ 25 - 3
Help/command/add_test.rst

@@ -27,9 +27,31 @@ directory the test is created in.
 ``add_test`` options are:
 
 ``COMMAND``
-  Specify the test command-line.  If ``<command>`` specifies an executable
-  target created by :command:`add_executable`, it will automatically be
-  replaced by the location of the executable created at build time.
+  Specify the test command-line.
+
+  If ``<command>`` specifies an executable target created by
+  :command:`add_executable`:
+
+  * It will automatically be replaced by the location of the executable
+    created at build time.
+
+  * .. versionadded:: 3.3
+
+      The target's :prop_tgt:`CROSSCOMPILING_EMULATOR`, if set, will be
+      used to run the command on the host::
+
+        <emulator> <command>
+
+  * .. versionadded:: 3.29
+
+      The target's :prop_tgt:`TEST_LAUNCHER`, if set, will be
+      used to launch the command::
+
+        <launcher> <command>
+
+      If the :prop_tgt:`CROSSCOMPILING_EMULATOR` is also set, both are used::
+
+        <launcher> <emulator> <command>
 
   The command may be specified using
   :manual:`generator expressions <cmake-generator-expressions(7)>`.

+ 11 - 0
Help/envvar/CMAKE_TEST_LAUNCHER.rst

@@ -0,0 +1,11 @@
+CMAKE_TEST_LAUNCHER
+-------------------
+
+.. versionadded:: 3.29
+
+.. include:: ENV_VAR.txt
+
+The default value for the :variable:`CMAKE_TEST_LAUNCHER` variable when there
+is no explicit configuration given on the first run while creating a new
+build tree.  On later runs in an existing build tree the value persists in
+the cache as :variable:`CMAKE_TEST_LAUNCHER`.

+ 1 - 0
Help/manual/cmake-env-variables.7.rst

@@ -56,6 +56,7 @@ Environment Variables that Control the Build
    /envvar/CMAKE_MSVCIDE_RUN_PATH
    /envvar/CMAKE_NO_VERBOSE
    /envvar/CMAKE_OSX_ARCHITECTURES
+   /envvar/CMAKE_TEST_LAUNCHER
    /envvar/CMAKE_TOOLCHAIN_FILE
    /envvar/DESTDIR
    /envvar/LDFLAGS

+ 4 - 0
Help/manual/cmake-file-api.7.rst

@@ -1022,6 +1022,10 @@ with members:
         An emulator for the target platform when cross-compiling.
         See the :prop_tgt:`CROSSCOMPILING_EMULATOR` target property.
 
+      ``test``
+        A start program for the execution of tests.
+        See the :prop_tgt:`TEST_LAUNCHER` target property.
+
     This field was added in codemodel version 2.7.
 
 ``link``

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

@@ -398,6 +398,7 @@ Properties on Targets
    /prop_tgt/Swift_MODULE_DIRECTORY
    /prop_tgt/Swift_MODULE_NAME
    /prop_tgt/SYSTEM
+   /prop_tgt/TEST_LAUNCHER
    /prop_tgt/TYPE
    /prop_tgt/UNITY_BUILD
    /prop_tgt/UNITY_BUILD_BATCH_SIZE

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

@@ -117,6 +117,7 @@ Variables that Provide Information
    /variable/CMAKE_Swift_COMPILATION_MODE
    /variable/CMAKE_Swift_MODULE_DIRECTORY
    /variable/CMAKE_Swift_NUM_THREADS
+   /variable/CMAKE_TEST_LAUNCHER
    /variable/CMAKE_TOOLCHAIN_FILE
    /variable/CMAKE_TWEAK_VERSION
    /variable/CMAKE_VERBOSE_MAKEFILE

+ 2 - 1
Help/prop_test/PASS_REGULAR_EXPRESSION.rst

@@ -23,7 +23,8 @@ Example:
 To run a test that may have a system-level failure, but still pass if
 ``PASS_REGULAR_EXPRESSION`` matches, use a CMake command to wrap the
 executable run. Note that this will prevent automatic handling of the
-:prop_tgt:`CROSSCOMPILING_EMULATOR` target property.
+:prop_tgt:`CROSSCOMPILING_EMULATOR` and :prop_tgt:`TEST_LAUNCHER`
+target property.
 
 .. code-block:: cmake
 

+ 2 - 1
Help/prop_test/SKIP_REGULAR_EXPRESSION.rst

@@ -25,7 +25,8 @@ Example:
 To run a test that may have a system-level failure, but still skip if
 ``SKIP_REGULAR_EXPRESSION`` matches, use a CMake command to wrap the
 executable run. Note that this will prevent automatic handling of the
-:prop_tgt:`CROSSCOMPILING_EMULATOR` target property.
+:prop_tgt:`CROSSCOMPILING_EMULATOR` and :prop_tgt:`TEST_LAUNCHER`
+target property.
 
 .. code-block:: cmake
 

+ 2 - 1
Help/prop_test/SKIP_RETURN_CODE.rst

@@ -25,7 +25,8 @@ signal abort, or heap errors may fail the test even if the return code matches.
 To run a test that may have a system-level failure, but still skip if
 ``SKIP_RETURN_CODE`` matches, use a CMake command to wrap the executable run.
 Note that this will prevent automatic handling of the
-:prop_tgt:`CROSSCOMPILING_EMULATOR` target property.
+:prop_tgt:`CROSSCOMPILING_EMULATOR` and :prop_tgt:`TEST_LAUNCHER` target
+property.
 
 .. code-block:: cmake
 

+ 2 - 1
Help/prop_test/WILL_FAIL.rst

@@ -19,7 +19,8 @@ is ``true``:
 To run a test that may have a system-level failure, but still pass if
 ``WILL_FAIL`` is set, use a CMake command to wrap the executable run.
 Note that this will prevent automatic handling of the
-:prop_tgt:`CROSSCOMPILING_EMULATOR` target property.
+:prop_tgt:`CROSSCOMPILING_EMULATOR` and :prop_tgt:`TEST_LAUNCHER`
+target property.
 
 .. code-block:: cmake
 

+ 20 - 0
Help/prop_tgt/TEST_LAUNCHER.rst

@@ -0,0 +1,20 @@
+TEST_LAUNCHER
+-------------
+
+.. versionadded:: 3.29
+
+Use the given launcher to run executables.
+This command will be added as a prefix to :command:`add_test` commands
+for build target system executables and is meant to be run on the host
+machine.
+
+It effectively acts as a run script for tests in a similar way
+to how :variable:`CMAKE_<LANG>_COMPILER_LAUNCHER` works for compilation.
+
+If this property contains a :ref:`semicolon-separated list <CMake Language
+Lists>`, then the first value is the command and remaining values are its
+arguments.
+
+This property is initialized by the value of the
+:variable:`CMAKE_TEST_LAUNCHER` variable if it is set when a target
+is created.

+ 7 - 0
Help/release/dev/cmake-test-launcher.rst

@@ -0,0 +1,7 @@
+cmake-test-launcher
+-------------------
+
+* A :variable:`CMAKE_TEST_LAUNCHER` variable and corresponding
+  :prop_tgt:`TEST_LAUNCHER` target property were added to specify
+  a launcher to be used by executable targets when invoked by
+  tests added by the :command:`add_test` command.

+ 16 - 0
Help/variable/CMAKE_TEST_LAUNCHER.rst

@@ -0,0 +1,16 @@
+CMAKE_TEST_LAUNCHER
+-------------------
+
+.. versionadded:: 3.29
+
+This variable is used to specify a launcher for running tests, added
+by the :command:`add_test` command, that run an executable target.
+If this variable contains a :ref:`semicolon-separated list <CMake Language
+Lists>`, then the first value is the command and remaining values are its
+arguments.
+
+This variable can be initialized via an
+:envvar:`CMAKE_TEST_LAUNCHER` environment variable.
+
+It is also used as the default value for the
+:prop_tgt:`TEST_LAUNCHER` target property of executables.

+ 12 - 7
Source/cmFileAPICodemodel.cxx

@@ -2102,7 +2102,9 @@ Json::Value Target::DumpLauncher(const char* name, const char* type)
     for (std::string const& arg : cmMakeRange(commandWithArgs).advance(1)) {
       args.append(arg);
     }
-    launcher["arguments"] = args;
+    if (!args.empty()) {
+      launcher["arguments"] = std::move(args);
+    }
   }
   return launcher;
 }
@@ -2110,13 +2112,16 @@ Json::Value Target::DumpLauncher(const char* name, const char* type)
 Json::Value Target::DumpLaunchers()
 {
   Json::Value launchers;
-  bool allow =
-    this->GT->Makefile->GetDefinition("CMAKE_CROSSCOMPILING").IsOn();
-  Json::Value launcher;
-  if (allow) {
-    launcher = DumpLauncher("CROSSCOMPILING_EMULATOR", "emulator");
+  {
+    Json::Value launcher = DumpLauncher("TEST_LAUNCHER", "test");
     if (!launcher.empty()) {
-      launchers.append(launcher);
+      launchers.append(std::move(launcher));
+    }
+  }
+  if (this->GT->Makefile->IsOn("CMAKE_CROSSCOMPILING")) {
+    Json::Value emulator = DumpLauncher("CROSSCOMPILING_EMULATOR", "emulator");
+    if (!emulator.empty()) {
+      launchers.append(std::move(emulator));
     }
   }
   return launchers;

+ 1 - 0
Source/cmTarget.cxx

@@ -594,6 +594,7 @@ TargetProperty const StaticTargetProperties[] = {
   { "CROSSCOMPILING_EMULATOR"_s, IC::ExecutableTarget },
   { "EXPORT_COMPILE_COMMANDS"_s, IC::CanCompileSources },
   { "FOLDER"_s },
+  { "TEST_LAUNCHER"_s, IC::ExecutableTarget },
 
   // Xcode properties
   { "XCODE_GENERATE_SCHEME"_s, IC::NeedsXcode },

+ 12 - 0
Source/cmTestGenerator.cxx

@@ -168,6 +168,18 @@ void cmTestGenerator::GenerateScriptForConfig(std::ostream& os,
     // Use the target file on disk.
     exe = target->GetFullPath(config);
 
+    // Prepend with the test launcher if specified.
+    cmValue launcher = target->GetProperty("TEST_LAUNCHER");
+    if (cmNonempty(launcher)) {
+      cmList launcherWithArgs{ *launcher };
+      std::string launcherExe(launcherWithArgs[0]);
+      cmSystemTools::ConvertToUnixSlashes(launcherExe);
+      os << cmOutputConverter::EscapeForCMake(launcherExe) << " ";
+      for (std::string const& arg : cmMakeRange(launcherWithArgs).advance(1)) {
+        os << cmOutputConverter::EscapeForCMake(arg) << " ";
+      }
+    }
+
     // Prepend with the emulator when cross compiling if required.
     cmValue emulator = target->GetProperty("CROSSCOMPILING_EMULATOR");
     if (cmNonempty(emulator)) {

+ 10 - 0
Source/cmake.cxx

@@ -2515,6 +2515,16 @@ int cmake::ActualConfigure()
                         "Name of generator toolset.", cmStateEnums::INTERNAL);
   }
 
+  if (!this->State->GetInitializedCacheValue("CMAKE_TEST_LAUNCHER")) {
+    cm::optional<std::string> testLauncher =
+      cmSystemTools::GetEnvVar("CMAKE_TEST_LAUNCHER");
+    if (testLauncher && !testLauncher->empty()) {
+      std::string message = "Test launcher to run tests executable.";
+      this->AddCacheEntry("CMAKE_TEST_LAUNCHER", *testLauncher, message,
+                          cmStateEnums::STRING);
+    }
+  }
+
   if (!this->State->GetInitializedCacheValue(
         "CMAKE_CROSSCOMPILING_EMULATOR")) {
     cm::optional<std::string> emulator =

+ 4 - 0
Tests/RunCMake/CrosscompilingEmulator/AddTest-check.cmake

@@ -26,3 +26,7 @@ endif()
 if(testfile_contents MATCHES "add_test[(]DoesNotUseEmulatorWithExecTargetFromSubdirAddedWithGenex [^\n]+pseudo_emulator[^\n]+\n")
   message(SEND_ERROR "Used emulator when it should not be used. ${error_details}")
 endif()
+
+if(NOT testfile_contents MATCHES "add_test[(]UsesTestLauncherAndEmulator[^\n]+pseudo_test_launcher.*pseudo_emulator[^\n]+\n")
+  message(SEND_ERROR "Did not use test launcher and emulator when they should be used. ${error_details}")
+endif()

+ 6 - 0
Tests/RunCMake/CrosscompilingEmulator/AddTest.cmake

@@ -18,3 +18,9 @@ add_test(NAME UsesEmulatorWithExecTargetFromSubdirAddedWithoutGenex
 
 add_test(NAME DoesNotUseEmulatorWithExecTargetFromSubdirAddedWithGenex
   COMMAND $<TARGET_FILE:generated_exe_in_subdir_added_to_test_with_genex>)
+
+add_executable(generated_exe_test_launcher simple_src_exiterror.cxx)
+set_property(TARGET generated_exe_test_launcher PROPERTY TEST_LAUNCHER "pseudo_test_launcher")
+
+add_test(NAME UsesTestLauncherAndEmulator
+  COMMAND generated_exe_test_launcher)

+ 11 - 6
Tests/RunCMake/FileAPI/codemodel-v2-check.py

@@ -410,15 +410,18 @@ def check_target(c):
                 expected_keys.append("launchers")
                 def check_launcher(actual, expected):
                     assert is_dict(actual)
-                    launcher_keys = ["arguments", "command", "type"]
+                    launcher_keys = ["command", "type"]
+                    if "arguments" in expected:
+                        launcher_keys.append("arguments")
                     assert sorted(actual.keys()) == sorted(launcher_keys)
                     assert matches(actual["command"], expected["command"])
                     assert matches(actual["type"], expected["type"])
-                    if expected["arguments"] is not None:
-                        check_list_match(lambda a, e: matches(a, e),
-                                        actual["arguments"], expected["arguments"],
-                                        missing_exception=lambda e: "argument: %s" % e,
-                                        extra_exception=lambda a: "argument: %s" % actual["arguments"])
+                    if "arguments" in expected:
+                        if expected["arguments"] is not None:
+                            check_list_match(lambda a, e: matches(a, e),
+                                             actual["arguments"], expected["arguments"],
+                                             missing_exception=lambda e: "argument: %s" % e,
+                                             extra_exception=lambda a: "argument: %s" % actual["arguments"])
                 check_list_match(lambda a, e: matches(a["type"], e["type"]),
                                 obj["launchers"], expected["launchers"],
                                 check=check_launcher,
@@ -806,6 +809,8 @@ def gen_check_targets(c, g, inSource):
         read_codemodel_json_data("targets/cxx_exe.json"),
         read_codemodel_json_data("targets/cxx_exe_cross_emulator.json"),
         read_codemodel_json_data("targets/cxx_exe_cross_emulator_args.json"),
+        read_codemodel_json_data("targets/cxx_exe_test_launcher_and_cross_emulator.json"),
+        read_codemodel_json_data("targets/cxx_exe_test_launcher.json"),
         read_codemodel_json_data("targets/cxx_standard_compile_feature_exe.json"),
         read_codemodel_json_data("targets/cxx_standard_exe.json"),
         read_codemodel_json_data("targets/cxx_shared_lib.json"),

+ 2 - 1
Tests/RunCMake/FileAPI/codemodel-v2-data/directories/cxx.cross.json

@@ -6,7 +6,8 @@
     "childSources": null,
     "targetIds": [
         "^cxx_exe_cross_emulator::@ee4a268216d1f53c4e2e$",
-        "^cxx_exe_cross_emulator_args::@ee4a268216d1f53c4e2e$"
+        "^cxx_exe_cross_emulator_args::@ee4a268216d1f53c4e2e$",
+        "^cxx_exe_test_launcher_and_cross_emulator::@ee4a268216d1f53c4e2e$"
     ],
     "projectName": "Cxx",
     "minimumCMakeVersion": "3.13",

+ 1 - 0
Tests/RunCMake/FileAPI/codemodel-v2-data/directories/cxx.json

@@ -9,6 +9,7 @@
         "^ALL_BUILD::@a56b12a3f5c0529fb296$",
         "^ZERO_CHECK::@a56b12a3f5c0529fb296$",
         "^cxx_exe::@a56b12a3f5c0529fb296$",
+        "^cxx_exe_test_launcher::@a56b12a3f5c0529fb296$",
         "^cxx_standard_compile_feature_exe::@a56b12a3f5c0529fb296$",
         "^cxx_standard_exe::@a56b12a3f5c0529fb296$",
         "^cxx_lib::@a56b12a3f5c0529fb296$",

+ 2 - 0
Tests/RunCMake/FileAPI/codemodel-v2-data/projects/cxx.json

@@ -13,6 +13,8 @@
         "^cxx_exe::@a56b12a3f5c0529fb296$",
         "^cxx_exe_cross_emulator::@ee4a268216d1f53c4e2e$",
         "^cxx_exe_cross_emulator_args::@ee4a268216d1f53c4e2e$",
+        "^cxx_exe_test_launcher_and_cross_emulator::@ee4a268216d1f53c4e2e$",
+        "^cxx_exe_test_launcher::@a56b12a3f5c0529fb296$",
         "^cxx_standard_compile_feature_exe::@a56b12a3f5c0529fb296$",
         "^cxx_standard_exe::@a56b12a3f5c0529fb296$",
         "^cxx_shared_lib::@a56b12a3f5c0529fb296$",

+ 8 - 0
Tests/RunCMake/FileAPI/codemodel-v2-data/targets/all_build_cxx.json

@@ -90,6 +90,14 @@
             "id": "^cxx_exe_cross_emulator_args::@ee4a268216d1f53c4e2e$",
             "backtrace": null
         },
+        {
+            "id": "^cxx_exe_test_launcher_and_cross_emulator::@ee4a268216d1f53c4e2e$",
+            "backtrace": null
+        },
+        {
+            "id": "^cxx_exe_test_launcher::@a56b12a3f5c0529fb296$",
+            "backtrace": null
+        },
         {
             "id": "^cxx_standard_compile_feature_exe::@a56b12a3f5c0529fb296$",
             "backtrace": null

+ 8 - 0
Tests/RunCMake/FileAPI/codemodel-v2-data/targets/all_build_top.json

@@ -130,6 +130,14 @@
             "id": "^cxx_exe_cross_emulator_args::@ee4a268216d1f53c4e2e$",
             "backtrace": null
         },
+        {
+            "id": "^cxx_exe_test_launcher_and_cross_emulator::@ee4a268216d1f53c4e2e$",
+            "backtrace": null
+        },
+        {
+            "id": "^cxx_exe_test_launcher::@a56b12a3f5c0529fb296$",
+            "backtrace": null
+        },
         {
             "id": "^cxx_standard_compile_feature_exe::@a56b12a3f5c0529fb296$",
             "backtrace": null

+ 0 - 1
Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_cross_emulator.json

@@ -80,7 +80,6 @@
     "install": null,
     "launchers" : [
         {
-            "arguments" : null,
             "command": "^no-such-emulator(\\.exe)?$",
             "type" : "emulator"
         }

+ 105 - 0
Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_test_launcher.json

@@ -0,0 +1,105 @@
+{
+    "name": "cxx_exe_test_launcher",
+    "id": "^cxx_exe_test_launcher::@a56b12a3f5c0529fb296$",
+    "directorySource": "^cxx$",
+    "projectName": "Cxx",
+    "type": "EXECUTABLE",
+    "isGeneratorProvided": null,
+    "fileSets": null,
+    "sources": [
+        {
+            "path": "^empty\\.cxx$",
+            "isGenerated": null,
+            "fileSetName": null,
+            "sourceGroupName": "Source Files",
+            "compileGroupLanguage": "CXX",
+            "backtrace": [
+                {
+                    "file": "^cxx/CMakeLists\\.txt$",
+                    "line": 49,
+                    "command": "add_executable",
+                    "hasParent": true
+                },
+                {
+                    "file": "^cxx/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        }
+    ],
+    "sourceGroups": [
+        {
+            "name": "Source Files",
+            "sourcePaths": [
+                "^empty\\.cxx$"
+            ]
+        }
+    ],
+    "compileGroups": [
+        {
+            "language": "CXX",
+            "sourcePaths": [
+                "^empty\\.cxx$"
+            ],
+            "includes": null,
+            "frameworks": null,
+            "defines": null,
+            "compileCommandFragments": null
+        }
+    ],
+    "backtrace": [
+        {
+            "file": "^cxx/CMakeLists\\.txt$",
+            "line": 49,
+            "command": "add_executable",
+            "hasParent": true
+        },
+        {
+            "file": "^cxx/CMakeLists\\.txt$",
+            "line": null,
+            "command": null,
+            "hasParent": false
+        }
+    ],
+    "folder": null,
+    "nameOnDisk": "^cxx_exe_test_launcher(\\.exe)?$",
+    "artifacts": [
+        {
+            "path": "^cxx/((Debug|Release|RelWithDebInfo|MinSizeRel)/)?cxx_exe_test_launcher(\\.exe)?$",
+            "_dllExtra": false
+        },
+        {
+            "path": "^cxx/((Debug|Release|RelWithDebInfo|MinSizeRel)/)?cxx_exe_test_launcher\\.pdb$",
+            "_dllExtra": true
+        }
+    ],
+    "build": "^cxx$",
+    "source": "^cxx$",
+    "install": null,
+    "launchers" : [
+        {
+            "command": "^no-such-launcher(\\.exe)?$",
+            "type" : "test"
+        }
+    ],
+    "link": {
+        "language": "CXX",
+        "lto": null,
+        "commandFragments": [
+            {
+                "fragment" : ".*",
+                "role" : "flags",
+                "backtrace": null
+            }
+        ]
+    },
+    "archive": null,
+    "dependencies": [
+        {
+            "id": "^ZERO_CHECK::@a56b12a3f5c0529fb296$",
+            "backtrace": null
+        }
+    ]
+}

+ 109 - 0
Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_test_launcher_and_cross_emulator.json

@@ -0,0 +1,109 @@
+{
+    "name": "cxx_exe_test_launcher_and_cross_emulator",
+    "id": "^cxx_exe_test_launcher_and_cross_emulator::@ee4a268216d1f53c4e2e$",
+    "directorySource": "^cxx/cross$",
+    "projectName": "Cxx",
+    "type": "EXECUTABLE",
+    "isGeneratorProvided": null,
+    "fileSets": null,
+    "sources": [
+        {
+            "path": "^empty\\.cxx$",
+            "isGenerated": null,
+            "fileSetName": null,
+            "sourceGroupName": "Source Files",
+            "compileGroupLanguage": "CXX",
+            "backtrace": [
+                {
+                    "file": "^cxx/cross/CMakeLists\\.txt$",
+                    "line": 9,
+                    "command": "add_executable",
+                    "hasParent": true
+                },
+                {
+                    "file": "^cxx/cross/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        }
+    ],
+    "sourceGroups": [
+        {
+            "name": "Source Files",
+            "sourcePaths": [
+                "^empty\\.cxx$"
+            ]
+        }
+    ],
+    "compileGroups": [
+        {
+            "language": "CXX",
+            "sourcePaths": [
+                "^empty\\.cxx$"
+            ],
+            "includes": null,
+            "frameworks": null,
+            "defines": null,
+            "compileCommandFragments": null
+        }
+    ],
+    "backtrace": [
+        {
+            "file": "^cxx/cross/CMakeLists\\.txt$",
+            "line": 9,
+            "command": "add_executable",
+            "hasParent": true
+        },
+        {
+            "file": "^cxx/cross/CMakeLists\\.txt$",
+            "line": null,
+            "command": null,
+            "hasParent": false
+        }
+    ],
+    "folder": null,
+    "nameOnDisk": "^cxx_exe_test_launcher_and_cross_emulator(\\.exe)?$",
+    "artifacts": [
+        {
+            "path": "^cxx/cross/((Debug|Release|RelWithDebInfo|MinSizeRel)/)?cxx_exe_test_launcher_and_cross_emulator(\\.exe)?$",
+            "_dllExtra": false
+        },
+        {
+            "path": "^cxx/cross/((Debug|Release|RelWithDebInfo|MinSizeRel)/)?cxx_exe_test_launcher_and_cross_emulator\\.pdb$",
+            "_dllExtra": true
+        }
+    ],
+    "build": "^cxx/cross$",
+    "source": "^cxx/cross$",
+    "install": null,
+    "launchers" : [
+        {
+            "command": "^no-such-launcher(\\.exe)?$",
+            "type" : "test"
+        },
+        {
+            "command": "^no-such-emulator(\\.exe)?$",
+            "type" : "emulator"
+        }
+    ],
+    "link": {
+        "language": "CXX",
+        "lto": null,
+        "commandFragments": [
+            {
+                "fragment" : ".*",
+                "role" : "flags",
+                "backtrace": null
+            }
+        ]
+    },
+    "archive": null,
+    "dependencies": [
+        {
+            "id": "^ZERO_CHECK::@6890427a1f51a3e7e1df$",
+            "backtrace": null
+        }
+    ]
+}

+ 3 - 0
Tests/RunCMake/FileAPI/cxx/CMakeLists.txt

@@ -46,4 +46,7 @@ if(_rdeps)
     )
 endif()
 
+add_executable(cxx_exe_test_launcher ../empty.cxx)
+set_property(TARGET cxx_exe_test_launcher PROPERTY TEST_LAUNCHER no-such-launcher)
+
 add_subdirectory(cross)

+ 4 - 0
Tests/RunCMake/FileAPI/cxx/cross/CMakeLists.txt

@@ -5,3 +5,7 @@ set_property(TARGET cxx_exe_cross_emulator PROPERTY CROSSCOMPILING_EMULATOR no-s
 
 add_executable(cxx_exe_cross_emulator_args ../../empty.cxx)
 set_property(TARGET cxx_exe_cross_emulator_args PROPERTY CROSSCOMPILING_EMULATOR "no-such-emulator;arg1;arg2 with space")
+
+add_executable(cxx_exe_test_launcher_and_cross_emulator ../../empty.cxx)
+set_property(TARGET cxx_exe_test_launcher_and_cross_emulator PROPERTY TEST_LAUNCHER "no-such-launcher")
+set_property(TARGET cxx_exe_test_launcher_and_cross_emulator PROPERTY CROSSCOMPILING_EMULATOR "no-such-emulator")

+ 6 - 0
Tests/RunCMake/add_test/RunCMakeTest.cmake

@@ -41,3 +41,9 @@ block()
   set(RunCMake_TEST_NO_CLEAN 1)
   run_cmake_command(EmptyArgument-ctest ${CMAKE_CTEST_COMMAND} -C Debug)
 endblock()
+
+set(RunCMake_TEST_OPTIONS
+    "-DCMAKE_TEST_LAUNCHER=/path/to/pseudo_test_launcher")
+
+run_cmake(TestLauncherProperty)
+run_cmake(TestLauncher)

+ 28 - 0
Tests/RunCMake/add_test/TestLauncher-check.cmake

@@ -0,0 +1,28 @@
+set(testfile "${RunCMake_TEST_BINARY_DIR}/CTestTestfile.cmake")
+if(EXISTS "${testfile}")
+  file(READ "${testfile}" testfile_contents)
+else()
+  message(FATAL_ERROR "Could not find expected CTestTestfile.cmake.")
+endif()
+
+set(error_details "There is a problem with generated test file: ${testfile}")
+
+if(testfile_contents MATCHES "add_test[(]DoesNotUseTestLauncher [^\n]+pseudo_test_launcher[^\n]+\n")
+  message(SEND_ERROR "Used test launcher when it should not be used. ${error_details}")
+endif()
+
+if(NOT testfile_contents MATCHES "add_test[(]UsesTestLauncher [^\n]+pseudo_test_launcher[^\n]+\n")
+  message(SEND_ERROR "Did not use test launcher when it should be used. ${error_details}")
+endif()
+
+if(testfile_contents MATCHES "add_test[(]DoesNotUseTestLauncherWithGenex [^\n]+pseudo_test_launcher[^\n]+\n")
+  message(SEND_ERROR "Used test launcher when it should not be used. ${error_details}")
+endif()
+
+if(NOT testfile_contents MATCHES "add_test[(]UsesTestLauncherWithExecTargetFromSubdirAddedWithoutGenex [^\n]+pseudo_test_launcher[^\n]+\n")
+  message(SEND_ERROR "Did not use test launcher when it should be used. ${error_details}")
+endif()
+
+if(testfile_contents MATCHES "add_test[(]DoesNotUseTestLauncherWithExecTargetFromSubdirAddedWithGenex [^\n]+pseudo_test_launcher[^\n]+\n")
+  message(SEND_ERROR "Used test launcher when it should not be used. ${error_details}")
+endif()

+ 22 - 0
Tests/RunCMake/add_test/TestLauncher.cmake

@@ -0,0 +1,22 @@
+project(test_launcher LANGUAGES C)
+
+enable_testing()
+add_test(NAME DoesNotUseLauncher
+  COMMAND ${CMAKE_COMMAND} -E echo "Hi")
+
+add_executable(generated_exe simple_src_exiterror.cxx)
+set_target_properties(generated_exe PROPERTIES LINKER_LANGUAGE C)
+
+add_test(NAME UsesTestLauncher
+  COMMAND generated_exe)
+
+add_test(NAME DoesNotUseTestLauncherWithGenex
+  COMMAND $<TARGET_FILE:generated_exe>)
+
+add_subdirectory(TestLauncher)
+
+add_test(NAME UsesTestLauncherWithExecTargetFromSubdirAddedWithoutGenex
+  COMMAND generated_exe_in_subdir_added_to_test_without_genex)
+
+add_test(NAME DoesNotUseTestLauncherWithExecTargetFromSubdirAddedWithGenex
+  COMMAND $<TARGET_FILE:generated_exe_in_subdir_added_to_test_with_genex>)

+ 9 - 0
Tests/RunCMake/add_test/TestLauncher/CMakeLists.txt

@@ -0,0 +1,9 @@
+add_executable(generated_exe_in_subdir_added_to_test_without_genex
+  ${CMAKE_CURRENT_SOURCE_DIR}/../simple_src_exiterror.cxx)
+set_target_properties(generated_exe_in_subdir_added_to_test_without_genex
+  PROPERTIES LINKER_LANGUAGE C)
+
+add_executable(generated_exe_in_subdir_added_to_test_with_genex
+  ${CMAKE_CURRENT_SOURCE_DIR}/../simple_src_exiterror.cxx)
+set_target_properties(generated_exe_in_subdir_added_to_test_with_genex
+  PROPERTIES LINKER_LANGUAGE C)

+ 41 - 0
Tests/RunCMake/add_test/TestLauncherProperty.cmake

@@ -0,0 +1,41 @@
+
+# This tests setting the TEST_LAUNCHER target property from the
+# CMAKE_TEST_LAUNCHER variable.
+
+# -DCMAKE_TEST_LAUNCHER=/path/to/pseudo_test_launcher is passed to this
+# test
+
+project(test_launcher LANGUAGES C)
+
+add_executable(target_with_test_launcher simple_src_exiterror.cxx)
+set_target_properties(target_with_test_launcher PROPERTIES LINKER_LANGUAGE C)
+get_property(launcher TARGET target_with_test_launcher
+             PROPERTY TEST_LAUNCHER)
+if(NOT "${launcher}" MATCHES "pseudo_test_launcher")
+  message(SEND_ERROR "Default TEST_LAUNCHER property not set")
+endif()
+
+set_property(TARGET target_with_test_launcher
+             PROPERTY TEST_LAUNCHER "another_test_launcher")
+get_property(launcher TARGET target_with_test_launcher
+             PROPERTY TEST_LAUNCHER)
+if(NOT "${launcher}" MATCHES "another_test_launcher")
+  message(SEND_ERROR
+    "set_property/get_property TEST_LAUNCHER is not consistent")
+endif()
+
+unset(CMAKE_TEST_LAUNCHER CACHE)
+add_executable(target_without_test_launcher simple_src_exiterror.cxx)
+set_target_properties(target_without_test_launcher PROPERTIES LINKER_LANGUAGE C)
+get_property(launcher TARGET target_without_test_launcher
+             PROPERTY TEST_LAUNCHER)
+if(NOT "${launcher}" STREQUAL "")
+  message(SEND_ERROR "Default TEST_LAUNCHER property not set to null")
+endif()
+
+add_executable(target_with_empty_test_launcher simple_src_exiterror.cxx)
+set_target_properties(target_with_empty_test_launcher PROPERTIES LINKER_LANGUAGE C)
+set_property(TARGET target_with_empty_test_launcher PROPERTY TEST_LAUNCHER "")
+
+enable_testing()
+add_test(NAME test_target_with_empty_test_launcher COMMAND target_with_empty_test_launcher)

+ 4 - 0
Tests/RunCMake/add_test/simple_src_exiterror.cxx

@@ -0,0 +1,4 @@
+int main(int, char**)
+{
+  return 13;
+}

+ 1 - 0
Tests/RunCMake/property_init/Executable.cmake

@@ -17,6 +17,7 @@ set(properties
 
   # Metadata
   "CROSSCOMPILING_EMULATOR"       "emu"     "<SAME>"
+  "TEST_LAUNCHER"                 "test"    "<SAME>"
   )
 
 prepare_target_types(executable