/* * vlibc — strings.h legacy functions test (todo 10). * * Covers the whole surface end to end: * * 1. strcasecmp: ASCII case-insensitive compare ("AbC" vs "aBc" == 0), * ordering sign, empty strings, and high bytes (>= 0x80) compared * unmodified — no locale folding; * 2. strncasecmp: the n bound (differences beyond n are invisible), * n == 0, and NUL-terminated early stop; * 3. ffs/ffsl/ffsll: ffs(0) == 0, ffs(8) == 4, ffs(INT_MIN) == 32, * ffsl(LONG_MIN) == 64, ffsll(LLONG_MIN) == 64, plus low-bit spots; * 4. bcopy: (src, dst) argument order (reversed vs memcpy), and the * discriminating overlap case dst == src + 1 — a forward-only copy * would smear the source byte and produce "aaaaaa" instead of * "aabcde"; * 5. bzero: zeroes the first n bytes and nothing beyond; n == 0 is a * no-op; * 6. bcmp: equal/differ, does not stop at NUL, unsigned byte order; * 7. index/rindex: first/last occurrence, the NUL counts as a match, * c converts through unsigned char, NULL when absent. * * With `-f`, only the plan's failure scenario runs: index("abc", 'z') and * rindex("abc", 'z') must BOTH be NULL (character absent); a non-NULL * result is the defect. Exits 0 when both are NULL as expected. * * No host libc headers are included (the -Iinclude path would shadow * GCC's internal headers); all output goes through raw SYS_write via * . Not yet wired into make check (the build * wiring todo owns that). * * The whole body mirrors the header's gate: every function is * level 2, so at level 1 this TU compiles to a no-op runner (the test_ctype * precedent). Compile the real test with -DVLIBC_LEVEL=2. * * Standalone build: * gcc -Iinclude -DVLIBC_LEVEL=2 -std=c23 -Wall -Wextra -pedantic \ * -o /tmp/t10 tests/test_strings.c src/string/strings_impl.c \ * src/string/{memmove,memset,memcmp}.c */ #include #include #include #if VLIBC_LEVEL_GE(2) /* ---- test functions (one per checked behavior) ---- */ static int test_strcasecmp(void) { static const unsigned char hi[] = {0xe1, 0x00}; static const unsigned char lo[] = {0x61, 0x00}; TEST_ASSERT_EQ(strcasecmp("AbC", "aBc"), 0); TEST_ASSERT_EQ(strcasecmp("", ""), 0); TEST_ASSERT_EQ(strcasecmp("cafe", "CAFE"), 0); TEST_ASSERT_TRUE(strcasecmp("abc", "abd") < 0); TEST_ASSERT_TRUE(strcasecmp("abd", "abc") > 0); TEST_ASSERT_TRUE(strcasecmp("", "a") < 0); TEST_ASSERT_TRUE(strcasecmp("a", "") > 0); TEST_ASSERT_TRUE(strcasecmp("Z", "a") > 0); /* folds 'Z' to 'z' */ TEST_ASSERT_TRUE(strcasecmp("A", "z") < 0); /* Bytes >= 0x80 pass through unmodified and sort above ASCII. */ TEST_ASSERT_EQ(strcasecmp((const char *)hi, (const char *)hi), 0); TEST_ASSERT_TRUE(strcasecmp((const char *)hi, (const char *)lo) > 0); return 0; } static int test_strncasecmp(void) { TEST_ASSERT_EQ(strncasecmp("AbCd", "aBcD", 4), 0); TEST_ASSERT_EQ(strncasecmp("abcX", "abcY", 3), 0); /* diff beyond n */ TEST_ASSERT_TRUE(strncasecmp("abcX", "abcY", 4) < 0); TEST_ASSERT_EQ(strncasecmp("x", "y", 0), 0); /* n == 0 */ TEST_ASSERT_EQ(strncasecmp("", "", 5), 0); TEST_ASSERT_EQ(strncasecmp("a\0b", "a\0c", 3), 0); /* stops at NUL */ TEST_ASSERT_TRUE(strncasecmp("abc", "abcd", 4) < 0); /* shorter lhs */ TEST_ASSERT_TRUE(strncasecmp("abcd", "abc", 4) > 0); TEST_ASSERT_EQ(strncasecmp("hello", "HELLO world", 5), 0); return 0; } static int test_ffs(void) { TEST_ASSERT_EQ(ffs(0), 0); TEST_ASSERT_EQ(ffs(1), 1); TEST_ASSERT_EQ(ffs(2), 2); TEST_ASSERT_EQ(ffs(8), 4); TEST_ASSERT_EQ(ffs(0x8000), 16); TEST_ASSERT_EQ(ffs(-1), 1); /* all bits set: lowest bit is bit 0 */ TEST_ASSERT_EQ(ffs(INT_MIN), 32); TEST_ASSERT_EQ(ffsl(0), 0); TEST_ASSERT_EQ(ffsl(1L << 40), 41); TEST_ASSERT_EQ(ffsl(LONG_MIN), 64); /* long is 64-bit on x86_64 */ TEST_ASSERT_EQ(ffsll(0), 0); TEST_ASSERT_EQ(ffsll(0x100000000LL), 33); TEST_ASSERT_EQ(ffsll(LLONG_MIN), 64); return 0; } static int test_bcopy(void) { char buf[16] = "abcdef"; char buf2[16] = "abcdef"; /* Non-overlapping copy; NOTE the (src, dst) argument order. */ bcopy(buf, buf2, 7); TEST_ASSERT_STREQ(buf2, "abcdef"); /* Overlap, dst == src + 1: only a backward (memmove-style) copy can * produce this; a forward copy smears src[0] over the whole range. */ bcopy(buf, buf + 1, 5); TEST_ASSERT_STREQ(buf, "aabcde"); /* Overlap, dst inside src at +2: both directions agree, sanity only. */ { char buf3[16] = "abcdef"; bcopy(buf3, buf3 + 2, 4); TEST_ASSERT_STREQ(buf3, "ababcd"); } /* Overlap, dst before src (backward region): forward copy is correct. */ { char buf4[16] = "abcdef"; bcopy(buf4 + 2, buf4, 4); TEST_ASSERT_STREQ(buf4, "cdefef"); } return 0; } static int test_bzero(void) { char buf[8]; int i; for (i = 0; i < 8; i++) { buf[i] = 'A'; } bzero(buf, 4); TEST_ASSERT_EQ(buf[0], '\0'); TEST_ASSERT_EQ(buf[3], '\0'); TEST_ASSERT_EQ(buf[4], 'A'); /* untouched past n */ bzero(buf, 0); /* n == 0: nothing */ TEST_ASSERT_EQ(buf[0], '\0'); TEST_ASSERT_EQ(buf[4], 'A'); return 0; } static int test_bcmp(void) { static const unsigned char hi[] = {0xff, 0x00}; static const unsigned char lo[] = {0x7f, 0x00}; TEST_ASSERT_EQ(bcmp("abc", "abc", 3), 0); TEST_ASSERT_TRUE(bcmp("abc", "abd", 3) != 0); TEST_ASSERT_EQ(bcmp("abc", "abd", 2), 0); TEST_ASSERT_EQ(bcmp("", "", 0), 0); /* Bytes compare as unsigned char; a NUL does not end the comparison. */ TEST_ASSERT_TRUE(bcmp(hi, lo, 1) != 0); TEST_ASSERT_EQ(bcmp(hi, hi, 2), 0); TEST_ASSERT_EQ(bcmp("a\0b", "a\0c", 3) != 0, 1); return 0; } static int test_index(void) { char s[] = "hello"; TEST_ASSERT_EQ(index(s, 'l') - s, 2); TEST_ASSERT_EQ(index(s, 'h') - s, 0); TEST_ASSERT_EQ(index(s, '\0') - s, 5); /* the NUL counts */ TEST_ASSERT_NULL(index(s, 'z')); /* c converts through unsigned char: 0x100 is byte 0, the NUL. */ TEST_ASSERT_EQ(index(s, 0x100) - s, 5); return 0; } static int test_rindex(void) { char s[] = "hello"; TEST_ASSERT_EQ(rindex(s, 'l') - s, 3); TEST_ASSERT_EQ(rindex(s, 'h') - s, 0); TEST_ASSERT_EQ(rindex(s, '\0') - s, 5); /* the NUL counts */ TEST_ASSERT_NULL(rindex(s, 'z')); return 0; } /* ---- failure mode: the plan's absent-character scenario ---- */ static int test_absent_char(void) { TEST_ASSERT_NULL(index("abc", 'z')); TEST_ASSERT_NULL(rindex("abc", 'z')); return 0; } /* ---- register + runner ---- */ static const struct vlibc_test tests[] = { {"strcasecmp", test_strcasecmp}, {"strncasecmp", test_strncasecmp}, {"ffs", test_ffs}, {"bcopy", test_bcopy}, {"bzero", test_bzero}, {"bcmp", test_bcmp}, {"index", test_index}, {"rindex", test_rindex}, }; /* * Own main (not TEST_MAIN): supports the -f failure 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 && argv[1][0] == '-' && argv[1][1] == 'f' && argv[1][2] == '\0') { int before = vlibc_test_failures; vlibc_test_say(1, "RUN absent-char: "); if (test_absent_char() == 0 && vlibc_test_failures == before) { vlibc_test_say(1, "PASS\n"); vlibc_test_say(1, "SUMMARY: 1/1 passed, 0 assertion failure(s)\n"); return 0; } vlibc_test_say(1, "FAIL\n"); return 1; } 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; } #else /* !VLIBC_LEVEL_GE(2) */ /* * Level 1: every function is gated at level 2, so there is * nothing to run. Keep the TU compilable at any configured profile. */ int main(void) { vlibc_test_say(1, "SKIP: is level 2, not available here\n"); return 0; } #endif /* VLIBC_LEVEL_GE(2) */