From c3c95f3010d902bd6a652fae442b3e09572ffb60 Mon Sep 17 00:00:00 2001 From: Tyler Yankee Date: Tue, 14 Jul 2026 12:14:29 -0400 Subject: [PATCH] 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 --- Modules/GoogleTest.cmake | 89 ++++++++++++------- .../GoogleTestDiscoveryDuplicate-stderr.txt | 8 ++ .../GoogleTestDiscoveryDuplicate.cmake | 20 +++++ Tests/RunCMake/GoogleTest/RunCMakeTest.cmake | 22 +++++ 4 files changed, 105 insertions(+), 34 deletions(-) create mode 100644 Tests/RunCMake/GoogleTest/GoogleTestDiscoveryDuplicate-stderr.txt create mode 100644 Tests/RunCMake/GoogleTest/GoogleTestDiscoveryDuplicate.cmake diff --git a/Modules/GoogleTest.cmake b/Modules/GoogleTest.cmake index 61c2bc2ae6..eb1e269c16 100644 --- a/Modules/GoogleTest.cmake +++ b/Modules/GoogleTest.cmake @@ -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" " [==[$]==]" "\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" " [==[$]==]" "\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 "") diff --git a/Tests/RunCMake/GoogleTest/GoogleTestDiscoveryDuplicate-stderr.txt b/Tests/RunCMake/GoogleTest/GoogleTestDiscoveryDuplicate-stderr.txt new file mode 100644 index 0000000000..c947f779e2 --- /dev/null +++ b/Tests/RunCMake/GoogleTest/GoogleTestDiscoveryDuplicate-stderr.txt @@ -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\. diff --git a/Tests/RunCMake/GoogleTest/GoogleTestDiscoveryDuplicate.cmake b/Tests/RunCMake/GoogleTest/GoogleTestDiscoveryDuplicate.cmake new file mode 100644 index 0000000000..664a950d63 --- /dev/null +++ b/Tests/RunCMake/GoogleTest/GoogleTestDiscoveryDuplicate.cmake @@ -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 +) diff --git a/Tests/RunCMake/GoogleTest/RunCMakeTest.cmake b/Tests/RunCMake/GoogleTest/RunCMakeTest.cmake index 8ebcfb8894..ed122717b7 100644 --- a/Tests/RunCMake/GoogleTest/RunCMakeTest.cmake +++ b/Tests/RunCMake/GoogleTest/RunCMakeTest.cmake @@ -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()