feat(strings): strcasecmp/ffs/bcopy/bzero
This commit is contained in:
@@ -0,0 +1,133 @@
|
|||||||
|
#ifndef VLIBC_STRINGS_H
|
||||||
|
#define VLIBC_STRINGS_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <strings.h>.
|
||||||
|
*
|
||||||
|
* BSD/XSI legacy string functions, gated by the active compatibility
|
||||||
|
* profile (see include/vlibc/features.h). This ENTIRE header is a legacy
|
||||||
|
* extension: nothing it declares is POSIX.1-2008 base, so at level 1
|
||||||
|
* (onlyposix) it is empty. POSIX mandates that <strings.h> (BSD legacy)
|
||||||
|
* and <string.h> (ISO C) remain separate headers; the two never share a
|
||||||
|
* declaration, so including both can never conflict.
|
||||||
|
*
|
||||||
|
* strcasecmp / strncasecmp — XSI case-insensitive comparisons (ASCII
|
||||||
|
* 'A'..'Z'/'a'..'z' fold only, byte-wise);
|
||||||
|
* ffs / ffsl / ffsll — XSI find-first-set-bit, 1-based;
|
||||||
|
* bcmp / bcopy / bzero — BSD legacy byte operations (bcopy takes
|
||||||
|
* (src, dst) — the arguments are REVERSED
|
||||||
|
* relative to memcpy/memmove);
|
||||||
|
* index / rindex — BSD legacy names for strchr / strrchr.
|
||||||
|
*
|
||||||
|
* This header includes <vlibc/features.h> itself, so the gate below always
|
||||||
|
* sees the configured VLIBC_LEVEL even when the caller included no vlibc
|
||||||
|
* header first, and <stddef.h> for size_t.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compare s1 and s2 byte-wise, ignoring case: each byte is read as
|
||||||
|
* unsigned char and an ASCII 'A'..'Z' is folded to 'a'..'z' before the
|
||||||
|
* comparison; bytes at or above 0x80 pass through unmodified. Return
|
||||||
|
* negative, zero, or positive when s1 is less than, equal to, or greater
|
||||||
|
* than s2.
|
||||||
|
* pure: reads memory, no side effects.
|
||||||
|
*/
|
||||||
|
__attribute__((pure)) int
|
||||||
|
strcasecmp(const char *s1, const char *s2);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compare at most n bytes of s1 and s2 as strcasecmp does, stopping early
|
||||||
|
* at the first difference or the first NUL; return negative, zero, or
|
||||||
|
* positive like strcasecmp. The NUL terminator of either string ends the
|
||||||
|
* comparison even when n is larger.
|
||||||
|
* pure: reads memory, no side effects.
|
||||||
|
*/
|
||||||
|
__attribute__((pure)) int
|
||||||
|
strncasecmp(const char *s1, const char *s2, size_t n);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the 1-based index of the least significant set bit of i, or 0
|
||||||
|
* when i has no set bit: ffs(0) == 0, ffs(1) == 1, ffs(8) == 4,
|
||||||
|
* ffs(INT_MIN) == 32.
|
||||||
|
* const: the result depends only on i.
|
||||||
|
*/
|
||||||
|
__attribute__((const)) int
|
||||||
|
ffs(int i);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Same as ffs for long: ffsl(0) == 0, ffsl(1L << 40) == 41,
|
||||||
|
* ffsl(LONG_MIN) == 64 (long is 64-bit on x86_64).
|
||||||
|
* const: the result depends only on i.
|
||||||
|
*/
|
||||||
|
__attribute__((const)) int
|
||||||
|
ffsl(long i);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Same as ffs for long long: ffsll(0) == 0, ffsll(LLONG_MIN) == 64.
|
||||||
|
* const: the result depends only on i.
|
||||||
|
*/
|
||||||
|
__attribute__((const)) int
|
||||||
|
ffsll(long long i);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compare the first n bytes of s1 and s2 as unsigned char, like memcmp;
|
||||||
|
* unlike strcmp a NUL byte does not end the comparison. Return 0 when the
|
||||||
|
* n bytes are equal, nonzero otherwise.
|
||||||
|
* pure: reads memory, no side effects.
|
||||||
|
*/
|
||||||
|
__attribute__((pure)) int
|
||||||
|
bcmp(const void *s1, const void *s2, size_t n);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copy n bytes from src to dst. The regions may overlap and the copy
|
||||||
|
* behaves like memmove. NOTE the argument order: source first, destination
|
||||||
|
* second — the reverse of memcpy/memmove.
|
||||||
|
* No intent attribute: it writes memory.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
bcopy(const void *src, void *dst, size_t n);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fill n bytes at s with zero.
|
||||||
|
* No intent attribute: it writes memory.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
bzero(void *s, size_t n);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return a pointer to the first occurrence of c (converted to unsigned
|
||||||
|
* char) in s, or NULL when absent. The terminating NUL is part of the
|
||||||
|
* string, so index(s, '\0') returns a pointer to it. Legacy name for
|
||||||
|
* strchr.
|
||||||
|
* pure: reads memory, no side effects.
|
||||||
|
*/
|
||||||
|
__attribute__((pure)) char *
|
||||||
|
index(const char *s, int c);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return a pointer to the last occurrence of c (converted to unsigned
|
||||||
|
* char) in s, or NULL when absent. The terminating NUL is part of the
|
||||||
|
* string, so rindex(s, '\0') returns a pointer to it. Legacy name for
|
||||||
|
* strrchr.
|
||||||
|
* pure: reads memory, no side effects.
|
||||||
|
*/
|
||||||
|
__attribute__((pure)) char *
|
||||||
|
rindex(const char *s, int c);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#endif /* VLIBC_STRINGS_H */
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <strings.h> legacy functions (todo 10).
|
||||||
|
*
|
||||||
|
* The whole file self-gates at VLIBC_LEVEL >= 2 (the build wiring pass may
|
||||||
|
* compile it unconditionally): at level 1 this translation unit produces
|
||||||
|
* nothing, because every function here is XSI/BSD legacy and none is
|
||||||
|
* POSIX.1-2008 base.
|
||||||
|
*
|
||||||
|
* bcopy/bzero/bcmp delegate to the project's own memmove/memset/memcmp
|
||||||
|
* (satisfied from the archive at link time) — bcopy MUST NOT be a bare
|
||||||
|
* memcpy alias, because its contract includes overlapping regions.
|
||||||
|
* index/rindex are written as direct byte scans (own code, not strchr
|
||||||
|
* wrappers) so their documented unsigned char conversion is explicit.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fold an ASCII uppercase letter to lowercase; every other byte passes
|
||||||
|
* through unchanged. Kept local and locale-free: the BSD comparisons must
|
||||||
|
* never depend on ctype tables or the active locale.
|
||||||
|
*/
|
||||||
|
static inline unsigned char
|
||||||
|
fold_ascii_byte(unsigned char c)
|
||||||
|
{
|
||||||
|
if (c >= 'A' && c <= 'Z')
|
||||||
|
{
|
||||||
|
return (unsigned char)(c + ('a' - 'A'));
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compare s1 and s2 byte-wise ignoring case. Equal-folded runs are skipped
|
||||||
|
* with a raw-byte fast path; the first differing byte pair is compared
|
||||||
|
* folded, so "A" and "a" order as equal and the returned sign is that of
|
||||||
|
* the folded difference. The scan stops at the first NUL in either string.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
strcasecmp(const char *s1, const char *s2)
|
||||||
|
{
|
||||||
|
const unsigned char *l = (const unsigned char *)s1;
|
||||||
|
const unsigned char *r = (const unsigned char *)s2;
|
||||||
|
|
||||||
|
for (; *l == *r; l++, r++)
|
||||||
|
{
|
||||||
|
if (*l == '\0')
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)fold_ascii_byte(*l) - (int)fold_ascii_byte(*r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compare at most n bytes of s1 and s2 as strcasecmp does, stopping early
|
||||||
|
* at the first folded difference or the first NUL. When the comparison
|
||||||
|
* ends at a NUL, the shorter (equal-prefix) string sorts first.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
strncasecmp(const char *s1, const char *s2, size_t n)
|
||||||
|
{
|
||||||
|
const unsigned char *l = (const unsigned char *)s1;
|
||||||
|
const unsigned char *r = (const unsigned char *)s2;
|
||||||
|
|
||||||
|
while (n != 0 && *l != '\0' && *r != '\0')
|
||||||
|
{
|
||||||
|
const unsigned char fl = fold_ascii_byte(*l);
|
||||||
|
const unsigned char fr = fold_ascii_byte(*r);
|
||||||
|
|
||||||
|
if (fl != fr)
|
||||||
|
{
|
||||||
|
return (int)fl - (int)fr;
|
||||||
|
}
|
||||||
|
l++;
|
||||||
|
r++;
|
||||||
|
n--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* n bytes compared: equal so far. */
|
||||||
|
if (n == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A NUL ended the scan: the one that is NUL here is the shorter. */
|
||||||
|
return (int)*l - (int)*r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the 1-based index of the least significant set bit of i, or 0
|
||||||
|
* when i is 0. The zero case is guarded because __builtin_ctz is
|
||||||
|
* undefined for a zero argument.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
ffs(int i)
|
||||||
|
{
|
||||||
|
return i == 0 ? 0 : __builtin_ctz((unsigned int)i) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ffsl(long i)
|
||||||
|
{
|
||||||
|
return i == 0 ? 0 : __builtin_ctzl((unsigned long)i) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ffsll(long long i)
|
||||||
|
{
|
||||||
|
return i == 0 ? 0 : __builtin_ctzll((unsigned long long)i) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copy n bytes from src to dst with memmove semantics: the regions may
|
||||||
|
* overlap and the copy behaves as if through a temporary. The argument
|
||||||
|
* order (src, dst) is the reverse of memmove's — kept as such because it
|
||||||
|
* is the BSD contract.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
bcopy(const void *src, void *dst, size_t n)
|
||||||
|
{
|
||||||
|
(void)memmove(dst, src, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fill n bytes at s with zero.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
bzero(void *s, size_t n)
|
||||||
|
{
|
||||||
|
(void)memset(s, 0, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compare the first n bytes of s1 and s2 as unsigned char, like memcmp;
|
||||||
|
* return 0 when equal, nonzero otherwise. A NUL byte does not end the
|
||||||
|
* comparison.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
bcmp(const void *s1, const void *s2, size_t n)
|
||||||
|
{
|
||||||
|
return memcmp(s1, s2, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return a pointer to the first occurrence of c (converted to unsigned
|
||||||
|
* char) in s, or NULL when absent. The terminating NUL is part of the
|
||||||
|
* string, so index(s, '\0') returns a pointer to it. Legacy name for
|
||||||
|
* strchr.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
index(const char *s, int c)
|
||||||
|
{
|
||||||
|
const unsigned char cc = (unsigned char)c;
|
||||||
|
|
||||||
|
for (;; s++)
|
||||||
|
{
|
||||||
|
if ((unsigned char)*s == cc)
|
||||||
|
{
|
||||||
|
return (char *)s;
|
||||||
|
}
|
||||||
|
if (*s == '\0')
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return a pointer to the last occurrence of c (converted to unsigned
|
||||||
|
* char) in s, or NULL when absent. The terminating NUL is part of the
|
||||||
|
* string, so rindex(s, '\0') returns a pointer to it. Legacy name for
|
||||||
|
* strrchr.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
rindex(const char *s, int c)
|
||||||
|
{
|
||||||
|
const unsigned char cc = (unsigned char)c;
|
||||||
|
const char *last = NULL;
|
||||||
|
|
||||||
|
for (;; s++)
|
||||||
|
{
|
||||||
|
if ((unsigned char)*s == cc)
|
||||||
|
{
|
||||||
|
last = s;
|
||||||
|
}
|
||||||
|
if (*s == '\0')
|
||||||
|
{
|
||||||
|
return (char *)last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — strings.h legacy functions test (todo 10).
|
||||||
|
*
|
||||||
|
* Covers the whole <strings.h> 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
|
||||||
|
* <vlibc/internal/test.h>. Not yet wired into make check (the build
|
||||||
|
* wiring todo owns that).
|
||||||
|
*
|
||||||
|
* The whole body mirrors the header's gate: every <strings.h> 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 <strings.h>
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include <vlibc/internal/test.h>
|
||||||
|
|
||||||
|
#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 <strings.h> 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: <strings.h> is level 2, not available here\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
Reference in New Issue
Block a user