Check memory with the suites that already exist
`cmake --build build --target memcheck` runs every registered CTest suite under valgrind. There are no new test programs, and there should not be any: tests/benchutil.h notices valgrind in LD_PRELOAD and drops the benchmark scale to 0.0005, which turns the perf suites into the broadest path coverage in the tree at a cost valgrind can survive. A benchmark walks one path a hundred thousand times; a leak check wants every path walked once. Same binaries, one flag apart, and the whole run is about thirty seconds. scripts/memcheck.sh wraps `ctest -T memcheck` because that command records defects and still exits 0, which cannot gate anything. It also forces the headless drivers, so the vendor GPU stack is never loaded -- that removes thousands of unfixable findings inside amdgpu_dri.so without suppressing anything, and it is what the suites are written for anyway. Only definite losses, invalid accesses and uninitialised reads count; "still reachable" is what SDL and FreeType keep for the process lifetime and says nothing about this library. The three suppressions in scripts/valgrind.supp are each a decision that a finding belongs to somebody else. Six defects, all filed in TODO.md under "Memory checking", the first of which is the one that matters: not one of the four json_load_file calls in src/ is ever matched by a json_decref, so every asset load abandons its parsed document -- 1.5 KB per sprite, 2.1 KB per character, 9 KB per load of the 2x2 fixture map, and on the order of a megabyte for a real one. A game that reloads a level on death leaks a level's worth of JSON every time. akgl_get_property also reads up to 4 KiB past the end of every property value it copies, and the savegame name tables read past the end of every registry key and write what they find into the file. tests/util.c zeroes three fixtures it used to leave as stack garbage. The library was never at fault there -- the tests handed it uninitialised floats and then made one real call with them -- but sixteen findings of noise in a new gate is how a gate gets ignored. The unit suites' TIMEOUT goes from 30 to 300 because CTest applies the same property to the checked run, where everything is twenty times slower. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,37 @@ cmake_minimum_required(VERSION 3.10)
|
||||
# Version field in akgl.pc. Bump it here and nowhere else.
|
||||
project(akgl VERSION 0.3.0 LANGUAGES C)
|
||||
|
||||
# Memory checking reuses the suites that already exist -- `ctest -T memcheck`
|
||||
# runs every registered test under valgrind -- rather than adding programs of its
|
||||
# own. The perf suites carry most of the weight there: they are the only things
|
||||
# in the tree that load assets, draw a scene, and run a frame loop in one
|
||||
# process, and tests/benchutil.h drops their iteration counts by three orders of
|
||||
# magnitude when it finds itself under valgrind, which turns a benchmark into
|
||||
# exactly the broad, once-through path coverage a leak check wants.
|
||||
#
|
||||
# These have to be set before include(CTest): that is what writes them into
|
||||
# DartConfiguration.tcl, and a memcheck run reads them from there.
|
||||
find_program(MEMORYCHECK_COMMAND valgrind)
|
||||
# Set with FORCE, but only when empty. include(CTest) declares both of these as
|
||||
# empty cache entries, so a build tree configured before this block existed has
|
||||
# them already and a plain set(... CACHE ...) would be ignored -- the values
|
||||
# would silently never reach DartConfiguration.tcl. Guarding on emptiness still
|
||||
# leaves a deliberate -DMEMORYCHECK_SUPPRESSIONS_FILE=... alone.
|
||||
if(NOT MEMORYCHECK_SUPPRESSIONS_FILE)
|
||||
set(MEMORYCHECK_SUPPRESSIONS_FILE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/scripts/valgrind.supp"
|
||||
CACHE FILEPATH "Suppressions for third-party findings the memcheck run cannot fix" FORCE)
|
||||
endif()
|
||||
# Definite losses only. "Still reachable" is every global SDL and FreeType keeps
|
||||
# for the process lifetime and says nothing about libakgl; "possibly lost" is
|
||||
# dominated by interior pointers into pools and thread stacks. Neither is worth
|
||||
# the false positives.
|
||||
if(NOT MEMORYCHECK_COMMAND_OPTIONS)
|
||||
set(MEMORYCHECK_COMMAND_OPTIONS
|
||||
"--leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite --track-origins=yes --num-callers=25"
|
||||
CACHE STRING "Options passed to the memory checker" FORCE)
|
||||
endif()
|
||||
|
||||
include(CTest)
|
||||
option(AKGL_COVERAGE "Instrument libakgl and generate coverage reports with CTest" OFF)
|
||||
|
||||
@@ -229,9 +260,13 @@ endforeach()
|
||||
|
||||
add_test(NAME semver_unit COMMAND akgl_test_semver_unit)
|
||||
|
||||
# TIMEOUT is generous because CTest applies the same property to `ctest -T
|
||||
# memcheck`, where every suite runs under valgrind at something like twenty
|
||||
# times its normal cost. The unit suites finish in well under a second each
|
||||
# without it; the ceiling is there for the checked run, not the ordinary one.
|
||||
set_tests_properties(
|
||||
${AKGL_TEST_SUITES} semver_unit
|
||||
PROPERTIES WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/tests" TIMEOUT 30
|
||||
PROPERTIES WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/tests" TIMEOUT 300
|
||||
)
|
||||
|
||||
set_tests_properties(
|
||||
@@ -382,6 +417,26 @@ if(Python3_FOUND)
|
||||
)
|
||||
endif()
|
||||
|
||||
# Memory checking runs the whole registered suite under valgrind. The wrapper
|
||||
# script exists because `ctest -T memcheck` records defects and still exits 0,
|
||||
# which cannot gate anything; it also forces the headless drivers, so the vendor
|
||||
# GPU stack is never loaded and never has to be suppressed.
|
||||
if(MEMORYCHECK_COMMAND)
|
||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
set(AKGL_MEMCHECK_TARGET memcheck)
|
||||
else()
|
||||
set(AKGL_MEMCHECK_TARGET akgl_memcheck)
|
||||
endif()
|
||||
add_custom_target(${AKGL_MEMCHECK_TARGET}
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
AKGL_BUILD_DIR=${CMAKE_CURRENT_BINARY_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/scripts/memcheck.sh
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
USES_TERMINAL
|
||||
COMMENT "Running every test suite under valgrind (slow; the perf suites scale themselves down)"
|
||||
)
|
||||
endif()
|
||||
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/akgl.pc DESTINATION "lib/pkgconfig/")
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/akgl/version.h DESTINATION "include/akgl/")
|
||||
install(TARGETS akgl DESTINATION "lib/")
|
||||
|
||||
Reference in New Issue
Block a user