← Back to team overview

kicad-developers team mailing list archive

[PATCH 1/1] Split Boost.Test based tests

 

---
 CMakeModules/BoostTest.cmake         | 204 +++++++++++++++++++++++++++
 CMakeModules/BoostTestAddTests.cmake | 138 ++++++++++++++++++
 qa/CMakeLists.txt                    |   1 +
 qa/common/CMakeLists.txt             |   6 +-
 qa/eeschema/CMakeLists.txt           |   2 +-
 qa/libs/sexpr/CMakeLists.txt         |   2 +-
 qa/pcbnew/CMakeLists.txt             |   2 +-
 qa/utils/kicad2step/CMakeLists.txt   |   2 +-
 8 files changed, 350 insertions(+), 7 deletions(-)
 create mode 100644 CMakeModules/BoostTest.cmake
 create mode 100644 CMakeModules/BoostTestAddTests.cmake

diff --git a/CMakeModules/BoostTest.cmake b/CMakeModules/BoostTest.cmake
new file mode 100644
index 000000000..ec3419694
--- /dev/null
+++ b/CMakeModules/BoostTest.cmake
@@ -0,0 +1,204 @@
+# 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`.
+
+    .. note::
+
+      In CMake versions 3.10.1 and 3.10.2, this option was called ``TIMEOUT``.
+      This clashed with the ``TIMEOUT`` test property, which is one of the
+      common properties that would be set with the ``PROPERTIES`` keyword,
+      usually leading to legal but unintended behavior.  The keyword was
+      changed to ``DISCOVERY_TIMEOUT`` in CMake 3.10.3 to address this
+      problem.  The ambiguous behavior of the ``TIMEOUT`` keyword in 3.10.1
+      and 3.10.2 has not been preserved.
+
+#]=======================================================================]
+
+# 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)
diff --git a/CMakeModules/BoostTestAddTests.cmake b/CMakeModules/BoostTestAddTests.cmake
new file mode 100644
index 000000000..e7fe64f53
--- /dev/null
+++ b/CMakeModules/BoostTestAddTests.cmake
@@ -0,0 +1,138 @@
+# 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}")
diff --git a/qa/CMakeLists.txt b/qa/CMakeLists.txt
index bb379e7ff..7d2beeb8e 100644
--- a/qa/CMakeLists.txt
+++ b/qa/CMakeLists.txt
@@ -20,6 +20,7 @@
 # 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
 include(KiCadQABuildUtils)
+include(BoostTest)
 
 if( KICAD_SCRIPTING_MODULES )
 
diff --git a/qa/common/CMakeLists.txt b/qa/common/CMakeLists.txt
index 061675514..99c4e9bb7 100644
--- a/qa/common/CMakeLists.txt
+++ b/qa/common/CMakeLists.txt
@@ -96,6 +96,6 @@ include_directories(
     ${INC_AFTER}
 )
 
-kicad_add_boost_test( qa_common_eeschema common_eeschema )
-kicad_add_boost_test( qa_common_pcbnew common_pcbnew )
-kicad_add_boost_test( qa_common_gerbview qa_common_gerbview )
+boost_test_discover_tests( qa_common_eeschema )
+boost_test_discover_tests( qa_common_pcbnew )
+boost_test_discover_tests( qa_common_gerbview )
diff --git a/qa/eeschema/CMakeLists.txt b/qa/eeschema/CMakeLists.txt
index 65009ca4b..09b162518 100644
--- a/qa/eeschema/CMakeLists.txt
+++ b/qa/eeschema/CMakeLists.txt
@@ -81,4 +81,4 @@ set_source_files_properties( eeschema_test_utils.cpp PROPERTIES
     COMPILE_DEFINITIONS "QA_EESCHEMA_DATA_LOCATION=(\"${CMAKE_CURRENT_SOURCE_DIR}/data\")"
 )
 
-kicad_add_boost_test( qa_eeschema eeschema )
+boost_test_discover_tests( qa_eeschema )
diff --git a/qa/libs/sexpr/CMakeLists.txt b/qa/libs/sexpr/CMakeLists.txt
index bee0b8a0a..414d6c300 100644
--- a/qa/libs/sexpr/CMakeLists.txt
+++ b/qa/libs/sexpr/CMakeLists.txt
@@ -41,4 +41,4 @@ target_include_directories( qa_sexpr PRIVATE
     ${CMAKE_CURRENT_SOURCE_DIR}
 )
 
-kicad_add_boost_test(qa_sexpr sexpr)
\ No newline at end of file
+boost_test_discover_tests( qa_sexpr )
diff --git a/qa/pcbnew/CMakeLists.txt b/qa/pcbnew/CMakeLists.txt
index f07ed6160..53d67c538 100644
--- a/qa/pcbnew/CMakeLists.txt
+++ b/qa/pcbnew/CMakeLists.txt
@@ -81,4 +81,4 @@ target_link_libraries( qa_pcbnew
     ${PCBNEW_EXTRA_LIBS}    # -lrt must follow Boost
 )
 
-kicad_add_boost_test( qa_pcbnew pcbnew )
\ No newline at end of file
+boost_test_discover_tests( qa_pcbnew )
diff --git a/qa/utils/kicad2step/CMakeLists.txt b/qa/utils/kicad2step/CMakeLists.txt
index fa58e300b..f349552ae 100644
--- a/qa/utils/kicad2step/CMakeLists.txt
+++ b/qa/utils/kicad2step/CMakeLists.txt
@@ -44,4 +44,4 @@ target_include_directories( qa_sexpr PRIVATE
     ${CMAKE_CURRENT_SOURCE_DIR}
 )
 
-kicad_add_boost_test( qa_kicad2step kicad2step )
\ No newline at end of file
+boost_test_discover_tests( qa_kicad2step kicad2step )

References