Files
libakstdlib/tests/test_strto.c

341 lines
12 KiB
C
Raw Normal View History

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 strto* family -- TODO.md section 3.1.
*
* These are the real implementation behind the ato* wrappers and the thing
* akbasic had to hand-write for itself (its src/convert.c, ~60 lines) because
* the library would not do it. What they add over the ato* forms is a base and
* an endptr: pass a non-NULL endptr to parse a number off the front of a longer
* string and find out where it stopped, and the trailing-junk check goes away
* because the caller has taken responsibility for what follows.
*
* tests/test_convert.c covers what the two families share; this file is the part
* that is specific to having a base and an endptr.
*/
#include "aksl_capture.h"
#include <errno.h>
#include <limits.h>
/* ---------------------------------------------------------------------- */
/* Base handling */
/* ---------------------------------------------------------------------- */
static int test_base_is_honoured(void)
{
long out = 0;
AKSL_CHECK_OK(aksl_strtol("ff", NULL, 16, &out));
AKSL_CHECK(out == 255);
AKSL_CHECK_OK(aksl_strtol("1010", NULL, 2, &out));
AKSL_CHECK(out == 10);
AKSL_CHECK_OK(aksl_strtol("777", NULL, 8, &out));
AKSL_CHECK(out == 511);
AKSL_CHECK_OK(aksl_strtol("z", NULL, 36, &out));
AKSL_CHECK(out == 35);
return 0;
}
/* Base 0 auto-detects the 0x and 0 prefixes, which is the counterpart to
* tests/test_convert.c's test that aksl_atoi refuses "0x10". */
static int test_base_zero_detects_the_prefix(void)
{
long out = 0;
AKSL_CHECK_OK(aksl_strtol("0x10", NULL, 0, &out));
AKSL_CHECK(out == 16);
AKSL_CHECK_OK(aksl_strtol("010", NULL, 0, &out));
AKSL_CHECK(out == 8);
AKSL_CHECK_OK(aksl_strtol("10", NULL, 0, &out));
AKSL_CHECK(out == 10);
return 0;
}
/* A digit that does not belong to the base is where the number ends. */
static int test_digits_outside_the_base_end_the_number(void)
{
long out = 0;
char *end = NULL;
/* "9" is not an octal digit, so with a NULL endptr it is trailing junk. */
AKSL_CHECK_STATUS(aksl_strtol("129", NULL, 8, &out), AKERR_VALUE);
/* With an endptr the caller gets the partial parse and the stopping point. */
AKSL_CHECK_OK(aksl_strtol("129", &end, 8, &out));
AKSL_CHECK(out == 10); /* 012 octal */
AKSL_CHECK(end != NULL && *end == '9');
return 0;
}
/* An invalid base is EINVAL from strtol(3) rather than a silent answer. */
static int test_invalid_base_is_rejected(void)
{
long out = 99;
AKSL_CHECK_STATUS(aksl_strtol("10", NULL, 37, &out), EINVAL);
AKSL_CHECK(out == 0);
AKSL_CHECK_STATUS(aksl_strtol("10", NULL, 1, &out), EINVAL);
AKSL_CHECK(out == 0);
return 0;
}
/* ---------------------------------------------------------------------- */
/* endptr */
/* ---------------------------------------------------------------------- */
/*
* With an endptr, trailing text is the caller's business and not an error. This
* is the shape a tokenizer wants: parse a number, carry on from where it ended.
*/
static int test_endptr_reports_where_parsing_stopped(void)
{
const char *input = "42 rest of the line";
char *end = NULL;
long out = 0;
AKSL_CHECK_OK(aksl_strtol(input, &end, 10, &out));
AKSL_CHECK(out == 42);
AKSL_CHECK(end == input + 2);
AKSL_CHECK(strcmp(end, " rest of the line") == 0);
return 0;
}
/* Consuming nothing is still an error, endptr or not: there was no number. */
static int test_no_digits_is_an_error_even_with_an_endptr(void)
{
char *end = (char *)0x1;
long out = 99;
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_strtol("nope", &end, 10, &out),
AKERR_VALUE, "no digits");
AKSL_CHECK(out == 0);
return 0;
}
Version at 0.2.0: complete the wishlist, document it, gate the docs Closes what was left of TODO.md sections 1, 2 and 3, and rewrites that file to hold outstanding items only. The API break gets a minor bump, because pre-1.0 the soname carries MAJOR.MINOR and 0.1 and 0.2 are therefore different ABIs. Five signatures changed and the ato* contract with them; UPGRADING.md is new and lists every one, with the before/after for the cases the compiler cannot warn about. Section 3.1 is finished: reallocarray with the multiplication checked, aligned_alloc and posix_memalign, asprintf/vasprintf, scanf/vscanf. Four functions on that list are deliberately absent rather than missing -- sprintf, strtok, setbuf and perror -- and TODO.md now says which and why, so nobody adds them thinking they were forgotten. Section 1.9, the cross-cutting tests: tests/test_pool.c drives every failure path AKERR_MAX_ARRAY_ERROR + 10 times and checks the pool after each round, because a wrapper that leaks a slot fails a hundred calls later in unrelated code. It also asserts that each error names the function and file it was raised from, which is what catches a FAIL that migrates into a helper during a refactor: status right, message right, origin quietly lying. tests/negative/ two sources that must FAIL to compile, built with -Werror and registered WILL_FAIL. AKERR_NOIGNORE and the format attributes are enforced by the compiler and by nothing else; drop either and every ordinary test still passes. Thread safety is answered rather than tested: the library is not thread-safe and cannot be made so from here, because libakerror's error pool is an unlocked process-global array. README.md says so plainly and TODO.md carries it as the item blocking any future pthread wrappers. Doxygen is configured and gated. All 147 public functions have @brief, a @param each, @throws per status and @return; EXTRACT_ALL is off and WARN_NO_PARAMDOC on, so `cmake --build build --target docs` fails on an undocumented entity. It ran to 0 warnings. The Doxyfile carries no version -- cmake/RunDoxygen.cmake feeds PROJECT_NUMBER in from project(), so that stays the one place a version is written. CI now builds against the submodule it pins instead of also installing libakerror@main and never linking it, adds -Werror, and gains a sanitizer job. The pre-push hook matches, and runs the docs check too. Coverage: 99.5% of lines (1643/1651), 100% of functions (147/147). The eight uncovered lines are each uncovered on purpose and TODO.md says which and why. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:00:16 -04:00
/*
* Every form writes through endptr, not just the two the other tests happen to
* use. Each type has its own function body, so "strtol handles endptr" says
* nothing whatsoever about strtoull.
*/
static int test_every_form_writes_the_endptr(void)
{
const char *input = "42rest";
char *end = NULL;
long lout = 0;
long long llout = 0;
unsigned long ulout = 0;
unsigned long long ullout = 0;
double dout = 0.0;
float fout = 0.0f;
long double ldout = 0.0L;
end = NULL;
AKSL_CHECK_OK(aksl_strtol(input, &end, 10, &lout));
AKSL_CHECK(lout == 42 && strcmp(end, "rest") == 0);
end = NULL;
AKSL_CHECK_OK(aksl_strtoll(input, &end, 10, &llout));
AKSL_CHECK(llout == 42 && strcmp(end, "rest") == 0);
end = NULL;
AKSL_CHECK_OK(aksl_strtoul(input, &end, 10, &ulout));
AKSL_CHECK(ulout == 42 && strcmp(end, "rest") == 0);
end = NULL;
AKSL_CHECK_OK(aksl_strtoull(input, &end, 10, &ullout));
AKSL_CHECK(ullout == 42 && strcmp(end, "rest") == 0);
end = NULL;
AKSL_CHECK_OK(aksl_strtod(input, &end, &dout));
AKSL_CHECK(dout == 42.0 && strcmp(end, "rest") == 0);
end = NULL;
AKSL_CHECK_OK(aksl_strtof(input, &end, &fout));
AKSL_CHECK(fout == 42.0f && strcmp(end, "rest") == 0);
end = NULL;
AKSL_CHECK_OK(aksl_strtold(input, &end, &ldout));
AKSL_CHECK(ldout == 42.0L && strcmp(end, "rest") == 0);
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
/* Successive calls chained through endptr walk a whole list of numbers. */
static int test_endptr_chains_across_a_list(void)
{
const char *input = "1,2,3";
char *cursor = (char *)input;
long out = 0;
long total = 0;
int seen = 0;
while ( *cursor != '\0' ) {
AKSL_CHECK_OK(aksl_strtol(cursor, &cursor, 10, &out));
total += out;
seen += 1;
if ( *cursor == ',' ) {
cursor++;
}
}
AKSL_CHECK(seen == 3);
AKSL_CHECK(total == 6);
return 0;
}
/* ---------------------------------------------------------------------- */
/* Ranges and signs */
/* ---------------------------------------------------------------------- */
static int test_range_errors_per_type(void)
{
long lout = 99;
long long llout = 99;
unsigned long ulout = 99;
unsigned long long ullout = 99;
char buf[64];
snprintf(buf, sizeof(buf), "%lld9", (long long)LLONG_MAX);
AKSL_CHECK_STATUS(aksl_strtol(buf, NULL, 10, &lout), ERANGE);
AKSL_CHECK(lout == 0);
AKSL_CHECK_STATUS(aksl_strtoll(buf, NULL, 10, &llout), ERANGE);
AKSL_CHECK(llout == 0);
snprintf(buf, sizeof(buf), "%llu9", (unsigned long long)ULLONG_MAX);
AKSL_CHECK_STATUS(aksl_strtoul(buf, NULL, 10, &ulout), ERANGE);
AKSL_CHECK(ulout == 0);
AKSL_CHECK_STATUS(aksl_strtoull(buf, NULL, 10, &ullout), ERANGE);
AKSL_CHECK(ullout == 0);
return 0;
}
/*
* strtoul(3) and strtoull(3) accept a leading '-' and hand back the negation
* wrapped into the unsigned range, so "-1" parses as ULONG_MAX and reports
* nothing at all. That is exactly the silent failure this library exists to
* surface, so the sign is refused before the call.
*/
static int test_unsigned_forms_refuse_a_negative(void)
{
unsigned long ulout = 99;
unsigned long long ullout = 99;
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_strtoul("-1", NULL, 10, &ulout),
AKERR_VALUE, "negative");
AKSL_CHECK(ulout == 0);
/* Including behind leading whitespace, which strtoul skips. */
AKSL_CHECK_STATUS(aksl_strtoul(" -1", NULL, 10, &ulout), AKERR_VALUE);
AKSL_CHECK_STATUS(aksl_strtoull("-1", NULL, 10, &ullout), AKERR_VALUE);
AKSL_CHECK(ullout == 0);
/* A leading '+' is fine: it is a sign, not a negation. */
AKSL_CHECK_OK(aksl_strtoul("+7", NULL, 10, &ulout));
AKSL_CHECK(ulout == 7);
return 0;
}
static int test_unsigned_boundaries_round_trip(void)
{
unsigned long ulout = 0;
unsigned long long ullout = 0;
char buf[64];
snprintf(buf, sizeof(buf), "%lu", ULONG_MAX);
AKSL_CHECK_OK(aksl_strtoul(buf, NULL, 10, &ulout));
AKSL_CHECK(ulout == ULONG_MAX);
snprintf(buf, sizeof(buf), "%llu", ULLONG_MAX);
AKSL_CHECK_OK(aksl_strtoull(buf, NULL, 10, &ullout));
AKSL_CHECK(ullout == ULLONG_MAX);
return 0;
}
/* ---------------------------------------------------------------------- */
/* Floating point */
/* ---------------------------------------------------------------------- */
static int test_floating_point_forms(void)
{
double dout = 0.0;
float fout = 0.0f;
long double ldout = 0.0L;
char *end = NULL;
AKSL_CHECK_OK(aksl_strtod("2.5", NULL, &dout));
AKSL_CHECK(dout == 2.5);
AKSL_CHECK_OK(aksl_strtof("2.5", NULL, &fout));
AKSL_CHECK(fout == 2.5f);
AKSL_CHECK_OK(aksl_strtold("2.5", NULL, &ldout));
AKSL_CHECK(ldout == 2.5L);
AKSL_CHECK_OK(aksl_strtod("2.5rest", &end, &dout));
AKSL_CHECK(dout == 2.5);
AKSL_CHECK(strcmp(end, "rest") == 0);
AKSL_CHECK_STATUS(aksl_strtod("2.5rest", NULL, &dout), AKERR_VALUE);
AKSL_CHECK_STATUS(aksl_strtof("nope", NULL, &fout), AKERR_VALUE);
AKSL_CHECK_STATUS(aksl_strtold("nope", NULL, &ldout), AKERR_VALUE);
/* float has a much smaller range than double, and says so. */
AKSL_CHECK_STATUS(aksl_strtof("1e300", NULL, &fout), ERANGE);
AKSL_CHECK(fout == 0.0f);
AKSL_CHECK_OK(aksl_strtod("1e300", NULL, &dout));
AKSL_CHECK(dout > 0.0);
return 0;
}
/* ---------------------------------------------------------------------- */
/* Argument validation */
/* ---------------------------------------------------------------------- */
static int test_every_form_rejects_null_arguments(void)
{
long lout = 0;
long long llout = 0;
unsigned long ulout = 0;
unsigned long long ullout = 0;
double dout = 0.0;
float fout = 0.0f;
long double ldout = 0.0L;
AKSL_CHECK_STATUS(aksl_strtol(NULL, NULL, 10, &lout), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_strtol("1", NULL, 10, NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_strtoll(NULL, NULL, 10, &llout), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_strtoll("1", NULL, 10, NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_strtoul(NULL, NULL, 10, &ulout), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_strtoul("1", NULL, 10, NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_strtoull(NULL, NULL, 10, &ullout), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_strtoull("1", NULL, 10, NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_strtod(NULL, NULL, &dout), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_strtod("1", NULL, NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_strtof(NULL, NULL, &fout), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_strtof("1", NULL, NULL), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_strtold(NULL, NULL, &ldout), AKERR_NULLPOINTER);
AKSL_CHECK_STATUS(aksl_strtold("1", NULL, NULL), AKERR_NULLPOINTER);
return 0;
}
int main(void)
{
int failures = 0;
akerr_init();
AKSL_RUN(failures, test_base_is_honoured);
AKSL_RUN(failures, test_base_zero_detects_the_prefix);
AKSL_RUN(failures, test_digits_outside_the_base_end_the_number);
AKSL_RUN(failures, test_invalid_base_is_rejected);
AKSL_RUN(failures, test_endptr_reports_where_parsing_stopped);
Version at 0.2.0: complete the wishlist, document it, gate the docs Closes what was left of TODO.md sections 1, 2 and 3, and rewrites that file to hold outstanding items only. The API break gets a minor bump, because pre-1.0 the soname carries MAJOR.MINOR and 0.1 and 0.2 are therefore different ABIs. Five signatures changed and the ato* contract with them; UPGRADING.md is new and lists every one, with the before/after for the cases the compiler cannot warn about. Section 3.1 is finished: reallocarray with the multiplication checked, aligned_alloc and posix_memalign, asprintf/vasprintf, scanf/vscanf. Four functions on that list are deliberately absent rather than missing -- sprintf, strtok, setbuf and perror -- and TODO.md now says which and why, so nobody adds them thinking they were forgotten. Section 1.9, the cross-cutting tests: tests/test_pool.c drives every failure path AKERR_MAX_ARRAY_ERROR + 10 times and checks the pool after each round, because a wrapper that leaks a slot fails a hundred calls later in unrelated code. It also asserts that each error names the function and file it was raised from, which is what catches a FAIL that migrates into a helper during a refactor: status right, message right, origin quietly lying. tests/negative/ two sources that must FAIL to compile, built with -Werror and registered WILL_FAIL. AKERR_NOIGNORE and the format attributes are enforced by the compiler and by nothing else; drop either and every ordinary test still passes. Thread safety is answered rather than tested: the library is not thread-safe and cannot be made so from here, because libakerror's error pool is an unlocked process-global array. README.md says so plainly and TODO.md carries it as the item blocking any future pthread wrappers. Doxygen is configured and gated. All 147 public functions have @brief, a @param each, @throws per status and @return; EXTRACT_ALL is off and WARN_NO_PARAMDOC on, so `cmake --build build --target docs` fails on an undocumented entity. It ran to 0 warnings. The Doxyfile carries no version -- cmake/RunDoxygen.cmake feeds PROJECT_NUMBER in from project(), so that stays the one place a version is written. CI now builds against the submodule it pins instead of also installing libakerror@main and never linking it, adds -Werror, and gains a sanitizer job. The pre-push hook matches, and runs the docs check too. Coverage: 99.5% of lines (1643/1651), 100% of functions (147/147). The eight uncovered lines are each uncovered on purpose and TODO.md says which and why. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:00:16 -04:00
AKSL_RUN(failures, test_every_form_writes_the_endptr);
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_no_digits_is_an_error_even_with_an_endptr);
AKSL_RUN(failures, test_endptr_chains_across_a_list);
AKSL_RUN(failures, test_range_errors_per_type);
AKSL_RUN(failures, test_unsigned_forms_refuse_a_negative);
AKSL_RUN(failures, test_unsigned_boundaries_round_trip);
AKSL_RUN(failures, test_floating_point_forms);
AKSL_RUN(failures, test_every_form_rejects_null_arguments);
AKSL_REPORT(failures);
}