mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
perf(test): cache local Vitest dependencies and add changed targeting (#33305)
This commit is contained in:
@@ -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:
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
? {
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user