mirror of
https://github.com/pry/pry.git
synced 2022-11-09 12:35:05 -05:00
495 lines
15 KiB
Ruby
495 lines
15 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
describe Pry do
|
|
before do
|
|
@str_output = StringIO.new
|
|
end
|
|
|
|
describe ".configure" do
|
|
it "yields a block with Pry.config as its argument" do
|
|
Pry.config.foo = nil
|
|
Pry.configure do |config|
|
|
config.foo = "bar"
|
|
end
|
|
expect(Pry.config.foo).to eq("bar")
|
|
end
|
|
end
|
|
|
|
describe "Exotic object support" do
|
|
# regression test for exotic object support
|
|
it "Should not error when return value is a BasicObject instance" do
|
|
ReplTester.start do
|
|
expect(input('BasicObject.new')).to match(/^=> #<BasicObject:/)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'DISABLE_PRY' do
|
|
before do
|
|
allow(Pry::Env).to receive(:[])
|
|
allow(Pry::Env).to receive(:[]).with('DISABLE_PRY').and_return(true)
|
|
end
|
|
|
|
it 'should not binding.pry' do
|
|
expect(binding.pry).to eq nil # rubocop:disable Lint/Debugger
|
|
end
|
|
|
|
it 'should not Pry.start' do
|
|
expect(Pry.start).to eq nil
|
|
end
|
|
end
|
|
|
|
describe 'FAIL_PRY' do
|
|
before do
|
|
allow(Pry::Env).to receive(:[])
|
|
allow(Pry::Env).to receive(:[]).with('FAIL_PRY').and_return(true)
|
|
end
|
|
|
|
it 'should raise an error for binding.pry' do
|
|
expect { binding.pry }.to raise_error(RuntimeError) # rubocop:disable Lint/Debugger
|
|
end
|
|
|
|
it 'should raise an error for Pry.start' do
|
|
expect { Pry.start }.to raise_error(RuntimeError)
|
|
end
|
|
end
|
|
|
|
describe "Pry.critical_section" do
|
|
it "should prevent Pry being called" do
|
|
output = StringIO.new
|
|
Pry.config.output = output
|
|
Pry.critical_section do
|
|
Pry.start
|
|
end
|
|
expect(output.string).to match(/Pry started inside Pry/)
|
|
end
|
|
end
|
|
|
|
describe "Pry.binding_for" do
|
|
# regression test for burg's bug (see git history)
|
|
it "Should not error when object doesn't have a valid == method" do
|
|
o = Object.new
|
|
def o.==(_other)
|
|
raise
|
|
end
|
|
|
|
expect { Pry.binding_for(o) }.to_not raise_error
|
|
end
|
|
|
|
it "should not leak local variables" do
|
|
[Object.new, Array, 3].each do |obj|
|
|
expect(Pry.binding_for(obj).eval("local_variables")).to be_empty
|
|
end
|
|
end
|
|
|
|
it "should work on frozen objects" do
|
|
a = "hello".freeze
|
|
expect(Pry.binding_for(a).eval("self")).to equal(a)
|
|
end
|
|
end
|
|
|
|
describe "#last_exception=" do
|
|
before do
|
|
@pry = Pry.new binding: binding
|
|
@e = mock_exception "foo.rb:1"
|
|
end
|
|
|
|
it "returns an instance of Pry::LastException" do
|
|
@pry.last_exception = @e
|
|
expect(@pry.last_exception.wrapped_exception).to eq @e
|
|
end
|
|
|
|
it "returns a frozen exception" do
|
|
@pry.last_exception = @e.freeze
|
|
expect(@pry.last_exception).to be_frozen
|
|
end
|
|
|
|
it "returns an object who mirrors itself as the wrapped exception" do
|
|
@pry.last_exception = @e.freeze
|
|
expect(@pry.last_exception).to be_an_instance_of StandardError
|
|
end
|
|
end
|
|
|
|
describe "open a Pry session on an object" do
|
|
describe "rep" do
|
|
before do
|
|
class Hello
|
|
end
|
|
end
|
|
|
|
after do
|
|
Object.send(:remove_const, :Hello)
|
|
end
|
|
|
|
# bug fix for https://github.com/pry/pry/issues/93
|
|
it 'should not leak pry constants into Object namespace' do
|
|
expect { pry_eval(Object.new, "Command") }.to raise_error NameError
|
|
end
|
|
|
|
it 'should be able to operate inside the BasicObject class' do
|
|
pry_eval(BasicObject, ":foo", "Pad.obj = _")
|
|
expect(Pad.obj).to eq :foo
|
|
end
|
|
|
|
it 'should set an ivar on an object' do
|
|
o = Object.new
|
|
pry_eval(o, "@x = 10")
|
|
expect(o.instance_variable_get(:@x)).to eq 10
|
|
end
|
|
|
|
it 'should display error if Pry instance runs out of input' do
|
|
redirect_pry_io(StringIO.new, @str_output) do
|
|
Pry.start
|
|
end
|
|
expect(@str_output.string).to match(/Error: Pry ran out of things to read/)
|
|
end
|
|
|
|
it 'should make self evaluate to the receiver of the rep session' do
|
|
o = :john
|
|
expect(pry_eval(o, "self")).to eq o
|
|
end
|
|
|
|
it 'should define a nested class under Hello and not on top-level or Pry' do
|
|
mock_pry(Pry.binding_for(Hello), "class Nested", "end")
|
|
expect(Hello.const_defined?(:Nested)).to eq true
|
|
end
|
|
|
|
it(
|
|
'should suppress output if input ends in a ";" and is an Exception ' \
|
|
'object (single line)'
|
|
) do
|
|
expect(mock_pry("Exception.new;")).to eq ""
|
|
end
|
|
|
|
it 'should suppress output if input ends in a ";" (single line)' do
|
|
expect(mock_pry("x = 5;")).to eq ""
|
|
end
|
|
|
|
it 'should be able to evaluate exceptions normally' do
|
|
was_called = false
|
|
mock_pry("RuntimeError.new", exception_handler: proc { was_called = true })
|
|
expect(was_called).to eq false
|
|
end
|
|
|
|
it 'should notice when exceptions are raised' do
|
|
was_called = false
|
|
mock_pry("raise RuntimeError", exception_handler: proc { was_called = true })
|
|
expect(was_called).to eq true
|
|
end
|
|
|
|
it 'should not try to catch intended exceptions' do
|
|
expect { mock_pry("raise SystemExit") }.to raise_error SystemExit
|
|
# SIGTERM
|
|
expect { mock_pry("raise SignalException.new(15)") }
|
|
.to raise_error SignalException
|
|
end
|
|
|
|
describe "inside signal handler" do
|
|
before do
|
|
unless Signal.list.key?('USR1')
|
|
skip "Host OS #{RbConfig::CONFIG['host_os']} doesn't support signal USR1"
|
|
end
|
|
|
|
if Pry::Helpers::Platform.jruby?
|
|
skip "jruby allows mutex usage in signal handlers"
|
|
end
|
|
|
|
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.0.0")
|
|
skip "pre-2.0 mri allows mutex usage in signal handlers"
|
|
end
|
|
|
|
trap("USR1") { @str_output = mock_pry }
|
|
end
|
|
|
|
after do
|
|
trap("USR1") do
|
|
# do nothing
|
|
end
|
|
end
|
|
|
|
it "should return with error message" do
|
|
expect(mock_pry('1 + 1')).to eql("=> 2\n")
|
|
Process.kill("USR1", Process.pid)
|
|
expect(@str_output).to match(/Unable to obtain mutex lock/)
|
|
end
|
|
end
|
|
|
|
describe "multi-line input" do
|
|
it "works" do
|
|
expect(mock_pry('x = ', '1 + 4')).to match(/5/)
|
|
end
|
|
|
|
it 'should suppress output if input ends in a ";" (multi-line)' do
|
|
expect(mock_pry('def self.blah', ':test', 'end;')).to eq ''
|
|
end
|
|
|
|
describe "newline stripping from an empty string" do
|
|
it "with double quotes" do
|
|
expect(mock_pry('"', '"')).to match(/"\\n"/)
|
|
expect(mock_pry('"', "\n", "\n", "\n", '"')).to match(/"\\n\\n\\n\\n"/)
|
|
end
|
|
|
|
it "with single quotes" do
|
|
expect(mock_pry("'", "'")).to match(/"\\n"/)
|
|
expect(mock_pry("'", "\n", "\n", "\n", "'")).to match(/"\\n\\n\\n\\n"/)
|
|
end
|
|
|
|
it "with fancy delimiters" do
|
|
expect(mock_pry('%(', ')')).to match(/"\\n"/)
|
|
expect(mock_pry('%|', "\n", "\n", '|')).to match(/"\\n\\n\\n"/)
|
|
expect(mock_pry('%q[', "\n", "\n", ']')).to match(/"\\n\\n\\n"/)
|
|
end
|
|
end
|
|
|
|
describe "newline stripping from an empty regexp" do
|
|
it "with regular regexp delimiters" do
|
|
expect(mock_pry('/', '/')).to match(%r{/\n/})
|
|
end
|
|
|
|
it "with fancy delimiters" do
|
|
expect(mock_pry('%r{', "\n", "\n", '}')).to match(%r{/\n\n\n/})
|
|
expect(mock_pry('%r<', "\n", '>')).to match(%r{/\n\n/})
|
|
end
|
|
end
|
|
|
|
describe "newline from an empty heredoc" do
|
|
it "works" do
|
|
expect(mock_pry('<<HERE', 'HERE')).to match(/""/)
|
|
expect(mock_pry("<<'HERE'", "\n", 'HERE')).to match(/"\\n"/)
|
|
expect(mock_pry("<<-'HERE'", "\n", "\n", 'HERE')).to match(/"\\n\\n"/)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "repl" do
|
|
describe "basic functionality" do
|
|
it 'should set an ivar on an object and exit the repl' do
|
|
input_strings = ["@x = 10", "exit-all"]
|
|
input = InputTester.new(*input_strings)
|
|
|
|
o = Object.new
|
|
|
|
Pry.start(o, input: input, output: StringIO.new)
|
|
|
|
expect(o.instance_variable_get(:@x)).to eq 10
|
|
end
|
|
end
|
|
|
|
describe "complete_expression?" do
|
|
it "should not mutate the input!" do
|
|
clean = "puts <<-FOO\nhi\nFOO\n"
|
|
a = clean.dup
|
|
Pry::Code.complete_expression?(a)
|
|
expect(a).to eq clean
|
|
end
|
|
end
|
|
|
|
describe "history arrays" do
|
|
it 'sets _ to the last result' do
|
|
t = pry_tester
|
|
t.eval ":foo"
|
|
expect(t.eval("_")).to eq :foo
|
|
t.eval "42"
|
|
expect(t.eval("_")).to eq 42
|
|
end
|
|
|
|
it 'sets out to an array with the result' do
|
|
t = pry_tester
|
|
t.eval ":foo"
|
|
t.eval "42"
|
|
res = t.eval "_out_"
|
|
|
|
expect(res).to be_a_kind_of(Pry::Ring)
|
|
expect(res[1..2]).to eq [:foo, 42]
|
|
end
|
|
|
|
it 'sets _in_ to an array with the entered lines' do
|
|
t = pry_tester
|
|
t.eval ":foo"
|
|
t.eval "42"
|
|
res = t.eval "_in_"
|
|
|
|
expect(res).to be_a_kind_of(Pry::Ring)
|
|
expect(res[1..2]).to eq [":foo\n", "42\n"]
|
|
end
|
|
|
|
it 'uses 100 as the size of _in_ and _out_' do
|
|
expect(pry_tester.eval("[_in_.max_size, _out_.max_size]")).to eq [100, 100]
|
|
end
|
|
|
|
it 'can change the size of the history arrays' do
|
|
expect(pry_tester(memory_size: 1000).eval("[_out_, _in_].map(&:max_size)"))
|
|
.to eq [1000, 1000]
|
|
end
|
|
|
|
it 'store exceptions' do
|
|
mock_pry("foo!", "Pad.in = _in_[-1]; Pad.out = _out_[-1]")
|
|
|
|
expect(Pad.in).to eq "foo!\n"
|
|
expect(Pad.out).to be_a_kind_of NoMethodError
|
|
end
|
|
end
|
|
|
|
describe "last_result" do
|
|
it "should be set to the most recent value" do
|
|
expect(pry_eval("2", "_ + 82")).to eq 84
|
|
end
|
|
|
|
# This test needs mock_pry because the command retvals work by
|
|
# replacing the eval_string, so _ won't be modified without Pry doing
|
|
# a REPL loop.
|
|
it "should be set to the result of a command with :keep_retval" do
|
|
Pry::Commands.block_command '++', '', keep_retval: true do |a|
|
|
a.to_i + 1
|
|
end
|
|
|
|
# rubocop:disable Lint/InterpolationCheck
|
|
expect(mock_pry('++ 86', '++ #{_}')).to match(/88/)
|
|
# rubocop:enable Lint/InterpolationCheck
|
|
end
|
|
|
|
it "should be preserved over an empty line" do
|
|
expect(pry_eval("2 + 2", " ", "\t", " ", "_ + 92")).to eq 96
|
|
end
|
|
|
|
it "should be preserved when evalling a command without :keep_retval" do
|
|
expect(pry_eval("2 + 2", "ls -l", "_ + 96")).to eq 100
|
|
end
|
|
end
|
|
|
|
describe "nesting" do
|
|
after do
|
|
Pry.reset_defaults
|
|
Pry.config.color = false
|
|
end
|
|
|
|
it 'should nest properly' do
|
|
Pry.config.input = InputTester.new(
|
|
"cd 1", "cd 2", "cd 3",
|
|
"\"nest:\#\{(pry_instance.binding_stack.size - 1)\}\"", "exit-all"
|
|
)
|
|
|
|
Pry.config.output = @str_output
|
|
|
|
o = Object.new
|
|
o.pry
|
|
|
|
expect(@str_output.string).to match(/nest:3/)
|
|
end
|
|
end
|
|
|
|
describe "defining methods" do
|
|
it(
|
|
'defines a method on the singleton class of an object when performing ' \
|
|
'"def meth;end" inside the object'
|
|
) do
|
|
[Object.new, {}, []].each do |val|
|
|
pry_eval(val, 'def hello; end')
|
|
expect(val.methods(false).map(&:to_sym).include?(:hello)).to eq true
|
|
end
|
|
end
|
|
|
|
it(
|
|
'defines an instance method on the module when performing ' \
|
|
'"def meth;end" inside the module'
|
|
) do
|
|
hello = Module.new
|
|
pry_eval(hello, "def hello; end")
|
|
expect(hello.instance_methods(false).map(&:to_sym).include?(:hello))
|
|
.to be_truthy
|
|
end
|
|
|
|
it(
|
|
'defines an instance method on the class when performing ' \
|
|
'"def meth;end" inside the class'
|
|
) do
|
|
hello = Class.new
|
|
pry_eval(hello, "def hello; end")
|
|
expect(hello.instance_methods(false).map(&:to_sym).include?(:hello))
|
|
.to be_truthy
|
|
end
|
|
|
|
it(
|
|
'defines a method on the class of an object when performing ' \
|
|
'"def meth;end" inside an immediate value or Numeric'
|
|
) do
|
|
# JRuby behaves different than CRuby here (seems it always has to some
|
|
# extent, see 'unless' below). It didn't seem trivial to work
|
|
# around. Skip for now.
|
|
skip "JRuby incompatibility" if Pry::Helpers::Platform.jruby?
|
|
[
|
|
:test, 0, true, false, nil, (0.0 unless Pry::Helpers::Platform.jruby?)
|
|
].each do |val|
|
|
pry_eval(val, "def hello; end")
|
|
expect(val.class.instance_methods(false).map(&:to_sym).include?(:hello))
|
|
.to be_truthy
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "Object#pry" do
|
|
after do
|
|
Pry.reset_defaults
|
|
Pry.config.color = false
|
|
end
|
|
|
|
it "should start a pry session on the receiver (first form)" do
|
|
Pry.config.input = InputTester.new("self", "exit-all")
|
|
|
|
str_output = StringIO.new
|
|
Pry.config.output = str_output
|
|
|
|
20.pry
|
|
|
|
expect(str_output.string).to match(/20/)
|
|
end
|
|
|
|
it "should start a pry session on the receiver (second form)" do
|
|
Pry.config.input = InputTester.new("self", "exit-all")
|
|
|
|
str_output = StringIO.new
|
|
Pry.config.output = str_output
|
|
|
|
pry 20
|
|
|
|
expect(str_output.string).to match(/20/)
|
|
end
|
|
|
|
it "should raise if more than two arguments are passed to Object#pry" do
|
|
expect { pry(20, :quiet, input: Readline) }.to raise_error ArgumentError
|
|
end
|
|
end
|
|
|
|
describe "Pry.binding_for" do
|
|
it 'should return TOPLEVEL_BINDING if parameter self is main' do
|
|
main = -> { TOPLEVEL_BINDING.eval('self') }
|
|
expect(Pry.binding_for(main.call).is_a?(Binding)).to eq true
|
|
expect(Pry.binding_for(main.call)).to eq TOPLEVEL_BINDING
|
|
expect(Pry.binding_for(main.call)).to eq Pry.binding_for(main.call)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'setting custom options' do
|
|
it 'does not raise for unrecognized options' do
|
|
expect { Pry.new(custom_option: 'custom value') }.to_not raise_error
|
|
end
|
|
|
|
it 'correctly handles the :quiet option (#1261)' do
|
|
instance = Pry.new(quiet: true)
|
|
expect(instance.quiet?).to eq true
|
|
end
|
|
end
|
|
|
|
describe "a fresh instance" do
|
|
it "should use `caller` as its backtrace" do
|
|
location = "#{__FILE__}:#{__LINE__ + 1}"[1..-1] # omit leading .
|
|
backtrace = Pry.new.backtrace
|
|
|
|
expect(backtrace).not_to equal nil
|
|
expect(backtrace.any? { |l| l.include?(location) }).to equal true
|
|
end
|
|
end
|
|
end
|