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
|
|
|
/*
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
* Tree traversal -- TODO.md section 1.8, complete.
|
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
|
|
|
*
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
* The old version of this file counted steps, which cannot tell the three
|
|
|
|
|
* depth-first orders apart because all three visit all seven nodes -- and could
|
|
|
|
|
* not prove that AKERR_ITERATOR_BREAK stopped anything either, because it hid
|
|
|
|
|
* its target in tree[6], the last node visited in every depth-first order. Every
|
|
|
|
|
* traversal here records the node pointers it was handed, in order, and compares
|
|
|
|
|
* against the expected sequence.
|
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
|
|
|
*
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
* The 7-node tree used throughout:
|
|
|
|
|
*
|
|
|
|
|
* TREE[0]
|
|
|
|
|
* +--------^^---------+
|
|
|
|
|
* | |
|
|
|
|
|
* TREE[1] TREE[2]
|
|
|
|
|
* +---^^---+ +---^^---+
|
|
|
|
|
* | | | |
|
|
|
|
|
* TREE[3] TREE[4] TREE[5] TREE[6]
|
|
|
|
|
*
|
|
|
|
|
* pre-order 0 1 3 4 2 5 6
|
|
|
|
|
* in-order 3 1 4 0 5 2 6
|
|
|
|
|
* post-order 3 4 1 5 6 2 0
|
|
|
|
|
* BFS 0 1 2 3 4 5 6
|
|
|
|
|
* BFS_RIGHT 0 2 1 6 5 4 3
|
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
|
|
|
*/
|
2026-06-27 13:13:17 -04:00
|
|
|
|
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 "aksl_capture.h"
|
|
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
#define MAX_LEAVES 7
|
|
|
|
|
#define MAX_VISITS 32
|
2026-06-27 13:13:17 -04:00
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
typedef struct VisitLog
|
2026-06-27 13:13:17 -04:00
|
|
|
{
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
int count;
|
|
|
|
|
aksl_TreeNode *seen[MAX_VISITS];
|
|
|
|
|
aksl_TreeNode *break_at; /* node to raise ITERATOR_BREAK on, or NULL */
|
|
|
|
|
aksl_TreeNode *fail_at; /* node to raise AKERR_VALUE on, or NULL */
|
|
|
|
|
} VisitLog;
|
2026-06-27 13:13:17 -04:00
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
static void visitlog_init(VisitLog *log)
|
2026-06-27 13:13:17 -04:00
|
|
|
{
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
memset((void *)log, 0x00, sizeof(VisitLog));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static akerr_ErrorContext AKERR_NOIGNORE *record_visit(aksl_TreeNode *node, void *data)
|
|
|
|
|
{
|
|
|
|
|
VisitLog *log = NULL;
|
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
|
|
|
|
2026-06-27 13:13:17 -04:00
|
|
|
PREPARE_ERROR(e);
|
|
|
|
|
FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "node");
|
|
|
|
|
FAIL_ZERO_RETURN(e, data, AKERR_NULLPOINTER, "data");
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
log = (VisitLog *)data;
|
|
|
|
|
if ( log->count < MAX_VISITS ) {
|
|
|
|
|
log->seen[log->count] = node;
|
|
|
|
|
}
|
|
|
|
|
log->count += 1;
|
|
|
|
|
if ( log->fail_at == node ) {
|
|
|
|
|
FAIL_RETURN(e, AKERR_VALUE, "iterator failed at node %p", (void *)node);
|
|
|
|
|
}
|
|
|
|
|
if ( log->break_at == node ) {
|
|
|
|
|
FAIL_RETURN(e, AKERR_ITERATOR_BREAK, "stop at node %p", (void *)node);
|
2026-06-27 13:13:17 -04:00
|
|
|
}
|
|
|
|
|
SUCCEED_RETURN(e);
|
|
|
|
|
}
|
|
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
static void build_tree(aksl_TreeNode *tree)
|
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
|
|
|
{
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
|
|
for ( i = 0; i < MAX_LEAVES; i++ ) {
|
|
|
|
|
memset((void *)&tree[i], 0x00, sizeof(aksl_TreeNode));
|
|
|
|
|
}
|
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
|
|
|
tree[0].left = &tree[1];
|
|
|
|
|
tree[0].right = &tree[2];
|
|
|
|
|
tree[1].left = &tree[3];
|
|
|
|
|
tree[1].right = &tree[4];
|
|
|
|
|
tree[2].left = &tree[5];
|
|
|
|
|
tree[2].right = &tree[6];
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Assert that the visit log matches `expected`, which is a list of tree indices. */
|
|
|
|
|
static int check_order(VisitLog *log, aksl_TreeNode *tree,
|
|
|
|
|
const int *expected, int n)
|
|
|
|
|
{
|
|
|
|
|
int i = 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
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
if ( log->count != n ) {
|
|
|
|
|
fprintf(stderr, " CHECK FAILED: visited %d nodes, expected %d\n",
|
|
|
|
|
log->count, n);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
for ( i = 0; i < n; i++ ) {
|
|
|
|
|
if ( log->seen[i] != &tree[expected[i]] ) {
|
|
|
|
|
fprintf(stderr, " CHECK FAILED: visit %d was node %ld, expected %d\n",
|
|
|
|
|
i, (long)(log->seen[i] - tree), expected[i]);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
/* aksl_tree_node_init */
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
static int test_node_init_zeroes_the_links(void)
|
|
|
|
|
{
|
|
|
|
|
aksl_TreeNode node;
|
|
|
|
|
int payload = 3;
|
|
|
|
|
|
|
|
|
|
memset((void *)&node, 0xff, sizeof(node));
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_node_init(&node, &payload));
|
|
|
|
|
AKSL_CHECK(node.parent == NULL);
|
|
|
|
|
AKSL_CHECK(node.left == NULL);
|
|
|
|
|
AKSL_CHECK(node.right == NULL);
|
|
|
|
|
AKSL_CHECK(node.leaf == (void *)&payload);
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_tree_node_init(NULL, NULL), AKERR_NULLPOINTER);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
/* Traversal orders */
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
static int test_preorder_visits_root_left_right(void)
|
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_TreeNode tree[MAX_LEAVES];
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
VisitLog log;
|
|
|
|
|
static const int expected[MAX_LEAVES] = { 0, 1, 3, 4, 2, 5, 6 };
|
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
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
build_tree(tree);
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_PREORDER, &log));
|
|
|
|
|
AKSL_CHECK(check_order(&log, tree, expected, MAX_LEAVES) == 0);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int test_inorder_visits_left_root_right(void)
|
|
|
|
|
{
|
|
|
|
|
aksl_TreeNode tree[MAX_LEAVES];
|
|
|
|
|
VisitLog log;
|
|
|
|
|
static const int expected[MAX_LEAVES] = { 3, 1, 4, 0, 5, 2, 6 };
|
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
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
build_tree(tree);
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_INORDER, &log));
|
|
|
|
|
AKSL_CHECK(check_order(&log, tree, expected, MAX_LEAVES) == 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
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
static int test_postorder_visits_left_right_root(void)
|
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
|
|
|
{
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
aksl_TreeNode tree[MAX_LEAVES];
|
|
|
|
|
VisitLog log;
|
|
|
|
|
static const int expected[MAX_LEAVES] = { 3, 4, 1, 5, 6, 2, 0 };
|
|
|
|
|
|
|
|
|
|
build_tree(tree);
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_POSTORDER, &log));
|
|
|
|
|
AKSL_CHECK(check_order(&log, tree, expected, MAX_LEAVES) == 0);
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
/* AKSL_TREE_SEARCH_DFS is documented as an alias for the pre-order mode. */
|
|
|
|
|
static int test_dfs_is_an_alias_for_preorder(void)
|
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
|
|
|
{
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
aksl_TreeNode tree[MAX_LEAVES];
|
|
|
|
|
VisitLog log;
|
|
|
|
|
static const int expected[MAX_LEAVES] = { 0, 1, 3, 4, 2, 5, 6 };
|
|
|
|
|
|
|
|
|
|
build_tree(tree);
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS, &log));
|
|
|
|
|
AKSL_CHECK(check_order(&log, tree, expected, MAX_LEAVES) == 0);
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
/*
|
|
|
|
|
* BFS was AKERR_NOT_IMPLEMENTED, and the lalloc/lfree parameters that existed to
|
|
|
|
|
* serve it were defaulted and then never called -- TODO.md 2.2.8 and 2.2.10.
|
|
|
|
|
* Both modes work now, and the allocator test below proves the queue is real.
|
|
|
|
|
*/
|
|
|
|
|
static int test_bfs_visits_level_by_level(void)
|
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
|
|
|
{
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
aksl_TreeNode tree[MAX_LEAVES];
|
|
|
|
|
VisitLog log;
|
|
|
|
|
static const int expected[MAX_LEAVES] = { 0, 1, 2, 3, 4, 5, 6 };
|
|
|
|
|
|
|
|
|
|
build_tree(tree);
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_BFS, &log));
|
|
|
|
|
AKSL_CHECK(check_order(&log, tree, expected, MAX_LEAVES) == 0);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int test_bfs_right_visits_right_child_first(void)
|
|
|
|
|
{
|
|
|
|
|
aksl_TreeNode tree[MAX_LEAVES];
|
|
|
|
|
VisitLog log;
|
|
|
|
|
static const int expected[MAX_LEAVES] = { 0, 2, 1, 6, 5, 4, 3 };
|
|
|
|
|
|
|
|
|
|
build_tree(tree);
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_BFS_RIGHT, &log));
|
|
|
|
|
AKSL_CHECK(check_order(&log, tree, expected, MAX_LEAVES) == 0);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* AKSL_TREE_SEARCH_VISIT: this node and no further. */
|
|
|
|
|
static int test_visit_mode_does_not_descend(void)
|
|
|
|
|
{
|
|
|
|
|
aksl_TreeNode tree[MAX_LEAVES];
|
|
|
|
|
VisitLog log;
|
|
|
|
|
static const int expected[1] = { 0 };
|
|
|
|
|
|
|
|
|
|
build_tree(tree);
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_VISIT, &log));
|
|
|
|
|
AKSL_CHECK(check_order(&log, tree, expected, 1) == 0);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
/* Custom allocator */
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
static int counting_alloc_calls = 0;
|
|
|
|
|
static int counting_free_calls = 0;
|
|
|
|
|
|
|
|
|
|
static akerr_ErrorContext AKERR_NOIGNORE *counting_alloc(size_t size, void **dest)
|
|
|
|
|
{
|
|
|
|
|
counting_alloc_calls += 1;
|
|
|
|
|
return aksl_malloc(size, dest);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static akerr_ErrorContext AKERR_NOIGNORE *counting_free(void *ptr)
|
|
|
|
|
{
|
|
|
|
|
counting_free_calls += 1;
|
|
|
|
|
return aksl_free(ptr);
|
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 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
|
|
|
/*
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
* TODO.md 1.8: "Custom lalloc/lfree are actually invoked -- currently they are
|
|
|
|
|
* stored and never called". They are called now, once per node enqueued, and
|
|
|
|
|
* every allocation is released. The depth-first modes allocate nothing at all,
|
|
|
|
|
* which is the other half of the contract.
|
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
|
|
|
*/
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
static int test_custom_allocator_is_used_and_balanced(void)
|
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_TreeNode tree[MAX_LEAVES];
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
VisitLog log;
|
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
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
build_tree(tree);
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
counting_alloc_calls = 0;
|
|
|
|
|
counting_free_calls = 0;
|
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
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit,
|
|
|
|
|
&counting_alloc, &counting_free,
|
|
|
|
|
AKSL_TREE_SEARCH_BFS, &log));
|
|
|
|
|
AKSL_CHECK(log.count == MAX_LEAVES);
|
|
|
|
|
/* One queue entry per node visited, and every one of them released. */
|
|
|
|
|
AKSL_CHECK(counting_alloc_calls == MAX_LEAVES);
|
|
|
|
|
AKSL_CHECK(counting_free_calls == MAX_LEAVES);
|
|
|
|
|
|
|
|
|
|
/* Depth-first needs no queue, so it must not touch the allocator. */
|
|
|
|
|
counting_alloc_calls = 0;
|
|
|
|
|
counting_free_calls = 0;
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit,
|
|
|
|
|
&counting_alloc, &counting_free,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_PREORDER, &log));
|
|
|
|
|
AKSL_CHECK(counting_alloc_calls == 0);
|
|
|
|
|
AKSL_CHECK(counting_free_calls == 0);
|
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
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
/* A break part-way through a BFS still drains the queue it had already built. */
|
|
|
|
|
static int test_break_during_bfs_releases_the_queue(void)
|
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
|
|
|
{
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
aksl_TreeNode tree[MAX_LEAVES];
|
|
|
|
|
VisitLog log;
|
|
|
|
|
|
|
|
|
|
build_tree(tree);
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
log.break_at = &tree[1];
|
|
|
|
|
counting_alloc_calls = 0;
|
|
|
|
|
counting_free_calls = 0;
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit,
|
|
|
|
|
&counting_alloc, &counting_free,
|
|
|
|
|
AKSL_TREE_SEARCH_BFS, &log));
|
|
|
|
|
/* 0 and 1 visited; the walk stops at 1 with 2 still sitting in the queue. */
|
|
|
|
|
AKSL_CHECK(log.count == 2);
|
|
|
|
|
AKSL_CHECK(counting_alloc_calls > 0);
|
|
|
|
|
AKSL_CHECK(counting_alloc_calls == counting_free_calls);
|
|
|
|
|
return 0;
|
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
|
|
|
}
|
|
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
/* Degenerate shapes */
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
static int test_single_node_tree_visits_once_in_every_order(void)
|
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
|
|
|
{
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
aksl_TreeNode node;
|
|
|
|
|
VisitLog log;
|
|
|
|
|
static const uint8_t modes[] = {
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_PREORDER,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_INORDER,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_POSTORDER,
|
|
|
|
|
AKSL_TREE_SEARCH_BFS,
|
|
|
|
|
AKSL_TREE_SEARCH_BFS_RIGHT,
|
|
|
|
|
AKSL_TREE_SEARCH_VISIT,
|
|
|
|
|
};
|
|
|
|
|
size_t i = 0;
|
|
|
|
|
|
|
|
|
|
for ( i = 0; i < sizeof(modes) / sizeof(modes[0]); i++ ) {
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_node_init(&node, NULL));
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&node, &record_visit, NULL, NULL,
|
|
|
|
|
modes[i], &log));
|
|
|
|
|
AKSL_CHECK(log.count == 1);
|
|
|
|
|
AKSL_CHECK(log.seen[0] == &node);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
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
|
|
|
}
|
|
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
/* Left-only and right-only chains of three nodes, no branching anywhere. */
|
|
|
|
|
static int test_degenerate_chains(void)
|
|
|
|
|
{
|
|
|
|
|
aksl_TreeNode chain[3];
|
|
|
|
|
VisitLog log;
|
|
|
|
|
static const int forward[3] = { 0, 1, 2 };
|
|
|
|
|
static const int backward[3] = { 2, 1, 0 };
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
|
|
/* Left-only: 0 -> 1 -> 2 */
|
|
|
|
|
for ( i = 0; i < 3; i++ ) {
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_node_init(&chain[i], NULL));
|
|
|
|
|
}
|
|
|
|
|
chain[0].left = &chain[1];
|
|
|
|
|
chain[1].left = &chain[2];
|
|
|
|
|
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_PREORDER, &log));
|
|
|
|
|
AKSL_CHECK(check_order(&log, chain, forward, 3) == 0);
|
|
|
|
|
|
|
|
|
|
/* In-order down a left chain arrives at the deepest node first. */
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_INORDER, &log));
|
|
|
|
|
AKSL_CHECK(check_order(&log, chain, backward, 3) == 0);
|
|
|
|
|
|
|
|
|
|
/* Right-only: the same shape, mirrored. */
|
|
|
|
|
for ( i = 0; i < 3; i++ ) {
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_node_init(&chain[i], NULL));
|
|
|
|
|
}
|
|
|
|
|
chain[0].right = &chain[1];
|
|
|
|
|
chain[1].right = &chain[2];
|
|
|
|
|
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_INORDER, &log));
|
|
|
|
|
AKSL_CHECK(check_order(&log, chain, forward, 3) == 0);
|
|
|
|
|
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_BFS, &log));
|
|
|
|
|
AKSL_CHECK(check_order(&log, chain, forward, 3) == 0);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* TODO.md 1.8 / 2.2.7: a chain deeper than the recursion can take. It used to
|
|
|
|
|
* overflow the stack; it is AKERR_OUTOFBOUNDS now, and the message names the
|
|
|
|
|
* documented limit. Built one node past the cap so the failure is the cap itself
|
|
|
|
|
* and not some incidental shortfall. `static` because AKSL_TREE_MAX_DEPTH nodes
|
|
|
|
|
* on the stack of a test function is not the point of the test.
|
|
|
|
|
*/
|
|
|
|
|
static int test_tree_deeper_than_the_cap_is_out_of_bounds(void)
|
|
|
|
|
{
|
|
|
|
|
static aksl_TreeNode chain[AKSL_TREE_MAX_DEPTH + 2];
|
|
|
|
|
VisitLog log;
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
|
|
for ( i = 0; i < AKSL_TREE_MAX_DEPTH + 2; i++ ) {
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_node_init(&chain[i], NULL));
|
|
|
|
|
}
|
|
|
|
|
for ( i = 0; i < AKSL_TREE_MAX_DEPTH + 1; i++ ) {
|
|
|
|
|
chain[i].left = &chain[i + 1];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(
|
|
|
|
|
aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_PREORDER, &log),
|
|
|
|
|
AKERR_OUTOFBOUNDS, "AKSL_TREE_MAX_DEPTH");
|
|
|
|
|
|
|
|
|
|
/* Exactly at the cap is fine -- a limit, not an off-by-one. */
|
|
|
|
|
chain[AKSL_TREE_MAX_DEPTH - 1].left = NULL;
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_PREORDER, &log));
|
|
|
|
|
AKSL_CHECK(log.count == AKSL_TREE_MAX_DEPTH);
|
2026-07-31 08:09:04 -04:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* And again leaning right. The recursion counts depth separately for each
|
|
|
|
|
* child, so a chain that only ever goes left says nothing about whether the
|
|
|
|
|
* right-hand descent counts at all -- mutation testing found exactly that:
|
|
|
|
|
* changing the right child's `depth + 1` to `depth + 0` survived the whole
|
|
|
|
|
* suite, because nothing here had ever recursed right more than three deep.
|
|
|
|
|
*/
|
|
|
|
|
for ( i = 0; i < AKSL_TREE_MAX_DEPTH + 2; i++ ) {
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_node_init(&chain[i], NULL));
|
|
|
|
|
}
|
|
|
|
|
for ( i = 0; i < AKSL_TREE_MAX_DEPTH + 1; i++ ) {
|
|
|
|
|
chain[i].right = &chain[i + 1];
|
|
|
|
|
}
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_PREORDER, &log),
|
|
|
|
|
AKERR_OUTOFBOUNDS);
|
|
|
|
|
|
|
|
|
|
/* Both orders that descend right, so in-order and post-order count too. */
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_INORDER, &log),
|
|
|
|
|
AKERR_OUTOFBOUNDS);
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_POSTORDER, &log),
|
|
|
|
|
AKERR_OUTOFBOUNDS);
|
|
|
|
|
|
|
|
|
|
/* And breadth-first, whose depth is carried on the queue entry instead. */
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_tree_iterate(&chain[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_BFS, &log),
|
|
|
|
|
AKERR_OUTOFBOUNDS);
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* A child pointing back at an ancestor. This used to recurse until the process
|
|
|
|
|
* died. The depth-first walk carries the ancestor chain and recognises the node
|
|
|
|
|
* as its own forebear; the breadth-first walk has no ancestor chain to compare
|
|
|
|
|
* against, so the same tree comes back as AKERR_OUTOFBOUNDS through the depth
|
|
|
|
|
* cap instead -- a different status for the same shape, which the header says
|
|
|
|
|
* out loud rather than leaving to be discovered.
|
|
|
|
|
*/
|
|
|
|
|
static int test_cyclic_tree_is_caught(void)
|
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_TreeNode tree[MAX_LEAVES];
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
VisitLog log;
|
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
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
build_tree(tree);
|
|
|
|
|
tree[3].left = &tree[0]; /* back to the root */
|
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
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
visitlog_init(&log);
|
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_CHECK_STATUS_MSG_CONTAINS(
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_PREORDER, &log),
|
|
|
|
|
AKERR_CIRCULAR_REFERENCE, "own ancestor");
|
|
|
|
|
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_BFS, &log),
|
|
|
|
|
AKERR_OUTOFBOUNDS);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
/* Callback control flow */
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The defect that made ITERATOR_BREAK useless on a tree: the recursive frame
|
|
|
|
|
* that raised the break handled it in its own PROCESS/HANDLE block and returned
|
|
|
|
|
* success, so the parent frame's PASS saw nothing wrong and carried straight on
|
|
|
|
|
* into the sibling subtree. All seven nodes were visited no matter where the
|
|
|
|
|
* break was raised. TODO.md 2.1.3.
|
|
|
|
|
*
|
|
|
|
|
* One case per order, each breaking on a node that is *not* last in that order --
|
|
|
|
|
* which is precisely what the old test could not do, because it hid its target
|
|
|
|
|
* in tree[6], the final node in all three depth-first walks.
|
|
|
|
|
*/
|
|
|
|
|
static int test_break_aborts_the_whole_traversal(void)
|
|
|
|
|
{
|
|
|
|
|
aksl_TreeNode tree[MAX_LEAVES];
|
|
|
|
|
VisitLog log;
|
|
|
|
|
|
|
|
|
|
build_tree(tree);
|
|
|
|
|
|
|
|
|
|
/* Pre-order 0 1 3 ... : breaking at 3 is the third visit. */
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
log.break_at = &tree[3];
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_PREORDER, &log));
|
|
|
|
|
AKSL_CHECK(log.count == 3);
|
|
|
|
|
|
|
|
|
|
/* In-order 3 1 4 ... : breaking at 4 is the third visit. */
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
log.break_at = &tree[4];
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_INORDER, &log));
|
|
|
|
|
AKSL_CHECK(log.count == 3);
|
|
|
|
|
|
|
|
|
|
/* Post-order 3 4 1 5 ... : breaking at 5 is the fourth visit. */
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
log.break_at = &tree[5];
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_POSTORDER, &log));
|
|
|
|
|
AKSL_CHECK(log.count == 4);
|
|
|
|
|
|
|
|
|
|
/* BFS 0 1 2 3 ... : breaking at 2 is the third visit. */
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
log.break_at = &tree[2];
|
|
|
|
|
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_BFS, &log));
|
|
|
|
|
AKSL_CHECK(log.count == 3);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Any other status propagates out with its message and its own status intact. */
|
|
|
|
|
static int test_callback_error_propagates(void)
|
|
|
|
|
{
|
|
|
|
|
aksl_TreeNode tree[MAX_LEAVES];
|
|
|
|
|
VisitLog log;
|
|
|
|
|
|
|
|
|
|
build_tree(tree);
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
log.fail_at = &tree[3];
|
|
|
|
|
|
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_CHECK_STATUS_MSG_CONTAINS(
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_PREORDER, &log),
|
|
|
|
|
AKERR_VALUE, "iterator failed at node");
|
|
|
|
|
AKSL_CHECK(log.count == 3);
|
|
|
|
|
|
|
|
|
|
/* And out of a BFS, where the queue has to be drained on the way past. */
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
log.fail_at = &tree[2];
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_BFS, &log),
|
|
|
|
|
AKERR_VALUE);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
/* Argument validation */
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
static int test_null_arguments(void)
|
|
|
|
|
{
|
|
|
|
|
aksl_TreeNode tree[MAX_LEAVES];
|
|
|
|
|
VisitLog log;
|
|
|
|
|
|
|
|
|
|
build_tree(tree);
|
|
|
|
|
visitlog_init(&log);
|
|
|
|
|
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_tree_iterate(NULL, &record_visit, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_PREORDER, &log),
|
|
|
|
|
AKERR_NULLPOINTER);
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_tree_iterate(&tree[0], NULL, NULL, NULL,
|
|
|
|
|
AKSL_TREE_SEARCH_DFS_PREORDER, &log),
|
|
|
|
|
AKERR_NULLPOINTER);
|
|
|
|
|
AKSL_CHECK(log.count == 0);
|
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
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
/*
|
|
|
|
|
* TODO.md 2.2.9: the switch had no default, so an unrecognised mode -- and
|
|
|
|
|
* AKSL_TREE_SEARCH_VISIT, which the header documented but nothing implemented --
|
|
|
|
|
* fell straight through to SUCCEED_RETURN having visited nothing at all. A
|
|
|
|
|
* traversal that silently did not happen, reported as success.
|
|
|
|
|
*/
|
|
|
|
|
static int test_unknown_searchmode_is_a_value_error(void)
|
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_TreeNode tree[MAX_LEAVES];
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
VisitLog log;
|
|
|
|
|
|
|
|
|
|
build_tree(tree);
|
|
|
|
|
visitlog_init(&log);
|
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_CHECK_STATUS_MSG_CONTAINS(
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL, 99, &log),
|
|
|
|
|
AKERR_VALUE, "unknown searchmode");
|
|
|
|
|
AKSL_CHECK(log.count == 0);
|
|
|
|
|
|
|
|
|
|
/* 6 is one past the last defined mode, and just as unacceptable. */
|
|
|
|
|
AKSL_CHECK_STATUS(aksl_tree_iterate(&tree[0], &record_visit, NULL, NULL, 6, &log),
|
|
|
|
|
AKERR_VALUE);
|
|
|
|
|
AKSL_CHECK(log.count == 0);
|
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
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-27 13:13:17 -04:00
|
|
|
int main(void)
|
|
|
|
|
{
|
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
|
|
|
int failures = 0;
|
|
|
|
|
|
|
|
|
|
akerr_init();
|
|
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
AKSL_RUN(failures, test_node_init_zeroes_the_links);
|
|
|
|
|
|
|
|
|
|
AKSL_RUN(failures, test_preorder_visits_root_left_right);
|
|
|
|
|
AKSL_RUN(failures, test_inorder_visits_left_root_right);
|
|
|
|
|
AKSL_RUN(failures, test_postorder_visits_left_right_root);
|
|
|
|
|
AKSL_RUN(failures, test_dfs_is_an_alias_for_preorder);
|
|
|
|
|
AKSL_RUN(failures, test_bfs_visits_level_by_level);
|
|
|
|
|
AKSL_RUN(failures, test_bfs_right_visits_right_child_first);
|
|
|
|
|
AKSL_RUN(failures, test_visit_mode_does_not_descend);
|
|
|
|
|
|
|
|
|
|
AKSL_RUN(failures, test_custom_allocator_is_used_and_balanced);
|
|
|
|
|
AKSL_RUN(failures, test_break_during_bfs_releases_the_queue);
|
|
|
|
|
|
|
|
|
|
AKSL_RUN(failures, test_single_node_tree_visits_once_in_every_order);
|
|
|
|
|
AKSL_RUN(failures, test_degenerate_chains);
|
|
|
|
|
AKSL_RUN(failures, test_tree_deeper_than_the_cap_is_out_of_bounds);
|
|
|
|
|
AKSL_RUN(failures, test_cyclic_tree_is_caught);
|
|
|
|
|
|
|
|
|
|
AKSL_RUN(failures, test_break_aborts_the_whole_traversal);
|
|
|
|
|
AKSL_RUN(failures, test_callback_error_propagates);
|
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
|
|
|
|
Fix the six confirmed defects and close the API contract gaps
TODO.md section 2.1 recorded six defects reproduced against the built
library, and section 2.2 seventeen contract gaps. Both are closed. The
four tests registered in AKSL_KNOWN_FAILING_TESTS are folded back into
the tests for the things they test, and that list is now empty.
The defects:
2.1.1 aksl_list_append conflated Floyd cycle detection with finding
the tail, so `tail` tracked the node behind the midpoint. Any
append to a list of 2+ nodes silently dropped everything after
it. Two separate walks now: Floyd to prove the list is finite,
then a plain walk to the end.
2.1.2 aksl_list_iterate started visiting from Floyd's `slow` cursor,
so the whole first half of the list -- head included -- was
never passed to the callback. It starts at the head.
2.1.3 AKERR_ITERATOR_BREAK did not stop a tree traversal: the frame
that raised it handled it and returned success, so the parent
carried on into the sibling subtree. The recursion is split out
and propagates the break; only the public entry swallows it.
2.1.4 va_end now matches every va_start on every path.
2.1.5 The ato* family had no error channel at all. Reimplemented over
a new strto* family with errno cleared, an endptr check and a
range check: AKERR_VALUE for junk, ERANGE for overflow.
2.1.6 aksl_realpath never checked resolved_path, could not be told
the buffer size, and formatted an unspecified buffer with %s on
its own error path. It takes a length; aksl_realpath_alloc is
the allocating form.
The contract gaps, in brief: errno is cleared before every wrapped call
and read back through a fallback so no error can carry status 0; fopen
validates pathname and mode; fread/fwrite report the transferred count
through a required out-param and no longer call a short transfer a
success; aksl_sprintf is gone in favour of aksl_snprintf, which treats
truncation as an error; the variadic wrappers carry format attributes;
djb2 reads bytes as unsigned; tree traversal is depth- and cycle-bounded
and implements BFS, so lalloc/lfree are used rather than merely stored;
an unknown searchmode is AKERR_VALUE rather than silent success;
list_pop takes the head by reference; aksl_freep, the node initialisers
and extern "C" are new.
Build: -pg is out of the default build (it never reached the C compiler
anyway, and it is what produced the stray gmon.out), -Wall -Wextra are
in, and there is a .gitignore.
Tests: 11 binaries, all green under the normal and sanitizer builds.
Visit-order assertions replace the step counts that could not tell the
three depth-first orders apart.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:14:11 -04:00
|
|
|
AKSL_RUN(failures, test_null_arguments);
|
|
|
|
|
AKSL_RUN(failures, test_unknown_searchmode_is_a_value_error);
|
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
|
|
|
|
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_REPORT(failures);
|
2026-06-27 13:13:17 -04:00
|
|
|
}
|