Make the walking skeleton fast by injecting its clocks (CL-7250) - #512
Merged
Merged
Conversation
CL-7250: the walking-skeleton e2e suite's routine-scheduling tests must wait out the hub's real 30s setInterval cadence for real, since fake timers in the test process don't reach the hub's spawned child process. Thread an optional pollIntervalMs through createRoutineScheduler (env ROUTINE_SCHEDULER_POLL_INTERVAL_MS, parsed at config.ts's arktype boundary; unset keeps the real 30s production cadence), and prove both the default and the override in-process with Bun's fake timers rather than a live hub.
CL-7250: routine-repeat.test.ts and routine-trigger-input.test.ts (its "scheduled fire" case) previously had to wait out the hub's real 30s routine-scheduler setInterval for real, since fake timers in the test process never reach the spawned hub child. startHub() now sets ROUTINE_SCHEDULER_POLL_INTERVAL_MS=300 for every spawned test hub by default; a caller's extraEnv can still override it back to the real cadence. No assertion changes — the same HTTP responses and database state are observed sooner. Measured at matched load (~4-10): routine-trigger-input.test.ts drops from 38.0s to 15.6s.
… tests CL-7250: every condition-polling loop across the slow e2e suites (sidecar-not-ready 502 retries, deployment/mailbox/run-appears polls) slept a fixed 500ms-1000ms between checks, so a loop that resolved after its first real retry still paid most of a full interval before noticing. Tightened every such loop to 200ms so it returns as soon as the condition holds instead of averaging most of a full second per retry. No assertion changes — same deadlines, same failure messages, just checked more often. Also fixes a stale comment in routine-trigger-input.test.ts's scheduled-fire case, which still claimed the scheduler's real 30s poll interval was "the only wait this needs" after the prior commit made the e2e harness inject a 300ms interval instead.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The walking-skeleton e2e suite (40 tests, 314s in CI) is about to become CI's critical path once #CL-7226 lands (typecheck drops to ~2min). The cost is concentrated in tests that sleep, not tests that do real work — the top five are 45% of the total time.
The instinct to delete the slow tests is wrong: they're the highest-value coverage in the repo (scheduling, onboarding, webhook delivery). This PR speeds them up without touching a single assertion.
Key finding (from CL-7250's own investigation comment): Bun 1.3.14's fake timers (
jest.useFakeTimers/advanceTimersByTime) work, but only in-process — the e2e harness spawns the hub as a separate child process (Bun.spawn), so fake timers in the test process never reach it. That splits the fix in two:Scheduler decision logic was already in-process.
apps/hub/test/routine-scheduler.test.tsalready drovetickRoutineSchedulerdirectly against an in-memory store, covering claim-and-fire, backoff, retry-after-backoff, and dead-letter-at-max — in milliseconds. No gap there; confirmed, not newly added.The real e2e cost was the hub's hardcoded 30s
setInterval(apps/hub/src/routine-scheduler.ts'sPOLL_INTERVAL_MS), which spawned-hub tests had to wait out for real. Made it injectable viaROUTINE_SCHEDULER_POLL_INTERVAL_MS(parsed atconfig.ts's one arktype env boundary; production default unchanged at 30s), and proved both the default and the override entirely in-process with Bun's fake timers — no live hub needed to verify this wiring. The e2e harness now defaults every spawned test hub to a 300ms interval.Tightened every fixed retry-sleep loop (sidecar-not-ready 502 retries, mailbox/deployment-appears polls) from 500-1000ms to 200ms across the slow suites, so a loop returns as soon as its condition holds instead of averaging most of a full interval per retry.
No test was deleted, skipped, or had an assertion removed or weakened. Same HTTP responses, same database state — observed sooner.
Commits
Make the routine scheduler's poll interval injectable— production change + 2 new fake-timer tests proving default/override cadence in-processe2e harness: default spawned test hubs to a fast scheduler poll interval— harness wiringe2e: tighten fixed retry-sleep intervals in the slow walking-skeleton tests— no production changeTimings
This machine runs several concurrent agent workloads, so load average swings 4x-8x between runs — noted per measurement. CI's numbers will differ, but the relative improvement should hold since it comes from waiting less wall-clock time for the same real work, not from a faster machine.
Controlled comparison (same machine, same 10-second window, load ~4-10 for both runs — the only fully apples-to-apples pair I could get given the noise):
routine-trigger-input.test.tsIndividual runs (load noted, not directly comparable to each other or to the CI-reported baseline due to machine noise):
Full suite (40 tests): 280s on this machine at load ~5, vs. the ticket's reported 314s CI baseline. A reliable before/after full-suite comparison on this box wasn't possible — a matched-load control run of the unmodified suite came back at 185s, faster than my "after" run at lower load, purely from other processes' noise swamping the signal at that granularity. The single-test controlled comparison above is the trustworthy number; CI will give the real full-suite before/after.
What still needs a live hub
routine-trigger-input.test.ts's three fires (run-now, scheduled, webhook) andsmoke-webhook.test.ts's delivery still spawn a real hub+sidecar+Postgres, because they prove the actual mail-delivery wiring (routine-launcher.ts'ssendFoldedMailWithRetry, webhook trigger HMAC verification, insights trace population) — none of that has an in-memory-store equivalent, nor should it; it's exactly the coverage the walking skeleton exists for.Nothing recommended for deletion
Every test kept its assertions. No coverage gap was found or created.
Unmet outcomes from the ticket
Interchange defects found
None.