perf(test): reuse CLI boundary worker module graphs

Share CLI boundary module evaluation in bounded forks while preserving fresh command instances and child processes. Keep module-mocked cleanup tests and source/compiled product catalog verification isolated, and update focused artifact selection.

Flameox full cohort with V8 coverage: 102.8s to 96.6s; 290 passed and two existing skips. Statement, function and branch coverage maps and hit sets match across all 1,153 source files. After syncing main, 75 focused tests, typecheck, lint, docs checks and formatting pass; all 834 test files are discovered exactly once.
This commit is contained in:
morluto
2026-10-11 20:29:12 +08:00
parent 30787297d4
commit f178bf1c00
6 changed files with 48 additions and 26 deletions
+13 -6
View File
@@ -927,7 +927,7 @@ behavior, and complete CI evidence before merging.
| `npm run test:focused -- PATH...` | Exact existing test files; compiled boundaries build first, and unmatched paths fail |
| `npm run test:changed` | Source tests affected since the merge base with `origin/main`, including committed and dirty changes |
| `npm run test:fast` | All domain, service, adapter, composition, conformance, and evaluation tests without build |
| `npm run test:boundary` | Boundary, MCP boundary, process boundary, and process-global projects |
| `npm run test:boundary` | Boundary, CLI boundary, MCP boundary, process boundary, and process-global projects |
| `npm run test:mcp` | MCP boundary project |
| `npm run test:acceptance` | Complete compiled CLI and MCP acceptance workflows |
| `npm run test:watch` | Dirty source tests in watch mode, without build |
@@ -989,11 +989,18 @@ Vitest projects use up to two workers, bounded by host parallelism.
`process-boundary` runs later with serial files because process-tree sampling
shares host resources. Acceptance and process-global cases use isolated forks
without serial scheduling. Pure domain/service, composition, conformance,
evaluation, and MCP boundary projects share module graphs; their tests avoid
process-global state and keep resource cleanup test-scoped. Each MCP session
still owns its resources. Adapter, non-MCP boundary, acceptance, and
process-global projects retain per-file isolation. See `vitest.config.ts` for
current settings.
evaluation, CLI boundary, and MCP boundary projects share module graphs. Their
tests avoid process-global state and keep resource cleanup test-scoped. Each
MCP session still owns its resources, and CLI cases retain fresh command
instances or subprocesses. CLI module mocks belong in `tests/process-global/`
so they keep per-file isolation. Product catalog verification stays with the
isolated filesystem boundary tests because it loads both source and compiled
module graphs. Adapter, other boundary, acceptance, and process-global projects
retain per-file isolation. See `vitest.config.ts` for current settings.
The reused CLI boundary workers cap V8 old space at 384 MiB, based on their
profiled allocation and cleanup behavior. This encourages collection between
files; spawned REA processes retain their own heap settings.
Vitest worker limits do not bound subprocesses launched within a test. Bound
those batches separately so repeated CLI validation cannot oversubscribe the
+2 -2
View File
@@ -124,14 +124,14 @@
"test:local": "node scripts/run-development-tests.mjs local",
"test:fast": "vitest run --project domain --project services --project adapters --project composition --project conformance --project evaluation",
"pretest:boundary": "npm run test:prepare",
"test:boundary": "vitest run --project boundary --project process-boundary --project mcp-boundary --project process-global",
"test:boundary": "vitest run --project boundary --project cli-boundary --project process-boundary --project mcp-boundary --project process-global",
"pretest:mcp": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run artifacts:skills artifacts:mcp-catalog",
"test:mcp": "vitest run --project mcp-boundary",
"pretest:acceptance": "npm run build:cached",
"test:acceptance": "vitest run --project acceptance",
"test:changed": "node scripts/run-development-tests.mjs changed",
"pretest:integration": "npm run test:prepare",
"test:integration": "vitest run --project boundary --project process-boundary --project mcp-boundary --project process-global --project acceptance",
"test:integration": "vitest run --project boundary --project cli-boundary --project process-boundary --project mcp-boundary --project process-global --project acceptance",
"pretest:coverage": "npm run test:prepare",
"test:coverage": "vitest run --coverage.enabled --coverage.provider=v8",
"test:ci": "npm run test:coverage -- --reporter=default --reporter=junit --outputFile.junit=test-results.xml",
+3 -1
View File
@@ -56,7 +56,9 @@ const artifactConsumers = {
"tests/boundary/mcp/contractPresentation.test.ts",
"tests/boundary/mcp/toolSchemaValidity.test.ts",
],
"artifacts:product-catalog": ["tests/boundary/cli/productCatalog.test.ts"],
"artifacts:product-catalog": [
"tests/boundary/filesystem/productCatalog.test.ts",
],
"artifacts:managed-evidence": [
"tests/boundary/filesystem/setupSkill.test.ts",
],
@@ -1,33 +1,33 @@
import { Cli } from "incur";
import { expect, it, vi } from "vitest";
import type { BrowserScenarioCapturePort } from "../../../src/application/BrowserScenarioCapturePort.js";
import { createBrowserScenarioEvidence } from "../../../src/application/BrowserScenarioEvidence.js";
import type { BrowserScenarioSessionPort } from "../../../src/browser/BrowserScenarioSessionPort.js";
import { PlaywrightBrowserScenarioProvider } from "../../../src/browser/PlaywrightBrowserScenarioProvider.js";
import { PlaywrightScenarioBrowserCleanupOwner } from "../../../src/browser/PlaywrightScenarioBrowser.js";
import { registerBrowserScenarioCommands } from "../../../src/cliBrowserScenarioCommands.js";
import { sanitizeBrowserUrl } from "../../../src/domain/browserObservation.js";
import type { BrowserScenarioCapturePort } from "../../src/application/BrowserScenarioCapturePort.js";
import { createBrowserScenarioEvidence } from "../../src/application/BrowserScenarioEvidence.js";
import type { BrowserScenarioSessionPort } from "../../src/browser/BrowserScenarioSessionPort.js";
import { PlaywrightBrowserScenarioProvider } from "../../src/browser/PlaywrightBrowserScenarioProvider.js";
import { PlaywrightScenarioBrowserCleanupOwner } from "../../src/browser/PlaywrightScenarioBrowser.js";
import { registerBrowserScenarioCommands } from "../../src/cliBrowserScenarioCommands.js";
import { sanitizeBrowserUrl } from "../../src/domain/browserObservation.js";
import {
browserScenarioSchema,
type BrowserScenario,
} from "../../../src/domain/browserScenario.js";
import type { BrowserScenarioAction } from "../../../src/domain/browserScenarioValues.js";
import { browserStepArtifactsSchema } from "../../../src/domain/browserScenarioCaptureValues.js";
import { silentLogger } from "../../../src/logger.js";
} from "../../src/domain/browserScenario.js";
import type { BrowserScenarioAction } from "../../src/domain/browserScenarioValues.js";
import { browserStepArtifactsSchema } from "../../src/domain/browserScenarioCaptureValues.js";
import { silentLogger } from "../../src/logger.js";
import {
AnalysisCancelledError,
AnalysisTimeoutError,
} from "../../../src/domain/analysisErrorCore.js";
import { BrowserObservationError } from "../../../src/domain/browserObservationError.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { jsonObjectSchema } from "../../../src/domain/jsonValue.js";
import { ok } from "../../../src/domain/result.js";
} from "../../src/domain/analysisErrorCore.js";
import { BrowserObservationError } from "../../src/domain/browserObservationError.js";
import { parseEvidence } from "../../src/domain/evidence.js";
import { jsonObjectSchema } from "../../src/domain/jsonValue.js";
import { ok } from "../../src/domain/result.js";
const state = vi.hoisted(() => ({
provider: undefined as BrowserScenarioCapturePort | undefined,
}));
vi.mock("../../../src/composition/browserScenario.js", () => ({
vi.mock("../../src/composition/browserScenario.js", () => ({
createBrowserScenarioProvider: () => state.provider,
}));
+13
View File
@@ -69,12 +69,25 @@ const TEST_PROJECTS = [
name: "boundary",
include: ["tests/boundary/**/*.test.ts"],
exclude: [
"tests/boundary/cli/**/*.test.ts",
"tests/boundary/mcp/**/*.test.ts",
"tests/boundary/process/**/*.test.ts",
],
pool: "forks" as const,
maxWorkers: MAX_TEST_WORKERS,
},
{
name: "cli-boundary",
include: ["tests/boundary/cli/**/*.test.ts"],
pool: "forks" as const,
maxWorkers: MAX_TEST_WORKERS,
// CLI tests create fresh command instances or subprocesses and own their
// cleanup. Reuse the immutable graph; module mocks belong in process-global.
isolate: false,
// Long-lived workers should collect temporary command/schema allocations
// before their two heaps compete with the real CLI child processes.
execArgv: ["--max-old-space-size=384"],
},
{
// Process-tree observations share host sampling resources; keep this small
// lane serial while the independent projects run together.