// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include "prelude.h" #include "tests.h" #include "xtime.h" #include "bfstd.h" #include "diag.h" #include #include #include #include static bool tm_equal(const struct tm *tma, const struct tm *tmb) { return tma->tm_year == tmb->tm_year && tma->tm_mon == tmb->tm_mon && tma->tm_mday == tmb->tm_mday && tma->tm_hour == tmb->tm_hour && tma->tm_min == tmb->tm_min && tma->tm_sec == tmb->tm_sec && tma->tm_wday == tmb->tm_wday && tma->tm_yday == tmb->tm_yday && tma->tm_isdst == tmb->tm_isdst; } /** Check one xgetdate() result. */ static bool check_one_xgetdate(const char *str, int error, time_t expected) { struct timespec ts; int ret = xgetdate(str, &ts); if (error) { return bfs_pcheck(ret == -1 && errno == error, "xgetdate('%s')", str); } else { return bfs_pcheck(ret == 0, "xgetdate('%s')", str) && bfs_check(ts.tv_sec == expected && ts.tv_nsec == 0, "xgetdate('%s'): %jd.%09jd != %jd", str, (intmax_t)ts.tv_sec, (intmax_t)ts.tv_nsec, (intmax_t)expected); } } /** xgetdate() tests. */ static bool check_xgetdate(void) { bool ret = true; ret &= check_one_xgetdate("", EINVAL, 0); ret &= check_one_xgetdate("????", EINVAL, 0); ret &= check_one_xgetdate("1991", EINVAL, 0); ret &= check_one_xgetdate("1991-??", EINVAL, 0); ret &= check_one_xgetdate("1991-12", EINVAL, 0); ret &= check_one_xgetdate("1991-12-", EINVAL, 0); ret &= check_one_xgetdate("1991-12-??", EINVAL, 0); ret &= check_one_xgetdate("1991-12-14", 0, 692668800); ret &= check_one_xgetdate("1991-12-14-", EINVAL, 0); ret &= check_one_xgetdate("1991-12-14T", EINVAL, 0); ret &= check_one_xgetdate("1991-12-14T??", EINVAL, 0); ret &= check_one_xgetdate("1991-12-14T10", 0, 692704800); ret &= check_one_xgetdate("1991-12-14T10:??", EINVAL, 0); ret &= check_one_xgetdate("1991-12-14T10:11", 0, 692705460); ret &= check_one_xgetdate("1991-12-14T10:11:??", EINVAL, 0); ret &= check_one_xgetdate("1991-12-14T10:11:12", 0, 692705472); ret &= check_one_xgetdate("1991-12-14T10Z", 0, 692704800); ret &= check_one_xgetdate("1991-12-14T10:11Z", 0, 692705460); ret &= check_one_xgetdate("1991-12-14T10:11:12Z", 0, 692705472); ret &= check_one_xgetdate("1991-12-14T10:11:12?", EINVAL, 0); ret &= check_one_xgetdate("1991-12-14T03-07", 0, 692704800); ret &= check_one_xgetdate("1991-12-14T06:41-03:30", 0, 692705460); ret &= check_one_xgetdate("1991-12-14T03:11:12-07:00", 0, 692705472); ret &= check_one_xgetdate("19911214 031112-0700", 0, 692705472);; return ret; } #define TM_FORMAT "%04d-%02d-%02d %02d:%02d:%02d (%d/7, %d/365%s)" #define TM_PRINTF(tm) \ (1900 + (tm).tm_year), (tm).tm_mon, (tm).tm_mday, \ (tm).tm_hour, (tm).tm_min, (tm).tm_sec, \ ((tm).tm_wday + 1), ((tm).tm_yday + 1), \ ((tm).tm_isdst ? ((tm).tm_isdst < 0 ? ", DST?" : ", DST") : "") /** Check one xmktime() result. */ static bool check_one_xmktime(time_t expected) { struct tm tm; if (!localtime_r(&expected, &tm)) { bfs_diag("localtime_r(%jd): %s", (intmax_t)expected, xstrerror(errno)); return false; } time_t actual; return bfs_pcheck(xmktime(&tm, &actual) == 0, "xmktime(" TM_FORMAT ")", TM_PRINTF(tm)) && bfs_check(actual == expected, "xmktime(" TM_FORMAT "): %jd != %jd", TM_PRINTF(tm), (intmax_t)actual, (intmax_t)expected); } /** xmktime() tests. */ static bool check_xmktime(void) { bool ret = true; for (time_t time = -10; time <= 10; ++time) { ret &= check_one_xmktime(time); } // Attempt to trigger overflow (but don't test for it, since it's not mandatory) struct tm tm = { .tm_year = INT_MAX, .tm_mon = INT_MAX, .tm_mday = INT_MAX, .tm_hour = INT_MAX, .tm_min = INT_MAX, .tm_sec = INT_MAX, .tm_isdst = -1, }; time_t time; xmktime(&tm, &time); return ret; } /** Check one xtimegm() result. */ static bool check_one_xtimegm(const struct tm *tm) { struct tm tma = *tm, tmb = *tm; time_t ta, tb; ta = mktime(&tma); if (xtimegm(&tmb, &tb) != 0) { tb = -1; } bool ret = true; ret &= bfs_check(ta == tb, "%jd != %jd", (intmax_t)ta, (intmax_t)tb); ret &= bfs_check(ta == -1 || tm_equal(&tma, &tmb)); if (!ret) { bfs_diag("mktime(): " TM_FORMAT, TM_PRINTF(tma)); bfs_diag("xtimegm(): " TM_FORMAT, TM_PRINTF(tmb)); bfs_diag("(input): " TM_FORMAT, TM_PRINTF(*tm)); } return ret; } /** Check an overflowing xtimegm() call. */ static bool check_xtimegm_overflow(const struct tm *tm) { struct tm copy = *tm; time_t time = 123; bool ret = true; ret &= bfs_check(xtimegm(©, &time) == -1 && errno == EOVERFLOW); ret &= bfs_check(tm_equal(©, tm)); ret &= bfs_check(time == 123); if (!ret) { bfs_diag("xtimegm(): " TM_FORMAT, TM_PRINTF(copy)); bfs_diag("(input): " TM_FORMAT, TM_PRINTF(*tm)); } return ret; } /** xtimegm() tests. */ static bool check_xtimegm(void) { bool ret = true; struct tm tm = { .tm_isdst = -1, }; // Check equivalence with mktime() for (tm.tm_year = 10; tm.tm_year <= 200; tm.tm_year += 10) for (tm.tm_mon = -3; tm.tm_mon <= 15; tm.tm_mon += 3) for (tm.tm_mday = -31; tm.tm_mday <= 61; tm.tm_mday += 4) for (tm.tm_hour = -1; tm.tm_hour <= 24; tm.tm_hour += 5) for (tm.tm_min = -1; tm.tm_min <= 60; tm.tm_min += 31) for (tm.tm_sec = -60; tm.tm_sec <= 120; tm.tm_sec += 5) { ret &= check_one_xtimegm(&tm); } // Check integer overflow cases check_xtimegm_overflow(&(struct tm) { .tm_sec = INT_MAX, .tm_min = INT_MAX }); check_xtimegm_overflow(&(struct tm) { .tm_min = INT_MAX, .tm_hour = INT_MAX }); check_xtimegm_overflow(&(struct tm) { .tm_hour = INT_MAX, .tm_mday = INT_MAX }); check_xtimegm_overflow(&(struct tm) { .tm_mon = INT_MAX, .tm_year = INT_MAX }); return ret; } bool check_xtime(void) { bool ret = true; ret &= check_xgetdate(); ret &= check_xmktime(); ret &= check_xtimegm(); return ret; }