test(perf): trim fixture churn in install and cron suites

This commit is contained in:
Peter Steinberger
2026-03-03 00:20:24 +00:00
parent 6bf84ac28c
commit 4b3d9f4fb2
2 changed files with 106 additions and 39 deletions

View File

@@ -36,6 +36,7 @@ vi.mock("../infra/net/fetch-guard.js", () => ({
installGatewayTestHooks({ scope: "suite" });
const CRON_WAIT_INTERVAL_MS = 5;
const CRON_WAIT_TIMEOUT_MS = 3_000;
const EMPTY_CRON_STORE_CONTENT = JSON.stringify({ version: 1, jobs: [] });
let cronSuiteTempRootPromise: Promise<string> | null = null;
let cronSuiteCaseId = 0;
@@ -79,10 +80,20 @@ async function waitForCondition(check: () => boolean | Promise<boolean>, timeout
);
}
async function createCronCasePaths(tempPrefix: string): Promise<{
dir: string;
storePath: string;
}> {
const suiteRoot = await getCronSuiteTempRoot();
const dir = path.join(suiteRoot, `${tempPrefix}${cronSuiteCaseId++}`);
const storePath = path.join(dir, "cron", "jobs.json");
await fs.mkdir(path.dirname(storePath), { recursive: true });
return { dir, storePath };
}
async function cleanupCronTestRun(params: {
ws: { close: () => void };
server: { close: () => Promise<void> };
dir: string;
prevSkipCron: string | undefined;
clearSessionConfig?: boolean;
}) {
@@ -108,16 +119,13 @@ async function setupCronTestRun(params: {
}): Promise<{ prevSkipCron: string | undefined; dir: string }> {
const prevSkipCron = process.env.OPENCLAW_SKIP_CRON;
process.env.OPENCLAW_SKIP_CRON = "0";
const suiteRoot = await getCronSuiteTempRoot();
const dir = path.join(suiteRoot, `${params.tempPrefix}${cronSuiteCaseId++}`);
await fs.mkdir(dir, { recursive: true });
testState.cronStorePath = path.join(dir, "cron", "jobs.json");
const { dir, storePath } = await createCronCasePaths(params.tempPrefix);
testState.cronStorePath = storePath;
testState.sessionConfig = params.sessionConfig;
testState.cronEnabled = params.cronEnabled;
await fs.mkdir(path.dirname(testState.cronStorePath), { recursive: true });
await fs.writeFile(
testState.cronStorePath,
JSON.stringify({ version: 1, jobs: params.jobs ?? [] }),
params.jobs ? JSON.stringify({ version: 1, jobs: params.jobs }) : EMPTY_CRON_STORE_CONTENT,
);
return { prevSkipCron, dir };
}
@@ -138,7 +146,7 @@ describe("gateway server cron", () => {
});
test("handles cron CRUD, normalization, and patch semantics", { timeout: 20_000 }, async () => {
const { prevSkipCron, dir } = await setupCronTestRun({
const { prevSkipCron } = await setupCronTestRun({
tempPrefix: "openclaw-gw-cron-",
sessionConfig: { mainKey: "primary" },
cronEnabled: false,
@@ -403,7 +411,6 @@ describe("gateway server cron", () => {
await cleanupCronTestRun({
ws,
server,
dir,
prevSkipCron,
clearSessionConfig: true,
});
@@ -514,7 +521,7 @@ describe("gateway server cron", () => {
const runs = autoEntries?.entries ?? [];
expect(runs.at(-1)?.jobId).toBe(autoJobId);
} finally {
await cleanupCronTestRun({ ws, server, dir, prevSkipCron });
await cleanupCronTestRun({ ws, server, prevSkipCron });
}
}, 45_000);
@@ -532,7 +539,7 @@ describe("gateway server cron", () => {
payload: { kind: "systemEvent", text: "legacy webhook" },
state: {},
};
const { prevSkipCron, dir } = await setupCronTestRun({
const { prevSkipCron } = await setupCronTestRun({
tempPrefix: "openclaw-gw-cron-webhook-",
cronEnabled: false,
jobs: [legacyNotifyJob],
@@ -741,7 +748,7 @@ describe("gateway server cron", () => {
await yieldToEventLoop();
expect(fetchWithSsrFGuardMock).toHaveBeenCalledTimes(1);
} finally {
await cleanupCronTestRun({ ws, server, dir, prevSkipCron });
await cleanupCronTestRun({ ws, server, prevSkipCron });
}
}, 60_000);
});

View File

@@ -29,6 +29,34 @@ const archiveFixturePathCache = new Map<string, string>();
const dynamicArchiveTemplatePathCache = new Map<string, string>();
let installPluginFromDirTemplateDir = "";
let manifestInstallTemplateDir = "";
const DYNAMIC_ARCHIVE_TEMPLATE_PRESETS = [
{
outName: "traversal.tgz",
withDistIndex: true,
packageJson: {
name: "@evil/..",
version: "0.0.1",
openclaw: { extensions: ["./dist/index.js"] },
} as Record<string, unknown>,
},
{
outName: "reserved.tgz",
withDistIndex: true,
packageJson: {
name: "@evil/.",
version: "0.0.1",
openclaw: { extensions: ["./dist/index.js"] },
} as Record<string, unknown>,
},
{
outName: "bad.tgz",
withDistIndex: false,
packageJson: {
name: "@openclaw/nope",
version: "0.0.1",
} as Record<string, unknown>,
},
];
function ensureSuiteTempRoot() {
if (suiteTempRoot) {
@@ -41,7 +69,7 @@ function ensureSuiteTempRoot() {
function makeTempDir() {
const dir = path.join(ensureSuiteTempRoot(), `case-${String(tempDirCounter)}`);
tempDirCounter += 1;
fs.mkdirSync(dir, { recursive: true });
fs.mkdirSync(dir);
return dir;
}
@@ -157,8 +185,10 @@ function setupPluginInstallDirs() {
}
function setupInstallPluginFromDirFixture(params?: { devDependencies?: Record<string, string> }) {
const stateDir = makeTempDir();
const pluginDir = path.join(makeTempDir(), "plugin");
const caseDir = makeTempDir();
const stateDir = path.join(caseDir, "state");
const pluginDir = path.join(caseDir, "plugin");
fs.mkdirSync(stateDir, { recursive: true });
fs.cpSync(installPluginFromDirTemplateDir, pluginDir, { recursive: true });
if (params?.devDependencies) {
const packageJsonPath = path.join(pluginDir, "package.json");
@@ -185,8 +215,10 @@ async function installFromDirWithWarnings(params: { pluginDir: string; extension
}
function setupManifestInstallFixture(params: { manifestId: string }) {
const stateDir = makeTempDir();
const pluginDir = path.join(makeTempDir(), "plugin-src");
const caseDir = makeTempDir();
const stateDir = path.join(caseDir, "state");
const pluginDir = path.join(caseDir, "plugin-src");
fs.mkdirSync(stateDir, { recursive: true });
fs.cpSync(manifestInstallTemplateDir, pluginDir, { recursive: true });
fs.writeFileSync(
path.join(pluginDir, "openclaw.plugin.json"),
@@ -226,31 +258,11 @@ async function installArchivePackageAndReturnResult(params: {
withDistIndex?: boolean;
}) {
const stateDir = makeTempDir();
const templateKey = JSON.stringify({
const archivePath = await ensureDynamicArchiveTemplate({
outName: params.outName,
packageJson: params.packageJson,
withDistIndex: params.withDistIndex === true,
});
let archivePath = dynamicArchiveTemplatePathCache.get(templateKey);
if (!archivePath) {
const templateDir = makeTempDir();
const pkgDir = path.join(templateDir, "package");
fs.mkdirSync(pkgDir, { recursive: true });
if (params.withDistIndex) {
fs.mkdirSync(path.join(pkgDir, "dist"), { recursive: true });
fs.writeFileSync(path.join(pkgDir, "dist", "index.js"), "export {};", "utf-8");
}
fs.writeFileSync(
path.join(pkgDir, "package.json"),
JSON.stringify(params.packageJson),
"utf-8",
);
archivePath = await packToArchive({
pkgDir,
outDir: ensureSuiteFixtureRoot(),
outName: params.outName,
});
dynamicArchiveTemplatePathCache.set(templateKey, archivePath);
}
const extensionsDir = path.join(stateDir, "extensions");
const result = await installPluginFromArchive({
@@ -260,6 +272,46 @@ async function installArchivePackageAndReturnResult(params: {
return result;
}
function buildDynamicArchiveTemplateKey(params: {
packageJson: Record<string, unknown>;
withDistIndex: boolean;
}): string {
return JSON.stringify({
packageJson: params.packageJson,
withDistIndex: params.withDistIndex,
});
}
async function ensureDynamicArchiveTemplate(params: {
packageJson: Record<string, unknown>;
outName: string;
withDistIndex: boolean;
}): Promise<string> {
const templateKey = buildDynamicArchiveTemplateKey({
packageJson: params.packageJson,
withDistIndex: params.withDistIndex,
});
const cachedPath = dynamicArchiveTemplatePathCache.get(templateKey);
if (cachedPath) {
return cachedPath;
}
const templateDir = makeTempDir();
const pkgDir = path.join(templateDir, "package");
fs.mkdirSync(pkgDir, { recursive: true });
if (params.withDistIndex) {
fs.mkdirSync(path.join(pkgDir, "dist"), { recursive: true });
fs.writeFileSync(path.join(pkgDir, "dist", "index.js"), "export {};", "utf-8");
}
fs.writeFileSync(path.join(pkgDir, "package.json"), JSON.stringify(params.packageJson), "utf-8");
const archivePath = await packToArchive({
pkgDir,
outDir: ensureSuiteFixtureRoot(),
outName: params.outName,
});
dynamicArchiveTemplatePathCache.set(templateKey, archivePath);
return archivePath;
}
afterAll(() => {
if (!suiteTempRoot) {
return;
@@ -327,6 +379,14 @@ beforeAll(async () => {
}),
"utf-8",
);
for (const preset of DYNAMIC_ARCHIVE_TEMPLATE_PRESETS) {
await ensureDynamicArchiveTemplate({
packageJson: preset.packageJson,
outName: preset.outName,
withDistIndex: preset.withDistIndex,
});
}
});
beforeEach(() => {