Files
libakstdlib/AGENTS.md
Andrew Kesterson 7940276f87 Test the installed package, not just the build tree
Found by building a consumer against a fresh install: an installed
libakstdlib was not usable. akstdlibConfig.cmake calls
find_dependency(akerror), and the top-level build pulls the submodule in
EXCLUDE_FROM_ALL, so `cmake --install` on this project installs only
this project and leaves that dependency unresolvable.

CI had been hiding it by installing libakerror@main in a separate step
-- a different libakerror from the one actually compiled, which is the
inconsistency TODO.md 2.3 recorded and the previous commit removed.
Removing it exposed the real gap.

CI now installs deps/libakerror, the same commit the top-level build
compiles, so there is one libakerror in play and the install is
consumable.

tests/consumer/ is the check that would have caught it: a standalone
project, configured against CMAKE_PREFIX_PATH rather than as part of
this build, because being part of this build is exactly what would let
it pass without testing anything. It exercises what only an install has
-- find_package with a version request against the generated version
file, find_dependency(akerror) resolving, and the exported
akstdlib::akstdlib target -- and touches one function from each of the
four sources, so a library installed with a source file missing from its
link line fails there rather than in the next consumer to find it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:05:09 -04:00

6.4 KiB

Repository Guidelines

Project Structure & Module Organization

libakstdlib is a C shared library that wraps libc calls and data structures in libakerror error contexts.

Public API declarations live in include/akstdlib.h, which is also where the Doxygen documentation lives -- the header is the contract, the sources carry the reasoning. The implementation is split by domain:

File Covers
src/stdlib.c memory, formatted output, string-to-number, realpath, djb2, and the list/tree traversal entry points
src/string.c the string.h surface
src/stream.c stdio.h beyond open/read/write/close
src/collections.c list and tree operations, hash map, string buffer, FNV-1a
src/aksl_internal.h shared internals; not installed, not public

Tests are one-file CTest executables under tests/test_<name>.c, with shared helpers in tests/aksl_capture.h. tests/negative/ holds sources that must fail to compile, and tests/consumer/ is a standalone project built against the installed package rather than the build tree -- see the testing section below. CMake package templates are in cmake/ and akstdlib.pc.in. The vendored dependency is deps/libakerror; update it as a submodule rather than editing generated files under build/, build-asan/ or build-coverage/.

Build, Test, and Development Commands

Initialize dependencies before a fresh build:

git submodule update --init --recursive
cmake -S . -B build
cmake --build build

Run the normal suite with ctest --test-dir build --output-on-failure. Use the instrumented build for memory and undefined-behavior checks:

cmake -S . -B build-asan -DAKSL_SANITIZE=ON
cmake --build build-asan
ctest --test-dir build-asan --output-on-failure

For coverage, configure a third tree; the report is part of that suite (the coverage_reset / coverage_report CTest entries) and also lands in build-coverage/coverage-summary.txt:

cmake -S . -B build-coverage -DAKSL_COVERAGE=ON
cmake --build build-coverage --target coverage

Run cmake --build build --target mutation only when you need the slower mutation harness, and cmake --build build --target docs to regenerate the API documentation -- that one fails if any public function, parameter or return value is undocumented, so it is a check as well as a generator.

.githooks/pre-push runs the default build, the sanitizer build and the documentation check before a push; enable it with git config core.hooksPath .githooks. rebuild.sh installs to /home/andrew/local and removes the local build directory, so treat it as a local convenience script.

Coding Style & Naming Conventions

Use C with 4-space indentation; existing files sometimes use tabs for continued statements, so match the surrounding block. Public symbols use the aksl_ prefix, structs use aksl_<Name>, and tests use test_<feature>.c plus static test_<case> functions. Preserve the akerr_ErrorContext AKERR_NOIGNORE * return convention and the PREPARE_ERROR / FAIL_* / SUCCEED_RETURN pattern.

Four conventions hold across the whole library, and a new wrapper that breaks one of them is wrong even if it compiles and passes:

  • A NULL out-param is a caller error, not "don't care".
  • Finding nothing is success -- searching functions write NULL or zero and return NULL.
  • Truncation is a failure. Take the destination's size, raise AKERR_OUTOFBOUNDS, and write nothing rather than a prefix.
  • Clear errno before the wrapped call and read it back through AKSL_ERRNO_OR, so no error can carry status 0 -- which every downstream DETECT reads as success.

Every new public function needs a Doxygen block on its declaration with @brief, a @param per parameter, a @throws per status it can raise, and @return. The docs target fails otherwise.

The build is -Wall -Wextra and CI adds -Werror. -Wpedantic is deliberately off: libakerror's FAIL_* macros trip "ISO C99 requires at least one argument for the ..." on their own expansion, not on anything at the call site.

Testing Guidelines

Add a new test by creating tests/test_mything.c and adding mything to the right list in CMakeLists.txt. AKSL_TESTS must exit zero. AKSL_WILL_FAIL_TESTS are deliberate abort/contract tests. AKSL_KNOWN_FAILING_TESTS assert documented defects from TODO.md; when one starts unexpectedly passing, move it into AKSL_TESTS with the fix. Both of the latter are currently empty -- all six confirmed defects are fixed -- but the mechanism stays for the next one.

tests/negative/ holds sources that must fail to compile. Each is an EXCLUDE_FROM_ALL target built with -Werror and registered as a WILL_FAIL CTest entry, so the test passes only when the compile fails. They cover the two guarantees the compiler enforces and nothing else does: AKERR_NOIGNORE and the format attributes. Drop either in a refactor and every ordinary test still passes.

tests/consumer/ is configured standalone against CMAKE_PREFIX_PATH, not as part of this build, because being part of this build is what would let it pass without testing anything. It covers the paths only an install has: the version file, find_dependency(akerror) resolving, and the exported akstdlib::akstdlib target. CI runs it after cmake --install.

Coverage is 99.5% of lines and 100% of functions across all four sources; CI gates at 90 (line) / 40 (branch), so new code needs tests in the same commit. Run cmake --build build-coverage --target coverage and check the uncovered-line listing before proposing a change. Tests for behaviour that TODO.md records as defective belong in AKSL_KNOWN_FAILING_TESTS asserting the correct contract — do not pin current-but-wrong behaviour in AKSL_TESTS, since that turns the eventual fix into a test failure.

Commit & Pull Request Guidelines

Recent commits use short imperative summaries, for example Add memory wrapper tests and Make error-status assertions authoritative. Keep commits focused and include tests with behavior changes. Pull requests should describe the changed API or behavior, list the CTest/sanitizer/mutation commands run, and link the relevant TODO.md item or issue when fixing a known defect.

Agent-Specific Instructions

Do not modify generated build trees, profiling artifacts, or untracked scratch files unless explicitly asked. Prefer small, test-backed changes and update README.md or TODO.md when changing documented workflows or known failures.