Skip to content

Commit

Permalink
Redirect output across streams
Browse files Browse the repository at this point in the history
`Runner` will "stream" output to `STDOUT`, `STDERR`, and each of the
associated streams instead of waiting until the subprocesses complete.
  • Loading branch information
seanpdoyle committed Mar 26, 2016
1 parent ef6e963 commit ba83120
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 31 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
master
------

* Stream output instead of waiting until subprocesses finish. [#423]
* Update `ember-cli-rails-assets` dependency. [#422]
* Only write errors to `STDERR`. [#421]
* Remove dependency on `tee`. Fixes bug [#417][#417]. [#420]
* Remove dependency on `tee`. Fixes bug [#417]. [#420]

[#423]: https://github.com/thoughtbot/ember-cli-rails/pull/423
[#422]: https://github.com/thoughtbot/ember-cli-rails/pull/422
[#421]: https://github.com/thoughtbot/ember-cli-rails/issues/421
[#420]: https://github.com/thoughtbot/ember-cli-rails/issues/420
Expand Down
28 changes: 18 additions & 10 deletions lib/ember_cli/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,22 @@ module EmberCli
class Runner
def initialize(out:, err:, env: {}, options: {})
@env = env
@out_streams = Array(out)
@err_streams = Array(err)
@output_streams = Array(out)
@error_streams = Array(err)
@options = options
@threads = []
end

def run(command)
output, error, status = Open3.capture3(env, command, options)
Open3.popen3(env, command, options) do |stdin, stdout, stderr, process|
stdin.close

write(output, streams: out_streams)
write(error, streams: err_streams)
threads << redirect_stream_in_thread(stdout, write_to: output_streams)
threads << redirect_stream_in_thread(stderr, write_to: error_streams)

status
threads.each(&:join)
process.value
end
end

def run!(command)
Expand All @@ -28,13 +32,17 @@ def run!(command)

protected

attr_reader :env, :err_streams, :options, :out_streams
attr_reader :env, :error_streams, :options, :output_streams, :threads

private

def write(output, streams:)
streams.each do |stream|
stream.write(output)
def redirect_stream_in_thread(stream, write_to:)
Thread.new do
Thread.current.abort_on_exception = true

while line = stream.gets
write_to.each { |redirection_stream| redirection_stream.puts(line) }
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/ember_cli/shell.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def spawn(command)
def runner
Runner.new(
options: { chdir: paths.root.to_s },
out: [$stdout, paths.log],
out: [$stdout, paths.log.open("a")],
err: [$stderr],
env: env,
)
Expand Down
54 changes: 35 additions & 19 deletions spec/lib/ember_cli/runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,33 @@
describe "#run!" do
context "when the command fails" do
it "writes output to `out` streams" do
output_streams = Array.new(2) { StringIO.new }
stdout = StringIO.new
logfile = StringIO.new
runner = EmberCli::Runner.new(
err: [],
out: output_streams,
out: [stdout, logfile],
)

expect { runner.run!("echo 'out'; echo 'err' > /dev/stderr; exit 1") }.
expect { runner.run!(command_with_error(out: "out")) }.
to raise_error(SystemExit)

ouput_strings = output_streams.each(&:rewind).map(&:read)

ouput_strings.each do |output|
expect(output).to eq("out\n")
end
expect(split_output_from_stream(stdout)).to eq(%w[out out])
expect(split_output_from_stream(logfile)).to eq(%w[out out])
end

it "writes errors to `err` streams" do
error_streams = Array.new(2) { StringIO.new }
stderr = StringIO.new
logfile = StringIO.new
runner = EmberCli::Runner.new(
err: error_streams,
err: [stderr, logfile],
out: [],
)

expect { runner.run!("echo 'out'; echo 'err' > /dev/stderr; exit 1") }.
expect { runner.run!(command_with_error(err: "err")) }.
to raise_error(SystemExit)

error_strings = error_streams.each(&:rewind).map(&:read)

error_strings.each do |error|
expect(error).to eq("err\n")
end
expect(split_output_from_stream(stderr)).to eq(%w[err err])
expect(split_output_from_stream(logfile)).to eq(%w[err err])
end
end

Expand All @@ -43,13 +39,33 @@
err = StringIO.new
runner = EmberCli::Runner.new(err: [err], out: [out])

status = runner.run!("echo 'out'")
status = runner.run!(command("out"))

[err, out].each(&:rewind)

expect(status).to be_success
expect(err.read).to be_empty
expect(out.read).to eq("out\n")
expect(split_output_from_stream(err)).to be_empty
expect(split_output_from_stream(out)).to eq(%w[out])
end

def split_output_from_stream(stream)
stream.rewind

stream.read.split
end

def command(output)
"echo '#{output}'"
end

def command_with_error(out: "", err: "")
[
"echo '#{out}'",
"echo '#{err}' > /dev/stderr",
"echo '#{out}'",
"echo '#{err}' > /dev/stderr",
"exit 1",
].join("; ")
end
end
end

0 comments on commit ba83120

Please sign in to comment.