diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc3d3fb..13e2e05 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,16 +11,18 @@ jobs: fail-fast: false matrix: ruby: - - jruby-9.4.1.0 + - jruby-9.4.12.0 - "2.7" - "3.0" - "3.1" - "3.2" + - "3.3" + - "3.4" runs-on: 'ubuntu-latest' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} diff --git a/.github/workflows/dynamic-readme.yml b/.github/workflows/dynamic-readme.yml index 41bb5db..f880407 100644 --- a/.github/workflows/dynamic-readme.yml +++ b/.github/workflows/dynamic-readme.yml @@ -1,6 +1,6 @@ name: update-templates -on: +on: push: branches: - main diff --git a/lib/terrapin/command_line.rb b/lib/terrapin/command_line.rb index 5626afd..e24bcf9 100644 --- a/lib/terrapin/command_line.rb +++ b/lib/terrapin/command_line.rb @@ -89,7 +89,7 @@ def run(interpolations = {}) unless @expected_outcodes.include?(@exit_status) message = [ - "Command '#{full_command}' returned #{@exit_status}. Expected #{@expected_outcodes.join(", ")}", + "Command '#{full_command}' returned #{@exit_status.inspect}. Expected #{@expected_outcodes.join(", ")}", "Here is the command output: STDOUT:\n", command_output, "\nSTDERR:\n", command_error_output ].join("\n") diff --git a/lib/terrapin/command_line/multi_pipe.rb b/lib/terrapin/command_line/multi_pipe.rb index 0a0d4b7..4bc904b 100644 --- a/lib/terrapin/command_line/multi_pipe.rb +++ b/lib/terrapin/command_line/multi_pipe.rb @@ -29,19 +29,51 @@ def close_write end def read - @stdout_output = read_stream(@stdout_in) - @stderr_output = read_stream(@stderr_in) + read_streams(@stdout_in, @stderr_in) end def close_read - @stdout_in.close + begin + @stdout_in.close + rescue IOError + # do nothing + end + + begin @stderr_in.close + rescue IOError + # do nothing + end + end + + def read_streams(output, error) + @stdout_output = "" + @stderr_output = "" + read_fds = [output, error] + while !read_fds.empty? + to_read, = IO.select(read_fds) + if to_read.include?(output) + @stdout_output << read_stream(output) + read_fds.delete(output) if output.closed? + end + + if to_read.include?(error) + @stderr_output << read_stream(error) + read_fds.delete(error) if error.closed? + end + end end def read_stream(io) result = String.new - while partial_result = io.read(8192) - result << partial_result + begin + while partial_result = io.read_nonblock(8192) + result << partial_result + end + rescue EOFError, Errno::EPIPE + io.close + rescue Errno::EINTR, Errno::EWOULDBLOCK, Errno::EAGAIN + # do nothing end result end diff --git a/spec/support/nonblocking_examples.rb b/spec/support/nonblocking_examples.rb index 59aa2de..46eb33b 100644 --- a/spec/support/nonblocking_examples.rb +++ b/spec/support/nonblocking_examples.rb @@ -1,4 +1,15 @@ -shared_examples_for "a command that does not block" do +shared_examples_for "a command that does not block" do |opts = {}| + if opts[:supports_stderr] + it "does not block if the command output a lot on stderr" do + Timeout.timeout(5) do + output = subject.call("ruby -e '$stdout.puts %{hello}; $stderr.puts %{goodbye}*10_000'") + + expect(output.output).to eq "hello\n" + expect(output.error_output).to eq "#{"goodbye" * 10_000}\n" + end + end + end + it 'does not block if the command outputs a lot of data' do garbage_file = Tempfile.new("garbage") 10.times{ garbage_file.write("A" * 1024 * 1024) } diff --git a/spec/terrapin/command_line/runners/backticks_runner_spec.rb b/spec/terrapin/command_line/runners/backticks_runner_spec.rb index dea9a25..cced409 100644 --- a/spec/terrapin/command_line/runners/backticks_runner_spec.rb +++ b/spec/terrapin/command_line/runners/backticks_runner_spec.rb @@ -2,7 +2,7 @@ describe Terrapin::CommandLine::BackticksRunner do if Terrapin::CommandLine::BackticksRunner.supported? - it_behaves_like 'a command that does not block' + it_behaves_like 'a command that does not block', { :supports_stderr => false } it 'runs the command given and captures the output in an Output' do output = subject.call("echo hello") diff --git a/spec/terrapin/command_line/runners/popen_runner_spec.rb b/spec/terrapin/command_line/runners/popen_runner_spec.rb index 8e5d993..0b5ebec 100644 --- a/spec/terrapin/command_line/runners/popen_runner_spec.rb +++ b/spec/terrapin/command_line/runners/popen_runner_spec.rb @@ -2,7 +2,7 @@ describe Terrapin::CommandLine::PopenRunner do if Terrapin::CommandLine::PopenRunner.supported? - it_behaves_like 'a command that does not block' + it_behaves_like 'a command that does not block', { :supports_stderr => false } it 'runs the command given and captures the output in an Output' do output = subject.call("echo hello") diff --git a/spec/terrapin/command_line/runners/process_runner_spec.rb b/spec/terrapin/command_line/runners/process_runner_spec.rb index 91dfa0e..02945f9 100644 --- a/spec/terrapin/command_line/runners/process_runner_spec.rb +++ b/spec/terrapin/command_line/runners/process_runner_spec.rb @@ -2,7 +2,7 @@ describe Terrapin::CommandLine::ProcessRunner do if Terrapin::CommandLine::ProcessRunner.supported? - it_behaves_like "a command that does not block" + it_behaves_like "a command that does not block", { :supports_stderr => true } it 'runs the command given and captures the output' do output = subject.call("echo hello")