fix(e2e): keep text file tails UTF-8 safe (#109669)

* fix(e2e): keep text file tails UTF-8 safe

* fix: preserve malformed UTF-8 tail bytes

Co-authored-by: qingminlong <0668001063@xydigit.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
qingminlong
2026-07-17 16:57:57 +08:00
committed by GitHub
parent 68c4cb31d2
commit ce4a16b0d3
2 changed files with 19 additions and 2 deletions
+12 -2
View File
@@ -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 {
+7
View File
@@ -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("");
});