diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 78688bf..5250a33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,11 +7,14 @@ on: jobs: build-and-test: + name: build-and-test (${{ matrix.cxx }}${{ matrix.asan && ', asan' || '' }}) runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: cxx: [g++-14, clang++-19] + # The sanitizer executables build in their own job, in parallel with the plain one. + asan: [false, true] steps: - uses: actions/checkout@v4 @@ -34,14 +37,15 @@ jobs: -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} \ -DNUMSIM_MATERIALS_BUILD_TESTS=ON \ - -DNUMSIM_MATERIALS_BUILD_REPORTS=ON \ - -DNUMSIM_MATERIALS_REPORT_TESTS=ON \ + -DNUMSIM_SANITIZER_TESTS=${{ matrix.asan && 'ON' || 'OFF' }} \ + -DNUMSIM_MATERIALS_BUILD_REPORTS=${{ matrix.asan && 'OFF' || 'ON' }} \ + -DNUMSIM_MATERIALS_REPORT_TESTS=${{ matrix.asan && 'OFF' || 'ON' }} \ -DNUMSIM_MATERIALS_BUILD_EXAMPLES=OFF \ -DNUMSIM_MATERIALS_ENABLE_PLOTTING=OFF \ -DNUMSIM_MATERIALS_DEBUG_CHECKS=ON - name: Build - run: cmake --build build -j$(nproc) + run: cmake --build build -j$(nproc) ${{ matrix.asan && '--target numsim_materials_asan_tests' || '' }} - name: Test - run: cd build && ctest --output-on-failure + run: cd build && ctest --output-on-failure -j$(nproc) ${{ matrix.asan && '-R _asan$' || '' }} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1844164..4bb570b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,8 +1,28 @@ find_package(Threads REQUIRED) +option(NUMSIM_MATERIALS_BATCH_TESTS "Compile single-file tests several to a translation unit" ON) +set(NUMSIM_MATERIALS_TEST_BATCH_SIZE 6 CACHE STRING "Test files per batch") +# Tests that need their own flags or check what a lone translation unit includes. +set(_numsim_standalone_tests test_material_conformance test_host_neutral_includes) +set(_numsim_batched_sources) +set(_numsim_test_targets) + macro(add_numsim_test TARGET_NAME) - numsim_add_test(${TARGET_NAME} SOURCES ${ARGN} - LINK numsim-materials::numsim-materials Threads::Threads) + set(_srcs ${ARGN}) + list(LENGTH _srcs _nsrcs) + set(_c_symbols) + if(_nsrcs EQUAL 1) + # C entry points ignore the batch namespaces and would be defined twice. + file(STRINGS "${_srcs}" _c_symbols REGEX "NUMSIM_MATERIALS_DEFINE_|extern \"C\"") + endif() + if(NUMSIM_MATERIALS_BATCH_TESTS AND _nsrcs EQUAL 1 AND NOT _c_symbols + AND NOT "${TARGET_NAME}" IN_LIST _numsim_standalone_tests) + list(APPEND _numsim_batched_sources "${CMAKE_CURRENT_SOURCE_DIR}/${_srcs}") + else() + numsim_add_test(${TARGET_NAME} SOURCES ${ARGN} + LINK numsim-materials::numsim-materials Threads::Threads) + list(APPEND _numsim_test_targets ${TARGET_NAME}) + endif() endmacro() add_numsim_test(test_property_graph test_property_graph.cpp) @@ -89,6 +109,39 @@ add_numsim_test(test_voigt_composite test_voigt_composite.cpp) add_numsim_test(test_hencky_elasticity test_hencky_elasticity.cpp) add_numsim_test(test_material_point test_material_point.cpp) +# One executable per batch: shared templates are instantiated and compiled once per batch, not once per file. +list(LENGTH _numsim_batched_sources _nbatched) +set(_first 0) +set(_batch 0) +while(_first LESS _nbatched) + list(SUBLIST _numsim_batched_sources ${_first} ${NUMSIM_MATERIALS_TEST_BATCH_SIZE} _srcs) + string(REPLACE ";" "|" _arg "${_srcs}") + set(_name test_batch_${_batch}) + set(_out "${CMAKE_CURRENT_BINARY_DIR}/${_name}.cpp") + add_custom_command(OUTPUT "${_out}" + COMMAND ${CMAKE_COMMAND} "-DOUT=${_out}" "-DSOURCES=${_arg}" -P "${CMAKE_CURRENT_SOURCE_DIR}/batch_tests.cmake" + DEPENDS ${_srcs} "${CMAKE_CURRENT_SOURCE_DIR}/batch_tests.cmake" VERBATIM) + add_custom_target(${_name}_source DEPENDS "${_out}") + numsim_add_test(${_name} SOURCES "${_out}" INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}" + LINK numsim-materials::numsim-materials Threads::Threads) + list(APPEND _numsim_test_targets ${_name}) + foreach(_t ${_name} ${_name}_asan ${_name}_tsan) + if(TARGET ${_t}) + add_dependencies(${_t} ${_name}_source) + endif() + endforeach() + math(EXPR _first "${_first} + ${NUMSIM_MATERIALS_TEST_BATCH_SIZE}") + math(EXPR _batch "${_batch} + 1") +endwhile() + +# Builds only the sanitizer executables (the CI sanitizer job). +add_custom_target(numsim_materials_asan_tests) +foreach(_t IN LISTS _numsim_test_targets) + if(TARGET ${_t}_asan) + add_dependencies(numsim_materials_asan_tests ${_t}_asan) + endif() +endforeach() + # Data dumper for plotting (not a test — standalone executable) add_executable(plot_data plot_data.cpp) target_link_libraries(plot_data PRIVATE numsim-materials::numsim-materials) diff --git a/tests/batch_tests.cmake b/tests/batch_tests.cmake new file mode 100644 index 0000000..3c7eac5 --- /dev/null +++ b/tests/batch_tests.cmake @@ -0,0 +1,23 @@ +# cmake -DOUT= -DSOURCES= -P batch_tests.cmake +# Writes one translation unit holding several test files, each in its own namespace. +# Every #include of the files is hoisted above the namespaces, so the inner ones are no-ops. +string(REPLACE "|" ";" SOURCES "${SOURCES}") +set(_includes) +set(_bodies) +set(_k 0) +foreach(_src IN LISTS SOURCES) + file(STRINGS "${_src}" _lines REGEX "^[ \t]*#[ \t]*include") + list(APPEND _includes ${_lines}) + string(APPEND _bodies "namespace numsim_test_batch_${_k} {\n#include \"${_src}\"\n}\n") + math(EXPR _k "${_k} + 1") +endforeach() +list(REMOVE_DUPLICATES _includes) +list(JOIN _includes "\n" _includes) +set(_content "// Generated by tests/batch_tests.cmake.\n${_includes}\n\n${_bodies}") +if(EXISTS "${OUT}") + file(READ "${OUT}" _old) + if(_old STREQUAL _content) + return() + endif() +endif() +file(WRITE "${OUT}" "${_content}")