【问题标题】:CMake - run test as part of the build process and capture stdout output to fileCMake - 作为构建过程的一部分运行测试并将标准输出输出捕获到文件
【发布时间】:2016-08-23 15:28:01
【问题描述】:

我们希望在构建过程中运行几个单元测试。

为了实现这一点,我有一个帮助脚本,它创建一个运行测试的自定义命令,如果成功,则创建一个文件"test_name.passed"

然后我添加一个自定义目标"test_name.run",它依赖于"test_name.passed"

这个想法是,如果"test_name.passed" 不存在或早于"test_name",则将运行自定义命令。

构建将继续运行自定义命令,直到测试通过。一旦通过,后续构建就不会调用自定义命令,所以不需要的时候就不会运行测试。

到目前为止,这一切都完全按照描述进行

这是脚本:

# create command which runs the test and creates a sentinel file if it passes
add_custom_command(
    OUTPUT  ${TEST_NAME}.passed
    COMMAND $<TARGET_FILE:${TEST_NAME}>
    COMMAND ${CMAKE_COMMAND} -E touch ${TEST_NAME}.passed
    DEPENDS ${TEST_NAME}
    )

# create test.run module which depends on test.passed
add_custom_target(${TEST_NAME}.run
    ALL
    DEPENDS ${TEST_NAME}.passed
    )

问题 - stdout 上的噪音

问题是我们的测试经常将大量信息记录到stdout,这会导致构建非常嘈杂。

我现在正在尝试将stdout 捕获到一个文件中,并且仅在发生故障时才显示测试输出。

我的第一次尝试是尝试 Bash shell 脚本语法 - 将 stdout 捕获到文件中,当退出状态为错误时,对文件进行分类。

add_custom_command(
    OUTPUT  ${TEST_NAME}.passed
    COMMAND $<TARGET_FILE:${TEST_NAME}> > ${TEST_NAME}.output || cat ${TEST_NAME}.output
    COMMAND ${CMAKE_COMMAND} -E touch ${TEST_NAME}.passed
    DEPENDS ${TEST_NAME}
    )

这不起作用,因为即使测试失败,我也会创建哨兵"test_name.passed" 文件,这意味着下次我尝试构建它时认​​为测试通过了。

可能的不合标准修复

通过与ctest 集成,我可以通过ctest 运行每个测试并使用命令行选项--output-on-failure

add_custom_command(
    OUTPUT  ${TEST_NAME}.passed
    COMMAND ctest --build-config $<CONFIGURATION> --tests-regex ${TEST_NAME} --output-on-failure
    COMMAND ${CMAKE_COMMAND} -E touch ${TEST_NAME}.passed
    DEPENDS ${TEST_NAME}
    )

这个问题有两个方面。

  1. 大大增加了构建时间。每个测试都必须通过单独的 ctest 进程执行,所有注册的测试名称都根据正则表达式进行解析,等等。随着单独测试的数量增加,我们额外的时间会大大增加。
  2. ctest 默认会输出很多噪音。指定 --quiet 标志会抑制 --output-on-failure 标志,因此您可以有嘈杂的输出或没有输出 - 没有办法只得到失败。

问题

有没有办法实现我想要的?

即:

  • 手动运行测试(即:不通过 ctest)
  • 将输出捕获到文件中
  • 仅在测试退出状态指示失败时输出该文件。
  • 如果测试退出状态指示成功,请触摸标记文件。

跨平台方法的加分点,但如果它必须仅适用于 Linux,那就这样吧。

【问题讨论】:

  • 为什么不直接使用 ctest 以及它与 cmake 的集成?
  • @user1034749 AFAIK ctest 不会在构建过程中运行测试。这是一个单独的目标make test。我之前确实使用 ctest 来运行每个测试 (COMMAND ctest --build-config $&lt;CONFIGURATION&gt; --tests-regex ${TEST_NAME} --output-on-failure),但是通过 ctest 调用每个单元测试会大大增加构建时间
  • @user1034749 用我在 ctest 方面的尝试更新了我的问题,以及我为什么要寻找替代方案

标签: unit-testing cmake


【解决方案1】:

问题是没有标准方法可以将通过add_custom_command 调用的命令的输出重定向到文件。然而,CMake 命令execute_process 确实具有该功能。

因此,一个可能的解决方案是从配置的 CMake 脚本运行测试可执行文件,该脚本本身作为 CMake 自定义命令运行。以下代码概述了必要的步骤:

在您添加测试的CMakeLists.txt 中,配置CMake 脚本模板:

configure_file("test_runner.cmake.in" "test_runner_${TEST_NAME}.cmake" @ONLY)

然后添加自定义命令以在构建时调用脚本:

add_custom_command(
    OUTPUT  ${TEST_NAME}.passed
    COMMAND ${CMAKE_COMMAND} -P "test_runner_${TEST_NAME}.cmake" $<TARGET_FILE:${TEST_NAME}>
    DEPENDS ${TEST_NAME}
    WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")

测试可执行文件的实际路径通过生成器表达式作为参数传递给脚本。

模板测试运行脚本test_runner.cmake.in 使用execute_process 运行测试可执行文件并将错误输出重定向到日志文件:

set (_testExecutable "${CMAKE_ARGV3}")
execute_process(COMMAND ${_testExecutable} ERROR_FILE "@TEST_NAME@.output" RESULT_VARIABLE _testResult)
if (_testResult)
    file(REMOVE "@TEST_NAME@.passed")
    file(READ "@TEST_NAME@.output" _contents)
    message (STATUS "${_contents}")
else()
    file(WRITE "@TEST_NAME@.passed" "")
endif()

如果测试失败,脚本会删除哨兵文件并输出错误日志。 如果测试成功,脚本会创建哨兵文件。

【讨论】:

    【解决方案2】:

    回答我自己的问题,因为我找到了适合我的解决方案。

    在下面的摘录中,${ARG_NAME} 是我们构建并希望运行的测试二进制文件。

    运行测试时创建了 2 个文件

    • ${ARG_NAME}.output 包含测试输出
    • ${ARG_NAME}.passed 是测试成功时创建的哨兵文件

    custom_command 由多个命令组成,我们依赖于这样一个事实,即一旦其中一个命令失败,cmake 就会停止执行。

    我们运行测试:

    ${ARG_NAME}
    

    所有测试输出都被重定向到${ARG_NAME}.output,所以我们不会在测试通过时污染标准输出

    ${ARG_NAME} >> ${OUTPUT_FILE} 2>&1
    

    使用测试的退出状态,在失败的情况下我们 cat 文件:

    ${ARG_NAME} >> ${OUTPUT_FILE} 2>&1 || cat ${OUTPUT_FILE}
    

    但是,只有当测试通过时,我们才想创建哨兵文件,所以我们 cat 文件并运行false

    ${ARG_NAME} >> ${OUTPUT_FILE} 2>&1 || (cat ${OUTPUT_FILE} && false)
    

    如果测试通过(退出状态为0),那么我们就不会运行cat output &amp;&amp; false,所以接下来运行cmake命令

    ${CMAKE_COMMAND} -E touch ${PASSED_FILE}
    

    最后我们创建一个custom_target,它依赖于.passed文件

    set(OUTPUT_FILE ${CMAKE_CURRENT_BINARY_DIR}/${ARG_NAME}.output)
    set(PASSED_FILE ${CMAKE_CURRENT_BINARY_DIR}/${ARG_NAME}.passed)
    
    # create test.passed command which runs this test and creates a sentinel file
    # if it passes
    add_custom_command(
        OUTPUT
            ${PASSED_FILE}
    
        COMMAND
            ${CMAKE_CURRENT_BINARY_DIR}/${ARG_NAME} >> ${OUTPUT_FILE} 2>&1 
                  || (cat ${OUTPUT_FILE} && false)
    
        COMMAND
            ${CMAKE_COMMAND} -E touch ${PASSED_FILE}
    
        COMMENT
            "Running ${ARG_NAME} tests"
    
        DEPENDS
            ${ARG_NAME}
    
        USES_TERMINAL
        )
    
    # create test.run target which depends on test.passed
    add_custom_target(${ARG_NAME}.run
        ALL
        DEPENDS ${PASSED_FILE}
        )
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-11-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-29
      • 2021-01-06
      • 1970-01-01
      相关资源
      最近更新 更多