testing/libc/arch_libc: Test memccpy and stpncpy.

Neither is covered here, and both are overridable, so a machine or libc
implementation of either goes in unmeasured and unchecked.

memccpy is checked with the search character present, where the copy
stops just past it and the result points there, and absent, where the
whole length is copied and the result is NULL.  stpncpy is checked
against every capacity from zero to four past the length, for the
content, the zero padding beyond the terminator, and the returned
pointer, which is the terminator when the string fits and one past the
end when it does not.

Both sweep all sixty four source and destination alignment pairs, and
both are added to the benchmark, which now covers nineteen functions.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
This commit is contained in:
Justin Hammond 2026-08-15 16:20:30 +08:00 committed by Alan C. Assis
parent f4bb54e4d1
commit f45a02c938
3 changed files with 207 additions and 0 deletions

View file

@ -43,6 +43,14 @@ config TESTING_ARCH_LIBC_STRCPY
bool "test strcpy"
default y
config TESTING_ARCH_LIBC_MEMCCPY
bool "test memccpy"
default y
config TESTING_ARCH_LIBC_STPNCPY
bool "test stpncpy"
default y
config TESTING_ARCH_LIBC_STRLCPY
bool "test strlcpy"
default y

View file

@ -92,6 +92,8 @@
#define OP_STPCPY 14
#define OP_STRNCPY 15
#define OP_STRCAT 16
#define OP_MEMCCPY 17
#define OP_STPNCPY 18
/****************************************************************************
* Private Types
@ -134,6 +136,8 @@ static const struct bench_op_s g_ops[] =
{"stpcpy", OP_STPCPY, true, true},
{"strncpy", OP_STRNCPY, true, true},
{"strcat", OP_STRCAT, true, true},
{"memccpy", OP_MEMCCPY, true, false},
{"stpncpy", OP_STPNCPY, true, true},
};
static const size_t g_sizes[] =
@ -324,6 +328,12 @@ static void bench_one(FAR const struct bench_op_s *o, size_t n,
case OP_STRNCPY:
strncpy(d, s, n);
break;
case OP_MEMCCPY:
g_sink += (uintptr_t)memccpy(d, s, '~', n);
break;
case OP_STPNCPY:
g_sink += (uintptr_t)stpncpy(d, s, n);
break;
case OP_STRCAT:
/* Appending to what the last turn appended would grow the

View file

@ -792,6 +792,183 @@ static void speed_strlcpy(void)
#endif
#endif
/****************************************************************************
* Name: test_memccpy
****************************************************************************/
#ifdef CONFIG_TESTING_ARCH_LIBC_MEMCCPY
static int test_memccpy(void)
{
int size;
int fail = 0;
int ai;
int i;
printf("Testing memccpy...\n");
for (ai = 0; ai < 64; ai++)
{
int da = ai / 8;
int sa = ai % 8;
for (size = 1; size <= 64; size++)
{
FAR char *p;
int at = size / 2;
fill_pattern(g_buf1 + sa, size);
g_buf1[sa + at] = '#';
memset(g_buf2, 0x5a, sizeof(g_buf2));
/* The character is present, so the copy stops just past it */
p = memccpy(g_buf2 + da, g_buf1 + sa, '#', size);
if (p != g_buf2 + da + at + 1 ||
memcmp(g_buf2 + da, g_buf1 + sa, at + 1) != 0 ||
(unsigned char)g_buf2[da + at + 1] != 0x5a)
{
printf(" FAIL found: sa=%d da=%d size=%d\n", sa, da, size);
fail++;
}
/* The character is absent, so the whole length is copied */
for (i = 0; i < size; i++)
{
g_buf1[sa + i] = 'A' + (i % 26);
}
memset(g_buf2, 0x5a, sizeof(g_buf2));
p = memccpy(g_buf2 + da, g_buf1 + sa, '#', size);
if (p != NULL || memcmp(g_buf2 + da, g_buf1 + sa, size) != 0 ||
(unsigned char)g_buf2[da + size] != 0x5a)
{
printf(" FAIL absent: sa=%d da=%d size=%d\n", sa, da, size);
fail++;
}
}
}
printf("memccpy: %s\n", fail ? "FAILED" : "PASSED");
return fail;
}
#ifdef ARCH_LIBC_HAVE_PERF
static void speed_memccpy(void)
{
clock_t start;
clock_t end;
int i;
fill_pattern(g_buf1, 128);
start = perf_gettime();
for (i = 0; i < TEST_REPEAT; i++)
{
g_sink = (uintptr_t)memccpy(g_buf2, g_buf1, '#', 128);
}
end = perf_gettime();
printf("memccpy(128) avg cycles: %ju\n",
(uintmax_t)(end - start) / TEST_REPEAT);
}
#endif
#endif
/****************************************************************************
* Name: test_stpncpy
****************************************************************************/
#ifdef CONFIG_TESTING_ARCH_LIBC_STPNCPY
static int test_stpncpy(void)
{
int size;
int fail = 0;
int ai;
int cap;
int i;
printf("Testing stpncpy...\n");
for (ai = 0; ai < 64; ai++)
{
int da = ai / 8;
int sa = ai % 8;
for (size = 1; size <= 48; size++)
{
fill_pattern(g_buf1 + sa, size);
g_buf1[sa + size] = '\0';
for (cap = 0; cap <= size + 4; cap++)
{
FAR char *p;
FAR char *want;
memset(g_buf2, 0x5a, sizeof(g_buf2));
p = stpncpy(g_buf2 + da, g_buf1 + sa, cap);
/* Up to the terminator is copied, the rest is padded, and
* the result points at the terminator or one past the end.
*/
want = size < cap ? g_buf2 + da + size : g_buf2 + da + cap;
if (p != want)
{
printf(" FAIL ret: sa=%d da=%d size=%d cap=%d\n",
sa, da, size, cap);
fail++;
continue;
}
for (i = 0; i < cap; i++)
{
char expect = i < size ? g_buf1[sa + i] : '\0';
if (g_buf2[da + i] != expect)
{
printf(" FAIL content: sa=%d da=%d size=%d cap=%d\n",
sa, da, size, cap);
fail++;
break;
}
}
if ((unsigned char)g_buf2[da + cap] != 0x5a)
{
printf(" FAIL overrun: sa=%d da=%d size=%d cap=%d\n",
sa, da, size, cap);
fail++;
}
}
}
}
printf("stpncpy: %s\n", fail ? "FAILED" : "PASSED");
return fail;
}
#ifdef ARCH_LIBC_HAVE_PERF
static void speed_stpncpy(void)
{
clock_t start;
clock_t end;
int i;
fill_pattern(g_buf1, 128);
g_buf1[128] = '\0';
start = perf_gettime();
for (i = 0; i < TEST_REPEAT; i++)
{
g_sink = (uintptr_t)stpncpy(g_buf2, g_buf1, 128);
}
end = perf_gettime();
printf("stpncpy(128) avg cycles: %ju\n",
(uintmax_t)(end - start) / TEST_REPEAT);
}
#endif
#endif
/****************************************************************************
* Name: test_strchr
****************************************************************************/
@ -1545,6 +1722,18 @@ int main(int argc, FAR char *argv[])
fail += test_strcpy();
speed_strcpy();
#endif
#ifdef CONFIG_TESTING_ARCH_LIBC_MEMCCPY
fail += test_memccpy();
# ifdef ARCH_LIBC_HAVE_PERF
speed_memccpy();
# endif
#endif
#ifdef CONFIG_TESTING_ARCH_LIBC_STPNCPY
fail += test_stpncpy();
# ifdef ARCH_LIBC_HAVE_PERF
speed_stpncpy();
# endif
#endif
#ifdef CONFIG_TESTING_ARCH_LIBC_STRLCPY
fail += test_strlcpy();
# ifdef ARCH_LIBC_HAVE_PERF