1

I wanted to write a little Unit-test system with CMake that I can easily copy and paste in all my projects so I came up with this:

include(CMakeParseArguments)
set(UNIT_TEST "unit_tests")
add_custom_target(${UNIT_TEST} ALL VERBATIM)

function(add_unit_test dependency)
    cmake_parse_arguments(UT_ "" "NAME" "" ${ARGN})
    if(NOT ${UT_NAME})
        set(${UT_NAME} ${ARG0})
    endif()

    add_test(${ARGN})
    add_dependencies(${UNIT_TEST} ${dependency})
    add_custom_command(TARGET ${UNIT_TEST}
                   COMMENT "Run tests"
                   POST_BUILD COMMAND ctest
                   ARGS -R ${UT_NAME} --output-on-failures
                   WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
                   VERBATIM)
endfunction(add_unit_test)

I hoped that it would work fine like that and run all unit tests I would add in my project by calling add_unit_test(dep ...) with a dependency to compile before and then the same arguments as for add_test(...). In reality this error shows up:

CMake Warning (dev) at cmake/testing.cmake:13 (add_custom_command):
  Policy CMP0040 is not set: The target in the TARGET signature of
  add_custom_command() must exist.  Run "cmake --help-policy CMP0040" for
  policy details.  Use the cmake_policy command to set the policy and
  suppress this warning.

  The target name "unit_tests" is unknown in this context.
Call Stack (most recent call first):
  source/test/CMakeLists.txt:10 (add_unit_test)
This warning is for project developers.  Use -Wno-dev to suppress it.

Why exactly is the target unknown at this point? include(cmake/testing.cmake) is the first thing I call after cmake_minimum_required in my project build script, so it can't be because add_custom_target(${UNIT_TEST} ALL VERBATIM) hasn't been called yet.

Is there a way I can add a custom command to the UNIT_TEST target?

WorldSEnder
  • 4,875
  • 2
  • 28
  • 64

1 Answers1

3

Just came across your question and tried to reproduce your problem.

My first recommendation would be: if you call enable_testing() you directly get a "Run Tests" target generated by CMake. See CMake: Testing with CTest:

Once you have built the project, you can execute all tests via

make test

with Makefile generators, or by rebuilding the RUN_TESTS target in your IDE. Internally this runs CTest to actually perform the testing; you could just as well execute

ctest

in the binary directory of your build.

Debugging your given Code

If I name a valid target as the first parameter of your add_unit_test() I don't get the CMP0040 error. So just check again that you give a valid target.

But your script has some errors in it. Here is a working version:

include(CMakeParseArguments)

enable_testing()

set(UNIT_TEST "unit_tests")
add_custom_target(${UNIT_TEST} ALL)

function(add_unit_test dependency)
    cmake_parse_arguments(UT "" "NAME" "COMMAND" ${ARGN} )
    if("${UT_NAME}" STREQUAL "")
        set(${UT_NAME} "${ARGV1}")
    endif()
    add_test(${ARGN})
    add_dependencies(${UNIT_TEST} ${dependency})
    add_custom_command(TARGET ${UNIT_TEST}
                   COMMENT "Run tests"
                   POST_BUILD COMMAND ctest
                   ARGS -C $<CONFIGURATION> -R "^${UT_NAME}$" --output-on-failures)
endfunction(add_unit_test)
  • Added enable_testing()
  • Removed the trailing _ of UT in cmake_parse_arguments(UT ...)
  • Compare for empty string with STREQUAL ""
  • Added a V to ARGV1 and going for the second parameter (index 0 is dependency)
  • Removed not needed VERBATIM
  • Added needed -C $<CONFIGURATION> parameter (see How to run ctest after building my project with cmake)
  • Changed the RegEx to "^${UT_NAME}$" for exact name matching

Moving the above code into a file called UnitTest.cmake I have successfully tested with the following main CMakeLists.txt:

cmake_minimum_required(VERSION 2.8)
project(Example CXX)

include(${CMAKE_CURRENT_LIST_DIR}/UnitTest.cmake)

file(WRITE foo.cc "#include <windows.h>\nint main() {\nreturn 0;\n}")

add_executable(MyTest foo.cc)
add_unit_test(MyTest NAME SomeTest COMMAND $<TARGET_FILE:MyTest>)

add_executable(MyTest2 foo.cc)
add_unit_test(MyTest2 NAME SomeTest2 COMMAND $<TARGET_FILE:MyTest2>)
Community
  • 1
  • 1
Florian
  • 39,996
  • 9
  • 133
  • 149