From 6b6e9fbc3296f5d0b285fbc2df1201948749138b Mon Sep 17 00:00:00 2001 From: Daniel Pfeifer Date: Sun, 1 Mar 2026 22:40:15 +0100 Subject: [PATCH] GoogleTest: Move test list parsing to separate file --- Modules/GoogleTest/ParseTestList.cmake | 145 ++++++++++++++++++++++++ Modules/GoogleTestAddTests.cmake | 147 +------------------------ 2 files changed, 148 insertions(+), 144 deletions(-) create mode 100644 Modules/GoogleTest/ParseTestList.cmake diff --git a/Modules/GoogleTest/ParseTestList.cmake b/Modules/GoogleTest/ParseTestList.cmake new file mode 100644 index 0000000000..077e8741ec --- /dev/null +++ b/Modules/GoogleTest/ParseTestList.cmake @@ -0,0 +1,145 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file LICENSE.rst or https://cmake.org/licensing for details. + +include_guard(GLOBAL) + +function(generate_testname_guards output open_guard_var close_guard_var) + set(open_guard "[=[") + set(close_guard "]=]") + set(counter 1) + while("${output}" MATCHES "${close_guard}") + math(EXPR counter "${counter} + 1") + string(REPEAT "=" ${counter} equals) + set(open_guard "[${equals}[") + set(close_guard "]${equals}]") + endwhile() + set(${open_guard_var} "${open_guard}" PARENT_SCOPE) + set(${close_guard_var} "${close_guard}" PARENT_SCOPE) +endfunction() + +function(escape_square_brackets output bracket placeholder placeholder_var output_var) + if("${output}" MATCHES "\\${bracket}") + set(placeholder "${placeholder}") + while("${output}" MATCHES "${placeholder}") + set(placeholder "${placeholder}_") + endwhile() + string(REPLACE "${bracket}" "${placeholder}" output "${output}") + set(${placeholder_var} "${placeholder}" PARENT_SCOPE) + set(${output_var} "${output}" PARENT_SCOPE) + endif() +endfunction() + +macro(parse_tests_from_output per_test_callback) + generate_testname_guards("${output}" open_guard close_guard) + escape_square_brackets("${output}" "[" "__osb" open_sb output) + escape_square_brackets("${output}" "]" "__csb" close_sb output) + + # Preserve semicolon in test-parameters + string(REPLACE [[;]] [[\;]] output "${output}") + string(REPLACE "\n" ";" output "${output}") + + # Command line output doesn't contain information about the file and line number of the tests + set(current_test_file "") + set(current_test_line "") + + # Parse output + foreach(line ${output}) + # Skip header + if(line MATCHES "gtest_main\\.cc") + continue() + endif() + + if(line STREQUAL "") + continue() + endif() + + # Do we have a module name or a test name? + if(NOT line MATCHES "^ ") + set(current_test_type_param "") + + # Module; remove trailing '.' to get just the name... + string(REGEX REPLACE "\\.( *#.*)?$" "" current_test_suite "${line}") + if(line MATCHES "# *TypeParam = (.*)$") + set(current_test_type_param "${CMAKE_MATCH_1}") + endif() + else() + string(STRIP "${line}" test) + string(REGEX REPLACE " ( *#.*)?$" "" current_test_name "${test}") + + set(current_test_value_param "") + if(line MATCHES "# *GetParam\\(\\) = (.*)$") + set(current_test_value_param "${CMAKE_MATCH_1}") + endif() + + cmake_language(CALL ${per_test_callback}) + endif() + endforeach() +endmacro() + +macro(get_json_member_with_default json_variable member_name out_variable) + string(JSON ${out_variable} + ERROR_VARIABLE error_param + GET "${${json_variable}}" "${member_name}" + ) + if(error_param) + # Member not present + set(${out_variable} "") + endif() +endmacro() + +macro(parse_tests_from_json json_file per_test_callback) + if(NOT EXISTS "${json_file}") + message(FATAL_ERROR "Missing expected JSON file with test list: ${json_file}") + endif() + + file(READ "${json_file}" test_json) + string(JSON test_suites_json GET "${test_json}" "testsuites") + + # Return if there are no testsuites + string(JSON len_test_suites LENGTH "${test_suites_json}") + if(len_test_suites GREATER 0) + set(open_sb) + set(close_sb) + + math(EXPR upper_limit_test_suite_range "${len_test_suites} - 1") + + foreach(index_test_suite RANGE ${upper_limit_test_suite_range}) + string(JSON test_suite_json GET "${test_suites_json}" ${index_test_suite}) + + # "suite" is expected to be set in write_test_to_file(). When parsing the + # plain text output, "suite" is expected to be the original suite name + # before accounting for pretty names. This may be used to construct the + # name of XML output results files. + string(JSON current_test_suite GET "${test_suite_json}" "name") + string(JSON tests_json GET "${test_suite_json}" "testsuite") + + # Skip test suites without tests + string(JSON len_tests LENGTH "${tests_json}") + if(len_tests LESS_EQUAL 0) + continue() + endif() + + math(EXPR upper_limit_test_range "${len_tests} - 1") + foreach(index_test RANGE ${upper_limit_test_range}) + string(JSON test_json GET "${tests_json}" ${index_test}) + + string(JSON len_test_parameters LENGTH "${test_json}") + if(len_test_parameters LESS_EQUAL 0) + continue() + endif() + + get_json_member_with_default(test_json "name" current_test_name) + get_json_member_with_default(test_json "file" current_test_file) + get_json_member_with_default(test_json "line" current_test_line) + get_json_member_with_default(test_json "value_param" current_test_value_param) + get_json_member_with_default(test_json "type_param" current_test_type_param) + + generate_testname_guards( + "${current_test_suite}${current_test_name}${current_test_value_param}${current_test_type_param}" + open_guard close_guard + ) + cmake_language(CALL ${per_test_callback}) + endforeach() + endforeach() + endif() +endmacro() diff --git a/Modules/GoogleTestAddTests.cmake b/Modules/GoogleTestAddTests.cmake index c10c80a047..554b00fd58 100644 --- a/Modules/GoogleTestAddTests.cmake +++ b/Modules/GoogleTestAddTests.cmake @@ -2,32 +2,7 @@ # file LICENSE.rst or https://cmake.org/licensing for details. cmake_minimum_required(VERSION 4.2) - -function(generate_testname_guards output open_guard_var close_guard_var) - set(open_guard "[=[") - set(close_guard "]=]") - set(counter 1) - while("${output}" MATCHES "${close_guard}") - math(EXPR counter "${counter} + 1") - string(REPEAT "=" ${counter} equals) - set(open_guard "[${equals}[") - set(close_guard "]${equals}]") - endwhile() - set(${open_guard_var} "${open_guard}" PARENT_SCOPE) - set(${close_guard_var} "${close_guard}" PARENT_SCOPE) -endfunction() - -function(escape_square_brackets output bracket placeholder placeholder_var output_var) - if("${output}" MATCHES "\\${bracket}") - set(placeholder "${placeholder}") - while("${output}" MATCHES "${placeholder}") - set(placeholder "${placeholder}_") - endwhile() - string(REPLACE "${bracket}" "${placeholder}" output "${output}") - set(${placeholder_var} "${placeholder}" PARENT_SCOPE) - set(${output_var} "${output}" PARENT_SCOPE) - endif() -endfunction() +include("${CMAKE_CURRENT_LIST_DIR}/GoogleTest/ParseTestList.cmake") macro(write_test_to_file) # Store the gtest test name before messing with these strings @@ -151,123 +126,7 @@ macro(write_test_to_file) endif() endmacro() -macro(parse_tests_from_output) - generate_testname_guards("${output}" open_guard close_guard) - escape_square_brackets("${output}" "[" "__osb" open_sb output) - escape_square_brackets("${output}" "]" "__csb" close_sb output) - - # Preserve semicolon in test-parameters - string(REPLACE [[;]] [[\;]] output "${output}") - string(REPLACE "\n" ";" output "${output}") - - # Command line output doesn't contain information about the file and line number of the tests - set(current_test_file "") - set(current_test_line "") - - # Parse output - foreach(line ${output}) - # Skip header - if(line MATCHES "gtest_main\\.cc") - continue() - endif() - - if(line STREQUAL "") - continue() - endif() - - # Do we have a module name or a test name? - if(NOT line MATCHES "^ ") - set(current_test_type_param "") - - # Module; remove trailing '.' to get just the name... - string(REGEX REPLACE "\\.( *#.*)?$" "" current_test_suite "${line}") - if(line MATCHES "# *TypeParam = (.*)$") - set(current_test_type_param "${CMAKE_MATCH_1}") - endif() - else() - string(STRIP "${line}" test) - string(REGEX REPLACE " ( *#.*)?$" "" current_test_name "${test}") - - set(current_test_value_param "") - if(line MATCHES "# *GetParam\\(\\) = (.*)$") - set(current_test_value_param "${CMAKE_MATCH_1}") - endif() - - write_test_to_file() - endif() - endforeach() -endmacro() - -macro(get_json_member_with_default json_variable member_name out_variable) - string(JSON ${out_variable} - ERROR_VARIABLE error_param - GET "${${json_variable}}" "${member_name}" - ) - if(error_param) - # Member not present - set(${out_variable} "") - endif() -endmacro() - -macro(parse_tests_from_json json_file) - if(NOT EXISTS "${json_file}") - message(FATAL_ERROR "Missing expected JSON file with test list: ${json_file}") - endif() - - file(READ "${json_file}" test_json) - string(JSON test_suites_json GET "${test_json}" "testsuites") - - # Return if there are no testsuites - string(JSON len_test_suites LENGTH "${test_suites_json}") - if(len_test_suites GREATER 0) - set(open_sb) - set(close_sb) - - math(EXPR upper_limit_test_suite_range "${len_test_suites} - 1") - - foreach(index_test_suite RANGE ${upper_limit_test_suite_range}) - string(JSON test_suite_json GET "${test_suites_json}" ${index_test_suite}) - - # "suite" is expected to be set in write_test_to_file(). When parsing the - # plain text output, "suite" is expected to be the original suite name - # before accounting for pretty names. This may be used to construct the - # name of XML output results files. - string(JSON current_test_suite GET "${test_suite_json}" "name") - string(JSON tests_json GET "${test_suite_json}" "testsuite") - - # Skip test suites without tests - string(JSON len_tests LENGTH "${tests_json}") - if(len_tests LESS_EQUAL 0) - continue() - endif() - - math(EXPR upper_limit_test_range "${len_tests} - 1") - foreach(index_test RANGE ${upper_limit_test_range}) - string(JSON test_json GET "${tests_json}" ${index_test}) - - string(JSON len_test_parameters LENGTH "${test_json}") - if(len_test_parameters LESS_EQUAL 0) - continue() - endif() - - get_json_member_with_default(test_json "name" current_test_name) - get_json_member_with_default(test_json "file" current_test_file) - get_json_member_with_default(test_json "line" current_test_line) - get_json_member_with_default(test_json "value_param" current_test_value_param) - get_json_member_with_default(test_json "type_param" current_test_type_param) - - generate_testname_guards( - "${current_test_suite}${current_test_name}${current_test_value_param}${current_test_type_param}" - open_guard close_guard - ) - write_test_to_file() - endforeach() - endforeach() - endif() -endmacro() - function(gtest_discover_tests_impl) - set(options "") set(oneValueArgs NO_PRETTY_TYPES # These two take a value, unlike gtest_discover_tests() @@ -379,7 +238,7 @@ function(gtest_discover_tests_impl) endif() if(EXISTS "${json_file}") - parse_tests_from_json("${json_file}") + parse_tests_from_json("${json_file}" write_test_to_file) 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 @@ -391,7 +250,7 @@ function(gtest_discover_tests_impl) # 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() + parse_tests_from_output(write_test_to_file) endif() if(NOT tests_buffer STREQUAL "")