From da5ed30d684cf15312b9922588de13985ee45db8 Mon Sep 17 00:00:00 2001 From: Deniz Bahadir Date: Mon, 25 May 2020 00:26:34 +0200 Subject: [PATCH 1/8] Add CMake module `BoostTestDiscoverTests` Add a new CMake module `BoostTestDiscoverTests` which provides a function `boosttest_discover_tests`. This function `boosttest_discover_tests` is similar to the function `gtest_discover_tests` provided by the `GoogleTest` module (which comes with CMake). It can discover all the tests from an executable target that was built with the Unit-Test-Framework from Boost.Test and creates individual test-targets for each of it. When running these tests using CTest each test will get an individual success or failure entry in CTest's output. This function `boosttest_discover_tests` provides two different modes for discovering tests: `POST_BUILD`, which discovers the tests at build-time, and `PRE_TEST`, which discovers the tests at run-time. (The later mode is more flexible and also better supports cross-compilation scenarios.) For more information read the documentation contained in the module `BoostTestDiscoverTests.cmake`. --- modules/BoostTestAddTests.cmake | 257 ++++++++++++++++++++++ modules/BoostTestDiscoverTests.cmake | 316 +++++++++++++++++++++++++++ 2 files changed, 573 insertions(+) create mode 100644 modules/BoostTestAddTests.cmake create mode 100644 modules/BoostTestDiscoverTests.cmake diff --git a/modules/BoostTestAddTests.cmake b/modules/BoostTestAddTests.cmake new file mode 100644 index 0000000..d5942f7 --- /dev/null +++ b/modules/BoostTestAddTests.cmake @@ -0,0 +1,257 @@ +# Copyright 2020, 2021 Deniz Bahadir and contributors +# Distributed under the Boost Software License, Version 1.0. +# See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt +# +# This code is based on an implementation of the `gtest_discover_tests()` +# functionality that was originally distributed together with CMake unter the +# OSI-approved BSD 3-Clause License. The original authors and contributors of +# that code were so kind to agree to dual-license their work also under the +# Boost Software License, Version 1.0, so that this derived worked can be +# distributed under the same license as well. +# A public record of their granted permission can be found in this discussion +# of merge-request 4145 in the CMake issue tracker: +# https://gitlab.kitware.com/cmake/cmake/-/merge_requests/4145#note_998500 +# +# Therefore many thanks go to the following original authors and contributors: +# Matthew Woehlke +# Steffen Seckler +# Ryan Thornton +# Kevin Puetz +# Stefan Floeren +# Alexander Stein +# +# Deniz Bahadir in August of 2021. +# + +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 variable. +macro(flush_tests_buffer) + list(APPEND tests "${tests_buffer}") + set(tests_buffer "") +endmacro() + + +# Removes surrounding double quotes (if any) from value of given variable VAR. +function(remove_outer_quotes VAR) + string(REGEX REPLACE "^\"(.*)\"$" "\\1" ${VAR} "${${VAR}}") + set(${VAR} "${${VAR}}" PARENT_SCOPE) +endfunction() + + +# Adds commands to script. +macro(add_command NAME) + set(_args "") + foreach(_arg ${ARGN}) + if(_arg MATCHES "[^-./:a-zA-Z0-9_]") + string(APPEND _args " [==[${_arg}]==]") # form a bracket argument + else() + string(APPEND _args " ${_arg}") + endif() + endforeach() + string(APPEND script "${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() + + +# Adds another test to the script. +macro(add_another_test hierarchy_list enabled separator) + # Create the name and path of the test-case... + list(JOIN ${hierarchy_list} ${separator} test_name) + list(JOIN ${hierarchy_list} "/" test_path) + # ...and add to script. + add_command(add_test + "${prefix}${test_name}${suffix}" + ${__TEST_EXECUTOR} + "${__TEST_EXECUTABLE}" + "--run_test=${test_path}" + ${extra_args} + ) + if(NOT ${enabled}) + add_command(set_tests_properties + "${prefix}${test_name}${suffix}" + PROPERTIES DISABLED TRUE + ) + endif() + add_command(set_tests_properties + "${prefix}${test_name}${suffix}" + PROPERTIES + WORKING_DIRECTORY "${__TEST_WORKING_DIR}" + ${properties} + ) + list(APPEND tests_buffer "${prefix}${test_name}${suffix}") + list(LENGTH tests_buffer tests_buffer_length) + if(${tests_buffer_length} GREATER "250") + flush_tests_buffer() + endif() +endmacro() + + +# Internal implementation for boosttest_discover_tests. +function(boosttest_discover_tests_impl) + + cmake_parse_arguments( + "_" + "" + "TEST_EXECUTABLE;TEST_WORKING_DIR;TEST_PREFIX;TEST_SUFFIX;TEST_NAME_SEPARATOR;TEST_LIST;TEST_SKIP_DISABLED;CTEST_FILE;TEST_DISCOVERY_TIMEOUT" + "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(tests) + set(tests_buffer) + + # Make sure the working directory exists. + file(MAKE_DIRECTORY "${__TEST_WORKING_DIR}") + # 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=HRF + WORKING_DIRECTORY "${__TEST_WORKING_DIR}" + TIMEOUT ${__TEST_DISCOVERY_TIMEOUT} + OUTPUT_VARIABLE output + ERROR_VARIABLE output # Boost.Test writes the requested content to stderr! + RESULT_VARIABLE result + ) + if(NOT ${result} EQUAL 0) + string(REPLACE "\n" "\n " output "${output}") + message(FATAL_ERROR + "Error running test executable.\n" + " Path: '${__TEST_EXECUTABLE}'\n" + " Result: ${result}\n" + " Output:\n" + " ${output}\n" + ) + endif() + + # Preserve semicolon in test-parameters + string(REPLACE [[;]] [[\;]] output "${output}") + string(REPLACE "\n" ";" output "${output}") + + # The hierarchy and its depth-level of the test of the former line. + set(hierarchy "") + set(former_level NaN) + set(test_is_enabled 1) + + # Parse output + foreach(line ${output}) + # Determine the depth-level of the next test-hierarchy. + # Note: Each new depth-level (except for the top one) is indented + # by 4 spaces. So we need to count the spaces. + string(REGEX MATCH "^[ ]+" next_level "${line}") + string(LENGTH "${next_level}" next_level) + math(EXPR next_level "${next_level} / 4") + + # Add the test for the test-case from the former loop-run? + if (next_level LESS_EQUAL former_level) + # Add test-case to the script. + add_another_test(hierarchy ${test_is_enabled} "${__TEST_NAME_SEPARATOR}") + + # Prepare the hierarchy list for the next test-case. + math(EXPR diff "${former_level} - ${next_level}") + foreach(i RANGE ${diff}) + list(POP_BACK hierarchy) + endforeach() + endif() + set(former_level ${next_level}) # Store depth-level for next loop-run. + + # Extract the name of the next test suite/case and determine if enabled. + # Note: A trailing '*' indicates that the test is enabled (by default). + string(REGEX REPLACE ":( .*)?$" "" name "${line}") + string(STRIP "${name}" name) + if(name MATCHES "\\*$") + set(test_is_enabled 1) + string(REGEX REPLACE "\\*$" "" name "${name}") + elseif(__TEST_SKIP_DISABLED) + set(test_is_enabled 0) + endif() + + # Sanitize name for further processing downstream: + # - escape \ + string(REPLACE [[\]] [[\\]] name "${name}") + # - escape ; + string(REPLACE [[;]] [[\;]] name "${name}") + # - escape $ + string(REPLACE [[$]] [[\$]] name "${name}") + + # Add the name to the hierarchy list. + list(APPEND hierarchy "${name}") + endforeach() + + # Add last test-case to the script (if any). + add_another_test(hierarchy ${test_is_enabled} "${__TEST_NAME_SEPARATOR}") + + + # 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 + file(WRITE "${__CTEST_FILE}" "${script}") + + # Write CTest script + flush_script() + +endfunction() + +if(CMAKE_SCRIPT_MODE_FILE) + # Note: Make sure to remove the outer layer of quotes that were added + # to preserve whitespace when handed over via cmdline. + remove_outer_quotes(TEST_TARGET) + remove_outer_quotes(TEST_EXECUTABLE) + remove_outer_quotes(TEST_EXECUTOR) + remove_outer_quotes(TEST_WORKING_DIR) + remove_outer_quotes(TEST_EXTRA_ARGS) + remove_outer_quotes(TEST_PROPERTIES) + remove_outer_quotes(TEST_PREFIX) + remove_outer_quotes(TEST_SUFFIX) + remove_outer_quotes(TEST_NAME_SEPARATOR) + remove_outer_quotes(TEST_LIST) + remove_outer_quotes(TEST_SKIP_DISABLED) + remove_outer_quotes(CTEST_FILE) + remove_outer_quotes(TEST_DISCOVERY_TIMEOUT) + + boosttest_discover_tests_impl( + TEST_EXECUTABLE ${TEST_EXECUTABLE} + TEST_EXECUTOR ${TEST_EXECUTOR} + TEST_WORKING_DIR ${TEST_WORKING_DIR} + TEST_PREFIX ${TEST_PREFIX} + TEST_SUFFIX ${TEST_SUFFIX} + TEST_NAME_SEPARATOR ${TEST_NAME_SEPARATOR} + TEST_LIST ${TEST_LIST} + TEST_SKIP_DISABLED ${TEST_SKIP_DISABLED} + CTEST_FILE ${CTEST_FILE} + TEST_DISCOVERY_TIMEOUT ${TEST_DISCOVERY_TIMEOUT} + TEST_EXTRA_ARGS ${TEST_EXTRA_ARGS} + TEST_PROPERTIES ${TEST_PROPERTIES} + ) +endif() diff --git a/modules/BoostTestDiscoverTests.cmake b/modules/BoostTestDiscoverTests.cmake new file mode 100644 index 0000000..c9dd934 --- /dev/null +++ b/modules/BoostTestDiscoverTests.cmake @@ -0,0 +1,316 @@ +# Copyright 2020, 2021 Deniz Bahadir and contributors +# Distributed under the Boost Software License, Version 1.0. +# See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt +# +# This code is based on an implementation of the `gtest_discover_tests()` +# functionality that was originally distributed together with CMake unter the +# OSI-approved BSD 3-Clause License. The original authors and contributors of +# that code were so kind to agree to dual-license their work also under the +# Boost Software License, Version 1.0, so that this derived worked can be +# distributed under the same license as well. +# A public record of their granted permission can be found in this discussion +# of merge-request 4145 in the CMake issue tracker: +# https://gitlab.kitware.com/cmake/cmake/-/merge_requests/4145#note_998500 +# +# Therefore many thanks go to the following original authors and contributors: +# Matthew Woehlke +# Steffen Seckler +# Ryan Thornton +# Kevin Puetz +# Stefan Floeren +# Alexander Stein +# +# Deniz Bahadir in August of 2021. +# + +#[=======================================================================[.rst: +BoostTestDiscoverTests +---------------------- + +This module defines a function to help use the Boost.Test infrastructure. + +The :command:`boosttest_discover_tests` discovers tests by asking the compiled +test executable to enumerate its tests. This does not require CMake to be +re-run when tests change. However, it may not work in a cross-compiling +environment, and setting test properties is less convenient. + +This command 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; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. + +.. command:: boosttest_discover_tests + + Automatically add tests with CTest by querying the compiled test executable + for available tests:: + + boosttest_discover_tests(target + [EXTRA_ARGS arg1...] + [WORKING_DIRECTORY dir] + [TEST_PREFIX prefix] + [TEST_SUFFIX suffix] + [TEST_NAME_SEPARATOR separator] + [PROPERTIES name1 value1...] + [TEST_LIST var] + [SKIP_DISABLED_TESTS] + [DISCOVERY_TIMEOUT seconds] + [DISCOVERY_MODE ] + ) + + ``boosttest_discover_tests`` sets up a post-build / pre-test command on the + test executable that generates the list of tests by parsing the output from + running the test with the ``--list_content=HRF`` argument. This ensures that + the full list of tests, including instantiations of parameterized tests, is + obtained. Since test discovery occurs at build time (or pre-test 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 ``_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. + Note, that if this directory does not exist it will be created. + + ``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 ``boosttest_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. + + ``TEST_NAME_SEPARATOR`` + With Boost.Test a test case can be continued in one or more test suites. + The names of the test suites and the test case will be joined into a full + name. By default they will be joined with a ``.`` character between them. + However, this option allows to use another character (or string) as + separator between the individual name parts. + Note however, that he ``TEST_PREFIX`` and ``TEST_SUFFIX`` will be + prepended/appended to this full name without an extra separator. + + ``PROPERTIES name1 value1...`` + Specifies additional properties to be set on all tests discovered by this + invocation of ``boosttest_discover_tests``. + + ``TEST_LIST var`` + Make the list of tests available in the variable ``var``, rather than the + default ``_TESTS``. This can be useful when the same test + executable is being used in multiple calls to + ``boosttest_discover_tests()``. Note that this variable is only available + in CTest. + + ``SKIP_DISABLED_TESTS`` + Specifies if disabled tests should be skipped. Boost.Test allows to disable + individual tests which in general would not be run by default but because + ``boosttest_discover_tests`` finds all tests and runs each one individually + we have to explicitly skip these with this option in case they shall not be + run. + + ``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`. + + ``DISCOVERY_MODE`` + Provides greater control over when ``boosttest_discover_tests``performs + test discovery. By default, ``PRE_TEST`` delays test discovery until just + prior to test execution. This way test discovery occurs in the target + environment where the test has a better chance at finding appropriate + runtime dependencies. + By contrast, ``POST_BUILD`` sets up a post-build command to perform test + discovery at build time. In certain scenarios, like cross-compiling, this + ``POST_BUILD`` behavior is not desirable. + + ``DISCOVERY_MODE`` defaults to the value of the + ``BOOSTTEST_DISCOVER_TESTS_DISCOVERY_MODE`` variable if it is not passed + when calling ``boosttest_discover_tests``. This provides a mechanism for + globally selecting a preferred test discovery behavior without having to + modify each call site. + +#]=======================================================================] + +#------------------------------------------------------------------------------ +function(boosttest_discover_tests TARGET) + + cmake_parse_arguments( + "_" + "SKIP_DISABLED_TESTS" + "WORKING_DIRECTORY;TEST_PREFIX;TEST_SUFFIX;TEST_NAME_SEPARATOR;TEST_LIST;DISCOVERY_TIMEOUT;DISCOVERY_MODE" + "EXTRA_ARGS;PROPERTIES" + ${ARGN} + ) + + if(NOT __WORKING_DIRECTORY) + set(__WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") + endif() + if(NOT __TEST_NAME_SEPARATOR) + set(__TEST_NAME_SEPARATOR ".") + endif() + if(NOT __TEST_LIST) + set(__TEST_LIST ${TARGET}_TESTS) + endif() + if(NOT __DISCOVERY_TIMEOUT) + set(__DISCOVERY_TIMEOUT 5) + endif() + if(NOT __DISCOVERY_MODE) + if(NOT BOOSTTEST_DISCOVER_TESTS_DISCOVERY_MODE) + set(BOOSTTEST_DISCOVER_TESTS_DISCOVERY_MODE "PRE_TEST") + endif() + set(__DISCOVERY_MODE ${BOOSTTEST_DISCOVER_TESTS_DISCOVERY_MODE}) + endif() + + # Store the absolute path to the helper script. + set(__BOOSTTEST_DISCOVER_TESTS_SCRIPT + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/BoostTestAddTests.cmake" + ) + + # Determine how often tests were discovered on the target and store in property + 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} + ) + + get_property(crosscompiling_emulator + TARGET ${TARGET} + PROPERTY CROSSCOMPILING_EMULATOR + ) + + get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL + PROPERTY GENERATOR_IS_MULTI_CONFIG + ) + + # Prepare file names for ctest configuration files. + 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") + + # Define rule to generate test list for aforementioned test executable + + if(__DISCOVERY_MODE STREQUAL "POST_BUILD") + + # Note: Make sure to properly quote all definitions that are given + # via the cmdline, or we might swallow some white-space. + add_custom_command( + TARGET ${TARGET} POST_BUILD + BYPRODUCTS "${ctest_tests_file}" + COMMAND "${CMAKE_COMMAND}" + -D "TEST_TARGET=\"${TARGET}\"" + -D "TEST_EXECUTABLE=\"$\"" + -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_NAME_SEPARATOR=\"${__TEST_NAME_SEPARATOR}\"" + -D "TEST_LIST=\"${__TEST_LIST}\"" + -D "TEST_SKIP_DISABLED=\"${__SKIP_DISABLED_TESTS}\"" + -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" + ) + + elseif(__DISCOVERY_MODE STREQUAL "PRE_TEST") + + if(GENERATOR_IS_MULTI_CONFIG) + set(ctest_tests_file "${ctest_file_base}_tests-$.cmake") + endif() + + string(CONCAT ctest_include_content + "if(EXISTS \"$\")" "\n" + " if(\"$\" IS_NEWER_THAN \"${ctest_tests_file}\")" "\n" + " include(\"${__BOOSTTEST_DISCOVER_TESTS_SCRIPT}\")" "\n" + " boosttest_discover_tests_impl(" "\n" + " TEST_EXECUTABLE" " [==[" "$" "]==]" "\n" + " TEST_EXECUTOR" " [==[" "${crosscompiling_emulator}" "]==]" "\n" + " TEST_WORKING_DIR" " [==[" "${__WORKING_DIRECTORY}" "]==]" "\n" + " TEST_EXTRA_ARGS" " [==[" "${__EXTRA_ARGS}" "]==]" "\n" + " TEST_PROPERTIES" " [==[" "${__PROPERTIES}" "]==]" "\n" + " TEST_PREFIX" " [==[" "${__TEST_PREFIX}" "]==]" "\n" + " TEST_SUFFIX" " [==[" "${__TEST_SUFFIX}" "]==]" "\n" + " TEST_NAME_SEPARATOR" " [==[" "${__TEST_NAME_SEPARATOR}" "]==]" "\n" + " TEST_LIST" " [==[" "${__TEST_LIST}" "]==]" "\n" + " TEST_SKIP_DISABLED" " [==[" "${__SKIP_DISABLED_TESTS}" "]==]" "\n" + " CTEST_FILE" " [==[" "${ctest_tests_file}" "]==]" "\n" + " TEST_DISCOVERY_TIMEOUT" " [==[" "${__DISCOVERY_TIMEOUT}" "]==]" "\n" + " )" "\n" + " endif()" "\n" + " include(\"${ctest_tests_file}\")" "\n" + "else()" "\n" + " add_test(${TARGET}_NOT_BUILT ${TARGET}_NOT_BUILT)" "\n" + "endif()" "\n" + ) + + if(GENERATOR_IS_MULTI_CONFIG) + foreach(_config ${CMAKE_CONFIGURATION_TYPES}) + file(GENERATE OUTPUT "${ctest_file_base}_include-${_config}.cmake" CONTENT "${ctest_include_content}" CONDITION $) + endforeach() + file(WRITE "${ctest_include_file}" "include(\"${ctest_file_base}_include-\${CTEST_CONFIGURATION_TYPE}.cmake\")") + else() + file(GENERATE OUTPUT "${ctest_file_base}_include.cmake" CONTENT "${ctest_include_content}") + endif() + + else() + message(SEND_ERROR "Unknown DISCOVERY_MODE: ${__DISCOVERY_MODE}") + endif() + + # Add discovered tests to directory TEST_INCLUDE_FILES + set_property(DIRECTORY + APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}" + ) + +endfunction() + +############################################################################### From 5a790fe667e39a61f931652649d7aa7b75264999 Mon Sep 17 00:00:00 2001 From: Deniz Bahadir Date: Thu, 28 May 2020 00:47:25 +0200 Subject: [PATCH 2/8] Adjust the BoostTestDiscoverTests module for special case of no tests In case the test executable does not contain any discoverable tests (that use Boost.Test) a dummy test-target will be registered which is disabled and whose name should indicate that tests are missing. --- modules/BoostTestAddTests.cmake | 16 +++++++++++----- modules/BoostTestDiscoverTests.cmake | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/BoostTestAddTests.cmake b/modules/BoostTestAddTests.cmake index d5942f7..1f15428 100644 --- a/modules/BoostTestAddTests.cmake +++ b/modules/BoostTestAddTests.cmake @@ -111,8 +111,8 @@ function(boosttest_discover_tests_impl) cmake_parse_arguments( "_" "" - "TEST_EXECUTABLE;TEST_WORKING_DIR;TEST_PREFIX;TEST_SUFFIX;TEST_NAME_SEPARATOR;TEST_LIST;TEST_SKIP_DISABLED;CTEST_FILE;TEST_DISCOVERY_TIMEOUT" - "TEST_EXTRA_ARGS;TEST_PROPERTIES;TEST_EXECUTOR;" + "TEST_TARGET;TEST_EXECUTABLE;TEST_WORKING_DIR;TEST_PREFIX;TEST_SUFFIX;TEST_NAME_SEPARATOR;TEST_LIST;TEST_SKIP_DISABLED;CTEST_FILE;TEST_DISCOVERY_TIMEOUT" + "TEST_EXTRA_ARGS;TEST_PROPERTIES;TEST_EXECUTOR" ${ARGN} ) @@ -157,9 +157,9 @@ function(boosttest_discover_tests_impl) string(REPLACE "\n" ";" output "${output}") # The hierarchy and its depth-level of the test of the former line. - set(hierarchy "") + set(hierarchy "${TEST_TARGET}_MISSING_TESTS") set(former_level NaN) - set(test_is_enabled 1) + set(test_is_enabled 0) # Parse output foreach(line ${output}) @@ -181,6 +181,9 @@ function(boosttest_discover_tests_impl) list(POP_BACK hierarchy) endforeach() endif() + if (former_level STREQUAL NaN) + set(hierarchy "") # Clear hierarchy, as we have at least one test. + endif() set(former_level ${next_level}) # Store depth-level for next loop-run. # Extract the name of the next test suite/case and determine if enabled. @@ -213,7 +216,9 @@ function(boosttest_discover_tests_impl) # 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}) + if (NOT former_level STREQUAL "NaN") + add_command(set ${__TEST_LIST} ${tests}) + endif() # Write CTest script file(WRITE "${__CTEST_FILE}" "${script}") @@ -241,6 +246,7 @@ if(CMAKE_SCRIPT_MODE_FILE) remove_outer_quotes(TEST_DISCOVERY_TIMEOUT) boosttest_discover_tests_impl( + TEST_TARGET ${TEST_TARGET} TEST_EXECUTABLE ${TEST_EXECUTABLE} TEST_EXECUTOR ${TEST_EXECUTOR} TEST_WORKING_DIR ${TEST_WORKING_DIR} diff --git a/modules/BoostTestDiscoverTests.cmake b/modules/BoostTestDiscoverTests.cmake index c9dd934..ddb5c7e 100644 --- a/modules/BoostTestDiscoverTests.cmake +++ b/modules/BoostTestDiscoverTests.cmake @@ -273,6 +273,7 @@ function(boosttest_discover_tests TARGET) " if(\"$\" IS_NEWER_THAN \"${ctest_tests_file}\")" "\n" " include(\"${__BOOSTTEST_DISCOVER_TESTS_SCRIPT}\")" "\n" " boosttest_discover_tests_impl(" "\n" + " TEST_TARGET" " [==[" "${TARGET}" "]==]" "\n" " TEST_EXECUTABLE" " [==[" "$" "]==]" "\n" " TEST_EXECUTOR" " [==[" "${crosscompiling_emulator}" "]==]" "\n" " TEST_WORKING_DIR" " [==[" "${__WORKING_DIRECTORY}" "]==]" "\n" From f65bc8ce37f7334a2f4a9943b368385ea02fd1be Mon Sep 17 00:00:00 2001 From: Deniz Bahadir Date: Thu, 12 Aug 2021 11:32:31 +0200 Subject: [PATCH 3/8] Support multi-config generators in POST_BUILD discovery mode --- modules/BoostTestDiscoverTests.cmake | 45 +++++++++++++++------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/modules/BoostTestDiscoverTests.cmake b/modules/BoostTestDiscoverTests.cmake index ddb5c7e..3351f9d 100644 --- a/modules/BoostTestDiscoverTests.cmake +++ b/modules/BoostTestDiscoverTests.cmake @@ -226,12 +226,21 @@ function(boosttest_discover_tests TARGET) # Prepare file names for ctest configuration files. 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") + if(GENERATOR_IS_MULTI_CONFIG) + set(ctest_tests_file "${ctest_file_base}_tests-$.cmake") + else() + set(ctest_tests_file "${ctest_file_base}_tests.cmake") + endif() # Define rule to generate test list for aforementioned test executable if(__DISCOVERY_MODE STREQUAL "POST_BUILD") + if(CMAKE_VERSION VERSION_LESS "3.20" AND GENERATOR_IS_MULTI_CONFIG) + message(WARNING "CMake is too old to support discovery mode POST_BUILD with multi-config generators. Consider using discovery mode PRE_TEST instead!") + set(ctest_tests_file "${ctest_file_base}_tests.cmake") + endif() + # Note: Make sure to properly quote all definitions that are given # via the cmdline, or we might swallow some white-space. add_custom_command( @@ -254,20 +263,16 @@ function(boosttest_discover_tests TARGET) -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" + string(CONCAT ctest_include_content + "if(EXISTS \"${ctest_tests_file}\")" "\n" + " include(\"${ctest_tests_file}\")" "\n" + "else()" "\n" + " add_test(${TARGET}_NOT_BUILT ${TARGET}_NOT_BUILT)" "\n" + "endif()" "\n" ) elseif(__DISCOVERY_MODE STREQUAL "PRE_TEST") - if(GENERATOR_IS_MULTI_CONFIG) - set(ctest_tests_file "${ctest_file_base}_tests-$.cmake") - endif() - string(CONCAT ctest_include_content "if(EXISTS \"$\")" "\n" " if(\"$\" IS_NEWER_THAN \"${ctest_tests_file}\")" "\n" @@ -294,19 +299,19 @@ function(boosttest_discover_tests TARGET) "endif()" "\n" ) - if(GENERATOR_IS_MULTI_CONFIG) - foreach(_config ${CMAKE_CONFIGURATION_TYPES}) - file(GENERATE OUTPUT "${ctest_file_base}_include-${_config}.cmake" CONTENT "${ctest_include_content}" CONDITION $) - endforeach() - file(WRITE "${ctest_include_file}" "include(\"${ctest_file_base}_include-\${CTEST_CONFIGURATION_TYPE}.cmake\")") - else() - file(GENERATE OUTPUT "${ctest_file_base}_include.cmake" CONTENT "${ctest_include_content}") - endif() - else() message(SEND_ERROR "Unknown DISCOVERY_MODE: ${__DISCOVERY_MODE}") endif() + if(GENERATOR_IS_MULTI_CONFIG) + foreach(_config ${CMAKE_CONFIGURATION_TYPES}) + file(GENERATE OUTPUT "${ctest_file_base}_include-${_config}.cmake" CONTENT "${ctest_include_content}" CONDITION $) + endforeach() + file(WRITE "${ctest_include_file}" "include(\"${ctest_file_base}_include-\${CTEST_CONFIGURATION_TYPE}.cmake\")") + else() + file(GENERATE OUTPUT "${ctest_file_base}_include.cmake" CONTENT "${ctest_include_content}") + endif() + # Add discovered tests to directory TEST_INCLUDE_FILES set_property(DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}" From e5a966fad2951ebefb4ef2978f8019cbfdbf6bf4 Mon Sep 17 00:00:00 2001 From: Deniz Bahadir Date: Mon, 25 May 2020 11:26:56 +0200 Subject: [PATCH 4/8] Adjust the BoostTestDiscoverTests module to support CMake < v3.17 --- modules/BoostTestDiscoverTests.cmake | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/BoostTestDiscoverTests.cmake b/modules/BoostTestDiscoverTests.cmake index 3351f9d..dc66f68 100644 --- a/modules/BoostTestDiscoverTests.cmake +++ b/modules/BoostTestDiscoverTests.cmake @@ -156,6 +156,10 @@ same as the Boost.Test name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. #]=======================================================================] +if(CMAKE_VERSION VERSION_LESS "3.17") + set(__BOOSTTEST_DISCOVER_TESTS_DIR "${CMAKE_CURRENT_LIST_DIR}") +endif() + #------------------------------------------------------------------------------ function(boosttest_discover_tests TARGET) @@ -190,6 +194,11 @@ function(boosttest_discover_tests TARGET) set(__BOOSTTEST_DISCOVER_TESTS_SCRIPT "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/BoostTestAddTests.cmake" ) + if(CMAKE_VERSION VERSION_LESS "3.17") + set(__BOOSTTEST_DISCOVER_TESTS_SCRIPT + "${__BOOSTTEST_DISCOVER_TESTS_DIR}/BoostTestAddTests.cmake" + ) + endif() # Determine how often tests were discovered on the target and store in property get_property( From 71c97c96db1a1b86dc53fad63822af6c73c40c53 Mon Sep 17 00:00:00 2001 From: Deniz Bahadir Date: Thu, 12 Aug 2021 11:21:14 +0200 Subject: [PATCH 5/8] Adjust the BoostTestDiscoverTests module to support CMake < v3.15 --- modules/BoostTestAddTests.cmake | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/BoostTestAddTests.cmake b/modules/BoostTestAddTests.cmake index 1f15428..e511558 100644 --- a/modules/BoostTestAddTests.cmake +++ b/modules/BoostTestAddTests.cmake @@ -178,7 +178,13 @@ function(boosttest_discover_tests_impl) # Prepare the hierarchy list for the next test-case. math(EXPR diff "${former_level} - ${next_level}") foreach(i RANGE ${diff}) - list(POP_BACK hierarchy) + if (CMAKE_VERSION VERSION_LESS "3.15") + list(LENGTH hierarchy length) + math(EXPR index "${length} - 1") + list(REMOVE_AT hierarchy ${index}) + else() + list(POP_BACK hierarchy) + endif() endforeach() endif() if (former_level STREQUAL NaN) From 68fac24bc95dfe98e6586c2b74d49302c8feeb0b Mon Sep 17 00:00:00 2001 From: Deniz Bahadir Date: Thu, 12 Aug 2021 11:12:08 +0200 Subject: [PATCH 6/8] Adjust the BoostTestDiscoverTests module to support CMake < v3.12 --- modules/BoostTestAddTests.cmake | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/modules/BoostTestAddTests.cmake b/modules/BoostTestAddTests.cmake index e511558..cdff394 100644 --- a/modules/BoostTestAddTests.cmake +++ b/modules/BoostTestAddTests.cmake @@ -75,8 +75,25 @@ endmacro() # Adds another test to the script. macro(add_another_test hierarchy_list enabled separator) # Create the name and path of the test-case... - list(JOIN ${hierarchy_list} ${separator} test_name) - list(JOIN ${hierarchy_list} "/" test_path) + if (CMAKE_VERSION VERSION_LESS "3.12") + set(test_name) + set(test_path) + foreach(hierarchy_entry IN LISTS ${hierarchy_list}) + if ("${test_name}" STREQUAL "") + set(test_name "${hierarchy_entry}") + else() + set(test_name "${test_name}${separator}${hierarchy_entry}") + endif() + if ("${test_path}" STREQUAL "") + set(test_path "${hierarchy_entry}") + else() + set(test_path "${test_path}/${hierarchy_entry}") + endif() + endforeach() + else() + list(JOIN ${hierarchy_list} ${separator} test_name) + list(JOIN ${hierarchy_list} "/" test_path) + endif() # ...and add to script. add_command(add_test "${prefix}${test_name}${suffix}" From 921e154f161e9f569f9c5fea8c341dd026bc8349 Mon Sep 17 00:00:00 2001 From: Deniz Bahadir Date: Thu, 12 Aug 2021 12:08:23 +0200 Subject: [PATCH 7/8] Adjust the BoostTestDiscoverTests module to support CMake < v3.7 --- modules/BoostTestAddTests.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/BoostTestAddTests.cmake b/modules/BoostTestAddTests.cmake index cdff394..6161b02 100644 --- a/modules/BoostTestAddTests.cmake +++ b/modules/BoostTestAddTests.cmake @@ -188,7 +188,7 @@ function(boosttest_discover_tests_impl) math(EXPR next_level "${next_level} / 4") # Add the test for the test-case from the former loop-run? - if (next_level LESS_EQUAL former_level) + if ((next_level LESS former_level) OR (next_level EQUAL former_level)) # Add test-case to the script. add_another_test(hierarchy ${test_is_enabled} "${__TEST_NAME_SEPARATOR}") From 226b725e03402b78cb7897a1a2db4a5522f61e09 Mon Sep 17 00:00:00 2001 From: Deniz Bahadir Date: Thu, 12 Aug 2021 12:19:27 +0200 Subject: [PATCH 8/8] Adjust the BoostTestDiscoverTests module to fail for CMake < v3.5 --- modules/BoostTestDiscoverTests.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/BoostTestDiscoverTests.cmake b/modules/BoostTestDiscoverTests.cmake index dc66f68..cd1241c 100644 --- a/modules/BoostTestDiscoverTests.cmake +++ b/modules/BoostTestDiscoverTests.cmake @@ -156,6 +156,10 @@ same as the Boost.Test name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. #]=======================================================================] +if(CMAKE_VERSION VERSION_LESS "3.5") + message(FATAL_ERROR "CMake version is too old for `boosttest_discover_tests`!") +endif() + if(CMAKE_VERSION VERSION_LESS "3.17") set(__BOOSTTEST_DISCOVER_TESTS_DIR "${CMAKE_CURRENT_LIST_DIR}") endif()