WiscSort / EMS / header / ips4o / extern / cmake-modules / BasisTest.cmake
BasisTest.cmake
Raw
# ============================================================================
# Copyright (c) 2011-2012 University of Pennsylvania
# Copyright (c) 2013-2014 Andreas Schuh
# All rights reserved.
#
# See COPYING file for license information or visit
# http://opensource.andreasschuh.com/cmake-basis/download.html#license
# ============================================================================

##############################################################################
# @file  BasisTest.cmake
# @brief CTest configuration. Include this module instead of CTest.
#
# @note This module is included by basis_project_initialize().
#
# @ingroup CMakeAPI
##############################################################################

# ============================================================================
# configuration
# ============================================================================

## @brief Request build of tests.
option (BUILD_TESTING "Request build of tests." OFF)

# include CTest module which enables testing, but prevent it from generating
# any configuration file or adding targets yet as we want to adjust the
# default CTest settings--in particular the site name--before
set (RUN_FROM_DART 1)
include (CTest)

# mark timeout option as advanced
mark_as_advanced (DART_TESTING_TIMEOUT)

set (RUN_FROM_CTEST_OR_DART 1)
include (CTestTargets)
set (RUN_FROM_CTEST_OR_DART)

# custom CTest configuration
if (EXISTS "${PROJECT_CONFIG_DIR}/CTestCustom.cmake.in")
  configure_file (
    "${PROJECT_CONFIG_DIR}/CTestCustom.cmake.in"
    "${PROJECT_BINARY_DIR}/CTestCustom.cmake"
    @ONLY
  )
elseif (EXISTS "${PROJECT_CONFIG_DIR}/CTestCustom.cmake")
  configure_file (
    "${PROJECT_CONFIG_DIR}/CTestCustom.cmake"
    "${PROJECT_BINARY_DIR}/CTestCustom.cmake"
    COPYONLY
  )
else ()
  basis_get_relative_path (CONFIG_DIR "${PROJECT_SOURCE_DIR}" "${PROJECT_CONFIG_DIR}")
  if (EXISTS "${BASIS_TEMPLATE_DIR}/${CONFIG_DIR}/CTestCustom.cmake.in")
    # to avoid a Doxygen warning, we need to replace certain patterns used by
    # the basisproject tool to replace them with project related information
    #
    # Note: Do this only on the first pass, otherwise configure_file() will
    #       retrigger CMake every time b/c the modification timestamp of the
    #       source file is newer than the previously configured file.
    #       The use of the "cmake -E copy_if_different" command could be used
    #       here instead, but as we do not expect the CTestCustom.cmake.in
    #       file of the BASIS project template to change, it is sufficient
    #       to only check if we copied the template file already.
    if (NOT EXISTS "${PROJECT_BINARY_DIR}/CTestCustom.cmake.in")
      file (READ "${BASIS_TEMPLATE_DIR}/${CONFIG_DIR}/CTestCustom.cmake.in" _TEMPLATE)
      string (REGEX REPLACE "<year>" "" _TEMPLATE "${_TEMPLATE}")
      file (WRITE "${PROJECT_BINARY_DIR}/CTestCustom.cmake.in" "${_TEMPLATE}")
      unset (_TEMPLATE)
    endif ()
    # configure the modified template file
    configure_file (
      "${PROJECT_BINARY_DIR}/CTestCustom.cmake.in"
      "${PROJECT_BINARY_DIR}/CTestCustom.cmake"
      @ONLY
    )
  endif ()
endif ()

# ============================================================================
# constants
# ============================================================================

## @brief Names of recognized properties on tests.
#
# Unfortunately, the @c ARGV and @c ARGN arguments of a CMake function()
# or macro() does not preserve values which themselves are lists. Therefore,
# it is not possible to distinguish between property names and their values
# in the arguments passed to basis_set_tests_properties().
# To overcome this problem, this list specifies all the possible property names.
# Everything else is considered to be a property value except the first argument
# follwing right after the @c PROPERTIES keyword. Alternatively,
# basis_set_property() can be used as here no disambiguity exists.
#
# @note Placeholders such as &lt;CONFIG&gt; are allowed. These are treated
#       as the regular expression "[^ ]+". See basis_list_to_regex().
#
# @sa http://www.cmake.org/cmake/help/cmake-2-8-docs.html#section_PropertiesonTests
set (BASIS_PROPERTIES_ON_TESTS
  ATTACHED_FILES
  ATTACHED_FILES_ON_FAIL
  COST
  DEPENDS
  ENVIRONMENT
  FAIL_REGULAR_EXPRESSION
  LABELS
  MEASUREMENT
  PASS_REGULAR_EXPRESSION
  PROCESSORS
  REQUIRED_FILES
  RESOURCE_LOCK
  RUN_SERIAL
  TIMEOUT
  WILL_FAIL
  WORKING_DIRECTORY
)

# convert list into regular expression
basis_list_to_regex (BASIS_PROPERTIES_ON_TESTS_RE ${BASIS_PROPERTIES_ON_TESTS})

# ============================================================================
# utilities
# ============================================================================

## @addtogroup CMakeUtilities
#  @{


# ----------------------------------------------------------------------------
## @brief Disable testing if project does not implement any tests.
#
# This function checks if there are test/ subdirectories in the project and
# disables and hides the BUILD_TESTING option if none are found.
function (basis_disable_testing_if_no_tests)
  if (IS_DIRECTORY "${PROJECT_TESTING_DIR}")
    set (DISABLE_TESTING FALSE)
  else ()
    set (DISABLE_TESTING TRUE)
  endif ()
  if (DISABLE_TESTING)
    basis_get_relative_path (TESTING_DIR "${PROJECT_SOURCE_DIR}" "${PROJECT_TESTING_DIR}")
    foreach (M IN LISTS PROJECT_MODULES_ENABLED)
      if (IS_DIRECTORY "${MODULE_${M}_SOURCE_DIR}/${TESTING_DIR}")
        set (DISABLE_TESTING FALSE)
        break ()
      endif ()
    endforeach ()
  endif ()
  if (DISABLE_TESTING)
    set_property (CACHE BUILD_TESTING PROPERTY VALUE OFF)
    set_property (CACHE BUILD_TESTING PROPERTY TYPE  INTERNAL)
  else ()
    set_property (CACHE BUILD_TESTING PROPERTY TYPE BOOL)
  endif ()
endfunction ()

# ----------------------------------------------------------------------------
## @brief Set a property of the tests.
#
# This function replaces CMake's
# <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:set_tests_properties">
# set_tests_properties()</a> command.
#
# @note Due to a bug in CMake (http://www.cmake.org/Bug/view.php?id=12303),
#       except of the first property given directly after the @c PROPERTIES keyword,
#       only properties listed in @c BASIS_PROPERTIES_ON_TESTS can be set.
#
# @param [in] ARGN List of arguments for
#                  <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:set_tests_properties">
#                  set_tests_properties()</a>.
#
# @returns Sets the given properties of the specified test.
#
# @sa http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:set_tests_properties
#
# @ingroup CMakeAPI
function (basis_set_tests_properties)
  # convert test names to UIDs
  set (TEST_UIDS)
  list (GET ARGN 0 ARG)
  while (ARG AND NOT ARG MATCHES "^PROPERTIES$")
    basis_get_test_uid (TEST_UID "${ARG}")
    list (APPEND TEST_UIDS "${TEST_UID}")
    list (REMOVE_AT ARGN 0)
    list (GET ARGN 0 ARG)
  endwhile ()
  if (NOT ARG MATCHES "^PROPERTIES$")
    message (FATAL_ERROR "Missing PROPERTIES argument!")
  elseif (NOT TEST_UIDS)
    message (FATAL_ERROR "No test specified!")
  endif ()
  # remove PROPERTIES keyword
  list (REMOVE_AT ARGN 0)
  # set tests properties
  #
  # Note: By iterating over the properties, the empty property values
  #       are correctly passed on to CMake's set_tests_properties()
  #       command, while
  #       set_tests_properties(${TEST_UIDS} PROPERTIES ${ARGN})
  #       (erroneously) discards the empty elements in ARGN.
  if (BASIS_DEBUG)
    message ("** basis_set_tests_properties:")
    message ("**   Test(s)  :  ${TEST_UIDS}")
    message ("**   Properties: [${ARGN}]")
  endif ()
  list (LENGTH ARGN N)
  while (N GREATER 1)
    list (GET ARGN 0 PROPERTY)
    list (GET ARGN 1 VALUE)
    list (REMOVE_AT ARGN 0 1)
    list (LENGTH ARGN N)
    # The following loop is only required b/c CMake's ARGV and ARGN
    # lists do not support arguments which are themselves lists.
    # Therefore, we need a way to decide when the list of values for a
    # property is terminated. Hence, we only allow known properties
    # to be set, except for the first property where the name follows
    # directly after the PROPERTIES keyword.
    while (N GREATER 0)
      list (GET ARGN 0 ARG)
      if (ARG MATCHES "${BASIS_PROPERTIES_ON_TESTS_RE}")
        break ()
      endif ()
      list (APPEND VALUE "${ARG}")
      list (REMOVE_AT ARGN 0)
      list (LENGTH ARGN N)
    endwhile ()
    # check property name
    if ("${PROPERTY}" STREQUAL "")
      message (FATAL_ERROR "Empty property name given!")
    endif ()
    # map test names to test UIDs
    if (PROPERTY MATCHES "^DEPENDS$")
      set (UIDS)
      foreach (V IN LISTS VALUE)
        basis_get_test_uid (UID "${V}")
        list (APPEND UIDS "${UID}")
      endforeach ()
      set (VALUE ${UIDS})
    endif ()
    # set target property
    if (BASIS_DEBUG)
      message ("**   -> ${PROPERTY} = [${VALUE}]")
    endif ()
    set_tests_properties (${TEST_UIDS} PROPERTIES ${PROPERTY} "${VALUE}")
  endwhile ()
  # make sure that every property had a corresponding value
  if (NOT N EQUAL 0)
    message (FATAL_ERROR "No value given for test property ${ARGN}")
  endif ()
endfunction ()

# ----------------------------------------------------------------------------
## @brief Get a property of the test.
#
# This function replaces CMake's
# <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:get_test_property">
# get_test_property()</a> command.
#
# @param [out] VAR       Property value.
# @param [in]  TEST_NAME Name of test.
# @param [in]  ARGN      Remaining arguments of
#                        <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:get_test_property">
#                        get_test_property()</a>.
#
# @returns Sets @p VAR to the value of the requested property.
#
# @sa http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:get_test_property
#
# @ingroup CMakeAPI
function (basis_get_test_property VAR TEST_NAME)
  basis_get_test_uid (TEST_UID "${TEST_NAME}")
  get_test_property (VALUE "${TEST_UID}" ${ARGN})
  set (${VAR} "${VALUE}" PARENT_SCOPE)
endfunction ()

# ----------------------------------------------------------------------------
## @brief Create and add a test driver executable.
#
# @param [in] TESTDRIVER_NAME Name of the test driver.
# @param [in] ARGN            List of source files implementing tests.
#
# @ingroup CMakeAPI
function (basis_add_test_driver TESTDRIVER_NAME)
  # parse arguments
  CMAKE_PARSE_ARGUMENTS (
    ARGN
      ""
      "EXTRA_INCLUDE;FUNCTION"
      ""
    ${ARGN}
  )
  if (ARGN_EXTRA_INCLUDE OR ARGN_FUNCTION)
    message (FATAL_ERROR "Invalid argument EXTRA_INCLUDE or FUNCTION! "
                         "Use create_test_sourcelist() if you want to create "
                         "a custom test driver.")
  endif ()
  # choose test driver implementation depending on which packages are available
  set (TESTDRIVER_INCLUDE      "basis/testdriver.h")
  set (TESTDRIVER_LINK_DEPENDS basis.basis)
  if (ITK_FOUND)
    basis_include_directories (BEFORE ${ITK_INCLUDE_DIRS})
    list (APPEND TESTDRIVER_LINK_DEPENDS ${ITK_LIBRARIES})
  endif ()
  if (WIN32)
    list (APPEND TESTDRIVER_LINK_DEPENDS Ws2_32)
  endif ()
  # create test driver source code
  set (TESTDRIVER_SOURCE "${TESTDRIVER_NAME}")
  if (NOT TESTDRIVER_SOURCE MATCHES "\\.cxx")
    set (TESTDRIVER_SOURCE "${TESTDRIVER_SOURCE}.cxx")
  endif ()
  set (CMAKE_TESTDRIVER_BEFORE_TESTMAIN "    #include <basis/testdriver-before-test.inc>")
  set (CMAKE_TESTDRIVER_AFTER_TESTMAIN  "    #include <basis/testdriver-after-test.inc>")
  create_test_sourcelist (
    TESTDRIVER_SOURCES
      ${TESTDRIVER_SOURCE} ${ARGN_UNPARSED_ARGUMENTS}
      EXTRA_INCLUDE "${TESTDRIVER_INCLUDE}"
      FUNCTION      testdriversetup
  )
  # add test driver executable
  basis_add_executable (${TESTDRIVER_NAME} ${TESTDRIVER_SOURCES})
  basis_target_link_libraries (${TESTDRIVER_NAME} ${TESTDRIVER_LINK_DEPENDS})
  if (ITK_FOUND)
    basis_set_target_properties (
      ${TESTDRIVER_NAME}
      PROPERTIES
        COMPILE_DEFINITIONS
          TESTDRIVER_NAME=\"${TESTDRIVER_NAME}\"
          ITK_VERSION=\"${ITK_VERSION_MAJOR}.${ITK_VERSION_MINOR}.${ITK_VERSION_PATCH}\"
          ITK_VERSION_MAJOR=${ITK_VERSION_MAJOR}
    )
  else ()
    basis_set_target_properties (
      ${TESTDRIVER_NAME}
      PROPERTIES
        COMPILE_DEFINITIONS
          TESTDRIVER_NAME=\"${TESTDRIVER_NAME}\"
    )
  endif ()
endfunction ()

# ----------------------------------------------------------------------------
## @brief Add test.
#
# This command is used similar to CMake's
# <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:add_test">
# add_test()</a> command. It adds a test to the
# <a href="http://www.cmake.org/cmake/help/ctest-2-8-docs.html">CTest</a>-based
# testing system. Unlike CMake's
# <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:add_test">
# add_test()</a>, this command can, for convenience, implicitly add
# the necessary executable build target to the build system. Therefore,
# instead of the name of the executable command, specify the sources of the
# test implementation. An executable build target is then added by this
# function using basis_add_executable(), and the built executable is used
# as test command. If the @p UNITTEST option is given, the necessary unit
# testing libraries which are part of the BASIS installation are added as
# link dependencies as well as the default implementation of the main()
# function if none of the specified source files has the suffix
# <tt>-main</tt> or <tt>_main</tt> in the file name.
#
# Generator expressions as supported by CMake's
# <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:add_test">
# add_test()</a> command are also supported by basis_add_test() as arguments of
# the test command. For the argument specifying the test command itself, however,
# only the generator expression $<TARGET_FILE:tgt> is allowed. Alternatively,
# for this special argument, the name of the executable target can be supplied
# directly without the use of the $<TARGET_FILE:tgt> generator expression.
# See documentation of basis_process_generator_expressions() for details on
# the supported generator expressions.
#
# Example:
# @code
# basis_add_test (COMMAND $<TARGET_FILE:basis.testdriver> $<TARGET_FILE:myexe> ...)
# basis_add_test (COMMAND basis.testdriver $<TARGET_FILE:myexe> ...)
# @endcode
#
# @param [in] TEST_NAME Name of the test. If a source file is given
#                       as first argument, the test name is derived
#                       from the name of this source file and the source
#                       file is added to the list of sources which implement
#                       the test command.
# @param [in] ARGN      The following parameters are parsed:
# @par
# <table border="0">
#   <tr>
#     @tp @b COMMAND cmd [arg1 [arg2 ...]] @endtp
#     <td>The command to execute and optionally its arguments. The command
#         can be the name of an executable target (including imported targets),
#         or the name or path of an executable. Alternatively,
#         a test can be build from sources and the build executable
#         used as command. In this case, specify the sources using the
#         @p SOURCES argument. The command name @c cmd if given is used as
#         output name of the built executable. If you do not want to
#         specify the name of the output executable explicitly, but have
#         it derived from the @p TEST_NAME, do not specify the @p COMMAND
#         option and use the @p ARGS option instead to only specify the
#         arguments of the test command.</td>
#   </tr>
#   <tr>
#     @tp @b ARGS arg1 [arg2 ...] @endtp
#     <td>Arguments of the test command. If this option is given, the
#         specified arguments are appended to the arguments specified
#         already as part of the @p COMMAND option, if any.</td>
#   </tr>
#   <tr>
#     @tp @b WORKING_DIRECTORY dir @endtp
#     <td>The working directory of the test command. The generator expression
#         $<TARGET_FILE_DIR:tgt> can be used to specify a working directory
#         which corresponds to the output directory of a given target file.
#         Default: @c TESTING_OUTPUT_DIR / @p TEST_NAME. </td>
#   </tr>
#   <tr>
#     @tp @b CONFIGURATIONS @endtp
#     <td>If a CONFIGURATIONS option is given then the test will be executed
#         only when testing under one of the named configurations.</td>
#   </tr>
#   <tr>
#     @tp @b SOURCES file1 [file2 ...] @endtp
#     <td>The source files of the test. Use the @p UNITTEST option to specify
#         that the sources are an implementation of a unit test. In this case,
#         the default implementation of the main() function is added to the
#         build of the test executable. However, if this list contains a
#         file with the suffix <tt>-main</tt> or <tt>_main</tt> in the name,
#         the default implementation of the main() function is not used.
#         See the documentation of the @p UNITTEST option for further details.</td>
#   </tr>
#   <tr>
#     @tp @b LINK_DEPENDS file1|target1 [file2|target2 ...] @endtp
#     <td>Link dependencies of test executable build from sources.</td>
#   </tr>
#   <tr>
#     @tp @b NO_DEFAULT_MAIN @endtp
#     <td>Force that the implementation of the default main() function
#         is not added to unit tests even if neither of the given source
#         files has the suffix <tt>-main</tt> or <tt>_main</tt> in the
#         file name.</td>
#   </tr>
#   <tr>
#     @tp @b UNITTEST @endtp
#     <td>Specifies that the test is a unit test. In this case, the test
#         implementation is linked to the default unit testing framework
#         for the used programming language which is part of the BASIS
#         installation.</td>
#   </tr>
#   <tr>
#     @tp @b WITH_EXT @endtp
#     <td>Do not strip extension if test name is derived from source file name.</td>
#   </tr>
#   <tr>
#     @tp @b ARGN @endtp
#     <td>All other arguments are passed on to basis_add_executable() if
#         an executable target for the test is added.</td>
#   </tr>
# </table>
#
# @returns Adds build target for test executable if test source files
#          are given and/or adds a CTest test which executes the given
#          test command.
#
# @sa basis_process_generator_expressions()
# @sa http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:add_test
#
# @todo Make use of ExternalData module to fetch remote test data.
#
# @ingroup CMakeAPI
function (basis_add_test TEST_NAME)
  basis_sanitize_for_regex (R "${PROJECT_TESTING_DIR}")
  if (NOT CMAKE_CURRENT_SOURCE_DIR MATCHES "^${R}")
    message (FATAL_ERROR "Tests can only be added inside ${PROJECT_TESTING_DIR}!")
  endif ()

  # --------------------------------------------------------------------------
  # parse arguments
  CMAKE_PARSE_ARGUMENTS (
    ARGN
    "UNITTEST;NO_DEFAULT_MAIN;WITH_EXT;CLEAN_WORKING_DIRECTORY_BEFORE_TEST;CLEAN_WORKING_DIRECTORY_AFTER_TEST"
    "WORKING_DIRECTORY"
    "CONFIGURATIONS;SOURCES;LINK_DEPENDS;COMMAND;ARGS"
    ${ARGN}
  )

  if (BASIS_DEBUG AND BASIS_VERBOSE)
    message ("** basis_add_test():")
    message ("**   Test:      ${TEST_NAME}")
    message ("**   Command:   ${ARGN_COMMAND}")
    message ("**   Arguments: ${ARGN_ARGS}")
  endif ()

  list (LENGTH ARGN_COMMAND N)
  if (N GREATER 1)
    list (GET ARGN_COMMAND 0 CMD)
    list (REMOVE_AT ARGN_COMMAND 0)
    list (APPEND ARGN_ARGS ${ARGN_COMMAND})
    set (ARGN_COMMAND "${CMD}")
  endif ()

  # --------------------------------------------------------------------------
  # test name
  if (NOT ARGN_COMMAND AND NOT ARGN_SOURCES)
    get_filename_component (ARGN_SOURCES "${TEST_NAME}" ABSOLUTE)
    if (ARGN_WITH_EXT)
      basis_get_source_target_name (TEST_NAME "${TEST_NAME}" NAME)
      list (APPEND ARGN_UNPARSED_ARGUMENTS WITH_EXT) # pass on to basis_add_executable()
    else ()
      basis_get_source_target_name (TEST_NAME "${TEST_NAME}" NAME_WE)
    endif ()
  endif ()

  basis_check_test_name ("${TEST_NAME}")
  basis_make_test_uid (TEST_UID "${TEST_NAME}")

  if (BASIS_DEBUG)
    message ("** basis_add_test():")
    message ("**   Name:      ${TEST_NAME}")
    message ("**   Command:   ${ARGN_COMMAND}")
    message ("**   Arguments: ${ARGN_ARGS}")
    message ("**   Sources:   ${ARGN_SOURCES}")
  endif ()

  # --------------------------------------------------------------------------
  # build test executable
  set (LANGUAGE)
  if (ARGN_SOURCES)
    if (ARGN_UNITTEST)
      basis_get_source_language (LANGUAGE ${ARGN_SOURCES})
      if (NOT ARGN_NO_DEFAULT_MAIN)
        foreach (SOURCE ${ARGN_SOURCES})
          if (SOURCE MATCHES "-main\\.|_main\\.")
            set (ARGN_NO_DEFAULT_MAIN 1)
            break ()
          endif ()
        endforeach ()
      endif ()
      if (LANGUAGE MATCHES "CXX")
        if (NOT ARGN_NO_DEFAULT_MAIN)
          if (BASIS_TEST_MAIN_LIBRARY)
            list (APPEND ARGN_LINK_DEPENDS "${BASIS_TEST_MAIN_LIBRARY}")
          else ()
            message (FATAL_ERROR "Test ${TEST_NAME} added as UNITTEST without NO_DEFAULT_MAIN option,"
                                 " but BASIS_TEST_MAIN_LIBRARY not set. This library is part of the"
                                 " CMake BASIS installation. When you are using the CMake BASIS Modules"
                                 " without CMake BASIS installation, add GTest to TEST_DEPENDS and"
                                 " set BASIS_TEST_MAIN_LIBRARY to the value of GTEST_MAIN_LIBRARY"
                                 " before using the basis_add_test command.")
          endif ()
        endif ()
        if (BASIS_TEST_LIBRARY)
          list (APPEND ARGN_LINK_DEPENDS "${BASIS_TEST_LIBRARY}")
        else ()
         message (FATAL_ERROR "Test ${TEST_NAME} added as UNITTEST, but BASIS_TEST_LIBRARY not set."
                              " This library is part of the CMake BASIS installation. When you are"
                              " using the CMake BASIS Modules without CMake BASIS installation,"
                              " add GTest to TEST_DEPENDS and set BASIS_TEST_LIBRARY to the value"
                              " of GTEST_LIBRARY before using the basis_add_test command.")
        endif ()
      endif ()
    endif ()

    basis_add_executable (${TEST_NAME} ${ARGN_SOURCES} ${ARGN_UNPARSED_ARGUMENTS})
    if (ARGN_LINK_DEPENDS)
      basis_target_link_libraries (${TEST_NAME} ${ARGN_LINK_DEPENDS})
    endif ()

    if (ARGN_COMMAND)
      basis_set_target_properties (${TEST_NAME} PROPERTIES OUTPUT_NAME ${CMD})
    endif ()
    set (ARGN_COMMAND ${TEST_NAME})

    if (BASIS_DEBUG)
      message ("** Added test executable ${TEST_UID} (${TEST_NAME})")
      message ("**   Sources:           ${ARGN_SOURCES}")
      message ("**   Link dependencies: ${ARGN_LINK_DEPENDS}")
      message ("**   Options:           ${ARGN_UNPARSED_ARGUMENTS}")
    endif ()
  endif ()

  # --------------------------------------------------------------------------
  # add test
  message (STATUS "Adding test ${TEST_UID}...")

  if (NOT ARGN_WORKING_DIRECTORY)
    set (ARGN_WORKING_DIRECTORY "${TESTING_OUTPUT_DIR}/${TEST_NAME}")
  endif ()
  if (ARGN_WORKING_DIRECTORY MATCHES "^\\$<(.*):(.*)>$")
    if (NOT "${CMAKE_MATCH_1}" STREQUAL "TARGET_FILE_DIR")
      message (FATAL_ERROR "Invalid generator expression used for working directory."
                           " Only $<TARGET_FILE_DIR:tgt> can be used as argument"
                           " of the WORKING_DIRECTORY option.")
    endif ()
    basis_get_target_location (ARGN_WORKING_DIRECTORY "${CMAKE_MATCH_2}" PATH)
  else ()
    if (NOT IS_DIRECTORY "${ARGN_WORKING_DIRECTORY}")
      file (MAKE_DIRECTORY "${ARGN_WORKING_DIRECTORY}")
    endif ()
  endif ()
  set (OPTS "WORKING_DIRECTORY" "${ARGN_WORKING_DIRECTORY}")
  if (ARGN_CONFIGURATIONS)
    list (APPEND OPTS "CONFIGURATIONS")
    foreach (CONFIG ${ARGN_CONFIGURATIONS})
      list (APPEND OPTS "${CONFIG}")
    endforeach ()
  endif ()

  if (ARGN_COMMAND MATCHES "^\\$<(.*):(.*)>$")
    if (NOT "${CMAKE_MATCH_1}" STREQUAL "TARGET_FILE")
      message (FATAL_ERROR "Invalid generator expression used for test command."
                           " Only $<TARGET_FILE:tgt> can be used as first"
                           " argument of the COMMAND option.")
    endif ()
    basis_get_target_location (ARGN_COMMAND "${CMAKE_MATCH_2}" ABSOLUTE)
  else ()
    basis_get_target_uid (COMMAND_UID "${ARGN_COMMAND}")
    if (TARGET "${COMMAND_UID}")
      basis_get_target_location (ARGN_COMMAND "${COMMAND_UID}" ABSOLUTE)
    endif ()
  endif ()

  basis_process_generator_expressions (ARGN_ARGS ${ARGN_ARGS})

  if (BASIS_DEBUG)
    message ("** Add test ${TEST_UID}")
    message ("**   Command:    ${ARGN_COMMAND}")
    message ("**   Arguments:  ${ARGN_ARGS}")
    message ("**   Working in: ${ARGN_WORKING_DIRECTORY}")
  endif ()

  add_test (NAME ${TEST_UID} COMMAND ${ARGN_COMMAND} ${ARGN_ARGS} ${OPTS})

  # especially in case of C++ unit tests, if the linkage of the tests is done
  # incorrectly, no tests are actually run and the unit test passes
  # therefore, add this fail regular expression to identify such issues
  set_tests_properties (${TEST_UID} PROPERTIES FAIL_REGULAR_EXPRESSION "(\\[ *PASSED *\\]|Ran) 0 tests|No tests were found!!!")

  message (STATUS "Adding test ${TEST_UID}... - done")
endfunction ()

# ----------------------------------------------------------------------------
## @brief Add tests of default options for given executable.
#
# @par Default options:
# <table border="0">
#   <tr>
#     @tp <b>--helpshort</b> @endtp
#     <td>Short help. The output has to match the regular expression
#         "[Uu]sage:\n\s*\<executable name\>", where \<executable name\>
#         is the name of the tested executable.</td>
#   </tr>
#   <tr>
#     @tp <b>--help, -h</b> @endtp
#     <td>Help screen. Simply tests if the option is accepted.</td>
#   </tr>
#   <tr>
#     @tp <b>--version</b> @endtp
#     <td>Version information. Output has to include the project version string.</td>
#   </tr>
#   <tr>
#     @tp <b>--verbose, -v</b> @endtp
#     <td>Increase verbosity of output messages. Simply tests if the option is accepted.</td>
#   </tr>
# </table>
#
# @param [in] TARGET_NAME Name of executable or script target.
#
# @returns Adds tests for the default options of the specified executable.
function (basis_add_tests_of_default_options TARGET_NAME)
  basis_get_target_uid (TARGET_UID "${TARGET_NAME}")

  message (STATUS "Adding tests of default options for ${TARGET_UID}...")

  if (NOT TARGET "${TARGET_UID}")
    message (FATAL_ERROR "Unknown target ${TARGET_UID}.")
  endif ()

  # get executable name
  get_target_property (PREFIX      ${TARGET_UID} "PREFIX")
  get_target_property (OUTPUT_NAME ${TARGET_UID} "OUTPUT_NAME")
  get_target_property (SUFFIX      ${TARGET_UID} "SUFFIX")

  if (NOT OUTPUT_NAME)
    set (EXEC_NAME "${TARGET_UID}")
  endif ()
  if (PREFIX)
    set (EXEC_NAME "${PREFIX}${EXEC_NAME}")
  endif ()
  if (SUFFIX)
    set (EXEC_NAME "${EXEC_NAME}${SUFFIX}")
  endif ()

  # get absolute path to executable
  get_target_property (EXEC_DIR ${TARGET_UID} "RUNTIME_OUTPUT_DIRECTORY")

  # executable command
  set (EXEC_CMD "${EXEC_DIR}/${EXEC_NAME}")

  # test option: -v
  basis_add_test (${EXEC}VersionS "${EXEC_CMD}" "-v")

  set_tests_properties (
    ${EXEC}VersionS
    PROPERTIES
      PASS_REGULAR_EXPRESSION "${EXEC} ${PROJECT_VERSION}"
  )

  # test option: --version
  basis_add_test (${EXEC}VersionL "${EXEC_CMD}" "--version")

  set_tests_properties (
    ${EXEC}VersionL
    PROPERTIES
      PASS_REGULAR_EXPRESSION "${EXEC} ${PROJECT_VERSION}"
  )

  # test option: -h
  basis_add_test (${EXEC}HelpS "${EXEC_CMD}" "-h")

  # test option: --help
  basis_add_test (${EXEC}HelpL "${EXEC_CMD}" "--help")

  # test option: --helpshort
  basis_add_test (${EXEC}UsageL "${EXEC_CMD}" "--helpshort")

  set_tests_properties (
    ${EXEC}UsageL
    PROPERTIES
      PASS_REGULAR_EXPRESSION "[Uu]sage:(\n)( )*${EXEC_NAME}"
  )

  message (STATUS "Adding tests of default options for ${EXEC}... - done")
endfunction ()


## @}
# end of Doxygen group