mirror of
https://github.com/Kitware/CMake.git
synced 2025-12-31 10:50:16 -06:00
If a test name contains a square bracket (due to parameters) then it
breaks gtest_discovery_test() function in some not-so-predictable
way. That happens due to the special meaning these brackets have in
the CMake language and they can't be escaped universally.
So the following treatment has been implemented:
* Every occurrence of ('[' | ']') in a test name gets replaced with
the corresponding placeholder ("__osb_*" | "__csb_*") before the
Google Test output processing and gets replaced back before adding
a new test to CTest, keeping the original test name intact in the
CTest output.
The placeholders are chosen that way to minimize the chance of
clashing with something in the user tests but even if the default
ones would clash with something then they are enhanced to not clash
with anything (hence "_*" at the placeholder's end).
* The GTest output gets searched for the default test name guards
("[=[" | "]=]") and if they are found a new one gets generated until
the one is found which can safely encompass any test name. The
search is quite simple: find the least amount of '=' which would
allow escaping any test.
* The resulting ${TEST_LIST} variable will contain every test but
tests with square brackets as there is no way to make sure such
tests won't break the list altogether.
Fixes: #23039
243 lines
7.7 KiB
CMake
243 lines
7.7 KiB
CMake
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
# file Copyright.txt or https://cmake.org/licensing for details.
|
|
|
|
cmake_minimum_required(VERSION ${CMAKE_VERSION})
|
|
|
|
# Overwrite possibly existing ${_CTEST_FILE} with empty file
|
|
set(flush_tests_MODE WRITE)
|
|
|
|
# Flushes script to ${_CTEST_FILE}
|
|
macro(flush_script)
|
|
file(${flush_tests_MODE} "${_CTEST_FILE}" "${script}")
|
|
set(flush_tests_MODE APPEND)
|
|
|
|
set(script "")
|
|
endmacro()
|
|
|
|
# Flushes tests_buffer to tests
|
|
macro(flush_tests_buffer)
|
|
list(APPEND tests "${tests_buffer}")
|
|
set(tests_buffer "")
|
|
endmacro()
|
|
|
|
macro(add_command NAME TEST_NAME)
|
|
set(_args "")
|
|
foreach(_arg ${ARGN})
|
|
if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
|
|
string(APPEND _args " [==[${_arg}]==]")
|
|
else()
|
|
string(APPEND _args " ${_arg}")
|
|
endif()
|
|
endforeach()
|
|
string(APPEND script "${NAME}(${TEST_NAME} ${_args})\n")
|
|
string(LENGTH "${script}" _script_len)
|
|
if(${_script_len} GREATER "50000")
|
|
flush_script()
|
|
endif()
|
|
# Unsets macro local variables to prevent leakage outside of this macro.
|
|
unset(_args)
|
|
unset(_script_len)
|
|
endmacro()
|
|
|
|
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()
|
|
|
|
function(gtest_discover_tests_impl)
|
|
|
|
cmake_parse_arguments(
|
|
""
|
|
""
|
|
"NO_PRETTY_TYPES;NO_PRETTY_VALUES;TEST_EXECUTABLE;TEST_WORKING_DIR;TEST_PREFIX;TEST_SUFFIX;TEST_LIST;CTEST_FILE;TEST_DISCOVERY_TIMEOUT;TEST_XML_OUTPUT_DIR;TEST_FILTER"
|
|
"TEST_EXTRA_ARGS;TEST_PROPERTIES;TEST_EXECUTOR"
|
|
${ARGN}
|
|
)
|
|
|
|
set(prefix "${_TEST_PREFIX}")
|
|
set(suffix "${_TEST_SUFFIX}")
|
|
set(extra_args ${_TEST_EXTRA_ARGS})
|
|
set(properties ${_TEST_PROPERTIES})
|
|
set(script)
|
|
set(suite)
|
|
set(tests)
|
|
set(tests_buffer)
|
|
|
|
if(_TEST_FILTER)
|
|
set(filter "--gtest_filter=${_TEST_FILTER}")
|
|
else()
|
|
set(filter)
|
|
endif()
|
|
|
|
# Run test executable to get list of available tests
|
|
if(NOT EXISTS "${_TEST_EXECUTABLE}")
|
|
message(FATAL_ERROR
|
|
"Specified test executable does not exist.\n"
|
|
" Path: '${_TEST_EXECUTABLE}'"
|
|
)
|
|
endif()
|
|
execute_process(
|
|
COMMAND ${_TEST_EXECUTOR} "${_TEST_EXECUTABLE}" --gtest_list_tests ${filter}
|
|
WORKING_DIRECTORY "${_TEST_WORKING_DIR}"
|
|
TIMEOUT ${_TEST_DISCOVERY_TIMEOUT}
|
|
OUTPUT_VARIABLE output
|
|
RESULT_VARIABLE result
|
|
)
|
|
if(NOT ${result} EQUAL 0)
|
|
string(REPLACE "\n" "\n " output "${output}")
|
|
if(_TEST_EXECUTOR)
|
|
set(path "${_TEST_EXECUTOR} ${_TEST_EXECUTABLE}")
|
|
else()
|
|
set(path "${_TEST_EXECUTABLE}")
|
|
endif()
|
|
message(FATAL_ERROR
|
|
"Error running test executable.\n"
|
|
" Path: '${path}'\n"
|
|
" Result: ${result}\n"
|
|
" Output:\n"
|
|
" ${output}\n"
|
|
)
|
|
endif()
|
|
|
|
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}")
|
|
|
|
# Parse output
|
|
foreach(line ${output})
|
|
# Skip header
|
|
if(NOT line MATCHES "gtest_main\\.cc")
|
|
# Do we have a module name or a test name?
|
|
if(NOT line MATCHES "^ ")
|
|
# Module; remove trailing '.' to get just the name...
|
|
string(REGEX REPLACE "\\.( *#.*)?$" "" suite "${line}")
|
|
if(line MATCHES "#" AND NOT _NO_PRETTY_TYPES)
|
|
string(REGEX REPLACE "/[0-9]\\.+ +#.*= +" "/" pretty_suite "${line}")
|
|
else()
|
|
set(pretty_suite "${suite}")
|
|
endif()
|
|
string(REGEX REPLACE "^DISABLED_" "" pretty_suite "${pretty_suite}")
|
|
else()
|
|
# Test name; strip spaces and comments to get just the name...
|
|
string(REGEX REPLACE " +" "" test "${line}")
|
|
if(test MATCHES "#" AND NOT _NO_PRETTY_VALUES)
|
|
string(REGEX REPLACE "/[0-9]+#GetParam..=" "/" pretty_test "${test}")
|
|
else()
|
|
string(REGEX REPLACE "#.*" "" pretty_test "${test}")
|
|
endif()
|
|
string(REGEX REPLACE "^DISABLED_" "" pretty_test "${pretty_test}")
|
|
string(REGEX REPLACE "#.*" "" test "${test}")
|
|
if(NOT "${_TEST_XML_OUTPUT_DIR}" STREQUAL "")
|
|
set(TEST_XML_OUTPUT_PARAM "--gtest_output=xml:${_TEST_XML_OUTPUT_DIR}/${prefix}${suite}.${test}${suffix}.xml")
|
|
else()
|
|
unset(TEST_XML_OUTPUT_PARAM)
|
|
endif()
|
|
|
|
set(testname "${prefix}${pretty_suite}.${pretty_test}${suffix}")
|
|
# sanitize test name for further processing downstream
|
|
# unescape []
|
|
if(open_sb)
|
|
string(REPLACE "${open_sb}" "[" testname "${testname}")
|
|
endif()
|
|
if(close_sb)
|
|
string(REPLACE "${close_sb}" "]" testname "${testname}")
|
|
endif()
|
|
# escape \
|
|
string(REPLACE [[\]] [[\\]] testname "${testname}")
|
|
# escape $
|
|
string(REPLACE [[$]] [[\$]] testname "${testname}")
|
|
set(guarded_testname "${open_guard}${testname}${close_guard}")
|
|
|
|
# ...and add to script
|
|
add_command(add_test
|
|
"${guarded_testname}"
|
|
${_TEST_EXECUTOR}
|
|
"${_TEST_EXECUTABLE}"
|
|
"--gtest_filter=${suite}.${test}"
|
|
"--gtest_also_run_disabled_tests"
|
|
${TEST_XML_OUTPUT_PARAM}
|
|
${extra_args}
|
|
)
|
|
if(suite MATCHES "^DISABLED_" OR test MATCHES "^DISABLED_")
|
|
add_command(set_tests_properties
|
|
"${guarded_testname}"
|
|
PROPERTIES DISABLED TRUE
|
|
)
|
|
endif()
|
|
|
|
add_command(set_tests_properties
|
|
"${guarded_testname}"
|
|
PROPERTIES
|
|
WORKING_DIRECTORY "${_TEST_WORKING_DIR}"
|
|
SKIP_REGULAR_EXPRESSION "\\\\[ SKIPPED \\\\]"
|
|
${properties}
|
|
)
|
|
|
|
# possibly unbalanced square brackets render lists invalid so skip such tests in _TEST_LIST
|
|
if(NOT "${testname}" MATCHES [=[(\[|\])]=])
|
|
# escape ;
|
|
string(REPLACE [[;]] [[\;]] testname "${testname}")
|
|
list(APPEND tests_buffer "${testname}")
|
|
list(LENGTH tests_buffer tests_buffer_length)
|
|
if(${tests_buffer_length} GREATER "250")
|
|
flush_tests_buffer()
|
|
endif()
|
|
endif()
|
|
endif()
|
|
endif()
|
|
endforeach()
|
|
|
|
|
|
# Create a list of all discovered tests, which users may use to e.g. set
|
|
# properties on the tests
|
|
flush_tests_buffer()
|
|
add_command(set "" ${_TEST_LIST} ${tests})
|
|
|
|
# Write CTest script
|
|
flush_script()
|
|
|
|
endfunction()
|
|
|
|
if(CMAKE_SCRIPT_MODE_FILE)
|
|
gtest_discover_tests_impl(
|
|
NO_PRETTY_TYPES ${NO_PRETTY_TYPES}
|
|
NO_PRETTY_VALUES ${NO_PRETTY_VALUES}
|
|
TEST_EXECUTABLE ${TEST_EXECUTABLE}
|
|
TEST_EXECUTOR ${TEST_EXECUTOR}
|
|
TEST_WORKING_DIR ${TEST_WORKING_DIR}
|
|
TEST_PREFIX ${TEST_PREFIX}
|
|
TEST_SUFFIX ${TEST_SUFFIX}
|
|
TEST_FILTER ${TEST_FILTER}
|
|
TEST_LIST ${TEST_LIST}
|
|
CTEST_FILE ${CTEST_FILE}
|
|
TEST_DISCOVERY_TIMEOUT ${TEST_DISCOVERY_TIMEOUT}
|
|
TEST_XML_OUTPUT_DIR ${TEST_XML_OUTPUT_DIR}
|
|
TEST_EXTRA_ARGS ${TEST_EXTRA_ARGS}
|
|
TEST_PROPERTIES ${TEST_PROPERTIES}
|
|
)
|
|
endif()
|