Files

385 lines
9.3 KiB
C

/*
* vlibc — time library test (todo 26, slice S3).
*
* Exercises the full time stack end to end against golden values verified
* against glibc (C locale, TZ=UTC):
*
* 1. strftime: every POSIX conversion on a fixed 2024-03-15 14:30:45
* (Friday, yday 74) struct, plus %e space-padding and %z rendering of
* a negative offset.
* 2. asctime/ctime fixed-format "Www Mmm dd hh:mm:ss yyyy\n" rendering.
* 3. mktime/gmtime/timegm round-trips; localtime under TZ=UTC.
* 4. nanosleep and (level 2) usleep, strptime round-trip, and the POSIX
* interval-timer syscall wrappers.
*
* The -f mode exercises the failure shapes (buffer overflow, year outside
* [1000,9999], unmatchable strptime) and leaves via a raw SYS_exit_group so
* no host cleanup runs afterwards (vlibc's errno slot collides with the
* host TCB, so errno is never read here).
*
* Host-header-free: everything comes from vlibc's public headers via
* -Iinclude; diagnostics go through raw SYS_write; environ is defined in
* this TU (the host startup does not install the vlibc environment) and
* pointed at the envp main receives.
*/
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define TEST_SYS_WRITE 1
#define TEST_SYS_EXIT_GROUP 231
char **environ;
char **__environ; // NOLINT(bugprone-reserved-identifier)
#if VLIBC_LEVEL_GE(2)
/* Level-2 timer ABI mirror (see src/time/timer.c). */
struct itimerspec
{
struct timespec it_interval;
struct timespec it_value;
};
#endif
static int failures;
static long
raw3(long n, long a, long b, long c)
{
unsigned long ret;
__asm__ volatile("syscall"
: "=a"(ret)
: "a"(n), "D"(a), "S"(b), "d"(c)
: "rcx", "r11", "memory");
return (long)ret;
}
static long
raw1(long n, long a)
{
unsigned long ret;
__asm__ volatile("syscall" : "=a"(ret) : "a"(n), "D"(a) : "rcx", "r11", "memory");
return (long)ret;
}
static void
say(const char *s)
{
size_t n = 0;
while (s[n] != '\0')
{
n++;
}
raw3(TEST_SYS_WRITE, 1, (long)s, (long)n);
}
static void
saynum(long v)
{
char buf[24];
size_t i = sizeof buf;
if (v == 0)
{
say("0");
return;
}
if (v < 0)
{
say("-");
v = -v;
}
while (v > 0)
{
buf[--i] = (char)('0' + v % 10);
v /= 10;
}
raw3(TEST_SYS_WRITE, 1, (long)(buf + i), (long)(sizeof buf - i));
}
static void
check(int cond, const char *what)
{
if (cond)
{
say("ok ");
}
else
{
say("FAIL ");
failures++;
}
say(what);
say("\n");
}
/* The canonical 2024-03-15 14:30:45 struct, normalized via mktime in UTC. */
static void
make_tm(struct tm *t)
{
memset(t, 0, sizeof *t);
t->tm_year = 2024 - 1900;
t->tm_mon = 2;
t->tm_mday = 15;
t->tm_hour = 14;
t->tm_min = 30;
t->tm_sec = 45;
t->tm_isdst = -1;
}
static void
strftime_scenario(void)
{
static const struct
{
const char *fmt;
const char *want;
} gold[] = {
{"%a", "Fri"},
{"%A", "Friday"},
{"%b", "Mar"},
{"%B", "March"},
{"%c", "Fri Mar 15 14:30:45 2024"},
{"%C", "20"},
{"%d", "15"},
{"%D", "03/15/24"},
{"%e", "15"},
{"%F", "2024-03-15"},
{"%g", "24"},
{"%G", "2024"},
{"%h", "Mar"},
{"%H", "14"},
{"%I", "02"},
{"%j", "075"},
{"%m", "03"},
{"%M", "30"},
{"%p", "PM"},
{"%r", "02:30:45 PM"},
{"%R", "14:30"},
{"%S", "45"},
{"%T", "14:30:45"},
{"%u", "5"},
{"%U", "10"},
{"%V", "11"},
{"%w", "5"},
{"%W", "11"},
{"%x", "03/15/24"},
{"%X", "14:30:45"},
{"%y", "24"},
{"%Y", "2024"},
{"%z", "+0000"},
{"%Z", "UTC"},
{"%%", "%"},
{"%Y-%m-%d %H:%M:%S", "2024-03-15 14:30:45"},
};
struct tm t;
struct tm t5;
char buf[128];
size_t i;
time_t e;
size_t want_len;
make_tm(&t);
e = mktime(&t);
check(t.tm_wday == 5 && t.tm_yday == 74, "mktime normalized 2024-03-15 -> Fri/yday 74");
check(t.tm_gmtoff == 0 && t.tm_isdst == 0, "mktime under TZ=UTC keeps gmtoff 0 isdst 0");
for (i = 0; i < sizeof gold / sizeof gold[0]; i++)
{
size_t n = strftime(buf, sizeof buf, gold[i].fmt, &t);
want_len = 0;
while (gold[i].want[want_len] != '\0')
{
want_len++;
}
if (strcmp(buf, gold[i].want) != 0 || n != want_len)
{
check(0, gold[i].fmt);
}
else
{
check(1, gold[i].fmt);
}
}
t5 = t;
t5.tm_mday = 5;
check(strftime(buf, sizeof buf, "%e", &t5) == 2 && strcmp(buf, " 5") == 0,
"%e space-pads single digits");
t.tm_gmtoff = -18000;
check(strftime(buf, sizeof buf, "%z", &t) == 5 && strcmp(buf, "-0500") == 0,
"%z renders negative gmtoff");
check(strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S", &t) == 19 &&
strcmp(buf, "2024-03-15 14:30:45") == 0,
"combined format golden");
(void)e;
}
static void
roundtrip_scenario(void)
{
struct tm tmv;
struct timespec ts;
time_t e0 = 1700000000;
time_t t0 = 0;
struct tm gtm;
time_t back;
/* ctime golden (UTC): same text as asctime of the fixed time. */
back = 0;
(void)back;
tmv.tm_year = 124;
tmv.tm_mon = 2;
tmv.tm_mday = 15;
tmv.tm_hour = 14;
tmv.tm_min = 30;
tmv.tm_sec = 45;
tmv.tm_isdst = -1;
back = mktime(&tmv);
check(strcmp(asctime(&tmv), "Fri Mar 15 14:30:45 2024\n") == 0, "asctime golden");
check(strcmp(ctime(&back), "Fri Mar 15 14:30:45 2024\n") == 0, "ctime golden");
/* mktime round-trips gmtime. */
gtm = *gmtime(&e0);
back = mktime(&gtm);
check(back == e0, "mktime round-trips gmtime(1700000000)");
localtime_r(&t0, &tmv);
check(tmv.tm_gmtoff == 0 && tmv.tm_isdst == 0, "localtime(0) under TZ=UTC");
ts.tv_sec = 0;
ts.tv_nsec = 1000000L;
check(nanosleep(&ts, NULL) == 0, "nanosleep 1ms");
#if VLIBC_LEVEL_GE(2)
/* timegm round-trips gmtime without any timezone state. */
gtm = *gmtime(&e0);
check(timegm(&gtm) == e0, "timegm round-trips gmtime(1700000000)");
/* strptime parses what strftime emitted for the fixed time. */
{
char sbuf[64];
char abuf[26];
struct tm pt;
struct tm ft;
time_t fe;
make_tm(&ft);
fe = mktime(&ft);
check(fe != (time_t)-1, "mktime on fixed time succeeds");
strftime(sbuf, sizeof sbuf, "%Y-%m-%d %H:%M:%S", &ft);
check(strcmp(sbuf, "2024-03-15 14:30:45") == 0, "strftime source for round-trip");
check(strptime(sbuf, "%Y-%m-%d %H:%M:%S", &pt) != NULL, "strptime parses golden");
check(timegm(&pt) == fe, "strptime -> timegm round-trip equals mktime");
check(strcmp(asctime_r(&ft, abuf), "Fri Mar 15 14:30:45 2024\n") == 0, "asctime_r golden");
check(strcmp(ctime_r(&fe, abuf), "Fri Mar 15 14:30:45 2024\n") == 0, "ctime_r golden");
check(usleep(1000) == 0, "usleep 1000us");
}
/* POSIX interval timers (S1 wrappers). */
{
struct itimerspec itv;
struct itimerspec got;
timer_t tid = 0;
check(timer_create(CLOCK_REALTIME, NULL, &tid) == 0, "timer_create");
itv.it_interval.tv_sec = 0;
itv.it_interval.tv_nsec = 0;
itv.it_value.tv_sec = 0;
itv.it_value.tv_nsec = 10000000L;
check(timer_settime(tid, 0, &itv, NULL) == 0, "timer_settime 10ms");
got.it_value.tv_sec = 0;
got.it_value.tv_nsec = 0;
check(timer_gettime(tid, &got) == 0 &&
(got.it_value.tv_sec > 0 || got.it_value.tv_nsec > 0),
"timer_gettime reports pending expiry");
check(timer_delete(tid) == 0, "timer_delete");
}
#endif
}
static void
failure_scenarios(void)
{
struct tm t;
struct tm y10k;
char buf[4];
make_tm(&t);
mktime(&t);
check(strftime(buf, sizeof buf, "%Y-%m-%d", &t) == 0, "-f strftime overflow returns 0");
memset(&y10k, 0, sizeof y10k);
y10k.tm_year = 10000 - 1900;
y10k.tm_mon = 0;
y10k.tm_mday = 1;
y10k.tm_hour = 0;
y10k.tm_min = 0;
y10k.tm_sec = 0;
y10k.tm_wday = 0;
check(asctime(&y10k) == NULL, "-f asctime year 10000 returns NULL");
#if VLIBC_LEVEL_GE(2)
{
struct tm pt;
check(strptime("nope", "%Y", &pt) == NULL, "-f strptime mismatch returns NULL");
}
#endif
}
int
main(int argc, char **argv, char **envp)
{
int fail_mode = argc > 1 && argv[1][0] == '-' && argv[1][1] == 'f';
environ = envp;
__environ = envp;
setenv("TZ", "UTC", 1);
tzset();
if (fail_mode)
{
failure_scenarios();
if (failures != 0)
{
say("FAILURES: ");
saynum(failures);
say("\n");
}
else
{
say("all time -f tests passed\n");
}
raw1(TEST_SYS_EXIT_GROUP, failures != 0);
/* not reached */
return failures != 0;
}
strftime_scenario();
roundtrip_scenario();
if (failures != 0)
{
say("FAILURES: ");
saynum(failures);
say("\n");
return 1;
}
say("all time tests passed\n");
return 0;
}