/* * 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 /* ---------------------------------------------------------------------- */ /* 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); }