diff --git a/lib/rubyshell/debugger.rb b/lib/rubyshell/debugger.rb index 6313f74..b208499 100644 --- a/lib/rubyshell/debugger.rb +++ b/lib/rubyshell/debugger.rb @@ -5,27 +5,41 @@ module Debugger class << self def run_wrapper(command, debug: nil) if debug || RubyShell.debug? - time_one = Process.clock_gettime(Process::CLOCK_MONOTONIC) - result = yield + begin + result = yield + rescue RubyShell::CommandError => e + time_two = Process.clock_gettime(Process::CLOCK_MONOTONIC) + log_command(command, time_two - time_one, e.status, e.stdout, e.stderr) + raise + end time_two = Process.clock_gettime(Process::CLOCK_MONOTONIC) - RubyShell.log(<<~TEXT - \nExecuted: #{command.to_shell.chomp} - Duration: #{format("%.6f", time_two - time_one)}s - Pid: #{result.metadata[:exit_status].pid} - Exit code: #{result.metadata[:exit_status].to_i} - Stdout: #{result.to_s.inspect} - TEXT - ) + log_command(command, time_two - time_one, result.metadata[:exit_status], result.to_s, "") result else yield end end + + private + + def log_command(command, duration, status, stdout, stderr) + pid = status.pid if status.respond_to?(:pid) + + RubyShell.log(<<~TEXT + \nExecuted: #{command.to_shell.chomp} + Duration: #{format("%.6f", duration)}s + Pid: #{pid} + Exit code: #{status.respond_to?(:exitstatus) ? status.exitstatus : status.to_i} + Stdout: #{stdout.inspect} + Stderr: #{stderr.inspect} + TEXT + ) + end end end -end +end \ No newline at end of file diff --git a/lib/rubyshell/error.rb b/lib/rubyshell/error.rb index 5f6120e..8b1c1e2 100644 --- a/lib/rubyshell/error.rb +++ b/lib/rubyshell/error.rb @@ -2,7 +2,7 @@ module RubyShell class CommandError < StandardError - attr_reader :command, :status + attr_reader :command, :stdout, :stderr, :status def initialize(command:, stdout: "", stderr: "", status: "", message: nil) @command = command diff --git a/spec/debugger_spec.rb b/spec/debugger_spec.rb index 900f202..ffd667d 100644 --- a/spec/debugger_spec.rb +++ b/spec/debugger_spec.rb @@ -41,6 +41,12 @@ expect(log_output.join).to include("Stdout: \"#{expected_output}\"") end + + it "logs the stderr" do + subject_method + + expect(log_output.join).to include("Stderr: \"\"") + end end RSpec.shared_examples "a silent command" do @@ -83,7 +89,62 @@ def subject_method def subject_method sh.echo("hello", _debug: true) end + + def run_failed_debug_command + sh.ruby("-e", "\"STDERR.write(%q{bad}); exit 1\"", _debug: true) + rescue RubyShell::CommandError + nil + end + + def rescued_failed_debug_command + sh.ruby("-e", "\"STDOUT.write(%q{out}); STDERR.write(%q{bad}); exit 1\"", _debug: true) + nil + rescue RubyShell::CommandError => e + e + end + it_behaves_like "a logged command", "hello" + + it "reraises failed commands" do + expect do + sh.ruby("-e", "\"STDERR.write(%q{bad}); exit 1\"", _debug: true) + end.to raise_error(RubyShell::CommandError) + end + + it "logs failed command exit code before reraising" do + run_failed_debug_command + expect(log_output.join).to include("Exit code: 1") + end + + it "logs failed command stdout before reraising" do + run_failed_debug_command + expect(log_output.join).to include("Stdout: \"\"") + end + + it "logs failed command stderr before reraising" do + run_failed_debug_command + expect(log_output.join).to include("Stderr: \"bad\"") + end + + it "keeps the rescued error type available" do + expect(rescued_failed_debug_command).to be_a(RubyShell::CommandError) + end + + it "keeps the rescued error command available" do + expect(rescued_failed_debug_command.command.to_s).to include("STDOUT.write") + end + + it "keeps the rescued error stdout available" do + expect(rescued_failed_debug_command.stdout).to eq("out") + end + + it "keeps the rescued error stderr available" do + expect(rescued_failed_debug_command.stderr).to eq("bad") + end + + it "keeps the rescued error status available" do + expect(rescued_failed_debug_command.status.exitstatus).to eq(1) + end end context "when debug mode is enabled globally" do @@ -104,4 +165,4 @@ def subject_method end end end -end +end \ No newline at end of file