fix(testing): run microtasks between timers in FakeTime async advancement - #7310
Open
tomas-zijdemans wants to merge 2 commits into
Open
fix(testing): run microtasks between timers in FakeTime async advancement#7310tomas-zijdemans wants to merge 2 commits into
tomas-zijdemans wants to merge 2 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7310 +/- ##
=======================================
Coverage 95.03% 95.04%
=======================================
Files 617 618 +1
Lines 51637 51656 +19
Branches 9359 9363 +4
=======================================
+ Hits 49075 49094 +19
Misses 2021 2021
Partials 541 541 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.
tickAsync(),nextAsync()andrunAllAsync()now drain microtasks after every timer callback, so a promise continuation created by a timer at 10 ms sees the clock at 10 ms instead of at the tick target.Problem
tickAsync(ms)drained microtasks once, then handed off to the synchronousnowsetter, which ran every due callback in one loop with no microtask checkpoint in between. Three consequences, each reproduced in the new tests:tickAsync(20).runAllAsync()produceda, b, microtaskwhile its docs promised microtasks before each timer.runAllAsync()with an empty due tree returned immediately. A pending microtask that would have scheduled a timer never got its timer run.Anything that chains
awaitwith timers (retry loops, debouncers, caches with TTL) was being tested against a schedule the runtime never produces.Change
The
nowsetter's loop body moves intorunNextTimer(limit), which runs one live timer at or beforelimitand returns whether it did. The setter calls it in a loop, unchanged in behavior. The async paths share a private#advanceAsync(target): run one timer, drain microtasks, repeat until nothing is due at or beforetarget, then setnow = target. Timers scheduled during the loop that land inside the target take part.nextAsync()drains before checking the tree and advances to the next deadline through the same loop.runAllAsync()now loops onnextAsync()until it returns false.Synchronous
tick(),next(),runAll()and thenowsetter are untouched. FIFO order for equal deadlines is preserved. A callback's returned promise is not awaited, since it may depend on another timer in the same advancement.Compatibility
This changes observable behavior of stable APIs. The old
tickAsync()doc described the narrow behavior accurately, so callers could have relied on it. Two tests inasync/retry_test.tsdid: one asserted that the retry promise was still pending right after the final timer fired, the other attached itsassertRejectshandler after the advancement that rejects the promise, which now surfaces as an unhandled rejection. Both are updated. I think this is afixsincerunAllAsync()already documented the per-timer contract and the old order can't occur under a real event loop, but happy to retitle if you'd rather treat it as breaking.Tests
Nine new cases in
testing/time_test.ts, seven of which fail onmain:runAllAsync()gaps aboveLint, format check and the full test suite pass.