GoogleTest: Avoid generation error on duplicate target test discovery

The discovery script created by commit 3748ca9f (GoogleTest: generate
single discovery script, 2026-04-08, v4.4.0-rc1~302^2~1) creates
generate-time collisions if gtest_discover_tests() is called on the
same target in the same directory with the same arguments multiple
times. Handle the case with a warning to preserve compatibility with
existing projects.

Fixes: #27940
This commit is contained in:
Tyler Yankee
2026-07-14 12:14:29 -04:00
committed by Brad King
parent 4f0dea4ea5
commit c3c95f3010
4 changed files with 105 additions and 34 deletions

View File

@@ -673,43 +673,64 @@ function(gtest_discover_tests target)
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.
add_custom_command(
TARGET ${target} POST_BUILD
BYPRODUCTS "${ctest_tests_file}"
COMMAND "${CMAKE_COMMAND}" -P "${discovery_file}" -- "${test_executor}"
)
# 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()
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"
get_target_property(
_current_gt_post_build_discovery ${_gt_real_target} _GT_POST_BUILD_DISCOVERY
)
file(GENERATE OUTPUT "${discovery_file}" CONTENT "${discovery_content}")
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()
else()
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.
add_custom_command(
TARGET ${target} POST_BUILD
BYPRODUCTS "${ctest_tests_file}"
COMMAND "${CMAKE_COMMAND}" -P "${discovery_file}" -- "${test_executor}"
)
string(CONCAT ctest_include_content
"if(EXISTS \"${ctest_tests_file}\")" "\n"
" include(\"${ctest_tests_file}\")" "\n"
)
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"
)
endif()
elseif(arg_DISCOVERY_MODE STREQUAL "PRE_TEST")
set(test_xml_output "")

View File

@@ -0,0 +1,8 @@
CMake Warning \(author\) at [^
]*/Modules/GoogleTest\.cmake:[0-9]+ \(message\):
gtest_discover_tests\(\) invoked multiple times with the same arguments for
the same target 'fake_gtest'\. This invocation will be ignored\.
Call Stack \(most recent call first\):
GoogleTestDiscoveryDuplicate\.cmake:[0-9]+ \(gtest_discover_tests\)
CMakeLists\.txt:[0-9]+ \(include\)
This warning is for project developers\. Use -Wno-author to suppress it\.

View File

@@ -0,0 +1,20 @@
enable_language(CXX)
include(GoogleTest)
enable_testing()
include(xcode_sign_adhoc.cmake)
add_executable(fake_gtest fake_gtest.cpp)
xcode_sign_adhoc(fake_gtest)
gtest_discover_tests(fake_gtest
DISCOVERY_MODE POST_BUILD
TEST_PREFIX DUPLICATE:
PROPERTIES LABELS DUPLICATE
)
gtest_discover_tests(fake_gtest
DISCOVERY_MODE POST_BUILD
TEST_PREFIX DUPLICATE:
PROPERTIES LABELS DUPLICATE
)

View File

@@ -487,6 +487,26 @@ function(run_GoogleTest_DEF_SOURCE_LINE)
)
endfunction()
function(run_GoogleTest_discovery_duplicate)
# Use a single build tree for a few tests without cleaning.
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/GoogleTest-discovery-duplicate-build)
set(RunCMake_TEST_NO_CLEAN 1)
if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Debug)
endif()
file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
run_cmake(GoogleTestDiscoveryDuplicate)
run_cmake_command(GoogleTest-discovery-duplicate-build
${CMAKE_COMMAND}
--build .
--config Debug
--target fake_gtest
)
endfunction()
foreach(DISCOVERY_MODE POST_BUILD PRE_TEST)
message(STATUS "Testing ${DISCOVERY_MODE} discovery mode via CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE global override...")
run_GoogleTest(${DISCOVERY_MODE})
@@ -542,3 +562,5 @@ run_GoogleTest_DEF_SOURCE_LINE()
if (NOT RunCMake_GENERATOR MATCHES "(Borland|NMake|Watcom)")
run_GoogleTest_discovery_post_build_race()
endif()
run_GoogleTest_discovery_duplicate()