From bcfd7164dee0039805238854612eae1e0ea089d9 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 7 Jun 2026 04:40:38 +0200 Subject: [PATCH] fix(e2e): bound plugin update log assertions --- scripts/e2e/lib/plugins/assertions.mjs | 43 +++++++++++++++++++++++-- test/scripts/plugins-assertions.test.ts | 34 +++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/scripts/e2e/lib/plugins/assertions.mjs b/scripts/e2e/lib/plugins/assertions.mjs index 38a72ef9250e..c99d4c10a9f4 100644 --- a/scripts/e2e/lib/plugins/assertions.mjs +++ b/scripts/e2e/lib/plugins/assertions.mjs @@ -8,11 +8,14 @@ import { readPluginInstallRecords, writePluginInstallIndexForE2E, } from "../plugin-index-sqlite.mjs"; +import { readTextFileTail } from "../text-file-utils.mjs"; const command = process.argv[2]; const scratchRoot = process.env.OPENCLAW_PLUGINS_TMP_DIR || os.tmpdir(); const readJson = (file) => JSON.parse(fs.readFileSync(file, "utf8")); const scratchFile = (name) => path.join(scratchRoot, name); +const ERROR_DETAIL_TAIL_BYTES = 16 * 1024; +const LOG_SCAN_CHUNK_BYTES = 64 * 1024; function readClawHubPreflightLimits() { return { @@ -114,6 +117,40 @@ function pathsEqual(left, right) { return comparablePath(left) === comparablePath(right); } +function fileContainsText(file, needle) { + let stat; + try { + stat = fs.statSync(file); + } catch { + return false; + } + if (!stat.isFile() || stat.size <= 0) { + return false; + } + const fd = fs.openSync(file, "r"); + try { + const buffer = Buffer.alloc(Math.min(LOG_SCAN_CHUNK_BYTES, stat.size)); + let carry = ""; + let offset = 0; + while (offset < stat.size) { + const bytesToRead = Math.min(buffer.length, stat.size - offset); + const bytesRead = fs.readSync(fd, buffer, 0, bytesToRead, offset); + if (bytesRead <= 0) { + break; + } + offset += bytesRead; + const text = carry + buffer.subarray(0, bytesRead).toString("utf8"); + if (text.includes(needle)) { + return true; + } + carry = text.slice(-Math.max(0, needle.length - 1)); + } + return false; + } finally { + fs.closeSync(fd); + } +} + function getInstallRecords() { const configPath = openClawConfigPath(); const config = readOpenClawConfig(); @@ -288,10 +325,10 @@ function assertSimplePlugin(jsonFile, inspectFile, pluginId, method) { } function assertUpdateOutput(logFile, expectedSnippet) { - const output = fs.readFileSync(logFile, "utf8"); - if (!output.includes(expectedSnippet)) { + if (!fileContainsText(logFile, expectedSnippet)) { + const outputTail = readTextFileTail(logFile, ERROR_DETAIL_TAIL_BYTES); throw new Error( - `expected update output to include ${JSON.stringify(expectedSnippet)}:\n${output}`, + `expected update output to include ${JSON.stringify(expectedSnippet)}. Output tail:\n${outputTail}`, ); } } diff --git a/test/scripts/plugins-assertions.test.ts b/test/scripts/plugins-assertions.test.ts index 4ecd5769d28d..bccff578c026 100644 --- a/test/scripts/plugins-assertions.test.ts +++ b/test/scripts/plugins-assertions.test.ts @@ -208,6 +208,40 @@ test -d "$OPENCLAW_PLUGINS_TMP_DIR" } }); + it("scans plugin update logs without echoing whole files on failure", async () => { + const root = mkdtempSync(path.join(tmpdir(), "openclaw-plugin-update-log-")); + try { + const passRoot = path.join(root, "pass"); + mkdirSync(passRoot, { recursive: true }); + writeFileSync( + path.join(passRoot, "plugins-dir-update.log"), + `Skipping "demo-plugin-dir" (source: path).\n${"x".repeat(256 * 1024)}`, + "utf8", + ); + const pass = await runAssertionAsync(["plugin-dir-update-skipped"], { + OPENCLAW_PLUGINS_TMP_DIR: passRoot, + }); + expect(pass.status).toBe(0); + + const failRoot = path.join(root, "fail"); + mkdirSync(failRoot, { recursive: true }); + writeFileSync( + path.join(failRoot, "plugins-dir-update.log"), + `${"x".repeat(256 * 1024)}\nmissing marker tail`, + "utf8", + ); + const fail = await runAssertionAsync(["plugin-dir-update-skipped"], { + OPENCLAW_PLUGINS_TMP_DIR: failRoot, + }); + expect(fail.status).toBe(1); + expect(fail.stderr).toContain("Output tail:"); + expect(fail.stderr).toContain("missing marker tail"); + expect(fail.stderr.length).toBeLessThan(20 * 1024); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + it("cleans npm fixture registry children when readiness times out", () => { const root = mkdtempSync(path.join(tmpdir(), "openclaw-plugin-npm-fixture-cleanup-")); try {