GoogleTest: Base on builtin command discover_tests

This commit is contained in:
Daniel Pfeifer
2026-06-02 21:19:28 +02:00
parent 6b6e9fbc32
commit a324c2bb58
8 changed files with 243 additions and 63 deletions
+87 -39
View File
@@ -661,39 +661,18 @@ 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)
string(CONCAT discovery_content
"include(\"${CMAKE_ROOT}/Modules/GoogleTestAddTests.cmake\")" "\n"
"gtest_discover_tests_impl(" "\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"
")" "\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)
# 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.
@@ -702,23 +681,92 @@ 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_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"
")" "\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")
string(APPEND ctest_include_content
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"
" 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"
" 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"
)
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"
+109
View File
@@ -0,0 +1,109 @@
# 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)
# Remove value param name, if any, from test name
string(REGEX REPLACE "^(.+)/.+$" "\\1" pretty_test_name "${pretty_test_name}")
set(pretty_test_name "${pretty_test_name}/${current_test_value_param}")
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
@@ -0,0 +1,34 @@
# 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,11 +1 @@
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
Process terminated due to timeout
@@ -14,13 +14,12 @@ 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 can have a config-specific tests file, POST_BUILD never does
# PRE_TEST writes a config-specific tests file
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}")
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")
return()
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: "?[^
]*[/\]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: Test command: [^
]*
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: "?[^
]*[/\]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: Test command: [^
]*
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]+