comportement étrange de cmake ctest pour plus grand CTEST_PARALLEL_LEVEL

Aug 19 2020

Je suis nouveau dans le SO. J'ai un code de test unitaire simple dans lequel je fais les opérations suivantes:

  1. calculer la racine carrée du nombre à l'aide de la mysqrtbibliothèque.
  2. En utilisant la sortie de la racine carrée, ajouter ce résultat avec le même nombre et afficher le résultat.

Lorsque j'exécute le code, CTEST_PARALLEL_LEVEL = 1tous mes cas de test passent.

Mais quand je le fais, CTEST_PARALLEL_LEVEL = 8mes cas de test échouent un certain temps pour une entrée qui n'est pas corrigée à chaque exécution.

99% les résultats de TOUS sont réussis mais 1% son échec.

Erreur:

mysqrt.o: file not recognized: File truncated

J'ai supprimé le fichier objet explicitement en utilisant rm * .o, mais cette erreur survient après quelques exécutions.

Je ne sais pas pourquoi cette erreur vient avec CTEST_PARALLEL_LEVEL = 8

Je joins mon CMakeListseul car certains experts de Stack Overflow peuvent comprendre le problème en vérifiant ces 3 CMakeLists.txtfichiers.

REMARQUE: conformément aux directives de dépassement de capacité de la pile, je ne joins pas mon code source de sqrt et la fonction d' ajout pour éviter la plus grande longueur de la question.

Ma structure de dossiers:

SAMPLE_TEST

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

ÉCHANTILLON TEST

CMakeLists.txt

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

Dossier 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})

dossier 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()

dossier 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)

Résultat: 1ère exécution:

      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)

2e manche:

      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

Réponses

Tsyvarev Aug 20 2020 at 13:51

Vos tests en cours d'exécution

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

qui s'exécute efficacement make(ou quel que soit l'outil de construction que vous utilisez) dans le répertoire de construction du projet.

Mais les appels simultanés de makedans le même répertoire ne sont jamais garantis de fonctionner correctement. C'est pourquoi vous avez des erreurs étranges lorsque vous exécutez des tests en parallèle (avec une CTEST_PARALLEL_LEVELvariable définie).

Par exemple, tous ces tests essaient de créer le même fichier objet mysqrt.o, et cette création n'est certainement pas thread-safe .

En exécutant

make sqrtlib

avant

ctest

vous pouvez être sûr que le fichier objet est déjà créé lorsque les tests sont exécutés, et les tests n'essaieront pas de le créer à nouveau. Mais vous pourriez toujours avoir d'autres conflits dans les tests parallèles.


Cela dépend de ce que vous voulez réellement vérifier par le test, mais généralement un test vérifie le comportement d'un programme ou d'une bibliothèque, et il n'a pas l'intention de vérifier une compilation (construction) de ce programme. Pour cette raison, les commandes de compilation (construction) sont exécutées avant le test.

Il est généralement pratique de suivre (implémenter) ce flux de travail pour les tests:

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

Dans ce cas, un test pourrait avoir la définition suivante:

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

(À moins que vous ne souhaitiez vérifier la sortie du test de manière automatique, il n'est pas nécessaire de sauvegarder explicitement cette sortie en la redirigeant dans le fichier. Elle ctestcollecterait elle-même la sortie du test, vous pouvez donc la lire si nécessaire)