mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 11:55:47 -06:00
e27fb6680f
* fix(ci): validate complete plugin binding proof logs * fix(ci): preserve bounded parser diagnostics * fix(ci): route plugin binding parser proof
135 lines
4.3 KiB
TypeScript
135 lines
4.3 KiB
TypeScript
import { Buffer } from "node:buffer";
|
|
import { readFileSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import * as fs from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import * as stringDecoder from "node:string_decoder";
|
|
import vm from "node:vm";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
const SCRIPT_PATH = "scripts/e2e/plugin-binding-command-escape-docker.sh";
|
|
const script = readFileSync(SCRIPT_PATH, "utf8");
|
|
const parserMatch = script.match(
|
|
/if ! node - "\$RUN_LOG" <<'NODE'\n(?<parser>[\s\S]*?)\nNODE\nthen/u,
|
|
);
|
|
|
|
if (!parserMatch?.groups?.parser) {
|
|
throw new Error(`failed to extract embedded log parser from ${SCRIPT_PATH}`);
|
|
}
|
|
|
|
const parser = parserMatch.groups.parser;
|
|
|
|
class ParserExit extends Error {
|
|
constructor(readonly code: number) {
|
|
super(`parser exited ${code}`);
|
|
}
|
|
}
|
|
|
|
function runParser(log: string, options: { maxReadBytes?: number } = {}) {
|
|
const root = mkdtempSync(join(tmpdir(), "openclaw-plugin-binding-log-proof-"));
|
|
const logPath = join(root, "vitest.log");
|
|
const errors: string[] = [];
|
|
const readSizes: number[] = [];
|
|
writeFileSync(logPath, log);
|
|
|
|
const fsProxy = new Proxy(fs, {
|
|
get(target, property, receiver) {
|
|
if (property !== "readSync") {
|
|
return Reflect.get(target, property, receiver);
|
|
}
|
|
return (
|
|
fd: number,
|
|
buffer: NodeJS.ArrayBufferView,
|
|
offset: number,
|
|
length: number,
|
|
position: number | null,
|
|
) => {
|
|
const boundedLength = Math.min(length, options.maxReadBytes ?? length);
|
|
const bytesRead = fs.readSync(fd, buffer, offset, boundedLength, position);
|
|
readSizes.push(bytesRead);
|
|
return bytesRead;
|
|
};
|
|
},
|
|
});
|
|
|
|
try {
|
|
vm.runInNewContext(parser, {
|
|
Buffer,
|
|
console: {
|
|
error: (...args: unknown[]) => errors.push(args.map(String).join(" ")),
|
|
},
|
|
process: {
|
|
argv: ["node", "-", logPath],
|
|
exit: (code = 0) => {
|
|
throw new ParserExit(code);
|
|
},
|
|
},
|
|
require: (specifier: string) => {
|
|
if (specifier === "node:fs") {
|
|
return fsProxy;
|
|
}
|
|
if (specifier === "node:string_decoder") {
|
|
return stringDecoder;
|
|
}
|
|
throw new Error(`unexpected parser dependency: ${specifier}`);
|
|
},
|
|
});
|
|
return { errors, readSizes, status: 0 };
|
|
} catch (error) {
|
|
if (error instanceof ParserExit) {
|
|
return { errors, readSizes, status: error.code };
|
|
}
|
|
throw error;
|
|
} finally {
|
|
rmSync(root, { force: true, recursive: true });
|
|
}
|
|
}
|
|
|
|
describe("plugin binding Docker log proof", () => {
|
|
it("runs the aggregate main suite and both files after the suite split", () => {
|
|
expect(script).toContain(
|
|
"if [[ -f src/auto-reply/reply/dispatch-from-config.lifecycle-and-bindings.test.ts ]]",
|
|
);
|
|
expect(script).toContain(
|
|
"src/auto-reply/reply/dispatch-from-config.lifecycle-and-bindings.test.ts",
|
|
);
|
|
expect(script).toContain("src/auto-reply/reply/dispatch-from-config.test.ts");
|
|
expect(script).toContain('node scripts/run-vitest.mjs "${test_files[@]}"');
|
|
});
|
|
|
|
it("aggregates split summaries across the complete log with bounded partial reads", () => {
|
|
const noise = "not a summary\n".repeat(6000);
|
|
const log = [
|
|
"\u001B[32m Tests 2 passed\u001B[39m\n",
|
|
`${noise}\u{1F98A}${noise}`,
|
|
"\u001B[32m Tests 1 passed\u001B[39m\n",
|
|
].join("");
|
|
|
|
const result = runParser(log, { maxReadBytes: 3 });
|
|
|
|
expect(Buffer.byteLength(noise)).toBeGreaterThan(65536);
|
|
expect(result.status).toBe(0);
|
|
expect(result.errors).toEqual([]);
|
|
expect(result.readSizes.length).toBeGreaterThan(2);
|
|
expect(result.readSizes.every((size) => size <= 3)).toBe(true);
|
|
});
|
|
|
|
it("accepts one aggregate summary", () => {
|
|
expect(runParser(" Tests 3 passed | 4 skipped (7)\n").status).toBe(0);
|
|
});
|
|
|
|
it("rejects split summaries with the wrong total", () => {
|
|
const result = runParser(" Tests 1 passed\n Tests 1 passed\n");
|
|
|
|
expect(result.status).toBe(1);
|
|
expect(result.errors.join("\n")).toContain("2 summaries totaling 2");
|
|
});
|
|
|
|
it("rejects extra summaries even when their total is three", () => {
|
|
const result = runParser(" Tests 1 passed\n Tests 1 passed\n Tests 1 passed\n");
|
|
|
|
expect(result.status).toBe(1);
|
|
expect(result.errors.join("\n")).toContain("3 summaries");
|
|
});
|
|
});
|