3

I have a project structured like:

|--assets/
|--core/
    |--deps/
        |--Catch2/
             |--win32/
                  |--# Have Catch2 library installed here
    |--include/
         |--# Nothing here
    |--src/
        |--sample.cpp # No content in this file
    |--tests/
         |--test.cpp
    |--CMakeLists.txt
|--main.cpp
|--CMakeLists.txt

The top level CMakeLists.txt content is:

cmake_minimum_required (VERSION 3.8)

set_property(GLOBAL PROPERTY USE_FOLDERS ON)
project("My.Project")

add_executable(MyProject main.cpp)

# Copy all DLLs if windows:
if(WIN32)
    file(GLOB_RECURSE DYNAMIC_LIBS "${CMAKE_CURRENT_SOURCE_DIR}/*.dll")
    foreach(dll ${DYNAMIC_LIBS})
        add_custom_command(TARGET AZTEC_EDITOR POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E copy
            ${dll} $<TARGET_FILE_DIR:AZTEC_EDITOR>)
    endforeach()
else(APPLE)
endif()

add_subdirectory(core)
target_link_libraries(MyProject MyLib)

The CMakeLists.txt file in the "core" folder is:

file(GLOB HEADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h")
file(GLOB SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
file(GLOB TEST_FILES "${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp")

add_library(MyLib ${HEADER_FILES} ${SOURCE_FILES} ${TEST_FILES})

target_include_directories(MyLib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")

# Copy all DLLs if windows:
if(WIN32)
    file(GLOB_RECURSE DYNAMIC_LIBS "${CMAKE_CURRENT_SOURCE_DIR}/*.dll")
    foreach(dll ${DYNAMIC_LIBS})
        add_custom_command(TARGET AZTEC_EDITOR_CORE POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E copy
            ${dll} $<TARGET_FILE_DIR:AZTEC_EDITOR_CORE>)
    endforeach()
else(APPLE)
endif()

# Catch2 stuff:
if(WIN32)
    find_package(Catch2 REQUIRED PATHS "${CMAKE_CURRENT_SOURCE_DIR}/deps/catch2/win32")
    target_link_libraries(MyLib Catch2::Catch2)
endif()

include(CTest)
include(Catch)
catch_discover_tests(MyLib)

My test.cpp content (from Catch2 documentation and this test should fail):

#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"

unsigned int Factorial(unsigned int number)
{
    return number <= 1 ? number : Factorial(number - 1)*number;
}

TEST_CASE("Factorials are computed", "[factorial]")
{
    REQUIRE(Factorial(1) == 2); // Should fail here.
    REQUIRE(Factorial(2) == 2);
    REQUIRE(Factorial(3) == 6);
    REQUIRE(Factorial(10) == 3628800);
}

When I generate the Visual Studio files using cmake -G "Visual Studio 15" . -B .\build, usually, when the tests are discovered, I see a project grouped under "CMakePredefinedTargets" called "RUN_TESTS". However, I can't see this project anymore.

Also, when I build the project (using Visual Studio 2017), the tests are not running. Please help. Thanks.

kovac
  • 4,945
  • 9
  • 47
  • 90
  • 4
    Try adding `enable_testing()` in the root `CMakeLists.txt` (https://cmake.org/cmake/help/latest/command/enable_testing.html). – Stephen Newell Sep 15 '19 at 12:57
  • @StephenNewell That worked :D Now I can see the RUN_TESTS project. There's one thing (I think this is related to Catch2 itself, but may be you can help?). Though the RUN_TESTS is now available, when I build it, it actually says no tests are discovered. I had to change the CMakeLists.txt in "core" from `add_library(MyLib ${HEADER_FILES} ${SOURCE_FILES} ${TEST_FILES})` to `add_executable(MyLib ${HEADER_FILES} ${SOURCE_FILES} ${TEST_FILES})`. Any idea how I can keep `add_library()`? Otherwise, may be you can post comment as ans, so I can accept it. Thanks. – kovac Sep 15 '19 at 13:05
  • @swdon: A **test** is something you could *run*. One may run an *executable* but how do you expect to run a *library*? – Tsyvarev Sep 15 '19 at 13:30
  • @Tsyvarev I understand. But a library is something that one would need to test. Exporting a library also as an executable just for unit tests seems strange. I come from a C# background, and usually we do not publish a library as an executable just for testing purposes. One way I might expect it to work is by creating a separate test runner executable that references the library and then run the tests (I tried this but it did not work - is this a feasible approach with CMake and unit tests)? – kovac Sep 15 '19 at 13:33
  • Your test source file should probably not be included in the `add_library()` call, but rather by itself in a call like this: `add_executable(Test1 tests/test.cpp)`. This will make a test *executable* to test your library. When using CTest, you should use [`add_test()`](https://cmake.org/cmake/help/latest/command/add_test.html) to add a test to be run by CTest. [Here](https://stackoverflow.com/a/14447765/3987854) is a good example of typical CTest usage. – Kevin Sep 15 '19 at 15:34
  • @squareskittles Thank you! Your `add_executable()` fixed it. – kovac Sep 15 '19 at 15:50
  • @swdon If possible, please post an answer with the fixed CMake files so future viewers can see what fixed the issue. – Kevin Sep 16 '19 at 02:20

1 Answers1

2

Based on the comments by @Stephen Newell and @ squareskittles, my final modified working CMakeLists.txt files are as follows.

I added enable_testing() to the root file. This makes the RUN_TESTS project visible in Visual Studio. I build this project when I want to run the tests:

cmake_minimum_required (VERSION 3.8)

set_property(GLOBAL PROPERTY USE_FOLDERS ON)
project("My.Project")

enable_testing()

add_executable(MyProject main.cpp)

# Copy all DLLs if windows:
if(WIN32)
    file(GLOB_RECURSE DYNAMIC_LIBS "${CMAKE_CURRENT_SOURCE_DIR}/*.dll")
    foreach(dll ${DYNAMIC_LIBS})
        add_custom_command(TARGET AZTEC_EDITOR POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E copy
            ${dll} $<TARGET_FILE_DIR:AZTEC_EDITOR>)
    endforeach()
else(APPLE)
endif()

add_subdirectory(core)
target_link_libraries(MyProject MyLib)

However, just adding this line still left with the issue that, even though the RUN_TESTS was there, it was not discovering the unit tests because the CMake-Catch2 integration expects an executable, not a library to run the tests. So, I added a separate target like in CMakeLists.txt file in the core folder:

file(GLOB HEADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h")
file(GLOB SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")

add_library(MyLib ${HEADER_FILES} ${SOURCE_FILES})

target_include_directories(MyLib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")

# Copy all DLLs if windows:
if(WIN32)
    file(GLOB_RECURSE DYNAMIC_LIBS "${CMAKE_CURRENT_SOURCE_DIR}/*.dll")
    foreach(dll ${DYNAMIC_LIBS})
        add_custom_command(TARGET AZTEC_EDITOR_CORE POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E copy
            ${dll} $<TARGET_FILE_DIR:AZTEC_EDITOR_CORE>)
    endforeach()
else(APPLE)
endif()

# Catch2 stuff:
file(GLOB TEST_FILES "${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp")
add_executable(MyLibTests ${TEST_FILES})

if(WIN32)
    find_package(Catch2 REQUIRED PATHS "${CMAKE_CURRENT_SOURCE_DIR}/deps/catch2/win32")
    target_link_libraries(MyLibTests Catch2::Catch2)
endif()

include(CTest)
include(Catch)
catch_discover_tests(MyLibTests)

Since the example given in the Catch2 documentation is fairly minimal hope this will help someone trying to integrate Catch2 with a more complex project structure. Thanks for all the help from the commentators.

kovac
  • 4,945
  • 9
  • 47
  • 90
  • Does this truly work on 'APPLE'? a) I would think you still need the "find_package(...)' and 'target_link_libraries(...) calls'. b) people seem to have issue with the 'catch_discover_tests(...)' call on a machine with an Apple Silicon chips, i.e. M1, M2,... – AudioDroid Oct 03 '22 at 14:26