Avoid n^2 behaviour when appending script commands in catch_discover_tests

Repeatedly calling `string(APPEND` on the same destination string
leads to quadratic running time. We avoid this by flushing the CTest
script into a file periodically, currently every 50kB of text.

This can cause _slight_ slowdown just around the flush boundary (each
file write is quite expensive, so if we flush just before the last test
is written, it hurts), but it avoids terrible performance for large
test suites.

The performance is roughly equal at 500 tests to discover, and clearly
wins at more; for 1/2/4/8 k tests, the improvements are 0.3/1.6/6.4/27 s.
This commit is contained in:
Martin Hořeňovský
2026-07-26 15:50:33 +02:00
parent 64a551e2e7
commit 60c8b87829
+17 -2
View File
@@ -75,6 +75,12 @@ function(catch_discover_tests_impl)
${ARGN}
)
# We periodically append to the output file below, so we have to ensure
# that it is empty at the start, or we get duplicated test scripts.
file(REMOVE "${_CTEST_FILE}")
# Size (in Bytes) at which the intermediate `script` var is dumped to file.
set(_WriteToFileThreshold 50000)
set(add_tags "${_ADD_TAGS_AS_LABELS}")
set(prefix "${_TEST_PREFIX}")
set(suffix "${_TEST_SUFFIX}")
@@ -213,6 +219,15 @@ function(catch_discover_tests_impl)
math(EXPR num_tests "${num_tests} - 1")
foreach(idx RANGE ${num_tests})
string(LENGTH "${script}" script_len)
# Because appending to the same string in CMake has quadratic runtime,
# we flush the script into the file periodically to avoid the worst case.
if (script_len GREATER _WriteToFileThreshold)
file(APPEND "${_CTEST_FILE}" "${script}")
set(script "")
endif()
if(add_tags)
string(JSON single_test GET "${test_listing}" ${idx})
string(JSON test_tags GET "${single_test}" "tags")
@@ -295,8 +310,8 @@ function(catch_discover_tests_impl)
prepare_command(set ${_TEST_LIST} ${tests})
string(APPEND script "${_Command}")
# Write CTest script
file(WRITE "${_CTEST_FILE}" "${script}")
# Write any script leftovers we have
file(APPEND "${_CTEST_FILE}" "${script}")
endfunction()
# To enable `include`ing this file in the unit test scripts, we only run