# Testing a lib with various build configurations

**URL:** https://discourse.cmake.org/t/testing-a-lib-with-various-build-configurations/9545
**Category:** Usage
**Tags:** tool:ctest
**Created:** [December 3, 2023, 2:53pm UTC](https://discourse.cmake.org/t/testing-a-lib-with-various-build-configurations/9545 "2023-12-03T14:53:58Z")
**Posts on this page:** 8
**Page:** 1

<div class="post-metadata">

### Author: ![ach](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/a/858c86/32.png) [@ach](https://discourse.cmake.org/u/ach)
#### Post date: [December 3, 2023, 2:53pm UTC](https://discourse.cmake.org/t/testing-a-lib-with-various-build-configurations/9545/1 "2023-12-03T14:53:58Z")

</div>

Hello,

I’m currently trying to set-up a small embedded-C project using CMake. Part of the code is conditionally built using preprocessor guards. The CMake project uses an option() to enable/disable this. Using generator expressions, the define is passed from cmake to the compiler. This basically looks like this:

```auto
option(OPTION_A "Option A description")
add_library(my_target STATIC my_target.c)
target_compile_definitions(my_target PUBLIC $<$<BOOL:${OPTION_A}>:OPTION_A>)

```

Now I’m trying to use ctest to run various tests. Of-course I would like to test my code both when the option is enabled or disabled. For the moment I use add\_test, but I can only test one version of the code at once.

```auto
add_executable(test_my_target test_my_target.c)
target_link_libraries(test_my_target PRIVATE my_target)
add_test(NAME test_my_target COMMAND test_my_target)

```

I would like to be able to define two tests i(one exploiting the option, the other not), that I could run at once. By that I mean that I don’t want to contantly reconfigure the project and rebuild then re-run the tests (I agree that at some point I’ll need to,build the lib and it’s dependency twice).

I started having a look at CTest “Build and Test Mode”, but for now I wasn’t able to achieve what I wanted. If that is a good direction to take, I’ll re-read the doc and persevere.

My questions are the following:

- Is what I want to achieve a good idea ?
- Is this achievable in a simple manner ?
- Is CTest “Build and Test Mode” a way to achieve this ?

---

<div class="post-metadata">

### Author: ![ben.boeckel](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/b/ea5d25/32.png) [@ben.boeckel](https://discourse.cmake.org/u/ben.boeckel)
#### Post date: [December 4, 2023, 1:01pm UTC](https://discourse.cmake.org/t/testing-a-lib-with-various-build-configurations/9545/2 "2023-12-04T13:01:43Z")

</div>

Since it comes in through a `-D` flag, you can have two tests, one does:

```auto
#ifdef OPTION_A
#undef OPTION_A
#endif

#include "header/from/my_target"

// tests without option set

```

```auto
#ifndef OPTION_A
#define OPTION_A
#endif

#include "header/from/my_target"

// tests with option set

```

However, this assumes that the option is purely an interface thing (i.e., `my_target`’s compilation itself doesn’t care about `OPTION_A`). Using `INTERFACE` instead of `PUBLIC` would help show this.

However, if the code depends on `OPTION_A`, you’ll need to compile `my_target` twice: once with the option set and once without.

---

<div class="post-metadata">

### Author: ![ach](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/a/858c86/32.png) [@ach](https://discourse.cmake.org/u/ach)
#### Post date: [December 10, 2023, 3:29pm UTC](https://discourse.cmake.org/t/testing-a-lib-with-various-build-configurations/9545/3 "2023-12-10T15:29:51Z")

</div>

Thanks @ben.boeckel for the answer. Unfortunately the compilation cares about OPTION\_A, this is not just a header thing.

I tried a few things and I’m currently in the following state:

```auto
$ tree                                                                                                                                                                                                           
.
├── src
│ ├── CMakeLists.txt
│ ├── my_lib.c
│ └── my_lib.h
├── tst
│ ├── CMakeLists.txt
│ └── my_lib_tst.c
└── tst.sh

```

```auto
# src/CMakeLists.txt
cmake_minimum_required(VERSION 3.28)
project(my_lib)

option(OPTION_A "Option A description")

add_library(my_lib STATIC my_lib.c)
target_include_directories(my_lib PUBLIC .)
target_compile_definitions(my_lib PUBLIC $<$<BOOL:${OPTION_A}>:OPTION_A>)

```

```auto
// src/my_lib.c
#include "my_lib.h"
int func(void)
{
#ifdef OPTION_A
  return 0;
#else
  return 1;
#endif
}

```

```auto
// src/my_lib.h
#ifndef MY_LIB_H
#define MY_LIB_H

int func(void);

#endif // MY_LIB_H

```

```auto
# tst/CMakeLists.txt                                                                                                                                                                                              
cmake_minimum_required(VERSION 3.28)
project(my_lib_tst)

add_subdirectory(../src ${CMAKE_CURRENT_BINARY_DIR}/src)

add_executable(my_lib_tst my_lib_tst.c)
target_link_libraries(my_lib_tst PRIVATE my_lib)

```

Currently the test files can use public defines from the lib.

```auto
// tst/my_lib_tst.c
#include "my_lib.h"

int main (void)
{
#ifdef OPTION_A
  return (func() == 0) ? 0 : -1;
#else
  return (func() == 1) ? 0 : -1;
#endif
}

```

And I’m driving the tests using a shell script and ctest in “Build and Test Mode”

```auto
ctest --build-and-test tst/ tst/build_on --build-generator Ninja \
  --build-project my_lib_tst \
  --build-options -DOPTION_A=ON \
  --test-command my_lib_tst

ctest --build-and-test tst/ tst/build_off --build-generator Ninja \
  --build-project my_lib_tst \
  --build-options -DOPTION_A=OFF \
  --test-command my_lib_tst

```

If this works as expected, the output is not easy to parse for a human, and I would like to drive the test execution using ctest.

I might be missing something but I’m unable to do that for now. Here is what I’ve tried so far:

```auto
cmake_minimum_required(VERSION 3.28)
project(test)

add_test(tst1
  ${CMAKE_CTEST_COMMAND}
  --build-and-test tst/ tst/build_on
  --build-generator ${CMAKE_GENERATOR}
  --build-makeprogram ${CMAKE_MAKE_PROGRAM}
  --build-project my_lib_tst
  --build-options -DOPTION_A=ON
  --test-command my_lib_tst)

add_test(tst2
  ${CMAKE_CTEST_COMMAND}
  --build-and-test tst/ tst/build_off
  --build-generator ${CMAKE_GENERATOR}
  --build-makeprogram ${CMAKE_MAKE_PROGRAM}
  --build-project my_lib_tst
  --build-options -DOPTION_A=OFF
  --test-command my_lib_tst)

```

This last bit is taken from [Using CTest to Drive Complex Tests](https://cmake.org/cmake/help/book/mastering-cmake/chapter/Testing%20With%20CMake%20and%20CTest.html#using-ctest-to-drive-complex-tests)

---

<div class="post-metadata">

### Author: ![ben.boeckel](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/b/ea5d25/32.png) [@ben.boeckel](https://discourse.cmake.org/u/ben.boeckel)
#### Post date: [December 11, 2023, 3:20pm UTC](https://discourse.cmake.org/t/testing-a-lib-with-various-build-configurations/9545/4 "2023-12-11T15:20:55Z")

</div>

VTK runs things like this here:

[https://gitlab.kitware.com/vtk/vtk/-/blob/master/Examples/CMakeLists.txt](https://gitlab.kitware.com/vtk/vtk/-/blob/master/Examples/CMakeLists.txt)

---

<div class="post-metadata">

### Author: ![ach](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/a/858c86/32.png) [@ach](https://discourse.cmake.org/u/ach)
#### Post date: [December 12, 2023, 9:35pm UTC](https://discourse.cmake.org/t/testing-a-lib-with-various-build-configurations/9545/5 "2023-12-12T21:35:59Z")

</div>

Thanks once again for the answer.

This kind of help me, as I realized that having some relative path like I had might not be the best idea.

The other missing part was that I also needed to include CTest in the CMakeLists.txt.

```auto
# CMakeLists.txt
cmake_minimum_required(VERSION 3.28)
project(test)

include(CTest)

add_test(NAME tst1 COMMAND
  ${CMAKE_CTEST_COMMAND}
  --build-and-test ${CMAKE_CURRENT_SOURCE_DIR}/tst/ 
  ${CMAKE_CURRENT_BINARY_DIR}/tst/build_on
  --build-generator ${CMAKE_GENERATOR}
  --build-makeprogram ${CMAKE_MAKE_PROGRAM}
  --build-project my_lib_tst
  --build-options
      -DOPTION_A=ON
  --test-command my_lib_tst)

add_test(NAME tst2 COMMAND
  ${CMAKE_CTEST_COMMAND}
  --build-and-test ${CMAKE_CURRENT_SOURCE_DIR}/tst/
  ${CMAKE_CURRENT_BINARY_DIR}/tst/build_off
  --build-generator ${CMAKE_GENERATOR}
  --build-makeprogram ${CMAKE_MAKE_PROGRAM}
  --build-project my_lib_tst
  --build-options
      -DOPTION_A=OFF
  --test-command my_lib_tst)

```

This is for now giving me the result I want. Still need to dig a bit more I guess, but as far as the original issue goes, this seems to be resolved.

---

<div class="post-metadata">

### Author: ![craig.scott](https://discourse.cmake.org/user_avatar/discourse.cmake.org/craig.scott/32/20_2.png) [@craig.scott](https://discourse.cmake.org/u/craig.scott)
#### Post date: [December 13, 2023, 8:33pm UTC](https://discourse.cmake.org/t/testing-a-lib-with-various-build-configurations/9545/6 "2023-12-13T20:33:11Z")

</div>

> [@ach](#):
>
> The other missing part was that I also needed to include CTest in the CMakeLists.txt.

You probably don’t actually want or need to `include(CTest)`. That’s more aimed at people using CDash. You most likely want to call `enable_testing()` instead, which won’t create all the additional CDash-related targets that `include(CTest)` does.

---

<div class="post-metadata">

### Author: ![ach](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/a/858c86/32.png) [@ach](https://discourse.cmake.org/u/ach)
#### Post date: [December 14, 2023, 6:27am UTC](https://discourse.cmake.org/t/testing-a-lib-with-various-build-configurations/9545/7 "2023-12-14T06:27:11Z")

</div>

Thanks for the feedback,

I’m now wondering, in the case the CMakeLists is not that simple and includes other not-test-related things, should I use the following pattern, so I can enable or not the build of tests from the command line ? (Being able to choose if tests are built or not was the reason I didn’t use `enable_testing()` in the first place)

```auto
# CMakeLists.txt
cmake_minimum_required(VERSION 3.28)
project(my_project)

add_executable(...)
add_library(...)

if (BUILD_TESTING)
   enable_testing()
   add_test(...)
endif()

```

---

<div class="post-metadata">

### Author: ![craig.scott](https://discourse.cmake.org/user_avatar/discourse.cmake.org/craig.scott/32/20_2.png) [@craig.scott](https://discourse.cmake.org/u/craig.scott)
#### Post date: [December 14, 2023, 6:43am UTC](https://discourse.cmake.org/t/testing-a-lib-with-various-build-configurations/9545/8 "2023-12-14T06:43:46Z")

</div>

> [@ach](#):
>
> so I can enable or not the build of tests from the command line ? (Being able to choose if tests are built or not was the reason I didn’t use `enable_testing()` in the first place)

This is a common misunderstanding. The `enable_testing()` command does not determine whether your tests are _built_. It only determines whether CMake writes out the necessary files for `ctest`. If you want to avoid building tests based on some cache option, you have to implement that yourself. As it happens, the `CTest` module does help with part of doing that (it defines the `BUILD_TESTING` option, but it’s still up to the project to use it), but `CTest` brings a whole lot of other things (i.e. targets associated with CDash) which most projects don’t actually want.

Frankly, I wish we didn’t need to call `enable_testing()` at all. In my opinion, every project should just call it straight after its top level call to `project()`. I’m yet to see a case where this had any measurable impact on generation time, or where it led to any problems at all. The advantage is that now your project always supports `ctest`, if you define any tests. Here’s the pattern I typically recommend:

```cmake
cmake_minimum_required(VERSION 3.28) # Must be at least 3.21 for PROJECT_IS_TOP_LEVEL
project(my_project)
enable_testing() # No harm even you ultimately don't add any tests

add_executable(...)
add_library(...)

if(PROJECT_IS_TOP_LEVEL)
   add_test(...)
endif()

```

Some variations on the above are also possible. If you want the flexibility to turn tests on or off when your project is the top level project (which it sounds like you do), then you can expand the `if()` condition to account for that. Some people want to allow the tests to be added to the build even if the project is not the top level. Occasionally that’s useful, but most of the time it isn’t. For the case where you want that degree of flexibility, here’s how I’d implement that:

```cmake
cmake_minimum_required(VERSION 3.28)
project(my_project)
enable_testing()

add_executable(...)
add_library(...)

option(MYPROJECT_ENABLE_TESTS "Enable tests for my_project" ${PROJECT_IS_TOP_LEVEL})
if(MYPROJECT_ENABLE_TESTS)
   add_test(...)
endif()

```
