Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/extras
diff options
context:
space:
mode:
authorMartin Hořeňovský2026-08-08 14:59:04 +0200
committerMartin Hořeňovský2026-08-10 00:17:26 +0200
commit630840c5000ec9f5e169c8a2f5fd163d3b0c54ca (patch)
treedf4e6a4290688c37120a1b6fd3b520ec8ce9a741 /extras
parent4cde12851773b32fc5a0171f262e4281c8e28b01 (diff)
downloadcatch3-630840c5000ec9f5e169c8a2f5fd163d3b0c54ca.tar.gz
Fix <target>_TESTS variable generated by `catch_discover_tests`
There were 2 separate bugs, one recently added and one ancient (since roughly the first version of the registration script). 1) The recent refactoring of how the JSON output from Catch2 is parsed caused shadowing between the variable containing the per-test JSON fragments and the accumulator of test names for <target>_TESTS variable. This led to the variable containing both the names and the JSON fragments, and thus being completely wrong. 2) The test name accumulation has never accounted for characters that need escaping to be present in a CMake list. This means that e.g. test names with semicolon in them would end up with two elements in the test list. Both of these are now fixed, at the cost of extra complexity and my sanity as I had to learn more about CMake escaping rules. (CMake escaping rules are dumb)
Diffstat (limited to 'extras')
-rw-r--r--extras/Catch.cmake5
-rw-r--r--extras/CatchAddTests.cmake48
2 files changed, 44 insertions, 9 deletions
diff --git a/extras/Catch.cmake b/extras/Catch.cmake
index 9426f67f..c143d330 100644
--- a/extras/Catch.cmake
+++ b/extras/Catch.cmake
@@ -58,6 +58,11 @@ same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
directory property. The set of discovered tests is made accessible to such a
script via the ``<target>_TESTS`` variable.
+ Note that ``<target>_TESTS`` variable contains test names with brackets
+ ("[", "]") escaped into ASCII char 2, 3 respectively, to work around CMake's
+ list parsing rules. You have to unescape them back for each element to get
+ the original names.
+
The options are:
``target``
diff --git a/extras/CatchAddTests.cmake b/extras/CatchAddTests.cmake
index 6116e4b8..46a81a85 100644
--- a/extras/CatchAddTests.cmake
+++ b/extras/CatchAddTests.cmake
@@ -12,14 +12,28 @@
# exist in JSON unescaped.
#
# 0x01 <=> ';' (CMake list separator)
-# 0x02 == element boundary marker used while splitting the tests array
-# 0x03 <-> '[' (opens a CMake bracket-argument context)
-# 0x04 <-> ']' (closes a CMake bracket-argument context)
+# 0x02 <-> '[' (opens a CMake bracket-argument context)
+# 0x03 <-> ']' (closes a CMake bracket-argument context)
+# 0x04 == element boundary marker used while splitting the tests array
#
string(ASCII 1 _SemicolonEscape)
-string(ASCII 2 _BoundaryEscape)
-string(ASCII 3 _OpenBracketEscape)
-string(ASCII 4 _CloseBracketEscape)
+string(ASCII 2 _OpenBracketEscape)
+string(ASCII 3 _CloseBracketEscape)
+string(ASCII 4 _BoundaryEscape)
+
+
+# As far as I can tell, the only way of having unclosed '[' or ']' in the
+# list of test names we define for CTest, is to escape them into completely
+# different characters. Otherwise, they will break parsing of unrelated
+# semicolons (inserted by CMake after parsing the list from the user-facing
+# space separated format). This means that the consumers of the list have
+# to unescape them back, but that's CMake :v
+#
+# 0x02 <-> '['
+# 0x03 <-> ']'
+#
+string(ASCII 2 _OpenBracketListingEscape)
+string(ASCII 3 _CloseBracketListingEscape)
# Placeholder bytes in the listing would break our parsing hack, so
@@ -221,6 +235,10 @@ function(catch_discover_tests_impl)
set(script)
set(suite)
set(tests)
+ # Holds the list of all registered test names **as a string**, not list.
+ # This avoids issue of CMake removing semicolon escapes even inside
+ # bracket-quoted strings.
+ set(_test_names)
if(WIN32)
set(dl_paths_variable_name PATH)
@@ -431,13 +449,25 @@ function(catch_discover_tests_impl)
string(APPEND script "${_Command}")
endif()
- list(APPEND tests "${prefix}${plain_name}${suffix}")
+ # The test name has to be escaped using the same rules as prepare_command
+ # uses for the arguments, so that it keeps being single element in list
+ # even with weird characters and semicolons.
+ set(full_list_name "${prefix}${plain_name}${suffix}")
+ string(REPLACE ";" "\\;" full_list_name "${full_list_name}")
+ string(REPLACE "[" "${_OpenBracketListingEscape}" full_list_name "${full_list_name}")
+ string(REPLACE "]" "${_CloseBracketListingEscape}" full_list_name "${full_list_name}")
+ if(full_list_name MATCHES "[^-./:a-zA-Z0-9_]")
+ # The space before the start of quote is important, so that we get
+ # space-separated list in the final file.
+ string(APPEND _test_names " [==[${full_list_name}]==]")
+ else()
+ string(APPEND _test_names " ${full_list_name}")
+ endif()
endforeach()
# Create a list of all discovered tests, which users may use to e.g. set
# properties on the tests
- prepare_command(set ${_TEST_LIST} ${tests})
- string(APPEND script "${_Command}")
+ string(APPEND script "set(${_TEST_LIST}${_test_names})\n")
# Write any script leftovers we have
file(APPEND "${_CTEST_FILE}" "${script}")