feat(string): complete string.h + strdup/memccpy
This commit is contained in:
@@ -0,0 +1,709 @@
|
||||
/*
|
||||
* vlibc — string.h completion test (todo 8).
|
||||
*
|
||||
* Covers the new string search/memory functions end to end:
|
||||
*
|
||||
* 1. copy family: strcpy/strncpy (NUL padding), strcat/strncat (always
|
||||
* NUL-terminated), L2 stpcpy/stpncpy (return the NUL position);
|
||||
* 2. search family: strchr/strrchr (including the NUL as a match),
|
||||
* strspn/strcspn/strpbrk, strstr (empty/longer/overlapping needles);
|
||||
* 3. tokenizer: strtok/strtok_r — empty source yields NULL, consecutive
|
||||
* delimiters produce NO empty tokens, an all-delimiter string yields
|
||||
* NULL on the first call, and the delimiter string is never modified;
|
||||
* 4. collation: strcoll == strcmp and strxfrm (identity transform,
|
||||
* returns strlen, truncates to n-1 bytes + NUL) — "C" locale only;
|
||||
* L2 strcoll_l/strxfrm_l ignore the locale argument;
|
||||
* 5. memory: memchr (incl. negative c), memcmp ordering, L2 memccpy;
|
||||
* 6. misc: strnlen (stops at maxlen), strncmp (stops at NUL), strdup/
|
||||
* strndup round-trips, strsignal (distinct non-null strings; unknown
|
||||
* numbers format as "Unknown signal <N>"; signal 0 is asserted
|
||||
* distinct + non-null only, per POSIX).
|
||||
*
|
||||
* The expected values were captured against the host glibc as the
|
||||
* ground-truth oracle for the corpus (see the task-8 evidence log); the
|
||||
* assertions below encode those golden results inline. Two strxfrm cases
|
||||
* intentionally assert VLIBc's pinned contract (n-1 bytes + NUL, return
|
||||
* strlen) rather than glibc's: POSIX leaves the dst contents UNSPECIFIED
|
||||
* when the return value is >= n.
|
||||
*
|
||||
* strdup/strndup allocate through the internal __libc_malloc seam
|
||||
* (src/internal/malloc.h); under make check the real allocator (todo 7)
|
||||
* provides it. For the STANDALONE verification of this todo the test links
|
||||
* a throwaway /tmp allocator stub (never committed) — see the evidence log.
|
||||
*
|
||||
* With `-p`, a representative sample of results is printed to stdout for
|
||||
* eyeball diffing; the assertions always run.
|
||||
*
|
||||
* No host libc headers are included (the -Iinclude path would shadow GCC's
|
||||
* internal headers); all output goes through raw SYS_write via
|
||||
* <vlibc/internal/test.h>. Not yet wired into make check (a later todo).
|
||||
*
|
||||
* Standalone build:
|
||||
* gcc -Iinclude -DVLIBC_LEVEL=2 -std=c23 -Wall -Wextra -pedantic \
|
||||
* -o /tmp/t8 tests/test_string.c \
|
||||
* src/string/{strcpy,strncpy,strcat,strchr,strspn,strstr,strtok,strcoll,strdup,strnlen,strncmp,memchr,memcmp,strsignal,stpcpy,memccpy}.c \
|
||||
* src/string/{strlen,strcmp}.c /tmp/allocstub.c \
|
||||
* src/internal/errno.c src/internal/syscall_ret.c
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <vlibc/internal/test.h>
|
||||
|
||||
#include "../src/internal/malloc.h"
|
||||
|
||||
/* ---- shared helpers ---- */
|
||||
|
||||
/*
|
||||
* Collect up to cap tokens from a fresh copy of s using strtok; the copy is
|
||||
* consumed (delimiters become NUL), matching POSIX strtok semantics.
|
||||
*/
|
||||
static int
|
||||
tok_collect(char *s, const char *sep, char *out[], int cap)
|
||||
{
|
||||
char *t;
|
||||
int n = 0;
|
||||
|
||||
t = strtok(s, sep);
|
||||
while (t != NULL && n < cap)
|
||||
{
|
||||
out[n++] = t;
|
||||
t = strtok(NULL, sep);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Same, but through strtok_r with an explicit state. */
|
||||
static int
|
||||
tokr_collect(char *s, const char *sep, char *out[], int cap)
|
||||
{
|
||||
char *t;
|
||||
char *state = NULL;
|
||||
int n = 0;
|
||||
|
||||
t = strtok_r(s, sep, &state);
|
||||
while (t != NULL && n < cap)
|
||||
{
|
||||
out[n++] = t;
|
||||
t = strtok_r(NULL, sep, &state);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/*
|
||||
* GCC's -Wstringop-truncation/-Wstringop-overread fire when the source
|
||||
* length is statically visible at a bounded-copy call site. The truncation
|
||||
* and oversized-n corpus cases below are exactly what those diagnostics
|
||||
* guard against — intentionally — so they go through noipa wrappers
|
||||
* (noipa, not just noinline: GCC's constprop clones ignore noinline at
|
||||
* -O2 and re-expose the call sites).
|
||||
*/
|
||||
static __attribute__((noipa)) char *
|
||||
strncpy_opaque(char *dst, const char *src, size_t n)
|
||||
{
|
||||
return strncpy(dst, src, n);
|
||||
}
|
||||
|
||||
static __attribute__((noipa)) char *
|
||||
strncat_opaque(char *dst, const char *src, size_t n)
|
||||
{
|
||||
return strncat(dst, src, n);
|
||||
}
|
||||
|
||||
static __attribute__((noipa)) char *
|
||||
strndup_opaque(const char *s, size_t n)
|
||||
{
|
||||
return strndup(s, n);
|
||||
}
|
||||
|
||||
/* ---- test functions (one per checked behavior) ---- */
|
||||
|
||||
static int
|
||||
test_strcpy(void)
|
||||
{
|
||||
char buf[16];
|
||||
|
||||
TEST_ASSERT_TRUE(strcpy(buf, "hello") == buf);
|
||||
TEST_ASSERT_STREQ(buf, "hello");
|
||||
TEST_ASSERT_TRUE(strcpy(buf, "") == buf);
|
||||
TEST_ASSERT_STREQ(buf, "");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strncpy(void)
|
||||
{
|
||||
char buf[8];
|
||||
|
||||
/* Shorter src: NUL-padded to n. */
|
||||
memset(buf, 0x41, sizeof buf);
|
||||
TEST_ASSERT_TRUE(strncpy(buf, "hi", 5) == buf);
|
||||
TEST_ASSERT_STREQ(buf, "hi");
|
||||
TEST_ASSERT_EQ(buf[2], '\0');
|
||||
TEST_ASSERT_EQ(buf[3], '\0');
|
||||
TEST_ASSERT_EQ(buf[4], '\0');
|
||||
TEST_ASSERT_EQ(buf[5], 'A'); /* untouched past n */
|
||||
|
||||
/* Src n bytes or longer: no NUL written within n. */
|
||||
memset(buf, 0x41, sizeof buf);
|
||||
TEST_ASSERT_TRUE(strncpy_opaque(buf, "hello", 3) == buf);
|
||||
TEST_ASSERT_EQ(buf[0], 'h');
|
||||
TEST_ASSERT_EQ(buf[1], 'e');
|
||||
TEST_ASSERT_EQ(buf[2], 'l');
|
||||
TEST_ASSERT_EQ(buf[3], 'A'); /* not NUL-terminated */
|
||||
|
||||
/* n == 0: nothing written. */
|
||||
memset(buf, 0x41, sizeof buf);
|
||||
strncpy_opaque(buf, "hello", 0);
|
||||
TEST_ASSERT_EQ(buf[0], 'A');
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strcat(void)
|
||||
{
|
||||
char buf[16];
|
||||
|
||||
strcpy(buf, "hello");
|
||||
TEST_ASSERT_TRUE(strcat(buf, " world") == buf);
|
||||
TEST_ASSERT_STREQ(buf, "hello world");
|
||||
strcpy(buf, "");
|
||||
strcat(buf, "x");
|
||||
TEST_ASSERT_STREQ(buf, "x");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strncat(void)
|
||||
{
|
||||
char buf[16];
|
||||
|
||||
strcpy(buf, "abc");
|
||||
TEST_ASSERT_TRUE(strncat_opaque(buf, "defgh", 3) == buf);
|
||||
TEST_ASSERT_STREQ(buf, "abcdef");
|
||||
|
||||
strcpy(buf, "abc");
|
||||
strncat(buf, "defgh", 10);
|
||||
TEST_ASSERT_STREQ(buf, "abcdefgh");
|
||||
|
||||
strcpy(buf, "abc");
|
||||
strncat_opaque(buf, "xyz", 0);
|
||||
TEST_ASSERT_STREQ(buf, "abc");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strchr(void)
|
||||
{
|
||||
const char *s = "hello";
|
||||
|
||||
TEST_ASSERT_EQ(strchr(s, 'l') - s, 2);
|
||||
TEST_ASSERT_EQ(strchr(s, 'h') - s, 0);
|
||||
TEST_ASSERT_NULL(strchr(s, 'z'));
|
||||
TEST_ASSERT_EQ(strchr(s, '\0') - s, 5);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strrchr(void)
|
||||
{
|
||||
const char *s = "hello";
|
||||
|
||||
TEST_ASSERT_EQ(strrchr(s, 'l') - s, 3);
|
||||
TEST_ASSERT_EQ(strrchr(s, 'h') - s, 0);
|
||||
TEST_ASSERT_NULL(strrchr(s, 'z'));
|
||||
TEST_ASSERT_EQ(strrchr(s, '\0') - s, 5);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strspn(void)
|
||||
{
|
||||
TEST_ASSERT_EQ(strspn("abcba", "abc"), 5);
|
||||
TEST_ASSERT_EQ(strspn("abcba", "ab"), 2);
|
||||
TEST_ASSERT_EQ(strspn("abcba", "z"), 0);
|
||||
TEST_ASSERT_EQ(strspn("", "ab"), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strcspn(void)
|
||||
{
|
||||
TEST_ASSERT_EQ(strcspn("abcba", "b"), 1);
|
||||
TEST_ASSERT_EQ(strcspn("abcba", "z"), 5);
|
||||
TEST_ASSERT_EQ(strcspn("abcba", "a"), 0);
|
||||
TEST_ASSERT_EQ(strcspn("", "ab"), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strpbrk(void)
|
||||
{
|
||||
const char *s = "hello";
|
||||
|
||||
TEST_ASSERT_EQ(strpbrk(s, "aeiou") - s, 1);
|
||||
TEST_ASSERT_EQ(strpbrk(s, "h") - s, 0);
|
||||
TEST_ASSERT_NULL(strpbrk(s, "xyz"));
|
||||
TEST_ASSERT_NULL(strpbrk("", "x"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strstr(void)
|
||||
{
|
||||
const char *h = "hello world";
|
||||
|
||||
TEST_ASSERT_EQ(strstr(h, "world") - h, 6);
|
||||
TEST_ASSERT_EQ(strstr(h, "hello") - h, 0);
|
||||
TEST_ASSERT_EQ(strstr(h, "o") - h, 4);
|
||||
TEST_ASSERT_NULL(strstr(h, "helloo"));
|
||||
TEST_ASSERT_NULL(strstr("hi", "hello")); /* needle longer than haystack */
|
||||
TEST_ASSERT_TRUE(strstr(h, "") == h); /* empty needle matches haystack */
|
||||
TEST_ASSERT_EQ(strstr("aaaa", "aaa") - "aaaa", 0); /* overlapping */
|
||||
TEST_ASSERT_EQ(strstr("banana", "nan") - "banana", 2);
|
||||
TEST_ASSERT_EQ(strstr("abc", "bc") - "abc", 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strtok(void)
|
||||
{
|
||||
char buf[32];
|
||||
char *out[8];
|
||||
int n;
|
||||
|
||||
/* Empty source: first call yields NULL (no tokens). */
|
||||
strcpy(buf, "");
|
||||
TEST_ASSERT_NULL(strtok(buf, " "));
|
||||
|
||||
/* Consecutive delimiters produce NO empty tokens. */
|
||||
strcpy(buf, "a,,b");
|
||||
n = tok_collect(buf, ",", out, 8);
|
||||
TEST_ASSERT_EQ(n, 2);
|
||||
TEST_ASSERT_STREQ(out[0], "a");
|
||||
TEST_ASSERT_STREQ(out[1], "b");
|
||||
|
||||
/* All delimiters: first call yields NULL. */
|
||||
strcpy(buf, ",,,");
|
||||
TEST_ASSERT_NULL(strtok(buf, ","));
|
||||
|
||||
/* Leading + trailing delimiters are skipped. */
|
||||
strcpy(buf, "xxhelloxx");
|
||||
n = tok_collect(buf, "x", out, 8);
|
||||
TEST_ASSERT_EQ(n, 1);
|
||||
TEST_ASSERT_STREQ(out[0], "hello");
|
||||
|
||||
/* No delimiter present: the whole string is one token. */
|
||||
strcpy(buf, "hello");
|
||||
n = tok_collect(buf, " ", out, 8);
|
||||
TEST_ASSERT_EQ(n, 1);
|
||||
TEST_ASSERT_STREQ(out[0], "hello");
|
||||
|
||||
/* Multiple whitespace runs collapse to the same tokens. */
|
||||
strcpy(buf, "a b c");
|
||||
n = tok_collect(buf, " ", out, 8);
|
||||
TEST_ASSERT_EQ(n, 3);
|
||||
TEST_ASSERT_STREQ(out[0], "a");
|
||||
TEST_ASSERT_STREQ(out[1], "b");
|
||||
TEST_ASSERT_STREQ(out[2], "c");
|
||||
|
||||
/* The delimiter string is never modified. */
|
||||
{
|
||||
static char sep[] = ",;";
|
||||
char *sep_before = sep;
|
||||
|
||||
strcpy(buf, "a,b;c");
|
||||
(void)tok_collect(buf, sep, out, 8);
|
||||
TEST_ASSERT_TRUE(sep == sep_before);
|
||||
TEST_ASSERT_EQ(sep[0], ',');
|
||||
TEST_ASSERT_EQ(sep[1], ';');
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strtok_r(void)
|
||||
{
|
||||
char buf[32];
|
||||
char *out[8];
|
||||
int n;
|
||||
|
||||
strcpy(buf, "a,,b");
|
||||
n = tokr_collect(buf, ",", out, 8);
|
||||
TEST_ASSERT_EQ(n, 2);
|
||||
TEST_ASSERT_STREQ(out[0], "a");
|
||||
TEST_ASSERT_STREQ(out[1], "b");
|
||||
|
||||
strcpy(buf, "");
|
||||
TEST_ASSERT_NULL(strtok_r(buf, " ", &(char *){NULL}));
|
||||
|
||||
/* Two interleaved scans keep independent state. */
|
||||
{
|
||||
char s1[16];
|
||||
char s2[16];
|
||||
char *state1 = NULL;
|
||||
char *state2 = NULL;
|
||||
char *t1;
|
||||
char *t2;
|
||||
|
||||
strcpy(s1, "one two");
|
||||
strcpy(s2, "red green blue");
|
||||
|
||||
t1 = strtok_r(s1, " ", &state1);
|
||||
t2 = strtok_r(s2, " ", &state2);
|
||||
TEST_ASSERT_STREQ(t1, "one");
|
||||
TEST_ASSERT_STREQ(t2, "red");
|
||||
t1 = strtok_r(NULL, " ", &state1);
|
||||
TEST_ASSERT_STREQ(t1, "two");
|
||||
t2 = strtok_r(NULL, " ", &state2);
|
||||
TEST_ASSERT_STREQ(t2, "green");
|
||||
t1 = strtok_r(NULL, " ", &state1);
|
||||
TEST_ASSERT_NULL(t1);
|
||||
t2 = strtok_r(NULL, " ", &state2);
|
||||
TEST_ASSERT_STREQ(t2, "blue");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strcoll(void)
|
||||
{
|
||||
TEST_ASSERT_EQ(strcoll("apple", "banana"), strcmp("apple", "banana"));
|
||||
TEST_ASSERT_EQ(strcoll("apple", "banana") < 0, 1);
|
||||
TEST_ASSERT_EQ(strcoll("b", "a") > 0, 1);
|
||||
TEST_ASSERT_EQ(strcoll("same", "same"), 0);
|
||||
TEST_ASSERT_EQ(strcoll("A", "a"), strcmp("A", "a"));
|
||||
TEST_ASSERT_EQ(strcoll("", ""), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strxfrm(void)
|
||||
{
|
||||
char xf[16];
|
||||
|
||||
/* Identity transform, returns strlen(src). */
|
||||
TEST_ASSERT_EQ(strxfrm(xf, "hello", sizeof xf), 5);
|
||||
TEST_ASSERT_STREQ(xf, "hello");
|
||||
|
||||
/* Truncation: at most n-1 bytes + NUL, return still 5. */
|
||||
TEST_ASSERT_EQ(strxfrm(xf, "hello", 4), 5);
|
||||
TEST_ASSERT_STREQ(xf, "hel");
|
||||
|
||||
/* n == 0: nothing written, return still 5. */
|
||||
memset(xf, 0x41, sizeof xf);
|
||||
TEST_ASSERT_EQ(strxfrm(xf, "hello", 0), 5);
|
||||
TEST_ASSERT_EQ(xf[0], 'A');
|
||||
|
||||
/* n == 1: only the NUL. */
|
||||
TEST_ASSERT_EQ(strxfrm(xf, "hello", 1), 5);
|
||||
TEST_ASSERT_EQ(xf[0], '\0');
|
||||
|
||||
TEST_ASSERT_EQ(strxfrm(xf, "", sizeof xf), 0);
|
||||
TEST_ASSERT_STREQ(xf, "");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strdup(void)
|
||||
{
|
||||
char src[] = "hello";
|
||||
char *p = strdup(src);
|
||||
|
||||
TEST_ASSERT_TRUE(p != NULL);
|
||||
TEST_ASSERT_STREQ(p, "hello");
|
||||
TEST_ASSERT_TRUE(p != src); /* a real copy, not the source array */
|
||||
__libc_free(p);
|
||||
|
||||
p = strdup("");
|
||||
TEST_ASSERT_TRUE(p != NULL);
|
||||
TEST_ASSERT_STREQ(p, "");
|
||||
__libc_free(p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strndup(void)
|
||||
{
|
||||
char *p = strndup("abcdef", 3);
|
||||
|
||||
TEST_ASSERT_TRUE(p != NULL);
|
||||
TEST_ASSERT_STREQ(p, "abc");
|
||||
__libc_free(p);
|
||||
|
||||
p = strndup_opaque("ab", 10); /* n beyond the NUL: whole string */
|
||||
TEST_ASSERT_TRUE(p != NULL);
|
||||
TEST_ASSERT_STREQ(p, "ab");
|
||||
__libc_free(p);
|
||||
|
||||
p = strndup("xyz", 0); /* n == 0: empty string */
|
||||
TEST_ASSERT_TRUE(p != NULL);
|
||||
TEST_ASSERT_STREQ(p, "");
|
||||
__libc_free(p);
|
||||
|
||||
p = strndup("", 0);
|
||||
TEST_ASSERT_TRUE(p != NULL);
|
||||
TEST_ASSERT_STREQ(p, "");
|
||||
__libc_free(p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strnlen(void)
|
||||
{
|
||||
TEST_ASSERT_EQ(strnlen("abc", 2), 2);
|
||||
TEST_ASSERT_EQ(strnlen("abc", 10), 3);
|
||||
TEST_ASSERT_EQ(strnlen("abc", 0), 0);
|
||||
TEST_ASSERT_EQ(strnlen("", 5), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strncmp(void)
|
||||
{
|
||||
TEST_ASSERT_EQ(strncmp("abc", "abc", 3), 0);
|
||||
TEST_ASSERT_EQ(strncmp("ab", "abc", 2), 0);
|
||||
TEST_ASSERT_EQ(strncmp("ab", "ac", 1), 0);
|
||||
TEST_ASSERT_EQ(strncmp("abc", "abd", 3) < 0, 1);
|
||||
TEST_ASSERT_EQ(strncmp("abd", "abc", 3) > 0, 1);
|
||||
TEST_ASSERT_EQ(strncmp("x", "y", 0), 0); /* n == 0 */
|
||||
/* Stops at the first NUL even when n is larger. */
|
||||
TEST_ASSERT_EQ(strncmp("a\0b", "a\0c", 5), 0);
|
||||
TEST_ASSERT_EQ(strncmp("a\0b", "a", 5), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_memchr(void)
|
||||
{
|
||||
const char *s = "hello";
|
||||
unsigned char high[] = {0x41, 0xff, 0x00};
|
||||
|
||||
TEST_ASSERT_EQ((char *)memchr(s, 'l', 5) - s, 2);
|
||||
TEST_ASSERT_NULL(memchr(s, 'z', 5));
|
||||
TEST_ASSERT_NULL(memchr(s, 'h', 0)); /* n == 0 */
|
||||
TEST_ASSERT_EQ((char *)memchr(s, '\0', 6) - s, 5);
|
||||
/* Negative c searches for the matching high byte. */
|
||||
TEST_ASSERT_EQ((char *)memchr(high, 0xff, 3) - (char *)high, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_memcmp(void)
|
||||
{
|
||||
unsigned char hi[] = {0xff, 0x00};
|
||||
unsigned char lo[] = {0x7f, 0x00};
|
||||
|
||||
TEST_ASSERT_EQ(memcmp("abc", "abc", 3), 0);
|
||||
TEST_ASSERT_EQ(memcmp("abc", "abd", 3) < 0, 1);
|
||||
TEST_ASSERT_EQ(memcmp("abd", "abc", 3) > 0, 1);
|
||||
TEST_ASSERT_EQ(memcmp("x", "y", 0), 0); /* n == 0 */
|
||||
/* Bytes compare as unsigned char: 0xff > 0x7f. */
|
||||
TEST_ASSERT_EQ(memcmp(hi, lo, 1) > 0, 1);
|
||||
TEST_ASSERT_EQ(memcmp(lo, hi, 1) < 0, 1);
|
||||
/* Does not stop at NUL bytes. */
|
||||
TEST_ASSERT_EQ(memcmp("a\0b", "a\0c", 3) < 0, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strsignal(void)
|
||||
{
|
||||
/* Known signals: pinned texts (vlibc's own table, x86_64 numbers). */
|
||||
TEST_ASSERT_STREQ(strsignal(2), "Interrupt");
|
||||
TEST_ASSERT_STREQ(strsignal(11), "Segmentation fault");
|
||||
TEST_ASSERT_STREQ(strsignal(1), "Hangup");
|
||||
TEST_ASSERT_STREQ(strsignal(9), "Killed");
|
||||
TEST_ASSERT_STREQ(strsignal(31), "Bad system call");
|
||||
|
||||
/* Distinct strings for distinct known signals. */
|
||||
TEST_ASSERT_TRUE(strsignal(2) != strsignal(11));
|
||||
TEST_ASSERT_TRUE(strsignal(1) != strsignal(2));
|
||||
|
||||
/* Signal 0: non-null and distinct, text deliberately not pinned. */
|
||||
TEST_ASSERT_TRUE(strsignal(0) != NULL);
|
||||
TEST_ASSERT_TRUE(strsignal(0) != strsignal(1));
|
||||
TEST_ASSERT_TRUE(strsignal(0) != strsignal(2));
|
||||
|
||||
/* Unknown numbers: formatted, non-null, distinct. */
|
||||
TEST_ASSERT_STREQ(strsignal(999), "Unknown signal 999");
|
||||
TEST_ASSERT_STREQ(strsignal(32), "Unknown signal 32");
|
||||
TEST_ASSERT_TRUE(strsignal(-1) != NULL);
|
||||
TEST_ASSERT_STREQ(strsignal(-1), "Unknown signal -1");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_stpcpy_stpncpy(void)
|
||||
{
|
||||
char buf[8];
|
||||
char *end;
|
||||
|
||||
end = stpcpy(buf, "abc");
|
||||
TEST_ASSERT_EQ(end - buf, 3);
|
||||
TEST_ASSERT_STREQ(buf, "abc");
|
||||
TEST_ASSERT_EQ(*end, '\0');
|
||||
|
||||
end = stpcpy(buf, "");
|
||||
TEST_ASSERT_EQ(end - buf, 0);
|
||||
TEST_ASSERT_EQ(buf[0], '\0');
|
||||
|
||||
/* Short src: NUL-padded; return points at the first NUL. */
|
||||
end = stpncpy(buf, "abc", 8);
|
||||
TEST_ASSERT_EQ(end - buf, 3);
|
||||
TEST_ASSERT_STREQ(buf, "abc");
|
||||
|
||||
/* Src >= n: no NUL written; return is dst + n. */
|
||||
memset(buf, 'X', sizeof buf);
|
||||
end = stpncpy(buf, "abcdefgh", 4);
|
||||
TEST_ASSERT_EQ(end - buf, 4);
|
||||
TEST_ASSERT_EQ(buf[0], 'a');
|
||||
TEST_ASSERT_EQ(buf[3], 'd');
|
||||
TEST_ASSERT_EQ(buf[4], 'X');
|
||||
|
||||
/* n == 0: return dst, nothing written. */
|
||||
memset(buf, 'X', sizeof buf);
|
||||
end = stpncpy(buf, "abc", 0);
|
||||
TEST_ASSERT_EQ(end - buf, 0);
|
||||
TEST_ASSERT_EQ(buf[0], 'X');
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_memccpy(void)
|
||||
{
|
||||
unsigned char buf[16];
|
||||
void *p;
|
||||
|
||||
p = memccpy(buf, "hello world", ' ', 20);
|
||||
TEST_ASSERT_EQ((char *)p - (char *)buf, 6); /* one past the ' ' */
|
||||
TEST_ASSERT_EQ(buf[5], ' ');
|
||||
|
||||
/* c never appears within n: NULL, full copy made. */
|
||||
p = memccpy(buf, "hello", 'z', 20);
|
||||
TEST_ASSERT_NULL(p);
|
||||
TEST_ASSERT_EQ(buf[4], 'o');
|
||||
|
||||
/* c exists but beyond n: NULL. */
|
||||
p = memccpy(buf, "hello", 'o', 3);
|
||||
TEST_ASSERT_NULL(p);
|
||||
|
||||
/* n == 0: NULL, nothing written. */
|
||||
memset(buf, 'X', sizeof buf);
|
||||
p = memccpy(buf, "hello", 'h', 0);
|
||||
TEST_ASSERT_NULL(p);
|
||||
TEST_ASSERT_EQ(buf[0], 'X');
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_strcoll_l(void)
|
||||
{
|
||||
char xf[16];
|
||||
|
||||
/* The locale argument is ignored ("C" locale behavior). */
|
||||
TEST_ASSERT_EQ(strcoll_l("a", "b", NULL), strcoll("a", "b"));
|
||||
TEST_ASSERT_EQ(strcoll_l("a", "b", NULL) < 0, 1);
|
||||
TEST_ASSERT_EQ(strcoll_l("x", "x", NULL), 0);
|
||||
TEST_ASSERT_EQ(strxfrm_l(xf, "hi", sizeof xf, NULL), strxfrm(xf, "hi", sizeof xf));
|
||||
TEST_ASSERT_STREQ(xf, "hi");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- register + runner ---- */
|
||||
|
||||
static const struct vlibc_test tests[] = {
|
||||
{"strcpy", test_strcpy}, {"strncpy", test_strncpy},
|
||||
{"strcat", test_strcat}, {"strncat", test_strncat},
|
||||
{"strchr", test_strchr}, {"strrchr", test_strrchr},
|
||||
{"strspn", test_strspn}, {"strcspn", test_strcspn},
|
||||
{"strpbrk", test_strpbrk}, {"strstr", test_strstr},
|
||||
{"strtok", test_strtok}, {"strtok_r", test_strtok_r},
|
||||
{"strcoll", test_strcoll}, {"strxfrm", test_strxfrm},
|
||||
{"strdup", test_strdup}, {"strndup", test_strndup},
|
||||
{"strnlen", test_strnlen}, {"strncmp", test_strncmp},
|
||||
{"memchr", test_memchr}, {"memcmp", test_memcmp},
|
||||
{"strsignal", test_strsignal}, {"stpcpy/stpncpy", test_stpcpy_stpncpy},
|
||||
{"memccpy", test_memccpy}, {"strcoll_l/strxfrm_l", test_strcoll_l},
|
||||
};
|
||||
|
||||
/* -p mode: print a representative sample for eyeball diffing. */
|
||||
static void
|
||||
print_sample(void)
|
||||
{
|
||||
char buf[32];
|
||||
char *out[8];
|
||||
int n;
|
||||
char *t;
|
||||
|
||||
vlibc_test_say(1, "strtok(a,,b, ,)= ");
|
||||
strcpy(buf, "a,,b");
|
||||
n = tok_collect(buf, ",", out, 8);
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
vlibc_test_say(1, "[");
|
||||
vlibc_test_say(1, out[i]);
|
||||
vlibc_test_say(1, "]");
|
||||
}
|
||||
vlibc_test_say(1, "\nstrxfrm(hello,n=4)=len ");
|
||||
vlibc_test_say_dec(1, (unsigned long)strxfrm(buf, "hello", 4));
|
||||
vlibc_test_say(1, " dst=\"");
|
||||
vlibc_test_say(1, buf);
|
||||
vlibc_test_say(1, "\"\nstrsignal(2)=\"");
|
||||
vlibc_test_say(1, strsignal(2));
|
||||
vlibc_test_say(1, "\" strsignal(0)=\"");
|
||||
vlibc_test_say(1, strsignal(0));
|
||||
vlibc_test_say(1, "\" strsignal(999)=\"");
|
||||
vlibc_test_say(1, strsignal(999));
|
||||
vlibc_test_say(1, "\"\n");
|
||||
t = strdup("dup-ok");
|
||||
vlibc_test_say(1, "strdup(dup-ok)=\"");
|
||||
vlibc_test_say(1, t);
|
||||
vlibc_test_say(1, "\"\n");
|
||||
__libc_free(t);
|
||||
}
|
||||
|
||||
/*
|
||||
* Own main (not TEST_MAIN): supports the -p print mode. Everything else
|
||||
* follows the TEST_MAIN contract — same output shape, 0 on all-pass.
|
||||
*/
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
const size_t count = sizeof tests / sizeof tests[0];
|
||||
size_t passed = 0;
|
||||
size_t i;
|
||||
|
||||
if (argc > 1 && strcmp(argv[1], "-p") == 0)
|
||||
{
|
||||
print_sample();
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
int before = vlibc_test_failures;
|
||||
|
||||
vlibc_test_say(1, "RUN ");
|
||||
vlibc_test_say(1, tests[i].name);
|
||||
vlibc_test_say(1, ": ");
|
||||
if (tests[i].run() == 0 && vlibc_test_failures == before)
|
||||
{
|
||||
vlibc_test_say(1, "PASS\n");
|
||||
passed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
vlibc_test_say(1, "FAIL\n");
|
||||
}
|
||||
}
|
||||
|
||||
vlibc_test_say(1, "SUMMARY: ");
|
||||
vlibc_test_say_dec(1, (unsigned long)passed);
|
||||
vlibc_test_say(1, "/");
|
||||
vlibc_test_say_dec(1, (unsigned long)count);
|
||||
vlibc_test_say(1, " passed, ");
|
||||
vlibc_test_say_dec(1, (unsigned long)vlibc_test_failures);
|
||||
vlibc_test_say(1, " assertion failure(s)\n");
|
||||
|
||||
return passed == count ? 0 : 1;
|
||||
}
|
||||
Reference in New Issue
Block a user