How heapline is tested

Each push to main builds heapline with GCC and Clang and runs its tests. They check exact counts wherever the numbers are exact, and the documented bounds where they are not, such as the peak with several threads (see Output format). Two more builds run the code under sanitizers.

What the tests check

Area

Tests

What they check

Entry points

alloc_functions, alloc_pattern

Each allocation function on its own, with realloc growing, shrinking and moving a block, failed allocations and free_sized: one allocation or release each, and a change of the live bytes equal to the usable size, exactly. Then a known pattern of all of them together

Threads

alloc_threads, stale_thread, peak_threads_bound, peak_threads_monotonic, peak_time_threads

Exact totals with threads that exit holding blocks, a thread that keeps its counters after its exit and whose stack is reused, the peak within its bound and never decreasing, the time of the peak when threads exit

Fork

fork_child

A child forked while other threads are alive starts a thread, reads the counters without hanging, and holds the counts of its parent once

Records

record_*, record, peak_time_early, peak_time_late

The rank read from each launcher variable and their order, the fallback to stderr, the CSV under a locale that writes a decimal comma, the time of an early and a late peak

Report

report_*

heapline-report against recorded jobs: summary, --top, --all, --csv, and the warnings for missing, duplicate and huge ranks, truncated records, unknown versions and several jobs

Packaging

library_dependencies, report_pie, man_page

The library needs the C library only, heapline-report is a position independent executable, and its manual page renders without warnings. The release workflow also checks that the archives need nothing newer than glibc 2.17, see Getting started, and installs, runs and removes the RPM on Rocky Linux 8 and 9 and the Debian package on Debian 12 and Ubuntu 22.04

Tutorials

CI job MPI example

The MPI example on four ranks and its peaks, and the commands of the plain tutorial, with the relations it explains: untouched blocks not resident, an early and a late peak, a wrapper that overwrites the record

Tests that need more address space than the machine allows, such as the 4 GiB block of alloc_threads, skip themselves rather than fail.

Sanitizers

Undefined behavior

The whole suite is built with -fsanitize=undefined -fno-sanitize-recover=all, and the library is preloaded as usual. The first undefined behavior, in the library or in a test, aborts the test. library_dependencies is skipped: the sanitizer runtime adds a dependency.

Data races

ThreadSanitizer cannot check the library while it replaces malloc. Its runtime allocates while it starts a thread, before the thread exists for it, and the code of heapline that runs at that moment gives false races and crashes.

tsan_stress builds the library into the test instead, with its entry points renamed (malloc becomes heapline_test_malloc), and calls them itself. Waves of threads allocate, reallocate and release blocks, release blocks of other threads and exit holding blocks, while a reader takes snapshots and the main thread forks once per wave. TSan checks the registration and exit of threads, the snapshots and the fork handlers; the test checks that the peak never decreases nor falls below the live bytes, the counts of each forked child, and the exact final totals.

CI builds it with Clang 18. With the TSan runtime of GCC 13 and 14, about one forked child in a hundred hangs in the TSan allocator, whatever heapline does.

What this does not cover: under TSan, only the operations of the test are counted, not those of glibc, and the preloaded library itself is not checked for races. The thread tests above cover it, without a sanitizer, and so does the undefined behavior build.

Run them yourself

The tests:

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failure

The data race test, with Clang 18 or newer:

cmake -S . -B build-tsan -DCMAKE_BUILD_TYPE=RelWithDebInfo \
  -DCMAKE_CXX_COMPILER=clang++ -DHEAPLINE_ENABLE_TSAN=ON
cmake --build build-tsan --target tsan_stress
ctest --test-dir build-tsan --output-on-failure -R tsan_stress

The undefined behavior build, with GCC:

SANITIZE="-fsanitize=undefined -fno-sanitize-recover=all"
cmake -S . -B build-ubsan -DCMAKE_BUILD_TYPE=RelWithDebInfo \
  -DCMAKE_CXX_COMPILER=g++ "-DCMAKE_CXX_FLAGS=$SANITIZE" \
  "-DCMAKE_SHARED_LINKER_FLAGS=$SANITIZE" "-DCMAKE_EXE_LINKER_FLAGS=$SANITIZE"
cmake --build build-ubsan
ctest --test-dir build-ubsan --output-on-failure -E library_dependencies