449 lines
16 KiB
C
449 lines
16 KiB
C
|
|
/*
|
||
|
|
* String wrappers -- src/string.c, TODO.md section 3.1.
|
||
|
|
*
|
||
|
|
* The two contracts worth testing hardest are the ones that differ from libc:
|
||
|
|
* every copying function takes the destination size and treats truncation as an
|
||
|
|
* error that writes nothing, and every searching function treats "not found" as
|
||
|
|
* a successful answer of NULL.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include "aksl_capture.h"
|
||
|
|
|
||
|
|
#include <errno.h>
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Length */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
static int test_strlen_and_strnlen(void)
|
||
|
|
{
|
||
|
|
size_t n = 99;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strlen("hello", &n));
|
||
|
|
AKSL_CHECK(n == 5);
|
||
|
|
AKSL_CHECK_OK(aksl_strlen("", &n));
|
||
|
|
AKSL_CHECK(n == 0);
|
||
|
|
|
||
|
|
/* strnlen stops at maxlen whether or not it found a terminator. */
|
||
|
|
AKSL_CHECK_OK(aksl_strnlen("hello", 3, &n));
|
||
|
|
AKSL_CHECK(n == 3);
|
||
|
|
AKSL_CHECK_OK(aksl_strnlen("hello", 99, &n));
|
||
|
|
AKSL_CHECK(n == 5);
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS(aksl_strlen(NULL, &n), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strlen("x", NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strnlen(NULL, 1, &n), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strnlen("x", 1, NULL), AKERR_NULLPOINTER);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Copying */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
static int test_strcpy_copies_within_the_buffer(void)
|
||
|
|
{
|
||
|
|
char buf[8];
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strcpy(buf, sizeof(buf), "abc"));
|
||
|
|
AKSL_CHECK(strcmp(buf, "abc") == 0);
|
||
|
|
|
||
|
|
/* Exactly filling the buffer, terminator included, is not truncation. */
|
||
|
|
AKSL_CHECK_OK(aksl_strcpy(buf, sizeof(buf), "1234567"));
|
||
|
|
AKSL_CHECK(strcmp(buf, "1234567") == 0);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The whole point of taking dstsize. akbasic writes this check by hand at ten
|
||
|
|
* sites: a length test, then strncpy, then an explicit NUL.
|
||
|
|
*/
|
||
|
|
static int test_strcpy_truncation_is_an_error_and_writes_nothing(void)
|
||
|
|
{
|
||
|
|
char buf[8] = "previous";
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_strcpy(buf, sizeof(buf), "12345678"),
|
||
|
|
AKERR_OUTOFBOUNDS, "destination holds");
|
||
|
|
/*
|
||
|
|
* Empty, not a truncated prefix. A caller who ignores the status gets
|
||
|
|
* nothing, which is far easier to notice than a plausible-looking "1234567".
|
||
|
|
*/
|
||
|
|
AKSL_CHECK(buf[0] == '\0');
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* strncpy(3) leaves the destination unterminated when the source is at least n
|
||
|
|
* bytes, and NUL-pads the whole remainder when it is shorter. This does neither.
|
||
|
|
*/
|
||
|
|
static int test_strncpy_always_terminates_and_never_pads(void)
|
||
|
|
{
|
||
|
|
char buf[16];
|
||
|
|
|
||
|
|
memset(buf, 'Z', sizeof(buf));
|
||
|
|
AKSL_CHECK_OK(aksl_strncpy(buf, sizeof(buf), "abcdef", 3));
|
||
|
|
AKSL_CHECK(strcmp(buf, "abc") == 0);
|
||
|
|
/* Not padded: everything past the terminator is untouched. */
|
||
|
|
AKSL_CHECK(buf[4] == 'Z');
|
||
|
|
|
||
|
|
/* n larger than the source just copies the source. */
|
||
|
|
AKSL_CHECK_OK(aksl_strncpy(buf, sizeof(buf), "abc", 99));
|
||
|
|
AKSL_CHECK(strcmp(buf, "abc") == 0);
|
||
|
|
|
||
|
|
/* n bytes that do not fit the destination is still an error. */
|
||
|
|
AKSL_CHECK_STATUS(aksl_strncpy(buf, 4, "abcdef", 6), AKERR_OUTOFBOUNDS);
|
||
|
|
AKSL_CHECK(buf[0] == '\0');
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int test_strcat_appends_within_the_buffer(void)
|
||
|
|
{
|
||
|
|
char buf[16] = "ab";
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strcat(buf, sizeof(buf), "cd"));
|
||
|
|
AKSL_CHECK(strcmp(buf, "abcd") == 0);
|
||
|
|
AKSL_CHECK_OK(aksl_strcat(buf, sizeof(buf), ""));
|
||
|
|
AKSL_CHECK(strcmp(buf, "abcd") == 0);
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_strcat(buf, sizeof(buf), "0123456789ab"),
|
||
|
|
AKERR_OUTOFBOUNDS, "in use plus");
|
||
|
|
/* The existing contents survive a refused append. */
|
||
|
|
AKSL_CHECK(strcmp(buf, "abcd") == 0);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* An unterminated destination is refused rather than walked off the end of. */
|
||
|
|
static int test_strcat_refuses_an_unterminated_destination(void)
|
||
|
|
{
|
||
|
|
char buf[4];
|
||
|
|
|
||
|
|
memset(buf, 'x', sizeof(buf));
|
||
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_strcat(buf, sizeof(buf), "y"),
|
||
|
|
AKERR_VALUE, "not terminated");
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int test_strncat_appends_at_most_n(void)
|
||
|
|
{
|
||
|
|
char buf[16] = "ab";
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strncat(buf, sizeof(buf), "cdef", 2));
|
||
|
|
AKSL_CHECK(strcmp(buf, "abcd") == 0);
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS(aksl_strncat(buf, 6, "xyz", 3), AKERR_OUTOFBOUNDS);
|
||
|
|
AKSL_CHECK(strcmp(buf, "abcd") == 0);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int test_copying_rejects_null_and_zero_size(void)
|
||
|
|
{
|
||
|
|
char buf[8] = "";
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcpy(NULL, 8, "x"), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcpy(buf, sizeof(buf), NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcpy(buf, 0, "x"), AKERR_VALUE);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strncpy(NULL, 8, "x", 1), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strncpy(buf, sizeof(buf), NULL, 1), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strncpy(buf, 0, "x", 1), AKERR_VALUE);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcat(NULL, 8, "x"), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcat(buf, sizeof(buf), NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcat(buf, 0, "x"), AKERR_VALUE);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strncat(NULL, 8, "x", 1), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strncat(buf, sizeof(buf), NULL, 1), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strncat(buf, 0, "x", 1), AKERR_VALUE);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Duplication */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
static int test_strdup_and_strndup(void)
|
||
|
|
{
|
||
|
|
char *copy = NULL;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strdup("duplicate me", ©));
|
||
|
|
AKSL_CHECK(copy != NULL);
|
||
|
|
AKSL_CHECK(strcmp(copy, "duplicate me") == 0);
|
||
|
|
AKSL_CHECK_OK(aksl_freep((void **)©));
|
||
|
|
AKSL_CHECK(copy == NULL);
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strndup("duplicate me", 9, ©));
|
||
|
|
AKSL_CHECK(strcmp(copy, "duplicate") == 0);
|
||
|
|
AKSL_CHECK_OK(aksl_freep((void **)©));
|
||
|
|
|
||
|
|
/* n past the end of the string just copies the string. */
|
||
|
|
AKSL_CHECK_OK(aksl_strndup("ab", 99, ©));
|
||
|
|
AKSL_CHECK(strcmp(copy, "ab") == 0);
|
||
|
|
AKSL_CHECK_OK(aksl_freep((void **)©));
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS(aksl_strdup(NULL, ©), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strdup("x", NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strndup(NULL, 1, ©), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strndup("x", 1, NULL), AKERR_NULLPOINTER);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Comparison */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
static int test_comparisons(void)
|
||
|
|
{
|
||
|
|
int r = 99;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strcmp("abc", "abc", &r));
|
||
|
|
AKSL_CHECK(r == 0);
|
||
|
|
AKSL_CHECK_OK(aksl_strcmp("abc", "abd", &r));
|
||
|
|
AKSL_CHECK(r < 0);
|
||
|
|
AKSL_CHECK_OK(aksl_strcmp("abd", "abc", &r));
|
||
|
|
AKSL_CHECK(r > 0);
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strncmp("abcXX", "abcYY", 3, &r));
|
||
|
|
AKSL_CHECK(r == 0);
|
||
|
|
AKSL_CHECK_OK(aksl_strncmp("abcXX", "abcYY", 4, &r));
|
||
|
|
AKSL_CHECK(r != 0);
|
||
|
|
|
||
|
|
/* The three case-folding sites akbasic writes out by hand. */
|
||
|
|
AKSL_CHECK_OK(aksl_strcasecmp("PRINT", "print", &r));
|
||
|
|
AKSL_CHECK(r == 0);
|
||
|
|
AKSL_CHECK_OK(aksl_strcasecmp("PRINT", "input", &r));
|
||
|
|
AKSL_CHECK(r != 0);
|
||
|
|
AKSL_CHECK_OK(aksl_strncasecmp("PRINTXX", "printYY", 5, &r));
|
||
|
|
AKSL_CHECK(r == 0);
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strcoll("abc", "abc", &r));
|
||
|
|
AKSL_CHECK(r == 0);
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcmp(NULL, "b", &r), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcmp("a", NULL, &r), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcmp("a", "b", NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strncmp(NULL, "b", 1, &r), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strncmp("a", NULL, 1, &r), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strncmp("a", "b", 1, NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcasecmp(NULL, "b", &r), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcasecmp("a", NULL, &r), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcasecmp("a", "b", NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strncasecmp(NULL, "b", 1, &r), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strncasecmp("a", NULL, 1, &r), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strncasecmp("a", "b", 1, NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcoll(NULL, "b", &r), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcoll("a", NULL, &r), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcoll("a", "b", NULL), AKERR_NULLPOINTER);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Searching */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Every one of these treats absence as an answer. A library that raised on it
|
||
|
|
* would have every caller catching a non-error, and burning a pool slot to do
|
||
|
|
* so.
|
||
|
|
*/
|
||
|
|
static int test_searching_reports_absence_as_success(void)
|
||
|
|
{
|
||
|
|
const char *s = "the quick brown fox";
|
||
|
|
char *at = (char *)0x1;
|
||
|
|
size_t n = 99;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strchr(s, 'q', &at));
|
||
|
|
AKSL_CHECK(at == s + 4);
|
||
|
|
AKSL_CHECK_OK(aksl_strchr(s, 'Z', &at));
|
||
|
|
AKSL_CHECK(at == NULL);
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strrchr(s, 'o', &at));
|
||
|
|
AKSL_CHECK(at == s + 17);
|
||
|
|
AKSL_CHECK_OK(aksl_strrchr(s, 'Z', &at));
|
||
|
|
AKSL_CHECK(at == NULL);
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strstr(s, "brown", &at));
|
||
|
|
AKSL_CHECK(at == s + 10);
|
||
|
|
AKSL_CHECK_OK(aksl_strstr(s, "purple", &at));
|
||
|
|
AKSL_CHECK(at == NULL);
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strpbrk(s, "xq", &at));
|
||
|
|
AKSL_CHECK(at == s + 4);
|
||
|
|
AKSL_CHECK_OK(aksl_strpbrk(s, "ZY", &at));
|
||
|
|
AKSL_CHECK(at == NULL);
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strspn("aaabbb", "a", &n));
|
||
|
|
AKSL_CHECK(n == 3);
|
||
|
|
AKSL_CHECK_OK(aksl_strcspn("aaabbb", "b", &n));
|
||
|
|
AKSL_CHECK(n == 3);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* The open-coded case-insensitive search, including its edge cases. */
|
||
|
|
static int test_strcasestr(void)
|
||
|
|
{
|
||
|
|
const char *s = "The Quick Brown Fox";
|
||
|
|
char *at = (char *)0x1;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strcasestr(s, "quick", &at));
|
||
|
|
AKSL_CHECK(at == s + 4);
|
||
|
|
AKSL_CHECK_OK(aksl_strcasestr(s, "QUICK", &at));
|
||
|
|
AKSL_CHECK(at == s + 4);
|
||
|
|
AKSL_CHECK_OK(aksl_strcasestr(s, "purple", &at));
|
||
|
|
AKSL_CHECK(at == NULL);
|
||
|
|
|
||
|
|
/* An empty needle matches at the start, as strstr(3) has it. */
|
||
|
|
AKSL_CHECK_OK(aksl_strcasestr(s, "", &at));
|
||
|
|
AKSL_CHECK(at == s);
|
||
|
|
|
||
|
|
/* A needle longer than the haystack cannot match, and must not read past. */
|
||
|
|
AKSL_CHECK_OK(aksl_strcasestr("ab", "abcdef", &at));
|
||
|
|
AKSL_CHECK(at == NULL);
|
||
|
|
|
||
|
|
/* A match right at the end. */
|
||
|
|
AKSL_CHECK_OK(aksl_strcasestr(s, "fox", &at));
|
||
|
|
AKSL_CHECK(at == s + 16);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int test_searching_rejects_null_arguments(void)
|
||
|
|
{
|
||
|
|
char *at = NULL;
|
||
|
|
size_t n = 0;
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS(aksl_strchr(NULL, 'a', &at), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strchr("a", 'a', NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strrchr(NULL, 'a', &at), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strrchr("a", 'a', NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strstr(NULL, "a", &at), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strstr("a", NULL, &at), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strstr("a", "a", NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcasestr(NULL, "a", &at), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcasestr("a", NULL, &at), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcasestr("a", "a", NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strpbrk(NULL, "a", &at), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strpbrk("a", NULL, &at), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strpbrk("a", "a", NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strspn(NULL, "a", &n), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strspn("a", NULL, &n), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strspn("a", "a", NULL), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcspn(NULL, "a", &n), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcspn("a", NULL, &n), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strcspn("a", "a", NULL), AKERR_NULLPOINTER);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Tokenising */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
static int test_strtok_r_walks_the_tokens(void)
|
||
|
|
{
|
||
|
|
char input[] = "one,two,,three";
|
||
|
|
char *save = NULL;
|
||
|
|
char *tok = NULL;
|
||
|
|
int seen = 0;
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strtok_r(input, ",", &save, &tok));
|
||
|
|
AKSL_CHECK(tok != NULL && strcmp(tok, "one") == 0);
|
||
|
|
seen++;
|
||
|
|
while ( 1 ) {
|
||
|
|
AKSL_CHECK_OK(aksl_strtok_r(NULL, ",", &save, &tok));
|
||
|
|
if ( tok == NULL ) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
seen++;
|
||
|
|
}
|
||
|
|
/* strtok_r collapses the empty field between the two commas. */
|
||
|
|
AKSL_CHECK(seen == 3);
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtok_r(NULL, NULL, &save, &tok), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtok_r(NULL, ",", NULL, &tok), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strtok_r(NULL, ",", &save, NULL), AKERR_NULLPOINTER);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* strsep keeps the empty fields, which is why it exists alongside strtok_r. */
|
||
|
|
static int test_strsep_keeps_empty_fields(void)
|
||
|
|
{
|
||
|
|
char input[] = "one,two,,three";
|
||
|
|
char *cursor = input;
|
||
|
|
char *tok = NULL;
|
||
|
|
int seen = 0;
|
||
|
|
|
||
|
|
while ( cursor != NULL ) {
|
||
|
|
AKSL_CHECK_OK(aksl_strsep(&cursor, ",", &tok));
|
||
|
|
if ( tok == NULL ) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
seen++;
|
||
|
|
}
|
||
|
|
AKSL_CHECK(seen == 4);
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS(aksl_strsep(NULL, ",", &tok), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strsep(&cursor, NULL, &tok), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strsep(&cursor, ",", NULL), AKERR_NULLPOINTER);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Status messages */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
/*
|
||
|
|
* aksl_strerror knows this library's own statuses as well as errno values,
|
||
|
|
* which is the reason it is not a strerror_r wrapper: strerror_r could never
|
||
|
|
* name AKERR_NULLPOINTER.
|
||
|
|
*/
|
||
|
|
static int test_strerror_names_both_kinds_of_status(void)
|
||
|
|
{
|
||
|
|
char buf[128];
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strerror(ENOENT, buf, sizeof(buf)));
|
||
|
|
AKSL_CHECK(buf[0] != '\0');
|
||
|
|
AKSL_CHECK(strcmp(buf, "Unknown status 2") != 0);
|
||
|
|
|
||
|
|
AKSL_CHECK_OK(aksl_strerror(AKERR_NULLPOINTER, buf, sizeof(buf)));
|
||
|
|
AKSL_CHECK(buf[0] != '\0');
|
||
|
|
|
||
|
|
/* A status nothing has a name for is rendered as its number. */
|
||
|
|
AKSL_CHECK_OK(aksl_strerror(-98765, buf, sizeof(buf)));
|
||
|
|
AKSL_CHECK(strcmp(buf, "Unknown status -98765") == 0);
|
||
|
|
|
||
|
|
/* Too small a buffer is an error, and leaves nothing rather than a prefix. */
|
||
|
|
AKSL_CHECK_STATUS(aksl_strerror(ENOENT, buf, 2), AKERR_OUTOFBOUNDS);
|
||
|
|
AKSL_CHECK(buf[0] == '\0');
|
||
|
|
|
||
|
|
AKSL_CHECK_STATUS(aksl_strerror(ENOENT, NULL, 16), AKERR_NULLPOINTER);
|
||
|
|
AKSL_CHECK_STATUS(aksl_strerror(ENOENT, buf, 0), AKERR_VALUE);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
int main(void)
|
||
|
|
{
|
||
|
|
int failures = 0;
|
||
|
|
|
||
|
|
akerr_init();
|
||
|
|
|
||
|
|
AKSL_RUN(failures, test_strlen_and_strnlen);
|
||
|
|
|
||
|
|
AKSL_RUN(failures, test_strcpy_copies_within_the_buffer);
|
||
|
|
AKSL_RUN(failures, test_strcpy_truncation_is_an_error_and_writes_nothing);
|
||
|
|
AKSL_RUN(failures, test_strncpy_always_terminates_and_never_pads);
|
||
|
|
AKSL_RUN(failures, test_strcat_appends_within_the_buffer);
|
||
|
|
AKSL_RUN(failures, test_strcat_refuses_an_unterminated_destination);
|
||
|
|
AKSL_RUN(failures, test_strncat_appends_at_most_n);
|
||
|
|
AKSL_RUN(failures, test_copying_rejects_null_and_zero_size);
|
||
|
|
|
||
|
|
AKSL_RUN(failures, test_strdup_and_strndup);
|
||
|
|
|
||
|
|
AKSL_RUN(failures, test_comparisons);
|
||
|
|
|
||
|
|
AKSL_RUN(failures, test_searching_reports_absence_as_success);
|
||
|
|
AKSL_RUN(failures, test_strcasestr);
|
||
|
|
AKSL_RUN(failures, test_searching_rejects_null_arguments);
|
||
|
|
|
||
|
|
AKSL_RUN(failures, test_strtok_r_walks_the_tokens);
|
||
|
|
AKSL_RUN(failures, test_strsep_keeps_empty_fields);
|
||
|
|
|
||
|
|
AKSL_RUN(failures, test_strerror_names_both_kinds_of_status);
|
||
|
|
|
||
|
|
AKSL_REPORT(failures);
|
||
|
|
}
|