Repository navigation
test(net): Manual.drive drives mocked I/O; driveUntil is deleted - #8226
Conversation
Quest-Claim: 2faea0dfa131390e
Mocked transport deliveries are WHATWG stream promise reactions, so the drain `Manual.drive` runs before each deadline already lands them. The two cases that looked like uncounted host I/O were a cold dynamic `import()` (reload.test now loads the IETF module up front) and bun's `expect(...).rejects`, which spins the event loop nested ahead of the caller's queued microtasks (connect.test now drives before asserting). Every driveUntil caller now passes drive a promise; predicate callers await the signal their predicate read through a shared `waitFor`, which also replaces the copies in auth.test and integration.test. mock.test pins the three required properties: a queued round trip lands before its deadline and time stops at the next one, a read with nothing in flight times out at its deadline, and a peer keeping the stream full never starves a deadline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 6 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed 859d52a.
No actionable findings. Replacing millisecond polling with promises for the actual signals and route events preserves the reconnect and delivery assertions while letting the existing clock drain control deadline order. Loading the IETF module before driving and asserting rejection only after the driven promise settles keeps those host-runtime concerns out of the clock implementation. The new regressions distinguish queued delivery, an idle read timeout, and continuous traffic without adding task accounting that would prevent time from advancing.
Direction: keep the existing clock mechanism and the event-driven test helpers. The documented Bun assertion caveat and proposed guard remain useful follow-up work, not a reason to add another clock workaround.
Verification limits: GitHub-only static review of the complete diff and relevant Manual/drain, mock streams, signal notifications, reconnect and tail-test context. No commands, tests, runtime probes or mutation tests run; the reported Bun scheduling and stress results were not independently reproduced. Rechecked open/non-draft state, full head and published reviews immediately before this COMMENT.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Manual.drivedocumentation — two doc lines injs/clock/src/manual.ts: promise-based I/O lands before each deadline, a coldimport()may not, and callers must awaitdrivedirectly rather than inside bun'sexpect(...).rejects. No mechanism added; the PR's proof thatMockTransportdeliveries are promise reactions covered bydrain()holds up under the new tests.driveUntilreplaced bywaitForinjs/net/src/mock.ts— the helper now resolves once aGetterpassesready, with the two duplicated copies inauth.test.tsandintegration.test.tsimporting it instead.- Predicate callers await the event instead of polling —
reload.error,reload.established,watched.active,watching.synced, and test-local counters/lists (attempts,dialed,servers) becomeSignals; a newpresent(routes)helper tracks aRoutesstream into a path set, replacingwireOf(...).routes(path)polling. - Promise callers now
clock.drivethe work —tail,ietf/tail,lite/datagram-tracks, andconnectassert ondrive's return value instead of awaiting the same promise again;connect.test.tsdrivesconnecting.catch(() => {})and keeps the rejection assertion on the original promise. - Eager
../ietf/connection.tsimport inreload.test.ts— a documented workaround for Bun resolving a cold dynamic import outside the task queue. - Three new
mock.test.tstests — a queued round trip before its deadline with time stopping at the next sleep (60 s timer left unfired), a read timing out exactly at 1 s, and a peer refilling the stream queue every host turn not starving a 1 s deadline. - Quest bookkeeping — the completed child quest file and its README
## Requiredentry are removed, consistent with how other quests land.
Verification
Ran bun test on every touched file plus integration.test.ts and js/clock: all pass (mock 7, reload/tail/ietf-tail 64, connect/datagram-tracks/auth 222, integration 106 pass 1 skip, clock 37). tsc --noEmit in js/net and biome check --error-on-warnings js/net js/clock are clean, and git grep driveUntil finds nothing. A mutation check confirms the first new test guards the drain: replacing await drain() with await Promise.resolve() in Manual.#advance fails it (at jumps to 60 001 ms). The dropped sessions.length > 1 && guard in the watcher test is also safe — re-adding expect(sessions.length).toBeGreaterThan(1) after the two synced waits passed under --rerun-each, so !synced→synced still implies a fresh session's initial set.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

Finishes quest/m1/mock-clock-tests/manual-drive-host-io.md, a child of the mocked-clock epic.
Summary
Manual.drivestops at the deadline the driven work reaches, even when the work first waits on a mocked transport delivery.js/net'sdriveUntilis deleted. Every caller now passesdrivea promise.No clock change was needed. The quest assumed a mock delivery resolves on a later host task that the clock doesn't count. I reproduced each
driveUntilcaller on plaindriveand found this isn't so.MockTransportis built on WHATWG streams, and their deliveries are promise reactions. Thedrain()thatdriveruns before each deadline already includes them. A probe confirmed it: a mock bidi round trip and apipeThroughboth settle within one drain. Two other things caused the early deadlines thatdriveUntilworked around:import(). The IETF handshake loadsietf/connection.tson first use. Bun resolves a cold import outside the task queue (a probe spinning MessageChannel tasks never saw it settle). A warm import settles in one drain. The quest puts this out of scope, soreload.test.tsnow imports the module up front.expect(promise).rejects. For a pending promise, it spins the event loop nested inside the call, ahead of microtasks the caller already queued. The connect-timeout failure test(net): js/net tests run on a mocked clock #8165 described ("the client needs a second host turn to read the server's SETUP") is this: underawait expect(clock.drive(connecting)).rejects, the 30 s connect timeout fired before the client decodedSERVER_SETUP. In a minimal repro, gated work rejectedat 110instead ofat 10.connect.test.tsnow callsdrivefirst and asserts on the settled promise.Manual.drive's doc says to await it directly.Changes
js/net/src/mock.ts:driveUntilis replaced bywaitFor(getter, ready), which resolves once a signal holds a value that passesready. Identical copies inauth.test.tsandintegration.test.tsnow import it.reload.test.ts) now passdrivea promise that resolves on the event the predicate read. This follows the epic's "await the event it means" rule. Thereload.error,reload.established,watched.active, andwatching.syncedchecks wait on those signals directly. Counters and dial lists becameSignals. The origins test watches its route streams through a smallpresent(routes)set instead of pollingwireOf(...).routes(path). The watcher test'ssessions.length > 1 &&guard is dropped: it waits for!syncedand thensynced, which already means a new sync.tail,ietf/tail,lite/datagram-tracks,connect) useclock.drive. Where a test awaited the same promise again after driving it, it now asserts ondrive's return value.js/net/src/mock.test.ts:Elapsedexactly at 1 s;js/clock/src/manual.ts: two doc lines ondrive. Stream I/O lands before each deadline; a dynamic import may not; and await it directly rather than inside bun'sexpect(...).rejects.I checked that the tests catch real regressions. If
driveskips its drain (await Promise.resolve()instead ofawait drain()), the first test fails. If each mock write is counted as a clock task (await yieldNow()in the sink), which is the approach planned in earlier drafts of this quest, the third test starves and times out. An earlier version of that test wrote one chunk per turn, and the counting mutation slipped past it. Keeping the queue full is what makes it catch the mutation.Public API: none.
Manual.drivegains doc lines only.mock.tsis test support that the package doesn't export:driveUntilis removed andwaitForadded, andjs/cryptoimports neither. Wire: none.Choices made without asking
Manualunchanged instead of adding a mechanism. The quest left the mechanism to this PR. The probes found no mocked I/O fordriveto wait on, and any form of counting continuous deliveries starves a deadline, which the third required test now rules out.waitForis shared inmock.ts, not local toreload.test.ts. Two test files already had identical copies.elapsed < GRACE, notelapsed == 0. That is the property they claim ("nothing waited out the grace"). An exact zero would break on any unrelated short timer.Validation
bun testinjs/net: 3131 pass, 0 fail, after mergingorigin/main(cb3d764410).js/clock: 37 pass.quest gate bun test --rerun-each 10runs over the changed files other thanconnect.test.ts(reload,tail,ietf/tail,lite/datagram-tracks,auth,integration,mock) passed 3420 of 3420 each. Five plainconnect.test.tsruns passed. That file fails under--rerun-eachonmaintoo, because of its module-level WebSocket preference cache.mock.test.ts --rerun-each 20: 140 pass.bun biome check --error-on-warnings js/net js/clockpasses, and so doesbun run checkinjs/netandjs/clock.quest check --base origin/mainpasses.git grep driveUntilfinds nothing.Models
Size: M
Implementer: Claude Code, claude-opus-5-5, high effort
Follow-ups
expect(clock.drive(...)).rejects/.resolves(plan). Bun's nested event loop silently breaksdrive's ordering, and the failure looks like a clock bug. A biomenoRestrictedSyntaxrule or a check in the@moq/clockpreload could reject the pattern. Today the only warning is the doc line.(Written by Claude Opus 5.5 with high)
🤖 Generated with Claude Code
Review
Rewritten by
quest reviewafter each round.01a1b8ad-7213-78a3-947d-72398e2d3d4a, round 1The promise-driven waits match the signal and mock-stream implementations with no actionable regressions found; builds and tests were not run, as instructed.