Files
vlibc/tests/test_times.c
T

187 lines
5.6 KiB
C

/*
* vlibc — sys/times.h test (todo 35).
*
* Exercises the whole <sys/times.h> surface end to end:
*
* 1. times(&t) succeeds: the returned tick counter is not -1 and the four
* struct tms fields are non-negative (fresh processes commonly show
* zeros here, which is legal).
* 2. Monotonicity across real work: a few-million-iteration busy loop
* burns user CPU, and a second times() read then reports tms_utime no
* smaller than the first. The tick counter itself must never go
* backwards between two reads.
* 3. A rapid-fire loop of times() calls (no work between them) keeps the
* tick counter strictly non-decreasing across every adjacent pair.
*
* The -f mode runs the one failure shape — times() on an out-of-range
* buffer pointer, which the kernel rejects with EFAULT — asserting the
* return value only (errno is never read: vlibc's errno slot collides
* with the host TCB). It leaves via a raw SYS_exit_group so no host
* cleanup runs after the library's errno write (house pattern,
* tests/test_stat.c).
*
* No host libc headers are included (the -Iinclude path would shadow
* GCC's internal headers); diagnostics go through raw SYS_write. The whole
* body mirrors the header's gate: <sys/times.h> is entirely level 2, so at
* level 1 this TU compiles to a no-op runner.
*/
#include <sys/times.h>
#include "../src/internal/syscall.h"
/* Write a NUL-terminated string to fd via the raw syscall layer. The
* optimize attribute keeps GCC from lowering the length loop into a
* strlen call, which would leave a vlibc-owned symbol undefined in this
* host-linked standalone binary (house idiom, see src/string). */
static __attribute__((optimize("no-tree-loop-distribute-patterns"))) void
say(int fd, const char *s)
{
long n = 0;
while (s[n] != '\0')
{
n++;
}
__syscall3(SYS_write, fd, (long)s, n);
}
#if VLIBC_LEVEL_GE(2)
static int failures;
static void
check(int ok, const char *msg)
{
if (ok)
{
say(1, "ok ");
}
else
{
say(1, "FAIL ");
failures++;
}
say(1, msg);
say(1, "\n");
}
/* Burn a few million iterations of real user CPU so tms_utime has a chance
* to advance between two times() reads. The sink is volatile so the loop
* cannot be optimized away. */
static volatile unsigned long t35_sink;
static void
burn_cpu(void)
{
for (unsigned long i = 0; i < 5000000UL; i++)
{
t35_sink += i;
}
}
/* 1-3. Success shapes: plausible fields, monotonic across work and across
* rapid reads. */
static void
times_scenario(void)
{
struct tms t1;
struct tms t2;
struct tms probe;
clock_t r1;
clock_t r2;
clock_t prev;
r1 = times(&t1);
check(r1 != (clock_t)-1, "times(&t) does not return -1");
check(t1.tms_utime >= 0 && t1.tms_stime >= 0, "tms_utime/tms_stime are non-negative");
check(t1.tms_cutime >= 0 && t1.tms_cstime >= 0, "tms_cutime/tms_cstime are non-negative");
burn_cpu();
r2 = times(&t2);
check(r2 != (clock_t)-1, "second times(&t) does not return -1");
check(r2 >= r1, "tick counter does not go backwards across work");
check(t2.tms_utime >= t1.tms_utime, "tms_utime is nondecreasing after a CPU burn");
check(t2.tms_stime >= t1.tms_stime, "tms_stime is nondecreasing after a CPU burn");
check(t2.tms_cutime >= t1.tms_cutime && t2.tms_cstime >= t1.tms_cstime,
"child-time fields are nondecreasing (no waited-for children here)");
/* Rapid-fire reads with no work between them: the kernel tick counter
* is monotonic, so every adjacent pair must be non-decreasing. */
prev = times(&probe);
check(prev != (clock_t)-1, "rapid-fire times() baseline is not -1");
for (int i = 0; i < 100000; i++)
{
clock_t now = times(&probe);
if (now == (clock_t)-1)
{
check(0, "rapid-fire times() never returns -1");
break;
}
if (now < prev)
{
check(0, "rapid-fire tick counter never goes backwards");
break;
}
prev = now;
}
check(prev != (clock_t)-1, "rapid-fire times() loop completed");
}
/* The failure shapes; return values only, never errno. Exit via a raw
* SYS_exit_group: the library's errno write on this path corrupts glibc's
* private dtv slot at %fs:0+8, so host cleanup must never run (house
* pattern, tests/test_stat.c).
*
* The probe buffer is the page-start address 1: non-NULL (the modern
* kernel treats times(NULL) as legal and returns the tick counter without
* writing anything) but unmapped, so SYS_times' copy_to_user fails with
* EFAULT and the wrapper returns -1. */
static void
failure_scenarios(void)
{
check(times((struct tms *)(unsigned long)1) == (clock_t)-1,
"-f times(out-of-range buf) returns -1");
say(1, "times: raw kernel ticks reported; not rescaled to clock() units\n");
__syscall1(SYS_exit_group, failures == 0 ? 0 : 1);
/* not reached */
}
int
main(int argc, char **argv)
{
if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'f' && argv[1][2] == '\0')
{
failure_scenarios();
}
times_scenario();
if (failures == 0)
{
say(1, "all times tests passed\n");
}
else
{
say(1, "FAILURES\n");
}
return failures == 0 ? 0 : 1;
}
#else /* !VLIBC_LEVEL_GE(2) */
/*
* Level 1: every <sys/times.h> symbol is gated at level 2, so there is
* nothing to run. Keep the TU compilable at any configured profile.
*/
int
main(void)
{
say(1, "SKIP: <sys/times.h> is level 2, not available here\n");
return 0;
}
#endif /* VLIBC_LEVEL_GE(2) */