diff --git a/.github/workflows/sanitizers.yaml b/.github/workflows/sanitizers.yaml new file mode 100644 index 000000000..ff156f5ac --- /dev/null +++ b/.github/workflows/sanitizers.yaml @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 329dbe542..859a8a50f 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/README.md b/README.md index afc63c04a..87d35181c 100755 --- a/README.md +++ b/README.md @@ -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. diff --git a/cmake/podioBuild.cmake b/cmake/podioBuild.cmake index 6012b1e05..075aab337 100644 --- a/cmake/podioBuild.cmake +++ b/cmake/podioBuild.cmake @@ -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) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5cf407c8d..9b4716ba5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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) @@ -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) @@ -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() diff --git a/tests/CTestCustom.cmake b/tests/CTestCustom.cmake new file mode 100644 index 000000000..00ccb0308 --- /dev/null +++ b/tests/CTestCustom.cmake @@ -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() diff --git a/tests/unittest.cpp b/tests/unittest.cpp old mode 100755 new mode 100644 index 48b6ab697..4518412ce --- a/tests/unittest.cpp +++ b/tests/unittest.cpp @@ -19,7 +19,7 @@ #include "datamodel/MutableExampleWithComponent.h" #include "datamodel/ExampleWithOneRelationCollection.h" -TEST_CASE("AutoDelete") { +TEST_CASE("AutoDelete", "[basics][memory-management]") { auto store = podio::EventStore(); auto hit1 = MutableEventInfo(); auto hit2 = MutableEventInfo(); @@ -29,7 +29,7 @@ TEST_CASE("AutoDelete") { hit3 = hit2; } -TEST_CASE("Basics") { +TEST_CASE("Basics", "[basics][memory-management]") { auto store = podio::EventStore(); // Adding auto& collection = store.create("name"); @@ -38,13 +38,12 @@ TEST_CASE("Basics") { hit2.energy(12.5); // Retrieving const ExampleHitCollection* coll2(nullptr); - bool success = store.get("name",coll2); + REQUIRE(store.get("name", coll2)); const ExampleHitCollection* coll3(nullptr); - if (store.get("wrongName",coll3) != false) success = false; - REQUIRE(success); + REQUIRE_FALSE(store.get("wrongName", coll3)); } -TEST_CASE("Assignment-operator ref count") { +TEST_CASE("Assignment-operator ref count", "[basics][memory-management]") { // Make sure that the assignment operator handles the reference count // correctly. (Will trigger in an ASan build if it is not the case) // See https://github.com/AIDASoft/podio/issues/200 @@ -61,7 +60,7 @@ TEST_CASE("Assignment-operator ref count") { } } -TEST_CASE("Clearing"){ +TEST_CASE("Clearing", "[ASAN-FAIL][THREAD-FAIL][basics][memory-management]"){ bool success = true; auto store = podio::EventStore(); auto& hits = store.create("hits"); @@ -88,7 +87,7 @@ TEST_CASE("Clearing"){ REQUIRE(success); } -TEST_CASE("Cloning"){ +TEST_CASE("Cloning", "[basics][memory-management]"){ bool success = true; auto hit = MutableExampleHit(); hit.energy(30); @@ -106,13 +105,13 @@ TEST_CASE("Cloning"){ REQUIRE(success); } -TEST_CASE("Component"){ +TEST_CASE("Component", "[basics]"){ auto info = MutableExampleWithComponent(); info.component().data.x = 3; REQUIRE(3 == info.component().data.x); } -TEST_CASE("Cyclic"){ +TEST_CASE("Cyclic", "[LEAK-FAIL][basics][relations][memory-management]"){ auto start = MutableExampleForCyclicDependency1(); auto isAvailable = start.ref().isAvailable(); REQUIRE_FALSE(isAvailable); @@ -128,8 +127,7 @@ TEST_CASE("Cyclic"){ REQUIRE(start == start.ref().ref()); } -TEST_CASE("Invalid_refs") { - bool success = false; +TEST_CASE("Invalid_refs", "[LEAK-FAIL][basics][relations]") { auto store = podio::EventStore(); auto& hits = store.create("hits"); auto hit1 = hits.create(0xcaffeeULL,0.,0.,0.,0.); @@ -138,15 +136,10 @@ TEST_CASE("Invalid_refs") { auto cluster = clusters.create(); cluster.addHits(hit1); cluster.addHits(hit2); - try { - clusters.prepareForWrite(); //should fail! - } catch (std::runtime_error&){ - success = true; - } - REQUIRE(success); + REQUIRE_THROWS_AS(clusters.prepareForWrite(), std::runtime_error); } -TEST_CASE("Looping") { +TEST_CASE("Looping", "[basics]") { auto store = podio::EventStore(); auto& coll = store.create("name"); auto hit1 = coll.create(0xbadULL,0.,0.,0.,0.); @@ -173,7 +166,7 @@ TEST_CASE("Looping") { } } -TEST_CASE("Notebook") { +TEST_CASE("Notebook", "[basics]") { bool success = true; auto store = podio::EventStore(); auto& hits = store.create("hits"); @@ -189,7 +182,7 @@ TEST_CASE("Notebook") { REQUIRE(success); } -TEST_CASE("OneToOneRelations") { +TEST_CASE("OneToOneRelations", "[basics][relations]") { bool success = true; auto cluster = ExampleCluster(); auto rel = MutableExampleWithOneRelation(); @@ -197,23 +190,17 @@ TEST_CASE("OneToOneRelations") { REQUIRE(success); } -TEST_CASE("Podness") { +TEST_CASE("Podness", "[basics][code-gen]") { // fail this already at compile time - static_assert(std::is_standard_layout_v, "Generated data classes do not have standard layout"); - static_assert(std::is_trivially_copyable_v, "Generated data classes are not trivially copyable"); - static_assert(std::is_standard_layout_v, "Generated data classes do not have standard layout"); - static_assert(std::is_trivially_copyable_v, "Generated data classes are not trivially copyable"); - static_assert(std::is_standard_layout_v, "Generated data classes do not have standard layout"); - static_assert(std::is_trivially_copyable_v, "Generated data classes are not trivially copyable"); - - // just to be sure the test does what it is supposed to do - static_assert(not std::is_standard_layout_v); - static_assert(not std::is_trivially_copyable_v); - REQUIRE(true); // just to have this also show up at runtime + STATIC_REQUIRE(std::is_standard_layout_v); // Generated data classes do not have standard layout + STATIC_REQUIRE(std::is_trivially_copyable_v); // Generated data classes are not trivially copyable + STATIC_REQUIRE(std::is_standard_layout_v); // Generated data classes do not have standard layout + STATIC_REQUIRE(std::is_trivially_copyable_v); // Generated data classes are not trivially copyable + STATIC_REQUIRE(std::is_standard_layout_v); // Generated data classes do not have standard layout + STATIC_REQUIRE(std::is_trivially_copyable_v); // Generated data classes are not trivially copyable } -TEST_CASE("Referencing") { - bool success = true; +TEST_CASE("Referencing", "[basics][relations]") { auto store = podio::EventStore(); auto& hits = store.create("hits"); auto hit1 = hits.create(0x42ULL,0.,0.,0.,0.); @@ -224,13 +211,13 @@ TEST_CASE("Referencing") { cluster.addHits(hit2); int index = 0; for (auto i = cluster.Hits_begin(), end = cluster.Hits_end(); i!=end; ++i){ - if( i->energy() != index) success = false; + REQUIRE(i->energy() == index); ++index; } - REQUIRE(success); } -TEST_CASE("VariadicCreate", "Test that objects created via the variadic create template function handle relations correctly") { +TEST_CASE("VariadicCreate", "[basics]") { + // Test that objects created via the variadic create template function handle relations correctly auto store = podio::EventStore(); auto& clusters = store.create("clusters"); @@ -243,7 +230,7 @@ TEST_CASE("VariadicCreate", "Test that objects created via the variadic create t REQUIRE(variadic_cluster.Clusters(0) == normal_cluster); } -TEST_CASE("write_buffer") { +TEST_CASE("write_buffer", "[basics][io]") { auto store = podio::EventStore(); auto& coll = store.create("data"); auto hit1 = coll.create(0x42ULL,0.,0.,0.,0.); @@ -254,17 +241,17 @@ TEST_CASE("write_buffer") { cluster.addHits(hit1); cluster.addHits(hit2); - clusters.prepareForWrite(); + REQUIRE_NOTHROW(clusters.prepareForWrite()); auto buffers = clusters.getBuffers(); REQUIRE(buffers.dataAsVector()->size() == clusters.size()); // a second call should not crash the whole thing and leave everything untouched - clusters.prepareForWrite(); + REQUIRE_NOTHROW(clusters.prepareForWrite()); REQUIRE(clusters.getBuffers().data == buffers.data); auto& ref_coll = store.create("onerel"); auto withRef = ref_coll.create(); - ref_coll.prepareForWrite(); + REQUIRE_NOTHROW(ref_coll.prepareForWrite()); } /* @@ -275,7 +262,7 @@ TEST_CASE("Arrays") { } */ -TEST_CASE("Extracode") { +TEST_CASE("Extracode", "[basics][code-gen]") { auto ev = MutableEventInfo(); ev.setNumber(42) ; REQUIRE(ev.getNumber() == 42); @@ -290,7 +277,7 @@ TEST_CASE("Extracode") { } -TEST_CASE("AssociativeContainer") { +TEST_CASE("AssociativeContainer", "[basics]") { auto clu1 = ExampleCluster(); auto clu2 = ExampleCluster(); auto clu3 = ExampleCluster(); @@ -326,7 +313,7 @@ TEST_CASE("AssociativeContainer") { } -TEST_CASE("Equality") { +TEST_CASE("Equality", "[basics]") { auto cluster = ExampleCluster(); auto rel = MutableExampleWithOneRelation(); rel.cluster(cluster); @@ -335,44 +322,35 @@ TEST_CASE("Equality") { REQUIRE(returned_cluster == cluster); } -TEST_CASE("NonPresentCollection") { +TEST_CASE("NonPresentCollection", "[basics][event-store]") { auto store = podio::EventStore(); REQUIRE_THROWS_AS(store.get("NonPresentCollection"), std::runtime_error); } TEST_CASE("const correct indexed access to const collections", "[const-correctness]") { - static_assert(std::is_same_v< - decltype(std::declval()[0]), - ExampleCluster>, - "const collections should only have indexed access to immutable objects"); - - static_assert(std::is_same_v< - decltype(std::declval().at(0)), - ExampleCluster>, - "const collections should only have indexed access to immutable objects"); - - REQUIRE(true); + STATIC_REQUIRE(std::is_same_v< + decltype(std::declval()[0]), + ExampleCluster>); // const collections should only have indexed access to mutable objects + STATIC_REQUIRE(std::is_same_v< + decltype(std::declval().at(0)), + ExampleCluster>); // const collections should only have indexed access to mutable objects } TEST_CASE("const correct indexed access to collections", "[const-correctness]") { auto store = podio::EventStore(); auto& collection = store.create("irrelevant name"); - static_assert(std::is_same_v, "collection created by store should not be const"); + STATIC_REQUIRE(std::is_same_v); // collection created by store should not be const - static_assert(std::is_same_v,"non-const collections should have indexed access to mutable objects"); + STATIC_REQUIRE(std::is_same_v); // non-const collections should have indexed access to mutable objects - static_assert(std::is_same_v< - decltype(std::declval()[0]), - MutableExampleCluster>, - "collections should have indexed access to mutable objects"); - - static_assert(std::is_same_v< - decltype(std::declval().at(0)), - MutableExampleCluster>, - "collections should have indexed access to mutable objects"); + STATIC_REQUIRE(std::is_same_v< + decltype(std::declval()[0]), + MutableExampleCluster>); // collections should have indexed access to mutable objects - REQUIRE(true); + STATIC_REQUIRE(std::is_same_v< + decltype(std::declval().at(0)), + MutableExampleCluster>); // collections should have indexed access to mutable objects } TEST_CASE("const correct iterators on const collections", "[const-correctness]") { @@ -380,63 +358,55 @@ TEST_CASE("const correct iterators on const collections", "[const-correctness]") // this essentially checks the whole "chain" from begin() / end() through // iterator operators for (auto hit : collection) { - static_assert(std::is_same_v, "const collection iterators should only return Const objects"); + STATIC_REQUIRE(std::is_same_v); // const collection iterators should only return immutable objects } // but we can exercise it in a detailed fashion as well to make it easier to // spot where things fail, should they fail - static_assert(std::is_same_v< - decltype(std::declval().begin()), - ExampleHitCollectionIterator>, - "const collection begin() should return an immutable CollectionIterator"); - - static_assert(std::is_same_v< - decltype(std::declval().end()), - ExampleHitCollectionIterator>, - "const collection end() should return an immutable CollectionIterator"); - - static_assert(std::is_same_v< - decltype(*std::declval().begin()), - ExampleHit>, - "CollectionIterator should only give access to immutable objects"); - - static_assert(std::is_same_v< - decltype(std::declval().operator->()), - ExampleHit*>, - "CollectionIterator should only give access to immutable objects"); - - REQUIRE(true); + STATIC_REQUIRE(std::is_same_v< + decltype(std::declval().begin()), + ExampleHitCollectionIterator>); // const collection begin() should return a CollectionIterator + + STATIC_REQUIRE(std::is_same_v< + decltype(std::declval().end()), + ExampleHitCollectionIterator>); // const collection end() should return a CollectionIterator + + STATIC_REQUIRE(std::is_same_v< + decltype(*std::declval().begin()), + ExampleHit>); // CollectionIterator should only give access to immutable objects + + STATIC_REQUIRE(std::is_same_v< + decltype(std::declval().operator->()), + ExampleHit*>); // CollectionIterator should only give access to immutable objects } TEST_CASE("const correct iterators on collections", "[const-correctness]") { auto collection = ExampleClusterCollection(); for (auto cluster : collection) { - static_assert(std::is_same_v, "collection iterators should return mutable objects"); + STATIC_REQUIRE(std::is_same_v); // collection iterators should return mutable objects cluster.energy(42); // this will necessarily also compile } // check the individual steps again from above, to see where things fail if they fail - static_assert(std::is_same_v< - decltype(std::declval().end()), - ExampleClusterMutableCollectionIterator>, - "non const collection end() should return a MutableCollectionIterator"); - - static_assert(std::is_same_v< - decltype(std::declval().begin()), - ExampleClusterMutableCollectionIterator>, - "non const collection begin() should return a MutableCollectionIterator"); - - static_assert(std::is_same_v< - decltype(*std::declval().begin()), - MutableExampleCluster>, - "MutableCollectionIterator should give access to mutable objects"); - - static_assert(std::is_same_v< - decltype(std::declval().operator->()), - MutableExampleCluster*>, - "MutableCollectionIterator should only give access to mutable objects"); - - REQUIRE(true); + STATIC_REQUIRE(std::is_same_v< + decltype(std::declval().end()), + ExampleClusterMutableCollectionIterator>); // non const collection end() should return a MutableCollectionIterator + + STATIC_REQUIRE(std::is_same_v< + decltype(std::declval().end()), + ExampleClusterMutableCollectionIterator>); // non const collection end() should return a MutableCollectionIterator + + STATIC_REQUIRE(std::is_same_v< + decltype(std::declval().end()), + ExampleClusterMutableCollectionIterator>); // collection end() should return a MutableCollectionIterator + + STATIC_REQUIRE(std::is_same_v< + decltype(*std::declval().begin()), + MutableExampleCluster>); // MutableCollectionIterator should give access to mutable objects + + STATIC_REQUIRE(std::is_same_v< + decltype(std::declval().operator->()), + MutableExampleCluster*>); // CollectionIterator should only give access to mutable objects } TEST_CASE("Subset collection basics", "[subset-colls]") { @@ -461,7 +431,7 @@ TEST_CASE("Subset collection can handle subsets", "[subset-colls]") { clusterRefs.push_back(cluster); auto clusterRef = clusterRefs[0]; - static_assert(std::is_same_v, "Elements that can be obtained from a collection and a subset collection should have the same type"); + STATIC_REQUIRE(std::is_same_v); // Elements that can be obtained from a collection and a subset collection should have the same type REQUIRE(clusterRef == cluster); @@ -473,7 +443,7 @@ TEST_CASE("Subset collection can handle subsets", "[subset-colls]") { REQUIRE(cluster.energy() == -42); } -TEST_CASE("Collection iterators work with subset collections", "[subset-colls]") { +TEST_CASE("Collection iterators work with subset collections", "[LEAK-FAIL][subset-colls]") { auto hits = ExampleHitCollection(); auto hit1 = hits.create(0x42ULL,0.,0.,0.,0.); auto hit2 = hits.create(0x42ULL,1.,1.,1.,1.);