Repository navigation
Conversation
End-to-end tests run protocol code on the real-time DefaultScheduler, so every timer-driven assertion waits for real time to pass and reads a clock the test does not control. NetworkContext already takes an external scheduler, but nothing in the test targets implements one that runs timers. VirtualTimeScheduler implements NetworkContext.Scheduler on a clock the test owns. Immediates queue until a driver method drains them, timers fire in deadline order with `now` set to each deadline before its task runs, and runningInScheduler is true only inside a task so the context's own assertion still catches state touched from outside. Timers are kept sorted and found by binary search, so arming or cancelling thousands of them stays fast, and a delay too long for the clock saturates the way DefaultScheduler clamps one. A step limit stops a driver call that still has work after that many tasks, and the stall handler is replaceable so the guard itself can be tested. Harness only: nothing under Sources/SwiftNetwork changes and no dependency is added.
Collaborator
|
We already have an open PR, #191, in the repository that adds mocked time, and additionally it looks like this change is directly taking code from that (identical comments in some places). Please wait for that to go in. |
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.
Part of #229.
Motivation
End-to-end tests run protocol code on the real-time
DefaultScheduler, so every timer-driven assertion waits for real time to pass and reads a clock the test does not control.NetworkContext(identifier:externalScheduler:)already lets a test inject a scheduler, but nothing in the test targets implements one that runs timers: the existing doubles inSwiftNetworkContextTestsrecord or advance a clock and arm nothing.Changes
Adds
VirtualTimeSchedulertoSwiftNetworkTestHarness, exported under@_spi(TestHarness)like the rest of the harness API. It implementsNetworkContext.Scheduleron a clock the test owns:runImmediatequeues; nothing runs on the caller's stack, as the protocol's doc comment requires.schedulearms a timer atnowplus the delay and replaces an earlier timer under the same reference, mirroringDefaultScheduler'sTimerList.insert. Equal deadlines fire in insertion order. A delay too long for the clock saturates atInstant.maximuminstead of trapping.nowis set to a timer's deadline before its task runs, so a deadline the library just reached looks reached.nowAbsolutestays a fixed 4000 ms ahead, the same splitAdvancingScheduleruses so a context that confuses the clocks fails loudly.runningInScheduleris true only inside a task, soEventContext.assert()still catches library state touched from outside one.Driver API, all on the test's thread:
runUntilIdle(),advance(by:),advance(to:),runUntilNextTimer(),nextDeadline,pendingTimerCount. A step limit (default 100 000 tasks per driver call, settable ininit) trips a precondition failure when a call has run that many tasks and more are still waiting, so a self-rescheduling loop fails instead of never ending. Calling a driver method from inside a task is a precondition failure.Harness only: nothing under
Sources/SwiftNetworkchanges and no dependency is added.Tests
Tests/SwiftNetworkTests/VirtualTimeSchedulerTests.swift, 15 cases: immediate ordering and never running on the caller's stack, deadline order withnowequal to the deadline, timers armed mid-advance, queued work running before each timer, unschedule and re-arm under one reference, a seeded random run of 5000 re-arms and unschedules,runningInSchedulerinside and outside tasks, the stall guard, a delay beyond the clock, both clocks moving together, and an end-to-end case that arms aNetworkContexttimer from inside a context task and sees it fire at the virtual deadline.Measurements
Setup: Apple M1, 8 cores, 16 GB, macOS 26.6.2, Swift 6.4, debug build,
swift test.This PR:
What it enables. These numbers come from a local experiment that routes the
QUICTestHarnesswaits through the scheduler. It is not part of this PR and was measured at c8ecea0:SwiftNetworkTestsTwo virtual runs give identical timelines for every harness test. The details and the open questions for that follow-up are in the issue.