Skip to content

Commit d5118e0

Browse files
committed
test: enforce wheelhouse budgets for each test lane
1 parent 8814d1f commit d5118e0

6 files changed

Lines changed: 211 additions & 5 deletions

File tree

.config/repo/socket-wheelhouse.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,19 @@
174174
]
175175
},
176176
"workspace": {
177-
"catalogDriftIgnore": ["typescript"]
177+
"catalogDriftIgnore": [
178+
"typescript"
179+
]
180+
},
181+
"vitest": {
182+
"lanes": {
183+
"mid": [
184+
"packages/cli/test/unit/commands/**",
185+
"packages/cli/test/unit/meow.test.mts"
186+
],
187+
"slow": [
188+
"packages/cli/test/integration/**"
189+
]
190+
}
178191
}
179192
}

packages/cli/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,23 @@
6464
"e2e:js": "node scripts/e2e.mts --js",
6565
"e2e:sea": "node scripts/e2e.mts --sea",
6666
"e2e:all": "node scripts/e2e.mts --all",
67-
"test": "run-s check test:prepare test:unit test:validate",
67+
"test": "run-s check test:prepare test:unit:all test:validate",
6868
"test:prepare": "pnpm build && del-cli 'test/**/node_modules'",
6969
"test:fuzz": "node scripts/repo/fuzz.mts",
7070
"test:unit": "node --import=./scripts/load.mts scripts/test-wrapper.mts",
7171
"test:unit:update": "node --import=./scripts/load.mts scripts/test-wrapper.mts --update",
7272
"test:unit:coverage": "node --import=./scripts/load.mts scripts/test-wrapper.mts --coverage",
7373
"test:validate": "node --import=./scripts/load.mts scripts/validate-tests.mts",
74-
"test-ci": "run-s test:prepare test:unit test:validate",
74+
"test-ci": "run-s test:prepare test:unit:all test:validate",
7575
"test-pre-commit": "cross-env PRE_COMMIT=1 pnpm test",
7676
"update": "node ../../scripts/fleet/update.mts",
7777
"verify": "node scripts/verify-package.mts",
7878
"wasm": "node scripts/wasm.mts",
7979
"wasm:build": "node scripts/wasm.mts --build",
80-
"wasm:download": "node scripts/wasm.mts --download"
80+
"wasm:download": "node scripts/wasm.mts --download",
81+
"test:unit:all": "node --import=./scripts/load.mts scripts/test-wrapper.mts --all",
82+
"test:integration": "node --import=./scripts/load.mts scripts/test-wrapper.mts --config vitest.integration.config.mts --lane slow",
83+
"test:mid": "node --import=./scripts/load.mts scripts/test-wrapper.mts --lane mid"
8184
},
8285
"dependencies": {
8386
"@socketsecurity/odai": "0.2.1",
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { readFileSync } from 'node:fs'
2+
import path from 'node:path'
3+
4+
import { normalizePath } from '@socketsecurity/lib-stable/paths/normalize'
5+
6+
import {
7+
configuredFastBudgetMs,
8+
coverBudgetMs,
9+
laneBudgetMs,
10+
} from '../../../scripts/fleet/constants/test-budget.mts'
11+
import type { TestLane } from '../../../scripts/fleet/constants/test-budget.mts'
12+
import { extractLane } from '../../../scripts/fleet/test-runner/cli-args.mts'
13+
14+
export function resolvePackageTestScope(
15+
args: readonly string[],
16+
repoRoot: string,
17+
) {
18+
const parsed = extractLane(args)
19+
const all = parsed.rest.includes('--all')
20+
const rest = parsed.rest.filter(arg => arg !== '--all')
21+
const requested =
22+
parsed.lane ?? (!all && rest.length === 0 ? 'fast' : undefined)
23+
const lane =
24+
requested === 'fast' || requested === 'mid' || requested === 'slow'
25+
? requested
26+
: undefined
27+
return {
28+
__proto__: null,
29+
args: rest,
30+
lane,
31+
timeout: lane
32+
? laneBudgetMs(lane, { configuredFast: configuredFastBudgetMs(repoRoot) })
33+
: coverBudgetMs(),
34+
}
35+
}
36+
37+
export function packageTestGlobs(
38+
globs: readonly string[],
39+
packagePath: string,
40+
): string[] {
41+
const prefix = `${normalizePath(packagePath).replace(/\/$/u, '')}/`
42+
return globs
43+
.map(glob => normalizePath(glob))
44+
.filter(glob => glob.startsWith(prefix))
45+
.map(glob => glob.slice(prefix.length))
46+
}
47+
48+
export function readPackageTestLanes(repoRoot: string, packagePath: string) {
49+
const settings: {
50+
vitest?:
51+
| { lanes?: Partial<Record<TestLane, string[]>> | undefined }
52+
| undefined
53+
} = JSON.parse(
54+
readFileSync(
55+
path.join(repoRoot, '.config/repo/socket-wheelhouse.json'),
56+
'utf8',
57+
),
58+
)
59+
return {
60+
__proto__: null,
61+
mid: packageTestGlobs(settings.vitest?.lanes?.mid ?? [], packagePath),
62+
slow: packageTestGlobs(settings.vitest?.lanes?.slow ?? [], packagePath),
63+
}
64+
}
65+
66+
export function selectPackageTestGlobs(
67+
lane: string | undefined,
68+
lanes: { mid: string[]; slow: string[] },
69+
) {
70+
const selected =
71+
lane === 'mid' ? lanes.mid : lane === 'slow' ? lanes.slow : undefined
72+
return {
73+
__proto__: null,
74+
include: selected?.map(glob =>
75+
glob.endsWith('/**') ? `${glob}/*.test.{mts,ts}` : glob,
76+
) ?? ['test/**/*.test.{mts,ts}'],
77+
exclude: lane === 'fast' ? [...lanes.mid, ...lanes.slow] : [],
78+
}
79+
}

packages/cli/scripts/test-wrapper.mts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import { isWin32 } from '@socketsecurity/lib-stable/constants/platform'
2020
import { getDefaultLogger } from '@socketsecurity/lib-stable/logger/default'
2121
import { spawn } from '@socketsecurity/lib-stable/process/spawn/child'
2222

23+
import { REPO_ROOT } from '../../../scripts/fleet/paths.mts'
2324
import { EnvironmentVariables } from './environment-variables.mts'
2425
import { loadEnvFile } from './util/load-env.mts'
26+
import { resolvePackageTestScope } from './test-lanes.mts'
2527

2628
const logger = getDefaultLogger()
2729
const __dirname = path.dirname(fileURLToPath(import.meta.url))
@@ -71,6 +73,9 @@ async function main() {
7173
args = args.slice(1)
7274
}
7375

76+
const scope = resolvePackageTestScope(args, REPO_ROOT)
77+
args = scope.args
78+
7479
// Check for and warn about environment variables that can cause snapshot mismatches.
7580
// These are all aliases for the Socket API token that should not be set during tests.
7681
const problematicEnvVars = [
@@ -98,6 +103,7 @@ async function main() {
98103

99104
const spawnEnv = {
100105
...process.env,
106+
FLEET_LANE: scope.lane,
101107
// Increase Node.js heap size to prevent out of memory errors.
102108
// Use 8GB in CI, 4GB locally.
103109
// Add --max-semi-space-size for better GC with RegExp-heavy tests.
@@ -147,6 +153,7 @@ async function main() {
147153
// On Windows, .cmd files need shell: true.
148154
const spawnOptions = {
149155
cwd: rootPath,
156+
timeout: scope.timeout,
150157
env: {
151158
...testEnv,
152159
...spawnEnv,
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { expect, it } from 'vitest'
2+
3+
import {
4+
coverBudgetMs,
5+
laneBudgetMs,
6+
} from '../../../../../scripts/fleet/constants/test-budget.mts'
7+
import {
8+
packageTestGlobs,
9+
resolvePackageTestScope,
10+
selectPackageTestGlobs,
11+
} from '../../../scripts/test-lanes.mts'
12+
13+
it('defaults bare package tests to the fast budget', () => {
14+
const scope = resolvePackageTestScope([], '/example/repo')
15+
expect(scope).toEqual({
16+
args: [],
17+
lane: 'fast',
18+
timeout: laneBudgetMs('fast'),
19+
})
20+
})
21+
22+
it('keeps explicit files and all-unit runs outside lane filtering', () => {
23+
for (const args of [
24+
['--all'],
25+
['test/unit/example.test.mts'],
26+
['--coverage'],
27+
]) {
28+
const scope = resolvePackageTestScope(args, '/example/repo')
29+
expect(scope.lane).toBeUndefined()
30+
expect(scope.timeout).toBe(coverBudgetMs())
31+
expect(scope.args).toEqual(args.filter(arg => arg !== '--all'))
32+
}
33+
})
34+
35+
it('consumes lane flags and preserves Vitest arguments', () => {
36+
expect(
37+
resolvePackageTestScope(
38+
['--lane', 'mid', '--reporter=json'],
39+
'/example/repo',
40+
),
41+
).toEqual({
42+
args: ['--reporter=json'],
43+
lane: 'mid',
44+
timeout: laneBudgetMs('mid'),
45+
})
46+
expect(
47+
resolvePackageTestScope(
48+
['--lane=slow', '--config', 'vitest.integration.config.mts'],
49+
'/example/repo',
50+
),
51+
).toEqual({
52+
args: ['--config', 'vitest.integration.config.mts'],
53+
lane: 'slow',
54+
timeout: laneBudgetMs('slow'),
55+
})
56+
expect(() =>
57+
resolvePackageTestScope(['--lane=unknown'], '/example/repo'),
58+
).toThrow()
59+
})
60+
61+
it('normalizes member globs and excludes other packages', () => {
62+
expect(
63+
packageTestGlobs(
64+
[
65+
'packages\\cli\\test\\unit\\commands\\**',
66+
'packages/cli/test/unit/meow.test.mts',
67+
'packages/other/test/unit/example.test.mts',
68+
'packages/cli-other/test/unit/example.test.mts',
69+
],
70+
'packages\\cli',
71+
),
72+
).toEqual(['test/unit/commands/**', 'test/unit/meow.test.mts'])
73+
})
74+
75+
it('partitions fast and mid while leaving unscoped coverage complete', () => {
76+
const lanes = {
77+
mid: ['test/unit/commands/**'],
78+
slow: ['test/integration/**'],
79+
}
80+
expect(selectPackageTestGlobs('fast', lanes)).toEqual({
81+
include: ['test/**/*.test.{mts,ts}'],
82+
exclude: [...lanes.mid, ...lanes.slow],
83+
})
84+
expect(selectPackageTestGlobs('mid', lanes)).toEqual({
85+
include: ['test/unit/commands/**/*.test.{mts,ts}'],
86+
exclude: [],
87+
})
88+
expect(selectPackageTestGlobs(undefined, lanes)).toEqual({
89+
include: ['test/**/*.test.{mts,ts}'],
90+
exclude: [],
91+
})
92+
})

packages/cli/vitest.config.mts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ import path from 'node:path'
44
import { fileURLToPath } from 'node:url'
55

66
import { defineConfig } from 'vitest/config'
7+
import { REPO_ROOT } from '../../scripts/fleet/paths.mts'
78

89
import { vitiatePlugin } from '@vitiate/core/plugin'
910

11+
import {
12+
readPackageTestLanes,
13+
selectPackageTestGlobs,
14+
} from './scripts/test-lanes.mts'
15+
1016
// The vitiate coverage-guided fuzz lane (scripts/repo/fuzz.mts) sets
1117
// VITIATE_FUZZ=1 and runs `vitest run` against THIS auto-discovered config. In
1218
// a monorepo package that already owns its vitest.config.mts we can't drop a
@@ -103,6 +109,11 @@ export function getMaxThreads(): number {
103109
return os.cpus().length
104110
}
105111

112+
const laneGlobs = selectPackageTestGlobs(
113+
process.env['FLEET_LANE'],
114+
readPackageTestLanes(REPO_ROOT, 'packages/cli'),
115+
)
116+
106117
const normalConfig = defineConfig({
107118
resolve: {
108119
preserveSymlinks: false,
@@ -125,8 +136,9 @@ const normalConfig = defineConfig({
125136
NO_COLOR: '',
126137
TZ: 'UTC',
127138
},
128-
include: ['test/**/*.test.{mts,ts}'],
139+
include: laneGlobs.include,
129140
exclude: [
141+
...laneGlobs.exclude,
130142
'**/node_modules/**',
131143
'**/dist/**',
132144
'**/.{idea,git,cache,output,temp}/**',

0 commit comments

Comments
 (0)