GoogleTest: Revert "Base on builtin command discover_tests"

Revert commit a324c2bb58 (GoogleTest: Base on builtin command
discover_tests, 2026-03-01, v4.4.0-rc1~30^2).  The `discover_tests`
implementation does not persist already-discovered tests across ctest
runs, making it much slower on projects whose test binaries take a while
just to start up and list tests.  Revert to the prior implementation
pending caching support in `discover_tests`.

Fixes: #28053
Issue: #28060
This commit is contained in:
Daniel Pfeifer
2026-08-28 11:08:59 -04:00
committed by Brad King
parent c0ef680605
commit 5e5467bfc3
8 changed files with 87 additions and 270 deletions
+63 -111
View File
@@ -669,40 +669,63 @@ function(gtest_discover_tests target)
string(APPEND ctest_file_base "_$<CONFIG>")
endif()
set(discovery_file "${ctest_file_base}_discovery.cmake")
set(ctest_include_file "${ctest_file_base}_include.cmake")
set(ctest_tests_file "${ctest_file_base}_tests.cmake")
list(JOIN test_executor "]==] [==[" test_executor)
list(JOIN arg_EXTRA_ARGS "]==] [==[" arg_EXTRA_ARGS)
list(JOIN arg_PROPERTIES "]==] [==[" arg_PROPERTIES)
list(JOIN arg_DISCOVERY_EXTRA_ARGS "]==] [==[" arg_DISCOVERY_EXTRA_ARGS)
# Resolve alias to support custom target properties.
get_target_property(_gt_real_target ${target} ALIASED_TARGET)
if(NOT _gt_real_target)
set(_gt_real_target ${target})
endif()
get_target_property(
_current_gt_post_build_discovery ${_gt_real_target} _GT_POST_BUILD_DISCOVERY
)
if("${discovery_file}" IN_LIST _current_gt_post_build_discovery)
message(AUTHOR_WARNING
"gtest_discover_tests() invoked multiple times with the same arguments "
"for the same target '${target}'. This invocation will be ignored."
)
return()
endif()
set_property(
TARGET ${_gt_real_target}
APPEND PROPERTY _GT_POST_BUILD_DISCOVERY "${discovery_file}"
)
string(CONCAT discovery_content
"include(\"${CMAKE_ROOT}/Modules/GoogleTestAddTests.cmake\")" "\n"
"gtest_discover_tests_impl(" "\n"
" TEST_TARGET" " [==[${target}]==]" "\n"
" TEST_EXECUTABLE" " [==[$<TARGET_FILE:${target}>]==]" "\n"
" TEST_EXECUTOR" " [==[${test_executor}]==]" "\n"
" TEST_WORKING_DIR" " [==[${arg_WORKING_DIRECTORY}]==]" "\n"
" TEST_EXTRA_ARGS" " [==[${arg_EXTRA_ARGS}]==]" "\n"
" TEST_PROPERTIES" " [==[${arg_PROPERTIES}]==]" "\n"
" TEST_PREFIX" " [==[${arg_TEST_PREFIX}]==]" "\n"
" TEST_SUFFIX" " [==[${arg_TEST_SUFFIX}]==]" "\n"
" TEST_FILTER" " [==[${arg_TEST_FILTER}]==]" "\n"
" NO_PRETTY_TYPES" " [==[${arg_NO_PRETTY_TYPES}]==]" "\n"
" NO_PRETTY_VALUES" " [==[${arg_NO_PRETTY_VALUES}]==]" "\n"
" TEST_LIST" " [==[${arg_TEST_LIST}]==]" "\n"
" CTEST_FILE" " [==[${ctest_tests_file}]==]" "\n"
" TEST_DISCOVERY_TIMEOUT" " [==[${arg_DISCOVERY_TIMEOUT}]==]" "\n"
" TEST_DISCOVERY_EXTRA_ARGS [==[${arg_DISCOVERY_EXTRA_ARGS}]==]" "\n"
" TEST_XML_OUTPUT_DIR" " [==[${arg_XML_OUTPUT_DIR}]==]" "\n"
" TEST_JSON_OUTPUT_DIR" " [==[${CMAKE_CURRENT_BINARY_DIR}]==]" "\n"
")" "\n"
)
file(GENERATE OUTPUT "${discovery_file}" CONTENT "${discovery_content}")
set(ctest_include_content)
if(arg_DISCOVERY_MODE STREQUAL "POST_BUILD")
set(discovery_file "${ctest_file_base}_discovery.cmake")
set(ctest_tests_file "${ctest_file_base}_tests.cmake")
list(JOIN test_executor "]==] [==[" test_executor)
list(JOIN arg_EXTRA_ARGS "]==] [==[" arg_EXTRA_ARGS)
list(JOIN arg_PROPERTIES "]==] [==[" arg_PROPERTIES)
list(JOIN arg_DISCOVERY_EXTRA_ARGS "]==] [==[" arg_DISCOVERY_EXTRA_ARGS)
# Resolve alias to support custom target properties.
get_target_property(_gt_real_target ${target} ALIASED_TARGET)
if(NOT _gt_real_target)
set(_gt_real_target ${target})
endif()
get_target_property(
_current_gt_post_build_discovery ${_gt_real_target} _GT_POST_BUILD_DISCOVERY
)
if("${discovery_file}" IN_LIST _current_gt_post_build_discovery)
message(AUTHOR_WARNING
"gtest_discover_tests() invoked multiple times with the same arguments "
"for the same target '${target}'. This invocation will be ignored."
)
return()
endif()
set_property(
TARGET ${_gt_real_target}
APPEND PROPERTY _GT_POST_BUILD_DISCOVERY "${discovery_file}"
)
# Make sure that TEST_LAUNCHER and CROSSCOMPILING_EMULATOR appear on the
# command line, so that CMake can add them as implicit dependencies in the
# case where they are executable targets.
@@ -711,94 +734,23 @@ function(gtest_discover_tests target)
BYPRODUCTS "${ctest_tests_file}"
COMMAND "${CMAKE_COMMAND}" -P "${discovery_file}" -- "${test_executor}"
)
string(CONCAT discovery_content
"include(\"${CMAKE_ROOT}/Modules/GoogleTestAddTests.cmake\")" "\n"
"gtest_discover_tests_impl(" "\n"
" TEST_TARGET" " [==[${target}]==]" "\n"
" TEST_EXECUTABLE" " [==[$<TARGET_FILE:${target}>]==]" "\n"
" TEST_EXECUTOR" " [==[${test_executor}]==]" "\n"
" TEST_WORKING_DIR" " [==[${arg_WORKING_DIRECTORY}]==]" "\n"
" TEST_EXTRA_ARGS" " [==[${arg_EXTRA_ARGS}]==]" "\n"
" TEST_PROPERTIES" " [==[${arg_PROPERTIES}]==]" "\n"
" TEST_PREFIX" " [==[${arg_TEST_PREFIX}]==]" "\n"
" TEST_SUFFIX" " [==[${arg_TEST_SUFFIX}]==]" "\n"
" TEST_FILTER" " [==[${arg_TEST_FILTER}]==]" "\n"
" NO_PRETTY_TYPES" " [==[${arg_NO_PRETTY_TYPES}]==]" "\n"
" NO_PRETTY_VALUES" " [==[${arg_NO_PRETTY_VALUES}]==]" "\n"
" TEST_LIST" " [==[${arg_TEST_LIST}]==]" "\n"
" CTEST_FILE" " [==[${ctest_tests_file}]==]" "\n"
" TEST_DISCOVERY_TIMEOUT" " [==[${arg_DISCOVERY_TIMEOUT}]==]" "\n"
" TEST_DISCOVERY_EXTRA_ARGS [==[${arg_DISCOVERY_EXTRA_ARGS}]==]" "\n"
" TEST_XML_OUTPUT_DIR" " [==[${arg_XML_OUTPUT_DIR}]==]" "\n"
" TEST_JSON_OUTPUT_DIR" " [==[${CMAKE_CURRENT_BINARY_DIR}]==]" "\n"
")" "\n"
)
file(GENERATE OUTPUT "${discovery_file}" CONTENT "${discovery_content}")
string(CONCAT ctest_include_content
"if(EXISTS \"${ctest_tests_file}\")" "\n"
" include(\"${ctest_tests_file}\")" "\n"
)
elseif(arg_DISCOVERY_MODE STREQUAL "PRE_TEST")
set(test_xml_output "")
if(arg_XML_OUTPUT_DIR)
set(test_xml_output "${arg_XML_OUTPUT_DIR}/${arg_TEST_PREFIX}\\2${arg_TEST_SUFFIX}.xml")
endif()
set(test_properties "")
if(arg_PROPERTIES)
list(JOIN arg_PROPERTIES "]] [[" test_properties)
set(test_properties "[[${test_properties}]]")
endif()
string(CONCAT ctest_include_content
"if(EXISTS \"$<TARGET_FILE:${target}>\")" "\n"
" discover_tests(" "\n"
" COMMAND \"${CMAKE_COMMAND}\"" "\n"
" -D [[TEST_EXECUTABLE=$<TARGET_FILE:${target}>]]" "\n"
" -D [[TEST_EXECUTOR=${test_executor}]]" "\n"
" DISCOVERY_ARGS" "\n"
" -D [[TEST_FILTER=${arg_TEST_FILTER}]]" "\n"
" -D [[TEST_DISCOVERY_EXTRA_ARGS=${arg_DISCOVERY_EXTRA_ARGS}]]" "\n"
" -D [[NO_PRETTY_TYPES=${arg_NO_PRETTY_TYPES}]]" "\n"
" -D [[NO_PRETTY_VALUES=${arg_NO_PRETTY_VALUES}]]" "\n"
" -P [[${CMAKE_ROOT}/Modules/GoogleTest/DiscoverTests.cmake]]" "\n"
" DISCOVERY_MATCH" "\n"
" \"-- ([^#]+)#([^#]+)#DISABLED=([^#]+)#LOCATION=([^#]*)#\"" "\n"
" DISCOVERY_PROPERTIES" "\n"
" TIMEOUT [[${arg_DISCOVERY_TIMEOUT}]]" "\n"
" WORKING_DIRECTORY [[${arg_WORKING_DIRECTORY}]]" "\n"
" TEST_NAME [[${arg_TEST_PREFIX}\\1${arg_TEST_SUFFIX}]]" "\n"
" TEST_ARGS" "\n"
" -D [[TEST_FILTER=\\2]]" "\n"
" -D [[TEST_XML_OUTPUT=${test_xml_output}]]" "\n"
" -D [[TEST_EXTRA_ARGS=${arg_EXTRA_ARGS}]]" "\n"
" -P [[${CMAKE_ROOT}/Modules/GoogleTest/LaunchTest.cmake]]" "\n"
" TEST_PROPERTIES" "\n"
" DISABLED [[\\3]]" "\n"
" DEF_SOURCE_LINE [[\\4]]" "\n"
" SKIP_REGULAR_EXPRESSION \"\\\\[ SKIPPED \\\\]\"" "\n"
" WORKING_DIRECTORY [[${arg_WORKING_DIRECTORY}]]" "\n"
" ${test_properties}" "\n"
" TEST_LIST ${arg_TEST_LIST}" "\n"
" )" "\n"
)
# For backward compatibility with the original implementation, filter out
# tests with square brackets in their names. Temporarily replace semicolons
# with newline characters to avoid issues with list filtering.
string(APPEND ctest_include_content
" list(TRANSFORM ${arg_TEST_LIST} REPLACE \";\" \"\\n\")" "\n"
" list(FILTER ${arg_TEST_LIST} EXCLUDE REGEX \"(\\\\[|])\")" "\n"
" list(TRANSFORM ${arg_TEST_LIST} REPLACE \"\\n\" [[\\\\;]])" "\n"
"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(\"${discovery_file}\")" "\n"
" endif()" "\n"
"endif()" "\n"
)
else()
message(FATAL_ERROR "Unknown DISCOVERY_MODE: ${arg_DISCOVERY_MODE}")
endif()
string(APPEND ctest_include_content
"if(EXISTS \"${ctest_tests_file}\")" "\n"
" include(\"${ctest_tests_file}\")" "\n"
"else()" "\n"
" add_test(${target}_NOT_BUILT ${target}_NOT_BUILT)" "\n"
"endif()" "\n"
-112
View File
@@ -1,112 +0,0 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file LICENSE.rst or https://cmake.org/licensing for details.
cmake_minimum_required(VERSION 4.3)
include("${CMAKE_CURRENT_LIST_DIR}/ParseTestList.cmake")
set(filter)
if(TEST_FILTER)
set(filter "--gtest_filter=${TEST_FILTER}")
endif()
set(launcher "")
if(NOT TEST_EXECUTOR STREQUAL "")
list(JOIN TEST_EXECUTOR "]==] [==[" launcher)
set(launcher "[==[${launcher}]==]")
endif()
set(discovery_extra_args "")
if(NOT TEST_DISCOVERY_EXTRA_ARGS STREQUAL "")
list(JOIN TEST_DISCOVERY_EXTRA_ARGS "]==] [==[" discovery_extra_args)
set(discovery_extra_args "[==[${discovery_extra_args}]==]")
endif()
string(SHA256 target_hash "${TEST_EXECUTABLE}")
string(SUBSTRING "${target_hash}" 0 10 target_hash)
set(json_file "${CMAKE_BINARY_DIR}/CMakeFiles/googletest_discovery_${target_hash}.json")
file(REMOVE "${json_file}")
cmake_language(EVAL CODE
"execute_process(
COMMAND ${launcher} [==[${TEST_EXECUTABLE}]==]
--gtest_list_tests
[==[--gtest_output=json:${json_file}]==]
${filter}
${discovery_extra_args}
COMMAND_ERROR_IS_FATAL LAST
OUTPUT_VARIABLE output
)"
)
macro(write_test_line)
# Store the gtest test name before messing with these strings
set(gtest_name ${current_test_suite}.${current_test_name})
set(pretty_test_suite ${current_test_suite})
set(pretty_test_name ${current_test_name})
# Handle disabled tests
set(disabled OFF)
if(pretty_test_suite MATCHES "^DISABLED_" OR pretty_test_name MATCHES "^DISABLED_")
set(disabled ON)
string(REGEX REPLACE "^DISABLED_" "" pretty_test_suite "${pretty_test_suite}")
string(REGEX REPLACE "^DISABLED_" "" pretty_test_name "${pretty_test_name}")
endif()
if (NOT current_test_value_param STREQUAL "" AND NOT NO_PRETTY_VALUES)
# If the test name contains a value parameter name (part of the string after last slash), and this
# name is an integer which indicates that it's the default name generated by googletest, replace it by
# the value parameter name provided separately
if("${pretty_test_name}" MATCHES "^(.*)/[0-9]+$")
set(pretty_test_name "${CMAKE_MATCH_1}/${current_test_value_param}")
endif()
endif()
if(NOT current_test_type_param STREQUAL "")
# Parse type param name from suite name
if(pretty_test_suite MATCHES "^(.+)/(.+)$")
set(pretty_test_suite "${CMAKE_MATCH_1}")
set(current_type_param_name "${CMAKE_MATCH_2}")
else()
set(current_type_param_name "")
endif()
if (NOT NO_PRETTY_TYPES)
string(APPEND pretty_test_name "<${current_test_type_param}>")
elseif(NOT current_type_param_name STREQUAL "")
string(APPEND pretty_test_name "<${current_type_param_name}>")
endif()
endif()
set(testname "${pretty_test_suite}.${pretty_test_name}")
# unescape []
if(open_sb)
string(REPLACE "${open_sb}" "[" testname "${testname}")
endif()
if(close_sb)
string(REPLACE "${close_sb}" "]" testname "${testname}")
endif()
set(location "")
if(NOT current_test_file STREQUAL "" AND NOT current_test_line STREQUAL "")
set(location "${current_test_file}:${current_test_line}")
endif()
message(STATUS "${testname}#${gtest_name}#DISABLED=${disabled}#LOCATION=${location}#")
endmacro()
if(EXISTS "${json_file}")
parse_tests_from_json("${json_file}" write_test_line)
else()
# gtest < 1.8.1, and all gtest compiled with GTEST_HAS_FILE_SYSTEM=0, don't
# recognize the --gtest_output=json option, and issue a warning or error on
# stdout about it being unrecognized, but still return an exit code 0 for
# success. All versions report the test list on stdout whether
# --gtest_output=json is recognized or not.
# NOTE: Because we are calling a macro, we don't want to pass "output" as
# an argument because it messes up the contents passed through due to the
# different escaping, etc. that gets applied. We rely on it picking up the
# "output" variable we have already set here.
parse_tests_from_output(write_test_line)
endif()
-34
View File
@@ -1,34 +0,0 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file LICENSE.rst or https://cmake.org/licensing for details.
cmake_minimum_required(VERSION 4.3)
set(launcher "")
if(NOT "${TEST_EXECUTOR}" STREQUAL "")
list(JOIN TEST_EXECUTOR "]==] [==[" launcher)
set(launcher "[==[${launcher}]==]")
endif()
set(xml_output_arg "")
if(NOT TEST_XML_OUTPUT STREQUAL "")
set(xml_output_arg "[==[--gtest_output=xml:${TEST_XML_OUTPUT}]==]")
endif()
set(extra_args "")
if(TEST_EXTRA_ARGS)
list(JOIN TEST_EXTRA_ARGS "]==] [==[" extra_args)
set(extra_args "[==[${extra_args}]==]")
endif()
cmake_language(EVAL CODE
"execute_process(
COMMAND ${launcher} [==[${TEST_EXECUTABLE}]==]
[==[--gtest_filter=${TEST_FILTER}]==]
--gtest_also_run_disabled_tests
${xml_output_arg}
${extra_args}
RESULT_VARIABLE result
)"
)
cmake_language(EXIT ${result})
@@ -1 +1,11 @@
Process terminated due to timeout
CMake Error at [^
]*GoogleTestAddTests\.cmake:[0-9]+ \(message\):
[ \t]*Error running test executable\.
+
[ \t]*Path: '[^']*discovery_timeout_test(\.exe)?'
[ \t]*Working directory: '[^']*/Tests/RunCMake/GoogleTest/GoogleTest-discovery-timeout'
[ \t]*Timeout: '[^']*'
[ \t]*Result: Process terminated due to timeout
[ \t]*Output:
[ \t]*timeout\.
[ \t]*case
@@ -14,12 +14,13 @@ endif()
# Note that flushing errors would repeat a test name, so such errors are not
# uncovered by checking the name buffer flushing above.
# PRE_TEST writes a config-specific tests file
# PRE_TEST can have a config-specific tests file, POST_BUILD never does
set(tests_file "@CMAKE_CURRENT_BINARY_DIR@/flush_script_test_e3b0c442_Debug_tests.cmake")
# POST_BUILD does not write a tests file
if(NOT EXISTS "${tests_file}")
return()
set(tests_file "@CMAKE_CURRENT_BINARY_DIR@/flush_script_test_e3b0c442_tests.cmake")
endif()
if(NOT EXISTS "${tests_file}")
message(FATAL_ERROR "Tests file is missing")
endif()
file(STRINGS "${tests_file}" add_test_lines REGEX "^add_test" ENCODING UTF-8)
@@ -2,7 +2,7 @@ test 1
Start 1: launcher_test\.test1
1: Test command: [^
]*
]*[/\]Tests[/\]RunCMake[/\]GoogleTest[/\]Launcher-CMP0178-NEW-build([/\]Debug)?[/\]test_launcher(\.exe)?"? "" "launcherparam" "--" "[^"]*/Tests/RunCMake/GoogleTest/Launcher-CMP0178-NEW-build(/Debug)?/test_launcher(\.exe)?" "" "emulatorparam" "--" "[^"]*/Tests/RunCMake/GoogleTest/Launcher-CMP0178-NEW-build(/Debug)?/launcher_test(\.exe)?" "--gtest_filter=launcher_test\.test1" "--gtest_also_run_disabled_tests" "a" "" "b"
1: Working Directory: [^
]*/Tests/RunCMake/GoogleTest/Launcher-CMP0178-NEW-build
1: Test timeout computed to be: [0-9]+
@@ -24,7 +24,7 @@ test 2
Start 2: launcher_test\.test1
2: Test command: [^
]*
]*[/\]Tests[/\]RunCMake[/\]GoogleTest[/\]Launcher-CMP0178-NEW-build([/\]Debug)?[/\]test_launcher(\.exe)?"? "" "launcherparam" "--" "[^"]*/Tests/RunCMake/GoogleTest/Launcher-CMP0178-NEW-build(/Debug)?/launcher_test(\.exe)?" "--gtest_filter=launcher_test\.test1" "a" "" "b"
2: Working Directory: [^
]*/Tests/RunCMake/GoogleTest/Launcher-CMP0178-NEW-build
2: Test timeout computed to be: [0-9]+
@@ -1,8 +1,8 @@
test 1
Start 1: launcher_test\.test1
1: Test command: [^
]*
1: Test command: "?[^
]*[/\]Tests[/\]RunCMake[/\]GoogleTest[/\]Launcher-CMP0178-OLD-build([/\]Debug)?[/\]test_launcher(\.exe)?"? "launcherparam" "--" "[^"]*/Tests/RunCMake/GoogleTest/Launcher-CMP0178-OLD-build(/Debug)?/test_launcher(\.exe)?" "emulatorparam" "--" "[^"]*/Tests/RunCMake/GoogleTest/Launcher-CMP0178-OLD-build(/Debug)?/launcher_test(\.exe)?" "--gtest_filter=launcher_test\.test1" "--gtest_also_run_disabled_tests" "a" "b"
1: Working Directory: [^
]*/Tests/RunCMake/GoogleTest/Launcher-CMP0178-OLD-build
1: Test timeout computed to be: [0-9]+
@@ -22,7 +22,7 @@ test 2
Start 2: launcher_test\.test1
2: Test command: [^
]*
]*[/\]Tests[/\]RunCMake[/\]GoogleTest[/\]Launcher-CMP0178-OLD-build([/\]Debug)?[/\]test_launcher(\.exe)?"? "launcherparam" "--" "[^"]*/Tests/RunCMake/GoogleTest/Launcher-CMP0178-OLD-build(/Debug)?/launcher_test(\.exe)?" "--gtest_filter=launcher_test\.test1" "a" "b"
2: Working Directory: [^
]*/Tests/RunCMake/GoogleTest/Launcher-CMP0178-OLD-build
2: Test timeout computed to be: [0-9]+
@@ -1,8 +1,8 @@
test 1
Start 1: launcher_test\.test1
1: Test command: [^
]*
1: Test command: "?[^
]*[/\]Tests[/\]RunCMake[/\]GoogleTest[/\]Launcher-CMP0178-WARN-build([/\]Debug)?[/\]test_launcher(\.exe)?"? "launcherparam" "--" "[^"]*/Tests/RunCMake/GoogleTest/Launcher-CMP0178-WARN-build(/Debug)?/test_launcher(\.exe)?" "emulatorparam" "--" "[^"]*/Tests/RunCMake/GoogleTest/Launcher-CMP0178-WARN-build(/Debug)?/launcher_test(\.exe)?" "--gtest_filter=launcher_test\.test1" "--gtest_also_run_disabled_tests" "a" "b"
1: Working Directory: [^
]*/Tests/RunCMake/GoogleTest/Launcher-CMP0178-WARN-build
1: Test timeout computed to be: [0-9]+
@@ -22,7 +22,7 @@ test 2
Start 2: launcher_test\.test1
2: Test command: [^
]*
]*[/\]Tests[/\]RunCMake[/\]GoogleTest[/\]Launcher-CMP0178-WARN-build([/\]Debug)?[/\]test_launcher(\.exe)?"? "launcherparam" "--" "[^"]*/Tests/RunCMake/GoogleTest/Launcher-CMP0178-WARN-build(/Debug)?/launcher_test(\.exe)?" "--gtest_filter=launcher_test\.test1" "a" "b"
2: Working Directory: [^
]*/Tests/RunCMake/GoogleTest/Launcher-CMP0178-WARN-build
2: Test timeout computed to be: [0-9]+