Skip to content

Commit b824f87

Browse files
authored
Merge pull request #1436 from mbj/improve/capture-stdout
Change to improved command capture
2 parents 9d85799 + fd3c87d commit b824f87

File tree

8 files changed

+108
-63
lines changed

8 files changed

+108
-63
lines changed

lib/mutant/license/subscription/commercial.rb

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,9 @@ def commit_author(world)
7575

7676
def capture(world, command)
7777
world
78-
.capture_stdout(command)
79-
.fmap(&:chomp)
80-
.fmap { |email| Author.new(email: email) }
81-
.fmap { |value| Set.new([value]) }
82-
.from_right { Set.new }
78+
.capture_command(command)
79+
.either(->(_) { EMPTY_ARRAY }, ->(status) { [Author.new(email: status.stdout.chomp)] })
80+
.to_set
8381
end
8482
end # Individual
8583
end # Commercial

lib/mutant/license/subscription/repository.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ def to_s
2020

2121
def self.load_from_git(world)
2222
world
23-
.capture_stdout(%w[git remote --verbose])
24-
.fmap(&method(:parse_remotes))
23+
.capture_command(%w[git remote --verbose])
24+
.fmap { |status| parse_remotes(status.stdout) }
2525
end
2626

2727
def self.parse_remotes(input)

lib/mutant/repository/diff.rb

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,8 @@ def touches_path?(path)
3636

3737
def repository_root
3838
world
39-
.capture_stdout(%w[git rev-parse --show-toplevel])
40-
.fmap(&:chomp)
41-
.fmap(&world.pathname.public_method(:new))
39+
.capture_command(%w[git rev-parse --show-toplevel])
40+
.fmap { |status| world.pathname.new(status.stdout.chomp) }
4241
end
4342

4443
def touched_path(path, &block)
@@ -52,11 +51,10 @@ def touched_paths
5251

5352
def diff_index(root)
5453
world
55-
.capture_stdout(%W[git diff-index #{to}])
56-
.fmap(&:lines)
57-
.bind do |lines|
54+
.capture_command(%W[git diff-index #{to}])
55+
.bind do |status|
5856
Mutant
59-
.traverse(->(line) { parse_line(root, line) }, lines)
57+
.traverse(->(line) { parse_line(root, line) }, status.stdout.lines)
6058
.fmap do |paths|
6159
paths.to_h { |path| [path.path, path] }
6260
end
@@ -105,8 +103,8 @@ def touches?(line_range)
105103

106104
def diff_ranges
107105
world
108-
.capture_stdout(%W[git diff --unified=0 #{to} -- #{path}])
109-
.fmap(&Ranges.public_method(:parse))
106+
.capture_command(%W[git diff --unified=0 #{to} -- #{path}])
107+
.fmap { |status| Ranges.parse(status.stdout) }
110108
.from_right
111109
end
112110
memoize :diff_ranges

lib/mutant/world.rb

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,25 @@ def inspect
3939
INSPECT
4040
end
4141

42+
class CommandStatus
43+
include Adamantium, Anima.new(:process_status, :stderr, :stdout)
44+
end # CommandStatus
45+
4246
# Capture stdout of a command
4347
#
4448
# @param [Array<String>] command
4549
#
46-
# @return [Either<String,String>]
47-
def capture_stdout(command)
48-
stdout, status = open3.capture2(*command, binmode: true)
50+
# @return [Either<CommandStatus,CommandStatus>]
51+
def capture_command(command)
52+
stdout, stderr, process_status = open3.capture3(*command, binmode: true)
4953

50-
if status.success?
51-
Either::Right.new(stdout)
52-
else
53-
Either::Left.new("Command #{command} failed!")
54-
end
54+
(process_status.success? ? Either::Right : Either::Left).new(
55+
CommandStatus.new(
56+
process_status: process_status,
57+
stderr: stderr,
58+
stdout: stdout
59+
)
60+
)
5561
end
5662

5763
# Try const get

spec/unit/mutant/license/repository_spec.rb

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,19 @@ def apply
6060
described_class.load_from_git(world)
6161
end
6262

63-
let(:world) { instance_double(Mutant::World) }
64-
6563
before do
66-
allow(world).to receive(:capture_stdout, &commands.public_method(:fetch))
64+
allow(world).to receive(:capture_command, &commands.public_method(:fetch))
6765
end
6866

69-
let(:git_remote_result) { right(git_remote) }
70-
let(:allowed_repositories) { %w[github.com/mbj/mutant] }
67+
let(:allowed_repositories) { %w[github.com/mbj/mutant] }
68+
let(:git_remote_result) { right(git_remote_status) }
69+
let(:world) { instance_double(Mutant::World) }
70+
71+
let(:git_remote_status) do
72+
instance_double(Mutant::World::CommandStatus, stdout: git_remote_stdout)
73+
end
7174

72-
let(:git_remote) do
75+
let(:git_remote_stdout) do
7376
<<~REMOTE
7477
origin\t[email protected]:mbj/Mutant (fetch)
7578
origin\t[email protected]:mbj/Mutant (push)
@@ -102,7 +105,7 @@ def apply
102105
end
103106

104107
context 'on ssh url with protocol and without suffix' do
105-
let(:git_remote) do
108+
let(:git_remote_stdout) do
106109
<<~REMOTE
107110
origin\tssh://[email protected]/mbj/mutant (fetch)
108111
origin\tssh://[email protected]/mbj/mutant (push)
@@ -113,7 +116,7 @@ def apply
113116
end
114117

115118
context 'on ssh url with protocol and suffix' do
116-
let(:git_remote) do
119+
let(:git_remote_stdout) do
117120
<<~REMOTE
118121
origin\tssh://[email protected]/mbj/mutant.git (fetch)
119122
origin\tssh://[email protected]/mbj/mutant.git (push)
@@ -124,7 +127,7 @@ def apply
124127
end
125128

126129
context 'on https url without suffix' do
127-
let(:git_remote) do
130+
let(:git_remote_stdout) do
128131
<<~REMOTE
129132
origin\thttps://github.com/mbj/mutant (fetch)
130133
origin\thttps://github.com/mbj/mutant (push)
@@ -135,7 +138,7 @@ def apply
135138
end
136139

137140
context 'on multiple different urls' do
138-
let(:git_remote) do
141+
let(:git_remote_stdout) do
139142
<<~REMOTE
140143
origin\thttps://github.com/mbj/mutant (fetch)
141144
origin\thttps://github.com/mbj/mutant (push)
@@ -161,7 +164,7 @@ def apply
161164
end
162165

163166
context 'on https url with .git suffix' do
164-
let(:git_remote) do
167+
let(:git_remote_stdout) do
165168
<<~REMOTE
166169
origin\thttps://github.com/mbj/mutant.git (fetch)
167170
origin\thttps://github.com/mbj/mutant.git (push)
@@ -172,13 +175,13 @@ def apply
172175
end
173176

174177
context 'when git remote line cannot be parsed' do
175-
let(:git_remote) { "some-bad-remote-line\n" }
178+
let(:git_remote_stdout) { "some-bad-remote-line\n" }
176179

177180
it_fails 'Unmatched remote line: "some-bad-remote-line\n"'
178181
end
179182

180183
context 'when git remote url cannot be parsed' do
181-
let(:git_remote) { "some-unknown\thttp://github.com/mbj/mutant (fetch)\n" }
184+
let(:git_remote_stdout) { "some-unknown\thttp://github.com/mbj/mutant (fetch)\n" }
182185

183186
it_fails 'Unmatched git remote URL: "http://github.com/mbj/mutant"'
184187
end

spec/unit/mutant/license/subscription_spec.rb

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def apply
7777
let(:world) { instance_double(Mutant::World) }
7878

7979
before do
80-
allow(world).to receive(:capture_stdout, &commands.public_method(:fetch))
80+
allow(world).to receive(:capture_command, &commands.public_method(:fetch))
8181
end
8282

8383
def self.it_fails(expected)
@@ -99,9 +99,13 @@ def self.it_fails_with_message(expected)
9999
end
100100

101101
describe 'on opensource license' do
102-
let(:git_remote_result) { right(git_remote) }
102+
let(:git_remote_result) { right(git_remote_status) }
103103
let(:allowed_repositories) { %w[github.com/mbj/mutant] }
104104

105+
let(:git_remote_status) do
106+
instance_double(Mutant::World::CommandStatus, stdout: git_remote_stdout)
107+
end
108+
105109
let(:license_json) do
106110
{
107111
'type' => 'oss',
@@ -117,7 +121,7 @@ def self.it_fails_with_message(expected)
117121
}
118122
end
119123

120-
let(:git_remote) do
124+
let(:git_remote_stdout) do
121125
<<~REMOTE
122126
origin\t[email protected]:mbj/mutant (fetch)
123127
origin\t[email protected]:mbj/mutant (push)
@@ -127,7 +131,7 @@ def self.it_fails_with_message(expected)
127131
context 'on one of many match' do
128132
let(:allowed_repositories) { %w[github.com/mbj/something github.com/mbj/mutant] }
129133

130-
let(:git_remote) do
134+
let(:git_remote_stdout) do
131135
<<~REMOTE
132136
origin\t[email protected]:mbj/mutant (fetch)
133137
origin\t[email protected]:mbj/mutant (push)
@@ -182,7 +186,11 @@ def self.it_fails_with_message(expected)
182186

183187
describe 'on commercial license' do
184188
context 'on organization licenses' do
185-
let(:git_remote_result) { right(git_remote) }
189+
let(:git_remote_result) { right(git_remote_status) }
190+
191+
let(:git_remote_status) do
192+
instance_double(Mutant::World::CommandStatus, stdout: git_remote_stdout)
193+
end
186194

187195
let(:license_json) do
188196
{
@@ -200,7 +208,7 @@ def self.it_fails_with_message(expected)
200208
}
201209
end
202210

203-
let(:git_remote) do
211+
let(:git_remote_stdout) do
204212
<<~REMOTE
205213
origin\t[email protected]:mbj/Mutant (fetch)
206214
origin\t[email protected]:mbj/Mutant (push)
@@ -227,7 +235,7 @@ def self.it_fails_with_message(expected)
227235
context 'on a one of many match' do
228236
let(:allowed_repositories) { %w[github.com/mbj/something github.com/mbj/mutant] }
229237

230-
let(:git_remote) do
238+
let(:git_remote_stdout) do
231239
<<~REMOTE
232240
origin\t[email protected]:mbj/mutant (fetch)
233241
origin\t[email protected]:mbj/mutant (push)
@@ -268,10 +276,24 @@ def self.it_fails_with_message(expected)
268276
end
269277

270278
shared_examples 'individual licenses' do
271-
let(:git_config_author) { "[email protected]\n" }
272-
let(:git_config_result) { Mutant::Either::Right.new(git_config_author) }
273-
let(:git_show_author) { "[email protected]\n" }
274-
let(:git_show_result) { Mutant::Either::Right.new(git_show_author) }
279+
let(:git_config_author) { "[email protected]\n" }
280+
let(:git_config_result) { Mutant::Either::Right.new(git_config_author_status) }
281+
let(:git_show_author) { "[email protected]\n" }
282+
let(:git_show_result) { Mutant::Either::Right.new(git_show_author_status) }
283+
284+
let(:git_config_author_status) do
285+
instance_double(
286+
Mutant::World::CommandStatus,
287+
stdout: git_config_author
288+
)
289+
end
290+
291+
let(:git_show_author_status) do
292+
instance_double(
293+
Mutant::World::CommandStatus,
294+
stdout: git_show_author
295+
)
296+
end
275297

276298
let(:licensed_authors) do
277299
%w[

spec/unit/mutant/repository/diff_spec.rb

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,28 @@
1616
)
1717
end
1818

19+
def mk_command_status_result(stdout)
20+
right(
21+
instance_double(
22+
Mutant::World::CommandStatus,
23+
stdout: stdout
24+
)
25+
)
26+
end
27+
1928
let(:raw_expectations) do
2029
[
2130
{
2231
receiver: world,
23-
selector: :capture_stdout,
32+
selector: :capture_command,
2433
arguments: [%w[git rev-parse --show-toplevel]],
25-
reaction: { return: Mutant::Either::Right.new("/foo\n") }
34+
reaction: { return: mk_command_status_result("/foo\n") }
2635
},
2736
{
2837
receiver: world,
29-
selector: :capture_stdout,
38+
selector: :capture_command,
3039
arguments: [%w[git diff-index to_rev]],
31-
reaction: { return: Mutant::Either::Right.new(index_stdout) }
40+
reaction: { return: mk_command_status_result(index_stdout) }
3241
},
3342
*file_diff_expectations
3443
]
@@ -83,9 +92,9 @@ def apply
8392
[
8493
{
8594
receiver: world,
86-
selector: :capture_stdout,
95+
selector: :capture_command,
8796
arguments: [%w[git diff --unified=0 to_rev -- /foo/bar.rb]],
88-
reaction: { return: Mutant::Either::Right.new(diff_stdout) }
97+
reaction: { return: mk_command_status_result(diff_stdout) }
8998
}
9099
]
91100
end

spec/unit/mutant/world_spec.rb

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,24 @@ def apply
5454
end
5555
end
5656

57-
describe '#capture_stdout' do
57+
describe '#capture_command' do
5858
def apply
59-
subject.capture_stdout(command)
59+
subject.capture_command(command)
6060
end
6161

62-
let(:open3) { class_double(Open3) }
63-
let(:stdout) { instance_double(String, :stdout) }
64-
let(:subject) { super().with(open3: open3) }
65-
let(:command) { %w[foo bar baz] }
62+
let(:open3) { class_double(Open3) }
63+
let(:stdout) { instance_double(String, :stdout) }
64+
let(:stderr) { instance_double(String, :stderr) }
65+
let(:subject) { super().with(open3: open3) }
66+
let(:command) { %w[foo bar baz] }
67+
68+
let(:command_status) do
69+
described_class::CommandStatus.new(
70+
process_status: process_status,
71+
stderr: stderr,
72+
stdout: stdout
73+
)
74+
end
6675

6776
let(:process_status) do
6877
instance_double(
@@ -72,22 +81,22 @@ def apply
7281
end
7382

7483
before do
75-
allow(open3).to receive_messages(capture2: [stdout, process_status])
84+
allow(open3).to receive_messages(capture3: [stdout, stderr, process_status])
7685
end
7786

7887
context 'when process exists successful' do
7988
let(:success?) { true }
8089

8190
it 'returns stdout' do
82-
expect(apply).to eql(Mutant::Either::Right.new(stdout))
91+
expect(apply).to eql(right(command_status))
8392
end
8493
end
8594

8695
context 'when process exists unsuccessful' do
8796
let(:success?) { false }
8897

8998
it 'returns stdout' do
90-
expect(apply).to eql(Mutant::Either::Left.new("Command #{command.inspect} failed!"))
99+
expect(apply).to eql(Mutant::Either::Left.new(command_status))
91100
end
92101
end
93102
end

0 commit comments

Comments
 (0)