我们有几个单元测试,我们希望将其作为构建过程的一部分运行。
为了实现这一点,我有一个帮助脚本,它创建一个运行测试的自定义命令,如果成功,则创建一个文件"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}
)
这不起作用,因为即使测试失败,我也会获得创建的sentinal "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}
)
这方面的问题有两方面。
--quiet
标志会抑制--output-on-failure
标志,因此您可以输出嘈杂或无输出 - 无法仅获取故障。问题
有没有办法实现我想要的?
即:
跨平台方法的奖励积分,但如果它必须只是Linux,那就这样吧。
答案 0 :(得分: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()
如果测试失败,脚本将删除sentinel文件并输出错误日志。 如果测试成功,脚本将创建sentinel文件。
答案 1 :(得分:0)
回答我自己的问题,因为我找到了适合我的解决方案。
在下面的摘录中,${ARG_NAME}
是我们构建并想要运行的测试二进制文件。
运行测试时创建了2个文件
${ARG_NAME}.output
包含测试输出${ARG_NAME}.passed
,如果测试成功,则创建一个标记文件 custom_command
由几个命令组成,我们依赖于cmake会在其中一个命令失败后立即停止执行的事实。
我们进行测试:
${ARG_NAME}
所有测试输出都重定向到${ARG_NAME}.output
,因此测试通过时我们不会污染标准输出
${ARG_NAME} >> ${OUTPUT_FILE} 2>&1
使用测试的退出状态,如果发生故障,我们会捕获文件:
${ARG_NAME} >> ${OUTPUT_FILE} 2>&1 || cat ${OUTPUT_FILE}
但是,只有在测试通过时我们才会创建sentinel文件,所以我们都捕获文件并运行false
${ARG_NAME} >> ${OUTPUT_FILE} 2>&1 || (cat ${OUTPUT_FILE} && false)
如果测试通过(退出状态为0),那么我们将不会运行cat output && 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}
)