Running unit tests during build of Windows DLL with CMake - c++

I have a C++ library that is built with cmake. The library is tested by unit tests which I would like to run as part of the build. I want the tests to run when the library or unit test source changes.
I found some help in past posts on the cmake mailing list:
http://www.cmake.org/pipermail/cmake/2010-January/034419.html
This approach, using add_custom_command() on the unit test program, works on Linux when the library is shared or static and on Windows when the libary is static. The problem is building the library shared on Windows. In that case, the test runs in the initial build, but doesn't run when the library changes.
Here is an example that illustrates the problem:
CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(xyz)
SET(BUILD_SHARED_LIBS ON)
#----------- build library
if(${BUILD_SHARED_LIBS})
add_definitions(-DXYZ_SHARED)
endif(${BUILD_SHARED_LIBS})
add_library(${PROJECT_NAME} xyz.cpp xyz.h)
#----------- build unit tests program
add_executable(${PROJECT_NAME}_unit_tests unit_tests.cpp)
target_link_libraries(${PROJECT_NAME}_unit_tests ${PROJECT_NAME})
#----------- run unit tests program after it is built
add_custom_command(TARGET ${PROJECT_NAME}_unit_tests
POST_BUILD COMMAND ${PROJECT_NAME}_unit_tests)
xyz.h
#pragma once
#ifdef XYZ_SHARED
#ifdef _WIN32
#ifdef xyz_EXPORTS
#define XYZ_EXPORT __declspec(dllexport)
#else
#define XYZ_EXPORT __declspec(dllimport)
#endif
#else //_WIN32
#define XYZ_EXPORT
#endif //_WIN32
#else //XYZ_SHARED
#define XYZ_EXPORT
#endif //XYZ_SHARED
XYZ_EXPORT bool xyz_return_true();
xyz.cpp
#include "xyz.h"
XYZ_EXPORT bool xyz_return_true()
{
return true;
}
unit_tests.cpp
#include <iostream>
#include "xyz.h"
int main(int argc, char *argv[])
{
using namespace std;
cout << "running unit tests: ";
if (xyz_return_true()) {
//no error
cout << "pass" << endl;
return 0;
}
//error
cout << "fail" << endl;
return 1;
}
If I build on Windows, then change xyz.cpp's function to return false and build again, the tests aren't run. What can I do to make the tests run in this case?
I think the tests aren't being run because at build time xyz_unit_tests.exe only depends on the import library (xyz.lib). The import library doesn't change if the library interface doesn't change.
I'm using Visual Studio 10 and cmake 2.8.11.2.

You could trigger a test rebuild by deleting the test executable as a post-build event of the library. So, for example:
if(WIN32 AND BUILD_SHARED_LIBS)
#----------- trigger rebuild of tests if lib has changed
get_target_property(TestPath ${PROJECT_NAME}_unit_tests LOCATION)
add_custom_command(TARGET ${PROJECT_NAME}
POST_BUILD COMMAND ${CMAKE_COMMAND} -E remove ${TestPath}
COMMENT "Removing ${TestPath}")
endif()
You have to use the LOCATION target property rather than a generator expression in the COMMAND argument since that doesn't introduce a cyclic dependency in CMake's dependency graph. Ideally, we'd have the COMMAND argument as:
COMMAND ${CMAKE_COMMAND} -E remove $<TARGET_FILE:${PROJECT_NAME}_unit_tests>
but this would cause the library to depend on the executable, and clearly the target_link_libraries call causes the exe to depend on the lib.
There's probably a better way to make the test executable go out-of-date rather than just deleting it; it seems a bit brute-force to me. But it should work.

Related

Using CMake targets in Visual Studio projects

I am pretty comfortable with CMake when used in Linux. That is, I use CMake to logically group my code into "targets" and abstract such things as "include directories" and other dependencies. Say, inside a large project my modus operandi would be to add_library(..SHARED) for a bunch of related files that provide one logically coherent unit, and target_link_libraries(...) to it from somewhere else.
(Of course, idiosyncrasies of CMake is another topic. I won't digress to discussions about e.g. its syntax.)
Now, for Windows 10. I fired up Visual Studio 2019 Community with the MSVC toolchain (that is, not WSL/WSL2), and created a CMake Project using VS's "New Project" wizard.
It has created for me some boilerplate code, and the whole project tree looks as below:
In this image, EnRouteGeneratedLibrary is something I added as the next step. Inside it, I have one *.hpp file:
#pragma once
template<typename T>
class __declspec(dllexport) SampleTemplateKlass {
public:
T sum(T x, T y) { return x + y; }
};
template class __declspec(dllexport) SampleTemplateKlass<int>;
and the CMakeLists.txt's contents are as follows:
add_library(${TRG} SHARED ${CMAKE_CURRENT_SOURCE_DIR}/SampleTemplateKlass.hpp)
# set_target_sources(${TRG} PUBLIC SampleTemplateKlass.hpp)
set_target_properties(${TRG} PROPERTIES LINKER_LANGUAGE CXX)
target_include_directories(${TRG} PUBLIC .)
set(WINDOWS_EXPORT_ALL_SYMBOLS 1)
Finally, in when building a simple "Hello World" executable, I have the following:
//
#include "SampleProjectWithDll.h"
#include "SampleTemplateKlass.hpp"
using namespace std;
int main()
{
cout << "Hello CMake." << endl;
SampleTemplateKlass<int> tmp;
std::cout << tmp.sum(42, 314) << std::endl;
return 0;
}
(This is the boilerplate code created by VS + my additions).
I link as follows:
# CMakeList.txt : CMake project for SampleProjectWithDll, include source and define
# project specific logic here.
#
cmake_minimum_required (VERSION 3.8)
add_subdirectory(EnRouteGeneratedLibrary)
# Add source to this project's executable.
add_executable (SampleProjectWithDll "SampleProjectWithDll.cpp" "SampleProjectWithDll.h")
target_link_libraries(SampleProjectWithDll PUBLIC SampleLibrary)
# TODO: Add tests and install targets if needed.
When building all this, I get two errors,
LNK2001 unresolved external symbol _DllMainCRTStartup and LNK1120 1 unresolved externalswith SampleLibrary.dll in the "File" field of the error panel.
The question is, how to make this simple "Hello World" compile and execute under Windows,
using the CMake infrastructure?

Catch2 test don't work when building application with CMake

I'm trying to test my program by using Catch2 tests and build the application using CMake. The program works and the application gets build when the Catch2 tests are not implemented.
Once I implemented the Catch2 tests, the application won't build anymore. I included #define CONFIG_CATCH_MAIN and #include "catch.hpp" into main.cpp.
But I always get something like this when I try to build the application with the tests included:
CMakeFiles\intent_recognition.dir/objects.a(main.cpp.obj):main.cpp:(.text+0x43d): undefined reference to Catch::StringRef::StringRef(char const*)' CMakeFiles\intent_recognition.dir/objects.a(main.cpp.obj):main.cpp:(.text+0x4b9): undefined reference to Catch::AssertionHandler::AssertionHandler(Catch::StringRef const&, Catch::SourceLineInfo const&, Catch::StringRef, Catch::ResultDisposition::Flags)'
I don't know what's going...
My main.cpp file looks like this:
#define CONFIG_CATCH_MAIN
#include "catch.hpp"
#include <iostream>
#include <string>
#include "Intent.h"
std::string func (std::string input)
{
std::cout << input << std::endl;
Intent IntentObj = Intent(input); // create Intent Object with input
IntentObj.analyze();
return IntentObj.result();
}
TEST_CASE("Get Weather", "[func]")
{
REQUIRE( func("What is the weather like today?") == "Intent: Get Weather" );
REQUIRE( func("Tell me what the weather is like.") == "Intent: Get Weather") ;
REQUIRE( func("I want to know the weather for tomorrow") == "Intent: Get Weather" );
REQUIRE( func("Can you tell me the weather for Wednesday?") == "Intent: Get Weather") ;
}
My CMakeLists.txt looks like this:
cmake_minimum_required(VERSION 3.20.2)
project(intent_recognition)
add_executable(intent_recognition main.cpp)
Having a quick look at the Catch2 tutorial you are at least missing the specification of the Catch2 library to link.
The tutorial states that you are at least need to have this code which your file above seems to miss:
find_package(Catch2 REQUIRED)
add_executable(tests test.cpp)
target_link_libraries(tests PRIVATE Catch2::Catch2)
The target_link_libraries... is missing in your example. That's probably why you are getting those linker errors.
The find_package will introduce the library under the name Catch2::Catch2 which you can then link to your test executable.
If the find_package command is not working in your setup, you may need to check how you did install Catch2.
I'm assuming that you want to use Catch2 as a header only library. The problem may be because you are including include/catch.hpp instead of single_include/catch2/catch.hpp.

Minimalistically building a test for a simple header using CMake

I have a C++ header which contains a class with a method like below. Lets say its a header only library I have.
// MyHeader.hpp
#pragma once
#include <string>
namespace myheaderonlylibrary {
class MyClass {
public:
unsigned int Func1(const std::string& str) {
if (!str.empty())
return 10;
else
return 0;
}
};
}
To test the above method I heve the following gtest source file inside a folder called unittest which is like the convention followed.
// MyTests.cpp
#include <limits.h>
#include "gtest/gtest.h"
#include "../MyHeader.hpp"
TEST(MyTest, Test_Something) {
myheaderonlylibrary::MyClass my_object;
unsigned int expected_value = 0;
EXPECT_EQ(expected_value, my_object.Func1(""));
}
All I want to do is build the test using Cmake and run the test. So, following is my CMakeLists.txt.
// CMakeLists.txt
cmake_minimum_required(VERSION 3.4)
set(MyheaderOnlyLibrary_HEADERS
MyHeader.hpp
)
set(MyheaderOnlyLibrary_unittest
unittest/MyTests.cpp
)
find_package(GTest REQUIRED)
enable_testing ()
add_executable(MyheaderOnlyLibraryTests ${MyheaderOnlyLibrary_unittest})
After the above is set, I do a cmake .. and make which results with the following error.
"_main", referenced from:
implicit entry/start for main executable
ld: symbol(s) not found for architecture x86_64
Question:
I am not a pro in CMake. My question is that if all I want to do is to be able run tests, then should I be doing an add_executable? Is this correct approach when the only thing I want is to build and run the tests? Seems like the error when calling make is because add_executable is looking for main method like in a normal executable and its not finding it.
What is the correct way to build a test only project where a header file is the only thing to be tested?
How to run the tests after building? Looks like there is a command CTest to run the tests built using CMake?
Environment:
I am building this on a macOS with clang as the compiler with C++11.
Just like a normal C++ program you need to have a main function to execute, for a googletest unit test you can use the following:
int main(int argc, char** argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
This will run your TEST function when executed.
To use ctest to actually run your tests you should have the following lines in your cmake file:
find_package(GTest)
include_directories(${GTEST_INCLUDE_DIRS})
include(CTest)
include(GoogleTest)
enable_testing()
target_link_libraries(testName ${GTEST_LIBRARIES})
gtest_discover_tests(testName)
EDIT:
As an easier place to start before jumping into cmake, I would recommend you start with compiling and running your command on the command line. You can do that with the following:
g++ unittest/myunittest.cpp -o unittest/myunittest.out -lgtest -std=c++11
./unittest/myunittest.out
Once you know it works, take a look at cmake’s documentation and reproduce your results with a cmake file. Then you can start to scale up your programs. Understanding how to run these commands through the command line will give you an appreciation and a better understanding of what cmake is doing.

spdlog crash on factory methods

Yesterday I have started including spdlog into a personal project of mine to use for logging. So far I have had some problems with getting library inclusion to work but those are now solved completely.
Now everything compiles just fine, with all headers found however when I try to create loggers or simply set the pattern for logging the code crashes with a segmentation fault. More specifically no matter which function I call from the spdlog namespace for the very first time in the program causes the crash.
I have a class abstracting some parts from spdlog (based on this repo) as follows:
//Logger.hpp
#ifndef TE_LOGGER_HPP
#define TE_LOGGER_HPP
#include <spdlog/spdlog.h>
namespace te {
class Logger {
public:
static void Init();
inline static std::shared_ptr<spdlog::logger> &getCoreLogger() {
return sCoreLogger;
}
inline static std::shared_ptr<spdlog::logger> &getClientLogger() {
return sClientLogger;
}
private:
static std::shared_ptr<spdlog::logger> sCoreLogger;
static std::shared_ptr<spdlog::logger> sClientLogger;
};
}
#endif //TE_LOGGER_HPP
//Logger.cpp
#include "Logger.hpp"
#include <spdlog/sinks/stdout_color_sinks.h>
std::shared_ptr<spdlog::logger> te::Logger::sCoreLogger;
std::shared_ptr<spdlog::logger> te::Logger::sClientLogger;
void te::Logger::Init() {
//The first of any of the following three lines cause a crash
//no matter the order, regardless of the pattern used in set_pattern
spdlog::set_pattern("%v");
sCoreLogger = spdlog::stdout_color_mt("CORE");
sClientLogger = spdlog::stdout_color_mt("CORE");
sCoreLogger->set_level(spdlog::level::trace);
sClientLogger->set_level(spdlog::level::trace);
}
From the stack traces it seems that the issue is with the formatter class in spdlog being set null for some reason somewhere within the library. I am using the latest CLion, C++14 (I am aware that spdlog is C++11, but I need features from 14 later down the line, also setting -std=c++11 doesn't solve the issue) and the latest version of spdlog as of yesterday (pulled straight from their GitHub repo) on Ubuntu 18.04.
EDIT: As per the request in the comments I have created a small project (single cpp file, include spdlog the way I do in the real project, or the same code and library setup as in the real project referenced from the main.cpp file and linked to accordingly) that aims to reproduce the issue and here are my findings:
* The issue is not present when I use spdlog directly in the executable
* The issue is present if the Logger class is moved into a shared library and linked to from there
Here is the error message I am getting:
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
And the CMakeLists.txt files I am using (I nest the library's one into the project since as of now CLion does not support "multiple projects in the same solution" like for example VS does):
#CMakeLists.txt for Library
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
project(TokenEngine VERSION 0.0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 14)
set(SOURCE_FILES src/Application.cpp src/Application.hpp src/EntryPoint.hpp src/Logger.cpp src/Logger.hpp)
#include_directories("${CMAKE_CURRENT_SOURCE_DIR}/libs/")
add_library(TokenEngine SHARED ${SOURCE_FILES})
target_include_directories(TokenEngine PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/libs/spdlog-1.x/include")
#Expose the public API of the engine to any project that might use it
target_include_directories(TokenEngine PUBLIC include)
#CMakeLists.txt for top level project
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
add_definitions(-DTE_PLATFORM_LINUX)
project(Build CXX)
add_subdirectory(TokenEngine)
add_subdirectory(Sandbox)
You are using the same name for both loggers, when you run it you'll get:
$ ./logger
libc++abi.dylib: terminating with uncaught exception of type spdlog::spdlog_ex: logger with name 'CORE' already exists
Abort trap: 6
If you change the name of the client logger to something else it works fine:
sCoreLogger = spdlog::stdout_color_mt("CORE");
sClientLogger = spdlog::stdout_color_mt("CLIENT");
The problem is probably that spdlog’s static objects are defined twice - from inside the shared library and from client code that includes your logger header (which incldes spdlog.h).
Try to remove the include to spdlog.h from the header file and (and use forward declaration of spdlog::logger instead), and include spdlog.h only from your Logger.cpp file.
Edit:
spdlog::logger cannot be forward declared across compilation unit boundries.
The solution is to wrap the logger with some simple class defined in logger.cpp and only export it in logger.h

pass variable value to make using cmake

I'm trying to do something like
cmake -DUSE_FILES_FOR_INPUT=ON ..
and then get make to compile my files with -DUSE_FILES_FOR_INPUT=ON. But I don't get the =ON value. It seems that it should be written to a flags.make file, but it isn't:
$ grep USE_FILES_FOR_INPUT source/CMakeFiles/myprogram.dir/flags.make
CXX_DEFINES = -DUSE_FILES_FOR_INPUT
If I manually change that file with -DUSE_FILES_FOR_INPUT=ON then everything works fine.
How can I get cmake to add the value in flags.make?
A different approach to what I need to do: the variable I am using has type BOOL, so it would be good enough to get
CXX_DEFINES = -DUSE_FILES_FOR_INPUT when I do cmake -DUSE_FILES_FOR_INPUT=ON ..
and
CXX_DEFINES = when I do cmake -DUSE_FILES_FOR_INPUT=OFF ..
Is this possible?
Here is a small demonstration,
// demo.cpp
#include <iostream>
using namespace std;
int main()
{
#ifdef USE_FILES_FOR_INPUT
cout << "Using files.." << endl;
#else
cout << "Not using files.." << endl;
#endif
return 0;
}
The CMakeLists.txt,
cmake_minimum_required(VERSION 3.0)
add_executable(demo demo.cpp)
if (USE_FILES_FOR_INPUT)
add_definitions(-DUSE_FILES_FOR_INPUT)
endif()
And a sample output,
baris$ cmake . && make && ./demo
<...>
Not using files..
baris$ cmake -DUSE_FILES_FOR_INPUT=ON . && make && ./demo
<...>
Using files..
You should look at the configure_file documentation. They show an example using the special cmake construct #cmakedefine. This may be a little more work than Baris describes, but I prefer using configure_file and #cmakedefine to get cmake settings into C++ code.
Here is the complete example:
Consider a source tree containing a foo.h.in file:
#cmakedefine FOO_ENABLE
#cmakedefine FOO_STRING "#FOO_STRING#"
An adjacent CMakeLists.txt may use configure_file to configure the header:
option(FOO_ENABLE "Enable Foo" ON)
if(FOO_ENABLE)
set(FOO_STRING "foo")
endif()
configure_file(foo.h.in foo.h #ONLY)
This creates a foo.h in the build directory corresponding to this source directory. If the FOO_ENABLE option is on, the configured file will contain:
#define FOO_ENABLE
#define FOO_STRING "foo"
Otherwise it will contain:
/* #undef FOO_ENABLE */
/* #undef FOO_STRING */
One may then use the include_directories() command to specify the output directory as an include directory:
include_directories(${CMAKE_CURRENT_BINARY_DIR})
so that sources may include the header as #include <foo.h>.