Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sanitizer build options #249

Merged
merged 6 commits into from
Jan 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/sanitizers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: sanitizers

on: [push, pull_request]
jobs:
build-and-test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
compiler: [gcc10, clang10]
# Since Leak is usually part of Address, we do not run it separately in
# CI. Keeping Address and Undefined separate for easier debugging
sanitizer: [Thread,
Address,
Undefined]
# Memory sanitizer triggers on almost anything and some of the things
# are outside our control. Additionally, there seem to be
# inconsistencies between local and CI runs, so disabling this for now
#
# include:
# # Memory sanitizer is not available for gcc
# - compiler: clang10
# sanitizer: MemoryWithOrigin
steps:
- uses: actions/checkout@v2
- uses: cvmfs-contrib/github-action-cvmfs@v2
- uses: aidasoft/run-lcg-view@v2
with:
release-platform: LCG_99/x86_64-centos7-${{ matrix.compiler }}-opt
run: |
set -x
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug \
-DUSE_SANITIZER=${{ matrix.sanitizer }} \
-DCMAKE_CXX_STANDARD=17 \
-DCMAKE_CXX_FLAGS=" -fdiagnostics-color=always " \
-DUSE_EXTERNAL_CATCH2=OFF \
-DENABLE_SIO=ON \
-G Ninja ..
ninja -k0
ctest --output-on-failure
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ include(cmake/podioBuild.cmake)
podio_set_compiler_flags()
podio_set_rpath()

set(USE_SANITIZER ""
CACHE STRING "Compile with a sanitizer. Options are Address, Memory, MemoryWithOrigin, Undefined, Thread, Leak, Address;Undefined")
ADD_SANITIZER_FLAGS()
option(FORCE_RUN_ALL_TESTS "Run all the tests even those with known problems" OFF)

#--- Declare options -----------------------------------------------------------
option(CREATE_DOC "Whether or not to create doxygen doc target." OFF)
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,16 @@ The generation script has the following additional options:
After compilation you can run rudimentary tests with

make test

## Advanced build topics

It is possible to instrument the complete podio build with sanitizers using the
`USE_SANITIZER` cmake option. Currently `Address`, `Memory[WithOrigin]`,
`Undefined` and `Thread` are available. Given the sanitizers limitations they
are more or less mutually exclusive, with the exception of `Address;Undefined`.
Currently some of the tests will fail with sanitizers enabled
([issue](https://github.com/AIDASoft/podio/issues/250)). In order to allow for a
smoother development experience with sanitizers enabled, these failing tests are
labelled (e.g. `[ASAN-FAIL]` or `[THREAD-FAIL]`) and will not be run by default
if the corresponding sanitizer is enabled. It is possible to force all tests to
be run via the `FORCE_RUN_ALL_TESTS` cmake option.
59 changes: 59 additions & 0 deletions cmake/podioBuild.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,62 @@ macro(podio_set_compiler_flags)
endif()

endmacro(podio_set_compiler_flags)

#--- add sanitizer flags, depending on the setting of USE_SANITIZER and the
#--- availability of the different sanitizers
macro(ADD_SANITIZER_FLAGS)
if(USE_SANITIZER)
if(USE_SANITIZER MATCHES "Address;Undefined" OR USE_SANITIZE MATCHES "Undefined;Address")
message(STATUS "Building with Address and Undefined behavior sanitizers")
add_compile_options("-fsanitize=address,undefined")
add_link_options("-fsanitize=address,undefined")

elseif(USE_SANITIZER MATCHES "Address")
message(STATUS "Building with Address sanitizer")
add_compile_options("-fsanitize=address")
add_link_options("-fsanitize=address")

elseif(USE_SANITIZER MATCHES "Undefined")
message(STATUS "Building with Undefined behaviour sanitizer")
add_compile_options("-fsanitize=undefined")
add_link_options("-fsanitize=undefined")

elseif(USE_SANITIZER MATCHES "Thread")
message(STATUS "Building with Thread sanitizer")
add_compile_options("-fsanitize=thread")
add_link_options("-fsanitize=thread")

elseif(USE_SANITIZER MATCHES "Leak")
# Usually already part of Address sanitizer
message(STATUS "Building with Leak sanitizer")
add_compile_options("-fsanitize=leak")
add_link_options("-fsanitize=leak")

elseif(USE_SANITIZER MATCHES "Memory(WithOrigins)?")
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
message(FATAL_ERROR "GCC does not have memory sanitizer support")
endif()
message(STATUS "Building with memory sanitizer")
add_compile_options("-fsanitize=memory")
add_link_options("-fsanitize=memory")
if(USE_SANITIZER MATCHES "MemoryWithOrigins")
message(STATUS "Adding origin tracking for memory sanitizer")
add_compile_options("-fsanitize-memory-track-origins")
endif()

else()
message(FATAL_ERROR "Unsupported value for USE_SANITIZER: ${USE_SANITIZER}")
endif()

# Set a few more flags if we have succesfully configured a sanitizer
# For nicer stack-traces in the output
add_compile_options("-fno-omit-frame-pointer")
# append_flag( CMAKE_CXX_FLAGS CMAKE_C_FLAGS)

# Make it even easier to interpret stack-traces
if(uppercase_CMAKE_BUILD_TYPE STREQUAL "DEBUG")
add_compile_options("-O0")
endif()

endif(USE_SANITIZER)
endmacro(ADD_SANITIZER_FLAGS)
44 changes: 39 additions & 5 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,6 @@ else()
set(CMAKE_CXX_FLAGS ${CXX_FLAGS_CMAKE_USED})
endif()

CREATE_PODIO_TEST(unittest.cpp "Catch2::Catch2;Catch2::Catch2WithMain")
include(Catch)
catch_discover_tests(unittest WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})


if (TARGET TestDataModelSioBlocks)
set(sio_dependent_tests write_sio.cpp read_sio.cpp read_and_write_sio.cpp write_timed_sio.cpp read_timed_sio.cpp)
Expand All @@ -81,7 +77,6 @@ if (TARGET TestDataModelSioBlocks)
target_link_libraries(read_timed_sio ROOT::Tree)
endif()


#--- set some dependencies between the different tests to ensure input generating ones are run first
set_property(TEST read PROPERTY DEPENDS write)
set_property(TEST read-multiple PROPERTY DEPENDS write)
Expand Down Expand Up @@ -110,3 +105,42 @@ set_property(TEST pyunittest
PYTHONPATH=${CMAKE_SOURCE_DIR}/python:$ENV{PYTHONPATH}
ROOT_INCLUDE_PATH=${CMAKE_SOURCE_DIR}/tests/datamodel:${ROOT_INCLUDE_PATH})
set_property(TEST pyunittest PROPERTY DEPENDS write)

# Customize CTest to potentially disable some of the tests with known problems
configure_file(CTestCustom.cmake ${CMAKE_BINARY_DIR}/CTestCustom.cmake)


add_executable(unittest unittest.cpp)
target_link_libraries(unittest PUBLIC TestDataModel PRIVATE Catch2::Catch2WithMain)

# The unittests are a bit better and they are labelled so we can put together a
# list of labels that we want to ignore
set(filter_tests "")
if (NOT FORCE_RUN_ALL_TESTS)
if(USE_SANITIZER MATCHES "Address")
set(filter_tests "~[LEAK-FAIL]~[ASAN-FAIL]")
elseif(USE_SANITIZER MATCHES "Leak")
set(filter_tests "~[LEAK-FAIL]")
elseif(USE_SANITIZER MATCHES "Thread")
set(filter_tests "~[THREAD-FAIL]")
endif()
endif()

if (USE_SANITIZER MATCHES "Memory(WithOrigin)?")
# Automatic test discovery fails with Memory sanitizers due to some issues in
# Catch2. So in that case we skip the discovery step and simply run the thing
# directly in the tests.
if (FORCE_RUN_ALL_TESTS)
# Unfortunately Memory sanitizer seems to be really unhappy with Catch2 and
# it fails to succesfully launch the executable and execute any test. Here
# we just include them in order to have them show up as failing
add_test(NAME unittest COMMAND unittest ${filter_tests})
endif()
else()
include(Catch)
catch_discover_tests(unittest
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
TEST_PREFIX "UT_" # make it possible to filter easily with -R ^UT
TEST_SPEC ${filter_tests} # discover only tests that are known to not fail
)
endif()
41 changes: 41 additions & 0 deletions tests/CTestCustom.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Some of the tests currently fail when run under some sanitizers, ignore them
# for now (but report that they have been ignored). This allows us to still run
# this in CI

# See: https://gitlab.kitware.com/cmake/community/-/wikis/doc/ctest/Testing-With-CTest#customizing-ctest

# "Integration style tests" pretty much all have problems at the moment with any
# sanitizer

if ((NOT "@FORCE_RUN_ALL_TESTS@" STREQUAL "ON") AND (NOT "@USE_SANITIZER@" STREQUAL ""))
set(CTEST_CUSTOM_TESTS_IGNORE
${CTEST_CUSTOM_TESTS_IGNORE}

write
read
read_and_write
write_timed
read_timed
check_benchmark_outputs
read-multiple

write_sio
read_sio
read_and_write_sio
write_timed_sio
read_timed_sio
check_benchmark_outputs_sio

write_ascii

ostream_operator
relation_range

pyunittest
)

# ostream_operator is working with Memory sanitizer (at least locally)
if("@USE_SANITIZER@" MATCHES "Memory(WithOrigin)?")
list(REMOVE_ITEM CTEST_CUSTOM_TESTS_IGNORE ostream_operator)
endif()
endif()
Loading