385 lines
9.3 KiB
C
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(>m);
|
|
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(>m) == 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;
|
|
}
|