perf(test): cache local Vitest dependencies and add changed targeting (#33305)

This commit is contained in:
Neil
2026-10-24 17:32:02 -07:00
committed by GitHub
parent 9c5d789a42
commit aadd6fb27b
5 changed files with 160 additions and 1 deletions
+36
View File
@@ -26,6 +26,42 @@ pnpm install
pnpm dev
```
### Targeting Local Tests
Use a test file or directory when you know the relevant suites. For dependency-based
feedback, Vitest can select tests affected by your edits:
```bash
pnpm test:changed src/main/runtime --maxWorkers=2
pnpm test --changed=origin/main --maxWorkers=2
pnpm test src/shared/app-version.test.ts --maxWorkers=2
```
`test:changed` considers staged, unstaged, and untracked files. `--changed=origin/main`
also includes committed changes since the branch's merge base. Update that ref when
you need a newer baseline. Both accept the same path filters and options as `pnpm test`.
Local runs enable Vitest's [dependency recording](https://vitest.dev/config/experimental#experimental-recorddependencies).
Ordinary test runs populate the cache; later changed-file runs read it instead of
transforming every test's import graph. Records are separate for the Bun and Node
projects. A test without a record always runs, so a cold cache runs everything in
the requested scope. Start with a narrow path to avoid a full cold run. Keep Vite's
local test cache between runs. Use `--experimental.recordDependencies=false` to
return to Vitest's static import graph.
This is local feedback, not a complete regression check. Vitest records loaded
imports, including executed dynamic imports, but cannot infer arbitrary filesystem
reads, subprocess inputs, or externalized module dependencies. Explicitly run tests
for changed fixtures, generated files, CLI entry points, and other indirect inputs.
After changing imports, run the affected suites before committing those changes so
later edits use current records. Configuration, lockfile, patch, and workspace-package
changes rerun every test within the requested scope. CI does not enable recording;
the existing CI selection policy is unchanged.
Vitest also supports [`related --run <source-files>`](https://vitest.dev/guide/cli#vitest-related)
and dependency-aware watch mode. `pnpm test --changed` uses Git to provide the source
inputs automatically and exits after one run.
To reduce main-process rebuild CPU and memory when you do not need its source maps,
set `ORCA_DEV_MAIN_SOURCEMAPS=0` before `pnpm dev`. In Bash/zsh:
+1
View File
@@ -3,6 +3,7 @@
- **Typecheck**: `pnpm tc` (or `tc:node` / `tc:cli` / `tc:web`)
- Local typechecks use incremental tsrs on Apple Silicon macOS and x64/arm64 Linux; other platforms use Bun. Keep `.cache/typecheck/` between checks; delete it to force a fresh local check. `pnpm typecheck:ci` explicitly uses Bun; `pnpm tc --compiler=native` uses TypeScript.
- **Test**: `pnpm test [path/to/file.test.ts]`
- **Affected tests**: `pnpm test:changed [path] --maxWorkers=2` for uncommitted edits, or `pnpm test --changed=origin/main` for branch changes. Local runs cache loaded imports; cold caches run all tests in scope. Filesystem/subprocess inputs need explicit tests; see [test targeting](.github/CONTRIBUTING.md#targeting-local-tests).
- **Lint**: `oxlint`, or `pnpm run check:code-quality:changed` for changed files (full `pnpm lint` is slow); format with `pnpm format`
- **Design system**: `pnpm run lint:design-system` for the full renderer report (not a gate); the changed-lines gate above is what CI enforces
@@ -0,0 +1,112 @@
import { runProcessSync } from '@orca/process-host'
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { join, relative, resolve } from 'node:path'
import { pathToFileURL } from 'node:url'
import { expect, it } from 'vitest'
it('uses recorded imports in both runtimes and retains cold, new, and global-input tests', () => {
const root = process.cwd()
mkdirSync(join(root, '.tmp'), { recursive: true })
const directory = mkdtempSync(join(root, '.tmp', 'vitest-affected-'))
const config = join(directory, 'vitest.config.mjs')
const report = join(directory, 'report.json')
const transformed = join(directory, 'transformed.jsonl')
const input = join(directory, 'value.ts')
const runtimes = ['bun', 'node-runtime']
const localPath = (file) => relative(root, file).replaceAll('\\', '/')
function writeTest(runtime, name, imported = false) {
const file = join(directory, runtime, `${name}.test.ts`)
writeFileSync(
file,
`
import { expect, it } from 'vitest'
${imported ? "import { value } from '../value'" : 'const value = 1'}
it('runs under ${runtime}', () => {
expect(value).toBe(1)
expect(Boolean(process.versions.bun)).toBe(${runtime === 'bun'})
})
`
)
return file
}
try {
writeFileSync(input, 'export const value = 1')
const affected = []
const unrelated = []
for (const runtime of runtimes) {
mkdirSync(join(directory, runtime))
affected.push(writeTest(runtime, 'affected', true))
unrelated.push(writeTest(runtime, 'unrelated'))
}
writeFileSync(
config,
`
import base from ${JSON.stringify(pathToFileURL(resolve('config/vitest.config.ts')).href)}
import { appendFileSync } from 'node:fs'
export default {
...base,
cacheDir: ${JSON.stringify(join(directory, 'cache'))},
plugins: [...base.plugins, {
name: 'observe-affected-test-transforms',
transform(code, id) {
if (id.replaceAll('\\\\', '/').startsWith(${JSON.stringify(directory.replaceAll('\\', '/'))})) {
appendFileSync(${JSON.stringify(transformed)}, JSON.stringify(id) + '\\n')
}
}
}],
test: {
...base.test,
maxWorkers: 1,
projects: base.test.projects
.filter(project => ${JSON.stringify(runtimes)}.includes(project.test.name))
.map(project => ({
...project,
test: {
...project.test,
include: [${JSON.stringify(localPath(directory))} + '/' + project.test.name + '/*.test.ts'],
exclude: []
}
}))
}
}
`
)
function run(changed) {
writeFileSync(transformed, '')
const result = runProcessSync({
program: process.execPath,
args: [
resolve('config/scripts/run-vitest.mjs'),
'related',
'--run',
localPath(changed),
'--config',
config,
'--reporter=json',
'--outputFile',
report
],
cwd: root,
env: { ...process.env, CI: '', ORCA_BACKGROUND_LAUNCH: '1' },
timeoutMs: 60_000
})
expect(result.timedOut, result.stderr).toBe(false)
expect(result.code, `${result.stdout}\n${result.stderr}`).toBe(0)
const output = JSON.parse(readFileSync(report, 'utf8'))
expect(output.success).toBe(true)
return output.testResults.map(({ name }) => name.replaceAll('\\', '/')).sort()
}
const normalize = (files) => files.map((file) => file.replaceAll('\\', '/')).sort()
expect(run(input)).toEqual(normalize([...affected, ...unrelated]))
expect(run(input)).toEqual(normalize(affected))
const transforms = readFileSync(transformed, 'utf8')
for (const file of unrelated) {
expect(transforms).not.toContain(JSON.stringify(file.replaceAll('\\', '/')))
}
const fresh = writeTest(runtimes[0], 'new')
expect(run(input)).toEqual(normalize([...affected, fresh]))
expect(run(join(root, 'pnpm-lock.yaml'))).toEqual(normalize([...affected, ...unrelated, fresh]))
} finally {
rmSync(directory, { recursive: true, force: true, maxRetries: 3 })
}
}, 180_000)
+10 -1
View File
@@ -1,7 +1,7 @@
import { readFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { resolve } from 'node:path'
import { defaultExclude, defineConfig } from 'vitest/config'
import { configDefaults, defaultExclude, defineConfig } from 'vitest/config'
import { UNIT_INCLUDE, UNIT_EXCLUDE, UNIT_SETUP_FILES } from './scripts/ci-unit-files.mjs'
import TimingSequencer from './scripts/ci-unit-sequencer.mjs'
import RuntimeSequencer from './scripts/vitest-runtime-sequencer.mjs'
@@ -131,6 +131,15 @@ export default defineConfig({
...transforms,
test: {
...testOptions,
experimental: { recordDependencies: !process.env.CI },
forceRerunTriggers: [
...configDefaults.forceRerunTriggers,
'**/pnpm-lock.yaml',
'**/pnpm-workspace.yaml',
'**/config/**',
'**/patches/**',
'**/src/packages/**'
],
sequence: { sequencer: balancedShards ? TimingSequencer : RuntimeSequencer },
...(balancedShards
? {
+1
View File
@@ -32,6 +32,7 @@
"lint:react-doctor:changed": "node config/scripts/lint-react-doctor-changed.mjs",
"prepare": "husky",
"test": "pnpm run build:packages && node config/scripts/ensure-native-runtime.mjs --runtime=node && node config/scripts/run-vitest.mjs run --config config/vitest.config.ts",
"test:changed": "pnpm test --changed=HEAD",
"test:node": "pnpm run build:packages && node config/scripts/ensure-native-runtime.mjs --runtime=node && vitest run --config config/vitest.config.ts",
"test:node-server": "node config/scripts/run-node-server-tests.mjs",
"test:skill-sharing:release": "vitest run --config config/vitest.config.ts src/main/skills src/main/runtime/rpc/methods/skills.test.ts src/shared/skill-bundle-install-contract.test.ts src/shared/skill-install-contract.test.ts src/shared/skill-install-failure.test.ts src/shared/skill-package-manifest.test.ts",