Files
frostbyte/src/rtcTest.c
T
2026-09-11 12:45:09 +02:00

216 lines
7.8 KiB
C

#include "ktest.h"
#include "rtc.h"
/*
* rtc.c gets two different kinds of coverage here, the same split
* ata.c/ataTest.c already established: pure arithmetic (the BCD conversion
* and the civil-date-to-epoch math) is tested with known, hand-verified
* values that need no hardware at all, while rtcReadDateTime/rtcNow are
* exercised against whatever CMOS RTC QEMU actually emulates, so a wiring
* mistake (wrong port, wrong status bit, missed BCD/24-hour branch) shows
* up as an implausible or non-monotonic reading here, not a mocked
* assertion.
*/
KTEST(rtcBcdToBinaryKnownValues)
{
KT_EQ(t, rtcBcdToBinary(0x00), 0);
KT_EQ(t, rtcBcdToBinary(0x09), 9);
KT_EQ(t, rtcBcdToBinary(0x10), 10);
KT_EQ(t, rtcBcdToBinary(0x23), 23);
KT_EQ(t, rtcBcdToBinary(0x59), 59);
}
KTEST(rtcEpochSecondsUnixEpochIsZero)
{
RtcDateTime dt = { .year = 1970, .month = 1, .day = 1, .hour = 0, .minute = 0, .second = 0 };
KT_EQ(t, rtcEpochSeconds(&dt), 0);
}
KTEST(rtcEpochSecondsTimeOfDayComponent)
{
RtcDateTime dt = { .year = 1970, .month = 1, .day = 1, .hour = 1, .minute = 2, .second = 3 };
KT_EQ(t, rtcEpochSeconds(&dt), 1 * 3600 + 2 * 60 + 3);
}
KTEST(rtcEpochSecondsOrdinaryDayIsOneDayApart)
{
RtcDateTime a = { .year = 2026, .month = 9, .day = 7, .hour = 0, .minute = 0, .second = 0 };
RtcDateTime b = { .year = 2026, .month = 9, .day = 8, .hour = 0, .minute = 0, .second = 0 };
KT_EQ(t, rtcEpochSeconds(&b) - rtcEpochSeconds(&a), 86400);
}
/* The single most common bug in hand-rolled calendar math: getting the
leap-year rule wrong. 2024 is a leap year (divisible by 4, not by a
century that isn't also divisible by 400), so February has 29 days and
Feb 28 -> Mar 1 must be a two-day gap, not one. */
KTEST(rtcEpochSecondsLeapYearFebruaryHas29Days)
{
RtcDateTime feb28 = { .year = 2024, .month = 2, .day = 28, .hour = 0, .minute = 0, .second = 0 };
RtcDateTime mar1 = { .year = 2024, .month = 3, .day = 1, .hour = 0, .minute = 0, .second = 0 };
KT_EQ(t, rtcEpochSeconds(&mar1) - rtcEpochSeconds(&feb28), 2 * 86400);
}
/* 2023 is not a leap year (not divisible by 4): the same Feb28 -> Mar1
comparison must collapse to exactly one day, proving the non-leap branch
is not just always adding the leap-year's extra day. */
KTEST(rtcEpochSecondsNonLeapYearFebruaryHas28Days)
{
RtcDateTime feb28 = { .year = 2023, .month = 2, .day = 28, .hour = 0, .minute = 0, .second = 0 };
RtcDateTime mar1 = { .year = 2023, .month = 3, .day = 1, .hour = 0, .minute = 0, .second = 0 };
KT_EQ(t, rtcEpochSeconds(&mar1) - rtcEpochSeconds(&feb28), 86400);
}
/* A century year is a leap year only when also divisible by 400: 2000 is
(400 | 2000), 1900 is not. This is the structural part of the rule the
simpler "divisible by 4" tests above cannot distinguish on their own. */
KTEST(rtcEpochSecondsCenturyLeapRule)
{
RtcDateTime feb28_2000 = { .year = 2000, .month = 2, .day = 28, .hour = 0, .minute = 0, .second = 0 };
RtcDateTime mar1_2000 = { .year = 2000, .month = 3, .day = 1, .hour = 0, .minute = 0, .second = 0 };
KT_EQ(t, rtcEpochSeconds(&mar1_2000) - rtcEpochSeconds(&feb28_2000), 2 * 86400);
RtcDateTime feb28_1900 = { .year = 1900, .month = 2, .day = 28, .hour = 0, .minute = 0, .second = 0 };
RtcDateTime mar1_1900 = { .year = 1900, .month = 3, .day = 1, .hour = 0, .minute = 0, .second = 0 };
KT_EQ(t, rtcEpochSeconds(&mar1_1900) - rtcEpochSeconds(&feb28_1900), 86400);
}
KTEST(rtcCivilFromEpochUnixEpochIsZero)
{
RtcDateTime dt;
rtcCivilFromEpoch(0, &dt);
KT_EQ(t, dt.year, 1970);
KT_EQ(t, dt.month, 1);
KT_EQ(t, dt.day, 1);
KT_EQ(t, dt.hour, 0);
KT_EQ(t, dt.minute, 0);
KT_EQ(t, dt.second, 0);
}
KTEST(rtcCivilFromEpochTimeOfDayComponent)
{
RtcDateTime dt;
rtcCivilFromEpoch(1 * 3600 + 2 * 60 + 3, &dt);
KT_EQ(t, dt.year, 1970);
KT_EQ(t, dt.month, 1);
KT_EQ(t, dt.day, 1);
KT_EQ(t, dt.hour, 1);
KT_EQ(t, dt.minute, 2);
KT_EQ(t, dt.second, 3);
}
/* Negative epoch (a date before 1970): -1 second must land on the very
last second of 1969, not silently truncate toward 1970-01-01 the way a
naive C division (which rounds toward zero) would. */
KTEST(rtcCivilFromEpochNegativeBeforeEpoch)
{
RtcDateTime dt;
rtcCivilFromEpoch(-1, &dt);
KT_EQ(t, dt.year, 1969);
KT_EQ(t, dt.month, 12);
KT_EQ(t, dt.day, 31);
KT_EQ(t, dt.hour, 23);
KT_EQ(t, dt.minute, 59);
KT_EQ(t, dt.second, 59);
rtcCivilFromEpoch(-86400, &dt);
KT_EQ(t, dt.year, 1969);
KT_EQ(t, dt.month, 12);
KT_EQ(t, dt.day, 31);
KT_EQ(t, dt.hour, 0);
KT_EQ(t, dt.minute, 0);
KT_EQ(t, dt.second, 0);
}
/* The exact leap-year civil date rtcEpochSecondsLeapYearFebruaryHas29Days
above already proves is one apart from March 1st -- rtcCivilFromEpoch
must recover 2024-02-29 exactly, not silently normalize it into
2024-03-01 (the classic off-by-one a wrong leap rule would produce). */
KTEST(rtcCivilFromEpochLeapDayRoundTrip)
{
RtcDateTime leapDay = { .year = 2024, .month = 2, .day = 29, .hour = 12, .minute = 30, .second = 45 };
int64 epoch = rtcEpochSeconds(&leapDay);
RtcDateTime back;
rtcCivilFromEpoch(epoch, &back);
KT_EQ(t, back.year, 2024);
KT_EQ(t, back.month, 2);
KT_EQ(t, back.day, 29);
KT_EQ(t, back.hour, 12);
KT_EQ(t, back.minute, 30);
KT_EQ(t, back.second, 45);
}
/* Round trip through both directions (rtcEpochSeconds then
rtcCivilFromEpoch) across a spread of dates spanning several years,
month/year boundaries, and a century-leap year -- proves the two
functions are genuine inverses of each other over a real range, not
just individually correct at a handful of hand-picked points. */
KTEST(rtcCivilFromEpochRoundTripsAcrossManyDates)
{
RtcDateTime samples[] = {
{ 1970, 1, 1, 0, 0, 0 },
{ 1999, 12, 31, 23, 59, 59 },
{ 2000, 1, 1, 0, 0, 1 },
{ 2000, 2, 29, 6, 7, 8 },
{ 2023, 2, 28, 18, 0, 0 },
{ 2023, 3, 1, 0, 0, 0 },
{ 2026, 9, 7, 17, 30, 0 },
{ 2099, 12, 31, 23, 59, 59 },
};
for (uint64 i = 0; i < sizeof(samples) / sizeof(samples[0]); i++) {
int64 epoch = rtcEpochSeconds(&samples[i]);
RtcDateTime back;
rtcCivilFromEpoch(epoch, &back);
KT_EQ(t, back.year, samples[i].year);
KT_EQ(t, back.month, samples[i].month);
KT_EQ(t, back.day, samples[i].day);
KT_EQ(t, back.hour, samples[i].hour);
KT_EQ(t, back.minute, samples[i].minute);
KT_EQ(t, back.second, samples[i].second);
}
}
KTEST(rtcReadDateTimeReturnsPlausibleFields)
{
RtcDateTime dt;
KT_TRUE(t, rtcReadDateTime(&dt));
KT_GE(t, dt.year, 2020);
KT_LE(t, dt.year, 2099);
KT_GE(t, dt.month, 1);
KT_LE(t, dt.month, 12);
KT_GE(t, dt.day, 1);
KT_LE(t, dt.day, 31);
KT_GE(t, dt.hour, 0);
KT_LT(t, dt.hour, 24);
KT_GE(t, dt.minute, 0);
KT_LT(t, dt.minute, 60);
KT_GE(t, dt.second, 0);
KT_LT(t, dt.second, 60);
}
KTEST(rtcNowIsNeverNegativeOrObviouslyWrong)
{
/* rtcEpochSecondsUnixEpochIsZero above already proves the math side
hits exactly 0 for 1970-01-01; a real read of a QEMU-emulated CMOS
clock in 2026 should be nowhere near that, so a driver that
accidentally reads all-zero registers (unplugged/uninitialized
hardware, a wrong port) is easy to tell apart from a real one here. */
int64 now = rtcNow();
KT_GT(t, now, 0);
/* 2020-01-01T00:00:00Z, a fixed sanity floor well before this test can
ever plausibly run. */
KT_GT(t, now, 1577836800);
}
KTEST(rtcNowIsMonotonicAcrossConsecutiveReads)
{
int64 first = rtcNow();
/* Real port I/O, not a busy-loop the compiler could fold away: two
fresh hardware reads a few microseconds apart must never see time
run backwards. */
for (volatile int i = 0; i < 20000; i++) { }
int64 second = rtcNow();
KT_GE(t, second, first);
}