mirror of https://github.com/openclaw/openclaw.git
test: simplify vitest runner pools
This commit is contained in:
parent
4ee41cc6f3
commit
d0f5e7cb2d
|
|
@ -118,7 +118,7 @@
|
|||
- Agents MUST NOT modify baseline, inventory, ignore, snapshot, or expected-failure files to silence failing checks without explicit approval in this chat.
|
||||
- For targeted/local debugging, keep using the wrapper: `pnpm test -- <path-or-filter> [vitest args...]` (for example `pnpm test -- src/commands/onboard-search.test.ts -t "shows registered plugin providers"`); do not default to raw `pnpm vitest run ...` because it bypasses wrapper config/profile/pool routing.
|
||||
- Do not set test workers above 16; tried already.
|
||||
- Do not switch CI `pnpm test` lanes back to Vitest `vmForks` by default without fresh green evidence on current `main`; keep CI on `forks` unless explicitly re-validated.
|
||||
- Do not reintroduce Vitest VM pools by default without fresh green evidence on current `main`; keep CI on `forks`.
|
||||
- If local Vitest runs cause memory pressure (common on non-Mac-Studio hosts), use `OPENCLAW_TEST_PROFILE=low OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test` for land/gate runs.
|
||||
- Live tests (real keys): `CLAWDBOT_LIVE_TEST=1 pnpm test:live` (OpenClaw-only) or `LIVE=1 pnpm test:live` (includes provider live tests). Docker: `pnpm test:docker:live-models`, `pnpm test:docker:live-gateway`. Onboarding Docker E2E: `pnpm test:docker:onboard`.
|
||||
- Full kit + what’s covered: `docs/help/testing.md`.
|
||||
|
|
|
|||
|
|
@ -71,12 +71,11 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost):
|
|||
sufficient substitute for those integration paths.
|
||||
- Pool note:
|
||||
- Base Vitest config still defaults to `forks`.
|
||||
- Unit wrapper lanes default to `threads`, with explicit manifest fork/vmFork exceptions.
|
||||
- Unit wrapper lanes default to `threads`, with explicit manifest fork-only exceptions.
|
||||
- Extension scoped config defaults to `threads`.
|
||||
- `pnpm test` also defaults to `--isolate=false` at the wrapper level for faster file startup.
|
||||
- Opt back into Vitest file isolation with `OPENCLAW_TEST_ISOLATE=1 pnpm test`.
|
||||
- `OPENCLAW_TEST_NO_ISOLATE=0` or `OPENCLAW_TEST_NO_ISOLATE=false` also force isolated runs.
|
||||
- `OPENCLAW_TEST_VM_FORKS=1` remains an opt-in experiment on Node 22, 23, and 24 only.
|
||||
|
||||
### E2E (gateway smoke)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,16 +11,14 @@ title: "Tests"
|
|||
|
||||
- `pnpm test:force`: Kills any lingering gateway process holding the default control port, then runs the full Vitest suite with an isolated gateway port so server tests don’t collide with a running instance. Use this when a prior gateway run left port 18789 occupied.
|
||||
- `pnpm test:coverage`: Runs the unit suite with V8 coverage (via `vitest.unit.config.ts`). Global thresholds are 70% lines/branches/functions/statements. Coverage excludes integration-heavy entrypoints (CLI wiring, gateway/telegram bridges, webchat static server) to keep the target focused on unit-testable logic.
|
||||
- `pnpm test` on Node 22, 23, and 24 uses Vitest `vmForks` by default for local runs with enough memory. CI stays on `forks` unless explicitly overridden. Node 25+ falls back to `forks` until re-validated. You can force behavior with `OPENCLAW_TEST_VM_FORKS=0|1`.
|
||||
- `pnpm test`: runs the full wrapper. It keeps only a small behavioral override manifest in git, then uses a checked-in timing snapshot to peel the heaviest measured unit files into dedicated lanes.
|
||||
- Unit files default to `threads` in the wrapper; keep fork-only or `forkBatched` exceptions documented in `test/fixtures/test-parallel.behavior.json`.
|
||||
- Unit files default to `threads` in the wrapper; keep fork-only exceptions documented in `test/fixtures/test-parallel.behavior.json`.
|
||||
- `pnpm test:extensions` now defaults to `threads` via `vitest.extensions.config.ts`; the March 22, 2026 direct full-suite control run passed clean without extension-specific fork exceptions.
|
||||
- Files marked `forkBatched` stay on `forks`, but the wrapper batches them into low-concurrency `forks` lanes with `maxWorkers=1` instead of spawning one fresh Vitest process per file. Tune lane count with `OPENCLAW_TEST_UNIT_FORK_BATCH_LANES=<n>`.
|
||||
- `pnpm test:channels`: runs channel-heavy suites.
|
||||
- `pnpm test:extensions`: runs extension/plugin suites.
|
||||
- `pnpm test:perf:update-timings`: refreshes the checked-in slow-file timing snapshot used by `scripts/test-parallel.mjs`.
|
||||
- Gateway integration: opt-in via `OPENCLAW_TEST_INCLUDE_GATEWAY=1 pnpm test` or `pnpm test:gateway`.
|
||||
- `pnpm test:e2e`: Runs gateway end-to-end smoke tests (multi-instance WS/HTTP/node pairing). Defaults to `vmForks` + adaptive workers in `vitest.e2e.config.ts`; tune with `OPENCLAW_E2E_WORKERS=<n>` and set `OPENCLAW_E2E_VERBOSE=1` for verbose logs.
|
||||
- `pnpm test:e2e`: Runs gateway end-to-end smoke tests (multi-instance WS/HTTP/node pairing). Defaults to `forks` + adaptive workers in `vitest.e2e.config.ts`; tune with `OPENCLAW_E2E_WORKERS=<n>` and set `OPENCLAW_E2E_VERBOSE=1` for verbose logs.
|
||||
- `pnpm test:live`: Runs provider live tests (minimax/zai). Requires API keys and `LIVE=1` (or provider-specific `*_LIVE_TEST=1`) to unskip.
|
||||
|
||||
## Local PR gate
|
||||
|
|
|
|||
|
|
@ -699,7 +699,7 @@
|
|||
"test:install:e2e:openai": "OPENCLAW_E2E_MODELS=openai CLAWDBOT_E2E_MODELS=openai bash scripts/test-install-sh-e2e-docker.sh",
|
||||
"test:install:smoke": "bash scripts/test-install-sh-docker.sh",
|
||||
"test:live": "OPENCLAW_LIVE_TEST=1 CLAWDBOT_LIVE_TEST=1 vitest run --config vitest.live.config.ts",
|
||||
"test:macmini": "OPENCLAW_TEST_VM_FORKS=0 OPENCLAW_TEST_PROFILE=macmini node scripts/test-parallel.mjs",
|
||||
"test:macmini": "OPENCLAW_TEST_PROFILE=macmini node scripts/test-parallel.mjs",
|
||||
"test:parallels:linux": "bash scripts/e2e/parallels-linux-smoke.sh",
|
||||
"test:parallels:macos": "bash scripts/e2e/parallels-macos-smoke.sh",
|
||||
"test:parallels:windows": "bash scripts/e2e/parallels-windows-smoke.sh",
|
||||
|
|
|
|||
|
|
@ -83,12 +83,8 @@ export function getExistingThreadCandidateExclusions(behavior) {
|
|||
...(behavior.base?.threadPinned ?? []).map((entry) => entry.file),
|
||||
...(behavior.base?.threadSingleton ?? []).map((entry) => entry.file),
|
||||
...(behavior.unit?.isolated ?? []).map((entry) => entry.file),
|
||||
...(behavior.unit?.forkBatched ?? []).map((entry) => entry.file),
|
||||
...(behavior.unit?.singletonIsolated ?? []).map((entry) => entry.file),
|
||||
...(behavior.unit?.threadPinned ?? []).map((entry) => entry.file),
|
||||
...(behavior.unit?.threadSingleton ?? []).map((entry) => entry.file),
|
||||
...(behavior.unit?.vmForkPinned ?? []).map((entry) => entry.file),
|
||||
...(behavior.unit?.vmForkSingleton ?? []).map((entry) => entry.file),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,16 +51,8 @@ const cleanupTempArtifacts = () => {
|
|||
const existingUnitConfigFiles = (entries) => existingFiles(entries).filter(isUnitConfigTestFile);
|
||||
const baseThreadPinnedFiles = existingFiles(behaviorManifest.base?.threadPinned ?? []);
|
||||
const unitForkIsolatedFiles = existingUnitConfigFiles(behaviorManifest.unit.isolated);
|
||||
const unitForkBatchedConfiguredFiles = existingUnitConfigFiles(behaviorManifest.unit.forkBatched);
|
||||
const unitThreadPinnedFiles = existingUnitConfigFiles(behaviorManifest.unit.threadPinned);
|
||||
const unitVmForkPinnedFiles = existingUnitConfigFiles(behaviorManifest.unit.vmForkPinned);
|
||||
const extensionIsolatedFiles = existingFiles(behaviorManifest.extensions.isolated);
|
||||
const unitBehaviorOverrideSet = new Set([
|
||||
...unitForkIsolatedFiles,
|
||||
...unitForkBatchedConfiguredFiles,
|
||||
...unitThreadPinnedFiles,
|
||||
...unitVmForkPinnedFiles,
|
||||
]);
|
||||
const unitBehaviorOverrideSet = new Set([...unitForkIsolatedFiles, ...unitThreadPinnedFiles]);
|
||||
const channelSingletonFiles = [];
|
||||
|
||||
const children = new Set();
|
||||
|
|
@ -87,14 +79,10 @@ const isMacMiniProfile = testProfile === "macmini";
|
|||
// Vitest executes Node tests through Vite's SSR/module-runner pipeline, so the
|
||||
// shared unit lane still retains transformed ESM/module state even when the
|
||||
// tests themselves are not "server rendering" a website. We previously kept
|
||||
// forks as the default after vmFork OOM regressions on constrained hosts. On
|
||||
// forks as the default after VM-pool regressions on constrained hosts. On
|
||||
// 2026-03-22, a direct full-unit threads run finished 1109/1110 green; the sole
|
||||
// correctness exception stayed on the manifest fork lane, so the wrapper now
|
||||
// defaults unit runs to threads while preserving explicit fork/vmFork escapes.
|
||||
// Preserve OPENCLAW_TEST_UNIT_DEFAULT_POOL=forks or OPENCLAW_TEST_VM_FORKS=1 as
|
||||
// explicit debug escape hatches.
|
||||
const supportsVmForks = Number.isFinite(nodeMajor) ? nodeMajor <= 24 : true;
|
||||
const useVmForks = process.env.OPENCLAW_TEST_VM_FORKS === "1" && supportsVmForks;
|
||||
// defaults unit runs to threads while preserving explicit fork escapes.
|
||||
const forceIsolation =
|
||||
process.env.OPENCLAW_TEST_ISOLATE === "1" || process.env.OPENCLAW_TEST_ISOLATE === "true";
|
||||
const disableIsolation =
|
||||
|
|
@ -107,9 +95,6 @@ const parsePoolOverride = (value, fallback) => {
|
|||
if (value === "threads" || value === "forks") {
|
||||
return value;
|
||||
}
|
||||
if (value === "vmForks") {
|
||||
return supportsVmForks ? value : "forks";
|
||||
}
|
||||
return fallback;
|
||||
};
|
||||
// Even on low-memory hosts, keep the isolated lane split so files like
|
||||
|
|
@ -275,7 +260,6 @@ const allKnownTestFiles = [
|
|||
const defaultUnitPool = parsePoolOverride(process.env.OPENCLAW_TEST_UNIT_DEFAULT_POOL, "threads");
|
||||
const isTargetedIsolatedUnitFile = (fileFilter) =>
|
||||
unitForkIsolatedFiles.includes(fileFilter) ||
|
||||
unitForkBatchFiles.includes(fileFilter) ||
|
||||
unitMemoryIsolatedFiles.includes(fileFilter);
|
||||
const inferTarget = (fileFilter) => {
|
||||
const isolated = isTargetedIsolatedUnitFile(fileFilter);
|
||||
|
|
@ -369,40 +353,14 @@ const { memoryHeavyFiles: memoryHeavyUnitFiles, timedHeavyFiles: timedHeavyUnitF
|
|||
memoryHeavyFiles: [],
|
||||
timedHeavyFiles: [],
|
||||
};
|
||||
const unitForkBatchFiles = dedupeFilesPreserveOrder(
|
||||
unitForkBatchedConfiguredFiles,
|
||||
new Set(unitForkIsolatedFiles),
|
||||
);
|
||||
const unitMemoryIsolatedFiles = dedupeFilesPreserveOrder(
|
||||
memoryHeavyUnitFiles,
|
||||
new Set([...unitBehaviorOverrideSet, ...unitForkBatchFiles]),
|
||||
unitBehaviorOverrideSet,
|
||||
);
|
||||
const unitSchedulingOverrideSet = new Set([...unitBehaviorOverrideSet, ...memoryHeavyUnitFiles]);
|
||||
const unitFastExcludedFiles = [
|
||||
...new Set([...unitSchedulingOverrideSet, ...timedHeavyUnitFiles, ...channelSingletonFiles]),
|
||||
];
|
||||
const defaultForkBatchLaneCount =
|
||||
testProfile === "serial"
|
||||
? 0
|
||||
: unitForkBatchFiles.length === 0
|
||||
? 0
|
||||
: isCI
|
||||
? Math.ceil(unitForkBatchFiles.length / 6)
|
||||
: testProfile === "low" && highMemLocalHost
|
||||
? Math.ceil(unitForkBatchFiles.length / 8) + 1
|
||||
: highMemLocalHost
|
||||
? Math.ceil(unitForkBatchFiles.length / 8)
|
||||
: lowMemLocalHost
|
||||
? Math.ceil(unitForkBatchFiles.length / 12)
|
||||
: Math.ceil(unitForkBatchFiles.length / 10);
|
||||
const configuredForkBatchLaneCount = parseEnvNumber(
|
||||
"OPENCLAW_TEST_UNIT_FORK_BATCH_LANES",
|
||||
defaultForkBatchLaneCount,
|
||||
);
|
||||
const forkBatchLaneCount =
|
||||
unitForkBatchFiles.length === 0
|
||||
? 0
|
||||
: Math.min(unitForkBatchFiles.length, Math.max(1, configuredForkBatchLaneCount));
|
||||
const estimateUnitDurationMs = (file) =>
|
||||
unitTimingManifest.files[file]?.durationMs ?? unitTimingManifest.defaultDurationMs;
|
||||
const splitFilesByDurationBudget = (files, targetDurationMs, estimateDurationMs) => {
|
||||
|
|
@ -431,18 +389,11 @@ const splitFilesByDurationBudget = (files, targetDurationMs, estimateDurationMs)
|
|||
|
||||
return batches;
|
||||
};
|
||||
const unitForkBatchBuckets =
|
||||
forkBatchLaneCount > 0
|
||||
? packFilesByDuration(unitForkBatchFiles, forkBatchLaneCount, estimateUnitDurationMs)
|
||||
: [];
|
||||
const unitFastExcludedFileSet = new Set(unitFastExcludedFiles);
|
||||
const unitFastCandidateFiles = allKnownUnitFiles.filter(
|
||||
(file) => !unitFastExcludedFileSet.has(file),
|
||||
);
|
||||
const extensionIsolatedExcludedFileSet = new Set(extensionIsolatedFiles);
|
||||
const extensionSharedCandidateFiles = allKnownTestFiles.filter(
|
||||
(file) => file.startsWith("extensions/") && !extensionIsolatedExcludedFileSet.has(file),
|
||||
);
|
||||
const extensionSharedCandidateFiles = allKnownTestFiles.filter((file) => file.startsWith("extensions/"));
|
||||
const defaultUnitFastLaneCount = isCI && !isWindows ? 3 : 1;
|
||||
const unitFastLaneCount = Math.max(
|
||||
1,
|
||||
|
|
@ -498,11 +449,6 @@ const unitHeavyEntries = heavyUnitBuckets.map((files, index) => ({
|
|||
name: `unit-heavy-${String(index + 1)}`,
|
||||
args: ["vitest", "run", "--config", "vitest.unit.config.ts", "--pool=forks", ...files],
|
||||
}));
|
||||
const unitForkBatchEntries = unitForkBatchBuckets.map((files, index) => ({
|
||||
name:
|
||||
unitForkBatchBuckets.length === 1 ? "unit-fork-batch" : `unit-fork-batch-${String(index + 1)}`,
|
||||
args: ["vitest", "run", "--config", "vitest.unit.config.ts", "--pool=forks", ...files],
|
||||
}));
|
||||
const unitThreadEntries =
|
||||
unitThreadPinnedFiles.length > 0
|
||||
? [
|
||||
|
|
@ -523,17 +469,12 @@ const unitIsolatedEntries = unitForkIsolatedFiles.map((file) => ({
|
|||
name: `unit-${path.basename(file, ".test.ts")}-isolated`,
|
||||
args: ["vitest", "run", "--config", "vitest.unit.config.ts", "--pool=forks", file],
|
||||
}));
|
||||
const extensionIsolatedEntries = extensionIsolatedFiles.map((file) => ({
|
||||
name: `${path.basename(file, ".test.ts")}-extensions-isolated`,
|
||||
args: ["vitest", "run", "--config", "vitest.extensions.config.ts", "--pool=forks", file],
|
||||
}));
|
||||
const baseRuns = [
|
||||
...(shouldSplitUnitRuns
|
||||
? [
|
||||
...unitFastEntries,
|
||||
...unitIsolatedEntries,
|
||||
...unitHeavyEntries,
|
||||
...unitForkBatchEntries,
|
||||
...unitMemoryIsolatedFiles.map((file) => ({
|
||||
name: `unit-${path.basename(file, ".test.ts")}-memory-isolated`,
|
||||
args: [
|
||||
|
|
@ -541,23 +482,11 @@ const baseRuns = [
|
|||
"run",
|
||||
"--config",
|
||||
"vitest.unit.config.ts",
|
||||
`--pool=${useVmForks ? "vmForks" : "forks"}`,
|
||||
"--pool=forks",
|
||||
file,
|
||||
],
|
||||
})),
|
||||
...unitThreadEntries,
|
||||
...unitVmForkPinnedFiles.map((file) => ({
|
||||
name: `${path.basename(file, ".test.ts")}-vmforks`,
|
||||
args: [
|
||||
"vitest",
|
||||
"run",
|
||||
"--config",
|
||||
"vitest.unit.config.ts",
|
||||
`--pool=${useVmForks ? "vmForks" : "forks"}`,
|
||||
...(disableIsolation ? ["--isolate=false"] : []),
|
||||
file,
|
||||
],
|
||||
})),
|
||||
...channelSingletonFiles.map((file) => ({
|
||||
name: `${path.basename(file, ".test.ts")}-channels-isolated`,
|
||||
args: ["vitest", "run", "--config", "vitest.channels.config.ts", "--pool=forks", file],
|
||||
|
|
@ -571,7 +500,7 @@ const baseRuns = [
|
|||
"run",
|
||||
"--config",
|
||||
"vitest.unit.config.ts",
|
||||
`--pool=${useVmForks ? "vmForks" : "forks"}`,
|
||||
"--pool=forks",
|
||||
...(disableIsolation ? ["--isolate=false"] : []),
|
||||
],
|
||||
},
|
||||
|
|
@ -594,10 +523,8 @@ const baseRuns = [
|
|||
"run",
|
||||
"--config",
|
||||
"vitest.extensions.config.ts",
|
||||
...(useVmForks ? ["--pool=vmForks"] : []),
|
||||
],
|
||||
},
|
||||
...extensionIsolatedEntries,
|
||||
]
|
||||
: []),
|
||||
...(includeGatewaySuite
|
||||
|
|
@ -633,26 +560,11 @@ const resolveFilterMatches = (fileFilter) => {
|
|||
}
|
||||
return allKnownTestFiles.filter((file) => file.includes(normalizedFilter));
|
||||
};
|
||||
const isVmForkPinnedUnitFile = (fileFilter) => unitVmForkPinnedFiles.includes(fileFilter);
|
||||
const isThreadPinnedUnitFile = (fileFilter) => unitThreadPinnedFiles.includes(fileFilter);
|
||||
const isBaseThreadPinnedFile = (fileFilter) => baseThreadPinnedFiles.includes(fileFilter);
|
||||
const createTargetedEntry = (owner, isolated, filters) => {
|
||||
const name = isolated ? `${owner}-isolated` : owner;
|
||||
const forceForks = isolated;
|
||||
if (owner === "unit-vmforks") {
|
||||
return {
|
||||
name,
|
||||
args: [
|
||||
"vitest",
|
||||
"run",
|
||||
"--config",
|
||||
"vitest.unit.config.ts",
|
||||
`--pool=${useVmForks ? "vmForks" : "forks"}`,
|
||||
...(disableIsolation ? ["--isolate=false"] : []),
|
||||
...filters,
|
||||
],
|
||||
};
|
||||
}
|
||||
if (owner === "unit") {
|
||||
return {
|
||||
name,
|
||||
|
|
@ -682,14 +594,7 @@ const createTargetedEntry = (owner, isolated, filters) => {
|
|||
if (owner === "extensions") {
|
||||
return {
|
||||
name,
|
||||
args: [
|
||||
"vitest",
|
||||
"run",
|
||||
"--config",
|
||||
"vitest.extensions.config.ts",
|
||||
...(forceForks ? ["--pool=forks"] : useVmForks ? ["--pool=vmForks"] : []),
|
||||
...filters,
|
||||
],
|
||||
args: ["vitest", "run", "--config", "vitest.extensions.config.ts", ...filters],
|
||||
};
|
||||
}
|
||||
if (owner === "gateway") {
|
||||
|
|
@ -701,14 +606,7 @@ const createTargetedEntry = (owner, isolated, filters) => {
|
|||
if (owner === "channels") {
|
||||
return {
|
||||
name,
|
||||
args: [
|
||||
"vitest",
|
||||
"run",
|
||||
"--config",
|
||||
"vitest.channels.config.ts",
|
||||
...(forceForks ? ["--pool=forks"] : useVmForks ? ["--pool=vmForks"] : []),
|
||||
...filters,
|
||||
],
|
||||
args: ["vitest", "run", "--config", "vitest.channels.config.ts", ...filters],
|
||||
};
|
||||
}
|
||||
if (owner === "live") {
|
||||
|
|
@ -747,9 +645,7 @@ const createPerFileTargetedEntry = (file) => {
|
|||
const target = inferTarget(file);
|
||||
const owner = isThreadPinnedUnitFile(file)
|
||||
? "unit-threads"
|
||||
: isVmForkPinnedUnitFile(file)
|
||||
? "unit-vmforks"
|
||||
: isBaseThreadPinnedFile(file)
|
||||
: isBaseThreadPinnedFile(file)
|
||||
? "base-threads"
|
||||
: target.owner;
|
||||
return {
|
||||
|
|
@ -768,9 +664,7 @@ const targetedEntries = (() => {
|
|||
const target = inferTarget(normalizedFile);
|
||||
const owner = isThreadPinnedUnitFile(normalizedFile)
|
||||
? "unit-threads"
|
||||
: isVmForkPinnedUnitFile(normalizedFile)
|
||||
? "unit-vmforks"
|
||||
: isBaseThreadPinnedFile(normalizedFile)
|
||||
: isBaseThreadPinnedFile(normalizedFile)
|
||||
? "base-threads"
|
||||
: target.owner;
|
||||
const key = `${owner}:${target.isolated ? "isolated" : "default"}`;
|
||||
|
|
@ -783,9 +677,7 @@ const targetedEntries = (() => {
|
|||
const target = inferTarget(matchedFile);
|
||||
const owner = isThreadPinnedUnitFile(matchedFile)
|
||||
? "unit-threads"
|
||||
: isVmForkPinnedUnitFile(matchedFile)
|
||||
? "unit-vmforks"
|
||||
: isBaseThreadPinnedFile(matchedFile)
|
||||
: isBaseThreadPinnedFile(matchedFile)
|
||||
? "base-threads"
|
||||
: target.owner;
|
||||
const key = `${owner}:${target.isolated ? "isolated" : "default"}`;
|
||||
|
|
@ -925,16 +817,13 @@ const maxWorkersForRun = (name) => {
|
|||
if (resolvedOverride) {
|
||||
return resolvedOverride;
|
||||
}
|
||||
if (name === "unit-fork-batch" || name.startsWith("unit-fork-batch-")) {
|
||||
return 1;
|
||||
}
|
||||
if (isCI && !isMacOS) {
|
||||
return null;
|
||||
}
|
||||
if (isCI && isMacOS) {
|
||||
return 1;
|
||||
}
|
||||
if (name.endsWith("-threads") || name.endsWith("-vmforks")) {
|
||||
if (name.endsWith("-threads")) {
|
||||
return 1;
|
||||
}
|
||||
if (name.endsWith("-isolated")) {
|
||||
|
|
@ -1012,12 +901,7 @@ const runOnce = (entry, extraArgs = []) =>
|
|||
new Promise((resolve) => {
|
||||
const startedAt = Date.now();
|
||||
const maxWorkers = maxWorkersForRun(entry.name);
|
||||
// vmForks with a single worker has shown cross-file leakage in extension suites.
|
||||
// Fall back to process forks when we intentionally clamp that lane to one worker.
|
||||
const entryArgs =
|
||||
entry.name === "extensions" && maxWorkers === 1 && entry.args.includes("--pool=vmForks")
|
||||
? entry.args.map((arg) => (arg === "--pool=vmForks" ? "--pool=forks" : arg))
|
||||
: entry.args;
|
||||
const entryArgs = entry.args;
|
||||
const explicitEntryFilters = getExplicitEntryFilters(entryArgs);
|
||||
const args = maxWorkers
|
||||
? [
|
||||
|
|
|
|||
|
|
@ -47,19 +47,13 @@ export function loadTestRunnerBehavior() {
|
|||
const raw = tryReadJsonFile(behaviorManifestPath, {});
|
||||
const unit = raw.unit ?? {};
|
||||
const base = raw.base ?? {};
|
||||
const extensions = raw.extensions ?? {};
|
||||
return {
|
||||
base: {
|
||||
threadPinned: mergeManifestEntries(base, ["threadPinned", "threadSingleton"]),
|
||||
},
|
||||
unit: {
|
||||
isolated: mergeManifestEntries(unit, ["isolated"]),
|
||||
forkBatched: mergeManifestEntries(unit, ["forkBatched", "singletonIsolated"]),
|
||||
threadPinned: mergeManifestEntries(unit, ["threadPinned", "threadSingleton"]),
|
||||
vmForkPinned: mergeManifestEntries(unit, ["vmForkPinned", "vmForkSingleton"]),
|
||||
},
|
||||
extensions: {
|
||||
isolated: mergeManifestEntries(extensions, ["isolated", "singletonIsolated"]),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@
|
|||
"reason": "Touches process-level unhandledRejection listeners."
|
||||
}
|
||||
],
|
||||
"forkBatched": [],
|
||||
"threadPinned": [
|
||||
{
|
||||
"file": "src/cli/secrets-cli.test.ts",
|
||||
|
|
@ -184,10 +183,6 @@
|
|||
"file": "src/media-understanding/runner.video.test.ts",
|
||||
"reason": "Measured ~25% faster under threads than forks on this host while keeping the file green."
|
||||
}
|
||||
],
|
||||
"vmForkPinned": []
|
||||
},
|
||||
"extensions": {
|
||||
"isolated": []
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,20 +49,10 @@ describe("scripts/test-find-thread-candidates exclusions", () => {
|
|||
},
|
||||
unit: {
|
||||
isolated: [{ file: "src/a.test.ts" }],
|
||||
forkBatched: [{ file: "src/b.test.ts" }],
|
||||
threadPinned: [{ file: "src/c.test.ts" }],
|
||||
vmForkPinned: [{ file: "src/d.test.ts" }],
|
||||
},
|
||||
}),
|
||||
).toEqual(
|
||||
new Set([
|
||||
"src/base-a.test.ts",
|
||||
"src/a.test.ts",
|
||||
"src/b.test.ts",
|
||||
"src/c.test.ts",
|
||||
"src/d.test.ts",
|
||||
]),
|
||||
);
|
||||
).toEqual(new Set(["src/base-a.test.ts", "src/a.test.ts", "src/c.test.ts"]));
|
||||
});
|
||||
|
||||
it("keeps backward-compatible aliases readable", () => {
|
||||
|
|
@ -73,20 +63,10 @@ describe("scripts/test-find-thread-candidates exclusions", () => {
|
|||
},
|
||||
unit: {
|
||||
isolated: [{ file: "src/a.test.ts" }],
|
||||
singletonIsolated: [{ file: "src/b.test.ts" }],
|
||||
threadSingleton: [{ file: "src/c.test.ts" }],
|
||||
vmForkSingleton: [{ file: "src/d.test.ts" }],
|
||||
},
|
||||
}),
|
||||
).toEqual(
|
||||
new Set([
|
||||
"src/base-a.test.ts",
|
||||
"src/a.test.ts",
|
||||
"src/b.test.ts",
|
||||
"src/c.test.ts",
|
||||
"src/d.test.ts",
|
||||
]),
|
||||
);
|
||||
).toEqual(new Set(["src/base-a.test.ts", "src/a.test.ts", "src/c.test.ts"]));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ export default defineConfig({
|
|||
testTimeout: 120_000,
|
||||
hookTimeout: isWindows ? 180_000 : 120_000,
|
||||
// Many suites rely on `vi.stubEnv(...)` and expect it to be scoped to the test.
|
||||
// This is especially important under `pool=vmForks` where env leaks cross-file.
|
||||
// Keep env restoration automatic so shared-worker runs do not leak state.
|
||||
unstubEnvs: true,
|
||||
// Same rationale as unstubEnvs: avoid cross-test pollution under vmForks.
|
||||
// Same rationale as unstubEnvs: avoid cross-test pollution from shared globals.
|
||||
unstubGlobals: true,
|
||||
pool: "forks",
|
||||
maxWorkers: isCI ? ciWorkers : localWorkers,
|
||||
|
|
|
|||
|
|
@ -21,8 +21,7 @@ export default defineConfig({
|
|||
...base,
|
||||
test: {
|
||||
...baseTest,
|
||||
// vmForks reuses VM contexts in ways that can leak module state/mocks across
|
||||
// files for our e2e harnesses. Use process forks for deterministic isolation.
|
||||
// Keep e2e in process forks for deterministic cross-file isolation.
|
||||
pool: "forks",
|
||||
maxWorkers: e2eWorkers,
|
||||
silent: !verboseE2E,
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@ import baseConfig from "./vitest.config.ts";
|
|||
|
||||
export function createScopedVitestConfig(
|
||||
include: string[],
|
||||
options?: { exclude?: string[]; pool?: "threads" | "forks" | "vmForks" },
|
||||
options?: { exclude?: string[]; pool?: "threads" | "forks" },
|
||||
) {
|
||||
const base = baseConfig as unknown as Record<string, unknown>;
|
||||
const baseTest =
|
||||
(baseConfig as { test?: { exclude?: string[]; pool?: "threads" | "forks" | "vmForks" } })
|
||||
.test ?? {};
|
||||
const baseTest = (baseConfig as { test?: { exclude?: string[]; pool?: "threads" | "forks" } })
|
||||
.test ?? {};
|
||||
const exclude = [...(baseTest.exclude ?? []), ...(options?.exclude ?? [])];
|
||||
|
||||
return defineConfig({
|
||||
|
|
|
|||
Loading…
Reference in New Issue