From 60c8b878292ec4afc625a807cdc3902397a408a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Sun, 26 Jul 2026 10:51:31 +0200 Subject: [PATCH] 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. --- extras/CatchAddTests.cmake | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/extras/CatchAddTests.cmake b/extras/CatchAddTests.cmake index 8005f31f..a8a20754 100644 --- a/extras/CatchAddTests.cmake +++ b/extras/CatchAddTests.cmake @@ -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