зеркало из https://github.com/github/ruby.git
514 строки
11 KiB
Ruby
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
|