CI builds and runs the suite with both GCC and Clang on every push, under
AddressSanitizer + UndefinedBehaviorSanitizer, on by default in Debug via the
USE_SANITIZERS option, with UBSan set to abort on the first finding.
cmake -B build -DLIBMEM_BUILD_TESTS=ON
cmake --build build
ctest --test-dir buildRunning both compilers is not redundant. Deducing-this and if constexpr inside
modules are exactly where GCC 15 and Clang 22 diverge, and GCC has already caught
mistakes in this suite that Clang accepted.
Allocators (slab, multislab, arena, typed_arena, pool): allocation,
exhaustion and reuse, slab growth and empty-slab hysteresis, full/active list
transitions, iteration, pointer stability, destructor ordering, and leak balance
via a counting memory_resource.
Storage and the containers over it: the block protocol (a reserved block leaves
the old one live, a failed growth changes nothing), relocation with a
lifetime-counting payload, alignment through resource_ref, the sparse/dense
invariant across growth and interleaved erase, payload/key alignment after a
swap-and-pop, move-only and non-trivially-destructible payloads, and each storage
kind per container.
small_storage: that the inline case reaches the resource zero times, that a
spill relocates the live elements and hands the block back with the size it came
with, that a failed spill leaves the inline slots exactly as they were, and that
adopt_from transfers the resource alongside the block. For small_vector, that a
spilled source moves as a pointer steal (zero element moves) and an unspilled one
moves element by element.
The paged sparse index: that probing an uncovered id allocates nothing, the page
boundaries at PageSize - 1 / PageSize / 2 * PageSize, that a page which could
not be allocated is never published into the directory, and a direct comparison
against the flat index over the same operations. One test measures the bytes both
actually request for twenty ids spread across a 20-million-wide range, which is the
claim the paged index exists to make.
Concurrency: the two-thread handoff runs for both ring variants, also under
ThreadSanitizer (-DTHREAD_SANITIZER=ON).
Constant evaluation: pinned with static_assert rather than an ordinary test,
because a runtime call passes whether or not the function can be constant-evaluated,
so only a compile-time assertion proves the claim. inline_vector, sparse_set, and
sparse_map each build, mutate, erase from and clone a container inside a
consteval lambda; spsc_ring is pinned with a constinit global instead, that
being as far as atomics allow. The non-trivial fallback is covered too: a
std::string element takes the byte-array branch, which cannot constant-evaluate but
must still move and clone correctly at run time.
The opt-in storage is pinned the same way, over both shapes the default trait turns
away: constexpr_inline_vector runs the same erase-and-clone round trip over an
aggregate with member initialisers and over a user-provided constexpr constructor,
and a sparse_map over constexpr_inline_storage does it with such a type as its
payload. What it costs is a runtime test rather than a static_assert, since
counting the default constructions is exactly what a static_assert cannot do.
Two helper types carry most of the weight. A counting payload proves no leaks and
no double destroys; a payload whose move throws once a budget runs out is what
actually instantiates the rollback branch of relocate_grow, which every
nothrow-move payload leaves discarded and therefore never even type-checked. An
auditing memory_resource records the exact (size, alignment) of every live block
and checks the pair it is given back, since -fsized-deallocation is on and a size
mismatch reaching deallocate is undefined behaviour that neither ASan nor
arena's no-op deallocate would surface.
Coverage-guided libFuzzer harnesses drive multislab, pool, sparse_set,
and sparse_map under ASan + UBSan (-DLIBMEM_BUILD_FUZZERS=ON, Clang only, Debug
only). CI runs a short, time-boxed smoke pass on each. The two sparse harnesses are
differential, comparing every operation against a standard container.
They currently drive the flat sparse index only. See fuzz/README.md.
Planned: harnesses for basic_vector and for the paged sparse index.
This is an early-stage library and a C++26 modules testbed. Treat the above as what is exercised today, not a guarantee of exhaustive coverage.