【问题标题】:strange behavior of cmake ctest for bigger CTEST_PARALLEL_LEVELcmake ctest 对于更大的 CTEST_PARALLEL_LEVEL 的奇怪行为
【发布时间】:2020-12-09 05:17:50
【问题描述】:

我是 SO 的新手。 我有一个简单的单元测试代码,我正在执行以下操作:

  1. 使用mysqrt库计算数字的平方根。
  2. 使用平方根的输出,将此结果与相同的数字相加并显示结果。

当我使用CTEST_PARALLEL_LEVEL = 1 运行代码时,我的所有测试用例都通过了。

但是当我执行CTEST_PARALLEL_LEVEL = 8 时,我的测试用例会因某些输入而失败一段时间,而这些输入在每次运行时都不是固定的。

99% 的所有结果都通过了,但 1% 的结果失败了。

错误:

mysqrt.o: file not recognized: File truncated

我已经使用 rm *.o 明确删除了目标文件,但运行几次后仍然出现此错误。

我不确定为什么CTEST_PARALLEL_LEVEL = 8 会出现此错误

我附上我的CMakeList 只是因为某些 Stack Overflow 专家可以通过检查这 3 个CMakeLists.txt 文件来理解这个问题。

注意:根据堆栈溢出指南,我没有附上 sqrtaddition 函数的源代码,以避免问题的长度过长。

我的文件夹结构:

SAMPLE_TEST

├── CMakeLists.txt
├── MathFunctions
│   ├── CMakeLists.txt
│   ├── MathFunctions.h
│   └── mysqrt.cpp
└── unit_test
    ├── CMakeLists.txt
    └── step2
        ├── CMakeLists.txt
        ├── execute.cpp
        └── tutorial.cpp

SAMPLE_TEST

CMakeLists.txt

cmake_minimum_required(VERSION 3.1)
project(Tutorial)
ENABLE_TESTING()    
add_subdirectory(MathFunctions)
add_subdirectory(unit_test)

MathFunctions 文件夹

CMakeLists.txt

add_library(MathFunctions mysqrt.cpp)
set(REF_FILES mysqrt.cpp)
add_definitions(-Wall -Wextra -pedantic -std=c++11)
add_custom_target(build_reference_library
      DEPENDS sqrtlib
      COMMENT "Generating sqrtlib")
ADD_LIBRARY(sqrtlib OBJECT ${REF_FILES})

unit_test 文件夹

CMakeLists.txt

set(REF_MATHLIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../MathFunctions)

macro(GENERATION file input)
  set(ip_generator ctest_input_${input})

  add_executable(${ip_generator}
    ${file}
    $<TARGET_OBJECTS:sqrtlib>
    )

  target_compile_options(${ip_generator} PUBLIC
    -Wall -Wextra -g -std=c++11 
    -DCTEST_INPUT=${input})


  target_link_libraries(${ip_generator} PUBLIC
    dl pthread
    )

  target_include_directories(${ip_generator} PUBLIC
    ${REF_MATHLIB_DIR}
    )

  set(INPUT_FILE0 ip0_${input}.y)
  set(INPUT_FILE0_TXT ip0_${input}.txt)

  add_custom_command(
    OUTPUT ${INPUT_FILE0}  ${INPUT_FILE0_TXT}
    COMMAND ${ip_generator} > ${INPUT_FILE0_TXT}
    MAIN_DEPENDENCY ${sqrtlib}
    COMMENT "Generating output files of for testcase")
  
  add_custom_target(gen_input_${input}
    DEPENDS ${INPUT_FILE0}
    COMMENT "Generated output files")

endmacro() 

####################

macro(EXECUTE file input)
  get_filename_component(main_base_name ${file} NAME_WE)
  set(main_base_name_mangled ${main_base_name}_${input})
  set(exe_generator ctest_ref_${input})

  add_executable(${exe_generator}
    ${file}
    $<TARGET_OBJECTS:sqrtlib>
    )

  target_compile_options(${exe_generator} PUBLIC
    -Wall -Wextra -g -std=c++11 
    -DCTEST_INPUT=${input})


  target_link_libraries(${exe_generator} PUBLIC
    dl pthread
    )

  target_include_directories(${exe_generator} PUBLIC
    ${REF_MATHLIB_DIR}
    )

  set(INPUT_FILE0 ip0_${input}.y)

  set(EXE_FILE0 exeadd_${input}.y)
  set(EXE_FILE_TXT exeadd_${input}.txt)

  add_custom_command(
    OUTPUT ${EXE_FILE0} ${EXE_FILE_TXT}
    COMMAND ${exe_generator}  > ${EXE_FILE_TXT}
    MAIN_DEPENDENCY ${INPUT_FILE0} ${sqrtlib}
    COMMENT "Generating output files of for testcase")
  
  add_custom_target(gen_execute_${input}
    DEPENDS ${EXE_FILE0}
    COMMENT "Generated output files")

    # add test to simulate
  add_test(NAME ctest_execute_${input}
      COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}
                               --target gen_execute_${input})

  #add_dependencies(execute_${main_base_name_mangled}   
  #gen_input)

endmacro() 

#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#
# add test directories
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#

set(TEST_DIRECTORIES
    step2
   )

foreach(dir ${TEST_DIRECTORIES})
  add_subdirectory(${dir})
endforeach()

step2 文件夹

CMakeLists.txt

set(UT_IPGEN_FILES tutorial.cpp)
set(UT_EXECUTE_FILES execute.cpp)

set(input_integer_range 1 4 9 16 25 36 49 64 81 100 121 144 )

foreach(ip_integer ${input_integer_range})
  GENERATION(${UT_IPGEN_FILES} ${ip_integer})
  EXECUTE(${UT_EXECUTE_FILES} ${ip_integer})
endforeach(ip_integer)

结果: 第一次运行:

      Start  1: ctest_execute_1
      Start  2: ctest_execute_4
      Start  3: ctest_execute_9
      Start  4: ctest_execute_16
      Start  5: ctest_execute_25
      Start  6: ctest_execute_36
      Start  7: ctest_execute_49
      Start  8: ctest_execute_64
 1/12 Test  #4: ctest_execute_16 .................***Failed    1.14 sec
 2/12 Test  #6: ctest_execute_36 .................   Passed    1.27 sec
 3/12 Test  #7: ctest_execute_49 .................   Passed    1.32 sec
 4/12 Test  #8: ctest_execute_64 .................   Passed    1.32 sec
      Start  9: ctest_execute_81
      Start 10: ctest_execute_100
      Start 11: ctest_execute_121
      Start 12: ctest_execute_144
 5/12 Test  #1: ctest_execute_1 ..................   Passed    1.33 sec
 6/12 Test  #2: ctest_execute_4 ..................   Passed    1.33 sec
 7/12 Test  #3: ctest_execute_9 ..................   Passed    1.33 sec
 8/12 Test  #5: ctest_execute_25 .................   Passed    1.33 sec
 9/12 Test #10: ctest_execute_100 ................   Passed    0.54 sec
10/12 Test #11: ctest_execute_121 ................   Passed    0.55 sec
11/12 Test  #9: ctest_execute_81 .................   Passed    0.55 sec
12/12 Test #12: ctest_execute_144 ................   Passed    0.55 sec
92% tests passed, 1 tests failed out of 12

Total Test time (real) =   1.88 sec

The following tests FAILED:
      4 - ctest_execute_16 (Failed)

第二次运行:

      Start  1: ctest_execute_1
      Start  2: ctest_execute_4
      Start  3: ctest_execute_9
      Start  4: ctest_execute_16
      Start  5: ctest_execute_25
      Start  6: ctest_execute_36
      Start  7: ctest_execute_49
      Start  8: ctest_execute_64
 1/12 Test  #6: ctest_execute_36 .................   Passed    1.31 sec
 2/12 Test  #7: ctest_execute_49 .................   Passed    1.36 sec
 3/12 Test  #8: ctest_execute_64 .................   Passed    1.36 sec
      Start  9: ctest_execute_81
      Start 10: ctest_execute_100
      Start 11: ctest_execute_121
 4/12 Test  #1: ctest_execute_1 ..................   Passed    1.37 sec
 5/12 Test  #2: ctest_execute_4 ..................   Passed    1.37 sec
 6/12 Test  #3: ctest_execute_9 ..................   Passed    1.36 sec
 7/12 Test  #4: ctest_execute_16 .................   Passed    1.36 sec
 8/12 Test  #5: ctest_execute_25 .................   Passed    1.37 sec
      Start 12: ctest_execute_144
 9/12 Test #11: ctest_execute_121 ................   Passed    0.50 sec
10/12 Test #10: ctest_execute_100 ................   Passed    0.51 sec
11/12 Test  #9: ctest_execute_81 .................   Passed    0.51 sec
12/12 Test #12: ctest_execute_144 ................   Passed    0.34 sec

100% tests passed, 0 tests failed out of 12

Total Test time (real) =   2.01 sec

【问题讨论】:

  • 您的测试似乎在同一目录中运行make(通过执行COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target ...)。 并发调用 make 在同一目录中永远不能保证正常工作。例如。所有这些调用都试图创建相同的对象文件mysqrt.o,而这种创建绝对不是线程安全的。通过在ctest 之前运行make sqrtlib,您可以确定在运行测试时已经创建了目标文件。但您仍然可能在并行测试中遇到其他冲突。
  • 通常,首先构建测试,然后才运行。因此,可能的工作流程是 1.cmake &lt;...&gt;。 2.make。 3.ctest.
  • @Tsyvarev,但我只创建了一次sqrtlibobject_library。并附加这个库以执行输入的数量。我背后的想法是减少每次执行mysqrt 代码。所以创建了库。但你提到的是:例如:所有这些调用都试图创建相同的目标文件 mysqrt.o。这怎么可能。我需要做任何更改以确保我将只生成一次 sqrtlib 并将此库用于给定的输入 sqrt 计算(除了在 cmake 运行之前手动运行 make sqrt。)
  • @Tsyvarev,我正在这样运行我的测试:1.cmake -G Ninja ../ 2.ninja test。我确保 step2_build 没有旧的二进制文件和目标文件。 没有 make 我在这里执行的命令。
  • step2_build$ rm -rf * step2_build$ cmake -G Ninja ../ step2_build$ ninja test

标签: makefile cmake googletest ctest


【解决方案1】:

您正在执行的测试

COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target ...

在项目的构建目录中有效地运行make(或您使用的任何构建工具)。

但同一目录中make并发调用永远不能保证正常工作。这就是为什么在并行运行测试时会出现奇怪的错误(设置了CTEST_PARALLEL_LEVEL 变量)。

例如所有这些测试都试图创建相同的目标文件mysqrt.o,而这种创建绝对不是线程安全的

通过运行

make sqrtlib

之前

ctest

您可以确定在运行测试时已经创建了目标文件,并且测试不会尝试再次创建它。 但您仍然可能在并行测试中遇到其他冲突。


这取决于您实际上想通过测试检查什么,但通常测试会检查某些程序或库的行为,它并不打算检查该程序的编译(构建)。因此,编译(构建)命令在测试之前执行。

通常遵循(实施)此工作流程进行测试很方便:

# Configure the project
cmake <source-directory>
# Build the project.
# It builds both program/library intended, and the tests themselves.
make
# run tests
ctest <params>

在这种情况下,测试可能具有以下定义:

add_test(NAME ctest_execute_${input} COMMAND ${exe_generator})

(除非您想通过某种自动方式检查测试的输出,否则无需通过重定向到文件来明确保存此输出。ctest 本身会收集测试的输出,因此您可以阅读它,如果需要)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-05-19
    • 1970-01-01
    • 2011-02-08
    • 1970-01-01
    • 2017-10-16
    • 1970-01-01
    • 2014-10-06
    相关资源
    最近更新 更多