You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by bu...@apache.org on 2020/10/14 02:38:29 UTC

[hbase] 02/04: HBASE-24806 Small Updates to Functionality of Shell IRB Workspace (#2232)

This is an automated email from the ASF dual-hosted git repository.

busbey pushed a commit to branch branch-2
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit 29bbc7de45058343d55a16ed8b4da2c558992d7b
Author: Elliot <el...@apple.com>
AuthorDate: Tue Aug 18 16:14:34 2020 -0400

    HBASE-24806 Small Updates to Functionality of Shell IRB Workspace (#2232)
    
    * HBASE-24806 Small Updates to Functionality of Shell IRB Workspace
    
    - Move exception handler from Shell::Shell#eval_io to new method,
      Shell::Shell#exception_handler
    - Add unit tests for Shell::Shell#exception_handler
    - Change Shell::Shell#eval_io to no longer raise SystemExit when any error is
      seen and update unit test
    - Update ruby test runner to catch SystemExit and fail to avoid tests that
      cause the test runner to incorrectly exit successfully
    - Add Hbase::Loader module to find ruby scripts in the $LOAD_PATH and classpath
      using JRuby's loader.
    - In hbase-shell, install IRB commands before exporting HBase commands. The
      HBase commands will override the IRB commands, and no warning will be
      printed.
    
    * Remove unused variables from shell_test
    
    Signed-off-by: Nick Dimiduk <nd...@apache.org>
    Signed-off-by: stack <st...@apache.org>
    (cherry picked from commit 98e35842ebeb9a419df37a1c3166b4468b85a6bc)
---
 bin/hirb.rb                                     |  8 +++-
 hbase-shell/src/main/ruby/shell.rb              | 36 ++++++++++------
 hbase-shell/src/main/ruby/shell/hbase_loader.rb | 56 +++++++++++++++++++++++++
 hbase-shell/src/test/ruby/shell/shell_test.rb   | 24 ++++++++++-
 hbase-shell/src/test/ruby/tests_runner.rb       | 11 +++--
 5 files changed, 115 insertions(+), 20 deletions(-)

diff --git a/bin/hirb.rb b/bin/hirb.rb
index 5b502f4..7b1b8f1 100644
--- a/bin/hirb.rb
+++ b/bin/hirb.rb
@@ -174,16 +174,20 @@ def debug?
   nil
 end
 
+
 # For backwards compatibility, this will export all the HBase shell commands, constants, and
 # instance variables (@hbase and @shell) onto Ruby's top-level receiver object known as "main".
 @shell.export_all(self) if top_level_definitions
 
 # If script2run, try running it.  If we're in interactive mode, will go on to run the shell unless
 # script calls 'exit' or 'exit 0' or 'exit errcode'.
-@shell.eval_io(File.new(script2run)) if script2run
+require 'shell/hbase_loader'
+if script2run
+  ::Shell::Shell.exception_handler(!$fullBackTrace) { @shell.eval_io(Hbase::Loader.file_for_load(script2run), filename = script2run) }
+end
 
 # If we are not running interactively, evaluate standard input
-@shell.eval_io(STDIN) unless interactive
+::Shell::Shell.exception_handler(!$fullBackTrace) { @shell.eval_io(STDIN) } unless interactive
 
 if interactive
   # Output a banner message that tells users where to go for help
diff --git a/hbase-shell/src/main/ruby/shell.rb b/hbase-shell/src/main/ruby/shell.rb
index b6cc8b0..96b7fe2 100644
--- a/hbase-shell/src/main/ruby/shell.rb
+++ b/hbase-shell/src/main/ruby/shell.rb
@@ -297,11 +297,11 @@ For more on the HBase Shell, see http://hbase.apache.org/book.html
       return @irb_workspace unless @irb_workspace.nil?
 
       hbase_receiver = HBaseReceiver.new
-      # Install all the hbase commands, constants, and instance variables @shell and @hbase. This
-      # must be BEFORE the irb commands are installed so that our help command is not overwritten.
-      export_all(hbase_receiver)
       # install all the IRB commands onto our receiver
       IRB::ExtendCommandBundle.extend_object(hbase_receiver)
+      # Install all the hbase commands, constants, and instance variables @shell and @hbase. This
+      # will override names that conflict with IRB methods like "help".
+      export_all(hbase_receiver)
       ::IRB::WorkSpace.new(hbase_receiver.get_binding)
     end
 
@@ -311,7 +311,10 @@ For more on the HBase Shell, see http://hbase.apache.org/book.html
     # Unlike Ruby's require or load, this method allows us to execute code with a custom binding. In
     # this case, we are using the binding constructed with all the HBase shell constants and
     # methods.
-    def eval_io(io)
+    #
+    # @param [IO] io instance of Ruby's IO (or subclass like File) to read script from
+    # @param [String] filename to print in tracebacks
+    def eval_io(io, filename = 'stdin')
       require 'irb/ruby-lex'
       # Mixing HBaseIOExtensions into IO allows us to pass IO objects to RubyLex.
       IO.include HBaseIOExtensions
@@ -320,10 +323,20 @@ For more on the HBase Shell, see http://hbase.apache.org/book.html
       scanner = RubyLex.new
       scanner.set_input(io)
 
+      scanner.each_top_level_statement do |statement, linenum|
+        puts(workspace.evaluate(nil, statement, filename, linenum))
+      end
+      nil
+    end
+
+    ##
+    # Runs a block and logs exception from both Ruby and Java, optionally discarding the traceback
+    #
+    # @param [Boolean] hide_traceback if true, Exceptions will be converted to
+    #   a SystemExit so that the traceback is not printed
+    def self.exception_handler(hide_traceback)
       begin
-        scanner.each_top_level_statement do |statement, linenum|
-          puts(workspace.evaluate(nil, statement, 'stdin', linenum))
-        end
+        yield
       rescue Exception => e
         message = e.to_s
         # exception unwrapping in shell means we'll have to handle Java exceptions
@@ -335,12 +348,9 @@ For more on the HBase Shell, see http://hbase.apache.org/book.html
         # Include the 'ERROR' string to try to make transition easier for scripts that
         # may have already been relying on grepping output.
         puts "ERROR #{e.class}: #{message}"
-        if $fullBacktrace
-          # re-raising the will include a backtrace and exit.
-          raise e
-        else
-          exit 1
-        end
+        raise e unless hide_traceback
+
+        exit 1
       end
       nil
     end
diff --git a/hbase-shell/src/main/ruby/shell/hbase_loader.rb b/hbase-shell/src/main/ruby/shell/hbase_loader.rb
new file mode 100644
index 0000000..2ad2ea9
--- /dev/null
+++ b/hbase-shell/src/main/ruby/shell/hbase_loader.rb
@@ -0,0 +1,56 @@
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+module Hbase
+  ##
+  # HBase::Loader serves a similar purpose to IRB::IrbLoader, but with a different separation of
+  # concerns. This loader allows us to directly get the path for a filename in ruby's load path,
+  # and then use that in combination with something like HBase::Shell#eval_io.
+  module Loader
+    ##
+    # Determine the loadable path for a given filename by searching through $LOAD_PATH
+    #
+    # This serves a similar purpose to IRB::IrbLoader#search_file_from_ruby_path, but uses JRuby's
+    # loader, which allows us to find special paths like "uri:classloader" inside of a Jar.
+    #
+    # @param [String] filename
+    # @return [String] path
+    def self.path_for_load(filename)
+      return File.absolute_path(filename) if File.exist? filename
+
+      # Get JRuby's LoadService from the global (singleton) instance of the runtime
+      # (org.jruby.Ruby), which allows us to use JRuby's tools for searching the load path.
+      runtime = org.jruby.Ruby.getGlobalRuntime
+      loader = runtime.getLoadService
+      search_state = loader.findFileForLoad filename
+      raise LoadError, "no such file to load -- #{filename}" if search_state.library.nil?
+
+      search_state.loadName
+    end
+
+    ##
+    # Return a file handle for the given file found in the load path
+    #
+    # @param [String] filename
+    # @return [File] file handle
+    def self.file_for_load(filename)
+      File.new(path_for_load(filename))
+    end
+  end
+end
diff --git a/hbase-shell/src/test/ruby/shell/shell_test.rb b/hbase-shell/src/test/ruby/shell/shell_test.rb
index c8f6807..5d3b07a 100644
--- a/hbase-shell/src/test/ruby/shell/shell_test.rb
+++ b/hbase-shell/src/test/ruby/shell/shell_test.rb
@@ -114,6 +114,7 @@ class ShellTest < Test::Unit::TestCase
   #-----------------------------------------------------------------------------
 
   define_test 'Shell::Shell#eval_io should evaluate IO' do
+    StringIO.include HBaseIOExtensions
     # check that at least one of the commands is present while evaluating
     io = StringIO.new <<~EOF
       puts (respond_to? :list)
@@ -123,7 +124,7 @@ class ShellTest < Test::Unit::TestCase
 
     # check that at least one of the HBase constants is present while evaluating
     io = StringIO.new <<~EOF
-      ROWPREFIXFILTER
+      ROWPREFIXFILTER.dump
     EOF
     output = capture_stdout { @shell.eval_io(io) }
     assert_match(/"ROWPREFIXFILTER"/, output)
@@ -131,6 +132,25 @@ class ShellTest < Test::Unit::TestCase
 
   #-----------------------------------------------------------------------------
 
+  define_test 'Shell::Shell#exception_handler should hide traceback' do
+    class TestException < RuntimeError; end
+    # When hide_traceback is true, exception_handler should replace exceptions
+    # with SystemExit so that the traceback is not printed.
+    assert_raises(SystemExit) do
+      ::Shell::Shell.exception_handler(true) { raise TestException, 'Custom Exception' }
+    end
+  end
+
+  define_test 'Shell::Shell#exception_handler should show traceback' do
+    class TestException < RuntimeError; end
+    # When hide_traceback is false, exception_handler should re-raise Exceptions
+    assert_raises(TestException) do
+      ::Shell::Shell.exception_handler(false) { raise TestException, 'Custom Exception' }
+    end
+  end
+
+  #-----------------------------------------------------------------------------
+
   define_test 'Shell::Shell#print_banner should display Reference Guide link' do
     @shell.interactive = true
     output = capture_stdout { @shell.print_banner }
@@ -141,7 +161,7 @@ class ShellTest < Test::Unit::TestCase
 
   #-----------------------------------------------------------------------------
 
-  define_test "Shell::Shell interactive mode should not throw" do
+  define_test 'Shell::Shell interactive mode should not throw' do
     # incorrect number of arguments
     @shell.command('create', 'nothrow_table')
     @shell.command('create', 'nothrow_table', 'family_1')
diff --git a/hbase-shell/src/test/ruby/tests_runner.rb b/hbase-shell/src/test/ruby/tests_runner.rb
index 73d4a6e..b0a0aaf 100644
--- a/hbase-shell/src/test/ruby/tests_runner.rb
+++ b/hbase-shell/src/test/ruby/tests_runner.rb
@@ -82,9 +82,14 @@ if java.lang.System.get_property('shell.test')
   puts "Only running tests that match #{shell_test_pattern}"
   runner_args << "--testcase=#{shell_test_pattern}"
 end
-# first couple of args are to match the defaults, so we can pass options to limit the tests run
-if !(Test::Unit::AutoRunner.run(false, nil, runner_args))
-  raise "Shell unit tests failed. Check output file for details."
+begin
+  # first couple of args are to match the defaults, so we can pass options to limit the tests run
+  unless Test::Unit::AutoRunner.run(false, nil, runner_args)
+    raise 'Shell unit tests failed. Check output file for details.'
+  end
+rescue SystemExit => e
+  # Unit tests should not raise uncaught SystemExit exceptions. This could cause tests to be ignored.
+  raise 'Caught SystemExit during unit test execution! Check output file for details.'
 end
 
 puts "Done with tests! Shutting down the cluster..."