diff --git a/Library/Homebrew/test/utils/popen_spec.rb b/Library/Homebrew/test/utils/popen_spec.rb index c3f6027cf7241570b0d5181a3b75439e2a08be15..dcce91d8b46b0fc1e1fb0d4aedc68397d4beb231 100644 --- a/Library/Homebrew/test/utils/popen_spec.rb +++ b/Library/Homebrew/test/utils/popen_spec.rb @@ -26,11 +26,49 @@ describe Utils do end describe "::popen_write" do - it "with supports writing to a command's standard input" do + let(:foo) { mktmpdir/"foo" } + + before { foo.write "Foo\n" } + + it "supports writing to a command's standard input" do subject.popen_write("grep", "-q", "success") do |pipe| - pipe.write("success\n") + pipe.write "success\n" + end + expect($CHILD_STATUS).to be_a_success + end + + it "returns the command's standard output before writing" do + child_stdout = subject.popen_write("cat", foo, "-") do |pipe| + pipe.write "Bar\n" + end + expect($CHILD_STATUS).to be_a_success + expect(child_stdout).to eq <<~EOS + Foo + Bar + EOS + end + + it "returns the command's standard output after writing" do + child_stdout = subject.popen_write("cat", "-", foo) do |pipe| + pipe.write "Bar\n" + end + expect($CHILD_STATUS).to be_a_success + expect(child_stdout).to eq <<~EOS + Bar + Foo + EOS + end + + it "supports interleaved writing between two reads" do + child_stdout = subject.popen_write("cat", foo, "-", foo) do |pipe| + pipe.write "Bar\n" end expect($CHILD_STATUS).to be_a_success + expect(child_stdout).to eq <<~EOS + Foo + Bar + Foo + EOS end end end diff --git a/Library/Homebrew/utils/popen.rb b/Library/Homebrew/utils/popen.rb index f9d189219c1d2957961aa8488f5701a769f30e15..eac4823c64c90354d4cc57223a3d62437ab80b7d 100644 --- a/Library/Homebrew/utils/popen.rb +++ b/Library/Homebrew/utils/popen.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true module Utils + IO_DEFAULT_BUFFER_SIZE = 4096 + private_constant :IO_DEFAULT_BUFFER_SIZE + def self.popen_read(*args, **options, &block) popen(args, "rb", options, &block) end @@ -12,8 +15,25 @@ module Utils raise ErrorDuringExecution.new(args, status: $CHILD_STATUS, output: [[:stdout, output]]) end - def self.popen_write(*args, **options, &block) - popen(args, "wb", options, &block) + def self.popen_write(*args, **options) + popen(args, "w+b", options) do |pipe| + output = "" + + # Before we yield to the block, capture as much output as we can + loop do + output += pipe.read_nonblock(IO_DEFAULT_BUFFER_SIZE) + rescue IO::WaitReadable, EOFError + break + end + + yield pipe + pipe.close_write + IO.select([pipe]) + + # Capture the rest of the output + output += pipe.read + output.freeze + end end def self.safe_popen_write(*args, **options, &block)