cmake ctest对于较大的CTEST_PARALLEL_LEVEL的奇怪行为 [英] strange behavior of cmake ctest for bigger CTEST_PARALLEL_LEVEL

查看:102
本文介绍了cmake ctest对于较大的CTEST_PARALLEL_LEVEL的奇怪行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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


  1. 使用 mysqrt计算数字的平方根库。

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

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


但是当我执行 CTEST_PARALLEL_LEVEL = 8 ,然后我的测试用例由于无法在每次运行中都不固定的某些输入而失败。


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


错误:

  mysqrt.o:无法识别文件:文件被截断

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


我不确定为什么此错误与 CTEST_PARALLEL_LEVEL = 8

我正在附加我的 CMakeList ,只有某些Stack Overflow专家可以通过检查这3个 CMakeLists.txt 文件来理解该问题。


注意:根据堆栈溢出准则,我不会附加源代码 sqrt addition 函数,以避免问题的长度变长。


我的文件夹结构:

  SAMPLE_TEST 

├──CMakeLists.txt
├── MathFunctions
│├──CMakeLists.txt
│├──MathFunctions.h
│└──mysqrt.cpp
└──单元测试
├──CMakeLists .txt
└──step2
├──CMakeLists.txt
├──execute.cpp
└──tutorial.cpp

SAMPLE_TEST


CMakeLists.txt

  cmake_minimum_required(版本3.1)
项目(教程)
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
评论生成sqrtlib)
ADD_LIBRARY(sqrtlib对象$ {REF_FILES})

unit_test文件夹


CMakeLists.txt

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

宏(GENERATION文件输入)
设置(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}
注释为生成输出文件测试用例)

add_custom_target(gen_input _ $ {input}
DEPENDS $ {INPUT_FILE0}
COMMENT生成的输出文件)

endmacro()

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

宏(执行文件输入)
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}


设置(INPUT_FILE0 ip0 _ $ {input} .y)

设置(EXE_FILE0 exeadd _ $ {input} .y)
设置(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}
注释为测试用例生成输出文件)

add_custom_target(gen_execute _ $ {input}
DEPENDS $ {EXE_FILE0}
注释生成的输出文件)

#添加测试以模拟
add_test(NAME ctest_execute _ $ {input}
COMMAND $ {CMAKE_COMMAND} --build $ {CMA KE_BINARY_DIR}
--target gen_execute _ $ {input})

#add_dependencies(execute _ $ {main_base_name_mangled}
#gen_input)

endmacro()

#++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++#
#添加测试目录
#++++ ++++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++#

设置(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 81100121 144)

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

结果:
第一次运行:

 开始1:ctest_execute_1 
开始2:ctest_execute_4
开始3 :ctest_execute_9
开始4:ctest_execute_16
开始5:ctest_execute_25
开始6:ctest_execute_36
开始7:ctest_execute_49
开始8:ctest_execute_64
1/12测试#4:ctest_execute_16 ................. ***失败1.14秒
2/12测试#6:ctest_execute_36 ...... ......通过1.27秒
3/12测试#7:ctest_execute_49 .................通过1.32秒
4/12测试# 8:ctest_execute_64 .................通过了1.32秒
开始9:ctest_execute_81
开始10:ctest_execute_100
开始11:ctest_execute_121
开始12:ctest_execute_144
5/12测试#1:ctest_execute_1 ..................通过1.33秒
6/12测试#2:ctest_execute_4 .........通过了1.33秒
7/12测试#3:ctest_execute_9 .... ..............通过了1.33秒
8/12测试#5:ctest_execute_25 .................通过了1.33秒
9/12测试#10:ctest_execute_100 ................通过0.54秒
10/12测试#11:ctest_execute_121 ........ ........通过0.55秒
11/12测试#9:ctest_execute_81 .................通过0.55秒
12/12测试#12:ctest_execute_144 ................通过0.55秒
92%测试通过,12个测试中有1个测试失败

总测试时间(真实)= 1.88秒

下列测试失败:
4-ctest_execute_16(失败)

第二次运行:

 开始1:ctest_execute_1 
开始2:ctest_execute_4
开始3:ctest_execute_9
开始4:ctest_execute_16
开始5:ctest_execute_25
开始6:ctest_execute_36
开始7:ctest_execute_49
开始8:ctest_execute_64
1/12测试#6:ctest_execute_36。 ...... .....通过1.31秒
2/12测试#7:ctest_execute_49 .................通过1.36秒
3/12测试#8 :ctest_execute_64 .................通过1.36秒
开始9:ctest_execute_81
开始10:ctest_execute_100
开始11:ctest_execute_121
4/12测试#1:ctest_execute_1 ..................通过1.37秒
5/12测试#2:ctest_execute_4 ......... .........通过1.37秒
6/12测试#3:ctest_execute_9 ..................通过1.36秒
7 / 12测试#4:ctest_execute_16 .................通过1.36秒
8/12测试#5:ctest_execute_25 ...... ......通过1.37秒
开始12:ctest_execute_144
9/12测试#11:ctest_execute_121 ................通过0.50秒
10/12测试#10:ctest_execute_100 ................通过0.51秒
11/12测试#9:ctest_execute_81 ......... ........通过0.51秒
12/12测试#12:ctest_execute_144 ......... .......通过0.34秒

100%测试通过,12个测试中有0个失败

总测试时间(实际)= 2.01秒


解决方案

您正在执行的测试

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

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


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


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


通过运行

 使sqrtlib 

之前

  test 

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




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


通常,遵循(实现)该工作流程进行测试很方便:

 #配置项目
cmake< source-directory>
#构建项目。
#它既可以构建计划的程序/库,也可以构建测试本身。
使
#运行测试
ctest< params>

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

  add_test(NAME ctest_execute _ $ {input}命令$ {exe_generator})

(除非您要 ctest 本身将收集测试的输出,因此您可以通过某种自动方式来检查测试的输出,而无需通过重定向到文件来显式保存此输出。如有需要,请阅读)。


I am new to the SO. I have a simple unit test code where I am doing following operations :

  1. calculating square root of the number using mysqrt library .
  2. Using the output of square root, adding this result with same number and display the result.

When I am running the code with CTEST_PARALLEL_LEVEL = 1 my all test cases are passing.

But when I do CTEST_PARALLEL_LEVEL = 8 then my test cases are failing some time for some input which is not fixed in every run.

99% the ALL results are passing but 1% its failing.

Error:

mysqrt.o: file not recognized: File truncated

I have deleted the object file explicitly by using rm *.o ,But still this error is coming after few runs .

I am not sure why this error is coming with CTEST_PARALLEL_LEVEL = 8

I am attaching my CMakeList only as some of Stack Overflow expert can understand the issue by checking these 3 CMakeLists.txt files.

NOTE: As per the Stack overflow guidelines I am not attaching my source code of sqrt and addition function to avoid the bigger length of the question .

My folder structure:

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 folder

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 folder

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 folder

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)

Result: 1st Run:

      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)

2nd Run:

      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

解决方案

Your tests executing

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

which effectively runs make (or whatever build tool you use) in the project's build directory.

But concurrent invocations of make in the same directory are never guarantee to work correctly. This is why you got weird errors when run tests in parallel (with CTEST_PARALLEL_LEVEL variable being set).

E.g. all these tests are trying to create the same object file mysqrt.o, and this creation is definitely not thread-safe.

By running

make sqrtlib

before

ctest

you may be sure that the object file is already created when tests are run, and tests wouldn't attempt to create it again. But you still could get other conflicts in the parallel tests.


It depends on what actually you want to check by the testing, but usually a test checks behavior of some program or library, and it doesn't intend to check a compilation(building) of that program. Because of that, compilation(building) commands are performed before the testing.

Usually it is convenient to follow(implement) this workflow for testing:

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

In that case a test could have the following definition:

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

(Unless you want to check output of the test by some automatic way, no need to explicitely save this output by redirecting into the file. ctest by itself would collect the output of the test, so you may read it if needed).

这篇关于cmake ctest对于较大的CTEST_PARALLEL_LEVEL的奇怪行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆