test: speed up slow assertions

This commit is contained in:
Peter Steinberger
2026-05-29 20:37:50 +02:00
parent 16cd7f9d3f
commit 7f4338d435
13 changed files with 332 additions and 145 deletions

View File

@@ -1,7 +1,7 @@
import { spawn } from "node:child_process";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { gzipSync } from "node:zlib";
import type { OpenClawPluginNodeInvokePolicyContext } from "openclaw/plugin-sdk/plugin-entry";
import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
import { createFileTransferNodeInvokePolicy } from "./node-invoke-policy.js";
@@ -32,34 +32,46 @@ afterAll(() => {
vi.resetModules();
});
async function tarEntries(entries: Record<string, string>): Promise<string> {
const tmpRoot = await fs.realpath(await fs.mkdtemp(path.join(os.tmpdir(), "node-policy-tar-")));
tmpRoots.push(tmpRoot);
function tarEntries(entries: Record<string, string>): string {
const blocks: Buffer[] = [];
for (const [relPath, contents] of Object.entries(entries)) {
const absPath = path.join(tmpRoot, relPath);
await fs.mkdir(path.dirname(absPath), { recursive: true });
await fs.writeFile(absPath, contents);
const payload = Buffer.from(contents);
blocks.push(createTarFileHeader(relPath, payload.byteLength), payload);
const padding = (512 - (payload.byteLength % 512)) % 512;
if (padding > 0) {
blocks.push(Buffer.alloc(padding));
}
}
return await new Promise<string>((resolve, reject) => {
const tarBin = process.platform !== "win32" ? "/usr/bin/tar" : "tar";
const child = spawn(tarBin, ["-czf", "-", "-C", tmpRoot, "."], {
stdio: ["ignore", "pipe", "pipe"],
});
const chunks: Buffer[] = [];
let stderr = "";
child.stdout.on("data", (chunk: Buffer) => chunks.push(chunk));
child.stderr.on("data", (chunk: Buffer) => {
stderr += chunk.toString();
});
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(`tar exited ${code}: ${stderr}`));
return;
}
resolve(Buffer.concat(chunks).toString("base64"));
});
child.on("error", reject);
});
blocks.push(Buffer.alloc(1024));
return gzipSync(Buffer.concat(blocks)).toString("base64");
}
function writeTarString(header: Buffer, offset: number, length: number, value: string): void {
header.write(value.slice(0, length), offset, length, "utf8");
}
function writeTarOctal(header: Buffer, offset: number, length: number, value: number): void {
const text = value.toString(8).padStart(length - 1, "0");
header.write(`${text}\0`.slice(-length), offset, length, "ascii");
}
function createTarFileHeader(name: string, size: number): Buffer {
const header = Buffer.alloc(512);
writeTarString(header, 0, 100, name);
writeTarOctal(header, 100, 8, 0o644);
writeTarOctal(header, 108, 8, 0);
writeTarOctal(header, 116, 8, 0);
writeTarOctal(header, 124, 12, size);
writeTarOctal(header, 136, 12, 0);
header.fill(" ", 148, 156);
header.write("0", 156, 1, "ascii");
header.write("ustar\0", 257, 6, "ascii");
header.write("00", 263, 2, "ascii");
const checksum = header.reduce((sum, byte) => sum + byte, 0);
header.write(checksum.toString(8).padStart(6, "0"), 148, 6, "ascii");
header[154] = 0;
header[155] = 0x20;
return header;
}
function createCtx(overrides: {

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node
import { existsSync, readdirSync, readFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { existsSync, lstatSync, readdirSync, readFileSync } from "node:fs";
import { join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
@@ -148,10 +149,68 @@ function collectTextFiles(dir: string): string[] {
return files;
}
function isExistingTextFile(file: string): boolean {
try {
return lstatSync(file).isFile();
} catch {
return false;
}
}
function collectWorkspaceTextFiles(): string[] {
return SOURCE_ROOTS.flatMap((root) => collectTextFiles(resolve(REPO_ROOT, root))).toSorted(
(left, right) => relative(REPO_ROOT, left).localeCompare(relative(REPO_ROOT, right)),
const gitFiles = collectWorkspaceTextFilesFromGit();
return (
gitFiles ?? SOURCE_ROOTS.flatMap((root) => collectTextFiles(resolve(REPO_ROOT, root)))
).toSorted((left, right) => relative(REPO_ROOT, left).localeCompare(relative(REPO_ROOT, right)));
}
function collectWorkspaceTextFilesFromGit(): string[] | null {
const result = spawnSync(
"git",
["ls-files", "--cached", "--others", "--exclude-standard", "--", ...SOURCE_ROOTS],
{
cwd: REPO_ROOT,
encoding: "utf8",
maxBuffer: 32 * 1024 * 1024,
stdio: ["ignore", "pipe", "ignore"],
},
);
if (result.status !== 0) {
return null;
}
return result.stdout
.split("\n")
.map((line) => line.trim())
.filter((line) => line.length > 0 && TEXT_FILE_PATTERN.test(line))
.filter((line) => !line.split("/").some((part) => SKIPPED_DIRS.has(part)))
.map((line) => resolve(REPO_ROOT, line))
.filter(isExistingTextFile);
}
function collectWorkspaceTextFilesMatchingGit(pattern: string): string[] | null {
const result = spawnSync(
"git",
["grep", "--untracked", "-l", "-E", pattern, "--", ...SOURCE_ROOTS],
{
cwd: REPO_ROOT,
encoding: "utf8",
maxBuffer: 32 * 1024 * 1024,
stdio: ["ignore", "pipe", "ignore"],
},
);
if (result.status === 1) {
return [];
}
if (result.status !== 0) {
return null;
}
return result.stdout
.split("\n")
.map((line) => line.trim())
.filter((line) => line.length > 0 && TEXT_FILE_PATTERN.test(line))
.filter((line) => !line.split("/").some((part) => SKIPPED_DIRS.has(part)))
.map((line) => resolve(REPO_ROOT, line))
.filter(isExistingTextFile);
}
function repoRelative(file: string): string {
@@ -166,6 +225,26 @@ function collectWorkspaceTextFileSources(): WorkspaceTextFile[] {
}));
}
function collectSummaryWorkspaceTextFileSources(): WorkspaceTextFile[] {
const pluginSdkFiles = collectWorkspaceTextFilesMatchingGit(
String.raw`openclaw/plugin-sdk/[a-z0-9][a-z0-9-]*`,
);
if (!pluginSdkFiles) {
return collectWorkspaceTextFileSources();
}
const files = new Set(pluginSdkFiles);
for (const file of collectTextFiles(resolve(REPO_ROOT, "packages/memory-host-sdk/src"))) {
files.add(file);
}
return [...files]
.toSorted((left, right) => repoRelative(left).localeCompare(repoRelative(right)))
.map((file) => ({
file,
relativeFile: repoRelative(file),
source: readFileSync(file, "utf8"),
}));
}
function isDocsFile(file: string): boolean {
return file.startsWith("docs/") || file === "README.md";
}
@@ -444,7 +523,9 @@ function buildSummary(report: BoundaryReport, owner?: string): BoundaryReportSum
}
function buildReport(options: Pick<CliOptions, "owner" | "summary"> = {}): BoundaryReport {
const files = collectWorkspaceTextFileSources();
const files = options.summary
? collectSummaryWorkspaceTextFileSources()
: collectWorkspaceTextFileSources();
const pluginIds = collectBundledPluginIds();
const compatRecords = collectCompatDebt(files, new Date(), {
includeReferenceFiles: !options.summary,

View File

@@ -11,7 +11,7 @@ afterEach(async () => {
});
describe("loadExtensions", () => {
it("resolves the generic LLM plugin SDK subpath in jiti-loaded extensions", async () => {
it("resolves plugin SDK subpaths in jiti-loaded extensions", async () => {
const dir = await mkdtemp(join(tmpdir(), "openclaw-extension-sdk-"));
tempDirs.push(dir);
const extensionPath = join(dir, "extension.ts");
@@ -19,12 +19,16 @@ describe("loadExtensions", () => {
extensionPath,
`
import { createAssistantMessageEventStream } from "openclaw/plugin-sdk/llm";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
export default async function(api) {
const stream = createAssistantMessageEventStream();
if (!stream || typeof stream.result !== "function") {
throw new Error("generic LLM helper unavailable");
}
if (normalizeLowercaseStringOrEmpty(" MIXED ") !== "mixed") {
throw new Error("generic sdk subpath unavailable");
}
api.registerCommand("sdk-subpath-probe", {
description: "probe",
handler() {},
@@ -39,38 +43,4 @@ export default async function(api) {
expect(result.extensions).toHaveLength(1);
expect(result.extensions[0]?.commands.has("sdk-subpath-probe")).toBe(true);
});
it("resolves generic plugin SDK subpaths through the shared plugin loader aliases", async () => {
const dir = await mkdtemp(join(tmpdir(), "openclaw-extension-sdk-"));
tempDirs.push(dir);
const extensionPath = join(dir, "extension.ts");
await writeFile(
extensionPath,
`
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
import { defineTool } from "@openclaw/plugin-sdk/agent-sessions";
export default async function(api) {
if (normalizeLowercaseStringOrEmpty(" MIXED ") !== "mixed") {
throw new Error("generic sdk subpath unavailable");
}
const tool = defineTool({
name: "shared-sdk-probe",
description: "probe",
parameters: { type: "object", properties: {}, additionalProperties: false },
handler() {
return { content: [{ type: "text", text: "ok" }] };
},
});
api.registerTool(tool);
}
`,
);
const result = await loadExtensions([extensionPath], dir);
expect(result.errors).toEqual([]);
expect(result.extensions).toHaveLength(1);
expect(result.extensions[0]?.tools.has("shared-sdk-probe")).toBe(true);
});
});

View File

@@ -588,11 +588,9 @@ function expectCoreSourceStaysOffPluginSpecificSdkFacades(file: string, imports:
describe("channel import guardrails", () => {
it("lists channel import guardrail sources from git without walking roots", () => {
expectNoReaddirSyncDuring(() => {
const extensionSources = collectExtensionSourceFiles();
const coreSources = collectCoreSourceFiles();
const telegramSources = collectExtensionFiles("telegram");
expect(extensionSources.length).toBeGreaterThan(0);
expect(coreSources.length).toBeGreaterThan(0);
expect(telegramSources.length).toBeGreaterThan(0);
});

View File

@@ -34,11 +34,19 @@ export function installSurfaceContractRegistryShard(params: ContractShardParams)
installEmptyShardSuite("surface contract registry shard");
return;
}
const pluginCache = new Map<string, Awaited<ReturnType<typeof getBundledChannelPluginAsync>>>();
beforeAll(async () => {
await Promise.all(
ids.map(async (id) => {
pluginCache.set(id, await getBundledChannelPluginAsync(id));
}),
);
});
for (const id of ids) {
describe(`${id} surface contracts`, () => {
it("exposes declared surface contracts", async () => {
const plugin = await getBundledChannelPluginAsync(id);
it("exposes declared surface contracts", () => {
const plugin = pluginCache.get(id);
if (!plugin) {
throw new Error(`Missing bundled channel plugin for ${id}`);
}
@@ -90,18 +98,26 @@ export function installThreadingContractRegistryShard(params: ContractShardParam
installEmptyShardSuite("threading contract registry shard");
return;
}
const pluginCache = new Map<string, Awaited<ReturnType<typeof getBundledChannelPluginAsync>>>();
beforeAll(async () => {
await Promise.all(
entries.map(async (entry) => {
pluginCache.set(entry.id, await getBundledChannelPluginAsync(entry.id));
}),
);
});
for (const entry of entries) {
describe(`${entry.id} threading contract`, () => {
it("exposes the base threading contract", async () => {
const plugin = await getBundledChannelPluginAsync(entry.id);
it("exposes the base threading contract", () => {
const plugin = pluginCache.get(entry.id);
if (!plugin) {
throw new Error(`Missing bundled channel plugin for ${entry.id}`);
}
expectChannelThreadingBaseContract(plugin);
});
it("keeps threading return values normalized", async () => {
const plugin = await getBundledChannelPluginAsync(entry.id);
it("keeps threading return values normalized", () => {
const plugin = pluginCache.get(entry.id);
if (!plugin) {
throw new Error(`Missing bundled channel plugin for ${entry.id}`);
}
@@ -117,10 +133,18 @@ export function installPluginContractRegistryShard(params: ContractShardParams)
installEmptyShardSuite("plugin contract registry shard");
return;
}
const pluginCache = new Map<string, Awaited<ReturnType<typeof getBundledChannelPluginAsync>>>();
beforeAll(async () => {
await Promise.all(
entries.map(async (entry) => {
pluginCache.set(entry.id, await getBundledChannelPluginAsync(entry.id));
}),
);
});
for (const entry of entries) {
describe(`${entry.id} plugin contract`, () => {
it("satisfies the base channel plugin contract", async () => {
const plugin = await getBundledChannelPluginAsync(entry.id);
it("satisfies the base channel plugin contract", () => {
const plugin = pluginCache.get(entry.id);
if (!plugin) {
throw new Error(`Missing bundled channel plugin for ${entry.id}`);
}

View File

@@ -2,7 +2,7 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
import { beforeAll, describe, expect, it } from "vitest";
import { defineToolPlugin, getToolPluginMetadata } from "../plugin-sdk/tool-plugin.js";
import {
buildToolPluginManifest,
@@ -55,7 +55,65 @@ function createOptionalDemoMetadata() {
return metadata;
}
function writeSourceToolPluginProject(params: {
tmpDir: string;
packageName: string;
pluginId: string;
toolName: string;
}): string {
const sourceDir = path.join(params.tmpDir, "src");
fs.mkdirSync(sourceDir, { recursive: true });
fs.writeFileSync(
path.join(params.tmpDir, "package.json"),
JSON.stringify(
{
name: params.packageName,
type: "module",
openclaw: { extensions: ["./src/index.ts"] },
},
null,
2,
),
);
const entryPath = path.join(sourceDir, "index.ts");
fs.writeFileSync(
entryPath,
`import { defineToolPlugin } from "openclaw/plugin-sdk/tool-plugin";
export default defineToolPlugin({
id: ${JSON.stringify(params.pluginId)},
name: "Source Demo",
description: "Source demo plugin.",
tools: (tool) => [
tool({
name: ${JSON.stringify(params.toolName)},
description: "Echo input.",
parameters: { type: "object", additionalProperties: false, properties: {} },
execute: async () => ({ ok: true }),
}),
],
});
`,
);
return entryPath;
}
describe("plugin authoring commands", () => {
beforeAll(async () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-source-warm-"));
try {
const entryPath = writeSourceToolPluginProject({
tmpDir,
packageName: "openclaw-plugin-source-warm",
pluginId: "source-warm",
toolName: "source_warm_echo",
});
await loadToolPlugin({ rootDir: tmpDir, entryPath });
} finally {
fs.rmSync(tmpDir, { force: true, recursive: true });
}
});
it("generates manifest metadata from defineToolPlugin metadata", () => {
const metadata = createDemoMetadata();
@@ -238,43 +296,16 @@ describe("plugin authoring commands", () => {
it("loads source entries that import the OpenClaw plugin SDK package subpath", async () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-source-"));
const sourceDir = path.join(tmpDir, "src");
fs.mkdirSync(sourceDir, { recursive: true });
fs.writeFileSync(
path.join(tmpDir, "package.json"),
JSON.stringify(
{
name: "openclaw-plugin-source-demo",
type: "module",
openclaw: { extensions: ["./src/index.ts"] },
},
null,
2,
),
);
fs.writeFileSync(
path.join(sourceDir, "index.ts"),
`import { defineToolPlugin } from "openclaw/plugin-sdk/tool-plugin";
export default defineToolPlugin({
id: "source-demo",
name: "Source Demo",
description: "Source demo plugin.",
tools: (tool) => [
tool({
name: "source_echo",
description: "Echo input.",
parameters: { type: "object", additionalProperties: false, properties: {} },
execute: async () => ({ ok: true }),
}),
],
});
`,
);
const entryPath = writeSourceToolPluginProject({
tmpDir,
packageName: "openclaw-plugin-source-demo",
pluginId: "source-demo",
toolName: "source_echo",
});
const loaded = await loadToolPlugin({
rootDir: tmpDir,
entryPath: path.join(sourceDir, "index.ts"),
entryPath,
});
expect(loaded.metadata.id).toBe("source-demo");

View File

@@ -392,7 +392,7 @@ describe("appendAssistantMessageToSessionTranscript", () => {
const sessionFile = resolveSessionTranscriptPathInDir(sessionId, fixture.sessionsDir());
await appendSessionTranscriptMessage({
transcriptPath: sessionFile,
message: { role: "user", content: "x".repeat(5 * 1024 * 1024) },
message: { role: "user", content: "x".repeat(128 * 1024) },
});
const latestAssistantText = await readLatestAssistantTextFromSessionTranscript(sessionFile);

View File

@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import type { SessionEntry } from "../config/sessions.js";
@@ -17,14 +17,19 @@ vi.mock("../plugins/current-plugin-metadata-snapshot.js", () => ({
getCurrentPluginMetadataSnapshot: () => emptyPluginMetadataSnapshot,
}));
let sessionUtils: typeof import("./session-utils.js");
describe("gateway session list plugin runtime normalization", () => {
beforeEach(() => {
beforeAll(async () => {
vi.resetModules();
sessionUtils = await import("./session-utils.js");
});
beforeEach(() => {
normalizeProviderModelIdWithPluginMock.mockReset();
});
it("skips provider runtime normalization for lightweight list rows", async () => {
const { listSessionsFromStoreAsync } = await import("./session-utils.js");
const cfg = {
agents: {
defaults: { model: { primary: "custom-provider/custom-legacy-model" } },
@@ -37,7 +42,7 @@ describe("gateway session list plugin runtime normalization", () => {
]),
);
const listed = await listSessionsFromStoreAsync({
const listed = await sessionUtils.listSessionsFromStoreAsync({
cfg,
storePath: "",
store,
@@ -62,14 +67,13 @@ describe("gateway session list plugin runtime normalization", () => {
},
);
const { buildGatewaySessionRow } = await import("./session-utils.js");
const cfg = {
agents: {
defaults: { model: { primary: "custom-provider/custom-legacy-model" } },
},
} as OpenClawConfig;
const row = buildGatewaySessionRow({
const row = sessionUtils.buildGatewaySessionRow({
cfg,
storePath: "",
store: {},

View File

@@ -1,7 +1,7 @@
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { beforeAll, describe, expect, it } from "vitest";
import { expectNoReaddirSyncDuring } from "../test-utils/fs-scan-assertions.js";
import { listGitTrackedFiles, toRepoRelativePath } from "../test-utils/repo-files.js";
import { collectBundledChannelConfigs } from "./bundled-channel-config-metadata.js";
@@ -126,6 +126,10 @@ let repoBundledPluginMetadataCache: readonly BundledPluginMetadata[] | undefined
let repoBundledPluginManifestsCache:
| ReturnType<typeof listRepoBundledPluginManifestsUncached>
| undefined;
const repoBundledChannelConfigsCache = new Map<
string,
ReturnType<typeof collectBundledChannelConfigs>
>();
function listRepoBundledPluginMetadata(): readonly BundledPluginMetadata[] {
repoBundledPluginMetadataCache ??= listBundledPluginMetadata({
@@ -264,16 +268,22 @@ function collectRootPackageExcludedExtensionDirsForTest(): readonly string[] {
}
function collectRepoBundledChannelConfigsForTest(dirName: string) {
const cached = repoBundledChannelConfigsCache.get(dirName);
if (cached) {
return cached;
}
const pluginDir = path.join(repoRoot, "extensions", dirName);
const manifest = loadPluginManifest(pluginDir, false);
if (!manifest.ok) {
throw manifest.error;
}
return collectBundledChannelConfigs({
const configs = collectBundledChannelConfigs({
pluginDir,
manifest: manifest.manifest,
packageManifest: getPackageManifestMetadata(readPackageManifest(pluginDir)),
});
repoBundledChannelConfigsCache.set(dirName, configs);
return configs;
}
function hasPluginKind(record: PluginManifestRecord, kind: string): boolean {
@@ -325,6 +335,12 @@ function createInstalledPluginIndexForManifests(
}
describe("bundled plugin metadata", () => {
beforeAll(() => {
listRepoBundledPluginMetadata();
collectRepoBundledChannelConfigsForTest("discord");
collectRepoBundledChannelConfigsForTest("tlon");
});
it("lists bundled plugin manifests without scanning extension directories in-process", () => {
expectNoReaddirSyncDuring(() => {
const manifests = listRepoBundledPluginManifestsUncached();

View File

@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { isEmbeddedMode, setEmbeddedMode } from "../infra/embedded-mode.js";
import { defaultRuntime } from "../runtime.js";
@@ -152,6 +152,10 @@ describe("EmbeddedTuiBackend", () => {
const originalRuntimeLog = defaultRuntime.log;
const originalRuntimeError = defaultRuntime.error;
beforeAll(async () => {
await import("./embedded-backend.js");
});
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(embeddedEventTimestamp);

View File

@@ -31,22 +31,12 @@ afterEach(() => {
describe("scripts/embedded-run-abort-leak", () => {
it("rejects loose numeric thresholds before writing heap snapshots", () => {
const cases = [
["--iters", "1e3", "positive"],
["--batches", "2abc", "positive"],
["--max-rss-growth-mb", "0x10", "non-negative"],
["--max-tracked-retention", "abc", "non-negative"],
["--scope-bytes", "1mb", "positive"],
] as const;
const snapDir = makeTempRoot();
const result = runHarness(["--snap-dir", snapDir, "--iters", "1e3", "--quiet"]);
for (const [flag, value, label] of cases) {
const snapDir = makeTempRoot();
const result = runHarness(["--snap-dir", snapDir, flag, value, "--quiet"]);
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain(`error: ${flag} must be a ${label} integer`);
expect(readdirSync(snapDir)).toEqual([]);
}
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("error: --iters must be a positive integer");
expect(readdirSync(snapDir)).toEqual([]);
});
});

View File

@@ -1,3 +1,4 @@
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";
@@ -16,6 +17,7 @@ type SuppressionEntry = {
};
let productionLintSuppressionsCache: SuppressionEntry[] | null = null;
let productionCodeFilesCache: string[] | null = null;
function isProductionCodeFile(relativePath: string): boolean {
const basename = path.posix.basename(relativePath);
@@ -77,8 +79,13 @@ function collectProductionLintSuppressions(): SuppressionEntry[] {
if (productionLintSuppressionsCache) {
return [...productionLintSuppressionsCache];
}
const gitEntries = collectProductionLintSuppressionsFromGit();
if (gitEntries) {
productionLintSuppressionsCache = gitEntries;
return [...gitEntries];
}
const entries: SuppressionEntry[] = [];
const files = ROOTS.flatMap((root) => walkCodeFiles(path.join(repoRoot, root))).toSorted();
const files = listProductionCodeFiles();
for (const relativePath of files) {
const source = fs.readFileSync(path.join(repoRoot, relativePath), "utf8");
for (const line of source.split("\n")) {
@@ -96,6 +103,56 @@ function collectProductionLintSuppressions(): SuppressionEntry[] {
return [...entries];
}
function collectProductionLintSuppressionsFromGit(): SuppressionEntry[] | null {
const result = spawnSync(
"git",
[
"grep",
"-n",
"-E",
String.raw`(oxlint|eslint)-disable(-next-line)?[[:space:]]+[@/[:alnum:]_-]+`,
"--",
...ROOTS,
],
{
cwd: repoRoot,
encoding: "utf8",
maxBuffer: 8 * 1024 * 1024,
stdio: ["ignore", "pipe", "ignore"],
},
);
if (result.status === 1) {
return [];
}
if (result.status !== 0) {
return null;
}
const entries: SuppressionEntry[] = [];
for (const line of result.stdout.split("\n")) {
const match = /^([^:]+):\d+:(.*)$/u.exec(line);
if (!match) {
continue;
}
const [, file, sourceLine] = match;
if (!isProductionCodeFile(file)) {
continue;
}
const suppression = sourceLine.match(SUPPRESSION_PATTERN);
if (!suppression) {
continue;
}
entries.push({ file, rule: suppression[1] });
}
return entries;
}
function listProductionCodeFiles(): string[] {
productionCodeFilesCache ??= ROOTS.flatMap((root) =>
walkCodeFiles(path.join(repoRoot, root)),
).toSorted();
return [...productionCodeFilesCache];
}
function summarizeSuppressions(entries: readonly SuppressionEntry[]): string[] {
const counts = new Map<string, number>();
for (const entry of entries) {
@@ -108,7 +165,7 @@ function summarizeSuppressions(entries: readonly SuppressionEntry[]): string[] {
describe("production lint suppressions", () => {
it("lists production files from git without walking source roots", () => {
expectNoReaddirSyncDuring(() => {
const files = ROOTS.flatMap((root) => walkCodeFiles(path.join(repoRoot, root))).toSorted();
const files = listProductionCodeFiles();
expect(files.length).toBeGreaterThan(0);
expect(files.some((file) => file.endsWith(".test.ts"))).toBe(false);

View File

@@ -138,10 +138,10 @@ describe("package-openclaw-for-docker", () => {
const runPromise = runCommandForTest(process.execPath, ["-e", parentScript], process.cwd(), {
env: { ...process.env, OPENCLAW_TEST_CHILD_PID: childPidPath },
killAfterMs: 50,
timeoutMs: 1500,
killAfterMs: 25,
timeoutMs: 1000,
});
const timeoutAssertion = expect(runPromise).rejects.toThrow(/timed out after 1500ms/u);
const timeoutAssertion = expect(runPromise).rejects.toThrow(/timed out after 1000ms/u);
await waitForFile(childPidPath, 2000);
childPid = Number(fs.readFileSync(childPidPath, "utf8"));
await timeoutAssertion;
@@ -177,10 +177,10 @@ describe("package-openclaw-for-docker", () => {
await expect(
runCommandForTest(process.execPath, ["-e", parentScript], process.cwd(), {
env: { ...process.env, OPENCLAW_TEST_CHILD_PID: childPidPath },
killAfterMs: 50,
timeoutMs: 2000,
killAfterMs: 25,
timeoutMs: 1000,
}),
).rejects.toThrow(/timed out after 2000ms/u);
).rejects.toThrow(/timed out after 1000ms/u);
await waitForFile(childPidPath, 2000);
childPid = Number(fs.readFileSync(childPidPath, "utf8"));