diff --git a/docs/testing.md b/docs/testing.md index 887d601b45..f249bbb04f 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -983,9 +983,16 @@ runner. Preserve simultaneous launches where concurrency is itself under test. Build/documentation writers hold checkout-local output locks. Tests do not hold a broad command lock; `check:pr` finishes tests before document validation. -Vitest and Node persistent compile caches are deliberately not enabled by -default. To evaluate repeated local runs, opt in for both cold and warm -measurements with an isolated cache: +Vitest's persistent compile cache remains disabled. The `cliTest` fixture shares +a temporary Node compile cache among its fresh CLI subprocesses. The worker +removes it after test-scoped processes finish. Explicit child environment +settings take precedence; children requesting `NODE_V8_COVERAGE` do not receive +the fixture cache. These subprocesses are outside aggregate Vitest coverage; +subprocess coverage attachment remains disabled. Cold-start import checks launch +independently without this fixture cache. + +To evaluate persistent compile caching for repeated local runs, opt in for both +cold and warm measurements with an isolated cache: ```bash NODE_COMPILE_CACHE=.cache/node-compile npm test diff --git a/tests/boundary/cli/cliCompileCache.test.ts b/tests/boundary/cli/cliCompileCache.test.ts new file mode 100644 index 0000000000..c2587d9ca9 --- /dev/null +++ b/tests/boundary/cli/cliCompileCache.test.ts @@ -0,0 +1,57 @@ +import { readdir } from "node:fs/promises"; + +import { expect } from "vitest"; + +import { PRODUCT_IDENTITY } from "../../../src/identity.js"; +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; +import { cliTest, createTestCli } from "../../support/cli/cliFixture.js"; + +cliTest( + "reuses an owned CLI compile cache and preserves a caller-selected cache", + async ({ cli, cliCompileCache }) => { + const cached = await cli.run({ arguments: ["--version"] }); + expect(cached.exitCode).toBe(0); + expect(cached.stdout).toContain(PRODUCT_IDENTITY.packageVersion); + expect(await readdir(cliCompileCache)).not.toHaveLength(0); + + const selected = await createTestTempDirectory("rea-cli-selected-cache-"); + const overridden = await cli.run({ + arguments: ["--version"], + environment: { NODE_COMPILE_CACHE: selected }, + }); + expect(overridden.exitCode).toBe(0); + expect(overridden.stdout).toBe(cached.stdout); + expect(await readdir(selected)).not.toHaveLength(0); + }, +); + +cliTest( + "collects requested child coverage without populating the fixture cache", + async ({ processes }) => { + const cache = await createTestTempDirectory("rea-cli-coverage-cache-"); + const coverage = await createTestTempDirectory("rea-cli-child-coverage-"); + const cli = createTestCli(processes, cache); + const result = await cli.run({ + arguments: ["--version"], + environment: { NODE_V8_COVERAGE: coverage }, + }); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain(PRODUCT_IDENTITY.packageVersion); + expect(await readdir(cache)).toEqual([]); + expect( + (await readdir(coverage)).some((path) => path.endsWith(".json")), + ).toBe(true); + }, +); + +cliTest("preserves a caller's cold CLI launch", async ({ processes }) => { + const cache = await createTestTempDirectory("rea-cli-disabled-cache-"); + const cli = createTestCli(processes, cache); + const result = await cli.run({ + arguments: ["--version"], + environment: { NODE_DISABLE_COMPILE_CACHE: "1" }, + }); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain(PRODUCT_IDENTITY.packageVersion); + expect(await readdir(cache)).toEqual([]); +}); diff --git a/tests/support/cli/cliFixture.ts b/tests/support/cli/cliFixture.ts index ad67889ea0..c6babc2f1b 100644 --- a/tests/support/cli/cliFixture.ts +++ b/tests/support/cli/cliFixture.ts @@ -1,5 +1,9 @@ import { resolve } from "node:path"; +import { + createTestWorkspace, + removeTestWorkspace, +} from "../workspace/workspaceFixture.js"; import { processTest, type ProcessResult, @@ -36,7 +40,10 @@ const parseJson = (stdout: string): unknown | undefined => { }; /** Create a compiled CLI driver backed by the supplied process registry. */ -export const createTestCli = (processes: TestProcesses): TestCli => ({ +export const createTestCli = ( + processes: TestProcesses, + compileCache?: string, +): TestCli => ({ run: async ({ arguments: arguments_, cwd, @@ -51,6 +58,10 @@ export const createTestCli = (processes: TestProcesses): TestCli => ({ cwd: cwd ?? process.cwd(), env: { PATH: process.env.PATH ?? "", + ...(compileCache === undefined || + environment?.NODE_V8_COVERAGE !== undefined + ? {} + : { NODE_COMPILE_CACHE: compileCache }), ...environment, }, ...(input === undefined ? {} : { input }), @@ -62,8 +73,23 @@ export const createTestCli = (processes: TestProcesses): TestCli => ({ }); /** Vitest API extended with an owned process registry and compiled CLI driver. */ -export const cliTest = processTest.extend<{ cli: TestCli }>({ - cli: async ({ processes }, use) => { - await use(createTestCli(processes)); +export const cliTest = processTest.extend<{ + $test: { cli: TestCli }; + $worker: { cliCompileCache: string }; +}>({ + cliCompileCache: [ + // oxlint-disable-next-line no-empty-pattern -- Vitest parses fixture dependencies from object destructuring. + async ({}, use) => { + const workspace = await createTestWorkspace("rea-cli-compile-cache-"); + try { + await use(workspace.path("node-compile")); + } finally { + await removeTestWorkspace(workspace.root); + } + }, + { scope: "worker" }, + ], + cli: async ({ processes, cliCompileCache }, use) => { + await use(createTestCli(processes, cliCompileCache)); }, });