2026-07-29 17:29:49 -04:00
|
|
|
# Repository Guidelines
|
|
|
|
|
|
|
|
|
|
## Project Structure & Module Organization
|
|
|
|
|
|
Version at 0.2.0: complete the wishlist, document it, gate the docs
Closes what was left of TODO.md sections 1, 2 and 3, and rewrites that
file to hold outstanding items only.
The API break gets a minor bump, because pre-1.0 the soname carries
MAJOR.MINOR and 0.1 and 0.2 are therefore different ABIs. Five
signatures changed and the ato* contract with them; UPGRADING.md is new
and lists every one, with the before/after for the cases the compiler
cannot warn about.
Section 3.1 is finished: reallocarray with the multiplication checked,
aligned_alloc and posix_memalign, asprintf/vasprintf, scanf/vscanf.
Four functions on that list are deliberately absent rather than missing
-- sprintf, strtok, setbuf and perror -- and TODO.md now says which and
why, so nobody adds them thinking they were forgotten.
Section 1.9, the cross-cutting tests:
tests/test_pool.c drives every failure path AKERR_MAX_ARRAY_ERROR
+ 10 times and checks the pool after each round,
because a wrapper that leaks a slot fails a
hundred calls later in unrelated code. It also
asserts that each error names the function and
file it was raised from, which is what catches a
FAIL that migrates into a helper during a
refactor: status right, message right, origin
quietly lying.
tests/negative/ two sources that must FAIL to compile, built with
-Werror and registered WILL_FAIL. AKERR_NOIGNORE
and the format attributes are enforced by the
compiler and by nothing else; drop either and
every ordinary test still passes.
Thread safety is answered rather than tested: the library is not
thread-safe and cannot be made so from here, because libakerror's error
pool is an unlocked process-global array. README.md says so plainly and
TODO.md carries it as the item blocking any future pthread wrappers.
Doxygen is configured and gated. All 147 public functions have @brief,
a @param each, @throws per status and @return; EXTRACT_ALL is off and
WARN_NO_PARAMDOC on, so `cmake --build build --target docs` fails on an
undocumented entity. It ran to 0 warnings. The Doxyfile carries no
version -- cmake/RunDoxygen.cmake feeds PROJECT_NUMBER in from
project(), so that stays the one place a version is written.
CI now builds against the submodule it pins instead of also installing
libakerror@main and never linking it, adds -Werror, and gains a
sanitizer job. The pre-push hook matches, and runs the docs check too.
Coverage: 99.5% of lines (1643/1651), 100% of functions (147/147). The
eight uncovered lines are each uncovered on purpose and TODO.md says
which and why.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:00:16 -04:00
|
|
|
`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
|
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
|
|
|
*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
|
Version at 0.2.0: complete the wishlist, document it, gate the docs
Closes what was left of TODO.md sections 1, 2 and 3, and rewrites that
file to hold outstanding items only.
The API break gets a minor bump, because pre-1.0 the soname carries
MAJOR.MINOR and 0.1 and 0.2 are therefore different ABIs. Five
signatures changed and the ato* contract with them; UPGRADING.md is new
and lists every one, with the before/after for the cases the compiler
cannot warn about.
Section 3.1 is finished: reallocarray with the multiplication checked,
aligned_alloc and posix_memalign, asprintf/vasprintf, scanf/vscanf.
Four functions on that list are deliberately absent rather than missing
-- sprintf, strtok, setbuf and perror -- and TODO.md now says which and
why, so nobody adds them thinking they were forgotten.
Section 1.9, the cross-cutting tests:
tests/test_pool.c drives every failure path AKERR_MAX_ARRAY_ERROR
+ 10 times and checks the pool after each round,
because a wrapper that leaks a slot fails a
hundred calls later in unrelated code. It also
asserts that each error names the function and
file it was raised from, which is what catches a
FAIL that migrates into a helper during a
refactor: status right, message right, origin
quietly lying.
tests/negative/ two sources that must FAIL to compile, built with
-Werror and registered WILL_FAIL. AKERR_NOIGNORE
and the format attributes are enforced by the
compiler and by nothing else; drop either and
every ordinary test still passes.
Thread safety is answered rather than tested: the library is not
thread-safe and cannot be made so from here, because libakerror's error
pool is an unlocked process-global array. README.md says so plainly and
TODO.md carries it as the item blocking any future pthread wrappers.
Doxygen is configured and gated. All 147 public functions have @brief,
a @param each, @throws per status and @return; EXTRACT_ALL is off and
WARN_NO_PARAMDOC on, so `cmake --build build --target docs` fails on an
undocumented entity. It ran to 0 warnings. The Doxyfile carries no
version -- cmake/RunDoxygen.cmake feeds PROJECT_NUMBER in from
project(), so that stays the one place a version is written.
CI now builds against the submodule it pins instead of also installing
libakerror@main and never linking it, adds -Werror, and gains a
sanitizer job. The pre-push hook matches, and runs the docs check too.
Coverage: 99.5% of lines (1643/1651), 100% of functions (147/147). The
eight uncovered lines are each uncovered on purpose and TODO.md says
which and why.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:00:16 -04:00
|
|
|
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/`.
|
2026-07-29 17:29:49 -04:00
|
|
|
|
|
|
|
|
## Build, Test, and Development Commands
|
|
|
|
|
|
|
|
|
|
Initialize dependencies before a fresh build:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
cmake -S . -B build-asan -DAKSL_SANITIZE=ON
|
|
|
|
|
cmake --build build-asan
|
|
|
|
|
ctest --test-dir build-asan --output-on-failure
|
|
|
|
|
```
|
|
|
|
|
|
Add code coverage to the CTest suite
New AKSL_COVERAGE option instruments the library and its tests with
--coverage -O0 and wires the report into the suite itself, so a plain
ctest --test-dir build-coverage both runs the tests and produces coverage.
Two CTest entries do the work, held in order by a CTest fixture rather
than by declaration order so they also hold under ctest -j:
coverage_reset (FIXTURES_SETUP) clears the .gcda counters before any test,
since gcov counts are cumulative and would otherwise fold in earlier runs;
coverage_report (FIXTURES_CLEANUP) aggregates gcov output afterwards.
AKSL_COVERAGE_THRESHOLD / AKSL_COVERAGE_BRANCH_THRESHOLD gate the report,
the same regression-ratchet idea as the mutation score. The `coverage`
target builds, runs and prints in one step.
scripts/coverage.py parses gcov's JSON output, aggregates line, branch and
function counts across translation units, and lists every uncovered line
and never-called function -- the actionable half, as with surviving
mutants. Python stdlib plus gcc's own gcov only: no lcov, gcovr or
genhtml. It also writes coverage-summary.txt (CTest hides the output of a
passing test) and a Cobertura coverage.xml for CI publishers.
Instrumentation is per target, so deps/libakerror stays out of the report.
The mutation harness now ignores build*/ and gcov artifacts when copying
the tree, so a coverage build does not slow it down.
Baseline on src/stdlib.c: 52.0% of lines, 23.6% of branches, 8 of 21
functions. The uncovered functions are the untested wrappers the mutation
survivors already point at (printf, ato*, stream, realpath, strhash).
Verified:
cmake -S . -B build-coverage -DAKSL_COVERAGE=ON
cmake --build build-coverage --target coverage # 8/8, report printed
ctest --test-dir build-coverage -j8 # fixture order holds
cmake -S . -B build-coverage -DAKSL_COVERAGE=ON -DAKSL_COVERAGE_THRESHOLD=60
ctest --test-dir build-coverage --output-on-failure # gate fails as expected
ctest --test-dir build --output-on-failure # 6/6, no .gcda emitted
ctest --test-dir build-asan --output-on-failure # 6/6
scripts/mutation_test.py --target src/stdlib.c --list # 173 mutants, unchanged
Totals match gcov itself: 51.98% of 202 lines, 23.60% of 661 branches.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 01:48:05 -04:00
|
|
|
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`:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
cmake -S . -B build-coverage -DAKSL_COVERAGE=ON
|
|
|
|
|
cmake --build build-coverage --target coverage
|
|
|
|
|
```
|
|
|
|
|
|
2026-07-29 17:29:49 -04:00
|
|
|
Run `cmake --build build --target mutation` only when you need the slower
|
Version at 0.2.0: complete the wishlist, document it, gate the docs
Closes what was left of TODO.md sections 1, 2 and 3, and rewrites that
file to hold outstanding items only.
The API break gets a minor bump, because pre-1.0 the soname carries
MAJOR.MINOR and 0.1 and 0.2 are therefore different ABIs. Five
signatures changed and the ato* contract with them; UPGRADING.md is new
and lists every one, with the before/after for the cases the compiler
cannot warn about.
Section 3.1 is finished: reallocarray with the multiplication checked,
aligned_alloc and posix_memalign, asprintf/vasprintf, scanf/vscanf.
Four functions on that list are deliberately absent rather than missing
-- sprintf, strtok, setbuf and perror -- and TODO.md now says which and
why, so nobody adds them thinking they were forgotten.
Section 1.9, the cross-cutting tests:
tests/test_pool.c drives every failure path AKERR_MAX_ARRAY_ERROR
+ 10 times and checks the pool after each round,
because a wrapper that leaks a slot fails a
hundred calls later in unrelated code. It also
asserts that each error names the function and
file it was raised from, which is what catches a
FAIL that migrates into a helper during a
refactor: status right, message right, origin
quietly lying.
tests/negative/ two sources that must FAIL to compile, built with
-Werror and registered WILL_FAIL. AKERR_NOIGNORE
and the format attributes are enforced by the
compiler and by nothing else; drop either and
every ordinary test still passes.
Thread safety is answered rather than tested: the library is not
thread-safe and cannot be made so from here, because libakerror's error
pool is an unlocked process-global array. README.md says so plainly and
TODO.md carries it as the item blocking any future pthread wrappers.
Doxygen is configured and gated. All 147 public functions have @brief,
a @param each, @throws per status and @return; EXTRACT_ALL is off and
WARN_NO_PARAMDOC on, so `cmake --build build --target docs` fails on an
undocumented entity. It ran to 0 warnings. The Doxyfile carries no
version -- cmake/RunDoxygen.cmake feeds PROJECT_NUMBER in from
project(), so that stays the one place a version is written.
CI now builds against the submodule it pins instead of also installing
libakerror@main and never linking it, adds -Werror, and gains a
sanitizer job. The pre-push hook matches, and runs the docs check too.
Coverage: 99.5% of lines (1643/1651), 100% of functions (147/147). The
eight uncovered lines are each uncovered on purpose and TODO.md says
which and why.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:00:16 -04:00
|
|
|
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.
|
2026-07-29 17:29:49 -04:00
|
|
|
|
|
|
|
|
## 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.
|
|
|
|
|
|
Version at 0.2.0: complete the wishlist, document it, gate the docs
Closes what was left of TODO.md sections 1, 2 and 3, and rewrites that
file to hold outstanding items only.
The API break gets a minor bump, because pre-1.0 the soname carries
MAJOR.MINOR and 0.1 and 0.2 are therefore different ABIs. Five
signatures changed and the ato* contract with them; UPGRADING.md is new
and lists every one, with the before/after for the cases the compiler
cannot warn about.
Section 3.1 is finished: reallocarray with the multiplication checked,
aligned_alloc and posix_memalign, asprintf/vasprintf, scanf/vscanf.
Four functions on that list are deliberately absent rather than missing
-- sprintf, strtok, setbuf and perror -- and TODO.md now says which and
why, so nobody adds them thinking they were forgotten.
Section 1.9, the cross-cutting tests:
tests/test_pool.c drives every failure path AKERR_MAX_ARRAY_ERROR
+ 10 times and checks the pool after each round,
because a wrapper that leaks a slot fails a
hundred calls later in unrelated code. It also
asserts that each error names the function and
file it was raised from, which is what catches a
FAIL that migrates into a helper during a
refactor: status right, message right, origin
quietly lying.
tests/negative/ two sources that must FAIL to compile, built with
-Werror and registered WILL_FAIL. AKERR_NOIGNORE
and the format attributes are enforced by the
compiler and by nothing else; drop either and
every ordinary test still passes.
Thread safety is answered rather than tested: the library is not
thread-safe and cannot be made so from here, because libakerror's error
pool is an unlocked process-global array. README.md says so plainly and
TODO.md carries it as the item blocking any future pthread wrappers.
Doxygen is configured and gated. All 147 public functions have @brief,
a @param each, @throws per status and @return; EXTRACT_ALL is off and
WARN_NO_PARAMDOC on, so `cmake --build build --target docs` fails on an
undocumented entity. It ran to 0 warnings. The Doxyfile carries no
version -- cmake/RunDoxygen.cmake feeds PROJECT_NUMBER in from
project(), so that stays the one place a version is written.
CI now builds against the submodule it pins instead of also installing
libakerror@main and never linking it, adds -Werror, and gains a
sanitizer job. The pre-push hook matches, and runs the docs check too.
Coverage: 99.5% of lines (1643/1651), 100% of functions (147/147). The
eight uncovered lines are each uncovered on purpose and TODO.md says
which and why.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:00:16 -04:00
|
|
|
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.
|
|
|
|
|
|
2026-07-29 17:29:49 -04:00
|
|
|
## 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
|
Version at 0.2.0: complete the wishlist, document it, gate the docs
Closes what was left of TODO.md sections 1, 2 and 3, and rewrites that
file to hold outstanding items only.
The API break gets a minor bump, because pre-1.0 the soname carries
MAJOR.MINOR and 0.1 and 0.2 are therefore different ABIs. Five
signatures changed and the ato* contract with them; UPGRADING.md is new
and lists every one, with the before/after for the cases the compiler
cannot warn about.
Section 3.1 is finished: reallocarray with the multiplication checked,
aligned_alloc and posix_memalign, asprintf/vasprintf, scanf/vscanf.
Four functions on that list are deliberately absent rather than missing
-- sprintf, strtok, setbuf and perror -- and TODO.md now says which and
why, so nobody adds them thinking they were forgotten.
Section 1.9, the cross-cutting tests:
tests/test_pool.c drives every failure path AKERR_MAX_ARRAY_ERROR
+ 10 times and checks the pool after each round,
because a wrapper that leaks a slot fails a
hundred calls later in unrelated code. It also
asserts that each error names the function and
file it was raised from, which is what catches a
FAIL that migrates into a helper during a
refactor: status right, message right, origin
quietly lying.
tests/negative/ two sources that must FAIL to compile, built with
-Werror and registered WILL_FAIL. AKERR_NOIGNORE
and the format attributes are enforced by the
compiler and by nothing else; drop either and
every ordinary test still passes.
Thread safety is answered rather than tested: the library is not
thread-safe and cannot be made so from here, because libakerror's error
pool is an unlocked process-global array. README.md says so plainly and
TODO.md carries it as the item blocking any future pthread wrappers.
Doxygen is configured and gated. All 147 public functions have @brief,
a @param each, @throws per status and @return; EXTRACT_ALL is off and
WARN_NO_PARAMDOC on, so `cmake --build build --target docs` fails on an
undocumented entity. It ran to 0 warnings. The Doxyfile carries no
version -- cmake/RunDoxygen.cmake feeds PROJECT_NUMBER in from
project(), so that stays the one place a version is written.
CI now builds against the submodule it pins instead of also installing
libakerror@main and never linking it, adds -Werror, and gains a
sanitizer job. The pre-push hook matches, and runs the docs check too.
Coverage: 99.5% of lines (1643/1651), 100% of functions (147/147). The
eight uncovered lines are each uncovered on purpose and TODO.md says
which and why.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:00:16 -04:00
|
|
|
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.
|
2026-07-29 17:29:49 -04:00
|
|
|
|
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
|
|
|
`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`.
|
|
|
|
|
|
Version at 0.2.0: complete the wishlist, document it, gate the docs
Closes what was left of TODO.md sections 1, 2 and 3, and rewrites that
file to hold outstanding items only.
The API break gets a minor bump, because pre-1.0 the soname carries
MAJOR.MINOR and 0.1 and 0.2 are therefore different ABIs. Five
signatures changed and the ato* contract with them; UPGRADING.md is new
and lists every one, with the before/after for the cases the compiler
cannot warn about.
Section 3.1 is finished: reallocarray with the multiplication checked,
aligned_alloc and posix_memalign, asprintf/vasprintf, scanf/vscanf.
Four functions on that list are deliberately absent rather than missing
-- sprintf, strtok, setbuf and perror -- and TODO.md now says which and
why, so nobody adds them thinking they were forgotten.
Section 1.9, the cross-cutting tests:
tests/test_pool.c drives every failure path AKERR_MAX_ARRAY_ERROR
+ 10 times and checks the pool after each round,
because a wrapper that leaks a slot fails a
hundred calls later in unrelated code. It also
asserts that each error names the function and
file it was raised from, which is what catches a
FAIL that migrates into a helper during a
refactor: status right, message right, origin
quietly lying.
tests/negative/ two sources that must FAIL to compile, built with
-Werror and registered WILL_FAIL. AKERR_NOIGNORE
and the format attributes are enforced by the
compiler and by nothing else; drop either and
every ordinary test still passes.
Thread safety is answered rather than tested: the library is not
thread-safe and cannot be made so from here, because libakerror's error
pool is an unlocked process-global array. README.md says so plainly and
TODO.md carries it as the item blocking any future pthread wrappers.
Doxygen is configured and gated. All 147 public functions have @brief,
a @param each, @throws per status and @return; EXTRACT_ALL is off and
WARN_NO_PARAMDOC on, so `cmake --build build --target docs` fails on an
undocumented entity. It ran to 0 warnings. The Doxyfile carries no
version -- cmake/RunDoxygen.cmake feeds PROJECT_NUMBER in from
project(), so that stays the one place a version is written.
CI now builds against the submodule it pins instead of also installing
libakerror@main and never linking it, adds -Werror, and gains a
sanitizer job. The pre-push hook matches, and runs the docs check too.
Coverage: 99.5% of lines (1643/1651), 100% of functions (147/147). The
eight uncovered lines are each uncovered on purpose and TODO.md says
which and why.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:00:16 -04:00
|
|
|
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
|
Test the libc wrappers: 52% -> 99% line coverage
Every wrapper outside the list and tree code was untested. Six new test
files close that, following the plan already written in TODO.md 1.2-1.6:
test_stream.c fopen/fread/fwrite/fclose -- happy paths, the round
trip, AKERR_EOF on a short read, AKERR_IO on a stream
opened in the wrong mode, ENOENT, and the NULL guards
test_format.c printf/fprintf/sprintf -- text *and* count asserted
(stdout is pointed at a temp file to check aksl_printf),
all eight NULL guards, EBADF on a read-only stream, and
512 variadic calls in a loop as sanitizer cover for the
missing va_end
test_convert.c ato{i,l,ll,f} happy paths, negatives, leading
whitespace, NULL guards
test_path.c realpath on a file and on a symlink, both compared
against realpath(3) since TMPDIR may itself be a link;
ENOENT, ENOTDIR, NULL path
test_strhash.c djb2 known-answer vectors, len == 0, embedded NUL,
stability, NULL guards
test_convert_strict.c
known-failing (2.1.5): the AKERR_VALUE / ERANGE
contract the ato* family cannot express today
test_tree.c gains the BFS AKERR_NOT_IMPLEMENTED contract, NULL arguments,
and a callback error that is not AKERR_ITERATOR_BREAK propagating out.
Tests deliberately say nothing about behaviour TODO.md records as
defective -- unchecked ptr/mode/resolved_path, short transfers reported as
success, *count left at -1, the djb2 sign extension -- so the eventual fix
does not have to come with a test rewrite. Each failure case in
test_path.c passes a zeroed buffer, because the wrapper's own error path
formats resolved_path with %s (2.1.6).
aksl_capture.h gains aksl_temp_file() with an atexit unlink backstop.
Without it every test that fails before its own unlink leaves temp files
behind -- which is the normal case for a known-failing test, and happens
173 times over in a mutation run.
Coverage on src/stdlib.c: 52.0% -> 99.0% of lines (200/202), 23.6% ->
51.0% of branches, 8/21 -> 21/21 functions. The two uncovered lines are
both `} HANDLE(e, AKERR_ITERATOR_BREAK) {`, where the macro starts with
the `break;` of PROCESS's `case 0:` arm -- reachable only via a non-NULL
error context whose status is zero, the pathology 2.2.1 exists to remove.
Mutation score on src/stdlib.c: 46.8% -> 89.6% (155/173 killed). CI, the
pre-push hook and the docs ratchet from 40 to 80 accordingly, and the 18
survivors are grouped by cause in TODO.md and README.md. A new CI
coverage job gates at 90% lines / 45% branches.
Verified:
ctest --test-dir build # 12/12
ctest --test-dir build-asan # 12/12 under ASan + UBSan
ctest --test-dir build-coverage # 14/14, report attached
ctest --test-dir build -j8 --repeat until-fail:3
gcc -Wall -Wextra -c on all nine test files # no warnings
python3 scripts/mutation_test.py --target src/stdlib.c # 89.6%
No temp files left in /tmp after any of the above.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:07:08 -04:00
|
|
|
`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.
|
|
|
|
|
|
2026-07-29 17:29:49 -04:00
|
|
|
## 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.
|