fix(tooling): bound tsgo core test memory

This commit is contained in:
Jesse Merhi
2026-08-13 03:18:41 +10:00
parent 7dbe21b9cf
commit 887c44ef8f
29 changed files with 590 additions and 104 deletions
+3 -3
View File
@@ -1942,7 +1942,7 @@
"tsgo:all": "node scripts/run-tsgo.mjs -b tsconfig.projects.json",
"tsgo:core": "node scripts/run-tsgo.mjs -p tsconfig.core.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core.tsbuildinfo",
"tsgo:core:all": "node scripts/run-tsgo.mjs -b tsconfig.core.projects.json",
"tsgo:core:test": "node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.core.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core-test.tsbuildinfo",
"tsgo:core:test": "node scripts/run-tsgo.mjs -b test/tsconfig/tsconfig.core.test.projects.json",
"tsgo:extensions": "node scripts/run-tsgo.mjs -p tsconfig.extensions.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions.tsbuildinfo",
"tsgo:extensions:all": "node scripts/run-tsgo.mjs -b tsconfig.extensions.projects.json",
"tsgo:extensions:test": "node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.extensions.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions-test.tsbuildinfo",
@@ -1953,8 +1953,8 @@
"tsgo:test:extensions": "pnpm tsgo:extensions:test",
"tsgo:test:packages": "node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.test.packages.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/test-packages.tsbuildinfo",
"tsgo:test:root": "node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.test.root.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/test-root.tsbuildinfo",
"tsgo:test:src": "node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.test.src.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/test-src.tsbuildinfo",
"tsgo:test:ui": "node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.test.ui.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/test-ui.tsbuildinfo",
"tsgo:test:src": "node scripts/run-tsgo.mjs -b test/tsconfig/tsconfig.core.test.src.projects.json",
"tsgo:test:ui": "node scripts/run-tsgo.mjs -b test/tsconfig/tsconfig.core.test.ui.projects.json",
"tsgo:ui": "node scripts/run-tsgo.mjs -p tsconfig.ui.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/ui.tsbuildinfo",
"tui": "node scripts/run-node.mjs tui",
"tui:dev": "node --import tsx scripts/run-with-env.mts OPENCLAW_PROFILE=dev -- node scripts/run-node.mjs --dev tui",
+112 -3
View File
@@ -2,18 +2,28 @@
// Enforces core tsgo project boundaries and sparse-checkout safety.
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { resolveRepoToolBinPath } from "./lib/local-heavy-check-runtime.mts";
import { createManagedCommandInvocation } from "./lib/managed-child-process.mts";
import { resolveRepoRoot } from "./lib/repo-root.mjs";
import {
findTsgoCoreTestShardViolations,
TSGO_CORE_TEST_MAX_ROOTS,
TSGO_CORE_TEST_SHARDS,
} from "./lib/tsgo-core-test-shards.mts";
const repoRoot = resolveRepoRoot(import.meta.url);
const tsgoPath = resolveRepoToolBinPath("tsgo", { cwd: repoRoot });
const canonicalCoreTestConfig = "test/tsconfig/tsconfig.core.test.json";
const coreTestProjectsConfig = "test/tsconfig/tsconfig.core.test.projects.json";
const coreGraphs = [
{ name: "core", config: "tsconfig.core.json" },
{ name: "ui", config: "tsconfig.ui.json" },
{ name: "core-test", config: "test/tsconfig/tsconfig.core.test.json" },
{ name: "core-test-agents", config: "test/tsconfig/tsconfig.core.test.agents.json" },
{ name: "core-test-non-agents", config: "test/tsconfig/tsconfig.core.test.non-agents.json" },
...TSGO_CORE_TEST_SHARDS.map((shard) => ({
name: `core-test-${shard.name}`,
config: shard.config,
})),
];
function normalizeFilePath(filePath: string) {
const normalized = filePath.trim().replaceAll("\\", "/");
@@ -46,6 +56,105 @@ function listGraphFiles(graph: (typeof coreGraphs)[number]) {
return (result.stdout ?? "").split(/\r?\n/u).map(normalizeFilePath).filter(Boolean);
}
function readGraphConfig(config: string): {
compilerOptions?: { tsBuildInfoFile?: string };
files?: string[];
} {
const tsgo = createManagedCommandInvocation({
args: ["-p", config, "--pretty", "false", "--showConfig"],
bin: tsgoPath,
});
const result = spawnSync(tsgo.command, tsgo.args, {
cwd: repoRoot,
encoding: "utf8",
maxBuffer: 256 * 1024 * 1024,
shell: tsgo.shell,
windowsVerbatimArguments: tsgo.windowsVerbatimArguments,
});
if (result.error) {
throw result.error;
}
if ((result.status ?? 1) !== 0) {
const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
throw new Error(`${config} config expansion failed with exit code ${result.status}\n${output}`);
}
return JSON.parse(result.stdout ?? "") as {
compilerOptions?: { tsBuildInfoFile?: string };
files?: string[];
};
}
function listProjectLeaves(config: string, seen = new Set<string>()): string[] {
const absoluteConfig = path.resolve(repoRoot, config);
if (seen.has(absoluteConfig)) {
throw new Error(`Project-reference cycle at ${normalizeFilePath(absoluteConfig)}`);
}
seen.add(absoluteConfig);
const parsed = JSON.parse(fs.readFileSync(absoluteConfig, "utf8")) as {
references?: { path: string }[];
};
if (!parsed.references || parsed.references.length === 0) {
return [normalizeFilePath(absoluteConfig)];
}
return parsed.references.flatMap((reference) => {
const referencedConfig = path.resolve(path.dirname(absoluteConfig), reference.path);
return listProjectLeaves(path.relative(repoRoot, referencedConfig), seen);
});
}
const testRootPattern = /\.test\.(?:ts|tsx)$/u;
const canonicalRoots = (readGraphConfig(canonicalCoreTestConfig).files ?? [])
.map(normalizeFilePath)
.filter((file) => testRootPattern.test(file));
const shardConfigs = TSGO_CORE_TEST_SHARDS.map((shard) => ({
...shard,
expanded: readGraphConfig(shard.config),
}));
const shardViolations = findTsgoCoreTestShardViolations({
canonicalRoots,
shards: shardConfigs.map((shard) => ({
name: shard.name,
roots: (shard.expanded.files ?? [])
.map(normalizeFilePath)
.filter((file) => testRootPattern.test(file)),
})),
});
const expectedLeaves = TSGO_CORE_TEST_SHARDS.map((shard) => shard.config).toSorted();
const actualLeaves = listProjectLeaves(coreTestProjectsConfig).toSorted();
if (JSON.stringify(actualLeaves) !== JSON.stringify(expectedLeaves)) {
shardViolations.push(
`${coreTestProjectsConfig} must reference exactly these leaves: ${expectedLeaves.join(", ")}`,
);
}
const buildInfoOwners = new Map<string, string[]>();
for (const shard of shardConfigs) {
const buildInfo = shard.expanded.compilerOptions?.tsBuildInfoFile;
if (!buildInfo) {
shardViolations.push(`${shard.name}: missing compilerOptions.tsBuildInfoFile`);
continue;
}
const owners = buildInfoOwners.get(buildInfo) ?? [];
owners.push(shard.name);
buildInfoOwners.set(buildInfo, owners);
}
for (const [buildInfo, owners] of buildInfoOwners) {
if (owners.length > 1) {
shardViolations.push(`shared tsBuildInfoFile (${owners.join(", ")}): ${buildInfo}`);
}
}
if (shardViolations.length > 0) {
console.error(
`Core test shards must cover every canonical test root exactly once and stay at or below ${TSGO_CORE_TEST_MAX_ROOTS} roots:`,
);
for (const violation of shardViolations) {
console.error(`- ${violation}`);
}
process.exit(1);
}
const violations: string[] = [];
for (const graph of coreGraphs) {
const extensionFiles = listGraphFiles(graph).filter((file) => file.startsWith("extensions/"));
+65
View File
@@ -0,0 +1,65 @@
export const TSGO_CORE_TEST_MAX_ROOTS = 720;
export const TSGO_CORE_TEST_SHARDS = [
{ name: "agents-root", config: "test/tsconfig/tsconfig.core.test.agents-root.json" },
{ name: "agents-other", config: "test/tsconfig/tsconfig.core.test.agents-other.json" },
{ name: "gateway", config: "test/tsconfig/tsconfig.core.test.gateway.json" },
{ name: "infra", config: "test/tsconfig/tsconfig.core.test.infra.json" },
{ name: "commands", config: "test/tsconfig/tsconfig.core.test.commands.json" },
{
name: "plugins-platform",
config: "test/tsconfig/tsconfig.core.test.plugins-platform.json",
},
{ name: "config-cli", config: "test/tsconfig/tsconfig.core.test.config-cli.json" },
{ name: "messaging", config: "test/tsconfig/tsconfig.core.test.messaging.json" },
{ name: "services", config: "test/tsconfig/tsconfig.core.test.services.json" },
{ name: "other", config: "test/tsconfig/tsconfig.core.test.other.json" },
{
name: "ui-pages-e2e",
config: "test/tsconfig/tsconfig.core.test.ui-pages-e2e.json",
},
{ name: "ui-other", config: "test/tsconfig/tsconfig.core.test.ui-other.json" },
{ name: "packages", config: "test/tsconfig/tsconfig.test.packages.json" },
] as const;
export type TsgoCoreTestShard = (typeof TSGO_CORE_TEST_SHARDS)[number];
export function findTsgoCoreTestShardViolations(params: {
canonicalRoots: readonly string[];
maxRoots?: number;
shards: readonly { name: string; roots: readonly string[] }[];
}): string[] {
const maxRoots = params.maxRoots ?? TSGO_CORE_TEST_MAX_ROOTS;
const canonical = new Set(params.canonicalRoots);
const owners = new Map<string, string[]>();
const violations: string[] = [];
for (const shard of params.shards) {
if (shard.roots.length > maxRoots) {
violations.push(
`${shard.name}: ${shard.roots.length} test roots exceeds the ${maxRoots} limit`,
);
}
for (const root of shard.roots) {
const rootOwners = owners.get(root) ?? [];
rootOwners.push(shard.name);
owners.set(root, rootOwners);
}
}
for (const root of canonical) {
const rootOwners = owners.get(root) ?? [];
if (rootOwners.length === 0) {
violations.push(`unassigned: ${root}`);
} else if (rootOwners.length > 1) {
violations.push(`assigned ${rootOwners.length} times (${rootOwners.join(", ")}): ${root}`);
}
}
for (const [root, rootOwners] of owners) {
if (!canonical.has(root)) {
violations.push(`not in the canonical core-test graph (${rootOwners.join(", ")}): ${root}`);
}
}
return violations;
}
+45 -18
View File
@@ -4,19 +4,32 @@ import fs from "node:fs";
import path from "node:path";
import { readFlagValue } from "./arg-utils.mts";
import { createManagedCommandInvocation } from "./managed-child-process.mts";
import { TSGO_CORE_TEST_SHARDS } from "./tsgo-core-test-shards.mts";
const PACKAGE_TEST_CONFIGS = new Set(["tsconfig.test.packages.json"]);
const CORE_TEST_CONFIGS = new Set([
"tsconfig.core.test.json",
"tsconfig.core.test.agents.json",
"tsconfig.core.test.non-agents.json",
"tsconfig.core.test.projects.json",
"tsconfig.core.test.src.projects.json",
"tsconfig.core.test.ui.projects.json",
...TSGO_CORE_TEST_SHARDS.map((shard) => path.basename(shard.config)).filter(
(config) => !PACKAGE_TEST_CONFIGS.has(config),
),
]);
const CORE_PROD_CONFIGS = new Set(["tsconfig.core.json"]);
const UI_PROD_CONFIGS = new Set(["tsconfig.ui.json"]);
const GUARDED_CONFIGS = new Set([
...CORE_PROD_CONFIGS,
...UI_PROD_CONFIGS,
...CORE_TEST_CONFIGS,
...PACKAGE_TEST_CONFIGS,
]);
const TSGO_SPARSE_SKIP_ENV_KEY = "OPENCLAW_TSGO_SPARSE_SKIP";
const CORE_PROD_SPARSE_ROOTS = ["packages"];
const UI_PROD_SPARSE_ROOTS = ["packages", "src", "ui/config", "ui/src"];
const CORE_TEST_SPARSE_ROOTS = ["packages", "ui/config", "ui/src"];
const PACKAGE_TEST_SPARSE_ROOTS = ["packages"];
const CORE_PROD_REQUIRED_PATHS = [
{
@@ -100,15 +113,8 @@ export function getSparseTsgoGuardError(
sparseCheckoutPatterns,
}: SparseGuardOptions = {},
) {
const projectPath = readProjectFlag(args);
const projectName = projectPath ? path.basename(projectPath) : null;
if (
!projectName ||
(!CORE_PROD_CONFIGS.has(projectName) &&
!UI_PROD_CONFIGS.has(projectName) &&
!CORE_TEST_CONFIGS.has(projectName)) ||
isMetadataOnlyCommand(args)
) {
const projectNames = readProjectNames(args);
if (projectNames.length === 0 || isMetadataOnlyCommand(args)) {
return null;
}
@@ -120,11 +126,17 @@ export function getSparseTsgoGuardError(
const sparsePatterns = sparseCheckoutPatterns ?? getSparseCheckoutPatterns({ cwd });
const missingPaths = [
...getRequiredSparseRootsForProject(projectName).filter((relativePath) =>
sparsePatterns ? !isSparseRootCovered(relativePath, sparsePatterns) : false,
...new Set(
projectNames
.flatMap(getRequiredSparseRootsForProject)
.filter((relativePath) =>
sparsePatterns ? !isSparseRootCovered(relativePath, sparsePatterns) : false,
),
),
...getRequiredPathsForProject(projectName, cwd, fileExists).filter(
(relativePath) => !fileExists(path.join(cwd, relativePath)),
...new Set(
projectNames
.flatMap((projectName) => getRequiredPathsForProject(projectName, cwd, fileExists))
.filter((relativePath) => !fileExists(path.join(cwd, relativePath))),
),
];
if (missingPaths.length === 0) {
@@ -132,7 +144,7 @@ export function getSparseTsgoGuardError(
}
return [
`${projectName} cannot be typechecked from this sparse checkout because tracked project inputs are missing or only partially included:`,
`${projectNames.join(", ")} cannot be typechecked from this sparse checkout because tracked project inputs are missing or only partially included:`,
...missingPaths.map((relativePath) => `- ${relativePath}`),
"Expand this worktree's sparse checkout to include those paths, or rerun in a full worktree.",
].join("\n");
@@ -148,6 +160,9 @@ function getRequiredSparseRootsForProject(projectName: string) {
if (CORE_TEST_CONFIGS.has(projectName)) {
return CORE_TEST_SPARSE_ROOTS;
}
if (PACKAGE_TEST_CONFIGS.has(projectName)) {
return PACKAGE_TEST_SPARSE_ROOTS;
}
return [];
}
@@ -236,8 +251,20 @@ function normalizeSparsePattern(pattern: string) {
.replace(/\/+$/, "");
}
function readProjectFlag(args: readonly string[]) {
return readFlagValue(args, "-p") ?? readFlagValue(args, "--project");
function readProjectNames(args: readonly string[]) {
const projectPath = readFlagValue(args, "-p") ?? readFlagValue(args, "--project");
const candidates = projectPath
? [projectPath]
: args.some((arg) => arg === "-b" || arg === "--build")
? args.filter((arg) => !arg.startsWith("-"))
: [];
return [
...new Set(candidates.map((candidate) => path.basename(candidate)).filter(isGuardedConfig)),
];
}
function isGuardedConfig(config: string) {
return GUARDED_CONFIGS.has(config);
}
function isMetadataOnlyCommand(args: readonly string[]) {
+37 -26
View File
@@ -14,10 +14,23 @@ import {
} from "./lib/local-heavy-check-runtime.mts";
import { createManagedCommandInvocation } from "./lib/managed-child-process.mts";
import { resolveRepoRoot } from "./lib/repo-root.mjs";
import { TSGO_CORE_TEST_SHARDS, type TsgoCoreTestShard } from "./lib/tsgo-core-test-shards.mts";
const repoRoot = resolveRepoRoot(import.meta.url);
const artifactRoot = path.resolve(repoRoot, ".artifacts/tsgo-profile");
const tsgoPath = resolveRepoToolBinPath("tsgo", { cwd: repoRoot });
type GraphDefinition = { config: string; description: string };
type CoreTestGraphName = `core-test-${TsgoCoreTestShard["name"]}`;
const CORE_TEST_GRAPH_DEFINITIONS = Object.fromEntries(
TSGO_CORE_TEST_SHARDS.map((shard) => [
`core-test-${shard.name}`,
{
config: shard.config,
description: `bounded core test shard: ${shard.name}`,
},
]),
) as Record<CoreTestGraphName, GraphDefinition>;
const GRAPH_DEFINITIONS = {
core: {
config: "tsconfig.core.json",
@@ -27,18 +40,7 @@ const GRAPH_DEFINITIONS = {
config: "tsconfig.ui.json",
description: "UI production graph",
},
"core-test": {
config: "test/tsconfig/tsconfig.core.test.json",
description: "core colocated test graph",
},
"core-test-agents": {
config: "test/tsconfig/tsconfig.core.test.agents.json",
description: "diagnostic slice: core agent colocated tests",
},
"core-test-non-agents": {
config: "test/tsconfig/tsconfig.core.test.non-agents.json",
description: "diagnostic slice: core tests excluding agent test roots",
},
...CORE_TEST_GRAPH_DEFINITIONS,
extensions: {
config: "tsconfig.extensions.json",
description: "bundled extension production graph",
@@ -50,6 +52,10 @@ const GRAPH_DEFINITIONS = {
} as const;
type GraphName = keyof typeof GRAPH_DEFINITIONS;
const DEFAULT_GRAPHS = [
...TSGO_CORE_TEST_SHARDS.map((shard) => `core-test-${shard.name}` as CoreTestGraphName),
"extensions-test",
] satisfies GraphName[];
type ProfileOptions = {
all: boolean;
deep: boolean;
@@ -73,19 +79,19 @@ function usage(): string {
"",
"Graphs:",
...Object.entries(GRAPH_DEFINITIONS).map(
([name, graph]) => ` ${name.padEnd(16)} ${graph.description}`,
([name, graph]) => ` ${name.padEnd(26)} ${graph.description}`,
),
"",
"Options:",
" --all Profile all graphs",
" --reuse Reuse profile tsbuildinfo files instead of forcing fresh checks",
" --deep Also write --generateTrace and --generateCpuProfile artifacts",
" --explain Also write --explainFiles artifacts",
" --deep Also write --generateTrace and --pprofDir artifacts",
" --explain Also write list-only --explainFiles artifacts",
" --out=<dir> Output directory (default: .artifacts/tsgo-profile)",
" --json Print JSON report to stdout",
" --help Show this help",
"",
"Default graphs: core-test extensions-test",
"Default graphs: all bounded core-test shards and extensions-test",
].join("\n");
}
@@ -138,7 +144,7 @@ function parseArgs(argv: string[]): { options: ProfileOptions; selectedGraphs: G
? (Object.keys(GRAPH_DEFINITIONS) as GraphName[])
: graphNames.length > 0
? graphNames
: (["core-test", "extensions-test"] satisfies GraphName[]);
: DEFAULT_GRAPHS;
return { options, selectedGraphs };
}
@@ -351,7 +357,7 @@ function renderTextReport(report: ProfileReport): string {
lines.push(`- ${group.key}: ${group.count}`);
}
if (graph.deep) {
lines.push(`Deep artifacts: ${graph.deep.traceDir}, ${graph.deep.cpuProfile}`);
lines.push(`Deep artifacts: ${graph.deep.traceDir}, ${graph.deep.profileDir}`);
}
if (graph.explain) {
lines.push(`Explain: ${graph.explain.artifact}`);
@@ -397,25 +403,30 @@ function profileGraph(name: GraphName, options: ProfileOptions) {
checkBuildInfo,
"--extendedDiagnostics",
];
let deep: { cpuProfile: string; traceDir: string } | undefined;
let deep: { profileDir: string; traceDir: string } | undefined;
if (options.deep) {
const traceDir = path.join(outDir, `${name}-trace`);
const cpuProfile = path.join(outDir, `${name}.cpuprofile`);
const profileDir = path.join(outDir, `${name}-pprof`);
fs.rmSync(traceDir, { force: true, recursive: true });
fs.rmSync(cpuProfile, { force: true });
checkArgs.push("--generateTrace", traceDir, "--generateCpuProfile", cpuProfile);
fs.rmSync(profileDir, { force: true, recursive: true });
fs.mkdirSync(profileDir, { recursive: true });
checkArgs.push("--generateTrace", traceDir, "--pprofDir", profileDir);
deep = {
traceDir: path.relative(repoRoot, traceDir),
cpuProfile: path.relative(repoRoot, cpuProfile),
profileDir: path.relative(repoRoot, profileDir),
};
}
const check = runTsgo(`${name}:check`, checkArgs);
let explain: { artifact: string; elapsedMs: number } | undefined;
if (options.explain) {
const explainArtifact = path.join(outDir, `${name}.explain.txt`);
const explainResult = runTsgo(`${name}:explainFiles`, [...baseArgs, "--explainFiles"], {
maxBuffer: 256 * 1024 * 1024,
});
const explainResult = runTsgo(
`${name}:explainFiles`,
[...baseArgs, "--listFilesOnly", "--explainFiles"],
{
maxBuffer: 256 * 1024 * 1024,
},
);
fs.writeFileSync(explainArtifact, `${explainResult.stdout}${explainResult.stderr}`);
explain = {
artifact: path.relative(repoRoot, explainArtifact),
+45 -1
View File
@@ -84,7 +84,7 @@ describe("run-tsgo sparse guard", () => {
}
expect(
getSparseTsgoGuardError(["-p", "test/tsconfig/tsconfig.core.test.non-agents.json"], {
getSparseTsgoGuardError(["-p", "test/tsconfig/tsconfig.core.test.other.json"], {
cwd,
isSparseCheckoutEnabled: () => true,
sparseCheckoutPatterns: ["/packages/", "/ui/config/", "/ui/src/"],
@@ -92,6 +92,50 @@ describe("run-tsgo sparse guard", () => {
).toBeNull();
});
it("guards the core-test build aggregate", () => {
const cwd = createTempDir("openclaw-run-tsgo-");
expect(
getSparseTsgoGuardError(["-b", "test/tsconfig/tsconfig.core.test.projects.json"], {
cwd,
isSparseCheckoutEnabled: () => true,
}),
).toContain("tsconfig.core.test.projects.json cannot be typechecked from this sparse checkout");
});
it("guards core-test build projects after options and alongside other projects", () => {
const cwd = createTempDir("openclaw-run-tsgo-");
expect(
getSparseTsgoGuardError(
[
"-b",
"--pretty",
"false",
"tsconfig.extensions.json",
"test/tsconfig/tsconfig.core.test.projects.json",
],
{
cwd,
isSparseCheckoutEnabled: () => true,
},
),
).toContain("tsconfig.core.test.projects.json cannot be typechecked from this sparse checkout");
});
it("keeps package-test sparse roots package-scoped", () => {
const cwd = createTempDir("openclaw-run-tsgo-");
expect(
getSparseTsgoGuardError(["-p", "test/tsconfig/tsconfig.test.packages.json"], {
cwd,
fileExists: () => true,
isSparseCheckoutEnabled: () => true,
sparseCheckoutPatterns: ["/packages/"],
}),
).toBeNull();
});
it("rejects sparse core worktrees that include only selected ui and package files", () => {
const cwd = createTempDir("openclaw-run-tsgo-");
const requiredPaths = [
@@ -0,0 +1,36 @@
import { describe, expect, it } from "vitest";
import { findTsgoCoreTestShardViolations } from "../../scripts/lib/tsgo-core-test-shards.mts";
describe("tsgo core test shards", () => {
it("accepts an exact once-only partition within the root budget", () => {
expect(
findTsgoCoreTestShardViolations({
canonicalRoots: ["src/a.test.ts", "src/b.test.ts"],
maxRoots: 1,
shards: [
{ name: "a", roots: ["src/a.test.ts"] },
{ name: "b", roots: ["src/b.test.ts"] },
],
}),
).toEqual([]);
});
it("reports missing, duplicate, extra, and oversized shard roots", () => {
expect(
findTsgoCoreTestShardViolations({
canonicalRoots: ["src/a.test.ts", "src/b.test.ts", "src/missing.test.ts"],
maxRoots: 1,
shards: [
{ name: "first", roots: ["src/a.test.ts", "src/b.test.ts"] },
{ name: "second", roots: ["src/b.test.ts", "src/extra.test.ts"] },
],
}),
).toEqual([
"first: 2 test roots exceeds the 1 limit",
"second: 2 test roots exceeds the 1 limit",
"assigned 2 times (first, second): src/b.test.ts",
"unassigned: src/missing.test.ts",
"not in the canonical core-test graph (second): src/extra.test.ts",
]);
});
});
@@ -0,0 +1,17 @@
{
"extends": "./tsconfig.core.test.shard.json",
"compilerOptions": {
"tsBuildInfoFile": "../../.artifacts/tsgo-cache/core-test-agents-other.tsbuildinfo"
},
"include": [
"../../src/agents/**/*.test.ts",
"../../src/agents/**/*.test.tsx"
],
"exclude": [
"../../node_modules",
"../../dist",
"../../**/dist/**",
"../../src/agents/*.test.ts",
"../../src/agents/*.test.tsx"
]
}
@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.core.test.shard.json",
"compilerOptions": {
"tsBuildInfoFile": "../../.artifacts/tsgo-cache/core-test-agents-root.tsbuildinfo"
},
"include": [
"../../src/agents/*.test.ts",
"../../src/agents/*.test.tsx"
]
}
@@ -1,10 +0,0 @@
{
"extends": "./tsconfig.test.json",
"include": [
"../../src/**/*.d.ts",
"../../src/agents/**/*.test.ts",
"../../src/agents/**/*.test.tsx",
"../../ui/**/*.d.ts",
"../../packages/**/*.d.ts"
]
}
@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.core.test.shard.json",
"compilerOptions": {
"tsBuildInfoFile": "../../.artifacts/tsgo-cache/core-test-commands.tsbuildinfo"
},
"include": [
"../../src/commands/**/*.test.ts",
"../../src/commands/**/*.test.tsx"
]
}
@@ -0,0 +1,12 @@
{
"extends": "./tsconfig.core.test.shard.json",
"compilerOptions": {
"tsBuildInfoFile": "../../.artifacts/tsgo-cache/core-test-config-cli.tsbuildinfo"
},
"include": [
"../../src/config/**/*.test.ts",
"../../src/config/**/*.test.tsx",
"../../src/cli/**/*.test.ts",
"../../src/cli/**/*.test.tsx"
]
}
@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.core.test.shard.json",
"compilerOptions": {
"tsBuildInfoFile": "../../.artifacts/tsgo-cache/core-test-gateway.tsbuildinfo"
},
"include": [
"../../src/gateway/**/*.test.ts",
"../../src/gateway/**/*.test.tsx"
]
}
@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.core.test.shard.json",
"compilerOptions": {
"tsBuildInfoFile": "../../.artifacts/tsgo-cache/core-test-infra.tsbuildinfo"
},
"include": [
"../../src/infra/**/*.test.ts",
"../../src/infra/**/*.test.tsx"
]
}
@@ -0,0 +1,12 @@
{
"extends": "./tsconfig.core.test.shard.json",
"compilerOptions": {
"tsBuildInfoFile": "../../.artifacts/tsgo-cache/core-test-messaging.tsbuildinfo"
},
"include": [
"../../src/auto-reply/**/*.test.ts",
"../../src/auto-reply/**/*.test.tsx",
"../../src/channels/**/*.test.ts",
"../../src/channels/**/*.test.tsx"
]
}
@@ -1,10 +0,0 @@
{
"extends": "./tsconfig.core.test.json",
"exclude": [
"../../node_modules",
"../../dist",
"../../**/dist/**",
"../../src/agents/**/*.test.ts",
"../../src/agents/**/*.test.tsx"
]
}
@@ -0,0 +1,37 @@
{
"extends": "./tsconfig.core.test.shard.json",
"compilerOptions": {
"tsBuildInfoFile": "../../.artifacts/tsgo-cache/core-test-other.tsbuildinfo"
},
"include": [
"../../src/**/*.test.ts",
"../../src/**/*.test.tsx"
],
"exclude": [
"../../node_modules",
"../../dist",
"../../**/dist/**",
"../../src/agents/**",
"../../src/gateway/**",
"../../src/infra/**",
"../../src/commands/**",
"../../src/plugins/**",
"../../src/config/**",
"../../src/cli/**",
"../../src/auto-reply/**",
"../../src/channels/**",
"../../src/cron/**",
"../../src/plugin-sdk/**",
"../../src/skills/**",
"../../src/secrets/**",
"../../src/shared/**",
"../../src/security/**",
"../../src/acp/**",
"../../src/tui/**",
"../../src/media/**",
"../../src/system-agent/**",
"../../src/logging/**",
"../../src/hooks/**",
"../../src/daemon/**"
]
}
@@ -0,0 +1,26 @@
{
"extends": "./tsconfig.core.test.shard.json",
"compilerOptions": {
"tsBuildInfoFile": "../../.artifacts/tsgo-cache/core-test-plugins-platform.tsbuildinfo"
},
"include": [
"../../src/plugins/**/*.test.ts",
"../../src/plugins/**/*.test.tsx",
"../../src/security/**/*.test.ts",
"../../src/security/**/*.test.tsx",
"../../src/acp/**/*.test.ts",
"../../src/acp/**/*.test.tsx",
"../../src/tui/**/*.test.ts",
"../../src/tui/**/*.test.tsx",
"../../src/media/**/*.test.ts",
"../../src/media/**/*.test.tsx",
"../../src/system-agent/**/*.test.ts",
"../../src/system-agent/**/*.test.tsx",
"../../src/logging/**/*.test.ts",
"../../src/logging/**/*.test.tsx",
"../../src/hooks/**/*.test.ts",
"../../src/hooks/**/*.test.tsx",
"../../src/daemon/**/*.test.ts",
"../../src/daemon/**/*.test.tsx"
]
}
@@ -0,0 +1,8 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.core.test.src.projects.json" },
{ "path": "./tsconfig.core.test.ui.projects.json" },
{ "path": "./tsconfig.test.packages.json" }
]
}
@@ -0,0 +1,18 @@
{
"extends": "./tsconfig.core.test.shard.json",
"compilerOptions": {
"tsBuildInfoFile": "../../.artifacts/tsgo-cache/core-test-services.tsbuildinfo"
},
"include": [
"../../src/cron/**/*.test.ts",
"../../src/cron/**/*.test.tsx",
"../../src/plugin-sdk/**/*.test.ts",
"../../src/plugin-sdk/**/*.test.tsx",
"../../src/skills/**/*.test.ts",
"../../src/skills/**/*.test.tsx",
"../../src/secrets/**/*.test.ts",
"../../src/secrets/**/*.test.tsx",
"../../src/shared/**/*.test.ts",
"../../src/shared/**/*.test.tsx"
]
}
@@ -0,0 +1,19 @@
{
"extends": "./tsconfig.core.test.json",
"files": [
"../../src/config/sessions/session-entry.test-compat.d.ts",
"../../src/infra/host-env-security-policy.d.ts",
"../../src/state/openclaw-agent-db.generated.d.ts",
"../../src/state/openclaw-state-db.generated.d.ts",
"../../src/types/agent-sessions.d.ts",
"../../src/types/microsoft-teams-sdk.d.ts",
"../../src/types/node-runtime-globals.d.ts",
"../../src/types/qrcode.d.ts",
"../../ui/src/css.d.ts",
"../../ui/src/i18n/virtual-locale.d.ts",
"../../ui/src/markdown-it-task-lists.d.ts",
"../../ui/src/types/highlight-js-subpaths.d.ts",
"../../ui/src/types/novnc.d.ts"
],
"include": []
}
@@ -0,0 +1,15 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.core.test.agents-root.json" },
{ "path": "./tsconfig.core.test.agents-other.json" },
{ "path": "./tsconfig.core.test.gateway.json" },
{ "path": "./tsconfig.core.test.infra.json" },
{ "path": "./tsconfig.core.test.commands.json" },
{ "path": "./tsconfig.core.test.plugins-platform.json" },
{ "path": "./tsconfig.core.test.config-cli.json" },
{ "path": "./tsconfig.core.test.messaging.json" },
{ "path": "./tsconfig.core.test.services.json" },
{ "path": "./tsconfig.core.test.other.json" }
]
}
@@ -0,0 +1,18 @@
{
"extends": "./tsconfig.core.test.shard.json",
"compilerOptions": {
"tsBuildInfoFile": "../../.artifacts/tsgo-cache/core-test-ui-other.tsbuildinfo"
},
"include": [
"../../ui/src/main.ts",
"../../ui/**/*.test.ts",
"../../ui/**/*.test.tsx"
],
"exclude": [
"../../node_modules",
"../../dist",
"../../**/dist/**",
"../../ui/src/pages/**",
"../../ui/src/e2e/**"
]
}
@@ -0,0 +1,13 @@
{
"extends": "./tsconfig.core.test.shard.json",
"compilerOptions": {
"tsBuildInfoFile": "../../.artifacts/tsgo-cache/core-test-ui-pages-e2e.tsbuildinfo"
},
"include": [
"../../ui/src/main.ts",
"../../ui/src/pages/**/*.test.ts",
"../../ui/src/pages/**/*.test.tsx",
"../../ui/src/e2e/**/*.test.ts",
"../../ui/src/e2e/**/*.test.tsx"
]
}
@@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.core.test.ui-pages-e2e.json" },
{ "path": "./tsconfig.core.test.ui-other.json" }
]
}
+4 -5
View File
@@ -1,10 +1,9 @@
{
"extends": "./tsconfig.test.json",
"extends": "./tsconfig.core.test.shard.json",
"compilerOptions": {
"tsBuildInfoFile": "../../.artifacts/tsgo-cache/test-packages.tsbuildinfo"
},
"include": [
"../../src/**/*.d.ts",
"../../ui/**/*.d.ts",
"../../extensions/**/*.d.ts",
"../../packages/**/*.d.ts",
"../../packages/**/*.test.ts",
"../../packages/**/*.test.tsx"
]
-16
View File
@@ -1,16 +0,0 @@
{
"extends": "./tsconfig.test.json",
"compilerOptions": {
// Retained plain-Node script exceptions are imported by their owner tests.
"allowJs": true
},
"include": [
"../../src/**/*.d.ts",
"../../src/**/*.test.ts",
"../../src/**/*.test.tsx",
"../../ui/**/*.d.ts",
"../../ui/src/main.ts",
"../../extensions/**/*.d.ts",
"../../packages/**/*.d.ts"
]
}
-11
View File
@@ -1,11 +0,0 @@
{
"extends": "./tsconfig.test.json",
"include": [
"../../src/**/*.d.ts",
"../../ui/**/*.d.ts",
"../../ui/**/*.test.ts",
"../../ui/**/*.test.tsx",
"../../extensions/**/*.d.ts",
"../../packages/**/*.d.ts"
]
}
+1 -1
View File
@@ -3,6 +3,6 @@
"references": [
{ "path": "./tsconfig.core.json" },
{ "path": "./tsconfig.ui.json" },
{ "path": "./test/tsconfig/tsconfig.core.test.json" }
{ "path": "./test/tsconfig/tsconfig.core.test.projects.json" }
]
}