mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-27 20:08:48 +00:00
fix(test): fail empty plugin gauntlet runs
This commit is contained in:
@@ -58,6 +58,7 @@ function parseArgs(argv) {
|
||||
commandTimeoutMs: 120_000,
|
||||
buildTimeoutMs: 600_000,
|
||||
qaTimeoutMs: 900_000,
|
||||
allowEmpty: false,
|
||||
keepRunRoot: process.env.OPENCLAW_PLUGIN_GATEWAY_GAUNTLET_KEEP_RUN_ROOT === "1",
|
||||
};
|
||||
const envIds = normalizeCsv(process.env.OPENCLAW_PLUGIN_GATEWAY_GAUNTLET_IDS);
|
||||
@@ -156,6 +157,9 @@ function parseArgs(argv) {
|
||||
case "--keep-run-root":
|
||||
options.keepRunRoot = true;
|
||||
break;
|
||||
case "--allow-empty":
|
||||
options.allowEmpty = true;
|
||||
break;
|
||||
case "--help":
|
||||
printHelp();
|
||||
process.exit(0);
|
||||
@@ -191,6 +195,7 @@ Options:
|
||||
--skip-lifecycle Skip plugin install/inspect/disable/enable/doctor/uninstall
|
||||
--skip-qa Skip QA Lab RPC conversation runs
|
||||
--skip-slash-help Skip CLI help probes for plugin-declared command aliases
|
||||
--allow-empty Allow zero-command runs when every active phase is skipped
|
||||
--keep-run-root Preserve isolated HOME/state/log temp root after success
|
||||
`);
|
||||
}
|
||||
@@ -611,6 +616,10 @@ export function runMeasuredCommandLive(params) {
|
||||
});
|
||||
}
|
||||
|
||||
export function hasGauntletWorkRows(rows) {
|
||||
return rows.some((row) => row.phase !== "prebuild");
|
||||
}
|
||||
|
||||
function runPluginLifecycle(params) {
|
||||
for (const plugin of params.plugins) {
|
||||
const commands = [
|
||||
@@ -814,7 +823,18 @@ async function main() {
|
||||
const failures = rows.filter(
|
||||
(row) => row.status !== 0 || row.timedOut || row.diagnosticFailure,
|
||||
);
|
||||
preserveRunRoot = preserveRunRoot || failures.length > 0;
|
||||
const guardFailures =
|
||||
!hasGauntletWorkRows(rows) && !options.allowEmpty
|
||||
? [
|
||||
{
|
||||
kind: "empty-run",
|
||||
message:
|
||||
"No lifecycle, slash-help, or QA gauntlet commands ran; remove a skip flag or pass --allow-empty for intentional dry runs.",
|
||||
},
|
||||
]
|
||||
: [];
|
||||
const hasFailures = failures.length > 0 || guardFailures.length > 0;
|
||||
preserveRunRoot = preserveRunRoot || hasFailures;
|
||||
let cleanupError = null;
|
||||
if (!preserveRunRoot) {
|
||||
try {
|
||||
@@ -841,6 +861,7 @@ async function main() {
|
||||
qaScenarios: options.qaScenarios,
|
||||
qaPluginChunkSize: options.qaPluginChunkSize,
|
||||
qaBaseline: options.qaBaseline,
|
||||
allowEmpty: options.allowEmpty,
|
||||
keepRunRoot: options.keepRunRoot,
|
||||
skipLifecycle: options.skipLifecycle,
|
||||
skipQa: options.skipQa,
|
||||
@@ -861,6 +882,7 @@ async function main() {
|
||||
rows,
|
||||
observations: [...metricObservations, ...qaBaselineObservations, ...gatewayObservations],
|
||||
failures,
|
||||
guardFailures,
|
||||
};
|
||||
const summaryPath = path.join(options.outputDir, "plugin-gateway-gauntlet-summary.json");
|
||||
fs.writeFileSync(summaryPath, `${JSON.stringify(summary, null, 2)}\n`, "utf8");
|
||||
@@ -876,10 +898,13 @@ async function main() {
|
||||
`[plugin-gauntlet] failure phase=${failure.phase} plugin=${failure.pluginId ?? "<none>"} status=${failure.status} timedOut=${failure.timedOut} diagnostic=${failure.diagnosticFailure ?? ""} wallMs=${Math.round(failure.wallMs)} log=${failure.logPath}\n`,
|
||||
);
|
||||
}
|
||||
for (const failure of guardFailures) {
|
||||
process.stdout.write(`[plugin-gauntlet] failure ${failure.kind}: ${failure.message}\n`);
|
||||
}
|
||||
for (const observation of summary.observations.slice(0, 20)) {
|
||||
process.stdout.write(`[plugin-gauntlet] observation ${JSON.stringify(observation)}\n`);
|
||||
}
|
||||
if (failures.length > 0) {
|
||||
if (hasFailures) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
createGauntletPrebuildCommand,
|
||||
hasGauntletWorkRows,
|
||||
parseTimedMetrics,
|
||||
runMeasuredCommand,
|
||||
runMeasuredCommandLive,
|
||||
@@ -328,6 +329,16 @@ describe("plugin gateway gauntlet helpers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("does not count prebuild setup as gauntlet work", () => {
|
||||
expect(hasGauntletWorkRows([])).toBe(false);
|
||||
expect(hasGauntletWorkRows([{ phase: "prebuild" }])).toBe(false);
|
||||
expect(hasGauntletWorkRows([{ phase: "prebuild" }, { phase: "lifecycle:install" }])).toBe(
|
||||
true,
|
||||
);
|
||||
expect(hasGauntletWorkRows([{ phase: "slash:help" }])).toBe(true);
|
||||
expect(hasGauntletWorkRows([{ phase: "qa:rpc" }])).toBe(true);
|
||||
});
|
||||
|
||||
it("parses macOS time -l metrics from strict trailing lines", () => {
|
||||
const metrics = parseTimedMetrics(
|
||||
[
|
||||
@@ -457,7 +468,7 @@ describe("plugin gateway gauntlet helpers", () => {
|
||||
await expect(fs.readFile(markerPath, "utf8")).resolves.toBe(afterReturn);
|
||||
});
|
||||
|
||||
it("cleans the isolated run root after a successful dry run", async () => {
|
||||
it("fails dry runs that do not execute any gauntlet commands", async () => {
|
||||
const outputDir = path.join(repoRoot, "artifacts");
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
@@ -478,10 +489,48 @@ describe("plugin gateway gauntlet helpers", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stdout).toContain("No lifecycle, slash-help, or QA gauntlet commands ran");
|
||||
const summary = JSON.parse(
|
||||
await fs.readFile(path.join(outputDir, "plugin-gateway-gauntlet-summary.json"), "utf8"),
|
||||
);
|
||||
expect(summary.guardFailures).toEqual([
|
||||
expect.objectContaining({
|
||||
kind: "empty-run",
|
||||
}),
|
||||
]);
|
||||
expect(summary.isolatedRunRootPreserved).toBe(true);
|
||||
await expect(fs.stat(summary.isolatedRunRoot)).resolves.toBeTruthy();
|
||||
await fs.rm(summary.isolatedRunRoot, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("cleans the isolated run root after an explicitly empty dry run", async () => {
|
||||
const outputDir = path.join(repoRoot, "artifacts");
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
path.resolve("scripts/check-plugin-gateway-gauntlet.mjs"),
|
||||
"--repo-root",
|
||||
repoRoot,
|
||||
"--output-dir",
|
||||
outputDir,
|
||||
"--skip-prebuild",
|
||||
"--skip-lifecycle",
|
||||
"--skip-slash-help",
|
||||
"--skip-qa",
|
||||
"--allow-empty",
|
||||
],
|
||||
{
|
||||
cwd: path.resolve("."),
|
||||
encoding: "utf8",
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.status, result.stderr).toBe(0);
|
||||
const summary = JSON.parse(
|
||||
await fs.readFile(path.join(outputDir, "plugin-gateway-gauntlet-summary.json"), "utf8"),
|
||||
);
|
||||
expect(summary.guardFailures).toEqual([]);
|
||||
expect(summary.isolatedRunRootPreserved).toBe(false);
|
||||
await expect(fs.stat(summary.isolatedRunRoot)).rejects.toHaveProperty("code", "ENOENT");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user