diff --git a/CHANGELOG b/CHANGELOG index 05d4601..51fb442 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +1.4.0 - 01-07-2026 + +- Stream command output directly to stdout instead of buffering + 1.3.0 - 07-08-2025 - Add --params option diff --git a/lib/pups/exec_command.rb b/lib/pups/exec_command.rb index 34b65cd..8fbc992 100644 --- a/lib/pups/exec_command.rb +++ b/lib/pups/exec_command.rb @@ -111,17 +111,24 @@ def spawn(command) return pid end - IO.popen(command, "w+") do |f| - if stdin - # need a way to get stdout without blocking - Pups.log.info(stdin) - f.write stdin - f.close - else - Pups.log.info(f.readlines.join) - end + opts = { out: $stdout, err: $stderr } + + if stdin + reader, writer = IO.pipe + opts[:in] = reader + end + + pid = Process.spawn(command, opts) + + if stdin + reader.close + Pups.log.info(stdin) + writer.write(stdin) + writer.close end + Process.wait(pid) + unless $CHILD_STATUS == 0 err = Pups::ExecError.new( diff --git a/lib/pups/version.rb b/lib/pups/version.rb index 281141f..77a9d1b 100644 --- a/lib/pups/version.rb +++ b/lib/pups/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Pups - VERSION = "1.3.0" + VERSION = "1.4.0" end diff --git a/test/exec_command_test.rb b/test/exec_command_test.rb index f20d0d4..aee40a6 100644 --- a/test/exec_command_test.rb +++ b/test/exec_command_test.rb @@ -98,5 +98,44 @@ def test_can_terminate_rogues assert_raises(Errno::ECHILD) { Process.waitpid(pid, Process::WNOHANG) } end + + def test_stdout_streaming + # Starts a long-running process that outputs immediately + # and then watches a file before quitting. + # If stdout is properly streamed, we should see the output + # immediately rather than waiting for the process to end. + + signal_file = Tempfile.new("signal") + signal_path = signal_file.path + signal_file.close + + reader, writer = IO.pipe + original_stdout = $stdout.dup + + $stdout.reopen(writer) + + cmd = ExecCommand.new({}) + cmd.add("echo 'streamed output'; while [ ! -s #{signal_path} ]; do sleep 0.1; done") + + thread = Thread.new { cmd.run } + + # Wait for output - if streaming works, it appears immediately + # If buffered, it would never appear since command loops until signaled + ready = IO.select([reader], nil, nil, 2) + assert ready, "Output should be available before command completes" + + output = reader.read_nonblock(1000) + assert_includes(output, "streamed output") + ensure + $stdout.reopen(original_stdout) if original_stdout + + writer&.close + reader&.close + + File.write(signal_path, "done") + signal_file&.unlink + + thread.join(2) + end end end