Final QA: year-rollover dry run #8
Labels
No labels
area/ai
area/backend
area/frontend
area/infra
area/scheduler
area/wled
good-first-issue
priority/high
priority/low
priority/medium
type/bug
type/chore
type/ci-cd
type/docs
type/feature
type/qa
v1.0.0
v1.1.0
v1.2.0
v2.0.0
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/Iris-WLED#8
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
Confirm the year boundary is handled without a restart: new-year events surface and the yearly Jan 1 job runs.
Steps
Acceptance criteria
Part of the M10 final QA pass. Related: deferred year-rollover scheduled job issue.
Picking this up.
Adjusting the method
The steps say "set the system clock to Dec 31 23:55." That isn't directly possible in a container — it shares the host clock, and changing it would need
CAP_SYS_TIMEand would affect every other stack on that host. So instead: a disposable container running the real:mainimage withlibfaketimepreloaded, started at a faked 2026-12-31 23:55 and left to roll over on its own.Deliberately isolated from anything real:
WLED_HOSTunset, so it cannot reach the controllerlibfaketimefakes the wall clock but not the monotonic clock, which is what we want here: APScheduler computes when to fire from the faked wall clock, then waits on a real monotonic timer — so the 23:55 → 00:05 window takes ten real minutes rather than being skipped instantly. The jobs have to genuinely fire.What I'll be checking
Beyond the stated criteria, there are specific things worth watching given #106 was a date-boundary bug that passed tests and manual invocation and only failed across a real midnight:
reschedule_daily(00:01) thenyearly_job(00:05) — four minutes apart, on the one night when both cross a year. Their ordering has never been exercised.get_active_timesnow readstoday - 1 day(from #106's fix), which on Jan 1 reaches into the previous year.run_yearly_jobderivesyearsfromdate.today().yearat execution time, so it depends on having actually crossed midnight rather than on when it was scheduled.yearslist once at startup.Recurrence tables run to 2035 (#53), so the lookups themselves should be fine; the risk is in cache and scheduling state, not the data.
Year rollover passes
Ran in a disposable container on the real
:mainimage withlibfaketimepreloaded (FAKETIME="+10496437s"), started at a faked 2026-12-31 23:56 CST and left to cross midnight on its own. Isolated: fresh DB,WLED_HOSTunset, own port. Because the monotonic clock is not faked, the nine faked minutes to 00:05 took nine real minutes — every job had to genuinely fire rather than be skipped.Before (23:56 on Dec 31)
After (00:07 on Jan 1)
No errors or warnings anywhere in the run.
Acceptance criteria
populate_db+refresh_cacheinrun_yearly_job.iris_20270101T060500Z.dbwritten before any mutation, and candidates generated for both boundary years (169 schemes across 2027/2028).Steps
Worth noting
The #106 fix holds across a year boundary, which I had not specifically tested.
off_jobwas pending at2027-01-01 07:20whenreschedule_dailyran four minutes into 2027, and it survived —get_active_timesreaches back totoday - 1 day, which on Jan 1 crosses into the previous year. Pre-#106 that job would have been pushed to Jan 2 and the lights would have stayed on through New Year's Day. This test would have caught #106 independently.reschedule_daily(00:01) andyearly_job(00:05) ran in that order without interfering: the reschedule set up Jan 1's cycle, and the yearly job then rebuilt the event/scheme set for 2027–2028 without disturbing it.One thing not verified: the
review_queue_readywebhook dispatch itself. No webhook endpoints are configured in a throwaway environment, so that path was a no-op. The substantive half — candidate generation — is confirmed; webhook delivery is covered bytest_webhooks.py.Harness torn down. Closing.