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 <ndimiduk@apache.org> Signed-off-by: stack <stack@apache.org>
This commit is contained in:
parent
6789aca9a0
commit
98e35842eb
|
@ -174,16 +174,20 @@ def debug?
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# For backwards compatibility, this will export all the HBase shell commands, constants, and
|
# 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".
|
# instance variables (@hbase and @shell) onto Ruby's top-level receiver object known as "main".
|
||||||
@shell.export_all(self) if top_level_definitions
|
@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
|
# 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'.
|
# 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
|
# 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
|
if interactive
|
||||||
# Output a banner message that tells users where to go for help
|
# Output a banner message that tells users where to go for help
|
||||||
|
|
|
@ -297,11 +297,11 @@ For more on the HBase Shell, see http://hbase.apache.org/book.html
|
||||||
return @irb_workspace unless @irb_workspace.nil?
|
return @irb_workspace unless @irb_workspace.nil?
|
||||||
|
|
||||||
hbase_receiver = HBaseReceiver.new
|
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
|
# install all the IRB commands onto our receiver
|
||||||
IRB::ExtendCommandBundle.extend_object(hbase_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)
|
::IRB::WorkSpace.new(hbase_receiver.get_binding)
|
||||||
end
|
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
|
# 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
|
# this case, we are using the binding constructed with all the HBase shell constants and
|
||||||
# methods.
|
# 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'
|
require 'irb/ruby-lex'
|
||||||
# Mixing HBaseIOExtensions into IO allows us to pass IO objects to RubyLex.
|
# Mixing HBaseIOExtensions into IO allows us to pass IO objects to RubyLex.
|
||||||
IO.include HBaseIOExtensions
|
IO.include HBaseIOExtensions
|
||||||
|
@ -320,10 +323,20 @@ For more on the HBase Shell, see http://hbase.apache.org/book.html
|
||||||
scanner = RubyLex.new
|
scanner = RubyLex.new
|
||||||
scanner.set_input(io)
|
scanner.set_input(io)
|
||||||
|
|
||||||
begin
|
|
||||||
scanner.each_top_level_statement do |statement, linenum|
|
scanner.each_top_level_statement do |statement, linenum|
|
||||||
puts(workspace.evaluate(nil, statement, 'stdin', linenum))
|
puts(workspace.evaluate(nil, statement, filename, linenum))
|
||||||
end
|
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
|
||||||
|
yield
|
||||||
rescue Exception => e
|
rescue Exception => e
|
||||||
message = e.to_s
|
message = e.to_s
|
||||||
# exception unwrapping in shell means we'll have to handle Java exceptions
|
# exception unwrapping in shell means we'll have to handle Java exceptions
|
||||||
|
@ -335,13 +348,10 @@ 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
|
# Include the 'ERROR' string to try to make transition easier for scripts that
|
||||||
# may have already been relying on grepping output.
|
# may have already been relying on grepping output.
|
||||||
puts "ERROR #{e.class}: #{message}"
|
puts "ERROR #{e.class}: #{message}"
|
||||||
if $fullBacktrace
|
raise e unless hide_traceback
|
||||||
# re-raising the will include a backtrace and exit.
|
|
||||||
raise e
|
|
||||||
else
|
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
end
|
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -114,6 +114,7 @@ class ShellTest < Test::Unit::TestCase
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
define_test 'Shell::Shell#eval_io should evaluate IO' do
|
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
|
# check that at least one of the commands is present while evaluating
|
||||||
io = StringIO.new <<~EOF
|
io = StringIO.new <<~EOF
|
||||||
puts (respond_to? :list)
|
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
|
# check that at least one of the HBase constants is present while evaluating
|
||||||
io = StringIO.new <<~EOF
|
io = StringIO.new <<~EOF
|
||||||
ROWPREFIXFILTER
|
ROWPREFIXFILTER.dump
|
||||||
EOF
|
EOF
|
||||||
output = capture_stdout { @shell.eval_io(io) }
|
output = capture_stdout { @shell.eval_io(io) }
|
||||||
assert_match(/"ROWPREFIXFILTER"/, output)
|
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
|
define_test 'Shell::Shell#print_banner should display Reference Guide link' do
|
||||||
@shell.interactive = true
|
@shell.interactive = true
|
||||||
output = capture_stdout { @shell.print_banner }
|
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
|
# incorrect number of arguments
|
||||||
@shell.command('create', 'nothrow_table')
|
@shell.command('create', 'nothrow_table')
|
||||||
@shell.command('create', 'nothrow_table', 'family_1')
|
@shell.command('create', 'nothrow_table', 'family_1')
|
||||||
|
|
|
@ -82,9 +82,14 @@ if java.lang.System.get_property('shell.test')
|
||||||
puts "Only running tests that match #{shell_test_pattern}"
|
puts "Only running tests that match #{shell_test_pattern}"
|
||||||
runner_args << "--testcase=#{shell_test_pattern}"
|
runner_args << "--testcase=#{shell_test_pattern}"
|
||||||
end
|
end
|
||||||
|
begin
|
||||||
# first couple of args are to match the defaults, so we can pass options to limit the tests run
|
# 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))
|
unless Test::Unit::AutoRunner.run(false, nil, runner_args)
|
||||||
raise "Shell unit tests failed. Check output file for details."
|
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
|
end
|
||||||
|
|
||||||
puts "Done with tests! Shutting down the cluster..."
|
puts "Done with tests! Shutting down the cluster..."
|
||||||
|
|
Loading…
Reference in New Issue