CMake:如何对您自己的 CMake 脚本宏/函数进行单元测试?

Flo*_*ian 5 unit-testing mocking cmake ctest

我已经围绕标准 CMake 命令编写了一些方便的包装器,并希望对该 CMake 脚本代码进行单元测试以确保其功能。

我已经取得了一些进展,但有两件事我希望得到帮助:

  1. 是否有一些“官方”方法可以对您自己的 CMake 脚本代码进行单元测试?类似于运行 CMake 的特殊模式?我的目标是“白盒测试”(尽可能多)。
  2. 如何处理全局变量和变量作用域问题?通过加载项目的缓存、配置测试 CMake 文件或通过 -D 命令行选项推送它,将全局变量注入到测试中?变量范围的模拟/测试(缓存与非缓存、宏/函数/包含、通过引用传递的参数)?

首先,我研究了 /Tests 下,特别是 Tests/CMakeTests 下的 CMake 源代码(我使用的是 CMake 版本 2.8.10)。有大量的品种可供发现,而且看起来其中很多都专门针对单个测试用例。

因此,我还研究了一些可用的 CMake 脚本库(例如CMake++)来查看它们的解决方案,但是当它们进行单元测试时,它们在很大程度上取决于它们自己的库函数。

Flo*_*ian 2

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:

function(cmakemocks_clearlists _message_type)
    _get_property(_list_names GLOBAL PROPERTY MockLists)
    if (NOT "${_list_names}" STREQUAL "")
        foreach(_name IN ITEMS ${_list_names})
            _get_property(_list_values GLOBAL PROPERTY ${_name})
            if (NOT "${_list_values}" STREQUAL "")
                foreach(_value IN ITEMS ${_list_values})
                    _message(${_message_type} "cmakemocks_clearlists(${_name}): \"${_value}\"")
                endforeach()
            endif()
            _set_property(GLOBAL PROPERTY ${_name} "")
        endforeach()
    endif()
endfunction()
Run Code Online (Sandbox Code Playgroud)
function(cmakemocks_pushcall _name _str)
    _message("cmakemocks_pushcall(${_name}): \"${_str}\"")
    _set_property(GLOBAL APPEND PROPERTY MockLists "${_name}")
    _set_property(GLOBAL APPEND PROPERTY ${_name} "${_str}")
endfunction()

function(cmakemocks_popcall _name _str)
    _get_property(_list GLOBAL PROPERTY ${_name})
    set(_idx -1)
    list(FIND _list "${_str}" _idx)
    if ((NOT "${_list}" STREQUAL "") AND (NOT ${_idx} EQUAL -1))
        _message("cmakemocks_popcall(${_name}): \"${_str}\"")
        list(REMOVE_AT _list ${_idx})
        _set_property(GLOBAL PROPERTY ${_name} ${_list})
    else()
        _message(FATAL_ERROR "cmakemocks_popcall(${_name}): No \"${_str}\"")
    endif()
endfunction()
Run Code Online (Sandbox Code Playgroud)
function(cmakemocks_expectcall _name _str)
    _message("cmakemocks_expectcall(${_name}): \"${_str}\" -> \"${ARGN}\"")
    _set_property(GLOBAL APPEND PROPERTY MockLists "${_name}")
    string(REPLACE ";" "|" _value_str "${ARGN}")
    _set_property(GLOBAL APPEND PROPERTY ${_name} "${_str} <<<${_value_str}>>>")
endfunction()

function(cmakemocks_getexpect _name _str _ret)
    if(NOT DEFINED ${_ret})
        _message(SEND_ERROR "cmakemocks_getexpect: ${_ret} given as _ret parameter in not a defined variable. Please specify a proper variable name as parameter.")
    endif()

    _message("cmakemocks_getexpect(${_name}): \"${_str}\"")
    _get_property(_list_values GLOBAL PROPERTY ${_name})

    set(_value_str "")

    foreach(_value IN ITEMS ${_list_values})
        set(_idx -1)
        string(FIND "${_value}" "${_str}" _idx)
        if ((NOT "${_value}" STREQUAL "") AND (NOT ${_idx} EQUAL -1))
            list(REMOVE_ITEM _list_values "${_value}")
            _set_property(GLOBAL PROPERTY ${_name} ${_list_values})

            string(FIND "${_value}" "<<<" _start)
            string(FIND "${_value}" ">>>" _end)
            math(EXPR _start "${_start} + 3")
            math(EXPR _len "${_end} - ${_start}")
            string(SUBSTRING "${_value}" ${_start} ${_len} _value_str)
            string(REPLACE "|" ";" _value_list "${_value_str}")
            set(${_ret} "${_value_list}" PARENT_SCOPE)
            break()
        endif()
    endforeach()
endfunction()
Run Code Online (Sandbox Code Playgroud)

The Mockups

By adding mockups like:

macro(add_library)
    string(REPLACE ";" " " _str "${ARGN}")
    cmakemocks_pushcall(MockLibraries "${_str}")
endmacro()

macro(get_target_property _var)
    string(REPLACE ";" " " _str "${ARGN}")
    set(${_var} "[NULL]")
    cmakemocks_getexpect(MockGetTargetProperties "${_str}" ${_var})
endmacro()
Run Code Online (Sandbox Code Playgroud)

The Tests

I can write a test like this:

MyUnitTests.cmake

cmakemocks_expectcall(MockGetTargetProperties "MyLib TYPE" "STATIC_LIBRARY")
my_add_library(MyLib "src/Test1.cc")
cmakemocks_popcall(MockLibraries "MyLib src/Test1.cc")
...
cmakemocks_clearlists(STATUS)
Run Code Online (Sandbox Code Playgroud)

And include it into my CMake projects with:

CMakeLists.txt

add_test(
    NAME TestMyCMake 
    COMMAND ${CMAKE_COMMAND} -P "MyUnitTests.cmake"
)
Run Code Online (Sandbox Code Playgroud)