Build a real test harness (TODO.md section 1.0)
The suite could not fail and did not run. test_linkedlist.c asserted nothing
at all -- it printed node names and returned 0 -- so every confirmed list
defect passed it. test_tree.c was red on every run because parms.steps was
never reset between its three searches, and four libakerror tests registered
but never built, reporting Not Run. CI, meanwhile, was not fetching the
submodule, so configure failed before reaching any of it.
- tests/aksl_capture.h: AKSL_CHECK (NDEBUG-proof), AKSL_CHECK_STATUS/_OK to
run an akerror-returning call, assert its status and release the context,
a capturing akerr_log_method, aksl_slots_in_use(), and an AKSL_RUN driver
that fails any test leaking an error-pool slot.
- test_linkedlist.c: rewritten as 15 assertion cases over the append,
iterate and pop behaviour that is correct today.
- test_tree.c: each search builds its own tree and params.
- Registration driven by AKSL_TESTS, plus AKSL_WILL_FAIL_TESTS (aborts by
design) and AKSL_KNOWN_FAILING_TESTS, which marks WILL_FAIL the three new
tests asserting correct behaviour for the confirmed defects in TODO.md
2.1.1-2.1.3. Fixing a defect flips its test to "unexpectedly passed",
which is the cue to promote it into AKSL_TESTS.
- add_test/set_tests_properties are shadowed across the libakerror
add_subdirectory call: CMake cannot un-register a test and
set_tests_properties cannot cross directory scopes. The dependency has
its own CI.
- AKSL_SANITIZE=ON builds library, tests and dependency with ASan+UBSan.
- scripts/mutation_test.py ported and retargeted at src/stdlib.c; wired to
a manual `mutation` target, not a CI gate until 1.1-1.9 exist.
- CI: checkout with submodules: recursive, and ctest --output-on-failure.
ctest is now 5/5 green in both the default and sanitizer builds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 12:39:54 -04:00
|
|
|
#ifndef AKSL_TEST_CAPTURE_H
|
|
|
|
|
#define AKSL_TEST_CAPTURE_H
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Shared test helpers for libakstdlib.
|
|
|
|
|
*
|
|
|
|
|
* Modelled on libakerror's tests/err_capture.h, with additions for the shape of
|
|
|
|
|
* this library: almost every akstdlib entry point returns an
|
|
|
|
|
* akerr_ErrorContext * that the caller owns and must release, so the common
|
|
|
|
|
* assertion is "this call returned status X" rather than "this call logged Y".
|
|
|
|
|
*
|
|
|
|
|
* What is here:
|
|
|
|
|
*
|
|
|
|
|
* AKSL_CHECK() an NDEBUG-proof assertion. Fails the test by
|
|
|
|
|
* returning 1 from the enclosing function (unlike
|
|
|
|
|
* assert(), which is compiled out in release builds
|
|
|
|
|
* and would silently turn a test into a no-op).
|
|
|
|
|
* AKSL_CHECK_STATUS() run an akerror-returning expression, assert on the
|
|
|
|
|
* status it came back with, and release the context
|
|
|
|
|
* so the error pool does not leak.
|
2026-07-29 15:47:17 -04:00
|
|
|
* AKSL_CHECK_STATUS_MSG_CONTAINS()
|
|
|
|
|
* assert status first, then message content.
|
2026-07-29 13:06:25 -04:00
|
|
|
* AKSL_CHECK_OK() assert a call returned no error context at all.
|
Build a real test harness (TODO.md section 1.0)
The suite could not fail and did not run. test_linkedlist.c asserted nothing
at all -- it printed node names and returned 0 -- so every confirmed list
defect passed it. test_tree.c was red on every run because parms.steps was
never reset between its three searches, and four libakerror tests registered
but never built, reporting Not Run. CI, meanwhile, was not fetching the
submodule, so configure failed before reaching any of it.
- tests/aksl_capture.h: AKSL_CHECK (NDEBUG-proof), AKSL_CHECK_STATUS/_OK to
run an akerror-returning call, assert its status and release the context,
a capturing akerr_log_method, aksl_slots_in_use(), and an AKSL_RUN driver
that fails any test leaking an error-pool slot.
- test_linkedlist.c: rewritten as 15 assertion cases over the append,
iterate and pop behaviour that is correct today.
- test_tree.c: each search builds its own tree and params.
- Registration driven by AKSL_TESTS, plus AKSL_WILL_FAIL_TESTS (aborts by
design) and AKSL_KNOWN_FAILING_TESTS, which marks WILL_FAIL the three new
tests asserting correct behaviour for the confirmed defects in TODO.md
2.1.1-2.1.3. Fixing a defect flips its test to "unexpectedly passed",
which is the cue to promote it into AKSL_TESTS.
- add_test/set_tests_properties are shadowed across the libakerror
add_subdirectory call: CMake cannot un-register a test and
set_tests_properties cannot cross directory scopes. The dependency has
its own CI.
- AKSL_SANITIZE=ON builds library, tests and dependency with ASan+UBSan.
- scripts/mutation_test.py ported and retargeted at src/stdlib.c; wired to
a manual `mutation` target, not a CI gate until 1.1-1.9 exist.
- CI: checkout with submodules: recursive, and ctest --output-on-failure.
ctest is now 5/5 green in both the default and sanitizer builds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 12:39:54 -04:00
|
|
|
* aksl_last_status/... the status, message and function name of the most
|
|
|
|
|
* recent context taken by AKSL_CHECK_STATUS.
|
|
|
|
|
* aksl_capture_install() swap in a capturing akerr_log_method so a test can
|
|
|
|
|
* assert on the *content* of stack traces and
|
|
|
|
|
* unhandled-error output.
|
|
|
|
|
* aksl_slots_in_use() how many slots are currently checked out of
|
|
|
|
|
* AKERR_ARRAY_ERROR, for pool-leak assertions.
|
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
|
|
|
* aksl_temp_file() create an empty temp file for the stream, formatted
|
|
|
|
|
* output and path tests to work on.
|
Build a real test harness (TODO.md section 1.0)
The suite could not fail and did not run. test_linkedlist.c asserted nothing
at all -- it printed node names and returned 0 -- so every confirmed list
defect passed it. test_tree.c was red on every run because parms.steps was
never reset between its three searches, and four libakerror tests registered
but never built, reporting Not Run. CI, meanwhile, was not fetching the
submodule, so configure failed before reaching any of it.
- tests/aksl_capture.h: AKSL_CHECK (NDEBUG-proof), AKSL_CHECK_STATUS/_OK to
run an akerror-returning call, assert its status and release the context,
a capturing akerr_log_method, aksl_slots_in_use(), and an AKSL_RUN driver
that fails any test leaking an error-pool slot.
- test_linkedlist.c: rewritten as 15 assertion cases over the append,
iterate and pop behaviour that is correct today.
- test_tree.c: each search builds its own tree and params.
- Registration driven by AKSL_TESTS, plus AKSL_WILL_FAIL_TESTS (aborts by
design) and AKSL_KNOWN_FAILING_TESTS, which marks WILL_FAIL the three new
tests asserting correct behaviour for the confirmed defects in TODO.md
2.1.1-2.1.3. Fixing a defect flips its test to "unexpectedly passed",
which is the cue to promote it into AKSL_TESTS.
- add_test/set_tests_properties are shadowed across the libakerror
add_subdirectory call: CMake cannot un-register a test and
set_tests_properties cannot cross directory scopes. The dependency has
its own CI.
- AKSL_SANITIZE=ON builds library, tests and dependency with ASan+UBSan.
- scripts/mutation_test.py ported and retargeted at src/stdlib.c; wired to
a manual `mutation` target, not a CI gate until 1.1-1.9 exist.
- CI: checkout with submodules: recursive, and ctest --output-on-failure.
ctest is now 5/5 green in both the default and sanitizer builds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 12:39:54 -04:00
|
|
|
* AKSL_RUN() run one test function and tally the result.
|
|
|
|
|
*
|
|
|
|
|
* Tests are written as a set of `static int test_xxx(void)` functions that
|
|
|
|
|
* return 0 on success and non-zero on failure, driven from main() by AKSL_RUN.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <akstdlib.h>
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
#include <stdio.h>
|
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
|
|
|
#include <stdlib.h>
|
Build a real test harness (TODO.md section 1.0)
The suite could not fail and did not run. test_linkedlist.c asserted nothing
at all -- it printed node names and returned 0 -- so every confirmed list
defect passed it. test_tree.c was red on every run because parms.steps was
never reset between its three searches, and four libakerror tests registered
but never built, reporting Not Run. CI, meanwhile, was not fetching the
submodule, so configure failed before reaching any of it.
- tests/aksl_capture.h: AKSL_CHECK (NDEBUG-proof), AKSL_CHECK_STATUS/_OK to
run an akerror-returning call, assert its status and release the context,
a capturing akerr_log_method, aksl_slots_in_use(), and an AKSL_RUN driver
that fails any test leaking an error-pool slot.
- test_linkedlist.c: rewritten as 15 assertion cases over the append,
iterate and pop behaviour that is correct today.
- test_tree.c: each search builds its own tree and params.
- Registration driven by AKSL_TESTS, plus AKSL_WILL_FAIL_TESTS (aborts by
design) and AKSL_KNOWN_FAILING_TESTS, which marks WILL_FAIL the three new
tests asserting correct behaviour for the confirmed defects in TODO.md
2.1.1-2.1.3. Fixing a defect flips its test to "unexpectedly passed",
which is the cue to promote it into AKSL_TESTS.
- add_test/set_tests_properties are shadowed across the libakerror
add_subdirectory call: CMake cannot un-register a test and
set_tests_properties cannot cross directory scopes. The dependency has
its own CI.
- AKSL_SANITIZE=ON builds library, tests and dependency with ASan+UBSan.
- scripts/mutation_test.py ported and retargeted at src/stdlib.c; wired to
a manual `mutation` target, not a CI gate until 1.1-1.9 exist.
- CI: checkout with submodules: recursive, and ctest --output-on-failure.
ctest is now 5/5 green in both the default and sanitizer builds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 12:39:54 -04:00
|
|
|
#include <string.h>
|
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
|
|
|
#include <unistd.h>
|
Build a real test harness (TODO.md section 1.0)
The suite could not fail and did not run. test_linkedlist.c asserted nothing
at all -- it printed node names and returned 0 -- so every confirmed list
defect passed it. test_tree.c was red on every run because parms.steps was
never reset between its three searches, and four libakerror tests registered
but never built, reporting Not Run. CI, meanwhile, was not fetching the
submodule, so configure failed before reaching any of it.
- tests/aksl_capture.h: AKSL_CHECK (NDEBUG-proof), AKSL_CHECK_STATUS/_OK to
run an akerror-returning call, assert its status and release the context,
a capturing akerr_log_method, aksl_slots_in_use(), and an AKSL_RUN driver
that fails any test leaking an error-pool slot.
- test_linkedlist.c: rewritten as 15 assertion cases over the append,
iterate and pop behaviour that is correct today.
- test_tree.c: each search builds its own tree and params.
- Registration driven by AKSL_TESTS, plus AKSL_WILL_FAIL_TESTS (aborts by
design) and AKSL_KNOWN_FAILING_TESTS, which marks WILL_FAIL the three new
tests asserting correct behaviour for the confirmed defects in TODO.md
2.1.1-2.1.3. Fixing a defect flips its test to "unexpectedly passed",
which is the cue to promote it into AKSL_TESTS.
- add_test/set_tests_properties are shadowed across the libakerror
add_subdirectory call: CMake cannot un-register a test and
set_tests_properties cannot cross directory scopes. The dependency has
its own CI.
- AKSL_SANITIZE=ON builds library, tests and dependency with ASan+UBSan.
- scripts/mutation_test.py ported and retargeted at src/stdlib.c; wired to
a manual `mutation` target, not a CI gate until 1.1-1.9 exist.
- CI: checkout with submodules: recursive, and ctest --output-on-failure.
ctest is now 5/5 green in both the default and sanitizer builds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 12:39:54 -04:00
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
/* Log capture */
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
#define AKSL_CAPTURE_BUFSZ 65536
|
|
|
|
|
static char aksl_capture_buf[AKSL_CAPTURE_BUFSZ];
|
|
|
|
|
static size_t aksl_capture_len = 0;
|
|
|
|
|
|
|
|
|
|
static void __attribute__((unused)) aksl_capture_logger(const char *fmt, ...)
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
int n = vsnprintf(aksl_capture_buf + aksl_capture_len,
|
|
|
|
|
AKSL_CAPTURE_BUFSZ - aksl_capture_len, fmt, ap);
|
|
|
|
|
va_end(ap);
|
|
|
|
|
if ( n > 0 ) {
|
|
|
|
|
aksl_capture_len += (size_t)n;
|
|
|
|
|
if ( aksl_capture_len >= AKSL_CAPTURE_BUFSZ ) {
|
|
|
|
|
aksl_capture_len = AKSL_CAPTURE_BUFSZ - 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void __attribute__((unused)) aksl_capture_reset(void)
|
|
|
|
|
{
|
|
|
|
|
aksl_capture_len = 0;
|
|
|
|
|
aksl_capture_buf[0] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Install the capturing logger. akerr_init() only assigns a default logger when
|
|
|
|
|
* akerr_log_method is NULL, and it is idempotent, so calling this either before
|
|
|
|
|
* or after the first PREPARE_ERROR keeps our logger in place.
|
|
|
|
|
*/
|
|
|
|
|
static void __attribute__((unused)) aksl_capture_install(void)
|
|
|
|
|
{
|
|
|
|
|
aksl_capture_reset();
|
|
|
|
|
akerr_log_method = &aksl_capture_logger;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
/* Error pool accounting */
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
/* Count array slots currently checked out of the pool (refcount != 0). */
|
|
|
|
|
static int __attribute__((unused)) aksl_slots_in_use(void)
|
|
|
|
|
{
|
|
|
|
|
int n = 0;
|
|
|
|
|
for ( int i = 0; i < AKERR_MAX_ARRAY_ERROR; i++ ) {
|
|
|
|
|
if ( AKERR_ARRAY_ERROR[i].refcount != 0 ) {
|
|
|
|
|
n++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return n;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
/* Temp files */
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
#define AKSL_TMP_MAX 256
|
|
|
|
|
#define AKSL_TMP_TRACKED 32
|
|
|
|
|
|
|
|
|
|
static char aksl_tmp_paths[AKSL_TMP_TRACKED][AKSL_TMP_MAX];
|
|
|
|
|
static int aksl_tmp_count = 0;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Unlink every path aksl_temp_file() handed out. Registered with atexit, so the
|
|
|
|
|
* files go away even when a test returns early on a failed assertion -- which is
|
|
|
|
|
* the normal case for a known-failing test and for every mutant the mutation
|
|
|
|
|
* harness builds. Paths a test already unlinked simply fail here, harmlessly.
|
|
|
|
|
*/
|
|
|
|
|
static void aksl_temp_cleanup(void)
|
|
|
|
|
{
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
|
|
for ( i = 0; i < aksl_tmp_count; i++ ) {
|
|
|
|
|
unlink(aksl_tmp_paths[i]);
|
|
|
|
|
}
|
|
|
|
|
aksl_tmp_count = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Create an empty temp file under $TMPDIR (or /tmp) and write its path into buf.
|
|
|
|
|
* Returns 0 on success, non-zero on failure.
|
|
|
|
|
*
|
|
|
|
|
* mkstemp both names and creates the file, so a test that opens the path for
|
|
|
|
|
* reading is never racing another process for the name. Tests should still
|
|
|
|
|
* unlink what they create -- asserting on it catches a wrapper that removed or
|
|
|
|
|
* renamed the file -- but aksl_temp_cleanup() is the backstop.
|
|
|
|
|
*/
|
|
|
|
|
static int __attribute__((unused)) aksl_temp_file(char *buf, size_t n)
|
|
|
|
|
{
|
|
|
|
|
const char *dir = getenv("TMPDIR");
|
|
|
|
|
int fd = -1;
|
|
|
|
|
|
|
|
|
|
if ( dir == NULL || dir[0] == '\0' ) {
|
|
|
|
|
dir = "/tmp";
|
|
|
|
|
}
|
|
|
|
|
if ( (size_t)snprintf(buf, n, "%s/aksl_test_XXXXXX", dir) >= n ) {
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
fd = mkstemp(buf);
|
|
|
|
|
if ( fd < 0 ) {
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
close(fd);
|
|
|
|
|
|
|
|
|
|
if ( aksl_tmp_count < AKSL_TMP_TRACKED ) {
|
|
|
|
|
if ( aksl_tmp_count == 0 && atexit(&aksl_temp_cleanup) != 0 ) {
|
|
|
|
|
return 0; /* tracking is best-effort; the file itself is fine */
|
|
|
|
|
}
|
|
|
|
|
snprintf(aksl_tmp_paths[aksl_tmp_count], AKSL_TMP_MAX, "%s", buf);
|
|
|
|
|
aksl_tmp_count++;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
Build a real test harness (TODO.md section 1.0)
The suite could not fail and did not run. test_linkedlist.c asserted nothing
at all -- it printed node names and returned 0 -- so every confirmed list
defect passed it. test_tree.c was red on every run because parms.steps was
never reset between its three searches, and four libakerror tests registered
but never built, reporting Not Run. CI, meanwhile, was not fetching the
submodule, so configure failed before reaching any of it.
- tests/aksl_capture.h: AKSL_CHECK (NDEBUG-proof), AKSL_CHECK_STATUS/_OK to
run an akerror-returning call, assert its status and release the context,
a capturing akerr_log_method, aksl_slots_in_use(), and an AKSL_RUN driver
that fails any test leaking an error-pool slot.
- test_linkedlist.c: rewritten as 15 assertion cases over the append,
iterate and pop behaviour that is correct today.
- test_tree.c: each search builds its own tree and params.
- Registration driven by AKSL_TESTS, plus AKSL_WILL_FAIL_TESTS (aborts by
design) and AKSL_KNOWN_FAILING_TESTS, which marks WILL_FAIL the three new
tests asserting correct behaviour for the confirmed defects in TODO.md
2.1.1-2.1.3. Fixing a defect flips its test to "unexpectedly passed",
which is the cue to promote it into AKSL_TESTS.
- add_test/set_tests_properties are shadowed across the libakerror
add_subdirectory call: CMake cannot un-register a test and
set_tests_properties cannot cross directory scopes. The dependency has
its own CI.
- AKSL_SANITIZE=ON builds library, tests and dependency with ASan+UBSan.
- scripts/mutation_test.py ported and retargeted at src/stdlib.c; wired to
a manual `mutation` target, not a CI gate until 1.1-1.9 exist.
- CI: checkout with submodules: recursive, and ctest --output-on-failure.
ctest is now 5/5 green in both the default and sanitizer builds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 12:39:54 -04:00
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
/* Taking ownership of a returned error context */
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
static int aksl_last_status = 0;
|
|
|
|
|
static char aksl_last_message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
|
|
|
|
|
/* Sized to match akerr_ErrorContext.function, which akerror declares with
|
|
|
|
|
* AKERR_MAX_ERROR_FNAME_LENGTH rather than AKERR_MAX_ERROR_FUNCTION_LENGTH. */
|
|
|
|
|
static char aksl_last_function[AKERR_MAX_ERROR_FNAME_LENGTH];
|
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
|
|
|
/*
|
|
|
|
|
* The source location the error was raised from. Captured so that
|
|
|
|
|
* tests/test_pool.c can assert an error came from the wrapper it names rather
|
|
|
|
|
* than from somewhere further down -- an error that reports the wrong origin is
|
|
|
|
|
* worse than useless when the only debugging you get is a log file after the
|
|
|
|
|
* fact, which is the stated reason this library exists.
|
|
|
|
|
*/
|
|
|
|
|
static char aksl_last_file[AKERR_MAX_ERROR_FNAME_LENGTH];
|
|
|
|
|
static int aksl_last_line = 0;
|
Build a real test harness (TODO.md section 1.0)
The suite could not fail and did not run. test_linkedlist.c asserted nothing
at all -- it printed node names and returned 0 -- so every confirmed list
defect passed it. test_tree.c was red on every run because parms.steps was
never reset between its three searches, and four libakerror tests registered
but never built, reporting Not Run. CI, meanwhile, was not fetching the
submodule, so configure failed before reaching any of it.
- tests/aksl_capture.h: AKSL_CHECK (NDEBUG-proof), AKSL_CHECK_STATUS/_OK to
run an akerror-returning call, assert its status and release the context,
a capturing akerr_log_method, aksl_slots_in_use(), and an AKSL_RUN driver
that fails any test leaking an error-pool slot.
- test_linkedlist.c: rewritten as 15 assertion cases over the append,
iterate and pop behaviour that is correct today.
- test_tree.c: each search builds its own tree and params.
- Registration driven by AKSL_TESTS, plus AKSL_WILL_FAIL_TESTS (aborts by
design) and AKSL_KNOWN_FAILING_TESTS, which marks WILL_FAIL the three new
tests asserting correct behaviour for the confirmed defects in TODO.md
2.1.1-2.1.3. Fixing a defect flips its test to "unexpectedly passed",
which is the cue to promote it into AKSL_TESTS.
- add_test/set_tests_properties are shadowed across the libakerror
add_subdirectory call: CMake cannot un-register a test and
set_tests_properties cannot cross directory scopes. The dependency has
its own CI.
- AKSL_SANITIZE=ON builds library, tests and dependency with ASan+UBSan.
- scripts/mutation_test.py ported and retargeted at src/stdlib.c; wired to
a manual `mutation` target, not a CI gate until 1.1-1.9 exist.
- CI: checkout with submodules: recursive, and ctest --output-on-failure.
ctest is now 5/5 green in both the default and sanitizer builds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 12:39:54 -04:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Record the status/message/function of a returned context, release it back to
|
|
|
|
|
* the pool, and hand back the status. A NULL context means success, which is
|
2026-07-29 13:06:25 -04:00
|
|
|
* status 0. Failure-path assertions should go through this so that no test
|
|
|
|
|
* leaks a pool slot. Success-path assertions use AKSL_CHECK_OK, which also
|
|
|
|
|
* verifies that no bogus status-0 context was returned.
|
Build a real test harness (TODO.md section 1.0)
The suite could not fail and did not run. test_linkedlist.c asserted nothing
at all -- it printed node names and returned 0 -- so every confirmed list
defect passed it. test_tree.c was red on every run because parms.steps was
never reset between its three searches, and four libakerror tests registered
but never built, reporting Not Run. CI, meanwhile, was not fetching the
submodule, so configure failed before reaching any of it.
- tests/aksl_capture.h: AKSL_CHECK (NDEBUG-proof), AKSL_CHECK_STATUS/_OK to
run an akerror-returning call, assert its status and release the context,
a capturing akerr_log_method, aksl_slots_in_use(), and an AKSL_RUN driver
that fails any test leaking an error-pool slot.
- test_linkedlist.c: rewritten as 15 assertion cases over the append,
iterate and pop behaviour that is correct today.
- test_tree.c: each search builds its own tree and params.
- Registration driven by AKSL_TESTS, plus AKSL_WILL_FAIL_TESTS (aborts by
design) and AKSL_KNOWN_FAILING_TESTS, which marks WILL_FAIL the three new
tests asserting correct behaviour for the confirmed defects in TODO.md
2.1.1-2.1.3. Fixing a defect flips its test to "unexpectedly passed",
which is the cue to promote it into AKSL_TESTS.
- add_test/set_tests_properties are shadowed across the libakerror
add_subdirectory call: CMake cannot un-register a test and
set_tests_properties cannot cross directory scopes. The dependency has
its own CI.
- AKSL_SANITIZE=ON builds library, tests and dependency with ASan+UBSan.
- scripts/mutation_test.py ported and retargeted at src/stdlib.c; wired to
a manual `mutation` target, not a CI gate until 1.1-1.9 exist.
- CI: checkout with submodules: recursive, and ctest --output-on-failure.
ctest is now 5/5 green in both the default and sanitizer builds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 12:39:54 -04:00
|
|
|
*/
|
|
|
|
|
static int __attribute__((unused)) aksl_take(akerr_ErrorContext *e)
|
|
|
|
|
{
|
|
|
|
|
akerr_ErrorContext *released = NULL;
|
|
|
|
|
|
|
|
|
|
aksl_last_message[0] = '\0';
|
|
|
|
|
aksl_last_function[0] = '\0';
|
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
|
|
|
aksl_last_file[0] = '\0';
|
|
|
|
|
aksl_last_line = 0;
|
Build a real test harness (TODO.md section 1.0)
The suite could not fail and did not run. test_linkedlist.c asserted nothing
at all -- it printed node names and returned 0 -- so every confirmed list
defect passed it. test_tree.c was red on every run because parms.steps was
never reset between its three searches, and four libakerror tests registered
but never built, reporting Not Run. CI, meanwhile, was not fetching the
submodule, so configure failed before reaching any of it.
- tests/aksl_capture.h: AKSL_CHECK (NDEBUG-proof), AKSL_CHECK_STATUS/_OK to
run an akerror-returning call, assert its status and release the context,
a capturing akerr_log_method, aksl_slots_in_use(), and an AKSL_RUN driver
that fails any test leaking an error-pool slot.
- test_linkedlist.c: rewritten as 15 assertion cases over the append,
iterate and pop behaviour that is correct today.
- test_tree.c: each search builds its own tree and params.
- Registration driven by AKSL_TESTS, plus AKSL_WILL_FAIL_TESTS (aborts by
design) and AKSL_KNOWN_FAILING_TESTS, which marks WILL_FAIL the three new
tests asserting correct behaviour for the confirmed defects in TODO.md
2.1.1-2.1.3. Fixing a defect flips its test to "unexpectedly passed",
which is the cue to promote it into AKSL_TESTS.
- add_test/set_tests_properties are shadowed across the libakerror
add_subdirectory call: CMake cannot un-register a test and
set_tests_properties cannot cross directory scopes. The dependency has
its own CI.
- AKSL_SANITIZE=ON builds library, tests and dependency with ASan+UBSan.
- scripts/mutation_test.py ported and retargeted at src/stdlib.c; wired to
a manual `mutation` target, not a CI gate until 1.1-1.9 exist.
- CI: checkout with submodules: recursive, and ctest --output-on-failure.
ctest is now 5/5 green in both the default and sanitizer builds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 12:39:54 -04:00
|
|
|
if ( e == NULL ) {
|
|
|
|
|
aksl_last_status = 0;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
aksl_last_status = e->status;
|
|
|
|
|
snprintf(aksl_last_message, sizeof(aksl_last_message), "%s", e->message);
|
|
|
|
|
snprintf(aksl_last_function, sizeof(aksl_last_function), "%s", e->function);
|
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
|
|
|
snprintf(aksl_last_file, sizeof(aksl_last_file), "%s", e->fname);
|
|
|
|
|
aksl_last_line = e->lineno;
|
Build a real test harness (TODO.md section 1.0)
The suite could not fail and did not run. test_linkedlist.c asserted nothing
at all -- it printed node names and returned 0 -- so every confirmed list
defect passed it. test_tree.c was red on every run because parms.steps was
never reset between its three searches, and four libakerror tests registered
but never built, reporting Not Run. CI, meanwhile, was not fetching the
submodule, so configure failed before reaching any of it.
- tests/aksl_capture.h: AKSL_CHECK (NDEBUG-proof), AKSL_CHECK_STATUS/_OK to
run an akerror-returning call, assert its status and release the context,
a capturing akerr_log_method, aksl_slots_in_use(), and an AKSL_RUN driver
that fails any test leaking an error-pool slot.
- test_linkedlist.c: rewritten as 15 assertion cases over the append,
iterate and pop behaviour that is correct today.
- test_tree.c: each search builds its own tree and params.
- Registration driven by AKSL_TESTS, plus AKSL_WILL_FAIL_TESTS (aborts by
design) and AKSL_KNOWN_FAILING_TESTS, which marks WILL_FAIL the three new
tests asserting correct behaviour for the confirmed defects in TODO.md
2.1.1-2.1.3. Fixing a defect flips its test to "unexpectedly passed",
which is the cue to promote it into AKSL_TESTS.
- add_test/set_tests_properties are shadowed across the libakerror
add_subdirectory call: CMake cannot un-register a test and
set_tests_properties cannot cross directory scopes. The dependency has
its own CI.
- AKSL_SANITIZE=ON builds library, tests and dependency with ASan+UBSan.
- scripts/mutation_test.py ported and retargeted at src/stdlib.c; wired to
a manual `mutation` target, not a CI gate until 1.1-1.9 exist.
- CI: checkout with submodules: recursive, and ctest --output-on-failure.
ctest is now 5/5 green in both the default and sanitizer builds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 12:39:54 -04:00
|
|
|
/*
|
|
|
|
|
* akerr_release_error is marked warn_unused_result, and a (void) cast does
|
|
|
|
|
* not silence that in GCC, so the result is assigned and discarded.
|
|
|
|
|
*/
|
|
|
|
|
released = akerr_release_error(e);
|
|
|
|
|
(void)released;
|
|
|
|
|
return aksl_last_status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
/* Assertions */
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
#define AKSL_CHECK(cond) \
|
|
|
|
|
do { \
|
|
|
|
|
if ( !(cond) ) { \
|
|
|
|
|
fprintf(stderr, " CHECK FAILED: %s at %s:%d\n", \
|
|
|
|
|
#cond, __FILE__, __LINE__); \
|
|
|
|
|
return 1; \
|
|
|
|
|
} \
|
|
|
|
|
} while ( 0 )
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Run an akerr_ErrorContext *-returning expression and assert on its status.
|
|
|
|
|
* The context is always released, including on the failure path, so a failing
|
|
|
|
|
* assertion does not also corrupt the pool-leak checks that follow it.
|
|
|
|
|
*/
|
Reindent to the canonical stroustrup style
Whitespace only -- `git diff -w` is empty. TODO.md 2.3 recorded that the
tree mixed hard tabs and space indents within the same functions;
tests/aksl_capture.h was the worst of it, with the assertion macros
space-indented and everything around them tabbed.
The style is the one libakerror standardised on and the one AGENTS.md
already describes: Emacs cc-mode "stroustrup", c-basic-offset 4,
indent-tabs-mode on, tab-width 8. A correct file is a fixed point of
indent-region under those settings, and the tree is one now -- a second
pass produces no diff.
scripts/reindent.el is that pass, checked in so "correct" is something
you can run rather than something you have to remember. It is not
clang-format and deliberately so: AGENTS.md forbids introducing one
without discussion, and this is a good illustration of why. cc-mode
indents every declaration in akstdlib.h one level for the extern "C" { }
wrapping the file body, so the script sets inextern-lang to 0 -- a
cc-mode offset with no clang-format equivalent, and without it the first
run moved 364 lines in the wrong direction.
Its own commit, no behaviour change, and the suite is green either side.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:02:55 -04:00
|
|
|
#define AKSL_CHECK_STATUS(__expr, __expected) \
|
|
|
|
|
do { \
|
|
|
|
|
int __st = aksl_take(__expr); \
|
|
|
|
|
if ( __st != (__expected) ) { \
|
|
|
|
|
fprintf(stderr, \
|
|
|
|
|
" CHECK FAILED: %s\n" \
|
|
|
|
|
" got %d (%s) \"%s\"\n" \
|
|
|
|
|
" expected %d (%s)\n" \
|
|
|
|
|
" at %s:%d\n", \
|
|
|
|
|
#__expr, \
|
|
|
|
|
__st, akerr_name_for_status(__st, NULL), \
|
|
|
|
|
aksl_last_message, \
|
|
|
|
|
(__expected), \
|
|
|
|
|
akerr_name_for_status((__expected), NULL), \
|
|
|
|
|
__FILE__, __LINE__); \
|
|
|
|
|
return 1; \
|
|
|
|
|
} \
|
Build a real test harness (TODO.md section 1.0)
The suite could not fail and did not run. test_linkedlist.c asserted nothing
at all -- it printed node names and returned 0 -- so every confirmed list
defect passed it. test_tree.c was red on every run because parms.steps was
never reset between its three searches, and four libakerror tests registered
but never built, reporting Not Run. CI, meanwhile, was not fetching the
submodule, so configure failed before reaching any of it.
- tests/aksl_capture.h: AKSL_CHECK (NDEBUG-proof), AKSL_CHECK_STATUS/_OK to
run an akerror-returning call, assert its status and release the context,
a capturing akerr_log_method, aksl_slots_in_use(), and an AKSL_RUN driver
that fails any test leaking an error-pool slot.
- test_linkedlist.c: rewritten as 15 assertion cases over the append,
iterate and pop behaviour that is correct today.
- test_tree.c: each search builds its own tree and params.
- Registration driven by AKSL_TESTS, plus AKSL_WILL_FAIL_TESTS (aborts by
design) and AKSL_KNOWN_FAILING_TESTS, which marks WILL_FAIL the three new
tests asserting correct behaviour for the confirmed defects in TODO.md
2.1.1-2.1.3. Fixing a defect flips its test to "unexpectedly passed",
which is the cue to promote it into AKSL_TESTS.
- add_test/set_tests_properties are shadowed across the libakerror
add_subdirectory call: CMake cannot un-register a test and
set_tests_properties cannot cross directory scopes. The dependency has
its own CI.
- AKSL_SANITIZE=ON builds library, tests and dependency with ASan+UBSan.
- scripts/mutation_test.py ported and retargeted at src/stdlib.c; wired to
a manual `mutation` target, not a CI gate until 1.1-1.9 exist.
- CI: checkout with submodules: recursive, and ctest --output-on-failure.
ctest is now 5/5 green in both the default and sanitizer builds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 12:39:54 -04:00
|
|
|
} while ( 0 )
|
|
|
|
|
|
2026-07-29 13:06:25 -04:00
|
|
|
/*
|
|
|
|
|
* Success is represented by a NULL error context, not just status 0. This catches
|
|
|
|
|
* wrappers that incorrectly raise an error using a stale errno value of 0.
|
|
|
|
|
*/
|
Reindent to the canonical stroustrup style
Whitespace only -- `git diff -w` is empty. TODO.md 2.3 recorded that the
tree mixed hard tabs and space indents within the same functions;
tests/aksl_capture.h was the worst of it, with the assertion macros
space-indented and everything around them tabbed.
The style is the one libakerror standardised on and the one AGENTS.md
already describes: Emacs cc-mode "stroustrup", c-basic-offset 4,
indent-tabs-mode on, tab-width 8. A correct file is a fixed point of
indent-region under those settings, and the tree is one now -- a second
pass produces no diff.
scripts/reindent.el is that pass, checked in so "correct" is something
you can run rather than something you have to remember. It is not
clang-format and deliberately so: AGENTS.md forbids introducing one
without discussion, and this is a good illustration of why. cc-mode
indents every declaration in akstdlib.h one level for the extern "C" { }
wrapping the file body, so the script sets inextern-lang to 0 -- a
cc-mode offset with no clang-format equivalent, and without it the first
run moved 364 lines in the wrong direction.
Its own commit, no behaviour change, and the suite is green either side.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:02:55 -04:00
|
|
|
#define AKSL_CHECK_OK(__expr) \
|
|
|
|
|
do { \
|
|
|
|
|
akerr_ErrorContext *__e = (__expr); \
|
|
|
|
|
if ( __e != NULL ) { \
|
|
|
|
|
int __st = aksl_take(__e); \
|
|
|
|
|
fprintf(stderr, \
|
|
|
|
|
" CHECK FAILED: %s\n" \
|
2026-07-29 13:06:25 -04:00
|
|
|
" returned error context with status %d (%s) \"%s\"\n" \
|
Reindent to the canonical stroustrup style
Whitespace only -- `git diff -w` is empty. TODO.md 2.3 recorded that the
tree mixed hard tabs and space indents within the same functions;
tests/aksl_capture.h was the worst of it, with the assertion macros
space-indented and everything around them tabbed.
The style is the one libakerror standardised on and the one AGENTS.md
already describes: Emacs cc-mode "stroustrup", c-basic-offset 4,
indent-tabs-mode on, tab-width 8. A correct file is a fixed point of
indent-region under those settings, and the tree is one now -- a second
pass produces no diff.
scripts/reindent.el is that pass, checked in so "correct" is something
you can run rather than something you have to remember. It is not
clang-format and deliberately so: AGENTS.md forbids introducing one
without discussion, and this is a good illustration of why. cc-mode
indents every declaration in akstdlib.h one level for the extern "C" { }
wrapping the file body, so the script sets inextern-lang to 0 -- a
cc-mode offset with no clang-format equivalent, and without it the first
run moved 364 lines in the wrong direction.
Its own commit, no behaviour change, and the suite is green either side.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:02:55 -04:00
|
|
|
" expected no error context\n" \
|
|
|
|
|
" at %s:%d\n", \
|
|
|
|
|
#__expr, \
|
|
|
|
|
__st, akerr_name_for_status(__st, NULL), \
|
|
|
|
|
aksl_last_message, \
|
|
|
|
|
__FILE__, __LINE__); \
|
|
|
|
|
return 1; \
|
|
|
|
|
} \
|
|
|
|
|
aksl_last_status = 0; \
|
|
|
|
|
aksl_last_message[0] = '\0'; \
|
|
|
|
|
aksl_last_function[0] = '\0'; \
|
|
|
|
|
aksl_last_file[0] = '\0'; \
|
|
|
|
|
aksl_last_line = 0; \
|
2026-07-29 13:06:25 -04:00
|
|
|
} while ( 0 )
|
Build a real test harness (TODO.md section 1.0)
The suite could not fail and did not run. test_linkedlist.c asserted nothing
at all -- it printed node names and returned 0 -- so every confirmed list
defect passed it. test_tree.c was red on every run because parms.steps was
never reset between its three searches, and four libakerror tests registered
but never built, reporting Not Run. CI, meanwhile, was not fetching the
submodule, so configure failed before reaching any of it.
- tests/aksl_capture.h: AKSL_CHECK (NDEBUG-proof), AKSL_CHECK_STATUS/_OK to
run an akerror-returning call, assert its status and release the context,
a capturing akerr_log_method, aksl_slots_in_use(), and an AKSL_RUN driver
that fails any test leaking an error-pool slot.
- test_linkedlist.c: rewritten as 15 assertion cases over the append,
iterate and pop behaviour that is correct today.
- test_tree.c: each search builds its own tree and params.
- Registration driven by AKSL_TESTS, plus AKSL_WILL_FAIL_TESTS (aborts by
design) and AKSL_KNOWN_FAILING_TESTS, which marks WILL_FAIL the three new
tests asserting correct behaviour for the confirmed defects in TODO.md
2.1.1-2.1.3. Fixing a defect flips its test to "unexpectedly passed",
which is the cue to promote it into AKSL_TESTS.
- add_test/set_tests_properties are shadowed across the libakerror
add_subdirectory call: CMake cannot un-register a test and
set_tests_properties cannot cross directory scopes. The dependency has
its own CI.
- AKSL_SANITIZE=ON builds library, tests and dependency with ASan+UBSan.
- scripts/mutation_test.py ported and retargeted at src/stdlib.c; wired to
a manual `mutation` target, not a CI gate until 1.1-1.9 exist.
- CI: checkout with submodules: recursive, and ctest --output-on-failure.
ctest is now 5/5 green in both the default and sanitizer builds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 12:39:54 -04:00
|
|
|
|
|
|
|
|
#define AKSL_CHECK_CONTAINS(needle) \
|
|
|
|
|
AKSL_CHECK(strstr(aksl_capture_buf, (needle)) != NULL)
|
|
|
|
|
|
|
|
|
|
#define AKSL_CHECK_NOT_CONTAINS(needle) \
|
|
|
|
|
AKSL_CHECK(strstr(aksl_capture_buf, (needle)) == NULL)
|
|
|
|
|
|
2026-07-29 15:47:17 -04:00
|
|
|
/*
|
|
|
|
|
* Message checks are secondary: the returned akerr status is the contract. Keep
|
|
|
|
|
* the status and message assertions coupled so tests cannot pass on text alone.
|
|
|
|
|
*/
|
Reindent to the canonical stroustrup style
Whitespace only -- `git diff -w` is empty. TODO.md 2.3 recorded that the
tree mixed hard tabs and space indents within the same functions;
tests/aksl_capture.h was the worst of it, with the assertion macros
space-indented and everything around them tabbed.
The style is the one libakerror standardised on and the one AGENTS.md
already describes: Emacs cc-mode "stroustrup", c-basic-offset 4,
indent-tabs-mode on, tab-width 8. A correct file is a fixed point of
indent-region under those settings, and the tree is one now -- a second
pass produces no diff.
scripts/reindent.el is that pass, checked in so "correct" is something
you can run rather than something you have to remember. It is not
clang-format and deliberately so: AGENTS.md forbids introducing one
without discussion, and this is a good illustration of why. cc-mode
indents every declaration in akstdlib.h one level for the extern "C" { }
wrapping the file body, so the script sets inextern-lang to 0 -- a
cc-mode offset with no clang-format equivalent, and without it the first
run moved 364 lines in the wrong direction.
Its own commit, no behaviour change, and the suite is green either side.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:02:55 -04:00
|
|
|
#define AKSL_CHECK_STATUS_MSG_CONTAINS(__expr, __expected, __needle) \
|
|
|
|
|
do { \
|
|
|
|
|
int __st = aksl_take(__expr); \
|
|
|
|
|
if ( __st != (__expected) ) { \
|
|
|
|
|
fprintf(stderr, \
|
|
|
|
|
" CHECK FAILED: %s\n" \
|
|
|
|
|
" got %d (%s) \"%s\"\n" \
|
|
|
|
|
" expected %d (%s)\n" \
|
|
|
|
|
" at %s:%d\n", \
|
|
|
|
|
#__expr, \
|
|
|
|
|
__st, akerr_name_for_status(__st, NULL), \
|
|
|
|
|
aksl_last_message, \
|
|
|
|
|
(__expected), \
|
|
|
|
|
akerr_name_for_status((__expected), NULL), \
|
|
|
|
|
__FILE__, __LINE__); \
|
|
|
|
|
return 1; \
|
|
|
|
|
} \
|
|
|
|
|
if ( strstr(aksl_last_message, (__needle)) == NULL ) { \
|
|
|
|
|
fprintf(stderr, \
|
|
|
|
|
" CHECK FAILED: message from %s\n" \
|
|
|
|
|
" got \"%s\"\n" \
|
|
|
|
|
" expected substring \"%s\"\n" \
|
|
|
|
|
" status %d (%s)\n" \
|
|
|
|
|
" at %s:%d\n", \
|
|
|
|
|
#__expr, \
|
|
|
|
|
aksl_last_message, \
|
|
|
|
|
(__needle), \
|
|
|
|
|
__st, akerr_name_for_status(__st, NULL), \
|
|
|
|
|
__FILE__, __LINE__); \
|
|
|
|
|
return 1; \
|
|
|
|
|
} \
|
2026-07-29 15:47:17 -04:00
|
|
|
} while ( 0 )
|
Build a real test harness (TODO.md section 1.0)
The suite could not fail and did not run. test_linkedlist.c asserted nothing
at all -- it printed node names and returned 0 -- so every confirmed list
defect passed it. test_tree.c was red on every run because parms.steps was
never reset between its three searches, and four libakerror tests registered
but never built, reporting Not Run. CI, meanwhile, was not fetching the
submodule, so configure failed before reaching any of it.
- tests/aksl_capture.h: AKSL_CHECK (NDEBUG-proof), AKSL_CHECK_STATUS/_OK to
run an akerror-returning call, assert its status and release the context,
a capturing akerr_log_method, aksl_slots_in_use(), and an AKSL_RUN driver
that fails any test leaking an error-pool slot.
- test_linkedlist.c: rewritten as 15 assertion cases over the append,
iterate and pop behaviour that is correct today.
- test_tree.c: each search builds its own tree and params.
- Registration driven by AKSL_TESTS, plus AKSL_WILL_FAIL_TESTS (aborts by
design) and AKSL_KNOWN_FAILING_TESTS, which marks WILL_FAIL the three new
tests asserting correct behaviour for the confirmed defects in TODO.md
2.1.1-2.1.3. Fixing a defect flips its test to "unexpectedly passed",
which is the cue to promote it into AKSL_TESTS.
- add_test/set_tests_properties are shadowed across the libakerror
add_subdirectory call: CMake cannot un-register a test and
set_tests_properties cannot cross directory scopes. The dependency has
its own CI.
- AKSL_SANITIZE=ON builds library, tests and dependency with ASan+UBSan.
- scripts/mutation_test.py ported and retargeted at src/stdlib.c; wired to
a manual `mutation` target, not a CI gate until 1.1-1.9 exist.
- CI: checkout with submodules: recursive, and ctest --output-on-failure.
ctest is now 5/5 green in both the default and sanitizer builds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 12:39:54 -04:00
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
/* Test driver */
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Run one test function, report it, and tally failures. Also asserts that the
|
|
|
|
|
* test left the error pool as it found it -- a wrapper that fails to release a
|
|
|
|
|
* context is a bug in the library, not just in the test.
|
|
|
|
|
*/
|
Reindent to the canonical stroustrup style
Whitespace only -- `git diff -w` is empty. TODO.md 2.3 recorded that the
tree mixed hard tabs and space indents within the same functions;
tests/aksl_capture.h was the worst of it, with the assertion macros
space-indented and everything around them tabbed.
The style is the one libakerror standardised on and the one AGENTS.md
already describes: Emacs cc-mode "stroustrup", c-basic-offset 4,
indent-tabs-mode on, tab-width 8. A correct file is a fixed point of
indent-region under those settings, and the tree is one now -- a second
pass produces no diff.
scripts/reindent.el is that pass, checked in so "correct" is something
you can run rather than something you have to remember. It is not
clang-format and deliberately so: AGENTS.md forbids introducing one
without discussion, and this is a good illustration of why. cc-mode
indents every declaration in akstdlib.h one level for the extern "C" { }
wrapping the file body, so the script sets inextern-lang to 0 -- a
cc-mode offset with no clang-format equivalent, and without it the first
run moved 364 lines in the wrong direction.
Its own commit, no behaviour change, and the suite is green either side.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:02:55 -04:00
|
|
|
#define AKSL_RUN(__failures, __fn) \
|
|
|
|
|
do { \
|
|
|
|
|
int __before = aksl_slots_in_use(); \
|
|
|
|
|
int __r = __fn(); \
|
|
|
|
|
int __after = aksl_slots_in_use(); \
|
|
|
|
|
if ( __r != 0 ) { \
|
|
|
|
|
fprintf(stderr, "FAIL %s\n", #__fn); \
|
|
|
|
|
(__failures)++; \
|
|
|
|
|
} else if ( __after != __before ) { \
|
|
|
|
|
fprintf(stderr, \
|
|
|
|
|
"FAIL %s (leaked %d error pool slot(s))\n", \
|
|
|
|
|
#__fn, __after - __before); \
|
|
|
|
|
(__failures)++; \
|
|
|
|
|
} else { \
|
|
|
|
|
fprintf(stderr, "ok %s\n", #__fn); \
|
|
|
|
|
} \
|
Build a real test harness (TODO.md section 1.0)
The suite could not fail and did not run. test_linkedlist.c asserted nothing
at all -- it printed node names and returned 0 -- so every confirmed list
defect passed it. test_tree.c was red on every run because parms.steps was
never reset between its three searches, and four libakerror tests registered
but never built, reporting Not Run. CI, meanwhile, was not fetching the
submodule, so configure failed before reaching any of it.
- tests/aksl_capture.h: AKSL_CHECK (NDEBUG-proof), AKSL_CHECK_STATUS/_OK to
run an akerror-returning call, assert its status and release the context,
a capturing akerr_log_method, aksl_slots_in_use(), and an AKSL_RUN driver
that fails any test leaking an error-pool slot.
- test_linkedlist.c: rewritten as 15 assertion cases over the append,
iterate and pop behaviour that is correct today.
- test_tree.c: each search builds its own tree and params.
- Registration driven by AKSL_TESTS, plus AKSL_WILL_FAIL_TESTS (aborts by
design) and AKSL_KNOWN_FAILING_TESTS, which marks WILL_FAIL the three new
tests asserting correct behaviour for the confirmed defects in TODO.md
2.1.1-2.1.3. Fixing a defect flips its test to "unexpectedly passed",
which is the cue to promote it into AKSL_TESTS.
- add_test/set_tests_properties are shadowed across the libakerror
add_subdirectory call: CMake cannot un-register a test and
set_tests_properties cannot cross directory scopes. The dependency has
its own CI.
- AKSL_SANITIZE=ON builds library, tests and dependency with ASan+UBSan.
- scripts/mutation_test.py ported and retargeted at src/stdlib.c; wired to
a manual `mutation` target, not a CI gate until 1.1-1.9 exist.
- CI: checkout with submodules: recursive, and ctest --output-on-failure.
ctest is now 5/5 green in both the default and sanitizer builds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 12:39:54 -04:00
|
|
|
} while ( 0 )
|
|
|
|
|
|
|
|
|
|
#define AKSL_REPORT(__failures) \
|
|
|
|
|
do { \
|
|
|
|
|
fprintf(stderr, "%s: %d failure(s)\n", __FILE__, (__failures)); \
|
|
|
|
|
return (__failures) == 0 ? 0 : 1; \
|
|
|
|
|
} while ( 0 )
|
|
|
|
|
|
|
|
|
|
#endif // AKSL_TEST_CAPTURE_H
|