ruby/test/irb/test_debugger_integration.rb

514 строки
11 KiB
Ruby

# frozen_string_literal: true
require "tempfile"
require "tmpdir"
require_relative "helper"
module TestIRB
class DebuggerIntegrationTest < IntegrationTestCase
def setup
super
if RUBY_ENGINE == 'truffleruby'
omit "This test runs with ruby/debug, which doesn't work with truffleruby"
end
@envs.merge!("NO_COLOR" => "true", "RUBY_DEBUG_HISTORY_FILE" => '')
end
def test_backtrace
write_ruby <<~'RUBY'
def foo
binding.irb
end
foo
RUBY
output = run_ruby_file do
type "backtrace"
type "exit!"
end
assert_match(/irb\(main\):001> backtrace/, output)
assert_match(/Object#foo at #{@ruby_file.to_path}/, output)
end
def test_debug
write_ruby <<~'ruby'
binding.irb
puts "hello"
ruby
output = run_ruby_file do
type "debug"
type "next"
type "continue"
end
assert_match(/irb\(main\):001> debug/, output)
assert_match(/irb:rdbg\(main\):002> next/, output)
assert_match(/=> 2\| puts "hello"/, output)
end
def test_debug_command_only_runs_once
write_ruby <<~'ruby'
binding.irb
ruby
output = run_ruby_file do
type "debug"
type "debug"
type "continue"
end
assert_match(/irb\(main\):001> debug/, output)
assert_match(/irb:rdbg\(main\):002> debug/, output)
assert_match(/IRB is already running with a debug session/, output)
end
def test_debug_command_can_only_be_called_from_binding_irb
write_ruby <<~'ruby'
require "irb"
# trick test framework
puts "binding.irb"
IRB.start
ruby
output = run_ruby_file do
type "debug"
type "exit"
end
assert_include(output, "Debugging commands are only available when IRB is started with binding.irb")
end
def test_next
write_ruby <<~'ruby'
binding.irb
puts "hello"
ruby
output = run_ruby_file do
type "next"
type "continue"
end
assert_match(/irb\(main\):001> next/, output)
assert_match(/=> 2\| puts "hello"/, output)
end
def test_break
write_ruby <<~'RUBY'
binding.irb
puts "Hello"
RUBY
output = run_ruby_file do
type "break 2"
type "continue"
type "continue"
end
assert_match(/irb\(main\):001> break/, output)
assert_match(/=> 2\| puts "Hello"/, output)
end
def test_delete
write_ruby <<~'RUBY'
binding.irb
puts "Hello"
binding.irb
puts "World"
RUBY
output = run_ruby_file do
type "break 4"
type "continue"
type "delete 0"
type "continue"
end
assert_match(/irb:rdbg\(main\):003> delete/, output)
assert_match(/deleted: #0 BP - Line/, output)
end
def test_step
write_ruby <<~'RUBY'
def foo
puts "Hello"
end
binding.irb
foo
RUBY
output = run_ruby_file do
type "step"
type "step"
type "continue"
end
assert_match(/irb\(main\):001> step/, output)
assert_match(/=> 5\| foo/, output)
assert_match(/=> 2\| puts "Hello"/, output)
end
def test_long_stepping
write_ruby <<~'RUBY'
class Foo
def foo(num)
bar(num + 10)
end
def bar(num)
num
end
end
binding.irb
Foo.new.foo(100)
RUBY
output = run_ruby_file do
type "step"
type "step"
type "step"
type "step"
type "num"
type "continue"
end
assert_match(/irb\(main\):001> step/, output)
assert_match(/irb:rdbg\(main\):002> step/, output)
assert_match(/irb:rdbg\(#<Foo:.*>\):003> step/, output)
assert_match(/irb:rdbg\(#<Foo:.*>\):004> step/, output)
assert_match(/irb:rdbg\(#<Foo:.*>\):005> num/, output)
assert_match(/=> 110/, output)
end
def test_continue
write_ruby <<~'RUBY'
binding.irb
puts "Hello"
binding.irb
puts "World"
RUBY
output = run_ruby_file do
type "continue"
type "continue"
end
assert_match(/irb\(main\):001> continue/, output)
assert_match(/=> 3: binding.irb/, output)
assert_match(/irb:rdbg\(main\):002> continue/, output)
end
def test_finish
write_ruby <<~'RUBY'
def foo
binding.irb
puts "Hello"
end
foo
RUBY
output = run_ruby_file do
type "finish"
type "continue"
end
assert_match(/irb\(main\):001> finish/, output)
assert_match(/=> 4\| end/, output)
end
def test_info
write_ruby <<~'RUBY'
def foo
a = "He" + "llo"
binding.irb
end
foo
RUBY
output = run_ruby_file do
type "info"
type "continue"
end
assert_match(/irb\(main\):001> info/, output)
assert_match(/%self = main/, output)
assert_match(/a = "Hello"/, output)
end
def test_catch
write_ruby <<~'RUBY'
binding.irb
1 / 0
RUBY
output = run_ruby_file do
type "catch ZeroDivisionError"
type "continue"
type "continue"
end
assert_match(/irb\(main\):001> catch/, output)
assert_match(/Stop by #0 BP - Catch "ZeroDivisionError"/, output)
end
def test_exit
write_ruby <<~'RUBY'
binding.irb
puts "he" + "llo"
RUBY
output = run_ruby_file do
type "debug"
type "exit"
end
assert_match(/irb:rdbg\(main\):002>/, output)
assert_match(/hello/, output)
end
def test_force_exit
write_ruby <<~'RUBY'
binding.irb
puts "he" + "llo"
RUBY
output = run_ruby_file do
type "debug"
type "exit!"
end
assert_match(/irb:rdbg\(main\):002>/, output)
assert_not_match(/hello/, output)
end
def test_quit
write_ruby <<~'RUBY'
binding.irb
puts "he" + "llo"
RUBY
output = run_ruby_file do
type "debug"
type "quit!"
end
assert_match(/irb:rdbg\(main\):002>/, output)
assert_not_match(/hello/, output)
end
def test_prompt_line_number_continues
write_ruby <<~'ruby'
binding.irb
puts "Hello"
puts "World"
ruby
output = run_ruby_file do
type "123"
type "456"
type "next"
type "info"
type "next"
type "continue"
end
assert_match(/irb\(main\):003> next/, output)
assert_match(/irb:rdbg\(main\):004> info/, output)
assert_match(/irb:rdbg\(main\):005> next/, output)
end
def test_prompt_irb_name_is_kept
write_rc <<~RUBY
IRB.conf[:IRB_NAME] = "foo"
RUBY
write_ruby <<~'ruby'
binding.irb
puts "Hello"
ruby
output = run_ruby_file do
type "next"
type "continue"
end
assert_match(/foo\(main\):001> next/, output)
assert_match(/foo:rdbg\(main\):002> continue/, output)
end
def test_irb_commands_are_available_after_moving_around_with_the_debugger
write_ruby <<~'ruby'
class Foo
def bar
puts "bar"
end
end
binding.irb
Foo.new.bar
ruby
output = run_ruby_file do
# Due to the way IRB defines its commands, moving into the Foo instance from main is necessary for proper testing.
type "next"
type "step"
type "irb_info"
type "continue"
end
assert_include(output, "InputMethod: RelineInputMethod")
end
def test_irb_command_can_check_local_variables
write_ruby <<~'ruby'
binding.irb
ruby
output = run_ruby_file do
type "debug"
type 'foobar = IRB'
type "show_source foobar.start"
type "show_source = 'Foo'"
type "show_source + 'Bar'"
type "continue"
end
assert_include(output, "def start(ap_path = nil)")
assert_include(output, '"FooBar"')
end
def test_help_command_is_delegated_to_the_debugger
write_ruby <<~'ruby'
binding.irb
ruby
output = run_ruby_file do
type "debug"
type "help"
type "continue"
end
assert_include(output, "### Frame control")
end
def test_help_display_different_content_when_debugger_is_enabled
write_ruby <<~'ruby'
binding.irb
ruby
output = run_ruby_file do
type "debug"
type "help"
type "continue"
end
# IRB's commands should still be listed
assert_match(/help\s+List all available commands/, output)
# debug gem's commands should be appended at the end
assert_match(/Debugging \(from debug\.gem\)\s+### Control flow/, output)
end
def test_input_is_evaluated_in_the_context_of_the_current_thread
write_ruby <<~'ruby'
current_thread = Thread.current
binding.irb
ruby
output = run_ruby_file do
type "debug"
type '"Threads match: #{current_thread == Thread.current}"'
type "continue"
end
assert_match(/irb\(main\):001> debug/, output)
assert_match(/Threads match: true/, output)
end
def test_irb_switches_debugger_interface_if_debug_was_already_activated
write_ruby <<~'ruby'
require 'debug'
class Foo
def bar
puts "bar"
end
end
binding.irb
Foo.new.bar
ruby
output = run_ruby_file do
# Due to the way IRB defines its commands, moving into the Foo instance from main is necessary for proper testing.
type "next"
type "step"
type 'irb_info'
type "continue"
end
assert_match(/irb\(main\):001> next/, output)
assert_include(output, "InputMethod: RelineInputMethod")
end
def test_debugger_cant_be_activated_while_multi_irb_is_active
write_ruby <<~'ruby'
binding.irb
a = 1
ruby
output = run_ruby_file do
type "jobs"
type "next"
type "exit"
end
assert_match(/irb\(main\):001> jobs/, output)
assert_include(output, "Can't start the debugger when IRB is running in a multi-IRB session.")
end
def test_multi_irb_commands_are_not_available_after_activating_the_debugger
write_ruby <<~'ruby'
binding.irb
a = 1
ruby
output = run_ruby_file do
type "next"
type "jobs"
type "continue"
end
assert_match(/irb\(main\):001> next/, output)
assert_include(output, "Multi-IRB commands are not available when the debugger is enabled.")
end
def test_irb_passes_empty_input_to_debugger_to_repeat_the_last_command
write_ruby <<~'ruby'
binding.irb
puts "foo"
puts "bar"
puts "baz"
ruby
output = run_ruby_file do
type "next"
type ""
# Test that empty input doesn't repeat expressions
type "123"
type ""
type "next"
type ""
type ""
end
assert_include(output, "=> 2\| puts \"foo\"")
assert_include(output, "=> 3\| puts \"bar\"")
assert_include(output, "=> 4\| puts \"baz\"")
end
end
end