From b64febf42596e806ab05b466c72a21dda3d512d7 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Tue, 8 Sep 2026 17:21:25 +0900 Subject: [PATCH] HBASE-30370 Shell exits on errors other than NameError and SyntaxError HBASE-26741 made eval_input re-raise every exception to set an exit code, which also ended interactive sessions. Worked around since for NameError (HBASE-26880) and SyntaxError (HBASE-27726); every other error still killed the prompt. Re-raise only when the exit code is used: a script run, or -n. Everything else falls through to handle_exception, as before HBASE-26741. A script given as an argument runs with interactive still true, so detect it by the FileInputMethod that HBaseLoader.file_for_load returns rather than by @interactive alone. --- hbase-shell/src/main/ruby/irb/hirb.rb | 12 +++++----- .../test/ruby/shell/general_test_cluster.rb | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/hbase-shell/src/main/ruby/irb/hirb.rb b/hbase-shell/src/main/ruby/irb/hirb.rb index ae534f6f00b3..87a62551281b 100644 --- a/hbase-shell/src/main/ruby/irb/hirb.rb +++ b/hbase-shell/src/main/ruby/irb/hirb.rb @@ -51,6 +51,8 @@ def initialize(workspace = nil, interactive = true, input_method = nil) `stty icrnl <&2` end @interactive = interactive + # Errors must abort a script run to set the exit code, but not a live prompt + @exit_on_error = !interactive || input_method.is_a?(::IRB::FileInputMethod) super(workspace, input_method) ensure f.close @@ -161,17 +163,13 @@ def eval_input rescue Interrupt => exc rescue SystemExit, SignalException raise - rescue SyntaxError => exc - # HBASE-27726: Ignore SyntaxError to prevent exiting Shell on unexpected syntax. - raise exc unless @interactive - rescue NameError => exc - raise exc unless @interactive - # HBASE-26880: Ignore NameError to prevent exiting Shell on mistyped commands. rescue Exception => exc # HBASE-26741: Raise exception so Shell::exception_handler can catch it. # This modifies this copied method from JRuby so that the HBase shell can # manage the exception and set a proper exit code on the process. - raise exc + # Otherwise keep the session alive and report the error, as the shell did before + # HBASE-26741. Supersedes HBASE-26880 (NameError) and HBASE-27726 (SyntaxError). + raise if @exit_on_error else exc = nil next diff --git a/hbase-shell/src/test/ruby/shell/general_test_cluster.rb b/hbase-shell/src/test/ruby/shell/general_test_cluster.rb index 8c1687dea019..4c3195d00c3c 100644 --- a/hbase-shell/src/test/ruby/shell/general_test_cluster.rb +++ b/hbase-shell/src/test/ruby/shell/general_test_cluster.rb @@ -21,6 +21,7 @@ require 'hbase_shell' require 'irb/hirb' require 'stringio' +require 'tempfile' class ShellTest < Test::Unit::TestCase include Hbase::TestHelpers @@ -226,4 +227,25 @@ def readable_after_eof? assert_match(/WARN: 'scan' is a reserved HBase command/, err_output) assert_match(/WARN: 'processlist' is a reserved HBase command/, err_output) end + + def new_hirb(input_method) + IRB.setup(__FILE__) unless IRB.conf[:IRB_NAME] + IRB::HIRB.new(@shell.workspace, true, input_method) + end + + define_test 'Shell::Shell should keep an interactive session alive on any error' do + hirb = new_hirb(MockInputMethod.new(["1 + '2'\n", "my_var = 5\n"])) + capture_stdout { hirb.eval_input } + assert_equal(5, hirb.context.workspace.binding.local_variable_get(:my_var)) + end + + define_test 'Shell::Shell should abort a script on error even when interactive' do + Tempfile.create(['hirb_test', '.rb']) do |file| + file.write("1 + '2'\n") + file.close + # interactive is true, as it is for `hbase shell script.rb` without -n + hirb = new_hirb(IRB::HBaseLoader.file_for_load(file.path)) + assert_raise(TypeError) { capture_stdout { hirb.eval_input } } + end + end end