Repository navigation
test(router): make the HMR recovery test deterministic and its cleanup failure-safe - #1048
Merged
Merged
Conversation
…p failure-safe (#1040) The recovery test asserted the generated file is at last-good right after the rediscovery failure marker. The static route parser writes the new route into that file before the runtime rediscovery fails, and it only returns to last-good when a second change event for the file regenerates it, so the assertion passed or failed on watcher timing. It now checks the runtime manifest, which is what recovery restores, and breaks the helper before adding the route. afterEach wrote and awaited baselines one file at a time; with discovery stuck failed the first wait threw and blog.handlers.tsx stayed broken, so the retry died in beforeAll. It now writes every baseline first, then waits.
…er's doc matches what it does (#1040)
commit: |
19 tasks done
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.
Closes #1040.
Problem
route-types-hmr"recovery: helper-file fix after broken-import error re-runs discovery" failed in about half of local runs, and a failed run lefte2e/test-app/src/urls/blog.handlers.tsxwith the appended syntax error. The serial retry then died inbeforeAll(dirty-tree guard), which is why a failure reported "1 failed, 1 flaky, 19 passed". The suite is local-only (CI skips it).Cause
Two separate defects, both in the test.
The failing assertion was the "still at last-good while discovery is failing" check (line 852 on main), not the sanity check before the edit. The hypothesis in the issue (a rediscovery succeeding between the two writes) is refuted: every captured run has exactly one rediscovery cycle for the test's edits, and it fails. The actual mechanism is in
src/vite/router-discovery.ts: the watcher's debounced handler first runs the static route parser and writes the generated file fromblog.tsx(writeCombinedRouteTypesWithTracking, "watcher: regenerated gen files"), and only then starts the runtime rediscovery that throws. Polling the generated file every 5ms from the writes on, on main:The file holds the new route until a second change event for it triggers
regenerateGeneratedRouteFiles()from the last-good manifest (about 65ms after the marker). A test that reads inside that window fails. In some runs the revert does not happen at all: a 4s probe showed the file keepingblog.recoveredfor the whole window, with the runtime still unable to serve the route. So the generated file's content during the failed state is nondeterministic, and the old assertion encoded a guarantee the product does not give.afterEachrestored the four baselines one at a time, waiting for the dev server to apply each. With discovery failed, the first wait could not succeed untilblog.handlers.tsxwas restored, so the loop threw onblog.tsxand never reached it. The test's ownfinallyrestored onlyblog.tsx, after the assertions that can fail.Change
packages/rangojs-router/e2e/route-types-hmr.test.tsonly; nothing undersrc/.blog.handlers.tsxis broken first (a change the watcher ignores outside recovery mode, nourls(/createRouter), thenblog.tsxgets the route, so the rediscovery that edit triggers can only see the broken helper. (No rediscovery was seen to succeed between the two writes on main; the reorder removes the dependence, it was not the cause.)queryReverse(["blog.recovered"])must not resolve the route (the dev server cannot evaluate the router and answers with an error page; either outcome passes). The final assertions are unchanged: after fixing only the helper, the generated file and the runtime manifest both carryblog.recovered. The runtime check is the one that discriminates recovery from a stale static write.afterEachwrites every modified baseline first, then waits once (one rewrite retry if the watcher missed the event). The test-localtry/finallyis gone;afterEachcovers every file.afterEachthen waits for five consecutive good runtime answers. Dropping thepagefixture fromafterEachremoved an accidental delay that was hiding a second race: after the gen-file recreate tests, the dev server reloads the rsc graph and the next test's firstqueryReversegot an HTML error page ("reverse() should still work after gen file deletion and recreation", seen on the first attempt in 2 of 20 runs before the settle, 0 in 12 after).Numbers
Full
hmr-routessuite, local, other browser suites running on the machine (load 2-12).Every failure on main left
blog.handlers.tsxmodified; after the changegit status --porcelainwas empty after every run, including all failing ones.Test plan
throw, not committed), full suite,--retries=0: 1 failed, 20 passed,git status --porcelainshows no test-app files; an immediate second run starts (no "uncommitted changes" skip) and gives the same 20 passed.mainand the review edits: 5 of 5 clean (21 passed each), tree clean after each.pnpm run typecheck,pnpm run test:unit:all,pnpm run lint,pnpm run format,pnpm check:e2e-bucketing,pnpm check:e2e-parity --strict.tools/e2e-parity-allowlist.jsonuntouched.Notes
writeFileAndAwaitHmrintests/shared-e2e/src/index.tsnow says what the helper does. Not changed: the "during failure" check still treats an erroring query as "not resolved"; asserting that the query errors would pin how a broken dev server answers, which is not what this test is about.