Files
next.js/test/e2e/filesystem-cache/evict-after-snapshot.test.ts
Luke SandbergandGitHub fa7e1a6c38 [turbopack] Don't persist if there is little work to do (#95137)
Currently we skip a persist cycle when there are no dirty tasks. This PR
extends that to skip persist cycles when little compilation work has
happened since the last snapshot, avoiding small writes and expensive
compaction for little benefit.

Compilation time is a better proxy for "how much work a snapshot would
save" than a raw dirty-task count. The background snapshot loop
accumulates active (non-idle) wall-clock time and skips a cycle when
less than a threshold has elapsed since the last persisted snapshot.
Default is 1s, overridable via
`TURBO_ENGINE_SNAPSHOT_MIN_ACTIVE_TIME_MILLIS` (set to `0` in tests).
Shutdown and test snapshots bypass the gate.

The active-time bookkeeping is encapsulated in a small `Stopwatch`
helper.

<!-- NEXT_JS_LLM_PR -->
2026-07-07 22:22:18 -07:00

95 lines
2.7 KiB
TypeScript

import { nextTestSetup, isNextDev } from 'e2e-utils'
import { retry, waitFor } from 'next-test-utils'
// Eviction requires the dev server (HMR) and persistent caching (Turbopack).
// Skip entirely in prod/start mode.
;(isNextDev ? describe : describe.skip)('evict-after-snapshot', () => {
const envVars = [
'ENABLE_CACHING=1',
'TURBO_ENGINE_IGNORE_DIRTY=1',
'TURBO_ENGINE_SNAPSHOT_IDLE_TIMEOUT_MILLIS=1000',
// Persist even tiny snapshots so the test doesn't depend on the
// minimum-compilation-time threshold.
'TURBO_ENGINE_SNAPSHOT_MIN_ACTIVE_TIME_MILLIS=0',
'ENABLE_EVICTION=1',
].join(' ')
const { skipped, next } = nextTestSetup({
files: __dirname,
skipDeployment: true,
packageJson: {
scripts: {
dev: `${envVars} next dev`,
},
},
installCommand: 'npm i',
startCommand: 'npm run dev',
})
if (skipped) {
return
}
async function waitForSnapshotAndEviction() {
// The idle timeout is 1s, give extra time for snapshot + eviction to complete
await waitFor(5000)
}
// Turbopack-only: eviction requires persistent caching
;(process.env.IS_TURBOPACK_TEST ? it : it.skip)(
'should serve correct content after eviction and HMR',
async () => {
const browser = await next.browser('/')
await retry(async () => {
expect(await browser.elementByCss('p').text()).toBe('hello world')
})
let currentContent = 'hello world'
for (let cycle = 1; cycle <= 3; cycle++) {
await waitForSnapshotAndEviction()
const prevContent = currentContent
const nextContent = `cycle ${cycle}`
await next.patchFile('app/page.tsx', (content) =>
content.replace(prevContent, nextContent)
)
currentContent = nextContent
const expected = currentContent
await retry(async () => {
expect(await browser.elementByCss('p').text()).toBe(expected)
}, 10000)
}
await browser.close()
},
90000
)
;(process.env.IS_TURBOPACK_TEST ? it : it.skip)(
'should handle client component HMR after eviction',
async () => {
const browser = await next.browser('/client')
await retry(async () => {
expect(await browser.elementByCss('p').text()).toBe('hello world')
})
await waitForSnapshotAndEviction()
await next.patchFile(
'app/client/page.tsx',
(content) => content.replace('hello world', 'hello eviction'),
async () => {
await retry(async () => {
expect(await browser.elementByCss('p').text()).toBe(
'hello eviction'
)
}, 10000)
}
)
await browser.close()
},
90000
)
})