【问题标题】:Running only changed or failed tests with CMake/CTest?仅使用 CMake/CTest 运行更改或失败的测试?
【发布时间】:2016-04-16 05:00:33
【问题描述】:

我在一个拥有近 400 个测试可执行文件的大型代码库上工作,运行时间在 0.001 秒到 1800 秒之间变化。当一些代码更改时,CMake 将仅智能地重建已更改的目标,这比实际测试运行花费的时间要短很多。

我知道的唯一方法是手动过滤您知道要运行的测试。我的直觉告诉我,我想重新运行任何没有存储成功运行的测试套件——要么因为它失败,要么因为它被重新编译。

这可能吗?如果是这样,怎么做?

【问题讨论】:

  • 如果您的测试成功生成文件,则使其依赖于 unitTest 可执行文件应该完成大部分工作。
  • @Jarod42 但是我如何将它与 CTest 联系起来?我对 CTest 和 CMake 的交互方式仍然有些模糊......只需将其作为自定义目标放在 CMake 中即可,但会完全失去 CTest 功能。
  • 您说您有“400 个测试可执行文件”(据此我理解为 400 个主要),因此您有选择地从这些可执行文件中运行。
  • 使用 CTest 我只能运行可执行文件 - 要么全部,要么全部上次失败。我无法让它重新运行所有已更改的成功可执行文件。我可以使用 cmake 中的自定义命令或类似的东西,或者通过修改参数来做到这一点,但重点是不要手动执行。
  • ctest 命令接受几个参数,这会影响要运行的测试集。例如。 “-R” - 按名称过滤测试,“-L” - 按标签过滤测试。可能,使用与仪表板相关的选项,您还可以选择要运行的测试。至于根据更改的可执行文件生成这些选项的值,您可以编写程序或脚本。仅运行选定的测试可执行文件的另一种方法是将每个可执行文件包装到脚本中,该脚本仅在应用特定条件时才运行可执行文件。

标签: c++ unit-testing cmake build-process ctest


【解决方案1】:

ctest command 接受几个参数,这会影响要运行的测试集。例如。 “-R” - 按名称过滤测试,“-L” - 按标签过滤测试。可能,使用与仪表板相关的选项,您还可以选择要运行的测试。

至于根据更改的可执行文件为这些选项生成值,您可以编写程序或脚本,检查可执行文件的修改时间和/或解析最后一个日志文件以查找失败的测试。

另一种只运行更改的可执行文件的方法是将测试包装到附加脚本中。仅当满足某些条件时,此脚本才会运行可执行文件。

对于 Linux 包装脚本可以实现如下:

test_wrapper.sh

# test_wrapper.sh <test_name> <executable> <params..>
# Run executable, given as second argument, with parameters, given as futher arguments.
#
# If environment variable `LAST_LOG_FILE` is set,
# checks that this file is older than the executable.
#
# If environment variable LAST_LOG_FAILED_FILE is set,
# check that testname is listed in this file.
#
# Test executable is run only if one of these checks succeed, or if none of checks is performed.

check_succeed=
check_performed=
if [ -n $LAST_LOG_FILE ]; then
    check_performed=1
    executable=$2
    if [ ! ( -e "$LAST_LOG_FILE" ) ]; then
        check_succeed=1 # Log file is absent
    elif [ "$LAST_LOG_FILE" -ot "$executable" ]; then
        check_succeed=1 # Log file is older than executable
    fi
fi

if [ -n "$LAST_LOG_FAILED_FILE" ]; then
    check_performed=1
    testname=$1
    if [ ! ( -e "$LAST_LOG_FAILED_FILE" ) ]; then
        # No failed tests at all
    elif grep ":${testname}\$" "$LAST_LOG_FAILED_FILE" > /dev/null; then
        check_succeed=1 # Test has been failed previously
    fi
fi

if [ -n "$check_performed" -a -z "$check_succeed" ]; then
    echo "Needn't to run test."
    exit 0
fi

shift 1 # remove `testname` argument
eval "$*"

用于添加包装测试的 CMake 宏:

CMakeLists.txt

# Similar to add_test(), but test is executed with our wrapper.
function(add_wrapped_test name command)
    if(name STREQUAL "NAME")
        # Complex add_test() command flow: NAME <name> COMMAND <command> ...
        set(other_params ${ARGN})
        list(REMOVE_AT other_params 0) # COMMAND keyword
        # Actual `command` argument
        list(GET other_params 0 real_command)
        list(REMOVE_AT other_params 0)
        # If `real_command` is a target, need to translate it to path to executable.
        if(TARGET real_command)
            # Generator expression is perfectly OK here.
            set(real_command "$<TARGET_FILE:${real_command}")
        endif()
        # `command` is actually value of 'NAME' parameter
        add_test("NAME" ${command} "COMMAND" /bin/sh <...>/test_wrapper.sh
            ${command} ${real_command} ${other_params}
        )
    else() # Simple add_test() command flow
        add_test(${name} /bin/sh <...>/test_wrapper.sh
            ${name} ${command} ${ARGN}
        )
    endif()
endfunction(add_wrapped_test)

如果您只想运行那些自上次运行以来已更改或上次失败的可执行文件,请使用

LAST_LOG_FILE=<build-dir>/Testing/Temporary/LastTest.log \
LAST_FAILED_LOG_FILE=<build-dir>/Testing/Temporary/LastTestsFailed.log \
ctest

所有其他测试将自动通过。

【讨论】:

  • 谢谢。 *.sh 也可以是任何脚本语言。一旦我使用它,我可以将它移植到一个多平台的 CMake 脚本。 CMake 显然已安装 ;)
猜你喜欢
  • 2020-01-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-16
  • 1970-01-01
  • 1970-01-01
  • 2021-09-25
相关资源
最近更新 更多