next.js
8fbe9509 - Add tests for use cache over-invalidation and under-invalidation in dev

Commit
13 days ago
Add tests for use cache over-invalidation and under-invalidation in dev In dev, "use cache" entries must be discarded exactly when something they can depend on may have changed. Pre-existing behaviors violate this in both directions, and this commit adds failing tests for them. Over-invalidation: adding or removing any page file — and every dev server start — currently discards all cached entries on webpack. The dev-only HMR refresh hash is part of every "use cache" key, and the hot reloader refreshes it on any rebuild that follows `invalidate({ reloadAfterInvalidation: true })`. setup-dev-bundler passes `reloadAfterInvalidation: envChange`, but `envChange` is also set whenever the client router filter changes: on every start (the first watcher pass computes the filter for the first time) and whenever a page file is added or removed. Neither affects cached data. The start-time refresh lands right after the first compile finishes; when a page request beats it, the entries that request just cached are discarded and the next reload is silently cold. That race is the cause of the cache-components-dev-warmup flakes on loaded machines (Prerender labels flipping to Server on warm reloads). The race can't be reproduced deterministically, so the coverage goes through the page add trigger, which takes the same code path: this commit tightens the page add/remove notification suite's re-fetch count, so an open tab must re-fetch exactly once when a page is added, on both bundlers, where the test previously expected the announcement re-fetch plus the false alarm's. Turbopack has the same bug on different timing: it advances its hash once per change-subscription emission, and an added or removed page's subscription can emit without an edit. A later PR in the stack derives the hash from the compiled output, closing that hole by construction, and tests the cached entries directly there. Turbopack does react to the false alarm in another way, though: it clears the server's module cache, so a page add resets module state. A test pins this with a module-scope render counter that must keep counting across an unrelated page add. It's Turbopack-only: on webpack, adding a page recompiles the server bundle (the compiled-in client router filter changes), which replaces the serving module instances regardless. Under-invalidation: entries persisted by a custom cache handler are currently served across dev server runs, even though the code may have changed while the server was down. Before the first edit the hash is unset (webpack) or a constant ("0" in Turbopack), so keys collide across runs; after an edit, webpack keys carry a content-derived `stats.hash`, so a run that re-applies an edit a previous run also made collides with that run's persisted entries. One test covers the pre-edit collision via a restart (guarding against a vacuous pass by checking that entries were actually persisted), and one covers the post-edit collision by re-applying a previous run's edit. Two more tests pin down behavior that must survive a fix: an env file change that the page's output depends on must still invalidate cached entries, and an env file change that doesn't affect compiled output must still make the browser refetch server components without a manual reload. Without the fixes, the re-applied edit test and the re-fetch count test fail on webpack on an idle machine, and the re-fetch count and module state tests fail on Turbopack the same way. The restart test fails on both, except when the two webpack runs happen to cache on opposite sides of their start-time refresh — the refresh swaps the unset hash for a content-derived one, so entries keyed on opposite sides don't collide, and the cross-run reuse this test is about doesn't occur that run.
Author
Committer
Parents
Loading