Skip to content

Commit 717f8ac

Browse files
Copilotdmichon-msft
andcommitted
Fix multi-byte character decoding by setting stream encoding
Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com>
1 parent 8231646 commit 717f8ac

3 files changed

Lines changed: 57 additions & 0 deletions

File tree

libraries/node-core-library/src/Executable.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,13 @@ export class Executable {
597597
const { exitCode, signal } = await new Promise<ISignalAndExitCode>(
598598
(resolve: (result: ISignalAndExitCode) => void, reject: (error: Error) => void) => {
599599
if (encoding) {
600+
// Set the encoding on the streams to ensure proper handling of multi-byte characters.
601+
// When encoding is set, Node.js uses StringDecoder internally which properly handles
602+
// character boundaries that may be split across chunks.
603+
if (encoding !== 'buffer') {
604+
childProcess.stdout!.setEncoding(encoding);
605+
childProcess.stderr!.setEncoding(encoding);
606+
}
600607
childProcess.stdout!.on('data', (chunk: Buffer | string) => {
601608
collectedStdout.push(normalizeChunk(chunk));
602609
});
@@ -667,6 +674,8 @@ export class Executable {
667674
if (process.stdout === null) {
668675
throw new InternalError('Child process did not provide stdout');
669676
}
677+
// Set the encoding to ensure proper handling of multi-byte characters
678+
process.stdout.setEncoding('utf8');
670679
const [processInfoByIdMap] = await Promise.all([
671680
parseProcessListOutputAsync(process.stdout),
672681
// Don't collect output in the result since we process it directly

libraries/node-core-library/src/test/Executable.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,34 @@ describe('Executable process tests', () => {
350350
Executable.waitForExitAsync(childProcess, { encoding: 'utf8', throwOnSignal: true })
351351
).rejects.toThrowError(/Process terminated by SIGTERM/);
352352
});
353+
354+
test('Executable.waitForExitAsync() handles multi-byte UTF-8 characters correctly', async () => {
355+
// Test that multi-byte characters are properly decoded even when split across chunks
356+
const executablePath: string = path.join(executableFolder, 'multibyte', 'output-multibyte.js');
357+
const childProcess: child_process.ChildProcess = Executable.spawn(
358+
process.argv0,
359+
[executablePath],
360+
{
361+
environment,
362+
currentWorkingDirectory: executableFolder,
363+
stdio: ['ignore', 'pipe', 'pipe']
364+
}
365+
);
366+
367+
const result: IWaitForExitResult<string> = await Executable.waitForExitAsync(childProcess, {
368+
encoding: 'utf8'
369+
});
370+
371+
expect(result.exitCode).toEqual(0);
372+
expect(result.signal).toBeNull();
373+
expect(typeof result.stdout).toEqual('string');
374+
375+
// The output should contain properly decoded multi-byte characters
376+
// Chinese characters (世界) and emoji (🎉)
377+
expect(result.stdout).toContain('Hello, 世界! 🎉');
378+
// Ensure no replacement characters (�) which would indicate improper decoding
379+
expect(result.stdout).not.toContain('�');
380+
});
353381
});
354382

355383
describe('Executable process list', () => {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// This script writes multi-byte UTF-8 characters byte by byte to test proper decoding
2+
const { setTimeout } = require('node:timers/promises');
3+
4+
const unicodeString = "Hello, 世界! 🎉"; // "Hello, World" in Chinese with emoji
5+
const encoded = Buffer.from(unicodeString, 'utf8');
6+
7+
async function writeChars() {
8+
// Write each byte individually to force chunks that split multi-byte characters
9+
for (let i = 0; i < encoded.length; i++) {
10+
process.stdout.write(encoded.subarray(i, i + 1));
11+
// Small delay to ensure each byte is in a separate chunk
12+
await setTimeout(1);
13+
}
14+
process.stdout.write('\n');
15+
}
16+
17+
writeChars().catch((error) => {
18+
console.error('Error:', error);
19+
process.exit(1);
20+
});

0 commit comments

Comments
 (0)