Unit Testing CMakePPLang Functions

This page contains guidelines, tips, and tricks which relate to unit testing functionality in the CMakePPLang module. The content of this page is somewhat exclusive to CMakePPLang and is in addition to project-wide guidelines.

Testing Signatures

The CMake language is weakly typed, while CMakePPLang is strongly typed. Because CMakePPLang is unit tested, users can be confident that CMakePPLang functions will correctly enforce type safety (and if they don’t, that is a bug). Unfortunately, this means that all code intended to implement CMakePPLang’s type-safety mechanism need to have their type-safety checked manually. This section focuses on how to unit test such code.

cpp_assert_signature

CMakePPLang provides the function cpp_assert_signature to facilitate the testing of signatures. cpp_assert_signature does the actual type-checking for you. In your unit test, simply make sure you are calling cpp_assert_signature correctly. Type-checking is only performed in debug mode, so CMAKEPP_LANG_DEBUG_MODE must be toggled on when testing function signatures as well. Due to the performance drop that debugging mode can cause, it should only be enabled in the scopes where it is needed. See Debugging Code Written in CMakePPLang for more information.

As an example of using cpp_assert_signature, consider the implementation of a function with the signature my_function(desc bool). This would look like:

include(cmakepp_lang/asserts/signature)

function(my_function arg0 arg1)
    cpp_assert_signature("${ARGV}" desc bool)
    # Function implementation goes here
endfunction()

The basic pattern is that your function’s first line should call cpp_assert_signature with the arguments provided to your function (this is always "${ARGV}") and the types that each argument should be (of course you should use your function’s actual types). To make sure you are calling cpp_assert_signature, your unit-test should look like:

include(cmake_test/cmake_test)

ct_add_test(NAME [[test_my_function]])
function("${CMAKETEST_TEST}")
    # Uncomment and include your function module file
    # include(path/to/my_function/implementation)

    ct_add_section(NAME [[test_signature]])
    function("${CMAKETEST_SECTION}")
        # Enable debug mode so type checking occurs in this section
        set(CMAKEPP_LANG_DEBUG_MODE TRUE)

        ct_add_section(NAME [[first_argument_must_be_a_desc]] EXPECTFAIL)
        function("${CMAKETEST_SECTION}")
            my_function(TRUE TRUE)
        endfunction()

        ct_add_section(NAME [[second_argument_must_be_a_bool]] EXPECTFAIL)
        function("${CMAKETEST_SECTION}")
            my_function(hello world)
        endfunction()

        ct_add_section(NAME [[function_only_takes_two_arguments]] EXPECTFAIL)
        function("${CMAKETEST_SECTION}")
            my_function(hello TRUE 42)
        endfunction()
    endfunction()
endfunction()

This is boilerplate heavy, but it is also the minimum required to make sure that you have correctly set the types of each argument and that your function is not variadic (in this example we did not define our function as variadic; if yours is variadic you can skip this check).

Functions that cannot be type-checked with cpp_assert_signature

The following UML diagram summarizes the call graph of cpp_assert_signature.

../_images/assert_signature_call_graph.png

Caveats regarding recursion aside, the call graph of any CMake script must ultimately be a directed-acyclic graph. This means we cannot use cpp_assert_signature to type-check any of the signatures of the functions used to implement it. More generally, with respect to the above UML diagram, each function in the diagram can only use functions below it to implement type-checking. For example, sanitize_string cannot use any function in the call-graph to implement its type-checking (because all functions in the graph depend on sanitize_string), whereas cmakepp_type can use functions in the global module as well as sanitize_string (which is not very helpful in the long run for this particular use case). For the most part, many of these functions are quite generic (operating on objects of type str in many cases) so type-checking is not particularly useful anyways (think of str as void* in C/C++). At the moment, for better or for worse, most of these functions in this call graph have no type-checking.