Skip to content

Instantly share code, notes, and snippets.

@K-ballo
Last active November 23, 2019 16:21
Show Gist options
  • Save K-ballo/dd73f748875f63cd854a473319f94a5e to your computer and use it in GitHub Desktop.
Save K-ballo/dd73f748875f63cd854a473319f94a5e to your computer and use it in GitHub Desktop.
BoostTest.cmake
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
BoostTest
----------
This module defines functions to help use the Boost.Test infrastructure.
The :command:`boost_test_discover_tests` discovers tests by asking the
compiled test executable to enumerate its tests.
This commands is intended to replace use of :command:`add_test` to register
tests, and will create a separate CTest test for each Boost.Test test case.
Note that this is in some cases less efficient, as common set-up and tear-down
logic cannot be shared by multiple test cases executing in the same instance.
However, it provides more fine-grained pass/fail information to CTest, which is
usually considered as more beneficial. By default, the CTest test name is the
same as the Boost.Test name (i.e. ``suite.testcase``); see also
``TEST_PREFIX`` and ``TEST_SUFFIX``.
.. command:: boost_test_discover_tests
Automatically add tests with CTest by querying the compiled test executable
for available tests::
boost_test_discover_tests(target
[EXTRA_ARGS arg1...]
[WORKING_DIRECTORY dir]
[TEST_PREFIX prefix]
[TEST_SUFFIX suffix]
[PROPERTIES name1 value1...]
[TEST_LIST var]
[DISCOVERY_TIMEOUT seconds]
)
``boost_test_discover_tests`` sets up a post-build command on the test executable
that generates the list of tests by parsing the output from running the test
with the ``--boost_test_list_tests`` argument. This ensures that the full list of
tests, including instantiations of parameterized tests, is obtained. Since
test discovery occurs at build time, it is not necessary to re-run CMake when
the list of tests changes.
However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set
in order to function in a cross-compiling environment.
Additionally, setting properties on tests is somewhat less convenient, since
the tests are not available at CMake time. Additional test properties may be
assigned to the set of tests as a whole using the ``PROPERTIES`` option. If
more fine-grained test control is needed, custom content may be provided
through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES`
directory property. The set of discovered tests is made accessible to such a
script via the ``<target>_TESTS`` variable.
The options are:
``target``
Specifies the Boost.Test executable, which must be a known CMake
executable target. CMake will substitute the location of the built
executable when running the test.
``EXTRA_ARGS arg1...``
Any extra arguments to pass on the command line to each test case.
``WORKING_DIRECTORY dir``
Specifies the directory in which to run the discovered test cases. If this
option is not provided, the current binary directory is used.
``TEST_PREFIX prefix``
Specifies a ``prefix`` to be prepended to the name of each discovered test
case. This can be useful when the same test executable is being used in
multiple calls to ``boost_test_discover_tests()`` but with different
``EXTRA_ARGS``.
``TEST_SUFFIX suffix``
Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of
every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may
be specified.
``PROPERTIES name1 value1...``
Specifies additional properties to be set on all tests discovered by this
invocation of ``boost_test_discover_tests``.
``TEST_LIST var``
Make the list of tests available in the variable ``var``, rather than the
default ``<target>_TESTS``. This can be useful when the same test
executable is being used in multiple calls to ``boost_test_discover_tests()``.
Note that this variable is only available in CTest.
``DISCOVERY_TIMEOUT num``
Specifies how long (in seconds) CMake will wait for the test to enumerate
available tests. If the test takes longer than this, discovery (and your
build) will fail. Most test executables will enumerate their tests very
quickly, but under some exceptional circumstances, a test may require a
longer timeout. The default is 5. See also the ``TIMEOUT`` option of
:command:`execute_process`.
#]=======================================================================]
if(${CMAKE_VERSION} VERSION_LESS "3.10.0")
message(FATAL_ERROR "CMake >= 3.10.0 required")
endif()
# Save project's policies
cmake_policy(PUSH)
cmake_policy(SET CMP0057 NEW) # if IN_LIST
#------------------------------------------------------------------------------
function(boost_test_discover_tests TARGET)
cmake_parse_arguments(
""
""
"TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST;DISCOVERY_TIMEOUT"
"EXTRA_ARGS;PROPERTIES"
${ARGN}
)
if(NOT _WORKING_DIRECTORY)
set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
endif()
if(NOT _TEST_LIST)
set(_TEST_LIST ${TARGET}_TESTS)
endif()
if(NOT _DISCOVERY_TIMEOUT)
set(_DISCOVERY_TIMEOUT 5)
endif()
get_property(
has_counter
TARGET ${TARGET}
PROPERTY CTEST_DISCOVERED_TEST_COUNTER
SET
)
if(has_counter)
get_property(
counter
TARGET ${TARGET}
PROPERTY CTEST_DISCOVERED_TEST_COUNTER
)
math(EXPR counter "${counter} + 1")
else()
set(counter 1)
endif()
set_property(
TARGET ${TARGET}
PROPERTY CTEST_DISCOVERED_TEST_COUNTER
${counter}
)
# Define rule to generate test list for aforementioned test executable
set(ctest_file_base "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}[${counter}]")
set(ctest_include_file "${ctest_file_base}_include.cmake")
set(ctest_tests_file "${ctest_file_base}_tests.cmake")
get_property(crosscompiling_emulator
TARGET ${TARGET}
PROPERTY CROSSCOMPILING_EMULATOR
)
add_custom_command(
TARGET ${TARGET} POST_BUILD
BYPRODUCTS "${ctest_tests_file}"
COMMAND "${CMAKE_COMMAND}"
-D "TEST_TARGET=${TARGET}"
-D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>"
-D "TEST_EXECUTOR=${crosscompiling_emulator}"
-D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}"
-D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}"
-D "TEST_PROPERTIES=${_PROPERTIES}"
-D "TEST_PREFIX=${_TEST_PREFIX}"
-D "TEST_SUFFIX=${_TEST_SUFFIX}"
-D "TEST_LIST=${_TEST_LIST}"
-D "CTEST_FILE=${ctest_tests_file}"
-D "TEST_DISCOVERY_TIMEOUT=${_DISCOVERY_TIMEOUT}"
-P "${_BoostTest_DISCOVER_TESTS_SCRIPT}"
VERBATIM
)
file(WRITE "${ctest_include_file}"
"if(EXISTS \"${ctest_tests_file}\")\n"
" include(\"${ctest_tests_file}\")\n"
"else()\n"
" add_test(${TARGET}_NOT_BUILT ${TARGET}_NOT_BUILT)\n"
"endif()\n"
)
# Add discovered tests to directory TEST_INCLUDE_FILES
set_property(DIRECTORY
APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}"
)
endfunction()
###############################################################################
set(_BoostTest_DISCOVER_TESTS_SCRIPT
${CMAKE_CURRENT_LIST_DIR}/BoostTestAddTests.cmake
)
# Restore project's policies
cmake_policy(POP)
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
set(prefix "${TEST_PREFIX}")
set(suffix "${TEST_SUFFIX}")
set(extra_args ${TEST_EXTRA_ARGS})
set(properties ${TEST_PROPERTIES})
set(script)
set(suite)
set(tests)
function(add_command NAME)
set(_args "")
foreach(_arg ${ARGN})
if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
set(_args "${_args} [==[${_arg}]==]")
else()
set(_args "${_args} ${_arg}")
endif()
endforeach()
set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
endfunction()
# 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}" --list_content
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
TIMEOUT ${TEST_DISCOVERY_TIMEOUT}
OUTPUT_VARIABLE output
ERROR_VARIABLE error
RESULT_VARIABLE result
)
if(NOT output)
# https://github.com/boostorg/test/issues/236
set(output "${error}")
set(error "")
endif()
if(NOT ${result} EQUAL 0)
string(REPLACE "\n" "\n " output "${output}")
string(REPLACE "\n" "\n " error "${error}")
message(FATAL_ERROR
"Error running test executable.\n"
" Path: '${TEST_EXECUTABLE}'\n"
" Result: ${result}\n"
" Output:\n"
" ${output}\n"
" Error:\n"
" ${error}\n"
)
endif()
string(REPLACE "\n" ";" output "${output}")
# Parse output
set(line "")
set(parent_prefix "")
set(parent_enabled TRUE)
foreach(next_line IN LISTS output)
if (NOT line MATCHES "( *)([^ *]+)(\\*)?:?(.*)")
set(line "${next_line}")
continue()
endif()
string(LENGTH "${CMAKE_MATCH_1}" indent)
set(name "${parent_prefix}${CMAKE_MATCH_2}")
if (parent_enabled AND CMAKE_MATCH_3 STREQUAL "*")
set(enabled TRUE)
else()
set(enabled FALSE)
endif()
string(REGEX REPLACE "^( *)?.+$" "\\1" next_indent "${next_line}")
string(LENGTH "${next_indent}" next_indent)
if(next_indent GREATER indent)
# Suite
list(INSERT scope_name 0 "${name}")
set(parent_prefix "${name}/")
list(INSERT scope_enabled 0 ${enabled})
set(parent_enabled ${enabled})
else()
# Test
# add to script
add_command(add_test
"${prefix}${name}${suffix}"
${TEST_EXECUTOR}
"${TEST_EXECUTABLE}"
"--run_test=${name}"
${extra_args}
)
if(NOT enabled)
add_command(set_tests_properties
"${prefix}${name}${suffix}"
PROPERTIES DISABLED TRUE
)
endif()
add_command(set_tests_properties
"${prefix}${name}${suffix}"
PROPERTIES
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
${properties}
)
list(APPEND tests "${prefix}${name}${suffix}")
endif()
while(indent GREATER next_indent)
list(REMOVE_AT scope_name 0)
if(scope_name)
list(GET scope_name 0 parent_prefix)
set(parent_prefix "${parent_prefix}/")
else()
set(parent_prefix "")
endif()
list(REMOVE_AT scope_enabled 0)
if(scope_enabled)
list(GET scope_enabled 0 parent_enabled)
else()
set(scope_enabled TRUE)
endif()
math(EXPR indent "${indent} - 4")
endwhile()
set(line "${next_line}")
endforeach()
# Create a list of all discovered tests, which users may use to e.g. set
# properties on the tests
add_command(set ${TEST_LIST} ${tests})
# Write CTest script
file(WRITE "${CTEST_FILE}" "${script}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment