Files
openclaw/scripts/test-planner/catalog.mjs
Tak Hoffman ab37d8810d test: introduce planner-backed test runner, stabilize local builds (#54650)
* test: stabilize ci and local vitest workers

* test: introduce planner-backed test runner

* test: address planner review follow-ups

* test: derive planner budgets from host capabilities

* test: restore planner filter helper import

* test: align planner explain output with execution

* test: keep low profile as serial alias

* test: restrict explicit planner file targets

* test: clean planner exits and pnpm launch

* test: tighten wrapper flag validation

* ci: gate heavy fanout on check

* test: key shard assignments by unit identity

* ci(bun): shard vitest lanes further

* test: restore ci overlap and stabilize planner tests

* test: relax planner output worker assertions

* test: reset plugin runtime state in optional tools suite

* ci: split macos node and swift jobs

* test: honor no-isolate top-level concurrency budgets

* ci: fix macos swift format lint

* test: cap max-profile top-level concurrency

* ci: shard macos node checks

* ci: use four macos node shards

* test: normalize explain targets before classification
2026-03-25 18:11:58 -05:00

188 lines
6.9 KiB
JavaScript

import fs from "node:fs";
import path from "node:path";
import { channelTestPrefixes } from "../../vitest.channel-paths.mjs";
import { isUnitConfigTestFile } from "../../vitest.unit-paths.mjs";
import { dedupeFilesPreserveOrder, loadTestRunnerBehavior } from "../test-runner-manifest.mjs";
const baseConfigPrefixes = ["src/agents/", "src/auto-reply/", "src/commands/", "test/", "ui/"];
export const normalizeRepoPath = (value) => value.split(path.sep).join("/");
const toRepoRelativePath = (value) => {
const relativePath = normalizeRepoPath(path.relative(process.cwd(), path.resolve(value)));
return relativePath.startsWith("../") || relativePath === ".." ? null : relativePath;
};
const walkTestFiles = (rootDir) => {
if (!fs.existsSync(rootDir)) {
return [];
}
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const fullPath = path.join(rootDir, entry.name);
if (entry.isDirectory()) {
files.push(...walkTestFiles(fullPath));
continue;
}
if (!entry.isFile()) {
continue;
}
if (
fullPath.endsWith(".test.ts") ||
fullPath.endsWith(".live.test.ts") ||
fullPath.endsWith(".e2e.test.ts")
) {
files.push(normalizeRepoPath(fullPath));
}
}
return files;
};
export function loadTestCatalog() {
const behaviorManifest = loadTestRunnerBehavior();
const existingFiles = (entries) =>
entries.map((entry) => entry.file).filter((file) => fs.existsSync(file));
const existingUnitConfigFiles = (entries) => existingFiles(entries).filter(isUnitConfigTestFile);
const baseThreadPinnedFiles = existingFiles(behaviorManifest.base?.threadPinned ?? []);
const channelIsolatedManifestFiles = existingFiles(behaviorManifest.channels?.isolated ?? []);
const channelIsolatedPrefixes = behaviorManifest.channels?.isolatedPrefixes ?? [];
const extensionForkIsolatedFiles = existingFiles(behaviorManifest.extensions?.isolated ?? []);
const unitForkIsolatedFiles = existingUnitConfigFiles(behaviorManifest.unit.isolated);
const unitThreadPinnedFiles = existingUnitConfigFiles(behaviorManifest.unit.threadPinned);
const unitBehaviorOverrideSet = new Set([...unitForkIsolatedFiles, ...unitThreadPinnedFiles]);
const allKnownTestFiles = [
...new Set([
...walkTestFiles("src"),
...walkTestFiles("extensions"),
...walkTestFiles("test"),
...walkTestFiles(path.join("ui", "src", "ui")),
]),
];
const channelIsolatedFiles = dedupeFilesPreserveOrder([
...channelIsolatedManifestFiles,
...allKnownTestFiles.filter((file) =>
channelIsolatedPrefixes.some((prefix) => file.startsWith(prefix)),
),
]);
const channelIsolatedFileSet = new Set(channelIsolatedFiles);
const extensionForkIsolatedFileSet = new Set(extensionForkIsolatedFiles);
const baseThreadPinnedFileSet = new Set(baseThreadPinnedFiles);
const unitThreadPinnedFileSet = new Set(unitThreadPinnedFiles);
const unitForkIsolatedFileSet = new Set(unitForkIsolatedFiles);
const classifyTestFile = (fileFilter, options = {}) => {
const normalizedFile = normalizeRepoPath(fileFilter);
const reasons = [];
const isolated =
options.unitMemoryIsolatedFiles?.includes(normalizedFile) ||
unitForkIsolatedFileSet.has(normalizedFile) ||
extensionForkIsolatedFileSet.has(normalizedFile) ||
channelIsolatedFileSet.has(normalizedFile);
if (options.unitMemoryIsolatedFiles?.includes(normalizedFile)) {
reasons.push("unit-memory-isolated");
}
if (unitForkIsolatedFileSet.has(normalizedFile)) {
reasons.push("unit-isolated-manifest");
}
if (extensionForkIsolatedFileSet.has(normalizedFile)) {
reasons.push("extensions-isolated-manifest");
}
if (channelIsolatedFileSet.has(normalizedFile)) {
reasons.push("channels-isolated-rule");
}
let surface = "base";
if (isUnitConfigTestFile(normalizedFile)) {
surface = "unit";
} else if (normalizedFile.endsWith(".live.test.ts")) {
surface = "live";
} else if (normalizedFile.endsWith(".e2e.test.ts")) {
surface = "e2e";
} else if (channelTestPrefixes.some((prefix) => normalizedFile.startsWith(prefix))) {
surface = "channels";
} else if (normalizedFile.startsWith("extensions/")) {
surface = "extensions";
} else if (normalizedFile.startsWith("src/gateway/")) {
surface = "gateway";
} else if (baseConfigPrefixes.some((prefix) => normalizedFile.startsWith(prefix))) {
surface = "base";
} else if (normalizedFile.startsWith("src/")) {
surface = "unit";
}
if (surface === "unit") {
reasons.push("unit-surface");
} else if (surface !== "base") {
reasons.push(`${surface}-surface`);
} else {
reasons.push("base-surface");
}
const legacyBasePinned = baseThreadPinnedFileSet.has(normalizedFile);
if (legacyBasePinned) {
reasons.push("base-pinned-manifest");
}
if (unitThreadPinnedFileSet.has(normalizedFile)) {
reasons.push("unit-pinned-manifest");
}
return {
file: normalizedFile,
surface,
isolated,
legacyBasePinned,
reasons,
};
};
const resolveFilterMatches = (fileFilter) => {
const normalizedFilter = normalizeRepoPath(fileFilter);
const repoRelativeFilter = toRepoRelativePath(fileFilter);
if (fs.existsSync(fileFilter)) {
const stats = fs.statSync(fileFilter);
if (stats.isFile()) {
if (repoRelativeFilter && allKnownTestFiles.includes(repoRelativeFilter)) {
return [repoRelativeFilter];
}
throw new Error(`Explicit path ${fileFilter} is not a known test file.`);
}
if (stats.isDirectory()) {
if (!repoRelativeFilter) {
throw new Error(`Explicit path ${fileFilter} is outside the repo test roots.`);
}
const prefix = repoRelativeFilter.endsWith("/")
? repoRelativeFilter
: `${repoRelativeFilter}/`;
const matches = allKnownTestFiles.filter((file) => file.startsWith(prefix));
if (matches.length === 0) {
throw new Error(`Explicit path ${fileFilter} does not contain known test files.`);
}
return matches;
}
}
if (/[*?[\]{}]/.test(normalizedFilter)) {
return allKnownTestFiles.filter((file) => path.matchesGlob(file, normalizedFilter));
}
return allKnownTestFiles.filter((file) => file.includes(normalizedFilter));
};
return {
allKnownTestFiles,
allKnownUnitFiles: allKnownTestFiles.filter((file) => isUnitConfigTestFile(file)),
baseThreadPinnedFiles,
channelIsolatedFiles,
channelIsolatedFileSet,
channelTestPrefixes,
extensionForkIsolatedFiles,
extensionForkIsolatedFileSet,
unitBehaviorOverrideSet,
unitForkIsolatedFiles,
unitThreadPinnedFiles,
baseThreadPinnedFileSet,
classifyTestFile,
resolveFilterMatches,
};
}
export const testSurfaces = ["unit", "extensions", "channels", "gateway", "live", "e2e", "base"];