Skip to content

Commit 534fcf5

Browse files
committed
chore: use ProcessExecuter.run instead of the implementing it in this gem
1 parent 629f3b6 commit 534fcf5

File tree

4 files changed

+85
-169
lines changed

4 files changed

+85
-169
lines changed

bin/command_line_test

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ class CommandLineParser
9191
option_parser.separator ''
9292
option_parser.separator 'Options:'
9393
%i[
94-
define_help_option define_stdout_option define_stderr_option
94+
define_help_option define_stdout_option define_stdout_file_option
95+
define_stderr_option define_stderr_file_option
9596
define_exitstatus_option define_signal_option define_duration_option
9697
].each { |m| send(m) }
9798
end
@@ -116,6 +117,15 @@ class CommandLineParser
116117
end
117118
end
118119

120+
# Define the stdout-file option
121+
# @return [void]
122+
# @api private
123+
def define_stdout_file_option
124+
option_parser.on('--stdout-file="file"', 'Send contents of file to stdout') do |filename|
125+
@stdout = File.read(filename)
126+
end
127+
end
128+
119129
# Define the stderr option
120130
# @return [void]
121131
# @api private
@@ -125,6 +135,15 @@ class CommandLineParser
125135
end
126136
end
127137

138+
# Define the stderr-file option
139+
# @return [void]
140+
# @api private
141+
def define_stderr_file_option
142+
option_parser.on('--stderr-file="file"', 'Send contents of file to stderr') do |filename|
143+
@stderr = File.read(filename)
144+
end
145+
end
146+
128147
# Define the exitstatus option
129148
# @return [void]
130149
# @api private

lib/git/command_line.rb

Lines changed: 49 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,14 @@ def initialize(env, binary_path, global_opts, logger)
189189
#
190190
# @raise [Git::TimeoutError] if the command times out
191191
#
192-
def run(*args, out:, err:, normalize:, chomp:, merge:, chdir: nil, timeout: nil)
192+
def run(*args, out: nil, err: nil, normalize:, chomp:, merge:, chdir: nil, timeout: nil)
193193
git_cmd = build_git_cmd(args)
194-
out ||= StringIO.new
195-
err ||= (merge ? out : StringIO.new)
196-
status = execute(git_cmd, out, err, chdir: (chdir || :not_set), timeout: timeout)
197-
198-
process_result(git_cmd, status, out, err, normalize, chomp, timeout)
194+
begin
195+
result = ProcessExecuter.run(env, *git_cmd, out: out, err: err, merge:, chdir: (chdir || :not_set), timeout: timeout, raise_errors: false)
196+
rescue ProcessExecuter::Command::ProcessIOError => e
197+
raise Git::ProcessIOError.new(e.message), cause: e.exception.cause
198+
end
199+
process_result(result, normalize, chomp, timeout)
199200
end
200201

201202
private
@@ -210,121 +211,12 @@ def build_git_cmd(args)
210211
[binary_path, *global_opts, *args].map { |e| e.to_s }
211212
end
212213

213-
# Determine the output to return in the `CommandLineResult`
214-
#
215-
# If the writer can return the output by calling `#string` (such as a StringIO),
216-
# then return the result of normalizing the encoding and chomping the output
217-
# as requested.
218-
#
219-
# If the writer does not support `#string`, then return nil. The output is
220-
# assumed to be collected by the writer itself such as when the writer
221-
# is a file instead of a StringIO.
222-
#
223-
# @param writer [#string] the writer to post-process
224-
#
225-
# @return [String, nil]
226-
#
227-
# @api private
228-
#
229-
def post_process(writer, normalize, chomp)
230-
if writer.respond_to?(:string)
231-
output = writer.string.dup
232-
output = output.lines.map { |l| Git::EncodingUtils.normalize_encoding(l) }.join if normalize
233-
output.chomp! if chomp
234-
output
235-
else
236-
nil
237-
end
238-
end
239-
240-
# Post-process all writers and return an array of the results
241-
#
242-
# @param writers [Array<#write>] the writers to post-process
243-
# @param normalize [Boolean] whether to normalize the output of each writer
244-
# @param chomp [Boolean] whether to chomp the output of each writer
245-
#
246-
# @return [Array<String, nil>] the output of each writer that supports `#string`
247-
#
248-
# @api private
249-
#
250-
def post_process_all(writers, normalize, chomp)
251-
Array.new.tap do |result|
252-
writers.each { |writer| result << post_process(writer, normalize, chomp) }
253-
end
254-
end
255-
256-
# Raise an error when there was exception while collecting the subprocess output
257-
#
258-
# @param git_cmd [Array<String>] the git command that was executed
259-
# @param pipe_name [Symbol] the name of the pipe that raised the exception
260-
# @param pipe [ProcessExecuter::MonitoredPipe] the pipe that raised the exception
261-
#
262-
# @raise [Git::ProcessIOError]
263-
#
264-
# @return [void] this method always raises an error
265-
#
266-
# @api private
267-
#
268-
def raise_pipe_error(git_cmd, pipe_name, pipe)
269-
raise Git::ProcessIOError.new("Pipe Exception for #{git_cmd}: #{pipe_name}"), cause: pipe.exception
270-
end
271-
272-
# Execute the git command and collect the output
273-
#
274-
# @param cmd [Array<String>] the git command to execute
275-
# @param chdir [String] the directory to run the command in
276-
# @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
277-
#
278-
# If timeout is zero of nil, the command will not time out. If the command
279-
# times out, it is killed via a SIGKILL signal and `Git::TimeoutError` is raised.
280-
#
281-
# If the command does not respond to SIGKILL, it will hang this method.
282-
#
283-
# @raise [Git::ProcessIOError] if an exception was raised while collecting subprocess output
284-
# @raise [Git::TimeoutError] if the command times out
285-
#
286-
# @return [ProcessExecuter::Status] the status of the completed subprocess
287-
#
288-
# @api private
289-
#
290-
def spawn(cmd, out_writers, err_writers, chdir:, timeout:)
291-
out_pipe = ProcessExecuter::MonitoredPipe.new(*out_writers, chunk_size: 10_000)
292-
err_pipe = ProcessExecuter::MonitoredPipe.new(*err_writers, chunk_size: 10_000)
293-
ProcessExecuter.spawn(env, *cmd, out: out_pipe, err: err_pipe, chdir: chdir, timeout: timeout)
294-
ensure
295-
out_pipe.close
296-
err_pipe.close
297-
raise_pipe_error(cmd, :stdout, out_pipe) if out_pipe.exception
298-
raise_pipe_error(cmd, :stderr, err_pipe) if err_pipe.exception
299-
end
300-
301-
# The writers that will be used to collect stdout and stderr
302-
#
303-
# Additional writers could be added here if you wanted to tee output
304-
# or send output to the terminal.
305-
#
306-
# @param out [#write] the object to write stdout to
307-
# @param err [#write] the object to write stderr to
308-
#
309-
# @return [Array<Array<#write>, Array<#write>>] the writers for stdout and stderr
310-
#
311-
# @api private
312-
#
313-
def writers(out, err)
314-
out_writers = [out]
315-
err_writers = [err]
316-
[out_writers, err_writers]
317-
end
318-
319214
# Process the result of the command and return a Git::CommandLineResult
320215
#
321216
# Post process output, log the command and result, and raise an error if the
322217
# command failed.
323218
#
324-
# @param git_cmd [Array<String>] the git command that was executed
325-
# @param status [Process::Status] the status of the completed subprocess
326-
# @param out [#write] the object that stdout was written to
327-
# @param err [#write] the object that stderr was written to
219+
# @param result [ProcessExecuter::Command::Result] the result it is a Process::Status and include command, stdout, and stderr
328220
# @param normalize [Boolean] whether to normalize the output of each writer
329221
# @param chomp [Boolean] whether to chomp the output of each writer
330222
# @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
@@ -338,40 +230,58 @@ def writers(out, err)
338230
#
339231
# @api private
340232
#
341-
def process_result(git_cmd, status, out, err, normalize, chomp, timeout)
342-
out_str, err_str = post_process_all([out, err], normalize, chomp)
343-
logger.info { "#{git_cmd} exited with status #{status}" }
344-
logger.debug { "stdout:\n#{out_str.inspect}\nstderr:\n#{err_str.inspect}" }
345-
Git::CommandLineResult.new(git_cmd, status, out_str, err_str).tap do |result|
346-
raise Git::TimeoutError.new(result, timeout) if status.timeout?
347-
raise Git::SignaledError.new(result) if status.signaled?
348-
raise Git::FailedError.new(result) unless status.success?
233+
def process_result(result, normalize, chomp, timeout)
234+
command = result.command
235+
processed_out, processed_err = post_process_all([result.stdout, result.stderr], normalize, chomp)
236+
logger.info { "#{command} exited with status #{result}" }
237+
logger.debug { "stdout:\n#{processed_out.inspect}\nstderr:\n#{processed_err.inspect}" }
238+
Git::CommandLineResult.new(command, result, processed_out, processed_err).tap do |processed_result|
239+
raise Git::TimeoutError.new(processed_result, timeout) if result.timeout?
240+
raise Git::SignaledError.new(processed_result) if result.signaled?
241+
raise Git::FailedError.new(processed_result) unless result.success?
349242
end
350243
end
351244

352-
# Execute the git command and write the command output to out and err
245+
# Post-process command output and return an array of the results
353246
#
354-
# @param git_cmd [Array<String>] the git command to execute
355-
# @param out [#write] the object to write stdout to
356-
# @param err [#write] the object to write stderr to
357-
# @param chdir [String] the directory to run the command in
358-
# @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
247+
# @param raw_outputs [Array] the output to post-process
248+
# @param normalize [Boolean] whether to normalize the output of each writer
249+
# @param chomp [Boolean] whether to chomp the output of each writer
359250
#
360-
# If timeout is zero of nil, the command will not time out. If the command
361-
# times out, it is killed via a SIGKILL signal and `Git::TimeoutError` is raised.
251+
# @return [Array<String, nil>] the processed output of each command output object that supports `#string`
362252
#
363-
# If the command does not respond to SIGKILL, it will hang this method.
253+
# @api private
364254
#
365-
# @raise [Git::ProcessIOError] if an exception was raised while collecting subprocess output
366-
# @raise [Git::TimeoutError] if the command times out
255+
def post_process_all(raw_outputs, normalize, chomp)
256+
Array.new.tap do |result|
257+
raw_outputs.each { |raw_output| result << post_process(raw_output, normalize, chomp) }
258+
end
259+
end
260+
261+
# Determine the output to return in the `CommandLineResult`
367262
#
368-
# @return [Git::CommandLineResult] the result of the command to return to the caller
263+
# If the writer can return the output by calling `#string` (such as a StringIO),
264+
# then return the result of normalizing the encoding and chomping the output
265+
# as requested.
266+
#
267+
# If the writer does not support `#string`, then return nil. The output is
268+
# assumed to be collected by the writer itself such as when the writer
269+
# is a file instead of a StringIO.
270+
#
271+
# @param raw_output [#string] the output to post-process
272+
# @return [String, nil]
369273
#
370274
# @api private
371275
#
372-
def execute(git_cmd, out, err, chdir:, timeout:)
373-
out_writers, err_writers = writers(out, err)
374-
spawn(git_cmd, out_writers, err_writers, chdir: chdir, timeout: timeout)
276+
def post_process(raw_output, normalize, chomp)
277+
if raw_output.respond_to?(:string)
278+
output = raw_output.string.dup
279+
output = output.lines.map { |l| Git::EncodingUtils.normalize_encoding(l) }.join if normalize
280+
output.chomp! if chomp
281+
output
282+
else
283+
nil
284+
end
375285
end
376286
end
377287
end

tests/units/test_command_line.rb

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,10 @@ def merge
9494
args = ['--stdout=stdout output', '--stderr=stderr output']
9595
result = command_line.run(*args, out: out_writer, err: err_writer, normalize: normalize, chomp: chomp, merge: merge)
9696

97-
assert_equal(['ruby', 'bin/command_line_test', '--stdout=stdout output', '--stderr=stderr output'], result.git_cmd)
97+
assert_equal([{}, 'ruby', 'bin/command_line_test', '--stdout=stdout output', '--stderr=stderr output'], result.git_cmd)
9898
assert_equal('stdout output', result.stdout.chomp)
9999
assert_equal('stderr output', result.stderr.chomp)
100-
assert(result.status.is_a? ProcessExecuter::Status)
100+
assert(result.status.is_a? ProcessExecuter::Command::Result)
101101
assert_equal(0, result.status.exitstatus)
102102
end
103103

@@ -111,7 +111,7 @@ def merge
111111
# The error raised should include the result of the command
112112
result = error.result
113113

114-
assert_equal(['ruby', 'bin/command_line_test', '--exitstatus=1', '--stdout=O1', '--stderr=O2'], result.git_cmd)
114+
assert_equal([{}, 'ruby', 'bin/command_line_test', '--exitstatus=1', '--stdout=O1', '--stderr=O2'], result.git_cmd)
115115
assert_equal('O1', result.stdout.chomp)
116116
assert_equal('O2', result.stderr.chomp)
117117
assert_equal(1, result.status.exitstatus)
@@ -130,7 +130,7 @@ def merge
130130
# The error raised should include the result of the command
131131
result = error.result
132132

133-
assert_equal(['ruby', 'bin/command_line_test', '--signal=9', '--stdout=O1', '--stderr=O2'], result.git_cmd)
133+
assert_equal([{}, 'ruby', 'bin/command_line_test', '--signal=9', '--stdout=O1', '--stderr=O2'], result.git_cmd)
134134
# If stdout is buffered, it may not be flushed when the process is killed
135135
# assert_equal('O1', result.stdout.chomp)
136136
assert_equal('O2', result.stderr.chomp)
@@ -149,14 +149,7 @@ def merge
149149

150150
test "run should normalize output if normalize is true" do
151151
command_line = Git::CommandLine.new(env, binary_path, global_opts, logger)
152-
args = ['--stdout=stdout output']
153-
154-
def command_line.spawn(cmd, out_writers, err_writers, chdir: nil, timeout: nil)
155-
out_writers.each { |w| w.write(File.read('tests/files/encoding/test1.txt')) }
156-
`true`
157-
ProcessExecuter::Status.new($?, false, nil) # return status
158-
end
159-
152+
args = ['--stdout-file=tests/files/encoding/test1.txt']
160153
normalize = true
161154
result = command_line.run(*args, out: out_writer, err: err_writer, normalize: normalize, chomp: chomp, merge: merge)
162155

@@ -167,28 +160,22 @@ def command_line.spawn(cmd, out_writers, err_writers, chdir: nil, timeout: nil)
167160
Φεθγιατ θρβανιτασ ρεπριμιqθε
168161
OUTPUT
169162

170-
assert_equal(expected_output, result.stdout)
163+
assert_equal(expected_output, result.stdout.delete("\r"))
171164
end
172165

173166
test "run should NOT normalize output if normalize is false" do
174167
command_line = Git::CommandLine.new(env, binary_path, global_opts, logger)
175-
args = ['--stdout=stdout output']
176-
177-
def command_line.spawn(cmd, out_writers, err_writers, chdir: nil, timeout: nil)
178-
out_writers.each { |w| w.write(File.read('tests/files/encoding/test1.txt')) }
179-
`true`
180-
ProcessExecuter::Status.new($?, false, nil) # return status
181-
end
182-
168+
args = ['--stdout-file=tests/files/encoding/test1.txt']
183169
normalize = false
184170
result = command_line.run(*args, out: out_writer, err: err_writer, normalize: normalize, chomp: chomp, merge: merge)
185171

186-
expected_output = <<~OUTPUT
187-
\xCB\xEF\xF1\xE5\xEC \xE9\xF0\xF3\xE8\xEC \xE4\xEF\xEB\xEF\xF1 \xF3\xE9\xF4
188-
\xC7\xE9\xF3 \xE5\xEE \xF4\xEF\xF4\xE1 \xF3\xE8\xE1v\xE9\xF4\xE1\xF4\xE5
189-
\xCD\xEF \xE8\xF1\xE2\xE1\xED\xE9\xF4\xE1\xF3
190-
\xD6\xE5\xE8\xE3\xE9\xE1\xF4 \xE8\xF1\xE2\xE1\xED\xE9\xF4\xE1\xF3 \xF1\xE5\xF0\xF1\xE9\xEC\xE9q\xE8\xE5
191-
OUTPUT
172+
eol = RUBY_PLATFORM =~ /mswin|mingw/ ? "\r\n" : "\n"
173+
174+
expected_output =
175+
"\xCB\xEF\xF1\xE5\xEC \xE9\xF0\xF3\xE8\xEC \xE4\xEF\xEB\xEF\xF1 \xF3\xE9\xF4#{eol}" \
176+
"\xC7\xE9\xF3 \xE5\xEE \xF4\xEF\xF4\xE1 \xF3\xE8\xE1v\xE9\xF4\xE1\xF4\xE5#{eol}" \
177+
"\xCD\xEF \xE8\xF1\xE2\xE1\xED\xE9\xF4\xE1\xF3#{eol}" \
178+
"\xD6\xE5\xE8\xE3\xE9\xE1\xF4 \xE8\xF1\xE2\xE1\xED\xE9\xF4\xE1\xF3 \xF1\xE5\xF0\xF1\xE9\xEC\xE9q\xE8\xE5#{eol}"
192179

193180
assert_equal(expected_output, result.stdout)
194181
end

tests/units/test_logger.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def test_logger
2828

2929
logc = File.read(log_path)
3030

31-
expected_log_entry = /INFO -- : \["git", "(?<global_options>.*?)", "branch", "-a"/
31+
expected_log_entry = /INFO -- : \[\{[^}]+}, "git", "(?<global_options>.*?)", "branch", "-a"/
3232
assert_match(expected_log_entry, logc, missing_log_entry)
3333

3434
expected_log_entry = /DEBUG -- : stdout:\n" cherry/
@@ -47,7 +47,7 @@ def test_logging_at_info_level_should_not_show_debug_messages
4747

4848
logc = File.read(log_path)
4949

50-
expected_log_entry = /INFO -- : \["git", "(?<global_options>.*?)", "branch", "-a"/
50+
expected_log_entry = /INFO -- : \[\{[^}]+}, "git", "(?<global_options>.*?)", "branch", "-a"/
5151
assert_match(expected_log_entry, logc, missing_log_entry)
5252

5353
expected_log_entry = /DEBUG -- : stdout:\n" cherry/

0 commit comments

Comments
 (0)