Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CMake Add (Test) Executable

I would like to create two executables: one executable for the application, and one for the testing of the application. To that end, I have the following in my CMakeLists.txt file:

include_directories(include)

file(GLOB SOURCE "src/*.cc")
file(GLOB TEST "test/*.cc")

add_executable(interest_calc ${SOURCE})
add_executable(interest_calc_test "src/interest_calc.cc" ${TEST})

Since both src and test directories contain main functions, I have to manually add source files to the "test" executable. Is there another, non-manual, way to add required source files to the "test" executable?

Further, is there a better way to test functionality than creating a separate test executable? If so, what/how?

like image 521
Ari Avatar asked Jan 23 '17 17:01

Ari


2 Answers

One way to improve your process would be to pull the guts of your executable into a library, then have a nominal "main" executable which just calls into your library and a "test" executable which exercises the library however you want to test it.

This way, any changes you need to make go into the library and the executable build process is untouched.

Edit to show CMake with your example:

include_directories(include)

file(GLOB SOURCE "src/*.cc")

# Remove main from library, only needed for exec.
list(REMOVE_ITEM SOURCE "main.cc")

file(GLOB TEST "test/*.cc")

add_library(interest_calc_lib STATIC ${SOURCE})
add_executable(interest_calc "main.cc")
target_link_libraries(interest_calc interest_calc_lib)
add_executable(interest_calc_test ${TEST})
target_link_libraries(interest_calc_test interest_calc_lib)
like image 127
mascoj Avatar answered Oct 02 '22 20:10

mascoj


There are already some good answers from Soeren and mascoj but I would like to give a more concrete recommendation.

When you already have a CMakeLists.txt for your executable and you like to add testing, I recommend adding a static dummy library. This library can have all the sources of the executable except the main method (it may be easiest to single out the main method in a separate file if you do not have that already). Using a static library will give you two benefits:

  • The final executable will behave exactly as your current one, so there is no need to deal with distribution of a new, shared library
  • You do not need to deal with exporting symbols or throwing exceptions across shared object boundaries

The changes to your CMakeLists.txt can be quite small. I will give an example here, assuming you use cmake 3.0 or newer. First, an example of CMakeLists.txt before adding the dummy library:

project(MyProject)
set(SOURCES src/First.cc src/Second.cc src/Third.cc)

add_executable(${PROJECT_NAME} ${SOURCES} src/Main.cc)
target_include_directories(${PROJECT_NAME}
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${CMAKE_CURRENT_SOURCE_DIR}/src
    ${CMAKE_CURRENT_BINARY_DIR})
target_compile_options(${PROJECT_NAME}
    $<$<CXX_COMPILER_ID:GNU>:-Wall;-pedantic)
target_compile_definitions(${PROJECT_NAME}
    $<$<CONFIG:Debug>:DEBUG;_DEBUG>)
set_target_properties(${PROJECT_NAME}
    PROPERTIES CXX_STANDARD 14)
target_link_libraries(${PROJECT_NAME}
    Threads::Threads)

To add the dummy library and testing, you need to introduce a new target with a different name. I choose here to use ${PROJECT_NAME}_lib because this will be very non-intrusive on the CMakeLists.txt. Here is the updated version. Notice the use of ${PROJECT_NAME}_lib in place of ${PROJECT_NAME} in almost all places. Most properties are now passed down to the executable by making them PUBLIC. Only calls to set_target_properties() are not transitive and must be duplicated for library and executable.

project(MyProject)
set(SOURCES src/First.cc src/Second.cc src/Third.cc)

add_library(${PROJECT_NAME}_lib STATIC ${SOURCES})
add_executable(${PROJECT_NAME} src/Main.cc)
target_include_directories(${PROJECT_NAME}_lib PUBLIC
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${CMAKE_CURRENT_SOURCE_DIR}/src
    ${CMAKE_CURRENT_BINARY_DIR})
target_compile_options(${PROJECT_NAME}_lib PUBLIC
    $<$<CXX_COMPILER_ID:GNU>:-Wall;-pedantic)
target_compile_definitions(${PROJECT_NAME}_lib PUBLIC
    $<$<CONFIG:Debug>:DEBUG;_DEBUG>)
set_target_properties(${PROJECT_NAME}_lib
    PROPERTIES CXX_STANDARD 14)
set_target_properties(${PROJECT_NAME}
    PROPERTIES CXX_STANDARD 14)
target_link_libraries(${PROJECT_NAME}
    ${PROJECT_NAME}_lib
    Threads::Threads)

Now you can link your tests against ${PROJECT_NAME}_lib with a different main method.

like image 31
emmenlau Avatar answered Oct 02 '22 21:10

emmenlau