mirror of
https://github.com/morluto/rea.git
synced 2026-11-14 10:43:30 +00:00
Probes in a source and a built project read each collected test's deadline, including test, suite, and module long-running tags, so a change in project inheritance or tag resolution fails directly. Both the policy and the probes read one set of base deadlines. Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
222 lines
7.4 KiB
TypeScript
222 lines
7.4 KiB
TypeScript
import { realpathSync } from "node:fs";
|
|
import { availableParallelism, tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
import { defineConfig } from "vitest/config";
|
|
import { parseCLI } from "vitest/node";
|
|
import {
|
|
BUILT_TEST_PROJECTS,
|
|
SOURCE_TEST_PROJECTS,
|
|
} from "./scripts/lib/test-projects.mjs";
|
|
|
|
import { CiTestSequencer } from "./tests/ciTestSequencer.js";
|
|
import {
|
|
COVERAGE_DEADLINE_SCALE,
|
|
HOSTED_RUNNER_DEADLINE_SCALE,
|
|
LONG_RUNNING_DEADLINE_MS,
|
|
TEST_DEADLINE_MS as BASE_TEST_DEADLINE_MS,
|
|
} from "./tests/support/testDeadlines.js";
|
|
|
|
// Playwright's registry does not derive a cache directory for Termux's
|
|
// `android` platform. Match the production CLI fallback before workers import
|
|
// browser modules.
|
|
if (
|
|
process.platform === "android" &&
|
|
process.env.PLAYWRIGHT_BROWSERS_PATH === undefined
|
|
) {
|
|
const cacheRoot =
|
|
process.env.XDG_CACHE_HOME ?? join(process.env.HOME ?? ".", ".cache");
|
|
process.env.PLAYWRIGHT_BROWSERS_PATH = join(cacheRoot, "ms-playwright");
|
|
}
|
|
|
|
const CANONICAL_TEMPORARY_DIRECTORY = realpathSync(tmpdir());
|
|
// Use Vitest's option parser so explicit false and spaced shard values retain
|
|
// their meaning. Unknown worker/embedding arguments are not Vitest options.
|
|
const { options: testOptions } = parseCLI(
|
|
["vitest", ...process.argv.slice(2)],
|
|
{ allowUnknownOptions: true },
|
|
);
|
|
const COVERAGE_ENABLED = testOptions.coverage?.enabled === true;
|
|
const COVERAGE_SHARD = testOptions.shard !== undefined;
|
|
// Bound concurrency by the runner rather than command locks. A hosted CI job
|
|
// owns its whole four-core runner, so it may use every core; other hosts stay
|
|
// at two workers because developers and agents share them with other checks.
|
|
const DEDICATED_CI_RUNNER = process.env.GITHUB_ACTIONS === "true";
|
|
const MAX_TEST_WORKERS = Math.min(
|
|
DEDICATED_CI_RUNNER ? 4 : 2,
|
|
availableParallelism(),
|
|
);
|
|
// A test deadline only turns a hang into a named failure; it is not a
|
|
// performance assertion. Projects own it so tests do not hard-code host speed.
|
|
// Built projects launch compiled CLI, MCP, and provider processes while other
|
|
// lanes share the runner, and coverage instrumentation slows every lane. Hosted
|
|
// CI runs a worker per core, so CPU-bound cases there share every core with
|
|
// other workers and their subprocesses.
|
|
const DEADLINE_SCALE =
|
|
(COVERAGE_ENABLED ? COVERAGE_DEADLINE_SCALE : 1) *
|
|
(DEDICATED_CI_RUNNER ? HOSTED_RUNNER_DEADLINE_SCALE : 1);
|
|
const TEST_DEADLINE_MS = {
|
|
source: BASE_TEST_DEADLINE_MS.source * DEADLINE_SCALE,
|
|
built: BASE_TEST_DEADLINE_MS.built * DEADLINE_SCALE,
|
|
} as const;
|
|
|
|
const TEST_PROJECTS = [...SOURCE_TEST_PROJECTS, ...BUILT_TEST_PROJECTS];
|
|
const PROJECT_POLICIES = {
|
|
domain: {
|
|
pool: "threads" as const,
|
|
maxWorkers: MAX_TEST_WORKERS,
|
|
// Domain and contract modules are pure and own no process-global state, so
|
|
// per-file module isolation adds startup cost without protecting a seam.
|
|
},
|
|
services: {
|
|
pool: "threads" as const,
|
|
maxWorkers: MAX_TEST_WORKERS,
|
|
// Service tests use recording ports and own no process-global state. Share
|
|
// their module graph so file startup does not dominate the lane.
|
|
},
|
|
adapters: { pool: "forks" as const, maxWorkers: MAX_TEST_WORKERS },
|
|
composition: {
|
|
pool: "threads" as const,
|
|
maxWorkers: MAX_TEST_WORKERS,
|
|
// Composition tests use provider-neutral sessions and test-scoped cleanup;
|
|
// sharing their immutable module graph avoids per-file startup work.
|
|
},
|
|
boundary: { pool: "forks" as const, maxWorkers: MAX_TEST_WORKERS },
|
|
"cli-boundary": {
|
|
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.
|
|
// The shared source/contract graph also needs transient schema/help space.
|
|
// Ordinary CI exhausted 384 MiB after earlier files warmed that graph.
|
|
// Use the measured 512 MiB budget with the same bounded concurrency.
|
|
execArgv: ["--max-old-space-size=512"],
|
|
},
|
|
"process-boundary": {
|
|
pool: "forks" as const,
|
|
maxWorkers: MAX_TEST_WORKERS,
|
|
// Process-tree observations share host sampling resources; keep this small
|
|
// lane serial while the independent projects run together.
|
|
fileParallelism: false,
|
|
sequence: { groupOrder: 1 },
|
|
},
|
|
"mcp-boundary": {
|
|
pool: "threads" as const,
|
|
maxWorkers: MAX_TEST_WORKERS,
|
|
// Each file creates and closes an explicit in-memory MCP session. Reuse the
|
|
// immutable server graph so module startup does not outweigh transport work.
|
|
},
|
|
acceptance: { pool: "forks" as const, maxWorkers: MAX_TEST_WORKERS },
|
|
"process-global": { pool: "forks" as const, maxWorkers: MAX_TEST_WORKERS },
|
|
conformance: {
|
|
pool: "threads" as const,
|
|
maxWorkers: MAX_TEST_WORKERS,
|
|
// Contract cases use recording providers and own no process-global state.
|
|
},
|
|
evaluation: {
|
|
pool: "threads" as const,
|
|
maxWorkers: MAX_TEST_WORKERS,
|
|
// Evaluator tests inspect deterministic inputs without mutating shared state.
|
|
},
|
|
};
|
|
|
|
const ZERO_COVERAGE_THRESHOLDS = {
|
|
statements: 0,
|
|
branches: 0,
|
|
functions: 0,
|
|
lines: 0,
|
|
"src/domain/**": {
|
|
statements: 0,
|
|
branches: 0,
|
|
functions: 0,
|
|
lines: 0,
|
|
},
|
|
"src/contracts/**": {
|
|
statements: 0,
|
|
branches: 0,
|
|
functions: 0,
|
|
lines: 0,
|
|
},
|
|
};
|
|
|
|
const projects = TEST_PROJECTS.map((project) => {
|
|
const policy =
|
|
PROJECT_POLICIES[project.name as keyof typeof PROJECT_POLICIES];
|
|
if (!policy)
|
|
throw new Error(`Missing test runner policy for ${project.name}`);
|
|
return {
|
|
extends: true as const,
|
|
test: {
|
|
name: project.name,
|
|
include: [...project.include],
|
|
...(project.exclude ? { exclude: [...project.exclude] } : {}),
|
|
isolate: project.isolate !== false,
|
|
testTimeout: TEST_DEADLINE_MS[project.group],
|
|
hookTimeout: TEST_DEADLINE_MS[project.group],
|
|
...policy,
|
|
},
|
|
};
|
|
});
|
|
|
|
export default defineConfig({
|
|
test: {
|
|
env: { TMPDIR: CANONICAL_TEMPORARY_DIRECTORY },
|
|
maxWorkers: MAX_TEST_WORKERS,
|
|
projects,
|
|
...(process.env.REA_CI_TEST_HISTORY
|
|
? { sequence: { sequencer: CiTestSequencer } }
|
|
: {}),
|
|
retry: 0,
|
|
reporters: ["default"],
|
|
runner: "./tests/processOwnershipRunner.ts",
|
|
testTimeout: TEST_DEADLINE_MS.source,
|
|
hookTimeout: TEST_DEADLINE_MS.source,
|
|
// Deadline probes compare what Vitest applies with the configured policy.
|
|
provide: { testDeadlineScale: DEADLINE_SCALE },
|
|
tags: [
|
|
{
|
|
name: "long-running",
|
|
description:
|
|
"A bounded workload that needs more than its project's deadline",
|
|
timeout: LONG_RUNNING_DEADLINE_MS * DEADLINE_SCALE,
|
|
},
|
|
],
|
|
coverage: {
|
|
enabled: false,
|
|
provider: "v8",
|
|
reportsDirectory: join(
|
|
tmpdir(),
|
|
`rea-vitest-coverage-${String(process.pid)}`,
|
|
),
|
|
include: ["src/**"],
|
|
thresholds: COVERAGE_SHARD
|
|
? ZERO_COVERAGE_THRESHOLDS
|
|
: {
|
|
statements: 65,
|
|
branches: 60,
|
|
functions: 60,
|
|
lines: 68,
|
|
"src/domain/**": {
|
|
statements: 80,
|
|
branches: 75,
|
|
functions: 75,
|
|
lines: 80,
|
|
},
|
|
"src/contracts/**": {
|
|
statements: 85,
|
|
branches: 80,
|
|
functions: 80,
|
|
lines: 85,
|
|
},
|
|
},
|
|
reporter: ["text", "text-summary"],
|
|
},
|
|
},
|
|
});
|
|
|
|
declare module "vitest" {
|
|
export interface ProvidedContext {
|
|
readonly testDeadlineScale: number;
|
|
}
|
|
}
|