feat(time): time/clock/mktime/strftime/nanosleep
This commit is contained in:
@@ -0,0 +1,384 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user