/* * 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 #include #include #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; }