mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 07:01:34 +00:00
* fix(e2e): keep phase log tails UTF-8 safe The phase runner keeps a bounded in-memory log tail per phase and writes it to stderr when a phase fails. The byte-based cut can land inside a multi-byte UTF-8 character, and decoding from that offset emits U+FFFD replacement characters at the start of the diagnostics tail. Reuse the decodeUtf8Tail helper from scripts/e2e/lib/text-file-utils.mjs (#109669) so the retained window starts on a character boundary. Co-Authored-By: Claude <noreply@anthropic.com> * fix(e2e): declare decodeUtf8Tail in text-file-utils types phase-runner.ts imports decodeUtf8Tail from text-file-utils.mjs; the adjacent declaration file must export it or check-test-types fails with TS2305. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(e2e): reuse tailText wrapper for phase log tails Co-Authored-By: Claude <noreply@anthropic.com> * test(e2e): focus phase tail regression Co-authored-by: wangmiao0668000666 <wang.miao86@xydigit.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
44 lines
1.4 KiB
TypeScript
44 lines
1.4 KiB
TypeScript
// Parallels Phase Runner tests cover bounded in-memory phase log tails.
|
||
import { afterEach, expect, it, vi } from "vitest";
|
||
import { PhaseRunner } from "../../scripts/e2e/parallels/phase-runner.ts";
|
||
import { cleanupTempDirs, makeTempDir } from "../helpers/temp-dir.js";
|
||
|
||
const tempRoots: string[] = [];
|
||
|
||
function makeTempRoot() {
|
||
return makeTempDir(tempRoots, "openclaw-parallels-phase-runner-");
|
||
}
|
||
|
||
afterEach(() => {
|
||
cleanupTempDirs(tempRoots);
|
||
vi.restoreAllMocks();
|
||
});
|
||
|
||
async function captureFailedPhaseTail(runner: PhaseRunner, text: string): Promise<string> {
|
||
const written: string[] = [];
|
||
vi.spyOn(process.stderr, "write").mockImplementation((chunk) => {
|
||
written.push(String(chunk));
|
||
return true;
|
||
});
|
||
await expect(
|
||
runner.phase("utf8-tail", 60, () => {
|
||
runner.append(text);
|
||
throw new Error("boom");
|
||
}),
|
||
).rejects.toThrow("boom");
|
||
return written.join("");
|
||
}
|
||
|
||
it("keeps the truncated phase tail UTF-8 safe when the byte cut splits a character", async () => {
|
||
// 134 bytes total; the retained window starts one byte into the 4-byte
|
||
// emoji, so a byte-naive decode would emit replacement characters.
|
||
const tail = await captureFailedPhaseTail(
|
||
new PhaseRunner(makeTempRoot(), 128),
|
||
`${"x".repeat(50)}😀${"y".repeat(79)}`,
|
||
);
|
||
|
||
expect(tail).toContain("[phase log tail truncated to last 128 bytes]");
|
||
expect(tail).not.toContain("<22>");
|
||
expect(tail).toContain("y".repeat(79));
|
||
});
|