How to make private sources public for unit tests ?

Hello,

I have a project with a CMakeLists.txt at the root, and two directories: src and test.
src builds a library, and test builds an executable testing the library.

In src, when I define access levels for sources, I’d like to make some .h private to the normal users of the library, but visible to the unit tests. How to do that ?

I’ve solved a similar situation by creating an interface library which brings in the normal one and additionally “publishes” the private path. Something like this:

add_library(TheLib
  include/public/public_header.h
  include/private/private_header.h
  src/source.cpp
)
target_include_directories(TheLib
  PUBLIC include/public
  PRIVATE include/private
)

add_library(TheLib_InternalAccess INTERFACE)
target_link_libraries(TheLib_InternalAccess INTERFACE TheLib)
target_include_directories(TheLib_InternalAccess
  INTERFACE include/private
)

add_executable(NormalConsumer whatever.cpp)
target_link_libraries(NormalConsumer PRIVATE TheLib)

add_executable(LibraryTester whatever_test.cpp)
target_link_libraries(LibraryTester PRIVATE TheLib_InternalAccess)

you mean this, or not?
target_link_libraries(LibraryTester PRIVATE TheLib_InternalAccess)

1 Like

Yes of course, thanks. Stupid copy-pasto. Edited the original reply to fix.

Do “normal users” exist in the project or with your project embedded in theirs? Or are “normal users” importing installed targets? In the latter case, CMake does have ways to expose properties differently in the build tree and in exports using generator expressions.

In recent versions of CMake, you can get away with a single target that is defined differently in the build tree and in the installed target using the https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#genex:BUILD_LOCAL_INTERFACE generator expression.

E.g.

add_library(TheLibHeaders INTERFACE)
target_include_directories(TheLibHeaders INTERFACE
    include/public
    $<BUILD_LOCAL_INTERFACE:include/private>
)
target_include_directories(TheLib
    PUBLIC TheLibHeaders
)

If all of the consumers have access to the build tree, though, that doesn’t help you, and a separate clearly named INTERFACE target makes sense, as previously suggested.

It seems to me that you are asking the wrong question. I’ve found that the process of unit testing frequently informs design (what is exposed; what is not). Your unit testing should be “black-box” testing (without relying on implementation details that, by definition should be subject to change).

Note that if any of your public headers include any of your private headers, you have to deliver them and your interface is exposed. Hyrum’s law applies.

If you really want to test internal implementations that are not exposed, I would create a private OBJECT library and run your unit tests against that, then link the private library into your exposed library. This is logically equivalent to the interface library suggested previously (and the interface may be superior, depending on your purpose) but the implementation-details library that you unit test explicitly captures your requirements in a straightforward way.

1 Like