From 956be376c94483948a98ae1569dec3440c64bb2d Mon Sep 17 00:00:00 2001 From: Wai-Shing Luk Date: Tue, 26 Mar 2024 14:13:13 +0000 Subject: [PATCH] update third party libraries --- cmake/doctest.cmake | 180 ++++++++++++++++++++++++++++++++++++ cmake/doctestAddTests.cmake | 128 +++++++++++++++++++++++++ cmake/specific.cmake | 2 +- test/CMakeLists.txt | 4 +- xmake.lua | 4 +- 5 files changed, 312 insertions(+), 6 deletions(-) create mode 100644 cmake/doctest.cmake create mode 100644 cmake/doctestAddTests.cmake diff --git a/cmake/doctest.cmake b/cmake/doctest.cmake new file mode 100644 index 0000000..166d7a3 --- /dev/null +++ b/cmake/doctest.cmake @@ -0,0 +1,180 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or +# https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +doctest +----- + +This module defines a function to help use the doctest test framework. + +The :command:`doctest_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 doctest 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 doctest name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. + +.. command:: doctest_discover_tests + + Automatically add tests with CTest by querying the compiled test executable + for available tests:: + + doctest_discover_tests(target + [TEST_SPEC arg1...] + [EXTRA_ARGS arg1...] + [WORKING_DIRECTORY dir] + [TEST_PREFIX prefix] + [TEST_SUFFIX suffix] + [PROPERTIES name1 value1...] + [ADD_LABELS value] + [TEST_LIST var] + [JUNIT_OUTPUT_DIR dir] + ) + + ``doctest_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 ``--list-test-cases`` argument. This ensures that the full + list of 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 ``_TESTS`` variable. + + The options are: + + ``target`` + Specifies the doctest executable, which must be a known CMake executable + target. CMake will substitute the location of the built executable when + running the test. + + ``TEST_SPEC arg1...`` + Specifies test cases, wildcarded test cases, tags and tag expressions to + pass to the doctest executable with the ``--list-test-cases`` argument. + + ``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 ``doctest_discover_tests()`` but with different + ``TEST_SPEC`` or ``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 ``doctest_discover_tests``. + + ``ADD_LABELS value`` + Specifies if the test labels should be set automatically. + + ``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 ``doctest_discover_tests()``. + Note that this variable is only available in CTest. + + ``JUNIT_OUTPUT_DIR dir`` + If specified, the parameter is passed along with ``--reporters=junit`` + and ``--out=`` to the test executable. The actual file name is the same + as the test target, including prefix and suffix. This should be used + instead of EXTRA_ARGS to avoid race conditions writing the XML result + output when using parallel test execution. + +#]=======================================================================] + +# ------------------------------------------------------------------------------ +function(doctest_discover_tests TARGET) + cmake_parse_arguments( + "" "" "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST;JUNIT_OUTPUT_DIR" + "TEST_SPEC;EXTRA_ARGS;PROPERTIES;ADD_LABELS" ${ARGN} + ) + + if(NOT _WORKING_DIRECTORY) + set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") + endif() + if(NOT _TEST_LIST) + set(_TEST_LIST ${TARGET}_TESTS) + endif() + + # Generate a unique name based on the extra arguments + string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}") + string(SUBSTRING ${args_hash} 0 7 args_hash) + + # Define rule to generate test list for aforementioned test executable + set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake") + set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.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=$" -D + "TEST_EXECUTOR=${crosscompiling_emulator}" -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" -D + "TEST_SPEC=${_TEST_SPEC}" -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" -D + "TEST_PROPERTIES=${_PROPERTIES}" -D "TEST_ADD_LABELS=${_ADD_LABELS}" -D + "TEST_PREFIX=${_TEST_PREFIX}" -D "TEST_SUFFIX=${_TEST_SUFFIX}" -D "TEST_LIST=${_TEST_LIST}" -D + "TEST_JUNIT_OUTPUT_DIR=${_JUNIT_OUTPUT_DIR}" -D "CTEST_FILE=${ctest_tests_file}" -P + "${_DOCTEST_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-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" "endif()\n" + ) + + if(NOT CMAKE_VERSION VERSION_LESS 3.10) + # Add discovered tests to directory TEST_INCLUDE_FILES + set_property( + DIRECTORY + APPEND + PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}" + ) + else() + # Add discovered tests as directory TEST_INCLUDE_FILE if possible + get_property( + test_include_file_set + DIRECTORY + PROPERTY TEST_INCLUDE_FILE + SET + ) + if(NOT ${test_include_file_set}) + set_property(DIRECTORY PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}") + else() + message(FATAL_ERROR "Cannot set more than one TEST_INCLUDE_FILE") + endif() + endif() + +endfunction() + +# ################################################################################################## + +set(_DOCTEST_DISCOVER_TESTS_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/doctestAddTests.cmake) diff --git a/cmake/doctestAddTests.cmake b/cmake/doctestAddTests.cmake new file mode 100644 index 0000000..41cdc7f --- /dev/null +++ b/cmake/doctestAddTests.cmake @@ -0,0 +1,128 @@ +# 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(spec ${TEST_SPEC}) +set(extra_args ${TEST_EXTRA_ARGS}) +set(properties ${TEST_PROPERTIES}) +set(add_labels ${TEST_ADD_LABELS}) +set(junit_output_dir "${TEST_JUNIT_OUTPUT_DIR}") +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}]==]") # form a bracket_argument + 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 '${TEST_EXECUTABLE}' does not exist") +endif() + +if("${spec}" MATCHES .) + set(spec "--test-case=${spec}") +endif() + +execute_process( + COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-cases + OUTPUT_VARIABLE output + RESULT_VARIABLE result + WORKING_DIRECTORY "${TEST_WORKING_DIR}" +) +if(NOT ${result} EQUAL 0) + message(FATAL_ERROR "Error running test executable '${TEST_EXECUTABLE}':\n" + " Result: ${result}\n" " Output: ${output}\n" + ) +endif() + +string(REPLACE "\n" ";" output "${output}") + +# Parse output +foreach(line ${output}) + if("${line}" STREQUAL + "===============================================================================" + OR "${line}" MATCHES [==[^\[doctest\] ]==] + ) + continue() + endif() + set(test ${line}) + set(labels "") + if(${add_labels}) + # get test suite that test belongs to + execute_process( + COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" --test-case=${test} --list-test-suites + OUTPUT_VARIABLE labeloutput + RESULT_VARIABLE labelresult + WORKING_DIRECTORY "${TEST_WORKING_DIR}" + ) + if(NOT ${labelresult} EQUAL 0) + message(FATAL_ERROR "Error running test executable '${TEST_EXECUTABLE}':\n" + " Result: ${labelresult}\n" " Output: ${labeloutput}\n" + ) + endif() + + string(REPLACE "\n" ";" labeloutput "${labeloutput}") + foreach(labelline ${labeloutput}) + if("${labelline}" STREQUAL + "===============================================================================" + OR "${labelline}" MATCHES [==[^\[doctest\] ]==] + ) + continue() + endif() + list(APPEND labels ${labelline}) + endforeach() + endif() + + if(NOT "${junit_output_dir}" STREQUAL "") + # turn testname into a valid filename by replacing all special characters with "-" + string(REGEX REPLACE "[/\\:\"|<>]" "-" test_filename "${test}") + set(TEST_JUNIT_OUTPUT_PARAM "--reporters=junit" + "--out=${junit_output_dir}/${prefix}${test_filename}${suffix}.xml" + ) + else() + unset(TEST_JUNIT_OUTPUT_PARAM) + endif() + # use escape commas to handle properly test cases with commas inside the name + string(REPLACE "," "\\," test_name ${test}) + # ...and add to script + add_command( + add_test + "${prefix}${test}${suffix}" + ${TEST_EXECUTOR} + "${TEST_EXECUTABLE}" + "--test-case=${test_name}" + "${TEST_JUNIT_OUTPUT_PARAM}" + ${extra_args} + ) + add_command( + set_tests_properties + "${prefix}${test}${suffix}" + PROPERTIES + WORKING_DIRECTORY + "${TEST_WORKING_DIR}" + ${properties} + LABELS + ${labels} + ) + unset(labels) + list(APPEND tests "${prefix}${test}${suffix}") +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}") diff --git a/cmake/specific.cmake b/cmake/specific.cmake index 176930a..dba162f 100644 --- a/cmake/specific.cmake +++ b/cmake/specific.cmake @@ -1,6 +1,6 @@ CPMAddPackage( NAME fmt - GIT_TAG 9.1.0 + GIT_TAG 10.2.1 GITHUB_REPOSITORY fmtlib/fmt OPTIONS "FMT_INSTALL YES" # create an installable target ) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 060d4a3..e39e21a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -16,7 +16,7 @@ include(../cmake/tools.cmake) include(../cmake/CPM.cmake) include(../cmake/specific.cmake) -CPMAddPackage("gh:doctest/doctest@2.4.9") +CPMAddPackage("gh:doctest/doctest@2.4.11") CPMAddPackage("gh:TheLartians/Format.cmake@1.7.3") if(TEST_INSTALLED_VERSION) @@ -52,7 +52,7 @@ enable_testing() # testing frameworks add the tests target instead: add_test(NAME ${PROJECT_NAME} COMMAND # ${PROJECT_NAME}) -include(${doctest_SOURCE_DIR}/scripts/cmake/doctest.cmake) +include(../cmake/doctest.cmake) doctest_discover_tests(${PROJECT_NAME}) # ---- code coverage ---- diff --git a/xmake.lua b/xmake.lua index af2a830..edc9851 100644 --- a/xmake.lua +++ b/xmake.lua @@ -1,11 +1,9 @@ set_languages("c++20") add_rules("mode.debug", "mode.release", "mode.coverage") -add_requires("fmt 9.1.0", {alias = "fmt"}) +add_requires("fmt", {alias = "fmt"}) add_requires("doctest", {alias = "doctest"}) --- add_requires("conan::ms-gsl/3.1.0", {alias = "ms-gsl"}) add_requires("microsoft-gsl", {alias = "ms-gsl"}) --- add_requires("apt::libboost-dev", {alias = "boost"}) if is_mode("coverage") then add_cxflags("-ftest-coverage", "-fprofile-arcs", {force = true})