Skip to content

Commit ba83120

Browse files
committed
Redirect output across streams
`Runner` will "stream" output to `STDOUT`, `STDERR`, and each of the associated streams instead of waiting until the subprocesses complete.
1 parent ef6e963 commit ba83120

File tree

4 files changed

+57
-31
lines changed

4 files changed

+57
-31
lines changed

CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
master
22
------
33

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

9+
[#423]: https://github.com/thoughtbot/ember-cli-rails/pull/423
810
[#422]: https://github.com/thoughtbot/ember-cli-rails/pull/422
911
[#421]: https://github.com/thoughtbot/ember-cli-rails/issues/421
1012
[#420]: https://github.com/thoughtbot/ember-cli-rails/issues/420

lib/ember_cli/runner.rb

+18-10
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,22 @@ module EmberCli
44
class Runner
55
def initialize(out:, err:, env: {}, options: {})
66
@env = env
7-
@out_streams = Array(out)
8-
@err_streams = Array(err)
7+
@output_streams = Array(out)
8+
@error_streams = Array(err)
99
@options = options
10+
@threads = []
1011
end
1112

1213
def run(command)
13-
output, error, status = Open3.capture3(env, command, options)
14+
Open3.popen3(env, command, options) do |stdin, stdout, stderr, process|
15+
stdin.close
1416

15-
write(output, streams: out_streams)
16-
write(error, streams: err_streams)
17+
threads << redirect_stream_in_thread(stdout, write_to: output_streams)
18+
threads << redirect_stream_in_thread(stderr, write_to: error_streams)
1719

18-
status
20+
threads.each(&:join)
21+
process.value
22+
end
1923
end
2024

2125
def run!(command)
@@ -28,13 +32,17 @@ def run!(command)
2832

2933
protected
3034

31-
attr_reader :env, :err_streams, :options, :out_streams
35+
attr_reader :env, :error_streams, :options, :output_streams, :threads
3236

3337
private
3438

35-
def write(output, streams:)
36-
streams.each do |stream|
37-
stream.write(output)
39+
def redirect_stream_in_thread(stream, write_to:)
40+
Thread.new do
41+
Thread.current.abort_on_exception = true
42+
43+
while line = stream.gets
44+
write_to.each { |redirection_stream| redirection_stream.puts(line) }
45+
end
3846
end
3947
end
4048
end

lib/ember_cli/shell.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def spawn(command)
8585
def runner
8686
Runner.new(
8787
options: { chdir: paths.root.to_s },
88-
out: [$stdout, paths.log],
88+
out: [$stdout, paths.log.open("a")],
8989
err: [$stderr],
9090
env: env,
9191
)

spec/lib/ember_cli/runner_spec.rb

+35-19
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,33 @@
44
describe "#run!" do
55
context "when the command fails" do
66
it "writes output to `out` streams" do
7-
output_streams = Array.new(2) { StringIO.new }
7+
stdout = StringIO.new
8+
logfile = StringIO.new
89
runner = EmberCli::Runner.new(
910
err: [],
10-
out: output_streams,
11+
out: [stdout, logfile],
1112
)
1213

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

16-
ouput_strings = output_streams.each(&:rewind).map(&:read)
17-
18-
ouput_strings.each do |output|
19-
expect(output).to eq("out\n")
20-
end
17+
expect(split_output_from_stream(stdout)).to eq(%w[out out])
18+
expect(split_output_from_stream(logfile)).to eq(%w[out out])
2119
end
2220

2321
it "writes errors to `err` streams" do
24-
error_streams = Array.new(2) { StringIO.new }
22+
stderr = StringIO.new
23+
logfile = StringIO.new
2524
runner = EmberCli::Runner.new(
26-
err: error_streams,
25+
err: [stderr, logfile],
2726
out: [],
2827
)
2928

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

33-
error_strings = error_streams.each(&:rewind).map(&:read)
34-
35-
error_strings.each do |error|
36-
expect(error).to eq("err\n")
37-
end
32+
expect(split_output_from_stream(stderr)).to eq(%w[err err])
33+
expect(split_output_from_stream(logfile)).to eq(%w[err err])
3834
end
3935
end
4036

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

46-
status = runner.run!("echo 'out'")
42+
status = runner.run!(command("out"))
4743

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

5046
expect(status).to be_success
51-
expect(err.read).to be_empty
52-
expect(out.read).to eq("out\n")
47+
expect(split_output_from_stream(err)).to be_empty
48+
expect(split_output_from_stream(out)).to eq(%w[out])
49+
end
50+
51+
def split_output_from_stream(stream)
52+
stream.rewind
53+
54+
stream.read.split
55+
end
56+
57+
def command(output)
58+
"echo '#{output}'"
59+
end
60+
61+
def command_with_error(out: "", err: "")
62+
[
63+
"echo '#{out}'",
64+
"echo '#{err}' > /dev/stderr",
65+
"echo '#{out}'",
66+
"echo '#{err}' > /dev/stderr",
67+
"exit 1",
68+
].join("; ")
5369
end
5470
end
5571
end

0 commit comments

Comments
 (0)