diff --git a/test/helpers/bounded-child-output.test.ts b/test/helpers/bounded-child-output.test.ts index 32c0e15e21b0..2ec1b29656ce 100644 --- a/test/helpers/bounded-child-output.test.ts +++ b/test/helpers/bounded-child-output.test.ts @@ -22,4 +22,29 @@ describe("bounded child output", () => { expect(output.text()).toBe("x-recent"); }); + + it("drops split UTF-8 prefixes after buffered output overflow", () => { + const output = createBoundedChildOutput(7); + + output.append(Buffer.from("prefix 😀")); + output.append(Buffer.from("tail")); + + expect(output.text()).toBe("tail"); + }); + + it("drops split UTF-8 prefixes from single oversized chunks", () => { + const output = createBoundedChildOutput(7); + + output.append(Buffer.from("prefix 😀tail")); + + expect(output.text()).toBe("tail"); + }); + + it("preserves replacement output for incomplete trailing bytes", () => { + const output = createBoundedChildOutput(7); + + output.append(Buffer.from([0x61, 0xe2])); + + expect(output.text()).toBe("a�"); + }); }); diff --git a/test/helpers/bounded-child-output.ts b/test/helpers/bounded-child-output.ts index d4669e710c81..d2826f5078f2 100644 --- a/test/helpers/bounded-child-output.ts +++ b/test/helpers/bounded-child-output.ts @@ -1,5 +1,15 @@ +import { StringDecoder } from "node:string_decoder"; + export const DEFAULT_CHILD_OUTPUT_TAIL_BYTES = 128 * 1024; +function decodeUtf8Tail(buffer: Buffer): string { + let start = 0; + while (start < buffer.length && (buffer[start]! & 0b1100_0000) === 0b1000_0000) { + start += 1; + } + return new StringDecoder("utf8").end(buffer.subarray(start)); +} + export function createBoundedChildOutput(maxBytes = DEFAULT_CHILD_OUTPUT_TAIL_BYTES) { const limit = Number.isInteger(maxBytes) && maxBytes > 0 ? maxBytes : DEFAULT_CHILD_OUTPUT_TAIL_BYTES; @@ -37,7 +47,7 @@ export function createBoundedChildOutput(maxBytes = DEFAULT_CHILD_OUTPUT_TAIL_BY trim(); }, text(): string { - return Buffer.concat(chunks, totalBytes).toString("utf8"); + return decodeUtf8Tail(Buffer.concat(chunks, totalBytes)); }, }; }