I've written some convenience wrappers around standard CMake commands and want to unit-test this CMake script code to ensure its functionality.
I've made some progress, but there are two things I hope to get help with:
- Is there some "official" way of unit-testing your own CMake script code? Something like a special mode to run CMake in? My goal is "white-box testing" (as much as possible).
- How do I handle the global variables and the variable scopes issues? Inject Global variables into the test via loading the a project's cache, configure the test CMake file or pushing it via -D command line option? Simulation/Testing of variable scopes (cached vs. non-cached, macros/functions/includes, parameters passed by references)?
To start with I've looked into the CMake source code (I'm using CMake version 2.8.10) under /Tests and especially under Tests/CMakeTests. There is a huge number of varieties to be found and it looks like a lot of them are specialized on a single test case.
So I looked also into some available CMake script libraries like CMake++ to see their solution, but those - when they have unit tests - are heavily depending on their own library functions.
Generally speaking all currently existing methods (via cache, via environment variables and via -D command line) are a bad choice in one or another case as involving unpredictable behaviour.
This is a least list of issues i can recall:
Find*
oradd_subdirectory
.I've used variables inside cmake lists a long time and decided to write my own solution to cut off them all at once out of the cmake lists.
The idea is to write a standalone parser through a cmake script to load variables from a file or set of files and define a set of rules to enable variables set in predefined or strict order with check on collisions and overlapping.
Here a list of several features:
bool A=ON
is equal tobool A=TRUE
is equal tobool A=1
path B="c:\abc"
is equal on the Windows topath B="C:\ABC"
(explicitpath
variable instead of a string which is by default)B_ROOT="c:\abc"
is equal on the Windows toB_ROOT="C:\ABC"
(variable's type detection by the ending of a variable name)LIB1:WIN="c:\lib1"
sets only in the Windows, whenLIB1:UNIX="/lib/lib1"
sets only in the Unix (a variable specialization).LIB1:WIN=c:\lib1
,LIB1:WIN:MSVC:RELEASE=$/{LIB1}\msvc_release
- variables reusing via expansion and specializationI can not said everything here, but you can take as an example the
tacklelib
library (https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/) to research the implementation on your own.The example of described configuration files is stored here: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/config/
The implementation: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/cmake/tacklelib/SetVarsFromFiles.cmake
As a mandatory the cmake list must be initialized through the
configure_environment(...)
macro: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/CMakeLists.txtRead the readme file for the details around the
tacklelib
project: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/README_EN.txtThe entire project currently is an experimental.
PS: Write a parser script on the cmake is a tough task, read at least these issues for the start:
;-escape list implicit unescaping
: https://gitlab.kitware.com/cmake/cmake/issues/18946Not paired
]or
[characters breaks "file(STRINGS"
: https://gitlab.kitware.com/cmake/cmake/issues/19156I did my own "white-box" or a way of testing my own scripts. I have write a set of modules (which itself dependent to the library) to run test in a separate cmake process: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/cmake/tacklelib/testlib/
My tests built on it: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/cmake_tests/
The idea is to put into the tests directory a hierarchy of directories and files with tests and the runner code would just search for the tests in predefined order to execute each test in a separate cmake process:
, where set of functions can be used both from a runner cmake script or
*.include.cmake
file:Where
TestLib.cmake
is designed to run cycle over creation external cmake processes with a test module -*.test.cmake
and these functions should be called from a runner script or from an include module (groups other include modules -*.include.cmake
or test modules -*.test.cmake
):Where
TestModule.cmake
automatically includes in all*.test.cmake
modules in which you have to put your testing code.After that you just use
tkl_test_assert_true
inside a*.test.cmake
module to mark a test as succeeded or failed.Additionally, you can use filter parameters in the runner scripts in the
_scripts
subdirectory to filter tests out:Pros:
TestModule.cmake
does traverse the entire directory with tests by predefined rules, you just need to make sure the correct hierarchy and naming to order the testing.*.include.cmake
to exclusive inclusion or to reorder the tests in the directory and its descedants.*.test.cmake
file is the only requirement to put the test to running by default. To exclusively include or exclude the test you can start use command line flags--path_match_filter ...
and--test_case_match_filter ...
.Cons:
function
keyword, which a bit reduces the functionality of several functions. For example, thetkl_test_assert_true
can only mark the test is succeeded or failed. To explicitly interrupt the test you have make the branching through the call totkl_return_if_failed
macro..test.cmake
- for a test, and.include.cmake
- for inclusion commands. All builtin search logic depends on it.RunTestLib.cmake
. The example of arun all
on the unix shell could be found here: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/cmake_tests/_scripts/test_all.shThe entire project currently is an experimental.
Here is my current solution for unit-testing my own CMake script code.
By the assumption that using CMake Script processing mode is my best catch and that I have to mock the CMake commands that are not usable in script mode I - so far - came up with the following.
The Helper Functions
Utilizing my own global properties, I have written helper functions to store and compare function calls:
The Mockups
By adding mockups like:
The Tests
I can write a test like this:
MyUnitTests.cmake
And include it into my CMake projects with:
CMakeLists.txt