diff --git a/scripts/e2e/lib/text-file-utils.mjs b/scripts/e2e/lib/text-file-utils.mjs index 0799b21b6819..636d48f7cdd9 100644 --- a/scripts/e2e/lib/text-file-utils.mjs +++ b/scripts/e2e/lib/text-file-utils.mjs @@ -1,11 +1,21 @@ // Text file tail helpers for E2E assertions. import fs from "node:fs"; +function decodeUtf8Tail(buffer, truncated) { + let start = 0; + if (truncated) { + while (start < buffer.length && (buffer[start] & 0b1100_0000) === 0b1000_0000) { + start += 1; + } + } + return buffer.subarray(start).toString("utf8"); +} + export function tailText(text, maxBytes) { if (Buffer.byteLength(text, "utf8") <= maxBytes) { return text; } - return Buffer.from(text, "utf8").subarray(-maxBytes).toString("utf8"); + return decodeUtf8Tail(Buffer.from(text, "utf8").subarray(-maxBytes), true); } export function readTextFileTail(file, maxBytes) { @@ -26,7 +36,7 @@ export function readTextFileTail(file, maxBytes) { fd = fs.openSync(file, "r"); const buffer = Buffer.alloc(length); const bytesRead = fs.readSync(fd, buffer, 0, length, start); - return buffer.subarray(0, bytesRead).toString("utf8"); + return decodeUtf8Tail(buffer.subarray(0, bytesRead), start > 0); } catch { return ""; } finally { diff --git a/test/scripts/e2e-text-file-utils.test.ts b/test/scripts/e2e-text-file-utils.test.ts index 63bbe5b84df6..a219976e900e 100644 --- a/test/scripts/e2e-text-file-utils.test.ts +++ b/test/scripts/e2e-text-file-utils.test.ts @@ -23,6 +23,8 @@ describe("e2e text file utilities", () => { it("keeps short diagnostic text intact and trims long text by byte count", () => { expect(tailText("short", 8)).toBe("short"); expect(tailText("prefix-tail", 4)).toBe("tail"); + expect(tailText("prefix \u{1f600}tail", 7)).toBe("tail"); + expect(tailText("prefix \u{1f600}tail", 8)).toBe("\u{1f600}tail"); }); it("reads only the requested file tail and treats missing or non-file paths as empty", () => { @@ -33,6 +35,11 @@ describe("e2e text file utilities", () => { writeFileSync(file, "line-one\nline-two\nline-three", "utf8"); expect(readTextFileTail(file, 10)).toBe("line-three"); + writeFileSync(file, "prefix \u{1f600}tail", "utf8"); + expect(readTextFileTail(file, 7)).toBe("tail"); + expect(readTextFileTail(file, 8)).toBe("\u{1f600}tail"); + writeFileSync(file, Buffer.concat([Buffer.from("prefix tail"), Buffer.from([0xf0])])); + expect(readTextFileTail(file, 5)).toBe("tail\ufffd"); expect(readTextFileTail(path.join(root, "missing.log"), 10)).toBe(""); expect(readTextFileTail(directory, 10)).toBe(""); });