Files

710 lines
20 KiB
C

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