CMake 秘籍(七)(3)https://developer.aliyun.com/article/1525426
它是如何工作的
工作流程的高层次概览显示,CTest 运行测试并将结果记录在本地 XML 文件中。这些 XML 文件随后被发送到 CDash 服务器,在那里可以进行浏览和分析。通过点击前面截图中显示的“通过”下的 2,我们可以获得关于通过或失败的测试的更多细节(在本例中,没有失败的测试)。如后续截图所示,详细记录了运行测试的机器信息以及时间信息。同样,个别测试的输出可以在网上浏览。
CTest 支持三种不同的运行提交模式:实验性、夜间和连续性。我们使用了ctest --dashboard Experimental
,因此测试结果出现在实验性下。实验模式适用于测试代码的当前状态,用于调试新的仪表板脚本(参见本章的第 3 和第 4 个食谱),或用于调试 CDash 服务器或项目。夜间模式将更新(或降级)代码到最接近最新夜间开始时间的仓库快照,这可以在CTestConfig.cmake
中设置;它为接收频繁更新的项目中的所有夜间测试提供了一个定义良好的参考点。例如,可以将夜间开始时间设置为协调世界时午夜,如下所示:
set(CTEST_NIGHTLY_START_TIME "00:00:00 UTC")
连续模式适用于持续集成工作流程,并将更新代码到最新版本。
使用单个命令即可完成构建、测试并提交到实验仪表板 - 即cmake --build . --target Experimental
命令。
还有更多
在本食谱中,我们直接从测试目标部署到 CDash。也可以使用专门的 CTest 脚本,我们将在本章稍后的第 3 和第 4 个食谱中演示这种方法。
CDash 不仅允许您监控测试是否通过或失败,还允许您监控测试时间。您可以为测试时间配置边际:如果测试花费的时间超过分配的时间,它将被标记为失败。这对于基准测试很有用,可以自动检测在重构代码时测试时间性能下降的情况。
另请参见
有关 CDash 定义和配置设置的详细讨论,请参阅官方 CDash 文档,网址为public.kitware.com/Wiki/CDash:Documentation
。
向 CDash 仪表板报告测试覆盖率
本食谱的代码可在github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-14/recipe-02
获取,并包含一个 C++示例。该食谱适用于 CMake 版本 3.5(及以上),并在 GNU/Linux、macOS 和 Windows 上进行了测试。
在本食谱中,我们将测量测试覆盖率并将其报告给 CDash 仪表板,以便我们能够逐行浏览测试覆盖率分析,以识别未测试或未使用的代码。
准备就绪
我们将在前一个食谱的源代码中添加一个微小的变化,在src/sum_integers.cpp
中,我们将添加一个函数 - sum_integers_unused
:
#include "sum_integers.hpp" #include <vector> int sum_integers(const std::vector<int> integers) { auto sum = 0; for (auto i : integers) { sum += i; } return sum; } int sum_integers_unused(const std::vector<int> integers) { auto sum = 0; for (auto i : integers) { sum += i; } return sum; }
我们的目标是使用测试覆盖率分析来检测这段未使用的代码,方法是使用 gcov(gcc.gnu.org/onlinedocs/gcc/Gcov.html
)。除了上述修改外,我们将使用前一个食谱的未修改源代码。
如何操作
通过以下步骤,我们将启用覆盖率分析并将结果上传到仪表板:
- 顶级
CMakeLists.txt
和tests/CMakeLists.txt
文件与之前的配方保持不变。 - 我们将在
src/CMakeLists.txt
中扩展,添加一个选项以添加代码覆盖率的编译标志。此选项默认启用,如下所示:
option(ENABLE_COVERAGE "Enable coverage" ON) if(ENABLE_COVERAGE) if(CMAKE_CXX_COMPILER_ID MATCHES GNU) message(STATUS "Coverage analysis with gcov enabled") target_compile_options(sum_integers PUBLIC -fprofile-arcs -ftest-coverage -g ) target_link_libraries(sum_integers PUBLIC gcov ) else() message(WARNING "Coverage not supported for this compiler") endif() endif()
- 然后,我们将配置、构建并部署到 CDash:
$ mkdir -p build $ cd build $ cmake .. $ cmake --build . --target Experimental
- 这将产生与之前配方类似的输出,但最后一步将执行测试覆盖率分析:
Performing coverage Processing coverage (each . represents one file): ... Accumulating results (each . represents one file): ... Covered LOC: 14 Not covered LOC: 7 Total LOC: 21 Percentage Coverage: 66.67% Submit files (using http) Using HTTP submit method Drop site:http://my.cdash.org/submit.php?project=cmake-cookbook Uploaded: /home/user/cmake-recipes/chapter-14/recipe-02/cxx-example/build/Testing/20180408-1530/Build.xml Uploaded: /home/user/cmake-recipes/chapter-14/recipe-02/cxx-example/build/Testing/20180408-1530/Configure.xml Uploaded: /home/user/cmake-recipes/chapter-14/recipe-02/cxx-example/build/Testing/20180408-1530/Coverage.xml Uploaded: /home/user/cmake-recipes/chapter-14/recipe-02/cxx-example/build/Testing/20180408-1530/CoverageLog-0.xml Uploaded: /home/user/cmake-recipes/chapter-14/recipe-02/cxx-example/build/Testing/20180408-1530/Test.xml Submission successful
- 最后,我们可以在浏览器中验证测试结果(在本例中,测试结果报告给
my.cdash.org/index.php?project=cmake-cookbook
)。
工作原理
测试覆盖率分析以 66.67%的百分比进行总结。为了获得更深入的见解,我们可以点击该百分比,并获得两个子目录的覆盖率分析,如下所示:
通过浏览子目录链接,我们可以检查单个文件的测试覆盖率百分比,甚至可以浏览逐行的总结(例如,src/sum_integers.cpp
):
绿色线条在运行测试套件时已被遍历,而红色线条则没有。通过这一点,我们不仅可以识别未使用/未测试的代码(使用sum_integers_unused
函数),还可以看到每行代码被遍历的频率。例如,代码行sum += i
已被访问 1,005 次(test_short
期间 5 次,test_long
期间 1,000 次)。测试覆盖率分析是自动化测试不可或缺的伴侣,CDash 为我们提供了一个在浏览器中浏览和图形化分析结果的界面。
另请参阅
如需进一步阅读,我们推荐以下博客文章,该文章讨论了 CDash 中的额外覆盖功能:blog.kitware.com/additional-coverage-features-in-cdash/
。
使用 AddressSanitizer 并将内存缺陷报告给 CDash
本配方的代码可在github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-14/recipe-03
找到,包括一个 C++和一个 Fortran 示例。本配方适用于 CMake 版本 3.5(及更高版本),并在 GNU/Linux 和 macOS 上进行了测试。
AddressSanitizer(ASan)是 C++、C 和 Fortran 的内存错误检测器。它可以发现内存缺陷,如使用后释放、使用后返回、使用后作用域、缓冲区溢出、初始化顺序错误和内存泄漏(参见github.com/google/sanitizers/wiki/AddressSanitizer
)。AddressSanitizer 是 LLVM 的一部分,从版本 3.1 开始,也是 GCC 的一部分,从版本 4.8 开始。在本菜谱中,我们将在我们的代码中制造两个可能未在正常测试运行中检测到的错误。为了检测这些错误,我们将 CTest 与使用 AddressSanitizer 的动态分析相结合,并将缺陷报告给 CDash。
准备工作
在本例中,我们将使用两个源文件和两个测试,如下所示:
. ├── CMakeLists.txt ├── CTestConfig.cmake ├── dashboard.cmake ├── src │ ├── buggy.cpp │ ├── buggy.hpp │ └── CMakeLists.txt └── tests ├── CMakeLists.txt ├── leaky.cpp └── use_after_free.cpp
文件buggy.cpp
包含两个有问题的函数,如下所示:
#include "buggy.hpp" #include <iostream> int function_leaky() { double *my_array = new double[1000]; // do some work ... // we forget to deallocate the array // delete[] my_array; return 0; } int function_use_after_free() { double *another_array = new double[1000]; // do some work ... // deallocate it, good! delete[] another_array; // however, we accidentally use the array // after it has been deallocated std::cout << "not sure what we get: " << another_array[123] << std::endl; return 0; }
这些函数在相应的头文件(buggy.hpp
)中公开:
#pragma once int function_leaky(); int function_use_after_free();
测试源码leaky.cpp
验证function_leaky
的返回码:
#include "buggy.hpp" int main() { int return_code = function_leaky(); return return_code; }
相应地,use_after_free.cpp
检查function_use_after_free
的返回值,如下所示:
#include "buggy.hpp" int main() { int return_code = function_use_after_free(); return return_code; }
如何操作
我们需要使用特定的标志编译我们的代码以利用 ASan。然后,我们将运行测试并将它们提交到仪表板。让我们看看如何做到这一点:
- 有问题的库在
src/CMakeLists.txt
中定义:
add_library(buggy "") target_sources(buggy PRIVATE buggy.cpp PUBLIC ${CMAKE_CURRENT_LIST_DIR}/buggy.hpp ) target_include_directories(buggy PUBLIC ${CMAKE_CURRENT_LIST_DIR} )
- 对于文件
src/CMakeLists.txt
,我们将添加一个选项和代码以使用 ASan 进行消毒:
option(ENABLE_ASAN "Enable AddressSanitizer" OFF) if(ENABLE_ASAN) if(CMAKE_CXX_COMPILER_ID MATCHES GNU) message(STATUS "AddressSanitizer enabled") target_compile_options(buggy PUBLIC -g -O1 -fsanitize=address -fno-omit-frame-pointer ) target_link_libraries(buggy PUBLIC asan ) else() message(WARNING "AddressSanitizer not supported for this compiler") endif() endif()
- 两个测试在
tests/CMakeLists.txt
中紧凑地定义,使用foreach
循环:
foreach(_test IN ITEMS leaky use_after_free) add_executable(${_test} ${_test}.cpp) target_link_libraries(${_test} buggy) add_test( NAME ${_test} COMMAND $<TARGET_FILE:${_test}> ) endforeach()
- 顶级
CMakeLists.txt
基本上与之前的菜谱保持不变:
# set minimum cmake version cmake_minimum_required(VERSION 3.5 FATAL_ERROR) # project name and language project(recipe-03 LANGUAGES CXX) # require C++11 set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) # process src/CMakeLists.txt add_subdirectory(src) enable_testing() # allow to report to a cdash dashboard include(CTest) # process tests/CMakeLists.txt add_subdirectory(tests)
- 同样,
CTestConfig.cmake
文件保持不变:
set(CTEST_DROP_METHOD "http") set(CTEST_DROP_SITE "my.cdash.org") set(CTEST_DROP_LOCATION "/submit.php?project=cmake-cookbook") set(CTEST_DROP_SITE_CDASH TRUE)
- 在本菜谱中,我们将使用 CTest 脚本向 CDash 报告;为此,我们将创建一个文件,
dashboard.cmake
(与主CMakeLists.txt
和CTestConfig.cmake
在同一目录中),包含以下内容:
set(CTEST_PROJECT_NAME "example") cmake_host_system_information(RESULT _site QUERY HOSTNAME) set(CTEST_SITE ${_site}) set(CTEST_BUILD_NAME "${CMAKE_SYSTEM_NAME}-${CMAKE_HOST_SYSTEM_PROCESSOR}") set(CTEST_SOURCE_DIRECTORY "${CTEST_SCRIPT_DIRECTORY}") set(CTEST_BINARY_DIRECTORY "${CTEST_SCRIPT_DIRECTORY}/build") include(ProcessorCount) ProcessorCount(N) if(NOT N EQUAL 0) set(CTEST_BUILD_FLAGS -j${N}) set(ctest_test_args ${ctest_test_args} PARALLEL_LEVEL ${N}) endif() ctest_start(Experimental) ctest_configure( OPTIONS -DENABLE_ASAN:BOOL=ON ) ctest_build() ctest_test() set(CTEST_MEMORYCHECK_TYPE "AddressSanitizer") ctest_memcheck() ctest_submit()
- 我们将直接执行
dashboard.cmake
脚本。请注意我们如何使用CTEST_CMAKE_GENERATOR
选项传递要使用的生成器,如下所示:
$ ctest -S dashboard.cmake -D CTEST_CMAKE_GENERATOR="Unix Makefiles" Each . represents 1024 bytes of output . Size of output: 0K Each symbol represents 1024 bytes of output. '!' represents an error and '*' a warning. . Size of output: 1K
- 结果将出现在 CDash 站点上,如下面的截图所示:
CMake 秘籍(七)(5)https://developer.aliyun.com/article/1525429