CMake: how to add Boost.Test cases with relative directories?
Asked Answered
B

2

7

I have a working project with CMake and Boost.Test with a directory structure like this (pardon the ASCII art):

+-proj
|---CMakeLists.txt
|---build
|---test
|\----dir1
|   \----foo.cpp // contains one BOOST_AUTO_TEST_SUITE and several BOOST_AUTO_TEST_CASE
|    |---bar.cpp // contains one BOOST_AUTO_TEST_SUITE and several BOOST_AUTO_TEST_CASE
 \----dir2
    \----foo.cpp // contains one BOOST_AUTO_TEST_SUITE and several BOOST_AUTO_TEST_CASE
     |---bar.cpp // contains one BOOST_AUTO_TEST_SUITE and several BOOST_AUTO_TEST_CASE

I currently compile all source files into one big executable that I can run with CTest. My CMakeLists.txt looks like this:

file(GLOB_RECURSE test_cases FOLLOW_SYMLINKS "test/*.[h,c]pp")
add_executable(test_suite ${test_cases})
include_directories(${PROJECT_SOURCE_DIR} ${Boost_INCLUDE_DIRS})
target_link_libraries(test_suite ${Boost_LIBRARIES})
include(CTest)
add_test(test_runner test_suite)

I would like to compile each .cpp file into a separate executable, and add it separately as a test so that I can use the CTest regular expression machinery (especially the test exclusion which Boost.Test doesn't seem to have) to selectively run certain tests. However, I get a name conflict when CMake is generating build targets for foo/bar from dir1/dir2.

My question is: how can I mirror the entire directory tree under test to a similar tree under build so that there are no more name conflicts between the various executables and so that CTest can run them all?

Note: Renaming them in the source tree is not an option. I'd like to do a foreach() over the variable ${test_cases} (as explained in this answer), but I am having trouble to extract the relative directory and the file name, and port those to the build/ directory on a file-by-file basis.

UPDATE: In the end, I pieced together this script:

# get the test sources
file(GLOB_RECURSE test_sources RELATIVE ${PROJECT_SOURCE_DIR} *.cpp)

# except any CMake generated sources under build/
string(REGEX REPLACE "build/[^;]+;?" "" test_sources "${test_sources}")

# get the test headers
file(GLOB_RECURSE test_headers RELATIVE ${PROJECT_SOURCE_DIR} *.hpp)

# except any CMake generated headers under build/
string(REGEX REPLACE "build/[^;]+;?" "" test_headers "${test_headers}")

# compile against the test headers, the parent project, and the Boost libraries
include_directories(${PROJECT_SOURCE_DIR} ${ParentProject_include_dirs} ${Boost_INCLUDE_DIRS})

# calls enable_testing()
include(CTest)

foreach(t ${test_sources} )
  # get the relative path in the source tree
  get_filename_component(test_path ${t} PATH)

  # get the source name without extension
  get_filename_component(test_name ${t} NAME_WE)

  # concatenate the relative path and name in an underscore separated identifier
  string(REPLACE "/" "_" test_concat "${test_path}/${test_name}")

  # strip the leading "test_" part from the test ID
  string(REGEX REPLACE "^test_" "" test_id ${test_concat})

  # depend on the current source file, all the test headers, and the parent project headers
  add_executable(${test_id} ${t} ${test_headers} ${ParentProject_headers})

  # link against the Boost libraries
  target_link_libraries(${test_id} ${Boost_LIBRARIES})

  # match the relative path in the build tree with the corresponding one in the source tree 
  set_target_properties(${test_id} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${test_path})

  # add a test with executable in the relative path of the build tree
  add_test(${test_id} ${test_path}/${test_id})
endforeach()
Borszcz answered 31/5, 2013 at 12:39 Comment(0)
D
4

It is possible to specify a RELATIVE flag and a directory to a file( GLOB ... ) command. Although not mentioned directly in the documentation of file( GLOB ), this works for file( GLOB_RECURSE ... ) too. Note, I tested this on my windows setup. I don't known about *nix.

  1. Together with some get_filename_component calls with NAME_WE and/or PATH flags, it is now possible to reconstruct the name and the relative path of the cpp-file with respect to the globbing dir.
  2. Extracting a path and a name (without extension) is mostly similar to the answer by Massimiliano. In addition, I have used his suggestion to generate a unique testname with string( REGEX REPLACE ... ); replacing forward slashes by underscores.
  3. With a unique test-name, the executable can be generated and afterwards its output directory can be modified with set_target_properties.

Check this and this question for more info on modifying the output directory.

file( GLOB_RECURSE TEST_CPP_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp )

foreach( test_case ${TEST_CPP_SOURCES} )
    # Get the name without extension
    get_filename_component( test_name ${test_case} NAME_WE )
    # Get the path to the test-case, relative to the ${CMAKE_CURRENT_SOURCE_DIR} 
    # thanks to the RELATIVE flag in file( GLOB_RECURSE ... )
    get_filename_component( test_path ${test_case} PATH )

    message( STATUS "  name = " ${test_name} )
    message( STATUS "  path = " ${test_path} )
    # I would suggests constructing a 'unique' test-name
    string( REPLACE "/" "_" full_testcase "${test_name}/${test_path}" )

    # Add an executable using the 'unique' test-name
    message( STATUS "  added " ${full_testcase} " in " ${test_path} )
    add_executable( ${full_testcase} ${test_case} )
    # and modify its output paths. 
    set_target_properties( ${full_testcase} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${test_path} )
endforeach( test_case ${TEST_CPP_SOURCES} )
Deplorable answered 3/6, 2013 at 8:5 Comment(5)
ok, tnx will try this tonight on my (Linux) dev machine. As I wrote in the question, I'd like to be able to selective run or exclude certain tests using CTest. Do you recommend putting all the executables in the same directory or keeping a nested directory structure? In other words, can I pass directory path arguments to CTest or only file name regexes?Borszcz
Sorry, but I have no experience with CTest and don't what is possible there. But does it really matter? Even if CTest can not handle directory path arguments, perhaps you could opt for an output-name with the full generated test-name with underscores.Celindaceline
Just tested it, it does mostly what I want, thanks! One issue: when I call cmake .., it also adds build/CMakeFiles/2.8.10.1/CompilerIdCXX as a build target, and that seems off. However, when I try to modify the RELATIVE path to `${CMAKE_CURRENT_SOURCE_DIR}/test (with or without quotes) this issue does not go away. Any suggestions?Borszcz
Never mind that, it's explained here how to filter those files. I will continue to finetune a bit more and assign the bounty when I'm fully satisfied :-)Borszcz
I updated my question with my final script with some polishing and adjustment for my project (it is a unit-testing project for a C++ template library). It works splendidly, thanks again for your answer!Borszcz
M
5

A possible solution to disambiguate names in a directory structure like the one you have using a FOREACH() over ${test_cases} may be:

# Set Cmake version and policy
CMAKE_MINIMUM_REQUIRED( VERSION 2.8.7 )
CMAKE_POLICY( VERSION 2.8.7 )

PROJECT( DUMMY CXX )

FILE( GLOB_RECURSE test_cases FOLLOW_SYMLINKS "test/*.[h,c]pp" )

FOREACH( case ${test_cases} )
  ## Get filename without extension
  GET_FILENAME_COMPONENT(case_name_we ${case} NAME_WE)
  ## Get innermost directory name
  GET_FILENAME_COMPONENT(case_directory ${case} PATH)
  GET_FILENAME_COMPONENT(case_innermost ${case_directory} NAME_WE)
  ## Construct executable name
  SET( exe_name "${case_innermost}_${case_name_we}")
  ## Construct test name
  SET( test_name "${exe_name}_test")
  ## Add executable and test
  ADD_EXECUTABLE( ${exe_name} ${case} )
  ADD_TEST( ${test_name} ${exe_name} )
ENDFOREACH()

As you can see this CMakeLists.txt creates 4 distinct test/executable couples.

Mathematics answered 3/6, 2013 at 6:13 Comment(3)
BTW, is it also possible to translate dir/sub/file.cpp to a an executable in camel case form: DirSubFile?Borszcz
You may try using the string command (REGEX REPLACE) with an appropriate regular expression before concatenating the stringsMathematics
thanks again for your answer, but I will most likely go with @Andre 's solution since he uses the full relative path which is more convenient for my project.Borszcz
D
4

It is possible to specify a RELATIVE flag and a directory to a file( GLOB ... ) command. Although not mentioned directly in the documentation of file( GLOB ), this works for file( GLOB_RECURSE ... ) too. Note, I tested this on my windows setup. I don't known about *nix.

  1. Together with some get_filename_component calls with NAME_WE and/or PATH flags, it is now possible to reconstruct the name and the relative path of the cpp-file with respect to the globbing dir.
  2. Extracting a path and a name (without extension) is mostly similar to the answer by Massimiliano. In addition, I have used his suggestion to generate a unique testname with string( REGEX REPLACE ... ); replacing forward slashes by underscores.
  3. With a unique test-name, the executable can be generated and afterwards its output directory can be modified with set_target_properties.

Check this and this question for more info on modifying the output directory.

file( GLOB_RECURSE TEST_CPP_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp )

foreach( test_case ${TEST_CPP_SOURCES} )
    # Get the name without extension
    get_filename_component( test_name ${test_case} NAME_WE )
    # Get the path to the test-case, relative to the ${CMAKE_CURRENT_SOURCE_DIR} 
    # thanks to the RELATIVE flag in file( GLOB_RECURSE ... )
    get_filename_component( test_path ${test_case} PATH )

    message( STATUS "  name = " ${test_name} )
    message( STATUS "  path = " ${test_path} )
    # I would suggests constructing a 'unique' test-name
    string( REPLACE "/" "_" full_testcase "${test_name}/${test_path}" )

    # Add an executable using the 'unique' test-name
    message( STATUS "  added " ${full_testcase} " in " ${test_path} )
    add_executable( ${full_testcase} ${test_case} )
    # and modify its output paths. 
    set_target_properties( ${full_testcase} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${test_path} )
endforeach( test_case ${TEST_CPP_SOURCES} )
Deplorable answered 3/6, 2013 at 8:5 Comment(5)
ok, tnx will try this tonight on my (Linux) dev machine. As I wrote in the question, I'd like to be able to selective run or exclude certain tests using CTest. Do you recommend putting all the executables in the same directory or keeping a nested directory structure? In other words, can I pass directory path arguments to CTest or only file name regexes?Borszcz
Sorry, but I have no experience with CTest and don't what is possible there. But does it really matter? Even if CTest can not handle directory path arguments, perhaps you could opt for an output-name with the full generated test-name with underscores.Celindaceline
Just tested it, it does mostly what I want, thanks! One issue: when I call cmake .., it also adds build/CMakeFiles/2.8.10.1/CompilerIdCXX as a build target, and that seems off. However, when I try to modify the RELATIVE path to `${CMAKE_CURRENT_SOURCE_DIR}/test (with or without quotes) this issue does not go away. Any suggestions?Borszcz
Never mind that, it's explained here how to filter those files. I will continue to finetune a bit more and assign the bounty when I'm fully satisfied :-)Borszcz
I updated my question with my final script with some polishing and adjustment for my project (it is a unit-testing project for a C++ template library). It works splendidly, thanks again for your answer!Borszcz

© 2022 - 2024 — McMap. All rights reserved.