mirror of
https://github.com/pry/pry.git
synced 2022-11-09 12:35:05 -05:00
cd1a9bbe5e
Fixes #1824 (Enabling `# frozen_string_literal: true` in `~/.pryc` crashes most operations)
534 lines
17 KiB
Ruby
534 lines
17 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
describe Pry::Hooks do
|
|
before do
|
|
@hooks = Pry::Hooks.new
|
|
end
|
|
|
|
describe ".default" do
|
|
it "returns hooks with default before_session hook" do
|
|
hooks = described_class.default
|
|
expect(hooks.hook_exists?('before_session', :default)).to be_truthy
|
|
end
|
|
|
|
context "when pry instance is quiet" do
|
|
let(:pry_instance) { Pry.new(quiet: true) }
|
|
|
|
it "doesn't run the whereami command" do
|
|
expect(pry_instance).not_to receive(:run_command)
|
|
hooks = described_class.default
|
|
hooks.exec_hook(:before_session, StringIO.new, {}, pry_instance)
|
|
end
|
|
end
|
|
|
|
context "when pry instance is not quiet" do
|
|
let(:pry_instance) { Pry.new(quiet: false) }
|
|
let(:output) { StringIO.new }
|
|
|
|
it "runs the whereami command" do
|
|
expect(pry_instance).to receive(:run_command).with('whereami --quiet')
|
|
hooks = described_class.default
|
|
hooks.exec_hook(:before_session, StringIO.new, {}, pry_instance)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "adding a new hook" do
|
|
it 'should not execute hook while adding it' do
|
|
run = false
|
|
@hooks.add_hook(:test_hook, :my_name) { run = true }
|
|
expect(run).to eq false
|
|
end
|
|
|
|
it 'should not allow adding of a hook with a duplicate name' do
|
|
@hooks.add_hook(:test_hook, :my_name) {}
|
|
|
|
expect { @hooks.add_hook(:test_hook, :my_name) {} }.to raise_error ArgumentError
|
|
end
|
|
|
|
it 'should create a new hook with a block' do
|
|
@hooks.add_hook(:test_hook, :my_name) {}
|
|
expect(@hooks.hook_count(:test_hook)).to eq 1
|
|
end
|
|
|
|
it 'should create a new hook with a callable' do
|
|
@hooks.add_hook(:test_hook, :my_name, proc {})
|
|
expect(@hooks.hook_count(:test_hook)).to eq 1
|
|
end
|
|
|
|
it 'should use block if given both block and callable' do
|
|
run = false
|
|
foo = false
|
|
@hooks.add_hook(:test_hook, :my_name, proc { foo = true }) { run = true }
|
|
expect(@hooks.hook_count(:test_hook)).to eq 1
|
|
@hooks.exec_hook(:test_hook)
|
|
expect(run).to eq true
|
|
expect(foo).to eq false
|
|
end
|
|
|
|
it 'should raise if not given a block or any other object' do
|
|
expect { @hooks.add_hook(:test_hook, :my_name) }.to raise_error ArgumentError
|
|
end
|
|
|
|
it 'should create multiple hooks for an event' do
|
|
@hooks.add_hook(:test_hook, :my_name) {}
|
|
@hooks.add_hook(:test_hook, :my_name2) {}
|
|
expect(@hooks.hook_count(:test_hook)).to eq 2
|
|
end
|
|
|
|
it 'should return a count of 0 for an empty hook' do
|
|
expect(@hooks.hook_count(:test_hook)).to eq 0
|
|
end
|
|
end
|
|
|
|
describe "Pry::Hooks#merge" do
|
|
describe "merge!" do
|
|
it 'should merge in the Pry::Hooks' do
|
|
h1 = Pry::Hooks.new.add_hook(:test_hook, :testing) {}
|
|
h2 = Pry::Hooks.new
|
|
|
|
h2.merge!(h1)
|
|
expect(h2.get_hook(:test_hook, :testing)).to eq h1.get_hook(:test_hook, :testing)
|
|
end
|
|
|
|
it 'should not share merged elements with original' do
|
|
h1 = Pry::Hooks.new.add_hook(:test_hook, :testing) {}
|
|
h2 = Pry::Hooks.new
|
|
|
|
h2.merge!(h1)
|
|
h2.add_hook(:test_hook, :testing2) {}
|
|
expect(h2.get_hook(:test_hook, :testing2)).not_to eq(
|
|
h1.get_hook(:test_hook, :testing2)
|
|
)
|
|
end
|
|
|
|
it 'should NOT overwrite hooks belonging to shared event in receiver' do
|
|
h1 = Pry::Hooks.new.add_hook(:test_hook, :testing) {}
|
|
callable = proc {}
|
|
h2 = Pry::Hooks.new.add_hook(:test_hook, :testing2, callable)
|
|
|
|
h2.merge!(h1)
|
|
expect(h2.get_hook(:test_hook, :testing2)).to eq callable
|
|
end
|
|
|
|
it 'should overwrite identical hook in receiver' do
|
|
callable1 = proc { :one }
|
|
h1 = Pry::Hooks.new.add_hook(:test_hook, :testing, callable1)
|
|
callable2 = proc { :two }
|
|
h2 = Pry::Hooks.new.add_hook(:test_hook, :testing, callable2)
|
|
|
|
h2.merge!(h1)
|
|
expect(h2.get_hook(:test_hook, :testing)).to eq callable1
|
|
expect(h2.hook_count(:test_hook)).to eq 1
|
|
end
|
|
|
|
it 'should preserve hook order' do
|
|
name = ''
|
|
h1 = Pry::Hooks.new
|
|
h1.add_hook(:test_hook, :testing3) { name += "h" }
|
|
h1.add_hook(:test_hook, :testing4) { name += "n" }
|
|
|
|
h2 = Pry::Hooks.new
|
|
h2.add_hook(:test_hook, :testing1) { name += "j" }
|
|
h2.add_hook(:test_hook, :testing2) { name += "o" }
|
|
|
|
h2.merge!(h1)
|
|
h2.exec_hook(:test_hook)
|
|
|
|
expect(name).to eq "john"
|
|
end
|
|
|
|
describe "merge" do
|
|
it 'should return a fresh, independent instance' do
|
|
h1 = Pry::Hooks.new.add_hook(:test_hook, :testing) {}
|
|
h2 = Pry::Hooks.new
|
|
|
|
h3 = h2.merge(h1)
|
|
expect(h3).not_to eq h1
|
|
expect(h3).not_to eq h2
|
|
end
|
|
|
|
it 'should contain hooks from original instance' do
|
|
h1 = Pry::Hooks.new.add_hook(:test_hook, :testing) {}
|
|
h2 = Pry::Hooks.new.add_hook(:test_hook2, :testing) {}
|
|
|
|
h3 = h2.merge(h1)
|
|
expect(h3.get_hook(:test_hook, :testing)).to eq(
|
|
h1.get_hook(:test_hook, :testing)
|
|
)
|
|
expect(h3.get_hook(:test_hook2, :testing)).to eq(
|
|
h2.get_hook(:test_hook2, :testing)
|
|
)
|
|
end
|
|
|
|
it 'should not affect original instances when new hooks are added' do
|
|
h1 = Pry::Hooks.new.add_hook(:test_hook, :testing) {}
|
|
h2 = Pry::Hooks.new.add_hook(:test_hook2, :testing) {}
|
|
|
|
h3 = h2.merge(h1)
|
|
h3.add_hook(:test_hook3, :testing) {}
|
|
|
|
expect(h1.get_hook(:test_hook3, :testing)).to eq nil
|
|
expect(h2.get_hook(:test_hook3, :testing)).to eq nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "dupping a Pry::Hooks instance" do
|
|
it 'should share hooks with original' do
|
|
@hooks.add_hook(:test_hook, :testing) do
|
|
:none_such
|
|
end
|
|
|
|
hooks_dup = @hooks.dup
|
|
expect(hooks_dup.get_hook(:test_hook, :testing)).to eq(
|
|
@hooks.get_hook(:test_hook, :testing)
|
|
)
|
|
end
|
|
|
|
it 'adding a new event to dupped instance should not affect original' do
|
|
@hooks.add_hook(:test_hook, :testing) { :none_such }
|
|
hooks_dup = @hooks.dup
|
|
|
|
hooks_dup.add_hook(:other_test_hook, :testing) { :okay_man }
|
|
|
|
expect(hooks_dup.get_hook(:other_test_hook, :testing))
|
|
.not_to eq @hooks.get_hook(:other_test_hook, :testing)
|
|
end
|
|
|
|
it 'adding a new hook to dupped instance should not affect original' do
|
|
@hooks.add_hook(:test_hook, :testing) { :none_such }
|
|
hooks_dup = @hooks.dup
|
|
|
|
hooks_dup.add_hook(:test_hook, :testing2) { :okay_man }
|
|
|
|
expect(hooks_dup.get_hook(:test_hook, :testing2))
|
|
.not_to eq @hooks.get_hook(:test_hook, :testing2)
|
|
end
|
|
end
|
|
|
|
describe "getting hooks" do
|
|
describe "get_hook" do
|
|
it 'should return the correct requested hook' do
|
|
run1 = false
|
|
run2 = false
|
|
@hooks.add_hook(:test_hook, :my_name) { run1 = true }
|
|
@hooks.add_hook(:test_hook, :my_name2) { run2 = true }
|
|
@hooks.get_hook(:test_hook, :my_name).call
|
|
expect(run1).to eq true
|
|
expect(run2).to eq false
|
|
end
|
|
|
|
it 'should return nil if hook does not exist' do
|
|
expect(@hooks.get_hook(:test_hook, :my_name)).to eq nil
|
|
end
|
|
end
|
|
|
|
describe "get_hooks" do
|
|
it 'should return a hash of hook names/hook functions for an event' do
|
|
hook1 = proc { 1 }
|
|
hook2 = proc { 2 }
|
|
@hooks.add_hook(:test_hook, :my_name1, hook1)
|
|
@hooks.add_hook(:test_hook, :my_name2, hook2)
|
|
hash = @hooks.get_hooks(:test_hook)
|
|
expect(hash.size).to eq 2
|
|
expect(hash[:my_name1]).to eq hook1
|
|
expect(hash[:my_name2]).to eq hook2
|
|
end
|
|
|
|
it 'should return an empty hash if no hooks defined' do
|
|
expect(@hooks.get_hooks(:test_hook)).to eq({})
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "clearing all hooks for an event" do
|
|
it 'should clear all hooks' do
|
|
@hooks.add_hook(:test_hook, :my_name) {}
|
|
@hooks.add_hook(:test_hook, :my_name2) {}
|
|
@hooks.add_hook(:test_hook, :my_name3) {}
|
|
@hooks.clear_event_hooks(:test_hook)
|
|
expect(@hooks.hook_count(:test_hook)).to eq 0
|
|
end
|
|
end
|
|
|
|
describe "deleting a hook" do
|
|
it 'should successfully delete a hook' do
|
|
@hooks.add_hook(:test_hook, :my_name) {}
|
|
@hooks.delete_hook(:test_hook, :my_name)
|
|
expect(@hooks.hook_count(:test_hook)).to eq 0
|
|
end
|
|
|
|
it 'should return the deleted hook' do
|
|
run = false
|
|
@hooks.add_hook(:test_hook, :my_name) { run = true }
|
|
@hooks.delete_hook(:test_hook, :my_name).call
|
|
expect(run).to eq true
|
|
end
|
|
|
|
it 'should return nil if hook does not exist' do
|
|
expect(@hooks.delete_hook(:test_hook, :my_name)).to eq nil
|
|
end
|
|
end
|
|
|
|
describe "executing a hook" do
|
|
it 'should execute block hook' do
|
|
run = false
|
|
@hooks.add_hook(:test_hook, :my_name) { run = true }
|
|
@hooks.exec_hook(:test_hook)
|
|
expect(run).to eq true
|
|
end
|
|
|
|
it 'should execute proc hook' do
|
|
run = false
|
|
@hooks.add_hook(:test_hook, :my_name, proc { run = true })
|
|
@hooks.exec_hook(:test_hook)
|
|
expect(run).to eq true
|
|
end
|
|
|
|
it 'should execute a general callable hook' do
|
|
callable = Object.new.tap do |obj|
|
|
obj.instance_variable_set(:@test_var, nil)
|
|
class << obj
|
|
attr_accessor :test_var
|
|
def call
|
|
@test_var = true
|
|
end
|
|
end
|
|
end
|
|
|
|
@hooks.add_hook(:test_hook, :my_name, callable)
|
|
@hooks.exec_hook(:test_hook)
|
|
expect(callable.test_var).to eq true
|
|
end
|
|
|
|
it 'should execute all hooks for an event if more than one is defined' do
|
|
x = nil
|
|
y = nil
|
|
@hooks.add_hook(:test_hook, :my_name1) { y = true }
|
|
@hooks.add_hook(:test_hook, :my_name2) { x = true }
|
|
@hooks.exec_hook(:test_hook)
|
|
expect(x).to eq true
|
|
expect(y).to eq true
|
|
end
|
|
|
|
it 'should execute hooks in order' do
|
|
array = []
|
|
@hooks.add_hook(:test_hook, :my_name1) { array << 1 }
|
|
@hooks.add_hook(:test_hook, :my_name2) { array << 2 }
|
|
@hooks.add_hook(:test_hook, :my_name3) { array << 3 }
|
|
@hooks.exec_hook(:test_hook)
|
|
expect(array).to eq [1, 2, 3]
|
|
end
|
|
|
|
it 'return value of exec_hook should be that of last executed hook' do
|
|
@hooks.add_hook(:test_hook, :my_name1) { 1 }
|
|
@hooks.add_hook(:test_hook, :my_name2) { 2 }
|
|
@hooks.add_hook(:test_hook, :my_name3) { 3 }
|
|
expect(@hooks.exec_hook(:test_hook)).to eq 3
|
|
end
|
|
|
|
it 'should add exceptions to the errors array' do
|
|
@hooks.add_hook(:test_hook, :foo1) { raise 'one' }
|
|
@hooks.add_hook(:test_hook, :foo2) { raise 'two' }
|
|
@hooks.add_hook(:test_hook, :foo3) { raise 'three' }
|
|
@hooks.exec_hook(:test_hook)
|
|
expect(@hooks.errors.map(&:message)).to eq %w[one two three]
|
|
end
|
|
|
|
it 'should return the last exception raised as the return value' do
|
|
@hooks.add_hook(:test_hook, :foo1) { raise 'one' }
|
|
@hooks.add_hook(:test_hook, :foo2) { raise 'two' }
|
|
@hooks.add_hook(:test_hook, :foo3) { raise 'three' }
|
|
expect(@hooks.exec_hook(:test_hook)).to eq @hooks.errors.last
|
|
end
|
|
end
|
|
|
|
describe "integration tests" do
|
|
describe "when_started hook" do
|
|
it 'should yield options to the hook' do
|
|
options = nil
|
|
Pry.config.hooks.add_hook(:when_started, :test_hook) do |_target, opt, _|
|
|
options = opt
|
|
end
|
|
|
|
redirect_pry_io(StringIO.new("exit"), StringIO.new) do
|
|
Pry.start binding, hello: :baby
|
|
end
|
|
|
|
expect(options[:hello]).to eq :baby
|
|
|
|
Pry.config.hooks.delete_hook(:when_started, :test_hook)
|
|
end
|
|
|
|
describe "target" do
|
|
it 'should yield the target, as a binding ' do
|
|
b = nil
|
|
Pry.config.hooks.add_hook(:when_started, :test_hook) do |target, _opt, _|
|
|
b = target
|
|
end
|
|
|
|
redirect_pry_io(StringIO.new("exit"), StringIO.new) do
|
|
Pry.start 5, hello: :baby
|
|
end
|
|
|
|
expect(b.is_a?(Binding)).to eq true
|
|
Pry.config.hooks.delete_hook(:when_started, :test_hook)
|
|
end
|
|
|
|
it 'should yield the target to the hook' do
|
|
b = nil
|
|
Pry.config.hooks.add_hook(:when_started, :test_hook) do |target, _opt, _|
|
|
b = target
|
|
end
|
|
|
|
redirect_pry_io(StringIO.new("exit"), StringIO.new) do
|
|
Pry.start 5, hello: :baby
|
|
end
|
|
|
|
expect(b.eval('self')).to eq 5
|
|
Pry.config.hooks.delete_hook(:when_started, :test_hook)
|
|
end
|
|
end
|
|
|
|
it 'should allow overriding of target (and binding_stack)' do
|
|
o = Object.new
|
|
class << o; attr_accessor :value; end
|
|
|
|
Pry.config.hooks.add_hook(
|
|
:when_started, :test_hook
|
|
) do |_target, _opt, pry_instance|
|
|
pry_instance.binding_stack = [Pry.binding_for(o)]
|
|
end
|
|
|
|
redirect_pry_io(InputTester.new("@value = true", "exit-all")) do
|
|
Pry.start binding, hello: :baby
|
|
end
|
|
|
|
expect(o.value).to eq true
|
|
Pry.config.hooks.delete_hook(:when_started, :test_hook)
|
|
end
|
|
end
|
|
|
|
describe "after_session hook" do
|
|
it 'should always run, even if uncaught exception bubbles out of repl' do
|
|
o = OpenStruct.new
|
|
o.great_escape = Class.new(StandardError)
|
|
|
|
old_ew = Pry.config.unrescued_exceptions
|
|
Pry.config.unrescued_exceptions << o.great_escape
|
|
|
|
array = [1, 2, 3, 4, 5]
|
|
|
|
begin
|
|
redirect_pry_io(StringIO.new("raise great_escape"), StringIO.new) do
|
|
Pry.start(
|
|
o,
|
|
hooks: Pry::Hooks.new.add_hook(:after_session, :cleanup) { array = nil }
|
|
)
|
|
end
|
|
rescue StandardError => ex
|
|
exception = ex
|
|
end
|
|
|
|
# ensure that an exception really was raised and it broke out
|
|
# of the repl
|
|
expect(exception.is_a?(o.great_escape)).to eq true
|
|
|
|
# check that after_session hook ran
|
|
expect(array).to eq nil
|
|
|
|
# cleanup after test
|
|
Pry.config.unrescued_exceptions = old_ew
|
|
end
|
|
|
|
describe "before_eval hook" do
|
|
describe "modifying input code" do
|
|
it 'should replace input code with code determined by hook' do
|
|
hooks = Pry::Hooks.new.add_hook(:before_eval, :quirk) do |code, _pry|
|
|
code.replace(":little_duck")
|
|
end
|
|
redirect_pry_io(InputTester.new(":jemima", "exit-all"), out = StringIO.new) do
|
|
Pry.start(self, hooks: hooks)
|
|
end
|
|
expect(out.string).to match(/little_duck/)
|
|
expect(out.string).not_to match(/jemima/)
|
|
end
|
|
|
|
it 'should not interfere with command processing when replacing input code' do
|
|
commands = Pry::CommandSet.new do
|
|
import_from Pry::Commands, "exit-all"
|
|
|
|
command "how-do-you-like-your-blue-eyed-boy-now-mister-death" do
|
|
output.puts "in hours of bitterness i imagine balls of sapphire, of metal"
|
|
end
|
|
end
|
|
|
|
hooks = Pry::Hooks.new.add_hook(:before_eval, :quirk) do |code, _pry|
|
|
code.replace(":little_duck")
|
|
end
|
|
|
|
redirect_pry_io(
|
|
InputTester.new(
|
|
"how-do-you-like-your-blue-eyed-boy-now-mister-death", "exit-all"
|
|
),
|
|
out = StringIO.new
|
|
) do
|
|
Pry.start(self, hooks: hooks, commands: commands)
|
|
end
|
|
expect(out.string).to match(
|
|
/in hours of bitterness i imagine balls of sapphire, of metal/
|
|
)
|
|
expect(out.string).not_to match(/little_duck/)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "exceptions" do
|
|
before do
|
|
Pry.config.hooks.add_hook(:after_eval, :baddums) { raise "Baddums" }
|
|
Pry.config.hooks.add_hook(:after_eval, :simbads) { raise "Simbads" }
|
|
end
|
|
|
|
after do
|
|
Pry.config.hooks.delete_hook(:after_eval, :baddums)
|
|
Pry.config.hooks.delete_hook(:after_eval, :simbads)
|
|
end
|
|
it "should not raise exceptions" do
|
|
expect { mock_pry("1", "2", "3") }.to_not raise_error
|
|
end
|
|
|
|
it "should print out a notice for each exception raised" do
|
|
expect(mock_pry("1")).to match(
|
|
/after_eval\shook\sfailed:\sRuntimeError:\sBaddums\n
|
|
.*after_eval\shook\sfailed:\sRuntimeError:\sSimbads/xm
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "anonymous hooks" do
|
|
it 'should allow adding of hook without a name' do
|
|
@hooks.add_hook(:test_hook, nil) {}
|
|
expect(@hooks.hook_count(:test_hook)).to eq 1
|
|
end
|
|
|
|
it 'should only allow one anonymous hook to exist' do
|
|
@hooks.add_hook(:test_hook, nil) {}
|
|
@hooks.add_hook(:test_hook, nil) {}
|
|
expect(@hooks.hook_count(:test_hook)).to eq 1
|
|
end
|
|
|
|
it 'should execute most recently added anonymous hook' do
|
|
x = nil
|
|
y = nil
|
|
@hooks.add_hook(:test_hook, nil) { y = 1 }
|
|
@hooks.add_hook(:test_hook, nil) { x = 2 }
|
|
@hooks.exec_hook(:test_hook)
|
|
expect(y).to eq nil
|
|
expect(x).to eq 2
|
|
end
|
|
end
|
|
end
|