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>
This commit is contained in:
@@ -1,19 +1,20 @@
|
||||
/*
|
||||
* String -> number wrappers: aksl_atoi / atol / atoll / atof.
|
||||
*
|
||||
* TODO.md section 1.4. This file covers the parts of the contract that are
|
||||
* stable today: the happy paths (including negatives and leading whitespace)
|
||||
* and the NULL guards.
|
||||
* TODO.md section 1.4, complete. The cases that used to live in
|
||||
* tests/test_convert_strict.c -- registered as a known failure because the ato*
|
||||
* family had no error channel at all (2.1.5) -- are folded back in here now that
|
||||
* they pass: non-numeric input, empty input, trailing junk and overflow are all
|
||||
* errors rather than silent wrong answers.
|
||||
*
|
||||
* It deliberately says nothing about non-numeric input, trailing junk or
|
||||
* overflow. Those all return *success* today (TODO.md 2.1.5) and the correct
|
||||
* behaviour is asserted by tests/test_convert_strict.c, which is registered as
|
||||
* a known failure. Pinning the current lax behaviour here as well would mean
|
||||
* this file starts failing on the day the defect is fixed.
|
||||
* The strto* family these are built on is tested in tests/test_strto.c.
|
||||
*/
|
||||
|
||||
#include "aksl_capture.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
|
||||
static int test_atoi_converts_positive(void)
|
||||
{
|
||||
int out = -1;
|
||||
@@ -46,9 +47,136 @@ static int test_atoi_rejects_null_arguments(void)
|
||||
int out = 0;
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_atoi(NULL, &out),
|
||||
AKERR_NULLPOINTER, "nptr=");
|
||||
AKERR_NULLPOINTER, "nptr=");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_atoi("1", NULL),
|
||||
AKERR_NULLPOINTER, "dest=");
|
||||
AKERR_NULLPOINTER, "dest=");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The whole reason 2.1.5 mattered. atoi("not a number") returned success and 0,
|
||||
* so a caller could not tell a parse failure from a legitimate zero -- and
|
||||
* akbasic had to write ~60 lines of its own strtoll/strtod wrapper to avoid
|
||||
* turning four diagnosable BASIC errors into four wrong answers.
|
||||
*/
|
||||
static int test_non_numeric_input_is_a_value_error(void)
|
||||
{
|
||||
int out = 99;
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_atoi("not a number", &out),
|
||||
AKERR_VALUE, "no digits");
|
||||
/* *dest is 0 on every failure path, never left holding a partial answer. */
|
||||
AKSL_CHECK(out == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_empty_input_is_a_value_error(void)
|
||||
{
|
||||
int out = 99;
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_atoi("", &out), AKERR_VALUE);
|
||||
AKSL_CHECK(out == 0);
|
||||
AKSL_CHECK_STATUS(aksl_atoi(" ", &out), AKERR_VALUE);
|
||||
AKSL_CHECK(out == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_trailing_junk_is_a_value_error(void)
|
||||
{
|
||||
int out = 99;
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_atoi("12abc", &out),
|
||||
AKERR_VALUE, "trailing junk");
|
||||
AKSL_CHECK(out == 0);
|
||||
/* Trailing whitespace counts as junk too: the whole string must be a number. */
|
||||
AKSL_CHECK_STATUS(aksl_atoi("12 ", &out), AKERR_VALUE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* "0x10" through the ato* forms is base 10, so parsing stops at the 'x' and the
|
||||
* rest is trailing junk. TODO.md 1.4 left this open; the answer is that the
|
||||
* prefix-honouring parse is aksl_strtol(nptr, NULL, 0, &dest), and tests/
|
||||
* test_strto.c holds that half.
|
||||
*/
|
||||
static int test_hex_literal_is_rejected_by_the_base_10_forms(void)
|
||||
{
|
||||
int out = 99;
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_atoi("0x10", &out), AKERR_VALUE);
|
||||
AKSL_CHECK(out == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_overflow_is_erange(void)
|
||||
{
|
||||
int out = 99;
|
||||
long lout = 99;
|
||||
long long llout = 99;
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_atoi("99999999999999999999", &out),
|
||||
ERANGE, "out of range");
|
||||
AKSL_CHECK(out == 0);
|
||||
AKSL_CHECK_STATUS(aksl_atol("99999999999999999999999999", &lout), ERANGE);
|
||||
AKSL_CHECK(lout == 0);
|
||||
AKSL_CHECK_STATUS(aksl_atoll("99999999999999999999999999", &llout), ERANGE);
|
||||
AKSL_CHECK(llout == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* aksl_atoi narrows from long to int, so a value that fits a long and not an int
|
||||
* is ERANGE from the wrapper rather than from strtol. On a platform where long
|
||||
* and int are the same width there is nothing to narrow and strtol has already
|
||||
* caught it, so the case is skipped rather than asserted into a false pass.
|
||||
*/
|
||||
static int test_atoi_narrows_to_int_range(void)
|
||||
{
|
||||
#if LONG_MAX > INT_MAX
|
||||
int out = 99;
|
||||
char buf[64];
|
||||
|
||||
snprintf(buf, sizeof(buf), "%ld", (long)INT_MAX + 1);
|
||||
AKSL_CHECK_STATUS(aksl_atoi(buf, &out), ERANGE);
|
||||
AKSL_CHECK(out == 0);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%ld", (long)INT_MIN - 1);
|
||||
AKSL_CHECK_STATUS(aksl_atoi(buf, &out), ERANGE);
|
||||
AKSL_CHECK(out == 0);
|
||||
#else
|
||||
fprintf(stderr, " (skipped: long and int are the same width here)\n");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TODO.md 1.4: the type boundaries round-trip exactly rather than nearly. */
|
||||
static int test_type_boundaries_round_trip(void)
|
||||
{
|
||||
char buf[64];
|
||||
int iout = 0;
|
||||
long lout = 0;
|
||||
long long llout = 0;
|
||||
|
||||
snprintf(buf, sizeof(buf), "%d", INT_MAX);
|
||||
AKSL_CHECK_OK(aksl_atoi(buf, &iout));
|
||||
AKSL_CHECK(iout == INT_MAX);
|
||||
snprintf(buf, sizeof(buf), "%d", INT_MIN);
|
||||
AKSL_CHECK_OK(aksl_atoi(buf, &iout));
|
||||
AKSL_CHECK(iout == INT_MIN);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%ld", LONG_MAX);
|
||||
AKSL_CHECK_OK(aksl_atol(buf, &lout));
|
||||
AKSL_CHECK(lout == LONG_MAX);
|
||||
snprintf(buf, sizeof(buf), "%ld", LONG_MIN);
|
||||
AKSL_CHECK_OK(aksl_atol(buf, &lout));
|
||||
AKSL_CHECK(lout == LONG_MIN);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%lld", LLONG_MAX);
|
||||
AKSL_CHECK_OK(aksl_atoll(buf, &llout));
|
||||
AKSL_CHECK(llout == LLONG_MAX);
|
||||
snprintf(buf, sizeof(buf), "%lld", LLONG_MIN);
|
||||
AKSL_CHECK_OK(aksl_atoll(buf, &llout));
|
||||
AKSL_CHECK(llout == LLONG_MIN);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -63,6 +191,7 @@ static int test_atol_converts_and_rejects_null(void)
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_atol(NULL, &out), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_atol("1", NULL), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_atol("nope", &out), AKERR_VALUE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -77,6 +206,7 @@ static int test_atoll_converts_and_rejects_null(void)
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_atoll(NULL, &out), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_atoll("1", NULL), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_atoll("nope", &out), AKERR_VALUE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -88,9 +218,45 @@ static int test_atof_converts_and_rejects_null(void)
|
||||
AKSL_CHECK(out == 2.5);
|
||||
AKSL_CHECK_OK(aksl_atof(" -0.125", &out));
|
||||
AKSL_CHECK(out == -0.125);
|
||||
AKSL_CHECK_OK(aksl_atof("1e3", &out));
|
||||
AKSL_CHECK(out == 1000.0);
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_atof(NULL, &out), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_atof("1", NULL), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_atof("nope", &out), AKERR_VALUE);
|
||||
AKSL_CHECK_STATUS(aksl_atof("1.5xyz", &out), AKERR_VALUE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO.md 1.4 left "inf" and "nan" open. They are accepted, because strtod(3)
|
||||
* accepts them and because they are exact round-trips rather than approximations
|
||||
* of something else -- a caller who did not want them has a domain check to make
|
||||
* that this library cannot make for it.
|
||||
*/
|
||||
static int test_atof_accepts_infinity_and_nan(void)
|
||||
{
|
||||
double out = 0.0;
|
||||
|
||||
AKSL_CHECK_OK(aksl_atof("inf", &out));
|
||||
AKSL_CHECK(out > 0.0 && (out * 2.0) == out);
|
||||
AKSL_CHECK_OK(aksl_atof("-INFINITY", &out));
|
||||
AKSL_CHECK(out < 0.0 && (out * 2.0) == out);
|
||||
AKSL_CHECK_OK(aksl_atof("nan", &out));
|
||||
/* The only value that is not equal to itself. */
|
||||
AKSL_CHECK(out != out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Overflow and underflow both come back as ERANGE, which is strtod's only signal. */
|
||||
static int test_atof_range_errors(void)
|
||||
{
|
||||
double out = 1.0;
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_atof("1e400", &out), ERANGE);
|
||||
AKSL_CHECK(out == 0.0);
|
||||
AKSL_CHECK_STATUS(aksl_atof("1e-400", &out), ERANGE);
|
||||
AKSL_CHECK(out == 0.0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -107,6 +273,25 @@ static int test_conversions_are_repeatable(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* errno is cleared before each wrapped call, so a failure left over from an
|
||||
* earlier unrelated call cannot be reported as this one's. Set errno to
|
||||
* something conspicuous first and check that a successful conversion still
|
||||
* succeeds -- the old code could report a stale errno as its own status.
|
||||
*/
|
||||
static int test_stale_errno_does_not_leak_into_the_result(void)
|
||||
{
|
||||
int out = 0;
|
||||
|
||||
errno = ERANGE;
|
||||
AKSL_CHECK_OK(aksl_atoi("5", &out));
|
||||
AKSL_CHECK(out == 5);
|
||||
|
||||
errno = ENOMEM;
|
||||
AKSL_CHECK_STATUS(aksl_atoi("junk", &out), AKERR_VALUE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int failures = 0;
|
||||
@@ -118,11 +303,22 @@ int main(void)
|
||||
AKSL_RUN(failures, test_atoi_skips_leading_whitespace);
|
||||
AKSL_RUN(failures, test_atoi_rejects_null_arguments);
|
||||
|
||||
AKSL_RUN(failures, test_non_numeric_input_is_a_value_error);
|
||||
AKSL_RUN(failures, test_empty_input_is_a_value_error);
|
||||
AKSL_RUN(failures, test_trailing_junk_is_a_value_error);
|
||||
AKSL_RUN(failures, test_hex_literal_is_rejected_by_the_base_10_forms);
|
||||
AKSL_RUN(failures, test_overflow_is_erange);
|
||||
AKSL_RUN(failures, test_atoi_narrows_to_int_range);
|
||||
AKSL_RUN(failures, test_type_boundaries_round_trip);
|
||||
|
||||
AKSL_RUN(failures, test_atol_converts_and_rejects_null);
|
||||
AKSL_RUN(failures, test_atoll_converts_and_rejects_null);
|
||||
AKSL_RUN(failures, test_atof_converts_and_rejects_null);
|
||||
AKSL_RUN(failures, test_atof_accepts_infinity_and_nan);
|
||||
AKSL_RUN(failures, test_atof_range_errors);
|
||||
|
||||
AKSL_RUN(failures, test_conversions_are_repeatable);
|
||||
AKSL_RUN(failures, test_stale_errno_does_not_leak_into_the_result);
|
||||
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* KNOWN FAILING -- TODO.md section 2.1.5
|
||||
*
|
||||
* The ato* wrappers cannot report a conversion failure. atoi(3) and friends have
|
||||
* no error channel at all: "not a number" converts to 0 and an overflowing
|
||||
* literal converts to a wrapped value, and in both cases the wrapper hands back
|
||||
* success. A library whose entire purpose is turning silent libc failures into
|
||||
* error contexts should not be the one place a bad conversion passes unnoticed.
|
||||
*
|
||||
* This test asserts the contract the fix should provide -- reimplemented over
|
||||
* strtol/strtoll/strtod with errno cleared, an endptr check for "no digits
|
||||
* consumed" and "trailing junk", and a range check:
|
||||
*
|
||||
* no digits consumed / trailing junk -> AKERR_VALUE
|
||||
* value out of range -> ERANGE
|
||||
*
|
||||
* Registered in AKSL_KNOWN_FAILING_TESTS (WILL_FAIL). When the wrappers get a
|
||||
* real error channel, CTest reports this as unexpectedly passing -- move it into
|
||||
* AKSL_TESTS then, and fold the cases into tests/test_convert.c.
|
||||
*/
|
||||
|
||||
#include "aksl_capture.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
static int test_non_numeric_input_is_a_value_error(void)
|
||||
{
|
||||
int out = 0;
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_atoi("not a number", &out), AKERR_VALUE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_empty_input_is_a_value_error(void)
|
||||
{
|
||||
int out = 0;
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_atoi("", &out), AKERR_VALUE);
|
||||
AKSL_CHECK_STATUS(aksl_atoi(" ", &out), AKERR_VALUE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_trailing_junk_is_a_value_error(void)
|
||||
{
|
||||
int out = 0;
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_atoi("12abc", &out), AKERR_VALUE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_overflow_is_erange(void)
|
||||
{
|
||||
int out = 0;
|
||||
long lout = 0;
|
||||
long long llout = 0;
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_atoi("99999999999999999999", &out), ERANGE);
|
||||
AKSL_CHECK_STATUS(aksl_atol("99999999999999999999999999", &lout), ERANGE);
|
||||
AKSL_CHECK_STATUS(aksl_atoll("99999999999999999999999999", &llout), ERANGE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int failures = 0;
|
||||
|
||||
akerr_init();
|
||||
|
||||
AKSL_RUN(failures, test_non_numeric_input_is_a_value_error);
|
||||
AKSL_RUN(failures, test_empty_input_is_a_value_error);
|
||||
AKSL_RUN(failures, test_trailing_junk_is_a_value_error);
|
||||
AKSL_RUN(failures, test_overflow_is_erange);
|
||||
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
/*
|
||||
* Formatted-output wrappers: aksl_printf / aksl_fprintf / aksl_sprintf.
|
||||
* Formatted-output wrappers: aksl_printf / aksl_fprintf / aksl_snprintf and
|
||||
* their va_list forms.
|
||||
*
|
||||
* TODO.md section 1.3. Each happy path asserts both halves of the contract --
|
||||
* the byte count handed back through *count and the text that actually landed
|
||||
* somewhere -- and every pointer argument is checked for its NULL guard.
|
||||
* TODO.md section 1.3, now complete. Each happy path asserts both halves of the
|
||||
* contract -- the byte count handed back through *count and the text that
|
||||
* actually landed somewhere -- and every pointer argument is checked for its
|
||||
* NULL guard.
|
||||
*
|
||||
* Readback goes through plain libc rather than aksl_fread so that a failure here
|
||||
* points at the formatted-output wrapper under test and not at the stream
|
||||
* wrappers, which tests/test_stream.c covers.
|
||||
*
|
||||
* Not covered: the destination-overflow case, because aksl_sprintf wraps the
|
||||
* unbounded vsprintf and there is no bounded entry point to test yet
|
||||
* (TODO.md 2.2.4).
|
||||
* aksl_sprintf is gone (TODO.md 2.2.4) and aksl_snprintf takes its place, so the
|
||||
* destination-overflow case that could not previously be written is here: it is
|
||||
* AKERR_OUTOFBOUNDS, not the short success snprintf(3) would have reported.
|
||||
*/
|
||||
|
||||
#include "aksl_capture.h"
|
||||
@@ -37,41 +39,81 @@ static long read_file(const char *path, char *buf, size_t n)
|
||||
return (long)got;
|
||||
}
|
||||
|
||||
static int test_sprintf_writes_text_and_count(void)
|
||||
static int test_snprintf_writes_text_and_count(void)
|
||||
{
|
||||
char buf[64];
|
||||
int count = -1;
|
||||
|
||||
memset(buf, 0x00, sizeof(buf));
|
||||
AKSL_CHECK_OK(aksl_sprintf(&count, buf, "%s=%d", "x", 7));
|
||||
AKSL_CHECK_OK(aksl_snprintf(&count, buf, sizeof(buf), "%s=%d", "x", 7));
|
||||
AKSL_CHECK(count == 3);
|
||||
AKSL_CHECK(strcmp(buf, "x=7") == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_sprintf_empty_format_writes_nothing(void)
|
||||
static int test_snprintf_empty_format_writes_nothing(void)
|
||||
{
|
||||
char buf[8] = { 'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z' };
|
||||
int count = -1;
|
||||
|
||||
AKSL_CHECK_OK(aksl_sprintf(&count, buf, "%s", ""));
|
||||
AKSL_CHECK_OK(aksl_snprintf(&count, buf, sizeof(buf), "%s", ""));
|
||||
AKSL_CHECK(count == 0);
|
||||
AKSL_CHECK(buf[0] == '\0');
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_sprintf_rejects_null_arguments(void)
|
||||
/*
|
||||
* The case that could not be written while the wrapper was aksl_sprintf: output
|
||||
* longer than the destination. snprintf(3) would truncate, NUL-terminate and
|
||||
* report the length it *would* have written, leaving the caller to notice; here
|
||||
* it is an error, and *count is 0 rather than the would-have-been length.
|
||||
*/
|
||||
static int test_snprintf_truncation_is_an_error(void)
|
||||
{
|
||||
char buf[8];
|
||||
int count = -1;
|
||||
|
||||
memset(buf, 0x00, sizeof(buf));
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(
|
||||
aksl_snprintf(&count, buf, sizeof(buf), "%s", "far too long for eight bytes"),
|
||||
AKERR_OUTOFBOUNDS, "truncated");
|
||||
AKSL_CHECK(count == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Exactly filling the buffer is not truncation; one byte more is. */
|
||||
static int test_snprintf_boundary_is_exact(void)
|
||||
{
|
||||
char buf[8];
|
||||
int count = -1;
|
||||
|
||||
/* 7 characters plus the NUL is exactly sizeof(buf). */
|
||||
AKSL_CHECK_OK(aksl_snprintf(&count, buf, sizeof(buf), "%s", "1234567"));
|
||||
AKSL_CHECK(count == 7);
|
||||
AKSL_CHECK(strcmp(buf, "1234567") == 0);
|
||||
|
||||
count = -1;
|
||||
AKSL_CHECK_STATUS(aksl_snprintf(&count, buf, sizeof(buf), "%s", "12345678"),
|
||||
AKERR_OUTOFBOUNDS);
|
||||
AKSL_CHECK(count == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_snprintf_rejects_null_arguments_and_zero_size(void)
|
||||
{
|
||||
char buf[8];
|
||||
int count = 0;
|
||||
|
||||
memset(buf, 0x00, sizeof(buf));
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_sprintf(NULL, buf, "x"),
|
||||
AKERR_NULLPOINTER, "count=");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_sprintf(&count, NULL, "x"),
|
||||
AKERR_NULLPOINTER, "str=");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_sprintf(&count, buf, NULL),
|
||||
AKERR_NULLPOINTER, "format=");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_snprintf(NULL, buf, sizeof(buf), "x"),
|
||||
AKERR_NULLPOINTER, "count=");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_snprintf(&count, NULL, 8, "x"),
|
||||
AKERR_NULLPOINTER, "str=");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_snprintf(&count, buf, sizeof(buf), NULL),
|
||||
AKERR_NULLPOINTER, "format=");
|
||||
/* size 0 leaves no room even for the terminator, so there is nothing to do. */
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_snprintf(&count, buf, 0, "x"),
|
||||
AKERR_VALUE, "size=0");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -96,19 +138,21 @@ static int test_fprintf_writes_to_stream(void)
|
||||
|
||||
/*
|
||||
* vfprintf on a stream opened "r" fails outright, so the wrapper reports the
|
||||
* errno it saw (EBADF on glibc). *count is left holding -1 in this case, which
|
||||
* TODO.md 1.3 flags as a contract gap -- the status is the assertion here.
|
||||
* errno it saw (EBADF on glibc). *count is 0 afterwards, not vfprintf's -1:
|
||||
* TODO.md 1.3 recorded the negative count as a contract gap, and this is the
|
||||
* assertion that closes it.
|
||||
*/
|
||||
static int test_fprintf_to_read_only_stream_reports_errno(void)
|
||||
{
|
||||
char path[AKSL_TMP_MAX];
|
||||
FILE *fp = NULL;
|
||||
int count = 0;
|
||||
int count = 99;
|
||||
|
||||
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
|
||||
AKSL_CHECK_OK(aksl_fopen(path, "r", &fp));
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fprintf(&count, fp, "%d", 1),
|
||||
EBADF, "Short write");
|
||||
EBADF, "Short write");
|
||||
AKSL_CHECK(count == 0);
|
||||
AKSL_CHECK_OK(aksl_fclose(fp));
|
||||
AKSL_CHECK(unlink(path) == 0);
|
||||
return 0;
|
||||
@@ -119,11 +163,11 @@ static int test_fprintf_rejects_null_arguments(void)
|
||||
int count = 0;
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fprintf(NULL, stdout, "x"),
|
||||
AKERR_NULLPOINTER, "count=");
|
||||
AKERR_NULLPOINTER, "count=");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fprintf(&count, NULL, "x"),
|
||||
AKERR_NULLPOINTER, "stream=");
|
||||
AKERR_NULLPOINTER, "stream=");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fprintf(&count, stdout, NULL),
|
||||
AKERR_NULLPOINTER, "format=");
|
||||
AKERR_NULLPOINTER, "format=");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -174,9 +218,43 @@ static int test_printf_rejects_null_arguments(void)
|
||||
int count = 0;
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_printf(NULL, "x"),
|
||||
AKERR_NULLPOINTER, "count=");
|
||||
AKERR_NULLPOINTER, "count=");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_printf(&count, NULL),
|
||||
AKERR_NULLPOINTER, "format=");
|
||||
AKERR_NULLPOINTER, "format=");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The va_list forms are what the variadic ones are built on, and TODO.md 3.1
|
||||
* wanted them exposed so consumers can write their own variadic wrappers. This
|
||||
* is a consumer doing exactly that.
|
||||
*/
|
||||
static akerr_ErrorContext AKERR_NOIGNORE *consumer_wrapper(int *count, char *buf, size_t n,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
akerr_ErrorContext *raised = NULL;
|
||||
|
||||
va_start(args, fmt);
|
||||
raised = aksl_vsnprintf(count, buf, n, fmt, args);
|
||||
va_end(args);
|
||||
return raised;
|
||||
}
|
||||
|
||||
static int test_va_list_forms_are_usable_from_outside(void)
|
||||
{
|
||||
char buf[32];
|
||||
int count = -1;
|
||||
|
||||
memset(buf, 0x00, sizeof(buf));
|
||||
AKSL_CHECK_OK(consumer_wrapper(&count, buf, sizeof(buf), "%s/%d", "via", 3));
|
||||
AKSL_CHECK(count == 5);
|
||||
AKSL_CHECK(strcmp(buf, "via/3") == 0);
|
||||
|
||||
/* The error contract survives the extra layer intact. */
|
||||
AKSL_CHECK_STATUS(consumer_wrapper(&count, buf, 4, "%s", "too long"),
|
||||
AKERR_OUTOFBOUNDS);
|
||||
AKSL_CHECK(count == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -193,8 +271,8 @@ static int test_variadic_wrappers_survive_repeated_calls(void)
|
||||
int i = 0;
|
||||
|
||||
for ( i = 0; i < 512; i++ ) {
|
||||
AKSL_CHECK_OK(aksl_sprintf(&count, buf, "%d %s %ld %c %f",
|
||||
i, "iteration", (long)i, 'x', (double)i));
|
||||
AKSL_CHECK_OK(aksl_snprintf(&count, buf, sizeof(buf), "%d %s %ld %c %f",
|
||||
i, "iteration", (long)i, 'x', (double)i));
|
||||
AKSL_CHECK(count > 0);
|
||||
AKSL_CHECK((size_t)count == strlen(buf));
|
||||
}
|
||||
@@ -207,9 +285,11 @@ int main(void)
|
||||
|
||||
akerr_init();
|
||||
|
||||
AKSL_RUN(failures, test_sprintf_writes_text_and_count);
|
||||
AKSL_RUN(failures, test_sprintf_empty_format_writes_nothing);
|
||||
AKSL_RUN(failures, test_sprintf_rejects_null_arguments);
|
||||
AKSL_RUN(failures, test_snprintf_writes_text_and_count);
|
||||
AKSL_RUN(failures, test_snprintf_empty_format_writes_nothing);
|
||||
AKSL_RUN(failures, test_snprintf_truncation_is_an_error);
|
||||
AKSL_RUN(failures, test_snprintf_boundary_is_exact);
|
||||
AKSL_RUN(failures, test_snprintf_rejects_null_arguments_and_zero_size);
|
||||
|
||||
AKSL_RUN(failures, test_fprintf_writes_to_stream);
|
||||
AKSL_RUN(failures, test_fprintf_to_read_only_stream_reports_errno);
|
||||
@@ -218,6 +298,7 @@ int main(void)
|
||||
AKSL_RUN(failures, test_printf_writes_to_stdout);
|
||||
AKSL_RUN(failures, test_printf_rejects_null_arguments);
|
||||
|
||||
AKSL_RUN(failures, test_va_list_forms_are_usable_from_outside);
|
||||
AKSL_RUN(failures, test_variadic_wrappers_survive_repeated_calls);
|
||||
|
||||
AKSL_REPORT(failures);
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Linked-list behaviour that the library gets right today.
|
||||
* Linked list -- TODO.md section 1.7, complete.
|
||||
*
|
||||
* The two confirmed list defects (TODO.md 2.1.1 aksl_list_append truncating the
|
||||
* chain, and 2.1.2 aksl_list_iterate skipping the head) are asserted separately
|
||||
* in tests/test_list_append_chain.c and tests/test_list_iterate_head.c, which
|
||||
* are registered as known-failing. Everything in this file must pass.
|
||||
* The two confirmed list defects are fixed, so the tests that used to live in
|
||||
* tests/test_list_append_chain.c and tests/test_list_iterate_head.c are folded
|
||||
* back in here: aksl_list_append builds the whole chain rather than truncating
|
||||
* it at the midpoint (2.1.1), and aksl_list_iterate starts at the head rather
|
||||
* than at whatever node Floyd's slow pointer happened to stop on (2.1.2).
|
||||
*
|
||||
* Note that the list-shape assertions here build their lists by hand rather
|
||||
* than with aksl_list_append: append cannot be trusted to produce a chain
|
||||
* longer than two nodes until 2.1.1 is fixed, and using it would make these
|
||||
* tests fail for a reason that has nothing to do with what they are checking.
|
||||
* The chain assertions build their lists with aksl_list_append now, which is the
|
||||
* point -- they could not, while append was the thing under suspicion.
|
||||
*/
|
||||
|
||||
#include "aksl_capture.h"
|
||||
|
||||
#define MAX_VISITS 16
|
||||
#define CHAIN_LEN 5
|
||||
|
||||
typedef struct VisitLog
|
||||
{
|
||||
@@ -54,6 +54,31 @@ static akerr_ErrorContext AKERR_NOIGNORE *record_visit(aksl_ListNode *node, void
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* aksl_list_node_init */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* TODO.md 2.2.14: every caller used to have to remember to memset a node before
|
||||
* its first use, and a stack node that skipped it walked straight into garbage.
|
||||
*/
|
||||
static int test_node_init_zeroes_the_links(void)
|
||||
{
|
||||
aksl_ListNode node;
|
||||
int payload = 7;
|
||||
|
||||
memset((void *)&node, 0xff, sizeof(node));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node, &payload));
|
||||
AKSL_CHECK(node.next == NULL);
|
||||
AKSL_CHECK(node.prev == NULL);
|
||||
AKSL_CHECK(node.data == (void *)&payload);
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
||||
AKSL_CHECK(node.data == NULL);
|
||||
AKSL_CHECK_STATUS(aksl_list_node_init(NULL, NULL), AKERR_NULLPOINTER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* aksl_list_append */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
@@ -63,8 +88,8 @@ static int test_append_single_node(void)
|
||||
aksl_ListNode head;
|
||||
aksl_ListNode tail;
|
||||
|
||||
memset((void *)&head, 0x00, sizeof(head));
|
||||
memset((void *)&tail, 0x00, sizeof(tail));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&head, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&tail, NULL));
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_append(&head, &tail));
|
||||
AKSL_CHECK(head.next == &tail);
|
||||
@@ -74,11 +99,66 @@ static int test_append_single_node(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The defect that made this library's list unusable: `tail` was assigned from
|
||||
* Floyd's `slow` cursor *before* slow advanced, so it tracked the node behind
|
||||
* the midpoint rather than the last node. Appending n1..n4 to n0 produced the
|
||||
* chain "n0 -> n4" and silently dropped n1, n2 and n3. TODO.md 2.1.1.
|
||||
*/
|
||||
static int test_append_builds_the_whole_chain(void)
|
||||
{
|
||||
aksl_ListNode node[CHAIN_LEN];
|
||||
aksl_ListNode *walk = NULL;
|
||||
int i = 0;
|
||||
|
||||
for ( i = 0; i < CHAIN_LEN; i++ ) {
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node[i], NULL));
|
||||
}
|
||||
for ( i = 1; i < CHAIN_LEN; i++ ) {
|
||||
AKSL_CHECK_OK(aksl_list_append(&node[0], &node[i]));
|
||||
}
|
||||
|
||||
/* Forward links, head to tail. */
|
||||
walk = &node[0];
|
||||
for ( i = 0; i < CHAIN_LEN; i++ ) {
|
||||
AKSL_CHECK(walk == &node[i]);
|
||||
walk = walk->next;
|
||||
}
|
||||
AKSL_CHECK(walk == NULL);
|
||||
|
||||
/* Back links, tail to head. */
|
||||
walk = &node[CHAIN_LEN - 1];
|
||||
for ( i = CHAIN_LEN - 1; i >= 0; i-- ) {
|
||||
AKSL_CHECK(walk == &node[i]);
|
||||
walk = walk->prev;
|
||||
}
|
||||
AKSL_CHECK(walk == NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* append points obj->prev at the real tail and terminates the chain at obj. */
|
||||
static int test_append_links_prev_to_the_real_tail(void)
|
||||
{
|
||||
aksl_ListNode node[4];
|
||||
int i = 0;
|
||||
|
||||
for ( i = 0; i < 4; i++ ) {
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node[i], NULL));
|
||||
}
|
||||
for ( i = 1; i < 4; i++ ) {
|
||||
AKSL_CHECK_OK(aksl_list_append(&node[0], &node[i]));
|
||||
AKSL_CHECK(node[i].prev == &node[i - 1]);
|
||||
AKSL_CHECK(node[i].next == NULL);
|
||||
AKSL_CHECK(node[i - 1].next == &node[i]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_append_null_arguments(void)
|
||||
{
|
||||
aksl_ListNode node;
|
||||
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_list_append(NULL, &node), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_list_append(&node, NULL), AKERR_NULLPOINTER);
|
||||
@@ -90,8 +170,8 @@ static int test_append_detects_self_cycle(void)
|
||||
aksl_ListNode head;
|
||||
aksl_ListNode node;
|
||||
|
||||
memset((void *)&head, 0x00, sizeof(head));
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&head, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
||||
head.next = &head;
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_list_append(&head, &node), AKERR_CIRCULAR_REFERENCE);
|
||||
@@ -104,9 +184,9 @@ static int test_append_detects_two_node_cycle(void)
|
||||
aksl_ListNode b;
|
||||
aksl_ListNode node;
|
||||
|
||||
memset((void *)&a, 0x00, sizeof(a));
|
||||
memset((void *)&b, 0x00, sizeof(b));
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&b, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
||||
a.next = &b;
|
||||
b.prev = &a;
|
||||
b.next = &a;
|
||||
@@ -115,6 +195,56 @@ static int test_append_detects_two_node_cycle(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* A cycle that does not include the head: a -> b -> c -> b. */
|
||||
static int test_append_detects_cycle_below_the_head(void)
|
||||
{
|
||||
aksl_ListNode a;
|
||||
aksl_ListNode b;
|
||||
aksl_ListNode c;
|
||||
aksl_ListNode node;
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&b, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&c, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
||||
a.next = &b;
|
||||
b.prev = &a;
|
||||
b.next = &c;
|
||||
c.prev = &b;
|
||||
c.next = &b;
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_list_append(&a, &node), AKERR_CIRCULAR_REFERENCE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO.md 1.7 asked for the aliasing contract to be defined. It is refusal:
|
||||
* relinking a node that is already in the list would orphan everything between
|
||||
* its old position and the tail, so the tail walk -- which happens anyway --
|
||||
* doubles as the check.
|
||||
*/
|
||||
static int test_append_refuses_a_node_already_in_the_list(void)
|
||||
{
|
||||
aksl_ListNode node[3];
|
||||
int i = 0;
|
||||
|
||||
for ( i = 0; i < 3; i++ ) {
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node[i], NULL));
|
||||
}
|
||||
AKSL_CHECK_OK(aksl_list_append(&node[0], &node[1]));
|
||||
AKSL_CHECK_OK(aksl_list_append(&node[0], &node[2]));
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_list_append(&node[0], &node[1]),
|
||||
AKERR_VALUE, "already in this list");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_list_append(&node[0], &node[0]),
|
||||
AKERR_VALUE, "already the head");
|
||||
/* The list is untouched by the refusal. */
|
||||
AKSL_CHECK(node[0].next == &node[1]);
|
||||
AKSL_CHECK(node[1].next == &node[2]);
|
||||
AKSL_CHECK(node[2].next == NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* aksl_list_iterate */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
@@ -124,7 +254,7 @@ static int test_iterate_null_arguments(void)
|
||||
aksl_ListNode node;
|
||||
VisitLog log;
|
||||
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
||||
visitlog_init(&log);
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_list_iterate(NULL, &record_visit, &log), AKERR_NULLPOINTER);
|
||||
@@ -138,7 +268,7 @@ static int test_iterate_single_node(void)
|
||||
aksl_ListNode node;
|
||||
VisitLog log;
|
||||
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
||||
visitlog_init(&log);
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_iterate(&node, &record_visit, &log));
|
||||
@@ -147,17 +277,69 @@ static int test_iterate_single_node(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Every node, exactly once, in order, starting at the head. The cycle check
|
||||
* leaves Floyd's `slow` cursor at the list midpoint, and the visiting loop used
|
||||
* to start from there -- so the whole first half of the list, head included, was
|
||||
* never passed to the callback at all. TODO.md 2.1.2.
|
||||
*/
|
||||
static int test_iterate_visits_every_node_from_the_head(void)
|
||||
{
|
||||
aksl_ListNode node[CHAIN_LEN];
|
||||
VisitLog log;
|
||||
int i = 0;
|
||||
|
||||
for ( i = 0; i < CHAIN_LEN; i++ ) {
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node[i], NULL));
|
||||
}
|
||||
for ( i = 1; i < CHAIN_LEN; i++ ) {
|
||||
AKSL_CHECK_OK(aksl_list_append(&node[0], &node[i]));
|
||||
}
|
||||
visitlog_init(&log);
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_iterate(&node[0], &record_visit, &log));
|
||||
AKSL_CHECK(log.count == CHAIN_LEN);
|
||||
for ( i = 0; i < CHAIN_LEN; i++ ) {
|
||||
AKSL_CHECK(log.seen[i] == &node[i]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* An even-length list too: Floyd's midpoint lands differently, the answer does not. */
|
||||
static int test_iterate_visits_every_node_of_an_even_list(void)
|
||||
{
|
||||
aksl_ListNode node[4];
|
||||
VisitLog log;
|
||||
int i = 0;
|
||||
|
||||
for ( i = 0; i < 4; i++ ) {
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node[i], NULL));
|
||||
}
|
||||
for ( i = 1; i < 4; i++ ) {
|
||||
AKSL_CHECK_OK(aksl_list_append(&node[0], &node[i]));
|
||||
}
|
||||
visitlog_init(&log);
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_iterate(&node[0], &record_visit, &log));
|
||||
AKSL_CHECK(log.count == 4);
|
||||
for ( i = 0; i < 4; i++ ) {
|
||||
AKSL_CHECK(log.seen[i] == &node[i]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_iterate_detects_self_cycle(void)
|
||||
{
|
||||
aksl_ListNode head;
|
||||
VisitLog log;
|
||||
|
||||
memset((void *)&head, 0x00, sizeof(head));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&head, NULL));
|
||||
head.next = &head;
|
||||
visitlog_init(&log);
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_list_iterate(&head, &record_visit, &log),
|
||||
AKERR_CIRCULAR_REFERENCE);
|
||||
AKSL_CHECK(log.count == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -167,8 +349,8 @@ static int test_iterate_detects_two_node_cycle(void)
|
||||
aksl_ListNode b;
|
||||
VisitLog log;
|
||||
|
||||
memset((void *)&a, 0x00, sizeof(a));
|
||||
memset((void *)&b, 0x00, sizeof(b));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&b, NULL));
|
||||
a.next = &b;
|
||||
b.prev = &a;
|
||||
b.next = &a;
|
||||
@@ -179,6 +361,29 @@ static int test_iterate_detects_two_node_cycle(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Tail pointing back into the middle: a -> b -> c -> b. */
|
||||
static int test_iterate_detects_tail_to_middle_cycle(void)
|
||||
{
|
||||
aksl_ListNode a;
|
||||
aksl_ListNode b;
|
||||
aksl_ListNode c;
|
||||
VisitLog log;
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&b, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&c, NULL));
|
||||
a.next = &b;
|
||||
b.prev = &a;
|
||||
b.next = &c;
|
||||
c.prev = &b;
|
||||
c.next = &b;
|
||||
visitlog_init(&log);
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_list_iterate(&a, &record_visit, &log),
|
||||
AKERR_CIRCULAR_REFERENCE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* An error other than ITERATOR_BREAK must come back out of the iteration with
|
||||
* its status and message intact. */
|
||||
static int test_iterate_propagates_callback_error(void)
|
||||
@@ -186,13 +391,13 @@ static int test_iterate_propagates_callback_error(void)
|
||||
aksl_ListNode node;
|
||||
VisitLog log;
|
||||
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
||||
visitlog_init(&log);
|
||||
log.fail_at = 0;
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(
|
||||
aksl_list_iterate(&node, &record_visit, &log),
|
||||
AKERR_VALUE, "iterator failed at visit 0");
|
||||
aksl_list_iterate(&node, &record_visit, &log),
|
||||
AKERR_VALUE, "iterator failed at visit 0");
|
||||
AKSL_CHECK(log.count == 1);
|
||||
return 0;
|
||||
}
|
||||
@@ -203,7 +408,7 @@ static int test_iterate_break_is_not_an_error(void)
|
||||
aksl_ListNode node;
|
||||
VisitLog log;
|
||||
|
||||
memset((void *)&node, 0x00, sizeof(node));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node, NULL));
|
||||
visitlog_init(&log);
|
||||
log.break_at = 0;
|
||||
|
||||
@@ -212,6 +417,32 @@ static int test_iterate_break_is_not_an_error(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* And the count is what proves it stopped early rather than merely finishing.
|
||||
* A break on the second of five nodes must leave three nodes unvisited.
|
||||
*/
|
||||
static int test_iterate_break_stops_at_that_node(void)
|
||||
{
|
||||
aksl_ListNode node[CHAIN_LEN];
|
||||
VisitLog log;
|
||||
int i = 0;
|
||||
|
||||
for ( i = 0; i < CHAIN_LEN; i++ ) {
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node[i], NULL));
|
||||
}
|
||||
for ( i = 1; i < CHAIN_LEN; i++ ) {
|
||||
AKSL_CHECK_OK(aksl_list_append(&node[0], &node[i]));
|
||||
}
|
||||
visitlog_init(&log);
|
||||
log.break_at = 1;
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_iterate(&node[0], &record_visit, &log));
|
||||
AKSL_CHECK(log.count == 2);
|
||||
AKSL_CHECK(log.seen[0] == &node[0]);
|
||||
AKSL_CHECK(log.seen[1] == &node[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* aksl_list_pop */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
@@ -221,16 +452,16 @@ static int test_pop_middle_node(void)
|
||||
aksl_ListNode a;
|
||||
aksl_ListNode b;
|
||||
aksl_ListNode c;
|
||||
aksl_ListNode *head = &a;
|
||||
|
||||
memset((void *)&a, 0x00, sizeof(a));
|
||||
memset((void *)&b, 0x00, sizeof(b));
|
||||
memset((void *)&c, 0x00, sizeof(c));
|
||||
a.next = &b;
|
||||
b.prev = &a;
|
||||
b.next = &c;
|
||||
c.prev = &b;
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&b, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&c, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_append(&a, &b));
|
||||
AKSL_CHECK_OK(aksl_list_append(&a, &c));
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_pop(&b));
|
||||
AKSL_CHECK_OK(aksl_list_pop(&head, &b));
|
||||
AKSL_CHECK(head == &a);
|
||||
AKSL_CHECK(a.next == &c);
|
||||
AKSL_CHECK(c.prev == &a);
|
||||
AKSL_CHECK(b.next == NULL);
|
||||
@@ -238,17 +469,23 @@ static int test_pop_middle_node(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_pop_head_node(void)
|
||||
/*
|
||||
* TODO.md 2.2.12: popping the head used to leave the caller's own head pointer
|
||||
* aimed at a node that was no longer in the list, with no way to learn the new
|
||||
* one. That is what the head out-param is for, and this is the assertion.
|
||||
*/
|
||||
static int test_pop_head_node_moves_the_head(void)
|
||||
{
|
||||
aksl_ListNode a;
|
||||
aksl_ListNode b;
|
||||
aksl_ListNode *head = &a;
|
||||
|
||||
memset((void *)&a, 0x00, sizeof(a));
|
||||
memset((void *)&b, 0x00, sizeof(b));
|
||||
a.next = &b;
|
||||
b.prev = &a;
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&b, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_append(&a, &b));
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_pop(&a));
|
||||
AKSL_CHECK_OK(aksl_list_pop(&head, &a));
|
||||
AKSL_CHECK(head == &b);
|
||||
AKSL_CHECK(b.prev == NULL);
|
||||
AKSL_CHECK(b.next == NULL);
|
||||
AKSL_CHECK(a.next == NULL);
|
||||
@@ -260,13 +497,14 @@ static int test_pop_tail_node(void)
|
||||
{
|
||||
aksl_ListNode a;
|
||||
aksl_ListNode b;
|
||||
aksl_ListNode *head = &a;
|
||||
|
||||
memset((void *)&a, 0x00, sizeof(a));
|
||||
memset((void *)&b, 0x00, sizeof(b));
|
||||
a.next = &b;
|
||||
b.prev = &a;
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&b, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_append(&a, &b));
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_pop(&b));
|
||||
AKSL_CHECK_OK(aksl_list_pop(&head, &b));
|
||||
AKSL_CHECK(head == &a);
|
||||
AKSL_CHECK(a.next == NULL);
|
||||
AKSL_CHECK(a.prev == NULL);
|
||||
AKSL_CHECK(b.next == NULL);
|
||||
@@ -274,21 +512,93 @@ static int test_pop_tail_node(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_pop_only_node(void)
|
||||
/* Popping the only node empties the list, and the head becomes NULL. */
|
||||
static int test_pop_only_node_empties_the_list(void)
|
||||
{
|
||||
aksl_ListNode a;
|
||||
aksl_ListNode *head = &a;
|
||||
|
||||
memset((void *)&a, 0x00, sizeof(a));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_pop(&a));
|
||||
AKSL_CHECK_OK(aksl_list_pop(&head, &a));
|
||||
AKSL_CHECK(head == NULL);
|
||||
AKSL_CHECK(a.next == NULL);
|
||||
AKSL_CHECK(a.prev == NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_pop_null_argument(void)
|
||||
static int test_pop_null_arguments(void)
|
||||
{
|
||||
AKSL_CHECK_STATUS(aksl_list_pop(NULL), AKERR_NULLPOINTER);
|
||||
aksl_ListNode a;
|
||||
aksl_ListNode *head = &a;
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
||||
AKSL_CHECK_STATUS(aksl_list_pop(NULL, &a), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_list_pop(&head, NULL), AKERR_NULLPOINTER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The list is still walkable after a pop, and the popped node is gone from it. */
|
||||
static int test_pop_then_iterate(void)
|
||||
{
|
||||
aksl_ListNode node[4];
|
||||
aksl_ListNode *head = &node[0];
|
||||
VisitLog log;
|
||||
int i = 0;
|
||||
|
||||
for ( i = 0; i < 4; i++ ) {
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&node[i], NULL));
|
||||
}
|
||||
for ( i = 1; i < 4; i++ ) {
|
||||
AKSL_CHECK_OK(aksl_list_append(&node[0], &node[i]));
|
||||
}
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_pop(&head, &node[2]));
|
||||
visitlog_init(&log);
|
||||
AKSL_CHECK_OK(aksl_list_iterate(head, &record_visit, &log));
|
||||
AKSL_CHECK(log.count == 3);
|
||||
AKSL_CHECK(log.seen[0] == &node[0]);
|
||||
AKSL_CHECK(log.seen[1] == &node[1]);
|
||||
AKSL_CHECK(log.seen[2] == &node[3]);
|
||||
|
||||
/* And again, this time taking the head out. */
|
||||
AKSL_CHECK_OK(aksl_list_pop(&head, &node[0]));
|
||||
visitlog_init(&log);
|
||||
AKSL_CHECK_OK(aksl_list_iterate(head, &record_visit, &log));
|
||||
AKSL_CHECK(log.count == 2);
|
||||
AKSL_CHECK(log.seen[0] == &node[1]);
|
||||
AKSL_CHECK(log.seen[1] == &node[3]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO.md 1.7's pool-accounting case. AKSL_RUN already asserts that each test
|
||||
* leaves the pool as it found it; this one drives enough failures in a row to
|
||||
* exhaust the pool several times over, which is where a wrapper that raises an
|
||||
* error and forgets to release it shows up as an outright exhaustion rather than
|
||||
* as a slow leak.
|
||||
*/
|
||||
static int test_error_pool_survives_a_long_run_of_failures(void)
|
||||
{
|
||||
aksl_ListNode a;
|
||||
aksl_ListNode b;
|
||||
aksl_ListNode *head = &a;
|
||||
VisitLog log;
|
||||
int i = 0;
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&a, NULL));
|
||||
AKSL_CHECK_OK(aksl_list_node_init(&b, NULL));
|
||||
a.next = &a;
|
||||
|
||||
for ( i = 0; i < AKERR_MAX_ARRAY_ERROR + 10; i++ ) {
|
||||
AKSL_CHECK_STATUS(aksl_list_append(&a, &b), AKERR_CIRCULAR_REFERENCE);
|
||||
AKSL_CHECK_STATUS(aksl_list_append(NULL, &b), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_list_pop(&head, NULL), AKERR_NULLPOINTER);
|
||||
visitlog_init(&log);
|
||||
AKSL_CHECK_STATUS(aksl_list_iterate(&a, &record_visit, &log),
|
||||
AKERR_CIRCULAR_REFERENCE);
|
||||
AKSL_CHECK(aksl_slots_in_use() == 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -298,23 +608,36 @@ int main(void)
|
||||
|
||||
akerr_init();
|
||||
|
||||
AKSL_RUN(failures, test_node_init_zeroes_the_links);
|
||||
|
||||
AKSL_RUN(failures, test_append_single_node);
|
||||
AKSL_RUN(failures, test_append_builds_the_whole_chain);
|
||||
AKSL_RUN(failures, test_append_links_prev_to_the_real_tail);
|
||||
AKSL_RUN(failures, test_append_null_arguments);
|
||||
AKSL_RUN(failures, test_append_detects_self_cycle);
|
||||
AKSL_RUN(failures, test_append_detects_two_node_cycle);
|
||||
AKSL_RUN(failures, test_append_detects_cycle_below_the_head);
|
||||
AKSL_RUN(failures, test_append_refuses_a_node_already_in_the_list);
|
||||
|
||||
AKSL_RUN(failures, test_iterate_null_arguments);
|
||||
AKSL_RUN(failures, test_iterate_single_node);
|
||||
AKSL_RUN(failures, test_iterate_visits_every_node_from_the_head);
|
||||
AKSL_RUN(failures, test_iterate_visits_every_node_of_an_even_list);
|
||||
AKSL_RUN(failures, test_iterate_detects_self_cycle);
|
||||
AKSL_RUN(failures, test_iterate_detects_two_node_cycle);
|
||||
AKSL_RUN(failures, test_iterate_detects_tail_to_middle_cycle);
|
||||
AKSL_RUN(failures, test_iterate_propagates_callback_error);
|
||||
AKSL_RUN(failures, test_iterate_break_is_not_an_error);
|
||||
AKSL_RUN(failures, test_iterate_break_stops_at_that_node);
|
||||
|
||||
AKSL_RUN(failures, test_pop_middle_node);
|
||||
AKSL_RUN(failures, test_pop_head_node);
|
||||
AKSL_RUN(failures, test_pop_head_node_moves_the_head);
|
||||
AKSL_RUN(failures, test_pop_tail_node);
|
||||
AKSL_RUN(failures, test_pop_only_node);
|
||||
AKSL_RUN(failures, test_pop_null_argument);
|
||||
AKSL_RUN(failures, test_pop_only_node_empties_the_list);
|
||||
AKSL_RUN(failures, test_pop_null_arguments);
|
||||
AKSL_RUN(failures, test_pop_then_iterate);
|
||||
|
||||
AKSL_RUN(failures, test_error_pool_survives_a_long_run_of_failures);
|
||||
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* KNOWN FAILING -- TODO.md section 2.1.1
|
||||
*
|
||||
* aksl_list_append conflates Floyd cycle detection with finding the tail: the
|
||||
* `tail` cursor is assigned from `slow` *before* `slow` advances, so it tracks
|
||||
* the node behind the list midpoint rather than the last node. Appending to any
|
||||
* list of two or more nodes therefore overwrites an interior link and silently
|
||||
* drops every node after it.
|
||||
*
|
||||
* Appending n1..n4 to n0 produces the chain "n0 -> n4"; this test asserts the
|
||||
* correct "n0 -> n1 -> n2 -> n3 -> n4" and so fails until append is fixed.
|
||||
* It is registered in AKSL_KNOWN_FAILING_TESTS, which marks it WILL_FAIL; when
|
||||
* the fix lands, CTest will report it as unexpectedly passing, which is the
|
||||
* signal to move it into AKSL_TESTS.
|
||||
*/
|
||||
|
||||
#include "aksl_capture.h"
|
||||
|
||||
#define CHAIN_LEN 5
|
||||
|
||||
static int test_append_builds_full_chain(void)
|
||||
{
|
||||
aksl_ListNode node[CHAIN_LEN];
|
||||
aksl_ListNode *walk = NULL;
|
||||
int i = 0;
|
||||
|
||||
memset((void *)node, 0x00, sizeof(node));
|
||||
|
||||
for ( i = 1; i < CHAIN_LEN; i++ ) {
|
||||
AKSL_CHECK_OK(aksl_list_append(&node[0], &node[i]));
|
||||
}
|
||||
|
||||
/* Forward links, head to tail. */
|
||||
walk = &node[0];
|
||||
for ( i = 0; i < CHAIN_LEN; i++ ) {
|
||||
AKSL_CHECK(walk == &node[i]);
|
||||
walk = walk->next;
|
||||
}
|
||||
AKSL_CHECK(walk == NULL);
|
||||
|
||||
/* Back links, tail to head. */
|
||||
walk = &node[CHAIN_LEN - 1];
|
||||
for ( i = CHAIN_LEN - 1; i >= 0; i-- ) {
|
||||
AKSL_CHECK(walk == &node[i]);
|
||||
walk = walk->prev;
|
||||
}
|
||||
AKSL_CHECK(walk == NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int failures = 0;
|
||||
|
||||
akerr_init();
|
||||
AKSL_RUN(failures, test_append_builds_full_chain);
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* KNOWN FAILING -- TODO.md section 2.1.2
|
||||
*
|
||||
* aksl_list_iterate runs a Floyd cycle check that leaves `slow` sitting at the
|
||||
* list midpoint, and then starts the visiting loop from `slow` instead of from
|
||||
* `list`. Every node before the midpoint -- including the head -- is never
|
||||
* passed to the callback.
|
||||
*
|
||||
* The list here is built by hand rather than with aksl_list_append so that this
|
||||
* test fails only for the iterate defect, not for the separate append defect in
|
||||
* TODO.md 2.1.1.
|
||||
*
|
||||
* Registered in AKSL_KNOWN_FAILING_TESTS (WILL_FAIL). When iterate is fixed,
|
||||
* CTest reports this as unexpectedly passing -- move it into AKSL_TESTS then.
|
||||
*/
|
||||
|
||||
#include "aksl_capture.h"
|
||||
|
||||
#define CHAIN_LEN 3
|
||||
|
||||
typedef struct VisitLog
|
||||
{
|
||||
int count;
|
||||
aksl_ListNode *seen[CHAIN_LEN];
|
||||
} VisitLog;
|
||||
|
||||
static akerr_ErrorContext AKERR_NOIGNORE *record_visit(aksl_ListNode *node, void *data)
|
||||
{
|
||||
VisitLog *log = NULL;
|
||||
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "node");
|
||||
FAIL_ZERO_RETURN(e, data, AKERR_NULLPOINTER, "data");
|
||||
log = (VisitLog *)data;
|
||||
if ( log->count < CHAIN_LEN ) {
|
||||
log->seen[log->count] = node;
|
||||
}
|
||||
log->count += 1;
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
static int test_iterate_visits_every_node_from_the_head(void)
|
||||
{
|
||||
aksl_ListNode node[CHAIN_LEN];
|
||||
VisitLog log;
|
||||
int i = 0;
|
||||
|
||||
memset((void *)node, 0x00, sizeof(node));
|
||||
memset((void *)&log, 0x00, sizeof(log));
|
||||
|
||||
for ( i = 1; i < CHAIN_LEN; i++ ) {
|
||||
node[i - 1].next = &node[i];
|
||||
node[i].prev = &node[i - 1];
|
||||
}
|
||||
|
||||
AKSL_CHECK_OK(aksl_list_iterate(&node[0], &record_visit, &log));
|
||||
AKSL_CHECK(log.count == CHAIN_LEN);
|
||||
for ( i = 0; i < CHAIN_LEN; i++ ) {
|
||||
AKSL_CHECK(log.seen[i] == &node[i]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int failures = 0;
|
||||
|
||||
akerr_init();
|
||||
AKSL_RUN(failures, test_iterate_visits_every_node_from_the_head);
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
@@ -1,15 +1,19 @@
|
||||
/*
|
||||
* Memory-wrapper behaviour that is deterministic today.
|
||||
* Memory wrappers -- TODO.md section 1.1, now complete, plus the additions from
|
||||
* section 3.1.
|
||||
*
|
||||
* TODO.md section 1.1 also calls out malloc(0), malloc(SIZE_MAX), and
|
||||
* overlapping memcpy. Those are intentionally not pinned here yet: malloc(0)
|
||||
* is platform-dependent under the current wrapper, huge allocations need
|
||||
* sanitizer-safe handling, and overlapping memcpy is still an API-contract
|
||||
* decision.
|
||||
* The three cases 1.1 left open are all pinned here: malloc(0) is AKERR_VALUE
|
||||
* rather than whatever errno happened to hold when the platform's malloc(0)
|
||||
* returned NULL; an allocation the system cannot satisfy reports ENOMEM and
|
||||
* leaves *dst NULL rather than garbage; and overlapping memcpy is refused with
|
||||
* AKERR_VALUE rather than left as undefined behaviour that usually looks fine.
|
||||
*/
|
||||
|
||||
#include "aksl_capture.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static int test_malloc_writes_nonnull_pointer(void)
|
||||
{
|
||||
void *ptr = NULL;
|
||||
@@ -23,14 +27,119 @@ static int test_malloc_writes_nonnull_pointer(void)
|
||||
static int test_malloc_rejects_null_destination(void)
|
||||
{
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_malloc(32, NULL),
|
||||
AKERR_NULLPOINTER, "NULL");
|
||||
AKERR_NULLPOINTER, "dst=");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO.md 1.1 asked for this contract to be pinned down. malloc(0) is allowed to
|
||||
* return either a unique pointer or NULL, and a NULL there is not a failure and
|
||||
* need not set errno -- so the old wrapper could raise an error whose status was
|
||||
* 0, which every DETECT downstream reads as success while the context holds a
|
||||
* pool slot. A zero-size request is refused on its own terms instead.
|
||||
*/
|
||||
static int test_malloc_rejects_zero_size(void)
|
||||
{
|
||||
void *ptr = (void *)0x1;
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_malloc(0, &ptr), AKERR_VALUE, "zero-size");
|
||||
AKSL_CHECK(ptr == NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* An allocation no system can satisfy. SIZE_MAX/2 rather than SIZE_MAX because
|
||||
* some allocators reject the latter as an obvious overflow before ever
|
||||
* consulting the system, and the case worth testing is the one where the request
|
||||
* is plausible and the answer is still no.
|
||||
*/
|
||||
static int test_malloc_reports_out_of_memory(void)
|
||||
{
|
||||
void *ptr = (void *)0x1;
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_malloc(SIZE_MAX / 2, &ptr), ENOMEM);
|
||||
/* Never garbage on the failure path. */
|
||||
AKSL_CHECK(ptr == NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_calloc_zeroes_and_guards(void)
|
||||
{
|
||||
unsigned char *buf = NULL;
|
||||
void *ptr = (void *)0x1;
|
||||
size_t i = 0;
|
||||
|
||||
AKSL_CHECK_OK(aksl_calloc(16, sizeof(unsigned char), (void **)&buf));
|
||||
AKSL_CHECK(buf != NULL);
|
||||
for ( i = 0; i < 16; i++ ) {
|
||||
AKSL_CHECK(buf[i] == 0);
|
||||
}
|
||||
AKSL_CHECK_OK(aksl_free(buf));
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_calloc(16, 1, NULL), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_calloc(0, 1, &ptr), AKERR_VALUE);
|
||||
AKSL_CHECK_STATUS(aksl_calloc(1, 0, &ptr), AKERR_VALUE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* realloc(3)'s trap: on failure it returns NULL and the original block is *still
|
||||
* valid*, so `p = realloc(p, n)` leaks it. The wrapper takes the pointer by
|
||||
* reference and leaves it untouched on failure, so the caller still holds a
|
||||
* pointer it can free -- which is what this asserts by freeing it.
|
||||
*/
|
||||
static int test_realloc_grows_and_preserves_the_old_block_on_failure(void)
|
||||
{
|
||||
unsigned char *buf = NULL;
|
||||
void *ptr = NULL;
|
||||
size_t i = 0;
|
||||
|
||||
AKSL_CHECK_OK(aksl_malloc(8, (void **)&buf));
|
||||
for ( i = 0; i < 8; i++ ) {
|
||||
buf[i] = (unsigned char)i;
|
||||
}
|
||||
|
||||
ptr = buf;
|
||||
AKSL_CHECK_OK(aksl_realloc(&ptr, 64));
|
||||
buf = (unsigned char *)ptr;
|
||||
AKSL_CHECK(buf != NULL);
|
||||
/* The contents that fit are carried over. */
|
||||
for ( i = 0; i < 8; i++ ) {
|
||||
AKSL_CHECK(buf[i] == (unsigned char)i);
|
||||
}
|
||||
|
||||
/* A request that cannot be met leaves ptr aimed at the original block. */
|
||||
AKSL_CHECK_STATUS(aksl_realloc(&ptr, SIZE_MAX / 2), ENOMEM);
|
||||
AKSL_CHECK(ptr == (void *)buf);
|
||||
for ( i = 0; i < 8; i++ ) {
|
||||
AKSL_CHECK(buf[i] == (unsigned char)i);
|
||||
}
|
||||
|
||||
AKSL_CHECK_OK(aksl_free(ptr));
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_realloc(NULL, 8), AKERR_NULLPOINTER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_free_rejects_null_pointer(void)
|
||||
{
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_free(NULL),
|
||||
AKERR_NULLPOINTER, "NULL");
|
||||
AKERR_NULLPOINTER, "NULL");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* aksl_freep clears the caller's pointer, so a second free is caught. */
|
||||
static int test_freep_clears_the_pointer(void)
|
||||
{
|
||||
void *ptr = NULL;
|
||||
|
||||
AKSL_CHECK_OK(aksl_malloc(16, &ptr));
|
||||
AKSL_CHECK(ptr != NULL);
|
||||
AKSL_CHECK_OK(aksl_freep(&ptr));
|
||||
AKSL_CHECK(ptr == NULL);
|
||||
/* The second attempt is now an error instead of a double free. */
|
||||
AKSL_CHECK_STATUS(aksl_freep(&ptr), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_freep(NULL), AKERR_NULLPOINTER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -79,7 +188,7 @@ static int test_memset_zero_length_is_noop(void)
|
||||
static int test_memset_rejects_null_buffer(void)
|
||||
{
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_memset(NULL, 0x00, 4),
|
||||
AKERR_NULLPOINTER, "s=");
|
||||
AKERR_NULLPOINTER, "s=");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -108,12 +217,45 @@ static int test_memcpy_zero_length_is_noop(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO.md 1.1's open question, decided: overlap is AKERR_VALUE. memcpy(3) calls
|
||||
* it undefined behaviour, which in practice means "works until the day a
|
||||
* compiler version or a length changes and it does not". Callers who mean to
|
||||
* overlap want aksl_memmove, and the message says so.
|
||||
*/
|
||||
static int test_memcpy_rejects_overlapping_ranges(void)
|
||||
{
|
||||
unsigned char buf[8] = { 0, 1, 2, 3, 4, 5, 6, 7 };
|
||||
|
||||
/* Destination inside the source range. */
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_memcpy(&buf[2], &buf[0], 4),
|
||||
AKERR_VALUE, "aksl_memmove");
|
||||
/* Source inside the destination range. */
|
||||
AKSL_CHECK_STATUS(aksl_memcpy(&buf[0], &buf[2], 4), AKERR_VALUE);
|
||||
/* Adjacent but not overlapping is fine. */
|
||||
AKSL_CHECK_OK(aksl_memcpy(&buf[4], &buf[0], 4));
|
||||
AKSL_CHECK(buf[4] == 0 && buf[5] == 1 && buf[6] == 2 && buf[7] == 3);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_memmove_handles_overlap(void)
|
||||
{
|
||||
unsigned char buf[8] = { 0, 1, 2, 3, 4, 5, 6, 7 };
|
||||
|
||||
AKSL_CHECK_OK(aksl_memmove(&buf[2], &buf[0], 4));
|
||||
AKSL_CHECK(buf[2] == 0 && buf[3] == 1 && buf[4] == 2 && buf[5] == 3);
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_memmove(NULL, buf, 1), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_memmove(buf, NULL, 1), AKERR_NULLPOINTER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_memcpy_rejects_null_destination(void)
|
||||
{
|
||||
unsigned char src[1] = { 1 };
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_memcpy(NULL, src, sizeof(src)),
|
||||
AKERR_NULLPOINTER);
|
||||
AKERR_NULLPOINTER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -122,7 +264,50 @@ static int test_memcpy_rejects_null_source(void)
|
||||
unsigned char dst[1] = { 0 };
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_memcpy(dst, NULL, sizeof(dst)),
|
||||
AKERR_NULLPOINTER);
|
||||
AKERR_NULLPOINTER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* memcmp and memchr answer through an out-param, because the return value is
|
||||
* already spoken for by the error context. A memchr that finds nothing is not an
|
||||
* error: "absent" is an ordinary answer to that question, so *dest is NULL and
|
||||
* the call succeeds.
|
||||
*/
|
||||
static int test_memcmp_reports_ordering(void)
|
||||
{
|
||||
unsigned char a[4] = { 1, 2, 3, 4 };
|
||||
unsigned char b[4] = { 1, 2, 3, 4 };
|
||||
unsigned char c[4] = { 1, 2, 9, 4 };
|
||||
int result = 99;
|
||||
|
||||
AKSL_CHECK_OK(aksl_memcmp(a, b, sizeof(a), &result));
|
||||
AKSL_CHECK(result == 0);
|
||||
AKSL_CHECK_OK(aksl_memcmp(a, c, sizeof(a), &result));
|
||||
AKSL_CHECK(result < 0);
|
||||
AKSL_CHECK_OK(aksl_memcmp(c, a, sizeof(a), &result));
|
||||
AKSL_CHECK(result > 0);
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_memcmp(NULL, b, 4, &result), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_memcmp(a, NULL, 4, &result), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_memcmp(a, b, 4, NULL), AKERR_NULLPOINTER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_memchr_finds_or_reports_absence(void)
|
||||
{
|
||||
unsigned char buf[5] = { 'a', 'b', 'c', 'd', 'e' };
|
||||
void *found = NULL;
|
||||
|
||||
AKSL_CHECK_OK(aksl_memchr(buf, 'c', sizeof(buf), &found));
|
||||
AKSL_CHECK(found == (void *)&buf[2]);
|
||||
|
||||
/* Not found is a successful answer of "nowhere". */
|
||||
AKSL_CHECK_OK(aksl_memchr(buf, 'z', sizeof(buf), &found));
|
||||
AKSL_CHECK(found == NULL);
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_memchr(NULL, 'a', 1, &found), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_memchr(buf, 'a', 1, NULL), AKERR_NULLPOINTER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -134,17 +319,29 @@ int main(void)
|
||||
|
||||
AKSL_RUN(failures, test_malloc_writes_nonnull_pointer);
|
||||
AKSL_RUN(failures, test_malloc_rejects_null_destination);
|
||||
AKSL_RUN(failures, test_free_rejects_null_pointer);
|
||||
AKSL_RUN(failures, test_malloc_rejects_zero_size);
|
||||
AKSL_RUN(failures, test_malloc_reports_out_of_memory);
|
||||
AKSL_RUN(failures, test_malloc_free_round_trip);
|
||||
|
||||
AKSL_RUN(failures, test_calloc_zeroes_and_guards);
|
||||
AKSL_RUN(failures, test_realloc_grows_and_preserves_the_old_block_on_failure);
|
||||
|
||||
AKSL_RUN(failures, test_free_rejects_null_pointer);
|
||||
AKSL_RUN(failures, test_freep_clears_the_pointer);
|
||||
|
||||
AKSL_RUN(failures, test_memset_fills_buffer);
|
||||
AKSL_RUN(failures, test_memset_zero_length_is_noop);
|
||||
AKSL_RUN(failures, test_memset_rejects_null_buffer);
|
||||
|
||||
AKSL_RUN(failures, test_memcpy_copies_bytes);
|
||||
AKSL_RUN(failures, test_memcpy_zero_length_is_noop);
|
||||
AKSL_RUN(failures, test_memcpy_rejects_overlapping_ranges);
|
||||
AKSL_RUN(failures, test_memcpy_rejects_null_destination);
|
||||
AKSL_RUN(failures, test_memcpy_rejects_null_source);
|
||||
AKSL_RUN(failures, test_memmove_handles_overlap);
|
||||
|
||||
AKSL_RUN(failures, test_memcmp_reports_ordering);
|
||||
AKSL_RUN(failures, test_memchr_finds_or_reports_absence);
|
||||
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
/*
|
||||
* aksl_realpath -- TODO.md section 1.5.
|
||||
* aksl_realpath and aksl_realpath_alloc -- TODO.md section 1.5, now complete.
|
||||
*
|
||||
* The happy paths compare against realpath(3) itself rather than against a
|
||||
* hard-coded string, because $TMPDIR may itself be a symlink (/tmp -> /private/tmp
|
||||
* and friends) and the resolved answer is what the platform says it is.
|
||||
*
|
||||
* Every failure case here passes a *zeroed* resolved_path buffer. That is
|
||||
* deliberate: on failure the wrapper formats resolved_path with %s while
|
||||
* realpath(3) leaves the buffer unspecified (TODO.md 2.1.6), so a test that
|
||||
* passed an uninitialised buffer would be reading uninitialised memory in the
|
||||
* library's own error path. The uninitialised-buffer crash is the defect's own
|
||||
* test to write, not something these should trip over incidentally.
|
||||
*
|
||||
* Also not covered: resolved_path == NULL, which is unchecked today and leaks
|
||||
* the buffer realpath(3) allocates (2.1.6).
|
||||
* The failure cases now pass an *uninitialised* resolved_path on purpose. That
|
||||
* used to be the crash case (TODO.md 2.1.6): the wrapper's own error path
|
||||
* formatted the buffer with %s while realpath(3) leaves its contents
|
||||
* unspecified on failure, so the library read uninitialised memory while
|
||||
* reporting an error. The message names only the input path now, and this test
|
||||
* is what holds that -- it is meant to be run under the sanitizer build, where a
|
||||
* regression is an immediate abort rather than a silent read of stack garbage.
|
||||
*/
|
||||
|
||||
#include "aksl_capture.h"
|
||||
@@ -31,7 +29,7 @@ static int test_resolves_an_existing_file(void)
|
||||
memset(resolved, 0x00, sizeof(resolved));
|
||||
AKSL_CHECK(realpath(path, expected) != NULL);
|
||||
|
||||
AKSL_CHECK_OK(aksl_realpath(path, resolved));
|
||||
AKSL_CHECK_OK(aksl_realpath(path, resolved, sizeof(resolved)));
|
||||
AKSL_CHECK(strcmp(resolved, expected) == 0);
|
||||
AKSL_CHECK(resolved[0] == '/');
|
||||
AKSL_CHECK(unlink(path) == 0);
|
||||
@@ -54,7 +52,7 @@ static int test_resolves_a_symlink_to_its_target(void)
|
||||
|
||||
memset(resolved, 0x00, sizeof(resolved));
|
||||
AKSL_CHECK(realpath(target, expected) != NULL);
|
||||
AKSL_CHECK_OK(aksl_realpath(link, resolved));
|
||||
AKSL_CHECK_OK(aksl_realpath(link, resolved, sizeof(resolved)));
|
||||
AKSL_CHECK(strcmp(resolved, expected) == 0);
|
||||
|
||||
AKSL_CHECK(unlink(link) == 0);
|
||||
@@ -62,14 +60,20 @@ static int test_resolves_a_symlink_to_its_target(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The failure path with a buffer nobody has written to. Uninitialised on
|
||||
* purpose: see the header comment. Under ASan/MSan this is the test that fails
|
||||
* if the error path ever starts reading resolved_path again.
|
||||
*/
|
||||
static int test_missing_path_reports_enoent(void)
|
||||
{
|
||||
char resolved[PATH_MAX];
|
||||
|
||||
memset(resolved, 0x00, sizeof(resolved));
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(
|
||||
aksl_realpath("/nonexistent/aksl/path", resolved),
|
||||
aksl_realpath("/nonexistent/aksl/path", resolved, sizeof(resolved)),
|
||||
ENOENT, "/nonexistent/aksl/path");
|
||||
/* The message must name the path and must not quote the buffer back. */
|
||||
AKSL_CHECK(strstr(aksl_last_message, "resolved") == NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -84,19 +88,94 @@ static int test_non_directory_component_reports_enotdir(void)
|
||||
AKSL_CHECK((size_t)snprintf(child, sizeof(child), "%s/child", path)
|
||||
< sizeof(child));
|
||||
|
||||
memset(resolved, 0x00, sizeof(resolved));
|
||||
AKSL_CHECK_STATUS(aksl_realpath(child, resolved), ENOTDIR);
|
||||
AKSL_CHECK_STATUS(aksl_realpath(child, resolved, sizeof(resolved)), ENOTDIR);
|
||||
AKSL_CHECK(unlink(path) == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_rejects_null_path(void)
|
||||
/* Two symlinks pointing at each other: the kernel gives up with ELOOP. */
|
||||
static int test_symlink_loop_reports_eloop(void)
|
||||
{
|
||||
char a[AKSL_TMP_MAX];
|
||||
char b[AKSL_TMP_MAX];
|
||||
char resolved[PATH_MAX];
|
||||
|
||||
AKSL_CHECK(aksl_temp_file(a, sizeof(a)) == 0);
|
||||
AKSL_CHECK(aksl_temp_file(b, sizeof(b)) == 0);
|
||||
AKSL_CHECK(unlink(a) == 0);
|
||||
AKSL_CHECK(unlink(b) == 0);
|
||||
AKSL_CHECK(symlink(a, b) == 0);
|
||||
AKSL_CHECK(symlink(b, a) == 0);
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_realpath(a, resolved, sizeof(resolved)), ELOOP);
|
||||
|
||||
AKSL_CHECK(unlink(a) == 0);
|
||||
AKSL_CHECK(unlink(b) == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_rejects_null_arguments(void)
|
||||
{
|
||||
char resolved[PATH_MAX];
|
||||
|
||||
memset(resolved, 0x00, sizeof(resolved));
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_realpath(NULL, resolved),
|
||||
AKERR_NULLPOINTER, "path=");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_realpath(NULL, resolved, sizeof(resolved)),
|
||||
AKERR_NULLPOINTER, "path=");
|
||||
/*
|
||||
* TODO.md 2.1.6: this used to be unchecked, and realpath(path, NULL)
|
||||
* allocated a buffer that the wrapper then discarded and leaked.
|
||||
*/
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_realpath("/tmp", NULL, PATH_MAX),
|
||||
AKERR_NULLPOINTER, "resolved_path=");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* realpath(3) cannot be told how much room it has, so the only safe answer to an
|
||||
* undersized buffer is to refuse before calling it. A caller who gets this back
|
||||
* has a bug that would otherwise have been a stack smash.
|
||||
*/
|
||||
static int test_rejects_a_buffer_below_path_max(void)
|
||||
{
|
||||
char small[16];
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_realpath("/tmp", small, sizeof(small)),
|
||||
AKERR_OUTOFBOUNDS, "PATH_MAX");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_alloc_form_resolves_and_hands_over_the_buffer(void)
|
||||
{
|
||||
char path[AKSL_TMP_MAX];
|
||||
char expected[PATH_MAX];
|
||||
char *resolved = NULL;
|
||||
|
||||
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
|
||||
AKSL_CHECK(realpath(path, expected) != NULL);
|
||||
|
||||
AKSL_CHECK_OK(aksl_realpath_alloc(path, &resolved));
|
||||
AKSL_CHECK(resolved != NULL);
|
||||
AKSL_CHECK(strcmp(resolved, expected) == 0);
|
||||
/* The buffer is the caller's; releasing it through the library closes the
|
||||
* leak that the old NULL-destination path opened. */
|
||||
AKSL_CHECK_OK(aksl_free(resolved));
|
||||
|
||||
AKSL_CHECK(unlink(path) == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_alloc_form_reports_failure_and_writes_no_pointer(void)
|
||||
{
|
||||
char *resolved = (char *)0x1;
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(
|
||||
aksl_realpath_alloc("/nonexistent/aksl/path", &resolved),
|
||||
ENOENT, "/nonexistent/aksl/path");
|
||||
/* Cleared before the call, so a failure cannot leave a stale pointer. */
|
||||
AKSL_CHECK(resolved == NULL);
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_realpath_alloc(NULL, &resolved), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_realpath_alloc("/tmp", NULL), AKERR_NULLPOINTER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -110,7 +189,11 @@ int main(void)
|
||||
AKSL_RUN(failures, test_resolves_a_symlink_to_its_target);
|
||||
AKSL_RUN(failures, test_missing_path_reports_enoent);
|
||||
AKSL_RUN(failures, test_non_directory_component_reports_enotdir);
|
||||
AKSL_RUN(failures, test_rejects_null_path);
|
||||
AKSL_RUN(failures, test_symlink_loop_reports_eloop);
|
||||
AKSL_RUN(failures, test_rejects_null_arguments);
|
||||
AKSL_RUN(failures, test_rejects_a_buffer_below_path_max);
|
||||
AKSL_RUN(failures, test_alloc_form_resolves_and_hands_over_the_buffer);
|
||||
AKSL_RUN(failures, test_alloc_form_reports_failure_and_writes_no_pointer);
|
||||
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
/*
|
||||
* Stream wrappers: aksl_fopen / aksl_fread / aksl_fwrite / aksl_fclose.
|
||||
*
|
||||
* TODO.md section 1.2. Covered here: the happy paths, the round trip, the NULL
|
||||
* guards that exist, and the two error statuses the wrappers can actually
|
||||
* produce today -- AKERR_EOF from a short read and AKERR_IO from a stream whose
|
||||
* error indicator is set.
|
||||
* TODO.md section 1.2, now complete. The happy paths, the round trip, every
|
||||
* NULL guard, both stream-error statuses, the transferred-member count that
|
||||
* aksl_fread and aksl_fwrite report through nmemb_out, and the three cases that
|
||||
* needed a hostile file to produce: a mode-denied path (EACCES), a full device
|
||||
* (/dev/full) for the short write, and a stream whose buffered flush fails at
|
||||
* fclose time.
|
||||
*
|
||||
* Not covered, because the behaviour is a documented gap rather than a
|
||||
* contract: aksl_fopen(NULL, ...) and aksl_fopen(path, NULL, ...) are unchecked
|
||||
* (2.2.2), aksl_fread/aksl_fwrite never check ptr and report a short transfer
|
||||
* that is neither EOF nor error as complete success (2.2.3).
|
||||
* The /dev/full tests are skipped where the device is absent -- it is a Linux
|
||||
* thing -- rather than failing, and say so on stderr so a skip cannot be
|
||||
* mistaken for a pass.
|
||||
*
|
||||
* Temp files come from aksl_temp_file() and are unlinked by the test that made
|
||||
* them, so a failing test leaves nothing behind but the file it was mid-way
|
||||
@@ -19,6 +20,7 @@
|
||||
#include "aksl_capture.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
static int test_fopen_writes_stream_pointer(void)
|
||||
{
|
||||
@@ -41,16 +43,49 @@ static int test_fopen_reports_missing_path(void)
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(
|
||||
aksl_fopen("/nonexistent/aksl/stream", "r", &fp),
|
||||
ENOENT, "/nonexistent/aksl/stream");
|
||||
/* *fp is cleared before the call, so a failed open cannot leave garbage. */
|
||||
AKSL_CHECK(fp == NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_fopen_rejects_null_stream_out(void)
|
||||
/*
|
||||
* A file the caller cannot open for reading is EACCES rather than ENOENT.
|
||||
* Skipped under a user who bypasses the permission bits -- root, or anything
|
||||
* holding CAP_DAC_OVERRIDE -- where chmod 000 simply does not deny anything.
|
||||
*/
|
||||
static int test_fopen_reports_permission_denied(void)
|
||||
{
|
||||
char path[AKSL_TMP_MAX];
|
||||
FILE *fp = NULL;
|
||||
|
||||
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
|
||||
AKSL_CHECK(chmod(path, 0000) == 0);
|
||||
|
||||
if ( geteuid() == 0 ) {
|
||||
fprintf(stderr, " (skipped: running as root, chmod 000 denies nothing)\n");
|
||||
AKSL_CHECK(unlink(path) == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(path, "r", &fp), EACCES, path);
|
||||
AKSL_CHECK(unlink(path) == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_fopen_rejects_null_arguments(void)
|
||||
{
|
||||
char path[AKSL_TMP_MAX];
|
||||
FILE *fp = NULL;
|
||||
|
||||
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(path, "r", NULL),
|
||||
AKERR_NULLPOINTER, "NULL");
|
||||
AKERR_NULLPOINTER, "fp=");
|
||||
/* TODO.md 2.2.2: both of these used to go straight through to fopen(3). */
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(NULL, "r", &fp),
|
||||
AKERR_NULLPOINTER, "pathname=");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(path, NULL, &fp),
|
||||
AKERR_NULLPOINTER, "mode=");
|
||||
AKSL_CHECK(unlink(path) == 0);
|
||||
return 0;
|
||||
}
|
||||
@@ -61,18 +96,22 @@ static int test_write_read_round_trip(void)
|
||||
char path[AKSL_TMP_MAX];
|
||||
char payload[] = "libakstdlib round trip";
|
||||
char readback[sizeof(payload)];
|
||||
size_t moved = 0;
|
||||
FILE *fp = NULL;
|
||||
|
||||
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
|
||||
|
||||
AKSL_CHECK_OK(aksl_fopen(path, "w", &fp));
|
||||
AKSL_CHECK_OK(aksl_fwrite(payload, 1, sizeof(payload), fp));
|
||||
AKSL_CHECK_OK(aksl_fwrite(payload, 1, sizeof(payload), fp, &moved));
|
||||
AKSL_CHECK(moved == sizeof(payload));
|
||||
AKSL_CHECK_OK(aksl_fclose(fp));
|
||||
|
||||
fp = NULL;
|
||||
moved = 0;
|
||||
memset(readback, 0x00, sizeof(readback));
|
||||
AKSL_CHECK_OK(aksl_fopen(path, "r", &fp));
|
||||
AKSL_CHECK_OK(aksl_fread(readback, 1, sizeof(readback), fp));
|
||||
AKSL_CHECK_OK(aksl_fread(readback, 1, sizeof(readback), fp, &moved));
|
||||
AKSL_CHECK(moved == sizeof(readback));
|
||||
AKSL_CHECK_OK(aksl_fclose(fp));
|
||||
|
||||
AKSL_CHECK(memcmp(payload, readback, sizeof(payload)) == 0);
|
||||
@@ -80,27 +119,35 @@ static int test_write_read_round_trip(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Asking for more members than the file holds sets feof, which is AKERR_EOF. */
|
||||
static int test_fread_short_read_is_eof(void)
|
||||
/*
|
||||
* Asking for more members than the file holds sets feof, which is AKERR_EOF --
|
||||
* and nmemb_out says how many did arrive, which is the entire reason for
|
||||
* distinguishing EOF from an error rather than reporting both as AKERR_IO.
|
||||
*/
|
||||
static int test_fread_short_read_is_eof_and_reports_the_count(void)
|
||||
{
|
||||
char path[AKSL_TMP_MAX];
|
||||
char buf[32];
|
||||
size_t moved = 0;
|
||||
FILE *fp = NULL;
|
||||
|
||||
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
|
||||
|
||||
AKSL_CHECK_OK(aksl_fopen(path, "w", &fp));
|
||||
AKSL_CHECK_OK(aksl_fwrite("abcd", 1, 4, fp));
|
||||
AKSL_CHECK_OK(aksl_fwrite("abcd", 1, 4, fp, &moved));
|
||||
AKSL_CHECK(moved == 4);
|
||||
AKSL_CHECK_OK(aksl_fclose(fp));
|
||||
|
||||
fp = NULL;
|
||||
moved = 99;
|
||||
memset(buf, 0x00, sizeof(buf));
|
||||
AKSL_CHECK_OK(aksl_fopen(path, "r", &fp));
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), fp),
|
||||
AKERR_EOF, "EOF");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), fp, &moved),
|
||||
AKERR_EOF, "EOF");
|
||||
/* TODO.md 2.2.3: this is what the caller could not previously find out. */
|
||||
AKSL_CHECK(moved == 4);
|
||||
AKSL_CHECK_OK(aksl_fclose(fp));
|
||||
|
||||
/* The bytes that did arrive are still in the buffer. */
|
||||
AKSL_CHECK(memcmp(buf, "abcd", 4) == 0);
|
||||
AKSL_CHECK(unlink(path) == 0);
|
||||
return 0;
|
||||
@@ -108,59 +155,151 @@ static int test_fread_short_read_is_eof(void)
|
||||
|
||||
/*
|
||||
* A stream opened "w" has no read permission, so fread sets the error indicator
|
||||
* rather than the EOF one: AKERR_IO, not AKERR_EOF.
|
||||
* rather than the EOF one -- and the status is the errno the C library actually
|
||||
* saw, EBADF, rather than a flat AKERR_IO.
|
||||
*
|
||||
* That is a change from the old wrapper, which read ferror() and reported
|
||||
* AKERR_IO without ever consulting errno. Routing it through AKSL_ERRNO_OR
|
||||
* (TODO.md 2.2.1) keeps AKERR_IO as the fallback for the case where the stream
|
||||
* is in error and errno says nothing, and hands back the real reason otherwise.
|
||||
*/
|
||||
static int test_fread_from_write_only_stream_is_io_error(void)
|
||||
static int test_fread_from_write_only_stream_reports_errno(void)
|
||||
{
|
||||
char path[AKSL_TMP_MAX];
|
||||
char buf[4];
|
||||
size_t moved = 99;
|
||||
FILE *fp = NULL;
|
||||
|
||||
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
|
||||
AKSL_CHECK_OK(aksl_fopen(path, "w", &fp));
|
||||
memset(buf, 0x00, sizeof(buf));
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), fp),
|
||||
AKERR_IO, "Error reading file");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), fp, &moved),
|
||||
EBADF, "of 4 members");
|
||||
AKSL_CHECK(moved == 0);
|
||||
AKSL_CHECK_OK(aksl_fclose(fp));
|
||||
AKSL_CHECK(unlink(path) == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_fread_rejects_null_stream(void)
|
||||
static int test_fread_rejects_null_arguments(void)
|
||||
{
|
||||
char path[AKSL_TMP_MAX];
|
||||
char buf[4];
|
||||
size_t moved = 0;
|
||||
FILE *fp = NULL;
|
||||
|
||||
memset(buf, 0x00, sizeof(buf));
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), NULL),
|
||||
AKERR_NULLPOINTER, "NULL");
|
||||
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
|
||||
AKSL_CHECK_OK(aksl_fopen(path, "r", &fp));
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), NULL, &moved),
|
||||
AKERR_NULLPOINTER, "fp=");
|
||||
/* TODO.md 2.2.3: ptr was never checked in either direction. */
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(NULL, 1, sizeof(buf), fp, &moved),
|
||||
AKERR_NULLPOINTER, "ptr=");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), fp, NULL),
|
||||
AKERR_NULLPOINTER, "nmemb_out=");
|
||||
|
||||
AKSL_CHECK_OK(aksl_fclose(fp));
|
||||
AKSL_CHECK(unlink(path) == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Mirror image of the fread case: a "r" stream cannot be written to. */
|
||||
static int test_fwrite_to_read_only_stream_is_io_error(void)
|
||||
static int test_fwrite_to_read_only_stream_reports_errno(void)
|
||||
{
|
||||
char path[AKSL_TMP_MAX];
|
||||
size_t moved = 99;
|
||||
FILE *fp = NULL;
|
||||
|
||||
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
|
||||
AKSL_CHECK_OK(aksl_fopen(path, "r", &fp));
|
||||
AKSL_CHECK_STATUS(aksl_fwrite("xy", 1, 2, fp), AKERR_IO);
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fwrite("xy", 1, 2, fp, &moved),
|
||||
EBADF, "wrote 0 of 2 members");
|
||||
AKSL_CHECK(moved == 0);
|
||||
AKSL_CHECK_OK(aksl_fclose(fp));
|
||||
AKSL_CHECK(unlink(path) == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_fwrite_rejects_null_stream(void)
|
||||
static int test_fwrite_rejects_null_arguments(void)
|
||||
{
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fwrite("xy", 1, 2, NULL),
|
||||
AKERR_NULLPOINTER, "NULL");
|
||||
size_t moved = 0;
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fwrite("xy", 1, 2, NULL, &moved),
|
||||
AKERR_NULLPOINTER, "fp=");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fwrite(NULL, 1, 2, stdout, &moved),
|
||||
AKERR_NULLPOINTER, "ptr=");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fwrite("xy", 1, 2, stdout, NULL),
|
||||
AKERR_NULLPOINTER, "nmemb_out=");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* /dev/full accepts an open for writing and then fails every write with ENOSPC.
|
||||
* It is the only convenient way to reach a genuinely short write on a stream
|
||||
* that is otherwise perfectly healthy.
|
||||
*/
|
||||
static int test_fwrite_to_full_device_reports_enospc(void)
|
||||
{
|
||||
char payload[8192];
|
||||
size_t moved = 99;
|
||||
FILE *fp = NULL;
|
||||
|
||||
if ( access("/dev/full", W_OK) != 0 ) {
|
||||
fprintf(stderr, " (skipped: no writable /dev/full on this platform)\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(payload, 'x', sizeof(payload));
|
||||
AKSL_CHECK_OK(aksl_fopen("/dev/full", "w", &fp));
|
||||
/*
|
||||
* Larger than any plausible stdio buffer, so the write reaches the device
|
||||
* inside fwrite rather than being deferred to fclose -- the deferred case is
|
||||
* the next test.
|
||||
*/
|
||||
AKSL_CHECK_STATUS(aksl_fwrite(payload, 1, sizeof(payload), fp, &moved), ENOSPC);
|
||||
AKSL_CHECK(moved < sizeof(payload));
|
||||
/*
|
||||
* fclose succeeds here, and that is not a contradiction: the write already
|
||||
* reached the device and failed, so by close time there is nothing left
|
||||
* buffered to fail on. The deferred case -- where the write fits in the
|
||||
* stdio buffer and only fclose finds out -- is the next test. Take the
|
||||
* context however it comes back rather than asserting either way, since
|
||||
* whether the error indicator survives to fclose is a stdio implementation
|
||||
* detail and not part of this library's contract.
|
||||
*/
|
||||
(void)aksl_take(aksl_fclose(fp));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The write that fits in the stdio buffer succeeds, and the failure surfaces
|
||||
* only when fclose flushes it. A caller who checks fwrite and ignores fclose
|
||||
* loses the data silently, which is why aksl_fclose reports errno rather than
|
||||
* just a non-zero return.
|
||||
*/
|
||||
static int test_fclose_surfaces_a_failed_flush(void)
|
||||
{
|
||||
size_t moved = 0;
|
||||
FILE *fp = NULL;
|
||||
|
||||
if ( access("/dev/full", W_OK) != 0 ) {
|
||||
fprintf(stderr, " (skipped: no writable /dev/full on this platform)\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
AKSL_CHECK_OK(aksl_fopen("/dev/full", "w", &fp));
|
||||
AKSL_CHECK_OK(aksl_fwrite("small", 1, 5, fp, &moved));
|
||||
AKSL_CHECK(moved == 5);
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fclose(fp), ENOSPC, "buffered data is lost");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_fclose_rejects_null_stream(void)
|
||||
{
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fclose(NULL),
|
||||
AKERR_NULLPOINTER, "NULL");
|
||||
AKERR_NULLPOINTER, "NULL");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -172,16 +311,19 @@ int main(void)
|
||||
|
||||
AKSL_RUN(failures, test_fopen_writes_stream_pointer);
|
||||
AKSL_RUN(failures, test_fopen_reports_missing_path);
|
||||
AKSL_RUN(failures, test_fopen_rejects_null_stream_out);
|
||||
AKSL_RUN(failures, test_fopen_reports_permission_denied);
|
||||
AKSL_RUN(failures, test_fopen_rejects_null_arguments);
|
||||
|
||||
AKSL_RUN(failures, test_write_read_round_trip);
|
||||
AKSL_RUN(failures, test_fread_short_read_is_eof);
|
||||
AKSL_RUN(failures, test_fread_from_write_only_stream_is_io_error);
|
||||
AKSL_RUN(failures, test_fread_rejects_null_stream);
|
||||
AKSL_RUN(failures, test_fread_short_read_is_eof_and_reports_the_count);
|
||||
AKSL_RUN(failures, test_fread_from_write_only_stream_reports_errno);
|
||||
AKSL_RUN(failures, test_fread_rejects_null_arguments);
|
||||
|
||||
AKSL_RUN(failures, test_fwrite_to_read_only_stream_is_io_error);
|
||||
AKSL_RUN(failures, test_fwrite_rejects_null_stream);
|
||||
AKSL_RUN(failures, test_fwrite_to_read_only_stream_reports_errno);
|
||||
AKSL_RUN(failures, test_fwrite_rejects_null_arguments);
|
||||
AKSL_RUN(failures, test_fwrite_to_full_device_reports_enospc);
|
||||
|
||||
AKSL_RUN(failures, test_fclose_surfaces_a_failed_flush);
|
||||
AKSL_RUN(failures, test_fclose_rejects_null_stream);
|
||||
|
||||
AKSL_REPORT(failures);
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
* h = h * 33 + byte for each of len bytes, truncated to 32 bits. They were
|
||||
* computed independently of this implementation.
|
||||
*
|
||||
* Every vector here is 7-bit ASCII, where signed and unsigned char agree. The
|
||||
* high-bit case ("\xff\xfe") is the sign-extension defect in TODO.md 2.2.6 and
|
||||
* is left for a test that can be registered as a known failure.
|
||||
* Most vectors here are 7-bit ASCII, where signed and unsigned char agree and
|
||||
* the test therefore says nothing either way about byte signedness. The high-bit
|
||||
* vector is the one that pins TODO.md 2.2.6 down.
|
||||
*/
|
||||
|
||||
#include "aksl_capture.h"
|
||||
@@ -46,6 +46,49 @@ static int test_known_answer_vectors(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO.md 2.2.6, pinned. The cursor is an unsigned char * now, so bytes at or
|
||||
* above 0x80 contribute their unsigned value. Iterating a plain char * on x86 or
|
||||
* ARM Linux made them negative, giving 5859874 here instead of 5868578 -- a hash
|
||||
* that disagreed with canonical djb2 and, worse, disagreed with itself across
|
||||
* platforms depending on whether char happened to be signed.
|
||||
*
|
||||
* Benign for the 7-bit identifiers akbasic hashes; quietly wrong for the first
|
||||
* caller to key a table on a filename or a UTF-8 string literal.
|
||||
*/
|
||||
static int test_high_bit_bytes_are_unsigned(void)
|
||||
{
|
||||
char buf[2] = { (char)0xff, (char)0xfe };
|
||||
uint32_t h = 0;
|
||||
|
||||
AKSL_CHECK_OK(aksl_strhash_djb2(buf, sizeof(buf), &h));
|
||||
AKSL_CHECK(h == 5868578u);
|
||||
/* The sign-extended answer, spelled out so a regression names itself. */
|
||||
AKSL_CHECK(h != 5859874u);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The NUL-terminated convenience form (TODO.md 3.6) agrees with the length one. */
|
||||
static int test_str_form_matches_the_length_form(void)
|
||||
{
|
||||
const char *s = "libakstdlib";
|
||||
uint32_t from_str = 0;
|
||||
uint32_t from_len = 0;
|
||||
|
||||
AKSL_CHECK_OK(aksl_strhash_djb2_str(s, &from_str));
|
||||
AKSL_CHECK_OK(aksl_strhash_djb2(s, strlen(s), &from_len));
|
||||
AKSL_CHECK(from_str == from_len);
|
||||
AKSL_CHECK(from_str == 884285482u);
|
||||
|
||||
/* The empty string is the seed, not an error. */
|
||||
AKSL_CHECK_OK(aksl_strhash_djb2_str("", &from_str));
|
||||
AKSL_CHECK(from_str == 5381);
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_strhash_djb2_str(NULL, &from_str), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_strhash_djb2_str("x", NULL), AKERR_NULLPOINTER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The function is length-driven, not NUL-driven: an embedded NUL is hashed. */
|
||||
static int test_embedded_nul_is_hashed(void)
|
||||
{
|
||||
@@ -95,6 +138,8 @@ int main(void)
|
||||
AKSL_RUN(failures, test_empty_string_is_the_djb2_seed);
|
||||
AKSL_RUN(failures, test_zero_length_ignores_the_buffer);
|
||||
AKSL_RUN(failures, test_known_answer_vectors);
|
||||
AKSL_RUN(failures, test_high_bit_bytes_are_unsigned);
|
||||
AKSL_RUN(failures, test_str_form_matches_the_length_form);
|
||||
AKSL_RUN(failures, test_embedded_nul_is_hashed);
|
||||
AKSL_RUN(failures, test_hash_is_stable_across_calls);
|
||||
AKSL_RUN(failures, test_rejects_null_arguments);
|
||||
|
||||
292
tests/test_strto.c
Normal file
292
tests/test_strto.c
Normal file
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
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);
|
||||
}
|
||||
@@ -1,165 +1,572 @@
|
||||
/*
|
||||
* Depth-first tree search.
|
||||
* Tree traversal -- TODO.md section 1.8, complete.
|
||||
*
|
||||
* Previously this test shared one TreeSearchParams across all three searches
|
||||
* without resetting it, so `steps` accumulated (7, then 14, then 21) and the
|
||||
* second assertion failed -- `tree` was a red test on every run. Each search
|
||||
* now gets its own params.
|
||||
* 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.
|
||||
*
|
||||
* Caveat worth knowing when reading the step counts below: the value is hidden
|
||||
* in tree[6], which is the last node visited in pre-, in- and post-order alike,
|
||||
* so "7 steps" holds for all three orders and does not actually distinguish
|
||||
* them -- nor does it prove that AKERR_ITERATOR_BREAK stopped anything (it does
|
||||
* not; see tests/test_tree_iterate_break.c and TODO.md 2.1.3). Visit-order
|
||||
* assertions that tell the three traversals apart are TODO.md section 1.8.
|
||||
*/
|
||||
|
||||
#include "aksl_capture.h"
|
||||
|
||||
#define MAX_LEAVES 7
|
||||
#define HIDDEN_VALUE ((void *)17336)
|
||||
|
||||
typedef struct TreeSearchParams
|
||||
{
|
||||
void *value;
|
||||
int steps;
|
||||
aksl_TreeNode *node;
|
||||
} TreeSearchParams;
|
||||
|
||||
static akerr_ErrorContext AKERR_NOIGNORE *find_value(aksl_TreeNode *node, void *data)
|
||||
{
|
||||
TreeSearchParams *parms = NULL;
|
||||
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "node");
|
||||
FAIL_ZERO_RETURN(e, data, AKERR_NULLPOINTER, "data");
|
||||
parms = (TreeSearchParams *)data;
|
||||
parms->steps += 1;
|
||||
if ( node->leaf == parms->value ) {
|
||||
parms->node = node;
|
||||
FAIL_RETURN(e, AKERR_ITERATOR_BREAK, "stop");
|
||||
}
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* Build the 3-level tree used by every case here, with the search value hidden
|
||||
* in the bottom-right leaf.
|
||||
* The 7-node tree used throughout:
|
||||
*
|
||||
* LEFT RIGHT
|
||||
* TREE[0]
|
||||
* +--------^^---------+
|
||||
* | |
|
||||
* TREE[1] TREE[2]
|
||||
* +---^^---+ +---^^---+
|
||||
* | | | |
|
||||
*TREE[3] TREE[4] TREE[5] TREE[6]
|
||||
* 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
|
||||
*/
|
||||
static void build_tree(aksl_TreeNode *tree, TreeSearchParams *parms)
|
||||
|
||||
#include "aksl_capture.h"
|
||||
|
||||
#define MAX_LEAVES 7
|
||||
#define MAX_VISITS 32
|
||||
|
||||
typedef struct VisitLog
|
||||
{
|
||||
memset((void *)tree, 0x00, sizeof(aksl_TreeNode) * MAX_LEAVES);
|
||||
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;
|
||||
|
||||
static void visitlog_init(VisitLog *log)
|
||||
{
|
||||
memset((void *)log, 0x00, sizeof(VisitLog));
|
||||
}
|
||||
|
||||
static akerr_ErrorContext AKERR_NOIGNORE *record_visit(aksl_TreeNode *node, void *data)
|
||||
{
|
||||
VisitLog *log = NULL;
|
||||
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "node");
|
||||
FAIL_ZERO_RETURN(e, data, AKERR_NULLPOINTER, "data");
|
||||
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);
|
||||
}
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
static void build_tree(aksl_TreeNode *tree)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for ( i = 0; i < MAX_LEAVES; i++ ) {
|
||||
memset((void *)&tree[i], 0x00, sizeof(aksl_TreeNode));
|
||||
}
|
||||
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];
|
||||
tree[6].leaf = HIDDEN_VALUE;
|
||||
|
||||
memset((void *)parms, 0x00, sizeof(TreeSearchParams));
|
||||
parms->value = HIDDEN_VALUE;
|
||||
}
|
||||
|
||||
static int search_finds_hidden_value(uint8_t searchmode)
|
||||
/* 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)
|
||||
{
|
||||
aksl_TreeNode tree[MAX_LEAVES];
|
||||
TreeSearchParams parms;
|
||||
int i = 0;
|
||||
|
||||
build_tree(tree, &parms);
|
||||
|
||||
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &find_value, NULL, NULL,
|
||||
searchmode, &parms, NULL));
|
||||
AKSL_CHECK(parms.node == &tree[6]);
|
||||
AKSL_CHECK(parms.steps == MAX_LEAVES);
|
||||
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;
|
||||
}
|
||||
|
||||
static int test_dfs_preorder(void)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* aksl_tree_node_init */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static int test_node_init_zeroes_the_links(void)
|
||||
{
|
||||
return search_finds_hidden_value(AKSL_TREE_SEARCH_DFS_PREORDER);
|
||||
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;
|
||||
}
|
||||
|
||||
static int test_dfs_inorder(void)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Traversal orders */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static int test_preorder_visits_root_left_right(void)
|
||||
{
|
||||
return search_finds_hidden_value(AKSL_TREE_SEARCH_DFS_INORDER);
|
||||
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_PREORDER, &log));
|
||||
AKSL_CHECK(check_order(&log, tree, expected, MAX_LEAVES) == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_dfs_postorder(void)
|
||||
static int test_inorder_visits_left_root_right(void)
|
||||
{
|
||||
return search_finds_hidden_value(AKSL_TREE_SEARCH_DFS_POSTORDER);
|
||||
aksl_TreeNode tree[MAX_LEAVES];
|
||||
VisitLog log;
|
||||
static const int expected[MAX_LEAVES] = { 3, 1, 4, 0, 5, 2, 6 };
|
||||
|
||||
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);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_postorder_visits_left_right_root(void)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/* AKSL_TREE_SEARCH_DFS is documented as an alias for the pre-order mode. */
|
||||
static int test_dfs_is_an_alias_for_preorder(void)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Both breadth-first modes are declared in the header but not implemented, and
|
||||
* say so through AKERR_NOT_IMPLEMENTED rather than by silently visiting nothing.
|
||||
* TODO.md 2.2.10 tracks implementing them; until then this is the contract.
|
||||
* 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 bfs_reports_not_implemented(uint8_t searchmode)
|
||||
static int test_bfs_visits_level_by_level(void)
|
||||
{
|
||||
aksl_TreeNode tree[MAX_LEAVES];
|
||||
TreeSearchParams parms;
|
||||
VisitLog log;
|
||||
static const int expected[MAX_LEAVES] = { 0, 1, 2, 3, 4, 5, 6 };
|
||||
|
||||
build_tree(tree, &parms);
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(
|
||||
aksl_tree_iterate(&tree[0], &find_value, NULL, NULL, searchmode,
|
||||
&parms, NULL),
|
||||
AKERR_NOT_IMPLEMENTED, "Searchmode");
|
||||
AKSL_CHECK(parms.steps == 0);
|
||||
AKSL_CHECK(parms.node == NULL);
|
||||
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_is_not_implemented(void)
|
||||
{
|
||||
return bfs_reports_not_implemented(AKSL_TREE_SEARCH_BFS);
|
||||
}
|
||||
|
||||
static int test_bfs_right_is_not_implemented(void)
|
||||
{
|
||||
return bfs_reports_not_implemented(AKSL_TREE_SEARCH_BFS_RIGHT);
|
||||
}
|
||||
|
||||
static int test_iterate_null_arguments(void)
|
||||
static int test_bfs_right_visits_right_child_first(void)
|
||||
{
|
||||
aksl_TreeNode tree[MAX_LEAVES];
|
||||
TreeSearchParams parms;
|
||||
VisitLog log;
|
||||
static const int expected[MAX_LEAVES] = { 0, 2, 1, 6, 5, 4, 3 };
|
||||
|
||||
build_tree(tree, &parms);
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(
|
||||
aksl_tree_iterate(NULL, &find_value, NULL, NULL,
|
||||
AKSL_TREE_SEARCH_DFS_PREORDER, &parms, NULL),
|
||||
AKERR_NULLPOINTER, "root");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(
|
||||
aksl_tree_iterate(&tree[0], NULL, NULL, NULL,
|
||||
AKSL_TREE_SEARCH_DFS_PREORDER, &parms, NULL),
|
||||
AKERR_NULLPOINTER, "iter");
|
||||
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;
|
||||
}
|
||||
|
||||
/* A callback error that is not AKERR_ITERATOR_BREAK reaches the caller. */
|
||||
static int test_iterate_propagates_callback_error(void)
|
||||
/* AKSL_TREE_SEARCH_VISIT: this node and no further. */
|
||||
static int test_visit_mode_does_not_descend(void)
|
||||
{
|
||||
aksl_TreeNode tree[MAX_LEAVES];
|
||||
TreeSearchParams parms;
|
||||
VisitLog log;
|
||||
static const int expected[1] = { 0 };
|
||||
|
||||
build_tree(tree, &parms);
|
||||
/* find_value raises AKERR_NULLPOINTER when it is handed no data. */
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
static int test_custom_allocator_is_used_and_balanced(void)
|
||||
{
|
||||
aksl_TreeNode tree[MAX_LEAVES];
|
||||
VisitLog log;
|
||||
|
||||
build_tree(tree);
|
||||
visitlog_init(&log);
|
||||
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));
|
||||
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);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Degenerate shapes */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static int test_single_node_tree_visits_once_in_every_order(void)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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(&tree[0], &find_value, NULL, NULL,
|
||||
AKSL_TREE_SEARCH_DFS_PREORDER, NULL, NULL),
|
||||
AKERR_NULLPOINTER, "data");
|
||||
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);
|
||||
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)
|
||||
{
|
||||
aksl_TreeNode tree[MAX_LEAVES];
|
||||
VisitLog log;
|
||||
|
||||
build_tree(tree);
|
||||
tree[3].left = &tree[0]; /* back to the root */
|
||||
|
||||
visitlog_init(&log);
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(
|
||||
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];
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(
|
||||
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);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
aksl_TreeNode tree[MAX_LEAVES];
|
||||
VisitLog log;
|
||||
|
||||
build_tree(tree);
|
||||
visitlog_init(&log);
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(
|
||||
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);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -169,14 +576,29 @@ int main(void)
|
||||
|
||||
akerr_init();
|
||||
|
||||
AKSL_RUN(failures, test_dfs_preorder);
|
||||
AKSL_RUN(failures, test_dfs_inorder);
|
||||
AKSL_RUN(failures, test_dfs_postorder);
|
||||
AKSL_RUN(failures, test_node_init_zeroes_the_links);
|
||||
|
||||
AKSL_RUN(failures, test_bfs_is_not_implemented);
|
||||
AKSL_RUN(failures, test_bfs_right_is_not_implemented);
|
||||
AKSL_RUN(failures, test_iterate_null_arguments);
|
||||
AKSL_RUN(failures, test_iterate_propagates_callback_error);
|
||||
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);
|
||||
|
||||
AKSL_RUN(failures, test_null_arguments);
|
||||
AKSL_RUN(failures, test_unknown_searchmode_is_a_value_error);
|
||||
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* KNOWN FAILING -- TODO.md section 2.1.3
|
||||
*
|
||||
* AKERR_ITERATOR_BREAK does not abort a tree traversal. aksl_tree_iterate
|
||||
* recurses into itself, and the frame in which the callback raises the break
|
||||
* handles it in its own PROCESS/HANDLE(e, AKERR_ITERATOR_BREAK) block and
|
||||
* returns *success*. The parent frame's PASS therefore sees no error at all and
|
||||
* carries straight on into the sibling subtree, so the traversal runs to
|
||||
* completion.
|
||||
*
|
||||
* The 7-node tree below is walked pre-order (0, 1, 3, 4, 2, 5, 6) with the
|
||||
* callback breaking on tree[3], the third node visited. A working break stops
|
||||
* the walk at 3 visits; today the callback is invoked all 7 times.
|
||||
*
|
||||
* tests/test_tree.c cannot catch this: it hides its value in tree[6], which is
|
||||
* the last node visited in all three depth-first orders, so a break that never
|
||||
* fires is indistinguishable from one that fires on the final node.
|
||||
*
|
||||
* Registered in AKSL_KNOWN_FAILING_TESTS (WILL_FAIL). When the break semantics
|
||||
* are fixed, CTest reports this as unexpectedly passing -- move it into
|
||||
* AKSL_TESTS then.
|
||||
*/
|
||||
|
||||
#include "aksl_capture.h"
|
||||
|
||||
#define MAX_LEAVES 7
|
||||
|
||||
typedef struct BreakParams
|
||||
{
|
||||
aksl_TreeNode *stop_at;
|
||||
int visits;
|
||||
} BreakParams;
|
||||
|
||||
static akerr_ErrorContext AKERR_NOIGNORE *break_at_node(aksl_TreeNode *node, void *data)
|
||||
{
|
||||
BreakParams *parms = NULL;
|
||||
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, node, AKERR_NULLPOINTER, "node");
|
||||
FAIL_ZERO_RETURN(e, data, AKERR_NULLPOINTER, "data");
|
||||
parms = (BreakParams *)data;
|
||||
parms->visits += 1;
|
||||
if ( node == parms->stop_at ) {
|
||||
FAIL_RETURN(e, AKERR_ITERATOR_BREAK, "stop");
|
||||
}
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
static int test_break_aborts_the_whole_traversal(void)
|
||||
{
|
||||
aksl_TreeNode tree[MAX_LEAVES];
|
||||
BreakParams parms;
|
||||
|
||||
memset((void *)tree, 0x00, sizeof(tree));
|
||||
memset((void *)&parms, 0x00, sizeof(parms));
|
||||
|
||||
/*
|
||||
* TREE[0]
|
||||
* +--------^^---------+
|
||||
* | |
|
||||
* TREE[1] TREE[2]
|
||||
* +---^^---+ +---^^---+
|
||||
* | | | |
|
||||
*TREE[3] TREE[4] TREE[5] TREE[6]
|
||||
*/
|
||||
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];
|
||||
|
||||
parms.stop_at = &tree[3];
|
||||
|
||||
AKSL_CHECK_OK(aksl_tree_iterate(&tree[0], &break_at_node, NULL, NULL,
|
||||
AKSL_TREE_SEARCH_DFS_PREORDER, &parms, NULL));
|
||||
/* Pre-order visits 0, 1, 3 -- the break on tree[3] must stop the walk there. */
|
||||
AKSL_CHECK(parms.visits == 3);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int failures = 0;
|
||||
|
||||
akerr_init();
|
||||
AKSL_RUN(failures, test_break_aborts_the_whole_traversal);
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
Reference in New Issue
Block a user