How can I use Catch2 to test my CMake static library project? - c++

I'm writing a static library which contains some shared code between several projects. In order to verify that the code in this library functions properly I'd like to use Catch2 to do some unit testing on it.
Unfortunately, when attempting to run the tests I run into the problem that the compilation's output file is a shared library (.a), rather than an executable.
I'm sure I can create a separate project which uses the functions from my static library, and subsequently run tests that way, but ideally I'd like to keep the tests and build configurations as close as possible to one another.
So my question is:
what's the best way to set up my project such that I can use Catch2 for unit testing my static library code?
Here's my project's CMakeLists.txt file for reference:
project(sharedLib CXX)
find_package(OpenMP)
if (CMAKE_COMPILER_IS_GNUCC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp -lpthread -Wall -Wextra -Wpedantic -std=c++17")
endif()
if (MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fopenmp /W4 /std:c++latest")
endif()
include_directories (
src/
lib/Catch2/single_include/
)
file (GLOB_RECURSE LIBRARY_SOURCES src/*.cpp
src/*.c
tests/*.cpp)
add_library(${PROJECT_NAME} STATIC ${LIBRARY_SOURCES})
target_include_directories(${PROJECT_NAME} PUBLIC src/)

A common pattern for testing static libraries is to have a separate executable which contains all the tests, and then consumes your library. for example
file (GLOB_RECURSE TEST_SOURCES tests/*.cpp)
add_executable(my_lib_tests test_main.cpp ${TEST_SOURCES})
target_link_libraries(my_lib_tests PRIVATE sharedLib)
target_include_directories(my_lib_tests PRIVATE ../path/to/secret/impl/details)
Here I also have added some include some directories to implementation details of your shared lib which you may need to test, but don't want to expose to clients via a public header.
test_main.cpp need only be:
#define CONFIG_CATCH_MAIN
#include <catch2/catch.hpp>
Then you don't have to include things in your library's build that are unrelated to the library itself, speeding up compilation time for clients, while you can work from the perspective of the test fixture

Related

How to properly create cmake library for both program and test usage and compile it only once

I have code with one big directory of models which are going to be used in two binaries - the main program and tests. I want to put the models to the library which will compile once and then both main program and tests can use it without recompiling it again.
Here are my current directories:
root/
CMakeLists.cpp
src/
CMakeLists.txt
main.cpp
model/
/*a lot of cpp/hpp files*/
impl/
impl.cpp
impl.hpp (uses models)
test/
CMakeLists.txt
main.cpp
test.cpp (uses models and impl)
root/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(server)
set(CMAKE_CXX_STANDARD 14)
file(GLOB MODEL_SOURCES "src/model/*.cpp")
add_library(modelLibrary ${MODEL_SOURCES})
add_subdirectory(src)
add_subdirectory(test)
src/CMakeLists.txt
set(BINARY ${CMAKE_PROJECT_NAME})
file(GLOB_RECURSE SOURCES LIST_DIRECTORIES true *.h *.cpp)
set(SOURCES ${SOURCES})
add_executable(${BINARY} ${SOURCES})
include_directories(impl)
# can't see model files without this line
target_include_directories(${BINARY} PRIVATE ./model)
target_link_libraries(${BINARY} PUBLIC modelLibrary pistache pthread ssl crypto)
test/CMakeLists.txt
set(BINARY ${CMAKE_PROJECT_NAME}_test)
file(GLOB_RECURSE TEST_SOURCES LIST_DIRECTORIES false *.hpp *.cpp)
set(SOURCES ${TEST_SOURCES})
add_executable(${BINARY} ${TEST_SOURCES})
add_test(NAME ${BINARY} COMMAND ${BINARY})
# can't see model files without this line
target_include_directories(${BINARY} PRIVATE ../src/model)
target_link_libraries(${BINARY} modelLibrary gtest gtest_main pthread)
I have failed with using modelLibrary as only source. It still compiles the models two times. I'd like to achieve solution where models are compiled once and they are reachable from both - main program and test.
I believe the reason you see the model files compiled twice is due to the recursive glob in src/CMakeLists.txt.
file(GLOB_RECURSE SOURCES LIST_DIRECTORIES true *.h *.cpp)
This recursive glob will also walk into src/model and grab the *.cpp files there. So the model files are getting compiled as part of modelLibrary as well as part of the server executable.
One way to fix this would be to remove the recursive glob and create a separate CMakeLists.txt in src/model with the contents of:
# I wouldn't actually glob, see note below.
file(GLOB MODEL_SOURCES "*.cpp")
add_library(modelLibrary ${MODEL_SOURCES})
target_include_directories(modelLibrary PUBLIC ".")
One would need to make a call add_subdirectory() for this new CMakeLists in either the root or the src/CMakeLists.txt.
Notice the use of target_include_directories in the possible solution. I put that there as I noticed it was being repeated in the test and executable. Using PUBLIC on modelLibrary means that consumers get the include directory just by using target_link_libraries.
Note about globbing: from CMake docs on GLOB
Note We do not recommend using GLOB to collect a list of source files from your source tree. If no CMakeLists.txt file changes when a source is added or removed then the generated build system cannot know when to ask CMake to regenerate. The CONFIGURE_DEPENDS flag may not work reliably on all generators, or if a new generator is added in the future that cannot support it, projects using it will be stuck. Even if CONFIGURE_DEPENDS works reliably, there is still a cost to perform the check on every rebuild.
That note can be a bit hard to grok, but try to fully understand the consequences if you do decide to glob.

Recompile CMake library for every main file (and consider #define)

I'm working on a Websockets library for C++. My project has the library folder and a tests folder.
The tests folder contains many source files that each is compiled as a stand-alone executable and considered a test. The relevant CMakeLists for it looks like this:
file(GLOB TEST_SOURCES "src/*.cpp")
foreach(file ${TEST_SOURCES})
get_filename_component(_F_NAME ${file} NAME_WE)
add_executable(${_F_NAME} ${file})
target_link_libraries (${_F_NAME} ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries(${_F_NAME} tiny_websockets_lib Catch)
if(WIN32)
target_link_libraries(${_F_NAME} wsock32 ws2_32)
endif()
add_test(NAME ${_F_NAME} COMMAND ${_F_NAME})
endforeach()
Basically for every cpp file in src/ I create a new executable and link to it my library (here called tiny_websockets_lib).
The libraries' CMakeLists.txt looks like this:
file(GLOB_RECURSE tinyws_SOURCES
"src/*.cpp"
)
file(GLOB_RECURSE tinyws_HEADERS
"include/*.h"
"include/*.hpp"
)
include_directories(include/tiny_websockets)
add_library(tiny_websockets_lib STATIC ${tinyws_HEADERS} ${tinyws_SOURCES})
set_target_properties(tiny_websockets_lib PROPERTIES LINKER_LANGUAGE CXX)
target_include_directories(tiny_websockets_lib PUBLIC include)
The Issue:
In the library I have some conditional compilation flags. For example, this flag:
#define _WS_CONFIG_NO_TRUE_RANDOMNESS
can be defined and it will change the behivour of the library.
The problem is that the library only compiles once, so if my test looks like this:
#define _WS_CONFIG_NO_TRUE_RANDOMNESS
#include <library_stuff_with_conditional_compile_ifdefs>
int main() {
// tests
}
The library will not be compiled again, and the flag will not be considered.
TLDR: To my understanding the library is compiled once and linked multiple times. I would like the library to recompile and consider the main file for every tests.
You can see the actual project and structure here: https://github.com/gilmaimon/TinyWebsockets
Thank You.

How to generate a DLL linked to a static library with CMake

The main objective with this question is to write an CMakeLists.txt to generate a dynamic library, "containing/linked" a static library.
Let me create the scenario:
My C++ code is written in mycode.cpp
In mycode.cpp, I call some functions from libthirdparty.a (static library)
I want to generate libmylib.so (shared library) to be dynamically linked by others
libmylib.so must to "contain" libthirdparty.a
My attempt to write this script is at the lines bellow:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Werror -m64 -fPIC ")
add_executable(myapp mycode.cpp)
target_link_libraries(myapp thirdparty)
add_library(mylib SHARED myapp)
But of course this is not working and I would like some help to write it correctly.
For now, let's remove the myapp and focus only on the library you are trying to create.
That said, here is what you could do
cmake_minimum_required(VERSION 3.12)
project(AwesomeLib)
include(GenerateExportHeader)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
find_package(ThirdParty REQUIRED)
add_library(mylib SHARED mylib.cpp)
target_link_libraries(mylib PUBLIC ThirdParty)
# Note: If you library will be cross-platform, flag should be conditionally specified. Have a look at generator-expression
target_compile_options(mylib PRIVATE -Wall -Werror)
generate_export_header(mylib)
# TODO:
# * add install rules
# * generate config-file package
# * add tests
Notes:
generate_export_header will generate "mylib_export.h" header with the MYLIB_EXPORT macro for exporting symbols. See https://cmake.org/cmake/help/latest/module/GenerateExportHeader.html
to understand the idea behind find_package(ThirdParty REQUIRED), I recommend you read config-file package. See Correct way to use third-party libraries in cmake project
to learn more about generator expression. See https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html

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?
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)
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.
You could do like this :
In the current CMakeLists.txt, put these lines :
add_subdirectory(src)
add_subdirectory(test)
and then, in each directories add a CMakeLists.txt that link correctly sources to each files.
About test, I've heard that CMake can do test automation, but I don't really know how it works.
In my opinion the best solution is to create a library (shared or static) and two executables (one for the main program and one for the test main). After that you should link the library against the two applications.
In this answer I write down a explanation with a little example how you could managed the project with cmake.

Using CMake to statically link to a library outside of the project

I would like to use CMake to link my project to my shared library. The library is only shared between a handful of projects and is rather small, so I would really like to build it before it is linked. Building it every time seems a better idea than having to maintain an up-to-date precompiled version, because I ten to change it together with the project. It is separate, because it contains stuff I will almost certainly need in the next project.
How can I configure CMake to do it?
My current CMakeLists.txt for the relevant project looks like this:
find_package( Boost REQUIRED COMPONENTS unit_test_framework)
include_directories(${BaumWelch_SOURCE_DIR}/../../grzesLib/src
${BaumWelch_SOURCE_DIR}/src
${Boost_INCLUDE_DIRS})
if(CMAKE_COMPILER_IS_GNUCXX)
add_definitions(-g -std=c++11 -Wall -Werror -Wextra -pedantic -Wuninitialized)
endif()
# Create the unit tests executable
add_executable(
baumwelchtests stateindextest.cpp baumiterationtest.cpp baumwelchtest.cpp sampleparameters.cpp sdetest.cpp
# Key includes for setting up Boost.Test
testrunner.cpp
# Just for handy reference
exampletests.cpp
)
# Link the libraries
target_link_libraries( baumwelchtests ${Boost_LIBRARIES} baumwelchlib grzeslib)
but obviously the compilation fails with:
/usr/bin/ld: cannot find -lgrzeslib
You mentioned you'd like to build the library rather than use a precompiled version. If the library has a CMakeList, you should add it using add_subdirectory(path/to/the/library/source/directory). It will then become a subproject of your project and you can use names of its targets normally in your CMakeList.
Note that while the command is called add_subdirectory, it can be an arbitrary directory on disk; it doesn't have to be a subdirectory of the master project's source dir. In case it's not a subdirectory, you have to explicitly specify a binary directory for it as well. Example:
add_subdirectory(/path/to/the/library/source/directory subproject/grzeslib)
The second argument, if given as a relative path, is interpreted relative to CMAKE_CURRENT_BINARY_DIR.