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>
144 lines
6.8 KiB
Markdown
144 lines
6.8 KiB
Markdown
# 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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
cmake --build build --target mutation
|
|
scripts/mutation_test.py --target src/error.c --threshold 65
|
|
```
|
|
|
|
Code coverage is available through:
|
|
|
|
```sh
|
|
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.
|