Files
libakerror/AGENTS.md
Andrew Kesterson 5695061130
Some checks failed
libakerror CI Build / cmake_build (push) Successful in 2m47s
libakerror CI Build / coverage (push) Successful in 2m48s
libakerror CI Build / thread_sanitizer (push) Failing after 2m49s
libakerror CI Build / mutation_test (push) Successful in 39m56s
Split the README reference material into docs/
The README was 794 lines: the summary, the design rationale, the whole macro
reference, the threading contract, the build internals and the exit-status
specification in one file. It is now 178 lines -- summary, installation,
quickstart, and an index -- and the reference material lives in docs/, one file
per topic: architecture, usage, status-codes, uncaught-errors, exit-status,
thread-safety, building.

The prose moved as written. Inbound references followed it: UPGRADING.md,
TODO.md, include/akerror.tmpl.h and tests/err_threads_handoff.c now name the
docs/ file that owns the text they cite, and AGENTS.md says where new
documentation goes so the README does not grow back.

Five factual errors fixed in the moved text:

- Both NULL-pointer examples inverted their test. FAIL_ZERO_* fails when the
  expression is zero, so `(somePointer == NULL)` failed on a *valid* pointer.
  They now read `(somePointer != NULL)`.
- AKERROR_NOIGNORE, four times including the #define, is AKERR_NOIGNORE.
- FINISH_NORExbTURN is FINISH_NORETURN.
- "functiions" is "functions".
- The architecture link pointed at include/akerror.h, which is generated and
  not in the tree; it points at include/akerror.tmpl.h.

The quickstart is new text. It compiles under -Wall -Wextra -Werror and was run
through all three of its paths: handled usage error exits 0, unhandled IO error
prints a trace and exits with the status, success exits 0.

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

6.8 KiB

Repository Guidelines

Project Structure & Module Organization

This is a small C library built with CMake. Core implementation lives in src/error.c. src/lock.h is private: it selects the threading backend (pthread or none) and is not installed. The public header is generated at build time from include/akerror.tmpl.h by scripts/generrno.sh, which also generates src/errno.c under the build directory and stamps in whether the build is thread safe. CMake package and pkg-config templates are in cmake/ and akerror.pc.in. Tests are one-file C programs in tests/; shared test helpers live beside them, such as tests/err_capture.h and tests/err_threads.h.

Prose documentation lives in docs/, one file per topic — architecture.md, usage.md, status-codes.md, uncaught-errors.md, exit-status.md, thread-safety.md, building.md. README.md is deliberately short: summary, installation, quickstart, and an index into docs/. Add new documentation to the docs/ file that owns the topic and link it from the README's index rather than growing the README back.

Build, Test, and Development Commands

Use an out-of-tree build:

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

cmake -S . -B build configures the build and generates akerror.h. cmake --build build compiles libakerror and test executables. ctest runs the registered unit tests. To emit CI-style results, use:

ctest --test-dir build --output-on-failure --output-junit "$(pwd)/ctest-junit.xml"

The suite includes thread tests. The run that proves there is no data race under them is ThreadSanitizer:

scripts/thread_test.sh

which configures build/tsan with -DAKERR_SANITIZE=thread, builds the library and the tests with it, and runs CTest with ASLR disabled (TSan aborts with an "unexpected memory mapping" on kernels with vm.mmap_rnd_bits above 28). AKERR_SANITIZE takes any sanitizer list, so -DAKERR_SANITIZE=address,undefined works the same way. CTest gives each test halt_on_error=1, so a report fails the test rather than being printed and passed over — running a sanitized test binary by hand does not inherit that. Set it yourself (TSAN_OPTIONS=halt_on_error=1 ./build/tsan/test_err_threads_pool) or the binary can print a race and still exit 0.

Mutation testing is available through:

cmake --build build --target mutation
scripts/mutation_test.py --target src/error.c --threshold 65

Code coverage is available through:

cmake --build build --target coverage
scripts/coverage.py --threshold 90 --branch-threshold 50

scripts/coverage.py configures its own instrumented build tree (default build/coverage, -DAKERR_COVERAGE=ON), runs the CTest suite there, and reports gcov line/branch coverage per library source. Thresholds gate each file as well as the total. Coverage measures the library's own sources only -- src/error.c, src/lock.h and the generated src/errno.c; the public header's macros expand at their call sites, so mutation testing is what checks those.

To build without threads (no locking, no thread-local storage, thread tests not registered), configure with -DAKERR_THREADS=none. auto is the default and fails the configure rather than falling back.

Coding Style & Naming Conventions

Use C99-compatible C and follow the surrounding style. Functions and types use the akerr_ prefix; macros and constants use AKERR_ or all-caps macro names such as PREPARE_ERROR. Keep generated-code changes in templates or generator scripts, not in build outputs. Preserve concise comments for invariants, macro constraints, and non-obvious error lifecycle behavior.

Locking. One recursive lock (akerr_state_lock, see src/lock.h) covers both the error pool and the status registry. A function named *_locked is called with that lock already held; a function without the suffix takes it. Never take it in a body written with the FAIL_*_RETURN macros — those return from the middle of the function and would skip the unlock. Split it instead: the locked body does the work, and a thin wrapper takes the lock, calls it, and releases it on the single return path. Do not call consumer code (akerr_log_method, akerr_handler_unhandled_error) while holding it.

Exiting. Never call exit() with a status value. Call akerr_exit(), which owns the one mapping from an akerr status to an exit code: an exit status is a byte, and every consumer status starts at 256, so passing a status through exit() silently truncates it — status 256 exits 0 and reports success. The only exits that bypass it are the two that have no status to map: ENSURE_ERROR_READY's pool-exhaustion abort in include/akerror.tmpl.h, and the NULL-context case in akerr_default_handler_unhandled_error() in src/error.c. This rule applies to test programs too, except where the test's whole point is to observe the raw truncation.

Testing Guidelines

Add tests as tests/err_<behavior>.c. Register each new test in the AKERR_TESTS list in CMakeLists.txt; tests that intentionally abort must also be listed in AKERR_WILL_FAIL_TESTS. Prefer focused executable tests that return zero on success and use existing helpers such as AKERR_CHECK. Run the CTest suite before submitting changes, and run mutation testing and coverage when changing core control-flow, reference counting, stack-trace, or handler behavior.

Tests that drive the library from several threads go in the AKERR_THREAD_SAFE branch of the AKERR_TESTS list — an -DAKERR_THREADS=none build has no threading to test — and use tests/err_threads.h, which runs a body on AKERR_TEST_THREADS threads that meet at a barrier first. Count failures per thread with AKERR_TCHECK rather than returning early: a thread that abandons its work leaves the others holding pool slots and turns one failure into a cascade. Anything the test shares between its own threads must go through __atomic builtins — a race in the test is still a race, and ThreadSanitizer cannot tell you whose it is. The capturing logger in err_capture.h is single-threaded (shared buffer, shared length); use akerr_thread_logger instead. Run scripts/thread_test.sh when changing anything that touches the pool, the registry, initialization, or the lock.

Commit & Pull Request Guidelines

Recent commits use short, imperative, sentence-case subjects, for example Fix refcount leak and stack-trace buffer overflow. Keep commits focused and describe the observable behavior changed. Pull requests should include a brief summary, tests run, and any compatibility impact for public macros, generated headers, installation paths, or CMake/pkg-config consumers.

Agent-Specific Instructions

Do not overwrite uncommitted user changes. Avoid editing generated files in build/; update include/akerror.tmpl.h, src/error.c, CMake files, tests, or scripts instead.