From: Boyuan Yang <byang@debian.org>
Date: Wed, 19 Jun 2024 22:01:45 -0400
Subject: Use up-to-date catch2 cmake files

Files retrieved from https://github.com/catchorg/Catch2/blob/devel/extras/

Forwarded: not-needed
---
 cmake/Catch.cmake                 | 197 ++++++++++++++++++++++++++------
 cmake/CatchAddTests.cmake         | 231 ++++++++++++++++++++++++++++----------
 cmake/ParseAndAddCatchTests.cmake | 119 +++++++++++++++-----
 3 files changed, 429 insertions(+), 118 deletions(-)

diff --git a/cmake/Catch.cmake b/cmake/Catch.cmake
index 486e323..8f30688 100644
--- a/cmake/Catch.cmake
+++ b/cmake/Catch.cmake
@@ -33,6 +33,11 @@ same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
                          [TEST_SUFFIX suffix]
                          [PROPERTIES name1 value1...]
                          [TEST_LIST var]
+                         [REPORTER reporter]
+                         [OUTPUT_DIR dir]
+                         [OUTPUT_PREFIX prefix]
+                         [OUTPUT_SUFFIX suffix]
+                         [DISCOVERY_MODE <POST_BUILD|PRE_TEST>]
     )
 
   ``catch_discover_tests`` sets up a post-build command on the test executable
@@ -90,15 +95,58 @@ same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
     executable is being used in multiple calls to ``catch_discover_tests()``.
     Note that this variable is only available in CTest.
 
+  ``REPORTER reporter``
+    Use the specified reporter when running the test case. The reporter will
+    be passed to the Catch executable as ``--reporter reporter``.
+
+  ``OUTPUT_DIR dir``
+    If specified, the parameter is passed along as
+    ``--out dir/<test_name>`` to Catch executable. The actual file name is the
+    same as the test name. This should be used instead of
+    ``EXTRA_ARGS --out foo`` to avoid race conditions writing the result output
+    when using parallel test execution.
+
+  ``OUTPUT_PREFIX prefix``
+    May be used in conjunction with ``OUTPUT_DIR``.
+    If specified, ``prefix`` is added to each output file name, like so
+    ``--out dir/prefix<test_name>``.
+
+  ``OUTPUT_SUFFIX suffix``
+    May be used in conjunction with ``OUTPUT_DIR``.
+    If specified, ``suffix`` is added to each output file name, like so
+    ``--out dir/<test_name>suffix``. This can be used to add a file extension to
+    the output e.g. ".xml".
+
+  ``DL_PATHS path...``
+    Specifies paths that need to be set for the dynamic linker to find shared
+    libraries/DLLs when running the test executable (PATH/LD_LIBRARY_PATH respectively).
+    These paths will both be set when retrieving the list of test cases from the
+    test executable and when the tests are executed themselves. This requires
+    cmake/ctest >= 3.22.
+
+  `DISCOVERY_MODE mode``
+    Provides control over when ``catch_discover_tests`` performs test discovery.
+    By default, ``POST_BUILD`` sets up a post-build command to perform test discovery
+    at build time. In certain scenarios, like cross-compiling, this ``POST_BUILD``
+    behavior is not desirable. By contrast, ``PRE_TEST`` delays test discovery until
+    just prior to test execution. This way test discovery occurs in the target environment
+    where the test has a better chance at finding appropriate runtime dependencies.
+
+    ``DISCOVERY_MODE`` defaults to the value of the
+    ``CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE`` variable if it is not passed when
+    calling ``catch_discover_tests``. This provides a mechanism for globally selecting
+    a preferred test discovery behavior without having to modify each call site.
+
 #]=======================================================================]
 
 #------------------------------------------------------------------------------
 function(catch_discover_tests TARGET)
+
   cmake_parse_arguments(
     ""
     ""
-    "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST"
-    "TEST_SPEC;EXTRA_ARGS;PROPERTIES"
+    "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST;REPORTER;OUTPUT_DIR;OUTPUT_PREFIX;OUTPUT_SUFFIX;DISCOVERY_MODE"
+    "TEST_SPEC;EXTRA_ARGS;PROPERTIES;DL_PATHS"
     ${ARGN}
   )
 
@@ -108,46 +156,128 @@ function(catch_discover_tests TARGET)
   if(NOT _TEST_LIST)
     set(_TEST_LIST ${TARGET}_TESTS)
   endif()
+  if (_DL_PATHS)
+    if(${CMAKE_VERSION} VERSION_LESS "3.22.0")
+        message(FATAL_ERROR "The DL_PATHS option requires at least cmake 3.22")
+    endif()
+  endif()
+  if(NOT _DISCOVERY_MODE)
+    if(NOT CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE)
+      set(CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE "POST_BUILD")
+    endif()
+    set(_DISCOVERY_MODE ${CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE})
+  endif()
+  if (NOT _DISCOVERY_MODE MATCHES "^(POST_BUILD|PRE_TEST)$")
+    message(FATAL_ERROR "Unknown DISCOVERY_MODE: ${_DISCOVERY_MODE}")
+  endif()
 
   ## Generate a unique name based on the extra arguments
-  string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}")
+  string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS} ${_REPORTER} ${_OUTPUT_DIR} ${_OUTPUT_PREFIX} ${_OUTPUT_SUFFIX}")
   string(SUBSTRING ${args_hash} 0 7 args_hash)
 
   # Define rule to generate test list for aforementioned test executable
-  set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake")
-  set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake")
+  set(ctest_file_base "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}-${args_hash}")
+  set(ctest_include_file "${ctest_file_base}_include.cmake")
+  set(ctest_tests_file "${ctest_file_base}_tests.cmake")
+
   get_property(crosscompiling_emulator
     TARGET ${TARGET}
     PROPERTY CROSSCOMPILING_EMULATOR
   )
-  add_custom_command(
-    TARGET ${TARGET} POST_BUILD
-    BYPRODUCTS "${ctest_tests_file}"
-    COMMAND "${CMAKE_COMMAND}"
-            -D "TEST_TARGET=${TARGET}"
-            -D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>"
-            -D "TEST_EXECUTOR=${crosscompiling_emulator}"
-            -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}"
-            -D "TEST_SPEC=${_TEST_SPEC}"
-            -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}"
-            -D "TEST_PROPERTIES=${_PROPERTIES}"
-            -D "TEST_PREFIX=${_TEST_PREFIX}"
-            -D "TEST_SUFFIX=${_TEST_SUFFIX}"
-            -D "TEST_LIST=${_TEST_LIST}"
-            -D "CTEST_FILE=${ctest_tests_file}"
-            -P "${_CATCH_DISCOVER_TESTS_SCRIPT}"
-    VERBATIM
-  )
 
-  file(WRITE "${ctest_include_file}"
-    "if(EXISTS \"${ctest_tests_file}\")\n"
-    "  include(\"${ctest_tests_file}\")\n"
-    "else()\n"
-    "  add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n"
-    "endif()\n"
-  )
+  if(_DISCOVERY_MODE STREQUAL "POST_BUILD")
+    add_custom_command(
+      TARGET ${TARGET} POST_BUILD
+      BYPRODUCTS "${ctest_tests_file}"
+      COMMAND "${CMAKE_COMMAND}"
+              -D "TEST_TARGET=${TARGET}"
+              -D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>"
+              -D "TEST_EXECUTOR=${crosscompiling_emulator}"
+              -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}"
+              -D "TEST_SPEC=${_TEST_SPEC}"
+              -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}"
+              -D "TEST_PROPERTIES=${_PROPERTIES}"
+              -D "TEST_PREFIX=${_TEST_PREFIX}"
+              -D "TEST_SUFFIX=${_TEST_SUFFIX}"
+              -D "TEST_LIST=${_TEST_LIST}"
+              -D "TEST_REPORTER=${_REPORTER}"
+              -D "TEST_OUTPUT_DIR=${_OUTPUT_DIR}"
+              -D "TEST_OUTPUT_PREFIX=${_OUTPUT_PREFIX}"
+              -D "TEST_OUTPUT_SUFFIX=${_OUTPUT_SUFFIX}"
+              -D "TEST_DL_PATHS=${_DL_PATHS}"
+              -D "CTEST_FILE=${ctest_tests_file}"
+              -P "${_CATCH_DISCOVER_TESTS_SCRIPT}"
+      VERBATIM
+    )
+
+    file(WRITE "${ctest_include_file}"
+      "if(EXISTS \"${ctest_tests_file}\")\n"
+      "  include(\"${ctest_tests_file}\")\n"
+      "else()\n"
+      "  add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n"
+      "endif()\n"
+    )
+
+  elseif(_DISCOVERY_MODE STREQUAL "PRE_TEST")
+
+    get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL
+        PROPERTY GENERATOR_IS_MULTI_CONFIG
+    )
+
+    if(GENERATOR_IS_MULTI_CONFIG)
+      set(ctest_tests_file "${ctest_file_base}_tests-$<CONFIG>.cmake")
+    endif()
 
-  if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0") 
+    string(CONCAT ctest_include_content
+      "if(EXISTS \"$<TARGET_FILE:${TARGET}>\")"                                    "\n"
+      "  if(NOT EXISTS \"${ctest_tests_file}\" OR"                                 "\n"
+      "     NOT \"${ctest_tests_file}\" IS_NEWER_THAN \"$<TARGET_FILE:${TARGET}>\" OR\n"
+      "     NOT \"${ctest_tests_file}\" IS_NEWER_THAN \"\${CMAKE_CURRENT_LIST_FILE}\")\n"
+      "    include(\"${_CATCH_DISCOVER_TESTS_SCRIPT}\")"                           "\n"
+      "    catch_discover_tests_impl("                                             "\n"
+      "      TEST_EXECUTABLE"        " [==[" "$<TARGET_FILE:${TARGET}>"   "]==]"   "\n"
+      "      TEST_EXECUTOR"          " [==[" "${crosscompiling_emulator}" "]==]"   "\n"
+      "      TEST_WORKING_DIR"       " [==[" "${_WORKING_DIRECTORY}"      "]==]"   "\n"
+      "      TEST_SPEC"              " [==[" "${_TEST_SPEC}"              "]==]"   "\n"
+      "      TEST_EXTRA_ARGS"        " [==[" "${_EXTRA_ARGS}"             "]==]"   "\n"
+      "      TEST_PROPERTIES"        " [==[" "${_PROPERTIES}"             "]==]"   "\n"
+      "      TEST_PREFIX"            " [==[" "${_TEST_PREFIX}"            "]==]"   "\n"
+      "      TEST_SUFFIX"            " [==[" "${_TEST_SUFFIX}"            "]==]"   "\n"
+      "      TEST_LIST"              " [==[" "${_TEST_LIST}"              "]==]"   "\n"
+      "      TEST_REPORTER"          " [==[" "${_REPORTER}"               "]==]"   "\n"
+      "      TEST_OUTPUT_DIR"        " [==[" "${_OUTPUT_DIR}"             "]==]"   "\n"
+      "      TEST_OUTPUT_PREFIX"     " [==[" "${_OUTPUT_PREFIX}"          "]==]"   "\n"
+      "      TEST_OUTPUT_SUFFIX"     " [==[" "${_OUTPUT_SUFFIX}"          "]==]"   "\n"
+      "      CTEST_FILE"             " [==[" "${ctest_tests_file}"        "]==]"   "\n"
+      "      TEST_DL_PATHS"          " [==[" "${_DL_PATHS}"               "]==]"   "\n"
+      "      CTEST_FILE"             " [==[" "${CTEST_FILE}"              "]==]"   "\n"
+      "    )"                                                                      "\n"
+      "  endif()"                                                                  "\n"
+      "  include(\"${ctest_tests_file}\")"                                         "\n"
+      "else()"                                                                     "\n"
+      "  add_test(${TARGET}_NOT_BUILT ${TARGET}_NOT_BUILT)"                        "\n"
+      "endif()"                                                                    "\n"
+    )
+
+    if(GENERATOR_IS_MULTI_CONFIG)
+      foreach(_config ${CMAKE_CONFIGURATION_TYPES})
+        file(GENERATE OUTPUT "${ctest_file_base}_include-${_config}.cmake" CONTENT "${ctest_include_content}" CONDITION $<CONFIG:${_config}>)
+      endforeach()
+      string(CONCAT ctest_include_multi_content
+        "if(NOT CTEST_CONFIGURATION_TYPE)"                                              "\n"
+        "  message(\"No configuration for testing specified, use '-C <cfg>'.\")"        "\n"
+        "else()"                                                                        "\n"
+        "  include(\"${ctest_file_base}_include-\${CTEST_CONFIGURATION_TYPE}.cmake\")"  "\n"
+        "endif()"                                                                       "\n"
+      )
+      file(GENERATE OUTPUT "${ctest_include_file}" CONTENT "${ctest_include_multi_content}")
+    else()
+      file(GENERATE OUTPUT "${ctest_file_base}_include.cmake" CONTENT "${ctest_include_content}")
+      file(WRITE "${ctest_include_file}" "include(\"${ctest_file_base}_include.cmake\")")
+    endif()
+  endif()
+
+  if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0")
     # Add discovered tests to directory TEST_INCLUDE_FILES
     set_property(DIRECTORY
       APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}"
@@ -160,9 +290,7 @@ function(catch_discover_tests TARGET)
         PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}"
       )
     else()
-      message(FATAL_ERROR
-        "Cannot set more than one TEST_INCLUDE_FILE"
-      )
+      message(FATAL_ERROR "Cannot set more than one TEST_INCLUDE_FILE")
     endif()
   endif()
 
@@ -172,4 +300,5 @@ endfunction()
 
 set(_CATCH_DISCOVER_TESTS_SCRIPT
   ${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake
+  CACHE INTERNAL "Catch2 full path to CatchAddTests.cmake helper file"
 )
diff --git a/cmake/CatchAddTests.cmake b/cmake/CatchAddTests.cmake
index 81d50b8..a073194 100644
--- a/cmake/CatchAddTests.cmake
+++ b/cmake/CatchAddTests.cmake
@@ -1,18 +1,12 @@
 # Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
 # file Copyright.txt or https://cmake.org/licensing for details.
 
-set(prefix "${TEST_PREFIX}")
-set(suffix "${TEST_SUFFIX}")
-set(spec ${TEST_SPEC})
-set(extra_args ${TEST_EXTRA_ARGS})
-set(properties ${TEST_PROPERTIES})
-set(script)
-set(suite)
-set(tests)
-
 function(add_command NAME)
   set(_args "")
-  foreach(_arg ${ARGN})
+  # use ARGV* instead of ARGN, because ARGN splits arrays into multiple arguments
+  math(EXPR _last_arg ${ARGC}-1)
+  foreach(_n RANGE 1 ${_last_arg})
+    set(_arg "${ARGV${_n}}")
     if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
       set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument
     else()
@@ -22,56 +16,177 @@ function(add_command NAME)
   set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
 endfunction()
 
-# Run test executable to get list of available tests
-if(NOT EXISTS "${TEST_EXECUTABLE}")
-  message(FATAL_ERROR
-    "Specified test executable '${TEST_EXECUTABLE}' does not exist"
-  )
-endif()
-execute_process(
-  COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-names-only
-  OUTPUT_VARIABLE output
-  RESULT_VARIABLE result
-)
-# Catch --list-test-names-only reports the number of tests, so 0 is... surprising
-if(${result} EQUAL 0)
-  message(WARNING
-    "Test executable '${TEST_EXECUTABLE}' contains no tests!\n"
-  )
-elseif(${result} LESS 0)
-  message(FATAL_ERROR
-    "Error running test executable '${TEST_EXECUTABLE}':\n"
-    "  Result: ${result}\n"
-    "  Output: ${output}\n"
-  )
-endif()
+function(catch_discover_tests_impl)
 
-string(REPLACE "\n" ";" output "${output}")
-
-# Parse output
-foreach(line ${output})
-  # Test name; strip spaces to get just the name...
-  string(REGEX REPLACE "^ +" "" test "${line}")
-  # ...and add to script
-  add_command(add_test
-    "${prefix}${test}${suffix}"
-    ${TEST_EXECUTOR}
-    "${TEST_EXECUTABLE}"
-    "${test}"
-    ${extra_args}
+  cmake_parse_arguments(
+    ""
+    ""
+    "TEST_EXECUTABLE;TEST_WORKING_DIR;TEST_OUTPUT_DIR;TEST_OUTPUT_PREFIX;TEST_OUTPUT_SUFFIX;TEST_PREFIX;TEST_REPORTER;TEST_SPEC;TEST_SUFFIX;TEST_LIST;CTEST_FILE"
+    "TEST_EXTRA_ARGS;TEST_PROPERTIES;TEST_EXECUTOR;TEST_DL_PATHS"
+    ${ARGN}
   )
-  add_command(set_tests_properties
-    "${prefix}${test}${suffix}"
-    PROPERTIES
-    WORKING_DIRECTORY "${TEST_WORKING_DIR}"
-    ${properties}
+
+  set(prefix "${_TEST_PREFIX}")
+  set(suffix "${_TEST_SUFFIX}")
+  set(spec ${_TEST_SPEC})
+  set(extra_args ${_TEST_EXTRA_ARGS})
+  set(properties ${_TEST_PROPERTIES})
+  set(reporter ${_TEST_REPORTER})
+  set(output_dir ${_TEST_OUTPUT_DIR})
+  set(output_prefix ${_TEST_OUTPUT_PREFIX})
+  set(output_suffix ${_TEST_OUTPUT_SUFFIX})
+  set(dl_paths ${_TEST_DL_PATHS})
+  set(script)
+  set(suite)
+  set(tests)
+
+  if(WIN32)
+    set(dl_paths_variable_name PATH)
+  elseif(APPLE)
+    set(dl_paths_variable_name DYLD_LIBRARY_PATH)
+  else()
+    set(dl_paths_variable_name LD_LIBRARY_PATH)
+  endif()
+
+  # Run test executable to get list of available tests
+  if(NOT EXISTS "${_TEST_EXECUTABLE}")
+    message(FATAL_ERROR
+      "Specified test executable '${_TEST_EXECUTABLE}' does not exist"
+    )
+  endif()
+
+  if(dl_paths)
+    cmake_path(CONVERT "${dl_paths}" TO_NATIVE_PATH_LIST paths)
+    set(ENV{${dl_paths_variable_name}} "${paths}")
+  endif()
+
+  execute_process(
+    COMMAND ${_TEST_EXECUTOR} "${_TEST_EXECUTABLE}" ${spec} --list-tests --verbosity quiet
+    OUTPUT_VARIABLE output
+    RESULT_VARIABLE result
+    WORKING_DIRECTORY "${_TEST_WORKING_DIR}"
   )
-  list(APPEND tests "${prefix}${test}${suffix}")
-endforeach()
+  if(NOT ${result} EQUAL 0)
+    message(FATAL_ERROR
+      "Error running test executable '${_TEST_EXECUTABLE}':\n"
+      "  Result: ${result}\n"
+      "  Output: ${output}\n"
+    )
+  endif()
+
+  # Make sure to escape ; (semicolons) in test names first, because
+  # that'd break the foreach loop for "Parse output" later and create
+  # wrongly splitted and thus failing test cases (false positives)
+  string(REPLACE ";" "\;" output "${output}")
+  string(REPLACE "\n" ";" output "${output}")
 
-# Create a list of all discovered tests, which users may use to e.g. set
-# properties on the tests
-add_command(set ${TEST_LIST} ${tests})
+  # Prepare reporter
+  if(reporter)
+    set(reporter_arg "--reporter ${reporter}")
 
-# Write CTest script
-file(WRITE "${CTEST_FILE}" "${script}")
+    # Run test executable to check whether reporter is available
+    # note that the use of --list-reporters is not the important part,
+    # we only want to check whether the execution succeeds with ${reporter_arg}
+    execute_process(
+      COMMAND ${_TEST_EXECUTOR} "${_TEST_EXECUTABLE}" ${spec} ${reporter_arg} --list-reporters
+      OUTPUT_VARIABLE reporter_check_output
+      RESULT_VARIABLE reporter_check_result
+      WORKING_DIRECTORY "${_TEST_WORKING_DIR}"
+    )
+    if(${reporter_check_result} EQUAL 255)
+      message(FATAL_ERROR
+        "\"${reporter}\" is not a valid reporter!\n"
+      )
+    elseif(NOT ${reporter_check_result} EQUAL 0)
+      message(FATAL_ERROR
+        "Error running test executable '${_TEST_EXECUTABLE}':\n"
+        "  Result: ${reporter_check_result}\n"
+        "  Output: ${reporter_check_output}\n"
+      )
+    endif()
+  endif()
+
+  # Prepare output dir
+  if(output_dir AND NOT IS_ABSOLUTE ${output_dir})
+    set(output_dir "${_TEST_WORKING_DIR}/${output_dir}")
+    if(NOT EXISTS ${output_dir})
+      file(MAKE_DIRECTORY ${output_dir})
+    endif()
+  endif()
+
+  if(dl_paths)
+    foreach(path ${dl_paths})
+      cmake_path(NATIVE_PATH path native_path)
+      list(APPEND environment_modifications "${dl_paths_variable_name}=path_list_prepend:${native_path}")
+    endforeach()
+  endif()
+
+  # Parse output
+  foreach(line ${output})
+    set(test "${line}")
+    # Escape characters in test case names that would be parsed by Catch2
+    # Note that the \ escaping must happen FIRST! Do not change the order.
+    set(test_name "${test}")
+    foreach(char \\ , [ ])
+      string(REPLACE ${char} "\\${char}" test_name "${test_name}")
+    endforeach(char)
+    # ...add output dir
+    if(output_dir)
+      string(REGEX REPLACE "[^A-Za-z0-9_]" "_" test_name_clean "${test_name}")
+      set(output_dir_arg "--out ${output_dir}/${output_prefix}${test_name_clean}${output_suffix}")
+    endif()
+
+    # ...and add to script
+    add_command(add_test
+      "${prefix}${test}${suffix}"
+      ${_TEST_EXECUTOR}
+      "${_TEST_EXECUTABLE}"
+      "${test_name}"
+      ${extra_args}
+      "${reporter_arg}"
+      "${output_dir_arg}"
+    )
+    add_command(set_tests_properties
+      "${prefix}${test}${suffix}"
+      PROPERTIES
+      WORKING_DIRECTORY "${_TEST_WORKING_DIR}"
+      ${properties}
+    )
+
+    if(environment_modifications)
+      add_command(set_tests_properties
+        "${prefix}${test}${suffix}"
+        PROPERTIES
+        ENVIRONMENT_MODIFICATION "${environment_modifications}")
+    endif()
+
+    list(APPEND tests "${prefix}${test}${suffix}")
+  endforeach()
+
+  # Create a list of all discovered tests, which users may use to e.g. set
+  # properties on the tests
+  add_command(set ${_TEST_LIST} ${tests})
+
+  # Write CTest script
+  file(WRITE "${_CTEST_FILE}" "${script}")
+endfunction()
+
+if(CMAKE_SCRIPT_MODE_FILE)
+  catch_discover_tests_impl(
+    TEST_EXECUTABLE ${TEST_EXECUTABLE}
+    TEST_EXECUTOR ${TEST_EXECUTOR}
+    TEST_WORKING_DIR ${TEST_WORKING_DIR}
+    TEST_SPEC ${TEST_SPEC}
+    TEST_EXTRA_ARGS ${TEST_EXTRA_ARGS}
+    TEST_PROPERTIES ${TEST_PROPERTIES}
+    TEST_PREFIX ${TEST_PREFIX}
+    TEST_SUFFIX ${TEST_SUFFIX}
+    TEST_LIST ${TEST_LIST}
+    TEST_REPORTER ${TEST_REPORTER}
+    TEST_OUTPUT_DIR ${TEST_OUTPUT_DIR}
+    TEST_OUTPUT_PREFIX ${TEST_OUTPUT_PREFIX}
+    TEST_OUTPUT_SUFFIX ${TEST_OUTPUT_SUFFIX}
+    TEST_DL_PATHS ${TEST_DL_PATHS}
+    CTEST_FILE ${CTEST_FILE}
+  )
+endif()
diff --git a/cmake/ParseAndAddCatchTests.cmake b/cmake/ParseAndAddCatchTests.cmake
index aa9cc93..31fc193 100644
--- a/cmake/ParseAndAddCatchTests.cmake
+++ b/cmake/ParseAndAddCatchTests.cmake
@@ -1,9 +1,11 @@
 #==================================================================================================#
 #  supported macros                                                                                #
 #    - TEST_CASE,                                                                                  #
+#    - TEMPLATE_TEST_CASE                                                                          #
 #    - SCENARIO,                                                                                   #
 #    - TEST_CASE_METHOD,                                                                           #
 #    - CATCH_TEST_CASE,                                                                            #
+#    - CATCH_TEMPLATE_TEST_CASE                                                                    #
 #    - CATCH_SCENARIO,                                                                             #
 #    - CATCH_TEST_CASE_METHOD.                                                                     #
 #                                                                                                  #
@@ -17,7 +19,7 @@
 #        set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/")          #
 #        enable_testing()                                                                          #
 #                                                                                                  #
-#        find_path(CATCH_INCLUDE_DIR "catch_all.hpp")                                                  #
+#        find_path(CATCH_INCLUDE_DIR "catch.hpp")                                                  #
 #        include_directories(${INCLUDE_DIRECTORIES} ${CATCH_INCLUDE_DIR})                          #
 #                                                                                                  #
 #        file(GLOB SOURCE_FILES "*.cpp")                                                           #
@@ -39,9 +41,24 @@
 #    PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS (Default OFF)                                      #
 #    -- causes CMake to rerun when file with tests changes so that new tests will be discovered    #
 #                                                                                                  #
+# One can also set (locally) the optional variable OptionalCatchTestLauncher to precise the way    #
+# a test should be run. For instance to use test MPI, one can write                                #
+#     set(OptionalCatchTestLauncher ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${NUMPROC})                 #
+# just before calling this ParseAndAddCatchTests function                                          #
+#                                                                                                  #
+# The AdditionalCatchParameters optional variable can be used to pass extra argument to the test   #
+# command. For example, to include successful tests in the output, one can write                   #
+#     set(AdditionalCatchParameters --success)                                                     #
+#                                                                                                  #
+# After the script, the ParseAndAddCatchTests_TESTS property for the target, and for each source   #
+# file in the target is set, and contains the list of the tests extracted from that target, or     #
+# from that file. This is useful, for example to add further labels or properties to the tests.    #
+#                                                                                                  #
 #==================================================================================================#
 
-cmake_minimum_required(VERSION 2.8.8)
+if (CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.8)
+  message(FATAL_ERROR "ParseAndAddCatchTests requires CMake 2.8.8 or newer")
+endif()
 
 option(PARSE_CATCH_TESTS_VERBOSE "Print Catch to CTest parser debug messages" OFF)
 option(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS "Exclude tests with [!hide], [.] or [.foo] tags" OFF)
@@ -49,7 +66,7 @@ option(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME "Add fixture class name to the
 option(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME "Add target name to the test name" ON)
 option(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS "Add test file to CMAKE_CONFIGURE_DEPENDS property" OFF)
 
-function(PrintDebugMessage)
+function(ParseAndAddCatchTests_PrintDebugMessage)
     if(PARSE_CATCH_TESTS_VERBOSE)
             message(STATUS "ParseAndAddCatchTests: ${ARGV}")
     endif()
@@ -60,7 +77,7 @@ endfunction()
 #  - full line comments (i.e. // ... )
 # contents have been read into '${CppCode}'.
 # !keep partial line comments
-function(RemoveComments CppCode)
+function(ParseAndAddCatchTests_RemoveComments CppCode)
   string(ASCII 2 CMakeBeginBlockComment)
   string(ASCII 3 CMakeEndBlockComment)
   string(REGEX REPLACE "/\\*" "${CMakeBeginBlockComment}" ${CppCode} "${${CppCode}}")
@@ -72,24 +89,30 @@ function(RemoveComments CppCode)
 endfunction()
 
 # Worker function
-function(ParseFile SourceFile TestTarget)
+function(ParseAndAddCatchTests_ParseFile SourceFile TestTarget)
+    # If SourceFile is an object library, do not scan it (as it is not a file). Exit without giving a warning about a missing file.
+    if(SourceFile MATCHES "\\\$<TARGET_OBJECTS:.+>")
+        ParseAndAddCatchTests_PrintDebugMessage("Detected OBJECT library: ${SourceFile} this will not be scanned for tests.")
+        return()
+    endif()
     # According to CMake docs EXISTS behavior is well-defined only for full paths.
     get_filename_component(SourceFile ${SourceFile} ABSOLUTE)
     if(NOT EXISTS ${SourceFile})
         message(WARNING "Cannot find source file: ${SourceFile}")
         return()
     endif()
-    PrintDebugMessage("parsing ${SourceFile}")
+    ParseAndAddCatchTests_PrintDebugMessage("parsing ${SourceFile}")
     file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME)
 
     # Remove block and fullline comments
-    RemoveComments(Contents)
+    ParseAndAddCatchTests_RemoveComments(Contents)
 
     # Find definition of test names
-    string(REGEX MATCHALL "[ \t]*(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^\)]+\\)+[ \t\n]*{+[ \t]*(//[^\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \t]*[0-9]+)*" Tests "${Contents}")
+    # https://regex101.com/r/JygOND/1
+    string(REGEX MATCHALL "[ \t]*(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([ \t\n]*\"[^\"]*\"[ \t\n]*(,[ \t\n]*\"[^\"]*\")?(,[ \t\n]*[^\,\)]*)*\\)[ \t\n]*\{+[ \t]*(//[^\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \t]*[0-9]+)*" Tests "${Contents}")
 
     if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests)
-      PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property")
+      ParseAndAddCatchTests_PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property")
       set_property(
         DIRECTORY
         APPEND
@@ -97,14 +120,22 @@ function(ParseFile SourceFile TestTarget)
       )
     endif()
 
+    # check CMP0110 policy for new add_test() behavior
+    if(POLICY CMP0110)
+        cmake_policy(GET CMP0110 _cmp0110_value) # new add_test() behavior
+    else()
+        # just to be thorough explicitly set the variable
+        set(_cmp0110_value)
+    endif()
+
     foreach(TestName ${Tests})
         # Strip newlines
         string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}")
 
         # Get test type and fixture if applicable
-        string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}")
-        string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}")
-        string(REPLACE "${TestType}(" "" TestFixture "${TestTypeAndFixture}")
+        string(REGEX MATCH "(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}")
+        string(REGEX MATCH "(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}")
+        string(REGEX REPLACE "${TestType}\\([ \t]*" "" TestFixture "${TestTypeAndFixture}")
 
         # Get string parts of test definition
         string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}")
@@ -124,7 +155,7 @@ function(ParseFile SourceFile TestTarget)
         if("${TestType}" STREQUAL "SCENARIO")
             set(Name "Scenario: ${Name}")
         endif()
-        if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND TestFixture)
+        if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND "${TestType}" MATCHES "(CATCH_)?TEST_CASE_METHOD" AND TestFixture )
             set(CTestName "${TestFixture}:${Name}")
         else()
             set(CTestName "${Name}")
@@ -143,43 +174,79 @@ function(ParseFile SourceFile TestTarget)
             endif()
             string(REPLACE "]" ";" Tags "${Tags}")
             string(REPLACE "[" "" Tags "${Tags}")
+        else()
+          # unset tags variable from previous loop
+          unset(Tags)
         endif()
 
         list(APPEND Labels ${Tags})
 
-        list(FIND Labels "!hide" IndexOfHideLabel)
         set(HiddenTagFound OFF)
         foreach(label ${Labels})
             string(REGEX MATCH "^!hide|^\\." result ${label})
             if(result)
                 set(HiddenTagFound ON)
                 break()
-            endif(result)
+            endif()
         endforeach(label)
-        if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound})
-            PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label")
+        if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_LESS "3.9")
+            ParseAndAddCatchTests_PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label")
         else()
-            PrintDebugMessage("Adding test \"${CTestName}\"")
+            ParseAndAddCatchTests_PrintDebugMessage("Adding test \"${CTestName}\"")
             if(Labels)
-                PrintDebugMessage("Setting labels to ${Labels}")
+                ParseAndAddCatchTests_PrintDebugMessage("Setting labels to ${Labels}")
+            endif()
+
+            # Escape commas in the test spec
+            string(REPLACE "," "\\," Name ${Name})
+
+            # Work around CMake 3.18.0 change in `add_test()`, before the escaped quotes were necessary,
+            # only with CMake 3.18.0 the escaped double quotes confuse the call. This change is reverted in 3.18.1
+            # And properly introduced in 3.19 with the CMP0110 policy
+            if(_cmp0110_value STREQUAL "NEW" OR ${CMAKE_VERSION} VERSION_EQUAL "3.18")
+                ParseAndAddCatchTests_PrintDebugMessage("CMP0110 set to NEW, no need for add_test(\"\") workaround")
+            else()
+                ParseAndAddCatchTests_PrintDebugMessage("CMP0110 set to OLD adding \"\" for add_test() workaround")
+                set(CTestName "\"${CTestName}\"")
+            endif()
+
+            # Handle template test cases
+            if("${TestTypeAndFixture}" MATCHES ".*TEMPLATE_.*")
+              set(Name "${Name} - *")
             endif()
 
             # Add the test and set its properties
-            add_test(NAME "\"${CTestName}\"" COMMAND ${TestTarget} ${Name} ${AdditionalCatchParameters})
-            set_tests_properties("\"${CTestName}\"" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran"
-                                                    LABELS "${Labels}")
+            add_test(NAME "${CTestName}" COMMAND ${OptionalCatchTestLauncher} $<TARGET_FILE:${TestTarget}> ${Name} ${AdditionalCatchParameters})
+            # Old CMake versions do not document VERSION_GREATER_EQUAL, so we use VERSION_GREATER with 3.8 instead
+            if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_GREATER "3.8")
+                ParseAndAddCatchTests_PrintDebugMessage("Setting DISABLED test property")
+                set_tests_properties("${CTestName}" PROPERTIES DISABLED ON)
+            else()
+                set_tests_properties("${CTestName}" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran"
+                                                        LABELS "${Labels}")
+            endif()
+            set_property(
+              TARGET ${TestTarget}
+              APPEND
+              PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}")
+            set_property(
+              SOURCE ${SourceFile}
+              APPEND
+              PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}")
         endif()
 
+
     endforeach()
 endfunction()
 
 # entry point
 function(ParseAndAddCatchTests TestTarget)
-    PrintDebugMessage("Started parsing ${TestTarget}")
+    message(DEPRECATION "ParseAndAddCatchTest: function deprecated because of possibility of missed test cases. Consider using 'catch_discover_tests' from 'Catch.cmake'")
+    ParseAndAddCatchTests_PrintDebugMessage("Started parsing ${TestTarget}")
     get_target_property(SourceFiles ${TestTarget} SOURCES)
-    PrintDebugMessage("Found the following sources: ${SourceFiles}")
+    ParseAndAddCatchTests_PrintDebugMessage("Found the following sources: ${SourceFiles}")
     foreach(SourceFile ${SourceFiles})
-        ParseFile(${SourceFile} ${TestTarget})
+        ParseAndAddCatchTests_ParseFile(${SourceFile} ${TestTarget})
     endforeach()
-    PrintDebugMessage("Finished parsing ${TestTarget}")
+    ParseAndAddCatchTests_PrintDebugMessage("Finished parsing ${TestTarget}")
 endfunction()
