From 31b1ed32328390ed5f126bc26d3a3821559c1b08 Mon Sep 17 00:00:00 2001 From: rupayon123 <80724680+rupayon123@users.noreply.github.com> Date: Sat, 13 Jun 2026 18:57:04 -0400 Subject: [PATCH 1/5] fix: log debug output for failed commands --- lib/rubyshell/debugger.rb | 32 ++++++++++++++++++++++---------- lib/rubyshell/error.rb | 2 +- spec/debugger_spec.rb | 16 ++++++++++++++++ 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/lib/rubyshell/debugger.rb b/lib/rubyshell/debugger.rb index 6313f74..c5f6133 100644 --- a/lib/rubyshell/debugger.rb +++ b/lib/rubyshell/debugger.rb @@ -5,27 +5,39 @@ 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) + RubyShell.log(<<~TEXT + \nExecuted: #{command.to_shell.chomp} + Duration: #{format("%.6f", duration)}s + Pid: #{status.respond_to?(:pid) ? status.pid : ""} + Exit code: #{status.respond_to?(:exitstatus) ? status.exitstatus : status.to_i} + Stdout: #{stdout.inspect} + Stderr: #{stderr.inspect} + TEXT + ) + end end end end 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..2c10b7f 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 @@ -84,6 +90,16 @@ def subject_method sh.echo("hello", _debug: true) end it_behaves_like "a logged command", "hello" + + it "logs failed commands before reraising" do + expect do + sh.ruby("-e", "\"STDERR.write(%q{bad}); exit 1\"", _debug: true) + end.to raise_error(RubyShell::CommandError) + + expect(log_output.join).to include("Exit code: 1") + expect(log_output.join).to include("Stdout: \"\"") + expect(log_output.join).to include("Stderr: \"bad\"") + end end context "when debug mode is enabled globally" do From 704dae2be5edb555c611ba6d1a9e3a750476b04c Mon Sep 17 00:00:00 2001 From: Rupayon Haldar <80724680+rupayon123@users.noreply.github.com> Date: Fri, 19 Jun 2026 16:35:09 -0400 Subject: [PATCH 2/5] test: cover rescued debug command errors --- spec/debugger_spec.rb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/spec/debugger_spec.rb b/spec/debugger_spec.rb index 2c10b7f..b6fc2f7 100644 --- a/spec/debugger_spec.rb +++ b/spec/debugger_spec.rb @@ -100,6 +100,23 @@ def subject_method expect(log_output.join).to include("Stdout: \"\"") expect(log_output.join).to include("Stderr: \"bad\"") end + + it "keeps error attributes available when rescued outside the wrapper" do + error = nil + + begin + sh.ruby("-e", "\"STDOUT.write(%q{out}); STDERR.write(%q{bad}); exit 1\"", _debug: true) + rescue RubyShell::CommandError => e + error = e + end + + expect(error).to be_a(RubyShell::CommandError) + expect(error.command.to_shell.chomp).to include("ruby") + expect(error.command.to_shell.chomp).to include("STDOUT.write") + expect(error.stdout).to eq("out") + expect(error.stderr).to eq("bad") + expect(error.status.exitstatus).to eq(1) + end end context "when debug mode is enabled globally" do @@ -120,4 +137,4 @@ def subject_method end end end -end +end \ No newline at end of file From 533670acc91ec0c2a781c8dea4601e192cceb327 Mon Sep 17 00:00:00 2001 From: Rupayon Haldar <80724680+rupayon123@users.noreply.github.com> Date: Mon, 22 Jun 2026 20:34:34 -0400 Subject: [PATCH 3/5] fix: avoid shell conversion in rescued command spec --- spec/debugger_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/debugger_spec.rb b/spec/debugger_spec.rb index b6fc2f7..e60a8a1 100644 --- a/spec/debugger_spec.rb +++ b/spec/debugger_spec.rb @@ -111,8 +111,8 @@ def subject_method end expect(error).to be_a(RubyShell::CommandError) - expect(error.command.to_shell.chomp).to include("ruby") - expect(error.command.to_shell.chomp).to include("STDOUT.write") + expect(error.command.to_s).to include("ruby") + expect(error.command.to_s).to include("STDOUT.write") expect(error.stdout).to eq("out") expect(error.stderr).to eq("bad") expect(error.status.exitstatus).to eq(1) @@ -137,4 +137,4 @@ def subject_method end end end -end \ No newline at end of file +end From 5e602aba6e1ad5ee616d079b70d34c165f2efba5 Mon Sep 17 00:00:00 2001 From: Rupayon Haldar <80724680+rupayon123@users.noreply.github.com> Date: Mon, 29 Jun 2026 08:22:59 -0400 Subject: [PATCH 4/5] fix: satisfy debugger rubocop warnings --- lib/rubyshell/debugger.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/rubyshell/debugger.rb b/lib/rubyshell/debugger.rb index c5f6133..b208499 100644 --- a/lib/rubyshell/debugger.rb +++ b/lib/rubyshell/debugger.rb @@ -28,10 +28,12 @@ def run_wrapper(command, debug: nil) 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: #{status.respond_to?(:pid) ? status.pid : ""} + Pid: #{pid} Exit code: #{status.respond_to?(:exitstatus) ? status.exitstatus : status.to_i} Stdout: #{stdout.inspect} Stderr: #{stderr.inspect} @@ -40,4 +42,4 @@ def log_command(command, duration, status, stdout, stderr) end end end -end +end \ No newline at end of file From c3ba42d7a4fef6cc6aec9beeb414775a798357d5 Mon Sep 17 00:00:00 2001 From: Rupayon Haldar <80724680+rupayon123@users.noreply.github.com> Date: Mon, 29 Jun 2026 08:23:00 -0400 Subject: [PATCH 5/5] fix: satisfy debugger rubocop warnings --- spec/debugger_spec.rb | 58 ++++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/spec/debugger_spec.rb b/spec/debugger_spec.rb index e60a8a1..ffd667d 100644 --- a/spec/debugger_spec.rb +++ b/spec/debugger_spec.rb @@ -89,33 +89,61 @@ 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 "logs failed commands before reraising" do + 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 error attributes available when rescued outside the wrapper" do - error = nil + it "keeps the rescued error type available" do + expect(rescued_failed_debug_command).to be_a(RubyShell::CommandError) + end - begin - sh.ruby("-e", "\"STDOUT.write(%q{out}); STDERR.write(%q{bad}); exit 1\"", _debug: true) - rescue RubyShell::CommandError => e - error = e - 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 - expect(error).to be_a(RubyShell::CommandError) - expect(error.command.to_s).to include("ruby") - expect(error.command.to_s).to include("STDOUT.write") - expect(error.stdout).to eq("out") - expect(error.stderr).to eq("bad") - expect(error.status.exitstatus).to eq(1) + it "keeps the rescued error status available" do + expect(rescued_failed_debug_command.status.exitstatus).to eq(1) end end @@ -137,4 +165,4 @@ def subject_method end end end -end +end \ No newline at end of file