From d0aebca528015a316fae9e1f58d89b3ef1f50db7 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Mon, 18 May 2026 15:38:06 +0200 Subject: [PATCH] Fix Capture::Tiny trailing newline issue in subprocess output Changed stream routing from line-based to byte-based to preserve exact output including or excluding trailing newlines. The previous implementation used BufferedReader.readLine() which strips newlines, then added them back, causing incorrect behavior when subprocess output didn't have trailing newlines. - Modified createStreamRouterThread to read raw bytes - Created writeToPerlStdoutBytes and writeToPerlStderrBytes methods - Removed unused string-based methods (writeToPerlStdout, writeToPerlStderr) Test results: Reduced test failures from 50 to 36 out of 331 tests. Remaining failures are unicode encoding issues (separate problem). --- .../runtime/operators/SystemOperator.java | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/perlonjava/runtime/operators/SystemOperator.java b/src/main/java/org/perlonjava/runtime/operators/SystemOperator.java index 27516df15..ccacbca3c 100644 --- a/src/main/java/org/perlonjava/runtime/operators/SystemOperator.java +++ b/src/main/java/org/perlonjava/runtime/operators/SystemOperator.java @@ -11,7 +11,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -465,55 +464,59 @@ private static void closeQuietly(BufferedReader reader) { } /** - * Writes a line to the current Perl-level STDOUT handle. + * Writes bytes to the current Perl-level STDERR handle. * This ensures output goes through any Perl-level redirections (e.g., - * when STDOUT has been reopened to a file via open STDOUT, ">", $file). + * when STDERR has been reopened to a file via open STDERR, ">", $file). */ - private static void writeToPerlStdout(String line) { + private static void writeToPerlStderrBytes(byte[] buffer, int bytesRead) { try { - RuntimeIO perlStdout = GlobalVariable.getGlobalIO("main::STDOUT").getRuntimeIO(); - if (perlStdout != null) { - perlStdout.write(line + "\n"); + RuntimeIO perlStderr = GlobalVariable.getGlobalIO("main::STDERR").getRuntimeIO(); + String text = new String(buffer, 0, bytesRead, java.nio.charset.StandardCharsets.ISO_8859_1); + if (perlStderr != null) { + perlStderr.write(text); } else { - System.out.println(line); + System.err.write(buffer, 0, bytesRead); } } catch (Exception e) { - System.out.println(line); + System.err.write(buffer, 0, bytesRead); } } /** - * Writes a line to the current Perl-level STDERR handle. + * Writes bytes to the current Perl-level STDOUT handle. * This ensures output goes through any Perl-level redirections (e.g., - * when STDERR has been reopened to a file via open STDERR, ">", $file). + * when STDOUT has been reopened to a file via open STDOUT, ">", $file). */ - private static void writeToPerlStderr(String line) { + private static void writeToPerlStdoutBytes(byte[] buffer, int bytesRead) { try { - RuntimeIO perlStderr = GlobalVariable.getGlobalIO("main::STDERR").getRuntimeIO(); - if (perlStderr != null) { - perlStderr.write(line + "\n"); + RuntimeIO perlStdout = GlobalVariable.getGlobalIO("main::STDOUT").getRuntimeIO(); + String text = new String(buffer, 0, bytesRead, java.nio.charset.StandardCharsets.ISO_8859_1); + if (perlStdout != null) { + perlStdout.write(text); } else { - System.err.println(line); + System.out.write(buffer, 0, bytesRead); } } catch (Exception e) { - System.err.println(line); + System.out.write(buffer, 0, bytesRead); } } /** - * Creates a daemon thread that reads from the given input stream and writes - * each line to the current Perl-level handle for the given global name. + * Creates a thread that routes an InputStream to the current Perl-level handle. + * Bytes are written directly without line-by-line processing to preserve + * exact output including or excluding trailing newlines. * This is used for routing child process stderr/stdout through Perl handles. */ private static Thread createStreamRouterThread(InputStream stream, boolean isStderr) { Thread t = new Thread(() -> { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) { - String line; - while ((line = reader.readLine()) != null) { + try { + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = stream.read(buffer)) != -1) { if (isStderr) { - writeToPerlStderr(line); + writeToPerlStderrBytes(buffer, bytesRead); } else { - writeToPerlStdout(line); + writeToPerlStdoutBytes(buffer, bytesRead); } } } catch (IOException e) {