summaryrefslogtreecommitdiff
path: root/test/irb
diff options
context:
space:
mode:
Diffstat (limited to 'test/irb')
-rw-r--r--test/irb/command/test_custom_command.rb149
-rw-r--r--test/irb/command/test_disable_irb.rb28
-rw-r--r--test/irb/command/test_force_exit.rb51
-rw-r--r--test/irb/command/test_help.rb75
-rw-r--r--test/irb/command/test_multi_irb_commands.rb50
-rw-r--r--test/irb/command/test_show_source.rb397
-rw-r--r--test/irb/helper.rb147
-rw-r--r--test/irb/test_color.rb5
-rw-r--r--test/irb/test_color_printer.rb5
-rw-r--r--test/irb/test_command.rb (renamed from test/irb/test_cmd.rb)460
-rw-r--r--test/irb/test_completion.rb276
-rw-r--r--test/irb/test_context.rb370
-rw-r--r--test/irb/test_debug_cmd.rb299
-rw-r--r--test/irb/test_debugger_integration.rb496
-rw-r--r--test/irb/test_eval_history.rb69
-rw-r--r--test/irb/test_evaluation.rb44
-rw-r--r--test/irb/test_helper_method.rb134
-rw-r--r--test/irb/test_history.rb410
-rw-r--r--test/irb/test_init.rb259
-rw-r--r--test/irb/test_input_method.rb116
-rw-r--r--test/irb/test_irb.rb924
-rw-r--r--test/irb/test_nesting_parser.rb341
-rw-r--r--test/irb/test_option.rb2
-rw-r--r--test/irb/test_raise_exception.rb (renamed from test/irb/test_raise_no_backtrace_exception.rb)30
-rw-r--r--test/irb/test_ruby_lex.rb886
-rw-r--r--test/irb/test_tracer.rb90
-rw-r--r--test/irb/test_type_completor.rb88
-rw-r--r--test/irb/test_workspace.rb5
-rw-r--r--test/irb/yamatanooroti/test_rendering.rb401
29 files changed, 4827 insertions, 1780 deletions
diff --git a/test/irb/command/test_custom_command.rb b/test/irb/command/test_custom_command.rb
new file mode 100644
index 0000000000..3a3ad11d5a
--- /dev/null
+++ b/test/irb/command/test_custom_command.rb
@@ -0,0 +1,149 @@
+# frozen_string_literal: true
+require "irb"
+
+require_relative "../helper"
+
+module TestIRB
+ class CustomCommandIntegrationTest < TestIRB::IntegrationTestCase
+ def test_command_registration_can_happen_after_irb_require
+ write_ruby <<~RUBY
+ require "irb"
+ require "irb/command"
+
+ class PrintCommand < IRB::Command::Base
+ category 'CommandTest'
+ description 'print_command'
+ def execute(*)
+ puts "Hello from PrintCommand"
+ end
+ end
+
+ IRB::Command.register(:print!, PrintCommand)
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "print!"
+ type "exit"
+ end
+
+ assert_include(output, "Hello from PrintCommand")
+ end
+
+ def test_command_registration_accepts_string_too
+ write_ruby <<~RUBY
+ require "irb/command"
+
+ class PrintCommand < IRB::Command::Base
+ category 'CommandTest'
+ description 'print_command'
+ def execute(*)
+ puts "Hello from PrintCommand"
+ end
+ end
+
+ IRB::Command.register("print!", PrintCommand)
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "print!"
+ type "exit"
+ end
+
+ assert_include(output, "Hello from PrintCommand")
+ end
+
+ def test_arguments_propagation
+ write_ruby <<~RUBY
+ require "irb/command"
+
+ class PrintArgCommand < IRB::Command::Base
+ category 'CommandTest'
+ description 'print_command_arg'
+ def execute(arg)
+ $nth_execution ||= 0
+ puts "\#{$nth_execution} arg=\#{arg.inspect}"
+ $nth_execution += 1
+ end
+ end
+
+ IRB::Command.register(:print_arg, PrintArgCommand)
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "print_arg"
+ type "print_arg \n"
+ type "print_arg a r g"
+ type "print_arg a r g \n"
+ type "exit"
+ end
+
+ assert_include(output, "0 arg=\"\"")
+ assert_include(output, "1 arg=\"\"")
+ assert_include(output, "2 arg=\"a r g\"")
+ assert_include(output, "3 arg=\"a r g\"")
+ end
+
+ def test_def_extend_command_still_works
+ write_ruby <<~RUBY
+ require "irb"
+
+ class FooBarCommand < IRB::Command::Base
+ category 'FooBarCategory'
+ description 'foobar_description'
+ def execute(*)
+ $nth_execution ||= 1
+ puts "\#{$nth_execution} FooBar executed"
+ $nth_execution += 1
+ end
+ end
+
+ IRB::ExtendCommandBundle.def_extend_command(:foobar, FooBarCommand, nil, [:fbalias, IRB::Command::OVERRIDE_ALL])
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "foobar"
+ type "fbalias"
+ type "help foobar"
+ type "exit"
+ end
+
+ assert_include(output, "1 FooBar executed")
+ assert_include(output, "2 FooBar executed")
+ assert_include(output, "foobar_description")
+ end
+
+ def test_no_meta_command_also_works
+ write_ruby <<~RUBY
+ require "irb/command"
+
+ class NoMetaCommand < IRB::Command::Base
+ def execute(*)
+ puts "This command does not override meta attributes"
+ end
+ end
+
+ IRB::Command.register(:no_meta, NoMetaCommand)
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "no_meta"
+ type "help no_meta"
+ type "exit"
+ end
+
+ assert_include(output, "This command does not override meta attributes")
+ assert_include(output, "No description provided.")
+ assert_not_include(output, "Maybe IRB bug")
+ end
+ end
+end
diff --git a/test/irb/command/test_disable_irb.rb b/test/irb/command/test_disable_irb.rb
new file mode 100644
index 0000000000..14a20043d5
--- /dev/null
+++ b/test/irb/command/test_disable_irb.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: false
+require 'irb'
+
+require_relative "../helper"
+
+module TestIRB
+ class DisableIRBTest < IntegrationTestCase
+ def test_disable_irb_disable_further_irb_breakpoints
+ write_ruby <<~'ruby'
+ puts "First line"
+ puts "Second line"
+ binding.irb
+ puts "Third line"
+ binding.irb
+ puts "Fourth line"
+ ruby
+
+ output = run_ruby_file do
+ type "disable_irb"
+ end
+
+ assert_match(/First line\r\n/, output)
+ assert_match(/Second line\r\n/, output)
+ assert_match(/Third line\r\n/, output)
+ assert_match(/Fourth line\r\n/, output)
+ end
+ end
+end
diff --git a/test/irb/command/test_force_exit.rb b/test/irb/command/test_force_exit.rb
new file mode 100644
index 0000000000..191a786872
--- /dev/null
+++ b/test/irb/command/test_force_exit.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: false
+require 'irb'
+
+require_relative "../helper"
+
+module TestIRB
+ class ForceExitTest < IntegrationTestCase
+ def test_forced_exit_finishes_process_immediately
+ write_ruby <<~'ruby'
+ puts "First line"
+ puts "Second line"
+ binding.irb
+ puts "Third line"
+ binding.irb
+ puts "Fourth line"
+ ruby
+
+ output = run_ruby_file do
+ type "123"
+ type "456"
+ type "exit!"
+ end
+
+ assert_match(/First line\r\n/, output)
+ assert_match(/Second line\r\n/, output)
+ assert_match(/irb\(main\):001> 123/, output)
+ assert_match(/irb\(main\):002> 456/, output)
+ refute_match(/Third line\r\n/, output)
+ refute_match(/Fourth line\r\n/, output)
+ end
+
+ def test_forced_exit_in_nested_sessions
+ write_ruby <<~'ruby'
+ def foo
+ binding.irb
+ end
+
+ binding.irb
+ binding.irb
+ ruby
+
+ output = run_ruby_file do
+ type "123"
+ type "foo"
+ type "exit!"
+ end
+
+ assert_match(/irb\(main\):001> 123/, output)
+ end
+ end
+end
diff --git a/test/irb/command/test_help.rb b/test/irb/command/test_help.rb
new file mode 100644
index 0000000000..b34832b022
--- /dev/null
+++ b/test/irb/command/test_help.rb
@@ -0,0 +1,75 @@
+require "tempfile"
+require_relative "../helper"
+
+module TestIRB
+ class HelpTest < IntegrationTestCase
+ def setup
+ super
+
+ write_rc <<~'RUBY'
+ IRB.conf[:USE_PAGER] = false
+ RUBY
+
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+ end
+
+ def test_help
+ out = run_ruby_file do
+ type "help"
+ type "exit"
+ end
+
+ assert_match(/List all available commands/, out)
+ assert_match(/Start the debugger of debug\.gem/, out)
+ end
+
+ def test_command_help
+ out = run_ruby_file do
+ type "help ls"
+ type "exit"
+ end
+
+ assert_match(/Usage: ls \[obj\]/, out)
+ end
+
+ def test_command_help_not_found
+ out = run_ruby_file do
+ type "help foo"
+ type "exit"
+ end
+
+ assert_match(/Can't find command `foo`\. Please check the command name and try again\./, out)
+ end
+
+ def test_show_cmds
+ out = run_ruby_file do
+ type "help"
+ type "exit"
+ end
+
+ assert_match(/List all available commands/, out)
+ assert_match(/Start the debugger of debug\.gem/, out)
+ end
+
+ def test_help_lists_user_aliases
+ out = run_ruby_file do
+ type "help"
+ type "exit"
+ end
+
+ assert_match(/\$\s+Alias for `show_source`/, out)
+ assert_match(/@\s+Alias for `whereami`/, out)
+ end
+
+ def test_help_lists_helper_methods
+ out = run_ruby_file do
+ type "help"
+ type "exit"
+ end
+
+ assert_match(/Helper methods\s+conf\s+Returns the current IRB context/, out)
+ end
+ end
+end
diff --git a/test/irb/command/test_multi_irb_commands.rb b/test/irb/command/test_multi_irb_commands.rb
new file mode 100644
index 0000000000..e313c0c5d2
--- /dev/null
+++ b/test/irb/command/test_multi_irb_commands.rb
@@ -0,0 +1,50 @@
+require "tempfile"
+require_relative "../helper"
+
+module TestIRB
+ class MultiIRBTest < IntegrationTestCase
+ def setup
+ super
+
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+ end
+
+ def test_jobs_command_with_print_deprecated_warning
+ out = run_ruby_file do
+ type "jobs"
+ type "exit"
+ end
+
+ assert_match(/Multi-irb commands are deprecated and will be removed in IRB 2\.0\.0\. Please use workspace commands instead\./, out)
+ assert_match(%r|If you have any use case for multi-irb, please leave a comment at https://github.com/ruby/irb/issues/653|, out)
+ assert_match(/#0->irb on main \(#<Thread:0x.+ run>: running\)/, out)
+ end
+
+ def test_irb_jobs_and_kill_commands
+ out = run_ruby_file do
+ type "irb"
+ type "jobs"
+ type "kill 1"
+ type "exit"
+ end
+
+ assert_match(/#0->irb on main \(#<Thread:0x.+ sleep_forever>: stop\)/, out)
+ assert_match(/#1->irb#1 on main \(#<Thread:0x.+ run>: running\)/, out)
+ end
+
+ def test_irb_fg_jobs_and_kill_commands
+ out = run_ruby_file do
+ type "irb"
+ type "fg 0"
+ type "jobs"
+ type "kill 1"
+ type "exit"
+ end
+
+ assert_match(/#0->irb on main \(#<Thread:0x.+ run>: running\)/, out)
+ assert_match(/#1->irb#1 on main \(#<Thread:0x.+ sleep_forever>: stop\)/, out)
+ end
+ end
+end
diff --git a/test/irb/command/test_show_source.rb b/test/irb/command/test_show_source.rb
new file mode 100644
index 0000000000..d014c78fc4
--- /dev/null
+++ b/test/irb/command/test_show_source.rb
@@ -0,0 +1,397 @@
+# frozen_string_literal: false
+require 'irb'
+
+require_relative "../helper"
+
+module TestIRB
+ class ShowSourceTest < IntegrationTestCase
+ def setup
+ super
+
+ write_rc <<~'RUBY'
+ IRB.conf[:USE_PAGER] = false
+ RUBY
+ end
+
+ def test_show_source
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "show_source IRB.conf"
+ type "exit"
+ end
+
+ assert_match(%r[/irb\/init\.rb], out)
+ end
+
+ def test_show_source_alias
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "$ IRB.conf"
+ type "exit"
+ end
+
+ assert_match(%r[/irb\/init\.rb], out)
+ end
+
+ def test_show_source_with_missing_signature
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "show_source foo"
+ type "exit"
+ end
+
+ assert_match(%r[Couldn't locate a definition for foo], out)
+ end
+
+ def test_show_source_with_missing_constant
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "show_source Foo"
+ type "exit"
+ end
+
+ assert_match(%r[Couldn't locate a definition for Foo], out)
+ end
+
+ def test_show_source_string
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "show_source 'IRB.conf'"
+ type "exit"
+ end
+
+ assert_match(%r[/irb\/init\.rb], out)
+ end
+
+ def test_show_source_method_s
+ write_ruby <<~RUBY
+ class Baz
+ def foo
+ end
+ end
+
+ class Bar < Baz
+ def foo
+ super
+ end
+ end
+
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "show_source Bar#foo -s"
+ type "exit"
+ end
+
+ assert_match(%r[#{@ruby_file.to_path}:2\s+def foo\r\n end\r\n], out)
+ end
+
+ def test_show_source_method_s_with_incorrect_signature
+ write_ruby <<~RUBY
+ class Baz
+ def foo
+ end
+ end
+
+ class Bar < Baz
+ def foo
+ super
+ end
+ end
+
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "show_source Bar#fooo -s"
+ type "exit"
+ end
+
+ assert_match(%r[Error: Couldn't locate a super definition for Bar#fooo], out)
+ end
+
+ def test_show_source_private_method
+ write_ruby <<~RUBY
+ class Bar
+ private def foo
+ end
+ end
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "show_source Bar#foo"
+ type "exit"
+ end
+
+ assert_match(%r[#{@ruby_file.to_path}:2\s+private def foo\r\n end\r\n], out)
+ end
+
+ def test_show_source_private_singleton_method
+ write_ruby <<~RUBY
+ class Bar
+ private def foo
+ end
+ end
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "bar = Bar.new"
+ type "show_source bar.foo"
+ type "exit"
+ end
+
+ assert_match(%r[#{@ruby_file.to_path}:2\s+private def foo\r\n end\r\n], out)
+ end
+
+ def test_show_source_method_multiple_s
+ write_ruby <<~RUBY
+ class Baz
+ def foo
+ end
+ end
+
+ class Bar < Baz
+ def foo
+ super
+ end
+ end
+
+ class Bob < Bar
+ def foo
+ super
+ end
+ end
+
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "show_source Bob#foo -ss"
+ type "exit"
+ end
+
+ assert_match(%r[#{@ruby_file.to_path}:2\s+def foo\r\n end\r\n], out)
+ end
+
+ def test_show_source_method_no_instance_method
+ write_ruby <<~RUBY
+ class Baz
+ end
+
+ class Bar < Baz
+ def foo
+ super
+ end
+ end
+
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "show_source Bar#foo -s"
+ type "exit"
+ end
+
+ assert_match(%r[Error: Couldn't locate a super definition for Bar#foo], out)
+ end
+
+ def test_show_source_method_exceeds_super_chain
+ write_ruby <<~RUBY
+ class Baz
+ def foo
+ end
+ end
+
+ class Bar < Baz
+ def foo
+ super
+ end
+ end
+
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "show_source Bar#foo -ss"
+ type "exit"
+ end
+
+ assert_match(%r[Error: Couldn't locate a super definition for Bar#foo], out)
+ end
+
+ def test_show_source_method_accidental_characters
+ write_ruby <<~'RUBY'
+ class Baz
+ def foo
+ end
+ end
+
+ class Bar < Baz
+ def foo
+ super
+ end
+ end
+
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "show_source Bar#foo -sddddd"
+ type "exit"
+ end
+
+ assert_match(%r[#{@ruby_file.to_path}:2\s+def foo\r\n end], out)
+ end
+
+ def test_show_source_receiver_super
+ write_ruby <<~RUBY
+ class Baz
+ def foo
+ end
+ end
+
+ class Bar < Baz
+ def foo
+ super
+ end
+ end
+
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "bar = Bar.new"
+ type "show_source bar.foo -s"
+ type "exit"
+ end
+
+ assert_match(%r[#{@ruby_file.to_path}:2\s+def foo\r\n end], out)
+ end
+
+ def test_show_source_with_double_colons
+ write_ruby <<~RUBY
+ class Foo
+ end
+
+ class Foo
+ class Bar
+ end
+ end
+
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "show_source ::Foo"
+ type "exit"
+ end
+
+ assert_match(%r[#{@ruby_file.to_path}:1\s+class Foo\r\nend], out)
+
+ out = run_ruby_file do
+ type "show_source ::Foo::Bar"
+ type "exit"
+ end
+
+ assert_match(%r[#{@ruby_file.to_path}:5\s+class Bar\r\n end], out)
+ end
+
+ def test_show_source_keep_script_lines
+ pend unless defined?(RubyVM.keep_script_lines)
+
+ write_ruby <<~RUBY
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "def foo; end"
+ type "show_source foo"
+ type "exit"
+ end
+
+ assert_match(%r[#{@ruby_file.to_path}\(irb\):1\s+def foo; end], out)
+ end
+
+ def test_show_source_unavailable_source
+ write_ruby <<~RUBY
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ type "RubyVM.keep_script_lines = false if defined?(RubyVM.keep_script_lines)"
+ type "def foo; end"
+ type "show_source foo"
+ type "exit"
+ end
+ assert_match(%r[#{@ruby_file.to_path}\(irb\):2\s+Source not available], out)
+ end
+
+ def test_show_source_shows_binary_source
+ write_ruby <<~RUBY
+ # io-console is an indirect dependency of irb
+ require "io/console"
+
+ binding.irb
+ RUBY
+
+ out = run_ruby_file do
+ # IO::ConsoleMode is defined in io-console gem's C extension
+ type "show_source IO::ConsoleMode"
+ type "exit"
+ end
+
+ # A safeguard to make sure the test subject is actually defined
+ refute_match(/NameError/, out)
+ assert_match(%r[Defined in binary file:.+io/console], out)
+ end
+
+ def test_show_source_with_constant_lookup
+ write_ruby <<~RUBY
+ X = 1
+ module M
+ Y = 1
+ Z = 2
+ end
+ class A
+ Z = 1
+ Array = 1
+ class B
+ include M
+ Object.new.instance_eval { binding.irb }
+ end
+ end
+ RUBY
+
+ out = run_ruby_file do
+ type "show_source X"
+ type "show_source Y"
+ type "show_source Z"
+ type "show_source Array"
+ type "exit"
+ end
+
+ assert_match(%r[#{@ruby_file.to_path}:1\s+X = 1], out)
+ assert_match(%r[#{@ruby_file.to_path}:3\s+Y = 1], out)
+ assert_match(%r[#{@ruby_file.to_path}:7\s+Z = 1], out)
+ assert_match(%r[#{@ruby_file.to_path}:8\s+Array = 1], out)
+ end
+ end
+end
diff --git a/test/irb/helper.rb b/test/irb/helper.rb
index 55f9e083eb..acaf6277f3 100644
--- a/test/irb/helper.rb
+++ b/test/irb/helper.rb
@@ -1,5 +1,6 @@
require "test/unit"
require "pathname"
+require "rubygems"
begin
require_relative "../lib/helper"
@@ -7,17 +8,22 @@ begin
rescue LoadError # ruby/ruby defines helpers differently
end
+begin
+ require "pty"
+rescue LoadError # some platforms don't support PTY
+end
+
module IRB
class InputMethod; end
end
module TestIRB
+ RUBY_3_4 = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0.dev")
class TestCase < Test::Unit::TestCase
class TestInputMethod < ::IRB::InputMethod
attr_reader :list, :line_no
def initialize(list = [])
- super("test")
@line_no = 0
@list = list
end
@@ -73,4 +79,143 @@ module TestIRB
}
end
end
+
+ class IntegrationTestCase < TestCase
+ LIB = File.expand_path("../../lib", __dir__)
+ TIMEOUT_SEC = 3
+
+ def setup
+ @envs = {}
+ @tmpfiles = []
+
+ unless defined?(PTY)
+ omit "Integration tests require PTY."
+ end
+
+ if ruby_core?
+ omit "This test works only under ruby/irb"
+ end
+
+ write_rc <<~RUBY
+ IRB.conf[:USE_PAGER] = false
+ RUBY
+ end
+
+ def teardown
+ @tmpfiles.each do |tmpfile|
+ File.unlink(tmpfile)
+ end
+ end
+
+ def run_ruby_file(&block)
+ cmd = [EnvUtil.rubybin, "-I", LIB, @ruby_file.to_path]
+ tmp_dir = Dir.mktmpdir
+
+ @commands = []
+ lines = []
+
+ yield
+
+ # Test should not depend on user's irbrc file
+ @envs["HOME"] ||= tmp_dir
+ @envs["XDG_CONFIG_HOME"] ||= tmp_dir
+ @envs["IRBRC"] = nil unless @envs.key?("IRBRC")
+
+ envs_for_spawn = @envs.merge('TERM' => 'dumb', 'TEST_IRB_FORCE_INTERACTIVE' => 'true')
+
+ PTY.spawn(envs_for_spawn, *cmd) do |read, write, pid|
+ Timeout.timeout(TIMEOUT_SEC) do
+ while line = safe_gets(read)
+ lines << line
+
+ # means the breakpoint is triggered
+ if line.match?(/binding\.irb/)
+ while command = @commands.shift
+ write.puts(command)
+ end
+ end
+ end
+ end
+ ensure
+ read.close
+ write.close
+ kill_safely(pid)
+ end
+
+ lines.join
+ rescue Timeout::Error
+ message = <<~MSG
+ Test timedout.
+
+ #{'=' * 30} OUTPUT #{'=' * 30}
+ #{lines.map { |l| " #{l}" }.join}
+ #{'=' * 27} END OF OUTPUT #{'=' * 27}
+ MSG
+ assert_block(message) { false }
+ ensure
+ FileUtils.remove_entry tmp_dir
+ end
+
+ # read.gets could raise exceptions on some platforms
+ # https://github.com/ruby/ruby/blob/master/ext/pty/pty.c#L721-L728
+ def safe_gets(read)
+ read.gets
+ rescue Errno::EIO
+ nil
+ end
+
+ def kill_safely pid
+ return if wait_pid pid, TIMEOUT_SEC
+
+ Process.kill :TERM, pid
+ return if wait_pid pid, 0.2
+
+ Process.kill :KILL, pid
+ Process.waitpid(pid)
+ rescue Errno::EPERM, Errno::ESRCH
+ end
+
+ def wait_pid pid, sec
+ total_sec = 0.0
+ wait_sec = 0.001 # 1ms
+
+ while total_sec < sec
+ if Process.waitpid(pid, Process::WNOHANG) == pid
+ return true
+ end
+ sleep wait_sec
+ total_sec += wait_sec
+ wait_sec *= 2
+ end
+
+ false
+ rescue Errno::ECHILD
+ true
+ end
+
+ def type(command)
+ @commands << command
+ end
+
+ def write_ruby(program)
+ @ruby_file = Tempfile.create(%w{irbtest- .rb})
+ @tmpfiles << @ruby_file
+ @ruby_file.write(program)
+ @ruby_file.close
+ end
+
+ def write_rc(content)
+ # Append irbrc content if a tempfile for it already exists
+ if @irbrc
+ @irbrc = File.open(@irbrc, "a")
+ else
+ @irbrc = Tempfile.new('irbrc')
+ @tmpfiles << @irbrc
+ end
+
+ @irbrc.write(content)
+ @irbrc.close
+ @envs['IRBRC'] = @irbrc.path
+ end
+ end
end
diff --git a/test/irb/test_color.rb b/test/irb/test_color.rb
index 652396c89e..9d78f5233e 100644
--- a/test/irb/test_color.rb
+++ b/test/irb/test_color.rb
@@ -1,12 +1,11 @@
# frozen_string_literal: false
require 'irb/color'
-require 'rubygems'
require 'stringio'
require_relative "helper"
module TestIRB
- class TestColor < TestCase
+ class ColorTest < TestCase
CLEAR = "\e[0m"
BOLD = "\e[1m"
UNDERLINE = "\e[4m"
@@ -100,7 +99,7 @@ module TestIRB
"foo %i[bar]" => "foo #{YELLOW}%i[#{CLEAR}#{YELLOW}bar#{CLEAR}#{YELLOW}]#{CLEAR}",
"foo :@bar, baz, :@@qux, :$quux" => "foo #{YELLOW}:#{CLEAR}#{YELLOW}@bar#{CLEAR}, baz, #{YELLOW}:#{CLEAR}#{YELLOW}@@qux#{CLEAR}, #{YELLOW}:#{CLEAR}#{YELLOW}$quux#{CLEAR}",
"`echo`" => "#{RED}#{BOLD}`#{CLEAR}#{RED}echo#{CLEAR}#{RED}#{BOLD}`#{CLEAR}",
- "\t" => "\t", # not ^I
+ "\t" => Reline::Unicode.escape_for_print("\t") == ' ' ? ' ' : "\t", # not ^I
"foo(*%W(bar))" => "foo(*#{RED}#{BOLD}%W(#{CLEAR}#{RED}bar#{CLEAR}#{RED}#{BOLD})#{CLEAR})",
"$stdout" => "#{GREEN}#{BOLD}$stdout#{CLEAR}",
"__END__" => "#{GREEN}__END__#{CLEAR}",
diff --git a/test/irb/test_color_printer.rb b/test/irb/test_color_printer.rb
index a717940d81..c2c624d868 100644
--- a/test/irb/test_color_printer.rb
+++ b/test/irb/test_color_printer.rb
@@ -1,12 +1,11 @@
# frozen_string_literal: false
require 'irb/color_printer'
-require 'rubygems'
require 'stringio'
require_relative "helper"
module TestIRB
- class TestColorPrinter < TestCase
+ class ColorPrinterTest < TestCase
CLEAR = "\e[0m"
BOLD = "\e[1m"
RED = "\e[31m"
@@ -41,7 +40,7 @@ module TestIRB
{
1 => "#{BLUE}#{BOLD}1#{CLEAR}\n",
"a\nb" => %[#{RED}#{BOLD}"#{CLEAR}#{RED}a\\nb#{CLEAR}#{RED}#{BOLD}"#{CLEAR}\n],
- IRBTestColorPrinter.new('test') => "#{GREEN}#<struct TestIRB::TestColorPrinter::IRBTestColorPrinter#{CLEAR} a#{GREEN}=#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{RED}test#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{GREEN}>#{CLEAR}\n",
+ IRBTestColorPrinter.new('test') => "#{GREEN}#<struct TestIRB::ColorPrinterTest::IRBTestColorPrinter#{CLEAR} a#{GREEN}=#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{RED}test#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{GREEN}>#{CLEAR}\n",
Ripper::Lexer.new('1').scan => "[#{GREEN}#<Ripper::Lexer::Elem:#{CLEAR} on_int@1:0 END token: #{RED}#{BOLD}\"#{CLEAR}#{RED}1#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{GREEN}>#{CLEAR}]\n",
Class.new{define_method(:pretty_print){|q| q.text("[__FILE__, __LINE__, __ENCODING__]")}}.new => "[#{CYAN}#{BOLD}__FILE__#{CLEAR}, #{CYAN}#{BOLD}__LINE__#{CLEAR}, #{CYAN}#{BOLD}__ENCODING__#{CLEAR}]\n",
}.each do |object, result|
diff --git a/test/irb/test_cmd.rb b/test/irb/test_command.rb
index 448f42587a..8cb8928adb 100644
--- a/test/irb/test_cmd.rb
+++ b/test/irb/test_command.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: false
-require "rubygems"
require "irb"
-require "irb/extend-command"
require_relative "helper"
@@ -22,6 +20,7 @@ module TestIRB
@xdg_config_home_backup = ENV.delete("XDG_CONFIG_HOME")
save_encodings
IRB.instance_variable_get(:@CONF).clear
+ IRB.instance_variable_set(:@existing_rc_name_generators, nil)
@is_win = (RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/)
end
@@ -34,16 +33,17 @@ module TestIRB
end
def execute_lines(*lines, conf: {}, main: self, irb_path: nil)
- IRB.init_config(nil)
- IRB.conf[:VERBOSE] = false
- IRB.conf[:PROMPT_MODE] = :SIMPLE
- IRB.conf.merge!(conf)
- input = TestInputMethod.new(lines)
- irb = IRB::Irb.new(IRB::WorkSpace.new(main), input)
- irb.context.return_format = "=> %s\n"
- irb.context.irb_path = irb_path if irb_path
- IRB.conf[:MAIN_CONTEXT] = irb.context
capture_output do
+ IRB.init_config(nil)
+ IRB.conf[:VERBOSE] = false
+ IRB.conf[:PROMPT_MODE] = :SIMPLE
+ IRB.conf[:USE_PAGER] = false
+ IRB.conf.merge!(conf)
+ input = TestInputMethod.new(lines)
+ irb = IRB::Irb.new(IRB::WorkSpace.new(main), input)
+ irb.context.return_format = "=> %s\n"
+ irb.context.irb_path = irb_path if irb_path
+ IRB.conf[:MAIN_CONTEXT] = irb.context
irb.eval_input
end
end
@@ -57,28 +57,11 @@ module TestIRB
"irb_info",
main: main
)
- assert_empty err
+ assert_empty(err)
assert_match(/RUBY_PLATFORM/, out)
end
end
- class CommnadAliasTest < CommandTestCase
- def test_vars_with_aliases
- @foo = "foo"
- $bar = "bar"
- out, err = execute_lines(
- "@foo\n",
- "$bar\n",
- )
- assert_empty err
- assert_match(/"foo"/, out)
- assert_match(/"bar"/, out)
- ensure
- remove_instance_variable(:@foo)
- $bar = nil
- end
- end
-
class InfoTest < CommandTestCase
def setup
super
@@ -93,6 +76,7 @@ module TestIRB
def test_irb_info_multiline
FileUtils.touch("#{@tmpdir}/.inputrc")
FileUtils.touch("#{@tmpdir}/.irbrc")
+ FileUtils.touch("#{@tmpdir}/_irbrc")
out, err = execute_lines(
"irb_info",
@@ -103,7 +87,8 @@ module TestIRB
Ruby\sversion:\s.+\n
IRB\sversion:\sirb\s.+\n
InputMethod:\sAbstract\sInputMethod\n
- \.irbrc\spath:\s.+\n
+ Completion: .+\n
+ \.irbrc\spaths:.*\.irbrc.*_irbrc\n
RUBY_PLATFORM:\s.+\n
East\sAsian\sAmbiguous\sWidth:\s\d\n
#{@is_win ? 'Code\spage:\s\d+\n' : ''}
@@ -126,7 +111,8 @@ module TestIRB
Ruby\sversion:\s.+\n
IRB\sversion:\sirb\s.+\n
InputMethod:\sAbstract\sInputMethod\n
- \.irbrc\spath:\s.+\n
+ Completion: .+\n
+ \.irbrc\spaths:\s.+\n
RUBY_PLATFORM:\s.+\n
East\sAsian\sAmbiguous\sWidth:\s\d\n
#{@is_win ? 'Code\spage:\s\d+\n' : ''}
@@ -152,6 +138,7 @@ module TestIRB
Ruby\sversion:\s.+\n
IRB\sversion:\sirb\s.+\n
InputMethod:\sAbstract\sInputMethod\n
+ Completion: .+\n
RUBY_PLATFORM:\s.+\n
East\sAsian\sAmbiguous\sWidth:\s\d\n
#{@is_win ? 'Code\spage:\s\d+\n' : ''}
@@ -181,6 +168,7 @@ module TestIRB
Ruby\sversion:\s.+\n
IRB\sversion:\sirb\s.+\n
InputMethod:\sAbstract\sInputMethod\n
+ Completion: .+\n
RUBY_PLATFORM:\s.+\n
East\sAsian\sAmbiguous\sWidth:\s\d\n
#{@is_win ? 'Code\spage:\s\d+\n' : ''}
@@ -209,7 +197,8 @@ module TestIRB
Ruby\sversion: .+\n
IRB\sversion:\sirb .+\n
InputMethod:\sAbstract\sInputMethod\n
- \.irbrc\spath: .+\n
+ Completion: .+\n
+ \.irbrc\spaths: .+\n
RUBY_PLATFORM: .+\n
LANG\senv:\sja_JP\.UTF-8\n
LC_ALL\senv:\sen_US\.UTF-8\n
@@ -228,8 +217,7 @@ module TestIRB
DEFAULT: {
PROMPT_I: '> ',
PROMPT_S: '> ',
- PROMPT_C: '> ',
- PROMPT_N: '> '
+ PROMPT_C: '> '
}
},
PROMPT_MODE: :DEFAULT,
@@ -238,17 +226,47 @@ module TestIRB
c = Class.new(Object)
out, err = execute_lines(
- "3\n",
"measure\n",
"3\n",
"measure :off\n",
"3\n",
+ "measure :on\n",
+ "3\n",
+ "measure :off\n",
+ "3\n",
conf: conf,
main: c
)
assert_empty err
- assert_match(/\A=> 3\nTIME is added\.\n=> nil\nprocessing time: .+\n=> 3\n=> nil\n=> 3\n/, out)
+ assert_match(/\A(TIME is added\.\n=> nil\nprocessing time: .+\n=> 3\n=> nil\n=> 3\n){2}/, out)
+ assert_empty(c.class_variables)
+ end
+
+ def test_measure_keeps_previous_value
+ conf = {
+ PROMPT: {
+ DEFAULT: {
+ PROMPT_I: '> ',
+ PROMPT_S: '> ',
+ PROMPT_C: '> '
+ }
+ },
+ PROMPT_MODE: :DEFAULT,
+ MEASURE: false
+ }
+
+ c = Class.new(Object)
+ out, err = execute_lines(
+ "measure\n",
+ "3\n",
+ "_\n",
+ conf: conf,
+ main: c
+ )
+
+ assert_empty err
+ assert_match(/\ATIME is added\.\n=> nil\nprocessing time: .+\n=> 3\nprocessing time: .+\n=> 3/, out)
assert_empty(c.class_variables)
end
@@ -258,8 +276,7 @@ module TestIRB
DEFAULT: {
PROMPT_I: '> ',
PROMPT_S: '> ',
- PROMPT_C: '> ',
- PROMPT_N: '> '
+ PROMPT_C: '> '
}
},
PROMPT_MODE: :DEFAULT,
@@ -289,8 +306,7 @@ module TestIRB
DEFAULT: {
PROMPT_I: '> ',
PROMPT_S: '> ',
- PROMPT_C: '> ',
- PROMPT_N: '> '
+ PROMPT_C: '> '
}
},
PROMPT_MODE: :DEFAULT,
@@ -320,8 +336,7 @@ module TestIRB
DEFAULT: {
PROMPT_I: '> ',
PROMPT_S: '> ',
- PROMPT_C: '> ',
- PROMPT_N: '> '
+ PROMPT_C: '> '
}
},
PROMPT_MODE: :DEFAULT,
@@ -341,42 +356,61 @@ module TestIRB
assert_match(/\A=> 3\nCUSTOM is added\.\n=> nil\ncustom processing time: .+\n=> 3\n=> nil\n=> 3\n/, out)
end
- def test_measure_with_proc
+ def test_measure_toggle
conf = {
PROMPT: {
DEFAULT: {
PROMPT_I: '> ',
PROMPT_S: '> ',
- PROMPT_C: '> ',
- PROMPT_N: '> '
+ PROMPT_C: '> '
}
},
PROMPT_MODE: :DEFAULT,
MEASURE: false,
+ MEASURE_PROC: {
+ FOO: proc { |&block| puts 'foo'; block.call },
+ BAR: proc { |&block| puts 'bar'; block.call }
+ }
}
- c = Class.new(Object)
out, err = execute_lines(
+ "measure :foo\n",
+ "1\n",
+ "measure :on, :bar\n",
+ "2\n",
+ "measure :off, :foo\n",
"3\n",
- "measure { |context, code, line_no, &block|\n",
- " result = block.()\n",
- " puts 'aaa' if IRB.conf[:MEASURE]\n",
- " result\n",
- "}\n",
- "3\n",
- "measure { |context, code, line_no, &block|\n",
- " result = block.()\n",
- " puts 'bbb' if IRB.conf[:MEASURE]\n",
- " result\n",
- "}\n",
+ "measure :off, :bar\n",
+ "4\n",
+ conf: conf
+ )
+
+ assert_empty err
+ assert_match(/\AFOO is added\.\n=> nil\nfoo\n=> 1\nBAR is added\.\n=> nil\nbar\nfoo\n=> 2\n=> nil\nbar\n=> 3\n=> nil\n=> 4\n/, out)
+ end
+
+ def test_measure_with_proc_warning
+ conf = {
+ PROMPT: {
+ DEFAULT: {
+ PROMPT_I: '> ',
+ PROMPT_S: '> ',
+ PROMPT_C: '> '
+ }
+ },
+ PROMPT_MODE: :DEFAULT,
+ MEASURE: false,
+ }
+ c = Class.new(Object)
+ out, err = execute_lines(
"3\n",
- "measure :off\n",
+ "measure do\n",
"3\n",
conf: conf,
main: c
)
- assert_empty err
- assert_match(/\A=> 3\nBLOCK is added\.\n=> nil\naaa\n=> 3\nBLOCK is added.\naaa\n=> nil\nbbb\n=> 3\n=> nil\n=> 3\n/, out)
+ assert_match(/to add custom measure/, err)
+ assert_match(/\A=> 3\n=> nil\n=> 3\n/, out)
assert_empty(c.class_variables)
end
end
@@ -438,99 +472,6 @@ module TestIRB
end
end
- class ShowSourceTest < CommandTestCase
- def test_show_source
- out, err = execute_lines(
- "show_source IRB.conf\n",
- )
- assert_empty err
- assert_match(%r[/irb\.rb], out)
- end
-
- def test_show_source_method
- out, err = execute_lines(
- "p show_source('IRB.conf')\n",
- )
- assert_empty err
- assert_match(%r[/irb\.rb], out)
- end
-
- def test_show_source_string
- out, err = execute_lines(
- "show_source 'IRB.conf'\n",
- )
- assert_empty err
- assert_match(%r[/irb\.rb], out)
- end
-
- def test_show_source_alias
- out, err = execute_lines(
- "$ 'IRB.conf'\n",
- conf: { COMMAND_ALIASES: { :'$' => :show_source } }
- )
- assert_empty err
- assert_match(%r[/irb\.rb], out)
- end
-
- def test_show_source_end_finder
- pend if RUBY_ENGINE == 'truffleruby'
- eval(code = <<-EOS, binding, __FILE__, __LINE__ + 1)
- def show_source_test_method
- unless true
- end
- end unless defined?(show_source_test_method)
- EOS
-
- out, err = execute_lines(
- "show_source '#{self.class.name}#show_source_test_method'\n",
- )
-
- assert_empty err
- assert_include(out, code)
- end
-
- def test_show_source_private_instance
- pend if RUBY_ENGINE == 'truffleruby'
- eval(code = <<-EOS, binding, __FILE__, __LINE__ + 1)
- class PrivateInstanceTest
- private def show_source_test_method
- unless true
- end
- end unless private_method_defined?(:show_source_test_method)
- end
- EOS
-
- out, err = execute_lines(
- "show_source '#{self.class.name}::PrivateInstanceTest#show_source_test_method'\n",
- )
-
- assert_empty err
- assert_include(out, code.lines[1..-2].join)
- end
-
-
- def test_show_source_private
- pend if RUBY_ENGINE == 'truffleruby'
- eval(code = <<-EOS, binding, __FILE__, __LINE__ + 1)
- class PrivateTest
- private def show_source_test_method
- unless true
- end
- end unless private_method_defined?(:show_source_test_method)
- end
-
- Instance = PrivateTest.new unless defined?(Instance)
- EOS
-
- out, err = execute_lines(
- "show_source '#{self.class.name}::Instance.show_source_test_method'\n",
- )
-
- assert_empty err
- assert_include(out, code.lines[1..-4].join)
- end
- end
-
class WorkspaceCommandTestCase < CommandTestCase
def setup
super
@@ -544,62 +485,67 @@ module TestIRB
class CwwsTest < WorkspaceCommandTestCase
def test_cwws_returns_the_current_workspace_object
out, err = execute_lines(
- "cwws.class",
+ "cwws"
)
assert_empty err
- assert_include(out, self.class.name)
+ assert_include(out, "Current workspace: #{self}")
end
end
class PushwsTest < WorkspaceCommandTestCase
def test_pushws_switches_to_new_workspace_and_pushes_the_current_one_to_the_stack
out, err = execute_lines(
- "pushws #{self.class}::Foo.new\n",
- "cwws.class",
+ "pushws #{self.class}::Foo.new",
+ "self.class",
+ "popws",
+ "self.class"
)
assert_empty err
- assert_include(out, "#{self.class}::Foo")
+
+ assert_match(/=> #{self.class}::Foo\n/, out)
+ assert_match(/=> #{self.class}\n$/, out)
end
def test_pushws_extends_the_new_workspace_with_command_bundle
out, err = execute_lines(
- "pushws Object.new\n",
+ "pushws Object.new",
"self.singleton_class.ancestors"
)
assert_empty err
assert_include(out, "IRB::ExtendCommandBundle")
end
- def test_pushws_prints_help_message_when_no_arg_is_given
+ def test_pushws_prints_workspace_stack_when_no_arg_is_given
out, err = execute_lines(
- "pushws\n",
+ "pushws",
)
assert_empty err
- assert_match(/No other workspace/, out)
+ assert_include(out, "[#<TestIRB::PushwsTe...>]")
end
- end
- class WorkspacesTest < WorkspaceCommandTestCase
- def test_workspaces_returns_the_array_of_non_main_workspaces
+ def test_pushws_without_argument_swaps_the_top_two_workspaces
out, err = execute_lines(
- "pushws #{self.class}::Foo.new\n",
- "workspaces.map { |w| w.class.name }",
+ "pushws #{self.class}::Foo.new",
+ "self.class",
+ "pushws",
+ "self.class"
)
-
assert_empty err
- # self.class::Foo would be the current workspace
- # self.class would be the old workspace that's pushed to the stack
- assert_include(out, "=> [\"#{self.class}\"]")
+ assert_match(/=> #{self.class}::Foo\n/, out)
+ assert_match(/=> #{self.class}\n$/, out)
end
+ end
- def test_workspaces_returns_empty_array_when_no_workspaces_were_added
+ class WorkspacesTest < WorkspaceCommandTestCase
+ def test_workspaces_returns_the_stack_of_workspaces
out, err = execute_lines(
- "workspaces.map(&:to_s)",
+ "pushws #{self.class}::Foo.new\n",
+ "workspaces",
)
assert_empty err
- assert_include(out, "=> []")
+ assert_match(/\[#<TestIRB::Workspac...>, #<TestIRB::Workspac...>\]\n/, out)
end
end
@@ -608,7 +554,8 @@ module TestIRB
out, err = execute_lines(
"pushws Foo.new\n",
"popws\n",
- "cwws.class",
+ "cwws\n",
+ "self.class",
)
assert_empty err
assert_include(out, "=> #{self.class}")
@@ -619,7 +566,7 @@ module TestIRB
"popws\n",
)
assert_empty err
- assert_match(/workspace stack empty/, out)
+ assert_match(/\[#<TestIRB::PopwsTes...>\]\n/, out)
end
end
@@ -627,19 +574,20 @@ module TestIRB
def test_chws_replaces_the_current_workspace
out, err = execute_lines(
"chws #{self.class}::Foo.new\n",
- "cwws.class",
+ "cwws\n",
+ "self.class\n"
)
assert_empty err
+ assert_include(out, "Current workspace: #<#{self.class.name}::Foo")
assert_include(out, "=> #{self.class}::Foo")
end
def test_chws_does_nothing_when_receiving_no_argument
out, err = execute_lines(
"chws\n",
- "cwws.class",
)
assert_empty err
- assert_include(out, "=> #{self.class}")
+ assert_include(out, "Current workspace: #{self}")
end
end
@@ -661,19 +609,6 @@ module TestIRB
end
end
-
- class ShowCmdsTest < CommandTestCase
- def test_show_cmds
- out, err = execute_lines(
- "show_cmds\n"
- )
-
- assert_empty err
- assert_match(/List all available commands and their description/, out)
- assert_match(/Start the debugger of debug\.gem/, out)
- end
- end
-
class LsTest < CommandTestCase
def test_ls
out, err = execute_lines(
@@ -797,18 +732,18 @@ module TestIRB
def test_ls_grep_empty
out, err = execute_lines("ls\n")
assert_empty err
- assert_match(/whereami/, out)
- assert_match(/show_source/, out)
+ assert_match(/assert/, out)
+ assert_match(/refute/, out)
[
- "ls grep: /whereami/\n",
- "ls -g whereami\n",
- "ls -G whereami\n",
+ "ls grep: /assert/\n",
+ "ls -g assert\n",
+ "ls -G assert\n",
].each do |line|
out, err = execute_lines(line)
assert_empty err
- assert_match(/whereami/, out)
- assert_not_match(/show_source/, out)
+ assert_match(/assert/, out)
+ assert_not_match(/refute/, out)
end
end
@@ -824,22 +759,6 @@ module TestIRB
end
class ShowDocTest < CommandTestCase
- def test_help
- out, err = execute_lines(
- "help String#gsub\n",
- "\n",
- )
-
- # the former is what we'd get without document content installed, like on CI
- # the latter is what we may get locally
- possible_rdoc_output = [/Nothing known about String#gsub/, /gsub\(pattern\)/]
- assert_include err, "[Deprecation] The `help` command will be repurposed to display command help in the future.\n"
- assert(possible_rdoc_output.any? { |output| output.match?(out) }, "Expect the `help` command to match one of the possible outputs. Got:\n#{out}")
- ensure
- # this is the only way to reset the redefined method without coupling the test with its implementation
- EnvUtil.suppress_warning { load "irb/cmd/help.rb" }
- end
-
def test_show_doc
out, err = execute_lines(
"show_doc String#gsub\n",
@@ -853,7 +772,7 @@ module TestIRB
assert(possible_rdoc_output.any? { |output| output.match?(out) }, "Expect the `show_doc` command to match one of the possible outputs. Got:\n#{out}")
ensure
# this is the only way to reset the redefined method without coupling the test with its implementation
- EnvUtil.suppress_warning { load "irb/cmd/help.rb" }
+ EnvUtil.suppress_warning { load "irb/command/help.rb" }
end
def test_show_doc_without_rdoc
@@ -869,18 +788,21 @@ module TestIRB
assert_include(err, "Can't display document because `rdoc` is not installed.\n")
ensure
# this is the only way to reset the redefined method without coupling the test with its implementation
- EnvUtil.suppress_warning { load "irb/cmd/help.rb" }
+ EnvUtil.suppress_warning { load "irb/command/help.rb" }
end
end
class EditTest < CommandTestCase
def setup
+ @original_visual = ENV["VISUAL"]
@original_editor = ENV["EDITOR"]
# noop the command so nothing gets executed
- ENV["EDITOR"] = ": code"
+ ENV["VISUAL"] = ": code"
+ ENV["EDITOR"] = ": code2"
end
def teardown
+ ENV["VISUAL"] = @original_visual
ENV["EDITOR"] = @original_editor
end
@@ -895,6 +817,16 @@ module TestIRB
assert_match("command: ': code'", out)
end
+ def test_edit_without_arg_and_non_existing_irb_path
+ out, err = execute_lines(
+ "edit",
+ irb_path: '/path/to/file.rb(irb)'
+ )
+
+ assert_empty err
+ assert_match(/Can not find file: \/path\/to\/file\.rb\(irb\)/, out)
+ end
+
def test_edit_with_path
out, err = execute_lines(
"edit #{__FILE__}"
@@ -943,5 +875,97 @@ module TestIRB
assert_match(/path: .*\/lib\/irb\.rb/, out)
assert_match("command: ': code'", out)
end
+
+ def test_edit_with_editor_env_var
+ ENV.delete("VISUAL")
+
+ out, err = execute_lines(
+ "edit",
+ irb_path: __FILE__
+ )
+
+ assert_empty err
+ assert_match("path: #{__FILE__}", out)
+ assert_match("command: ': code2'", out)
+ end
+ end
+
+ class HistoryCmdTest < CommandTestCase
+ def teardown
+ TestInputMethod.send(:remove_const, "HISTORY") if defined?(TestInputMethod::HISTORY)
+ super
+ end
+
+ def test_history
+ TestInputMethod.const_set("HISTORY", %w[foo bar baz])
+
+ out, err = without_rdoc do
+ execute_lines("history")
+ end
+
+ assert_include(out, <<~EOF)
+ 2: baz
+ 1: bar
+ 0: foo
+ EOF
+ assert_empty err
+ end
+
+ def test_multiline_history_with_truncation
+ TestInputMethod.const_set("HISTORY", ["foo", "bar", <<~INPUT])
+ [].each do |x|
+ puts x
+ end
+ INPUT
+
+ out, err = without_rdoc do
+ execute_lines("hist")
+ end
+
+ assert_include(out, <<~EOF)
+ 2: [].each do |x|
+ puts x
+ ...
+ 1: bar
+ 0: foo
+ EOF
+ assert_empty err
+ end
+
+ def test_history_grep
+ TestInputMethod.const_set("HISTORY", ["foo", "bar", <<~INPUT])
+ [].each do |x|
+ puts x
+ end
+ INPUT
+
+ out, err = without_rdoc do
+ execute_lines("hist -g each\n")
+ end
+
+ assert_include(out, <<~EOF)
+ 2: [].each do |x|
+ puts x
+ ...
+ EOF
+ assert_empty err
+ end
+
+ end
+
+ class HelperMethodInsallTest < CommandTestCase
+ def test_helper_method_install
+ IRB::ExtendCommandBundle.module_eval do
+ def foobar
+ "test_helper_method_foobar"
+ end
+ end
+
+ out, err = execute_lines("foobar.upcase")
+ assert_empty err
+ assert_include(out, '=> "TEST_HELPER_METHOD_FOOBAR"')
+ ensure
+ IRB::ExtendCommandBundle.remove_method :foobar
+ end
end
end
diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb
index e259428d76..5fe7952b3d 100644
--- a/test/irb/test_completion.rb
+++ b/test/irb/test_completion.rb
@@ -5,77 +5,90 @@ require "irb"
require_relative "helper"
module TestIRB
- class TestCompletion < TestCase
- def setup
- # make sure require completion candidates are not cached
- IRB::InputCompletor.class_variable_set(:@@files_from_load_path, nil)
+ class CompletionTest < TestCase
+ def completion_candidates(target, bind)
+ IRB::RegexpCompletor.new.completion_candidates('', target, '', bind: bind)
+ end
+
+ def doc_namespace(target, bind)
+ IRB::RegexpCompletor.new.doc_namespace('', target, '', bind: bind)
+ end
+
+ class CommandCompletionTest < CompletionTest
+ def test_command_completion
+ assert_include(IRB::RegexpCompletor.new.completion_candidates('', 'show_s', '', bind: binding), 'show_source')
+ assert_not_include(IRB::RegexpCompletor.new.completion_candidates(';', 'show_s', '', bind: binding), 'show_source')
+ end
end
- class TestMethodCompletion < TestCompletion
+ class MethodCompletionTest < CompletionTest
def test_complete_string
- assert_include(IRB::InputCompletor.retrieve_completion_data("'foo'.up", bind: binding), "'foo'.upcase")
+ assert_include(completion_candidates("'foo'.up", binding), "'foo'.upcase")
# completing 'foo bar'.up
- assert_include(IRB::InputCompletor.retrieve_completion_data("bar'.up", bind: binding), "bar'.upcase")
- assert_equal("String.upcase", IRB::InputCompletor.retrieve_completion_data("'foo'.upcase", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("bar'.up", binding), "bar'.upcase")
+ assert_equal("String.upcase", doc_namespace("'foo'.upcase", binding))
end
def test_complete_regexp
- assert_include(IRB::InputCompletor.retrieve_completion_data("/foo/.ma", bind: binding), "/foo/.match")
+ assert_include(completion_candidates("/foo/.ma", binding), "/foo/.match")
# completing /foo bar/.ma
- assert_include(IRB::InputCompletor.retrieve_completion_data("bar/.ma", bind: binding), "bar/.match")
- assert_equal("Regexp.match", IRB::InputCompletor.retrieve_completion_data("/foo/.match", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("bar/.ma", binding), "bar/.match")
+ assert_equal("Regexp.match", doc_namespace("/foo/.match", binding))
end
def test_complete_array
- assert_include(IRB::InputCompletor.retrieve_completion_data("[].an", bind: binding), "[].any?")
- assert_equal("Array.any?", IRB::InputCompletor.retrieve_completion_data("[].any?", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("[].an", binding), "[].any?")
+ assert_equal("Array.any?", doc_namespace("[].any?", binding))
end
def test_complete_hash_and_proc
# hash
- assert_include(IRB::InputCompletor.retrieve_completion_data("{}.an", bind: binding), "{}.any?")
- assert_equal(["Proc.any?", "Hash.any?"], IRB::InputCompletor.retrieve_completion_data("{}.any?", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("{}.an", binding), "{}.any?")
+ assert_equal(["Hash.any?", "Proc.any?"], doc_namespace("{}.any?", binding))
# proc
- assert_include(IRB::InputCompletor.retrieve_completion_data("{}.bin", bind: binding), "{}.binding")
- assert_equal(["Proc.binding", "Hash.binding"], IRB::InputCompletor.retrieve_completion_data("{}.binding", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("{}.bin", binding), "{}.binding")
+ assert_equal(["Hash.binding", "Proc.binding"], doc_namespace("{}.binding", binding))
end
def test_complete_numeric
- assert_include(IRB::InputCompletor.retrieve_completion_data("1.positi", bind: binding), "1.positive?")
- assert_equal("Integer.positive?", IRB::InputCompletor.retrieve_completion_data("1.positive?", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("1.positi", binding), "1.positive?")
+ assert_equal("Integer.positive?", doc_namespace("1.positive?", binding))
- assert_include(IRB::InputCompletor.retrieve_completion_data("1r.positi", bind: binding), "1r.positive?")
- assert_equal("Rational.positive?", IRB::InputCompletor.retrieve_completion_data("1r.positive?", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("1r.positi", binding), "1r.positive?")
+ assert_equal("Rational.positive?", doc_namespace("1r.positive?", binding))
- assert_include(IRB::InputCompletor.retrieve_completion_data("0xFFFF.positi", bind: binding), "0xFFFF.positive?")
- assert_equal("Integer.positive?", IRB::InputCompletor.retrieve_completion_data("0xFFFF.positive?", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("0xFFFF.positi", binding), "0xFFFF.positive?")
+ assert_equal("Integer.positive?", doc_namespace("0xFFFF.positive?", binding))
- assert_empty(IRB::InputCompletor.retrieve_completion_data("1i.positi", bind: binding))
+ assert_empty(completion_candidates("1i.positi", binding))
end
def test_complete_symbol
- assert_include(IRB::InputCompletor.retrieve_completion_data(":foo.to_p", bind: binding), ":foo.to_proc")
- assert_equal("Symbol.to_proc", IRB::InputCompletor.retrieve_completion_data(":foo.to_proc", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates(":foo.to_p", binding), ":foo.to_proc")
+ assert_equal("Symbol.to_proc", doc_namespace(":foo.to_proc", binding))
end
def test_complete_class
- assert_include(IRB::InputCompletor.retrieve_completion_data("String.ne", bind: binding), "String.new")
- assert_equal("String.new", IRB::InputCompletor.retrieve_completion_data("String.new", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("String.ne", binding), "String.new")
+ assert_equal("String.new", doc_namespace("String.new", binding))
end
end
- class TestRequireComepletion < TestCompletion
+ class RequireComepletionTest < CompletionTest
def test_complete_require
- candidates = IRB::InputCompletor::CompletionProc.("'irb", "require ", "")
+ candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'irb", "", bind: binding)
%w['irb/init 'irb/ruby-lex].each do |word|
assert_include candidates, word
end
# Test cache
- candidates = IRB::InputCompletor::CompletionProc.("'irb", "require ", "")
+ candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'irb", "", bind: binding)
%w['irb/init 'irb/ruby-lex].each do |word|
assert_include candidates, word
end
+ # Test string completion not disturbed by require completion
+ candidates = IRB::RegexpCompletor.new.completion_candidates("'string ", "'.", "", bind: binding)
+ assert_include candidates, "'.upcase"
end
def test_complete_require_with_pathname_in_load_path
@@ -84,7 +97,7 @@ module TestIRB
test_path = Pathname.new(temp_dir)
$LOAD_PATH << test_path
- candidates = IRB::InputCompletor::CompletionProc.("'foo", "require ", "")
+ candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'foo", "", bind: binding)
assert_include candidates, "'foo"
ensure
$LOAD_PATH.pop if test_path
@@ -98,7 +111,7 @@ module TestIRB
object.define_singleton_method(:to_s) { temp_dir }
$LOAD_PATH << object
- candidates = IRB::InputCompletor::CompletionProc.("'foo", "require ", "")
+ candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'foo", "", bind: binding)
assert_include candidates, "'foo"
ensure
$LOAD_PATH.pop if object
@@ -111,27 +124,28 @@ module TestIRB
$LOAD_PATH << object
assert_nothing_raised do
- IRB::InputCompletor::CompletionProc.("'foo", "require ", "")
+ IRB::RegexpCompletor.new.completion_candidates("require ", "'foo", "", bind: binding)
end
ensure
$LOAD_PATH.pop if object
end
def test_complete_require_library_name_first
- candidates = IRB::InputCompletor::CompletionProc.("'csv", "require ", "")
- assert_equal "'csv", candidates.first
+ # Test that library name is completed first with subdirectories
+ candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'irb", "", bind: binding)
+ assert_equal "'irb", candidates.first
end
def test_complete_require_relative
candidates = Dir.chdir(__dir__ + "/../..") do
- IRB::InputCompletor::CompletionProc.("'lib/irb", "require_relative ", "")
+ IRB::RegexpCompletor.new.completion_candidates("require_relative ", "'lib/irb", "", bind: binding)
end
%w['lib/irb/init 'lib/irb/ruby-lex].each do |word|
assert_include candidates, word
end
# Test cache
candidates = Dir.chdir(__dir__ + "/../..") do
- IRB::InputCompletor::CompletionProc.("'lib/irb", "require_relative ", "")
+ IRB::RegexpCompletor.new.completion_candidates("require_relative ", "'lib/irb", "", bind: binding)
end
%w['lib/irb/init 'lib/irb/ruby-lex].each do |word|
assert_include candidates, word
@@ -139,7 +153,7 @@ module TestIRB
end
end
- class TestVariableCompletion < TestCompletion
+ class VariableCompletionTest < CompletionTest
def test_complete_variable
# Bug fix issues https://github.com/ruby/irb/issues/368
# Variables other than `str_example` and `@str_example` are defined to ensure that irb completion does not cause unintended behavior
@@ -160,13 +174,13 @@ module TestIRB
local_variables.clear
instance_variables.clear
- assert_include(IRB::InputCompletor.retrieve_completion_data("str_examp", bind: binding), "str_example")
- assert_equal("String", IRB::InputCompletor.retrieve_completion_data("str_example", bind: binding, doc_namespace: true))
- assert_equal("String.to_s", IRB::InputCompletor.retrieve_completion_data("str_example.to_s", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("str_examp", binding), "str_example")
+ assert_equal("String", doc_namespace("str_example", binding))
+ assert_equal("String.to_s", doc_namespace("str_example.to_s", binding))
- assert_include(IRB::InputCompletor.retrieve_completion_data("@str_examp", bind: binding), "@str_example")
- assert_equal("String", IRB::InputCompletor.retrieve_completion_data("@str_example", bind: binding, doc_namespace: true))
- assert_equal("String.to_s", IRB::InputCompletor.retrieve_completion_data("@str_example.to_s", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("@str_examp", binding), "@str_example")
+ assert_equal("String", doc_namespace("@str_example", binding))
+ assert_equal("String.to_s", doc_namespace("@str_example.to_s", binding))
end
def test_complete_sort_variables
@@ -176,12 +190,12 @@ module TestIRB
xzy_1.clear
xzy2.clear
- candidates = IRB::InputCompletor.retrieve_completion_data("xz", bind: binding, doc_namespace: false)
+ candidates = completion_candidates("xz", binding)
assert_equal(%w[xzy xzy2 xzy_1], candidates)
end
end
- class TestConstantCompletion < TestCompletion
+ class ConstantCompletionTest < CompletionTest
class Foo
B3 = 1
B1 = 1
@@ -189,136 +203,56 @@ module TestIRB
end
def test_complete_constants
- assert_equal(["Foo"], IRB::InputCompletor.retrieve_completion_data("Fo", bind: binding))
- assert_equal(["Foo::B1", "Foo::B2", "Foo::B3"], IRB::InputCompletor.retrieve_completion_data("Foo::B", bind: binding))
- assert_equal(["Foo::B1.positive?"], IRB::InputCompletor.retrieve_completion_data("Foo::B1.pos", bind: binding))
+ assert_equal(["Foo"], completion_candidates("Fo", binding))
+ assert_equal(["Foo::B1", "Foo::B2", "Foo::B3"], completion_candidates("Foo::B", binding))
+ assert_equal(["Foo::B1.positive?"], completion_candidates("Foo::B1.pos", binding))
- assert_equal(["::Forwardable"], IRB::InputCompletor.retrieve_completion_data("::Fo", bind: binding))
- assert_equal("Forwardable", IRB::InputCompletor.retrieve_completion_data("::Forwardable", bind: binding, doc_namespace: true))
+ assert_equal(["::Forwardable"], completion_candidates("::Fo", binding))
+ assert_equal("Forwardable", doc_namespace("::Forwardable", binding))
end
end
- class TestPerfectMatching < TestCompletion
- def setup
- # trigger PerfectMatchedProc to set up RDocRIDriver constant
- IRB::InputCompletor::PerfectMatchedProc.("foo", bind: binding)
-
- @original_use_stdout = IRB::InputCompletor::RDocRIDriver.use_stdout
- # force the driver to use stdout so it doesn't start a pager and interrupt tests
- IRB::InputCompletor::RDocRIDriver.use_stdout = true
- end
-
- def teardown
- IRB::InputCompletor::RDocRIDriver.use_stdout = @original_use_stdout
- end
-
- def test_perfectly_matched_namespace_triggers_document_display
- omit unless has_rdoc_content?
-
- out, err = capture_output do
- IRB::InputCompletor::PerfectMatchedProc.("String", bind: binding)
- end
-
- assert_empty(err)
-
- assert_include(out, " S\bSt\btr\bri\bin\bng\bg")
- end
-
- def test_perfectly_matched_multiple_namespaces_triggers_document_display
- result = nil
- out, err = capture_output do
- result = IRB::InputCompletor::PerfectMatchedProc.("{}.nil?", bind: binding)
- end
-
- assert_empty(err)
-
- # check if there're rdoc contents (e.g. CI doesn't generate them)
- if has_rdoc_content?
- # if there's rdoc content, we can verify by checking stdout
- # rdoc generates control characters for formatting method names
- assert_include(out, "P\bPr\bro\boc\bc.\b.n\bni\bil\bl?\b?") # Proc.nil?
- assert_include(out, "H\bHa\bas\bsh\bh.\b.n\bni\bil\bl?\b?") # Hash.nil?
- else
- # this is a hacky way to verify the rdoc rendering code path because CI doesn't have rdoc content
- # if there are multiple namespaces to be rendered, PerfectMatchedProc renders the result with a document
- # which always returns the bytes rendered, even if it's 0
- assert_equal(0, result)
- end
- end
-
- def test_not_matched_namespace_triggers_nothing
- result = nil
- out, err = capture_output do
- result = IRB::InputCompletor::PerfectMatchedProc.("Stri", bind: binding)
- end
-
- assert_empty(err)
- assert_empty(out)
- assert_nil(result)
- end
-
- def test_perfect_matching_stops_without_rdoc
- result = nil
-
- out, err = capture_output do
- without_rdoc do
- result = IRB::InputCompletor::PerfectMatchedProc.("String", bind: binding)
- end
- end
-
- assert_empty(err)
- assert_not_match(/from ruby core/, out)
- assert_nil(result)
- end
-
- def test_perfect_matching_handles_nil_namespace
- out, err = capture_output do
- # symbol literal has `nil` doc namespace so it's a good test subject
- assert_nil(IRB::InputCompletor::PerfectMatchedProc.(":aiueo", bind: binding))
- end
-
- assert_empty(err)
- assert_empty(out)
- end
-
- private
-
- def has_rdoc_content?
- File.exist?(RDoc::RI::Paths::BASE)
- end
+ def test_not_completing_empty_string
+ assert_equal([], completion_candidates("", binding))
+ assert_equal([], completion_candidates(" ", binding))
+ assert_equal([], completion_candidates("\t", binding))
+ assert_equal(nil, doc_namespace("", binding))
end
def test_complete_symbol
- %w"UTF-16LE UTF-7".each do |enc|
+ symbols = %w"UTF-16LE UTF-7".map do |enc|
"K".force_encoding(enc).to_sym
rescue
end
- _ = :aiueo
- assert_include(IRB::InputCompletor.retrieve_completion_data(":a", bind: binding), ":aiueo")
- assert_empty(IRB::InputCompletor.retrieve_completion_data(":irb_unknown_symbol_abcdefg", bind: binding))
+ symbols += [:aiueo, :"aiu eo"]
+ candidates = completion_candidates(":a", binding)
+ assert_include(candidates, ":aiueo")
+ assert_not_include(candidates, ":aiu eo")
+ assert_empty(completion_candidates(":irb_unknown_symbol_abcdefg", binding))
# Do not complete empty symbol for performance reason
- assert_empty(IRB::InputCompletor.retrieve_completion_data(":", bind: binding))
+ assert_empty(completion_candidates(":", binding))
end
def test_complete_invalid_three_colons
- assert_empty(IRB::InputCompletor.retrieve_completion_data(":::A", bind: binding))
- assert_empty(IRB::InputCompletor.retrieve_completion_data(":::", bind: binding))
+ assert_empty(completion_candidates(":::A", binding))
+ assert_empty(completion_candidates(":::", binding))
end
def test_complete_absolute_constants_with_special_characters
- assert_empty(IRB::InputCompletor.retrieve_completion_data("::A:", bind: binding))
- assert_empty(IRB::InputCompletor.retrieve_completion_data("::A.", bind: binding))
- assert_empty(IRB::InputCompletor.retrieve_completion_data("::A(", bind: binding))
- assert_empty(IRB::InputCompletor.retrieve_completion_data("::A)", bind: binding))
+ assert_empty(completion_candidates("::A:", binding))
+ assert_empty(completion_candidates("::A.", binding))
+ assert_empty(completion_candidates("::A(", binding))
+ assert_empty(completion_candidates("::A)", binding))
+ assert_empty(completion_candidates("::A[", binding))
end
def test_complete_reserved_words
- candidates = IRB::InputCompletor.retrieve_completion_data("de", bind: binding)
+ candidates = completion_candidates("de", binding)
%w[def defined?].each do |word|
assert_include candidates, word
end
- candidates = IRB::InputCompletor.retrieve_completion_data("__", bind: binding)
+ candidates = completion_candidates("__", binding)
%w[__ENCODING__ __LINE__ __FILE__].each do |word|
assert_include candidates, word
end
@@ -339,11 +273,39 @@ module TestIRB
}
bind = obj.instance_exec { binding }
- assert_include(IRB::InputCompletor.retrieve_completion_data("public_hog", bind: bind), "public_hoge")
- assert_include(IRB::InputCompletor.retrieve_completion_data("public_hoge", bind: bind, doc_namespace: true), "public_hoge")
+ assert_include(completion_candidates("public_hog", bind), "public_hoge")
+ assert_include(doc_namespace("public_hoge", bind), "public_hoge")
+
+ assert_include(completion_candidates("private_hog", bind), "private_hoge")
+ assert_include(doc_namespace("private_hoge", bind), "private_hoge")
+ end
+ end
+
+ class DeprecatedInputCompletorTest < TestCase
+ def setup
+ save_encodings
+ @verbose, $VERBOSE = $VERBOSE, nil
+ IRB.init_config(nil)
+ IRB.conf[:VERBOSE] = false
+ IRB.conf[:MAIN_CONTEXT] = IRB::Context.new(IRB::WorkSpace.new(binding))
+ end
+
+ def teardown
+ restore_encodings
+ $VERBOSE = @verbose
+ end
+
+ def test_completion_proc
+ assert_include(IRB::InputCompletor::CompletionProc.call('1.ab'), '1.abs')
+ assert_include(IRB::InputCompletor::CompletionProc.call('1.ab', '', ''), '1.abs')
+ end
- assert_include(IRB::InputCompletor.retrieve_completion_data("private_hog", bind: bind), "private_hoge")
- assert_include(IRB::InputCompletor.retrieve_completion_data("private_hoge", bind: bind, doc_namespace: true), "private_hoge")
+ def test_retrieve_completion_data
+ assert_include(IRB::InputCompletor.retrieve_completion_data('1.ab'), '1.abs')
+ assert_equal(IRB::InputCompletor.retrieve_completion_data('1.abs', doc_namespace: true), 'Integer.abs')
+ bind = eval('a = 1; binding')
+ assert_include(IRB::InputCompletor.retrieve_completion_data('a.ab', bind: bind), 'a.abs')
+ assert_equal(IRB::InputCompletor.retrieve_completion_data('a.abs', bind: bind, doc_namespace: true), 'Integer.abs')
end
end
end
diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb
index c4ca56aba6..cd3f2c8f62 100644
--- a/test/irb/test_context.rb
+++ b/test/irb/test_context.rb
@@ -1,16 +1,16 @@
# frozen_string_literal: false
require 'tempfile'
require 'irb'
-require 'rubygems' if defined?(Gem)
require_relative "helper"
module TestIRB
- class TestContext < TestCase
+ class ContextTest < TestCase
def setup
IRB.init_config(nil)
IRB.conf[:USE_SINGLELINE] = false
IRB.conf[:VERBOSE] = false
+ IRB.conf[:USE_PAGER] = false
workspace = IRB::WorkSpace.new(Object.new)
@context = IRB::Context.new(nil, workspace, TestInputMethod.new)
@@ -28,41 +28,6 @@ module TestIRB
restore_encodings
end
- def test_last_value
- assert_nil(@context.last_value)
- assert_nil(@context.evaluate('_', 1))
- obj = Object.new
- @context.set_last_value(obj)
- assert_same(obj, @context.last_value)
- assert_same(obj, @context.evaluate('_', 1))
- end
-
- def test_evaluate_with_exception
- assert_nil(@context.evaluate("$!", 1))
- e = assert_raise_with_message(RuntimeError, 'foo') {
- @context.evaluate("raise 'foo'", 1)
- }
- assert_equal('foo', e.message)
- assert_same(e, @context.evaluate('$!', 1, exception: e))
- e = assert_raise(SyntaxError) {
- @context.evaluate("1,2,3", 1, exception: e)
- }
- assert_match(/\A\(irb\):1:/, e.message)
- assert_not_match(/rescue _\.class/, e.message)
- end
-
- def test_evaluate_with_encoding_error_without_lineno
- assert_raise_with_message(EncodingError, /invalid symbol/) {
- @context.evaluate(%q[:"\xAE"], 1)
- # The backtrace of this invalid encoding hash doesn't contain lineno.
- }
- end
-
- def test_evaluate_still_emits_warning
- assert_warning("(irb):1: warning: END in method; use at_exit\n") do
- @context.evaluate(%q[def foo; END {}; end], 1)
- end
- end
def test_eval_input
verbose, $VERBOSE = $VERBOSE, nil
@@ -77,11 +42,27 @@ module TestIRB
irb.eval_input
end
assert_empty err
- assert_pattern_list([:*, /\(irb\):1:in `<main>': Foo \(RuntimeError\)\n/,
- :*, /#<RuntimeError: Foo>\n/,
- :*, /0$/,
- :*, /0$/,
- /\s*/], out)
+
+ expected_output =
+ if RUBY_3_4
+ [
+ :*, /\(irb\):1:in '<main>': Foo \(RuntimeError\)\n/,
+ :*, /#<RuntimeError: Foo>\n/,
+ :*, /0$/,
+ :*, /0$/,
+ /\s*/
+ ]
+ else
+ [
+ :*, /\(irb\):1:in `<main>': Foo \(RuntimeError\)\n/,
+ :*, /#<RuntimeError: Foo>\n/,
+ :*, /0$/,
+ :*, /0$/,
+ /\s*/
+ ]
+ end
+
+ assert_pattern_list(expected_output, out)
ensure
$VERBOSE = verbose
end
@@ -97,11 +78,33 @@ module TestIRB
irb.eval_input
end
assert_empty err
- assert_pattern_list([
- :*, /\(irb\):1:in `<main>': Foo \(RuntimeError\)\n/,
- :*, /\(irb\):2:in `<main>': Bar \(RuntimeError\)\n/,
- :*, /#<RuntimeError: Bar>\n/,
- ], out)
+ expected_output =
+ if RUBY_3_4
+ [
+ :*, /\(irb\):1:in '<main>': Foo \(RuntimeError\)\n/,
+ :*, /\(irb\):2:in '<main>': Bar \(RuntimeError\)\n/,
+ :*, /#<RuntimeError: Bar>\n/,
+ ]
+ else
+ [
+ :*, /\(irb\):1:in `<main>': Foo \(RuntimeError\)\n/,
+ :*, /\(irb\):2:in `<main>': Bar \(RuntimeError\)\n/,
+ :*, /#<RuntimeError: Bar>\n/,
+ ]
+ end
+ assert_pattern_list(expected_output, out)
+ end
+
+ def test_prompt_n_deprecation
+ irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), TestInputMethod.new)
+
+ _, err = capture_output do
+ irb.context.prompt_n = "foo"
+ irb.context.prompt_n
+ end
+
+ assert_include err, "IRB::Context#prompt_n is deprecated"
+ assert_include err, "IRB::Context#prompt_n= is deprecated"
end
def test_output_to_pipe
@@ -127,9 +130,9 @@ module TestIRB
[:marshal, "123", Marshal.dump(123)],
],
failed: [
- [false, "BasicObject.new", /#<NoMethodError: undefined method `to_s' for/],
- [:p, "class Foo; undef inspect ;end; Foo.new", /#<NoMethodError: undefined method `inspect' for/],
- [:yaml, "BasicObject.new", /#<NoMethodError: undefined method `inspect' for/],
+ [false, "BasicObject.new", /#<NoMethodError: undefined method (`|')to_s' for/],
+ [:p, "class Foo; undef inspect ;end; Foo.new", /#<NoMethodError: undefined method (`|')inspect' for/],
+ [:yaml, "BasicObject.new", /#<NoMethodError: undefined method (`|')inspect' for/],
[:marshal, "[Object.new, Class.new]", /#<TypeError: can't dump anonymous class #<Class:/]
]
}.each do |scenario, cases|
@@ -207,7 +210,7 @@ module TestIRB
end
assert_empty err
assert_match(/An error occurred when inspecting the object: #<RuntimeError: foo>/, out)
- assert_match(/An error occurred when running Kernel#inspect: #<NoMethodError: undefined method `inspect' for/, out)
+ assert_match(/An error occurred when running Kernel#inspect: #<NoMethodError: undefined method (`|')inspect' for/, out)
ensure
$VERBOSE = verbose
end
@@ -216,59 +219,6 @@ module TestIRB
assert_equal(true, @context.use_autocomplete?)
end
- def test_assignment_expression
- input = TestInputMethod.new
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
- [
- "foo = bar",
- "@foo = bar",
- "$foo = bar",
- "@@foo = bar",
- "::Foo = bar",
- "a::Foo = bar",
- "Foo = bar",
- "foo.bar = 1",
- "foo[1] = bar",
- "foo += bar",
- "foo -= bar",
- "foo ||= bar",
- "foo &&= bar",
- "foo, bar = 1, 2",
- "foo.bar=(1)",
- "foo; foo = bar",
- "foo; foo = bar; ;\n ;",
- "foo\nfoo = bar",
- ].each do |exp|
- assert(
- irb.assignment_expression?(exp),
- "#{exp.inspect}: should be an assignment expression"
- )
- end
-
- [
- "foo",
- "foo.bar",
- "foo[0]",
- "foo = bar; foo",
- "foo = bar\nfoo",
- ].each do |exp|
- refute(
- irb.assignment_expression?(exp),
- "#{exp.inspect}: should not be an assignment expression"
- )
- end
- end
-
- def test_assignment_expression_with_local_variable
- input = TestInputMethod.new
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
- code = "a /1;x=1#/"
- refute(irb.assignment_expression?(code), "#{code}: should not be an assignment expression")
- irb.context.workspace.binding.eval('a = 1')
- assert(irb.assignment_expression?(code), "#{code}: should be an assignment expression")
- refute(irb.assignment_expression?(""), "empty code should not be an assignment expression")
- end
-
def test_echo_on_assignment
input = TestInputMethod.new([
"a = 1\n",
@@ -403,7 +353,7 @@ module TestIRB
end
assert_empty err
assert_equal("=> \n#{value}\n", out)
- irb.context.evaluate('A.remove_method(:inspect)', 0)
+ irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
input.reset
irb.context.echo = true
@@ -413,7 +363,7 @@ module TestIRB
end
assert_empty err
assert_equal("=> #{value_first_line[0..(input.winsize.last - 9)]}...\n=> \n#{value}\n", out)
- irb.context.evaluate('A.remove_method(:inspect)', 0)
+ irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
input.reset
irb.context.echo = true
@@ -423,7 +373,7 @@ module TestIRB
end
assert_empty err
assert_equal("=> \n#{value}\n=> \n#{value}\n", out)
- irb.context.evaluate('A.remove_method(:inspect)', 0)
+ irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
input.reset
irb.context.echo = false
@@ -433,7 +383,7 @@ module TestIRB
end
assert_empty err
assert_equal("", out)
- irb.context.evaluate('A.remove_method(:inspect)', 0)
+ irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
input.reset
irb.context.echo = false
@@ -443,7 +393,7 @@ module TestIRB
end
assert_empty err
assert_equal("", out)
- irb.context.evaluate('A.remove_method(:inspect)', 0)
+ irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
input.reset
irb.context.echo = false
@@ -453,7 +403,7 @@ module TestIRB
end
assert_empty err
assert_equal("", out)
- irb.context.evaluate('A.remove_method(:inspect)', 0)
+ irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
end
end
@@ -521,7 +471,6 @@ module TestIRB
def test_default_return_format
IRB.conf[:PROMPT][:MY_PROMPT] = {
:PROMPT_I => "%03n> ",
- :PROMPT_N => "%03n> ",
:PROMPT_S => "%03n> ",
:PROMPT_C => "%03n> "
# without :RETURN
@@ -551,22 +500,30 @@ module TestIRB
irb.eval_input
end
assert_empty err
- if RUBY_VERSION < '3.0.0' && STDOUT.tty?
- expected = [
- :*, /Traceback \(most recent call last\):\n/,
- :*, /\t 2: from \(irb\):1:in `<main>'\n/,
- :*, /\t 1: from \(irb\):1:in `hoge'\n/,
- :*, /\(irb\):1:in `fuga': unhandled exception\n/,
- ]
- else
- expected = [
- :*, /\(irb\):1:in `fuga': unhandled exception\n/,
- :*, /\tfrom \(irb\):1:in `hoge'\n/,
- :*, /\tfrom \(irb\):1:in `<main>'\n/,
- :*
- ]
- end
- assert_pattern_list(expected, out)
+ expected_output =
+ if RUBY_3_4
+ [
+ :*, /\(irb\):1:in 'fuga': unhandled exception\n/,
+ :*, /\tfrom \(irb\):1:in 'hoge'\n/,
+ :*, /\tfrom \(irb\):1:in '<main>'\n/,
+ :*
+ ]
+ elsif RUBY_VERSION < '3.0.0' && STDOUT.tty?
+ [
+ :*, /Traceback \(most recent call last\):\n/,
+ :*, /\t 2: from \(irb\):1:in `<main>'\n/,
+ :*, /\t 1: from \(irb\):1:in `hoge'\n/,
+ :*, /\(irb\):1:in `fuga': unhandled exception\n/,
+ ]
+ else
+ [
+ :*, /\(irb\):1:in `fuga': unhandled exception\n/,
+ :*, /\tfrom \(irb\):1:in `hoge'\n/,
+ :*, /\tfrom \(irb\):1:in `<main>'\n/,
+ :*
+ ]
+ end
+ assert_pattern_list(expected_output, out)
ensure
$VERBOSE = verbose
end
@@ -581,22 +538,31 @@ module TestIRB
irb.eval_input
end
assert_empty err
- if RUBY_VERSION < '3.0.0' && STDOUT.tty?
- expected = [
- :*, /Traceback \(most recent call last\):\n/,
- :*, /\t 2: from \(irb\):1:in `<main>'\n/,
- :*, /\t 1: from \(irb\):1:in `hoge'\n/,
- :*, /\(irb\):1:in `fuga': A\\xF3B \(RuntimeError\)\n/,
- ]
- else
- expected = [
- :*, /\(irb\):1:in `fuga': A\\xF3B \(RuntimeError\)\n/,
- :*, /\tfrom \(irb\):1:in `hoge'\n/,
- :*, /\tfrom \(irb\):1:in `<main>'\n/,
- :*
- ]
- end
- assert_pattern_list(expected, out)
+ expected_output =
+ if RUBY_3_4
+ [
+ :*, /\(irb\):1:in 'fuga': A\\xF3B \(RuntimeError\)\n/,
+ :*, /\tfrom \(irb\):1:in 'hoge'\n/,
+ :*, /\tfrom \(irb\):1:in '<main>'\n/,
+ :*
+ ]
+ elsif RUBY_VERSION < '3.0.0' && STDOUT.tty?
+ [
+ :*, /Traceback \(most recent call last\):\n/,
+ :*, /\t 2: from \(irb\):1:in `<main>'\n/,
+ :*, /\t 1: from \(irb\):1:in `hoge'\n/,
+ :*, /\(irb\):1:in `fuga': A\\xF3B \(RuntimeError\)\n/,
+ ]
+ else
+ [
+ :*, /\(irb\):1:in `fuga': A\\xF3B \(RuntimeError\)\n/,
+ :*, /\tfrom \(irb\):1:in `hoge'\n/,
+ :*, /\tfrom \(irb\):1:in `<main>'\n/,
+ :*
+ ]
+ end
+
+ assert_pattern_list(expected_output, out)
ensure
$VERBOSE = verbose
end
@@ -622,43 +588,43 @@ module TestIRB
expected = [
:*, /Traceback \(most recent call last\):\n/,
:*, /\t... \d+ levels...\n/,
- :*, /\t16: from \(irb\):1:in `a4'\n/,
- :*, /\t15: from \(irb\):1:in `a5'\n/,
- :*, /\t14: from \(irb\):1:in `a6'\n/,
- :*, /\t13: from \(irb\):1:in `a7'\n/,
- :*, /\t12: from \(irb\):1:in `a8'\n/,
- :*, /\t11: from \(irb\):1:in `a9'\n/,
- :*, /\t10: from \(irb\):1:in `a10'\n/,
- :*, /\t 9: from \(irb\):1:in `a11'\n/,
- :*, /\t 8: from \(irb\):1:in `a12'\n/,
- :*, /\t 7: from \(irb\):1:in `a13'\n/,
- :*, /\t 6: from \(irb\):1:in `a14'\n/,
- :*, /\t 5: from \(irb\):1:in `a15'\n/,
- :*, /\t 4: from \(irb\):1:in `a16'\n/,
- :*, /\t 3: from \(irb\):1:in `a17'\n/,
- :*, /\t 2: from \(irb\):1:in `a18'\n/,
- :*, /\t 1: from \(irb\):1:in `a19'\n/,
- :*, /\(irb\):1:in `a20': unhandled exception\n/,
+ :*, /\t16: from \(irb\):1:in (`|')a4'\n/,
+ :*, /\t15: from \(irb\):1:in (`|')a5'\n/,
+ :*, /\t14: from \(irb\):1:in (`|')a6'\n/,
+ :*, /\t13: from \(irb\):1:in (`|')a7'\n/,
+ :*, /\t12: from \(irb\):1:in (`|')a8'\n/,
+ :*, /\t11: from \(irb\):1:in (`|')a9'\n/,
+ :*, /\t10: from \(irb\):1:in (`|')a10'\n/,
+ :*, /\t 9: from \(irb\):1:in (`|')a11'\n/,
+ :*, /\t 8: from \(irb\):1:in (`|')a12'\n/,
+ :*, /\t 7: from \(irb\):1:in (`|')a13'\n/,
+ :*, /\t 6: from \(irb\):1:in (`|')a14'\n/,
+ :*, /\t 5: from \(irb\):1:in (`|')a15'\n/,
+ :*, /\t 4: from \(irb\):1:in (`|')a16'\n/,
+ :*, /\t 3: from \(irb\):1:in (`|')a17'\n/,
+ :*, /\t 2: from \(irb\):1:in (`|')a18'\n/,
+ :*, /\t 1: from \(irb\):1:in (`|')a19'\n/,
+ :*, /\(irb\):1:in (`|')a20': unhandled exception\n/,
]
else
expected = [
- :*, /\(irb\):1:in `a20': unhandled exception\n/,
- :*, /\tfrom \(irb\):1:in `a19'\n/,
- :*, /\tfrom \(irb\):1:in `a18'\n/,
- :*, /\tfrom \(irb\):1:in `a17'\n/,
- :*, /\tfrom \(irb\):1:in `a16'\n/,
- :*, /\tfrom \(irb\):1:in `a15'\n/,
- :*, /\tfrom \(irb\):1:in `a14'\n/,
- :*, /\tfrom \(irb\):1:in `a13'\n/,
- :*, /\tfrom \(irb\):1:in `a12'\n/,
- :*, /\tfrom \(irb\):1:in `a11'\n/,
- :*, /\tfrom \(irb\):1:in `a10'\n/,
- :*, /\tfrom \(irb\):1:in `a9'\n/,
- :*, /\tfrom \(irb\):1:in `a8'\n/,
- :*, /\tfrom \(irb\):1:in `a7'\n/,
- :*, /\tfrom \(irb\):1:in `a6'\n/,
- :*, /\tfrom \(irb\):1:in `a5'\n/,
- :*, /\tfrom \(irb\):1:in `a4'\n/,
+ :*, /\(irb\):1:in (`|')a20': unhandled exception\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a19'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a18'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a17'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a16'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a15'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a14'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a13'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a12'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a11'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a10'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a9'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a8'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a7'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a6'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a5'\n/,
+ :*, /\tfrom \(irb\):1:in (`|')a4'\n/,
:*, /\t... \d+ levels...\n/,
]
end
@@ -670,21 +636,38 @@ module TestIRB
def test_prompt_main_escape
main = Struct.new(:to_s).new("main\a\t\r\n")
irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new)
- assert_equal("irb(main )>", irb.prompt('irb(%m)>', nil, 1, 1))
+ assert_equal("irb(main )>", irb.send(:format_prompt, 'irb(%m)>', nil, 1, 1))
end
def test_prompt_main_inspect_escape
main = Struct.new(:inspect).new("main\\n\nmain")
irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new)
- assert_equal("irb(main\\n main)>", irb.prompt('irb(%M)>', nil, 1, 1))
+ assert_equal("irb(main\\n main)>", irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1))
end
def test_prompt_main_truncate
main = Struct.new(:to_s).new("a" * 100)
def main.inspect; to_s.inspect; end
irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new)
- assert_equal('irb(aaaaaaaaaaaaaaaaaaaaaaaaaaaaa...)>', irb.prompt('irb(%m)>', nil, 1, 1))
- assert_equal('irb("aaaaaaaaaaaaaaaaaaaaaaaaaaaa...)>', irb.prompt('irb(%M)>', nil, 1, 1))
+ assert_equal('irb(aaaaaaaaaaaaaaaaaaaaaaaaaaaaa...)>', irb.send(:format_prompt, 'irb(%m)>', nil, 1, 1))
+ assert_equal('irb("aaaaaaaaaaaaaaaaaaaaaaaaaaaa...)>', irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1))
+ end
+
+ def test_prompt_main_raise
+ main = Object.new
+ def main.to_s; raise TypeError; end
+ def main.inspect; raise ArgumentError; end
+ irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new)
+ assert_equal("irb(!TypeError)>", irb.send(:format_prompt, 'irb(%m)>', nil, 1, 1))
+ assert_equal("irb(!ArgumentError)>", irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1))
+ end
+
+ def test_prompt_format
+ main = 'main'
+ irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new)
+ assert_equal('%% main %m %main %%m >', irb.send(:format_prompt, '%%%% %m %%m %%%m %%%%m %l', '>', 1, 1))
+ assert_equal('42,%i, 42,%3i,042,%03i', irb.send(:format_prompt, '%i,%%i,%3i,%%3i,%03i,%%03i', nil, 42, 1))
+ assert_equal('42,%n, 42,%3n,042,%03n', irb.send(:format_prompt, '%n,%%n,%3n,%%3n,%03n,%%03n', nil, 1, 42))
end
def test_lineno
@@ -708,6 +691,31 @@ module TestIRB
], out)
end
+ def test_irb_path_setter
+ @context.irb_path = __FILE__
+ assert_equal(__FILE__, @context.irb_path)
+ assert_equal("#{__FILE__}(irb)", @context.instance_variable_get(:@eval_path))
+ @context.irb_path = 'file/does/not/exist'
+ assert_equal('file/does/not/exist', @context.irb_path)
+ assert_equal('file/does/not/exist', @context.instance_variable_get(:@eval_path))
+ @context.irb_path = "#{__FILE__}(irb)"
+ assert_equal("#{__FILE__}(irb)", @context.irb_path)
+ assert_equal("#{__FILE__}(irb)", @context.instance_variable_get(:@eval_path))
+ end
+
+ def test_build_completor
+ verbose, $VERBOSE = $VERBOSE, nil
+ original_completor = IRB.conf[:COMPLETOR]
+ IRB.conf[:COMPLETOR] = :regexp
+ assert_equal 'IRB::RegexpCompletor', @context.send(:build_completor).class.name
+ IRB.conf[:COMPLETOR] = :unknown
+ assert_equal 'IRB::RegexpCompletor', @context.send(:build_completor).class.name
+ # :type is tested in test_type_completor.rb
+ ensure
+ $VERBOSE = verbose
+ IRB.conf[:COMPLETOR] = original_completor
+ end
+
private
def without_colorize
diff --git a/test/irb/test_debug_cmd.rb b/test/irb/test_debug_cmd.rb
deleted file mode 100644
index 3bc00638dd..0000000000
--- a/test/irb/test_debug_cmd.rb
+++ /dev/null
@@ -1,299 +0,0 @@
-# frozen_string_literal: true
-
-begin
- require "pty"
-rescue LoadError
- return
-end
-
-require "tempfile"
-require "tmpdir"
-
-require_relative "helper"
-
-module TestIRB
- LIB = File.expand_path("../../lib", __dir__)
-
- class DebugCommandTestCase < TestCase
- IRB_AND_DEBUGGER_OPTIONS = {
- "NO_COLOR" => "true", "RUBY_DEBUG_HISTORY_FILE" => ''
- }
-
- def setup
- if ruby_core?
- omit "This test works only under ruby/irb"
- end
-
- if RUBY_ENGINE == 'truffleruby'
- omit "This test runs with ruby/debug, which doesn't work with truffleruby"
- end
- end
-
- def test_backtrace
- write_ruby <<~'RUBY'
- def foo
- binding.irb
- end
- foo
- RUBY
-
- output = run_ruby_file do
- type "backtrace"
- type "q!"
- end
-
- assert_match(/\(rdbg:irb\) 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(/\(rdbg\) next/, output)
- assert_match(/=> 2\| puts "hello"/, output)
- end
-
- def test_next
- write_ruby <<~'ruby'
- binding.irb
- puts "hello"
- ruby
-
- output = run_ruby_file do
- type "next"
- type "continue"
- end
-
- assert_match(/\(rdbg:irb\) 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(/\(rdbg:irb\) 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(/\(rdbg:irb\) 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(/\(rdbg:irb\) step/, output)
- assert_match(/=> 5\| foo/, output)
- assert_match(/=> 2\| puts "Hello"/, 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(/\(rdbg:irb\) continue/, output)
- assert_match(/=> 3: binding.irb/, 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(/\(rdbg:irb\) 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(/\(rdbg:irb\) 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(/\(rdbg:irb\) catch/, output)
- assert_match(/Stop by #0 BP - Catch "ZeroDivisionError"/, output)
- end
-
- private
-
- TIMEOUT_SEC = 3
-
- def run_ruby_file(&block)
- cmd = [EnvUtil.rubybin, "-I", LIB, @ruby_file.to_path]
- tmp_dir = Dir.mktmpdir
-
- @commands = []
- lines = []
-
- yield
-
- PTY.spawn(IRB_AND_DEBUGGER_OPTIONS.merge("TERM" => "dumb"), *cmd) do |read, write, pid|
- Timeout.timeout(TIMEOUT_SEC) do
- while line = safe_gets(read)
- lines << line
-
- # means the breakpoint is triggered
- if line.match?(/binding\.irb/)
- while command = @commands.shift
- write.puts(command)
- end
- end
- end
- end
- ensure
- read.close
- write.close
- kill_safely(pid)
- end
-
- lines.join
- rescue Timeout::Error
- message = <<~MSG
- Test timedout.
-
- #{'=' * 30} OUTPUT #{'=' * 30}
- #{lines.map { |l| " #{l}" }.join}
- #{'=' * 27} END OF OUTPUT #{'=' * 27}
- MSG
- assert_block(message) { false }
- ensure
- File.unlink(@ruby_file) if @ruby_file
- FileUtils.remove_entry tmp_dir
- end
-
- # read.gets could raise exceptions on some platforms
- # https://github.com/ruby/ruby/blob/master/ext/pty/pty.c#L729-L736
- def safe_gets(read)
- read.gets
- rescue Errno::EIO
- nil
- end
-
- def kill_safely pid
- return if wait_pid pid, TIMEOUT_SEC
-
- Process.kill :TERM, pid
- return if wait_pid pid, 0.2
-
- Process.kill :KILL, pid
- Process.waitpid(pid)
- rescue Errno::EPERM, Errno::ESRCH
- end
-
- def wait_pid pid, sec
- total_sec = 0.0
- wait_sec = 0.001 # 1ms
-
- while total_sec < sec
- if Process.waitpid(pid, Process::WNOHANG) == pid
- return true
- end
- sleep wait_sec
- total_sec += wait_sec
- wait_sec *= 2
- end
-
- false
- rescue Errno::ECHILD
- true
- end
-
- def type(command)
- @commands << command
- end
-
- def write_ruby(program)
- @ruby_file = Tempfile.create(%w{irb- .rb})
- @ruby_file.write(program)
- @ruby_file.close
- end
- end
-end
diff --git a/test/irb/test_debugger_integration.rb b/test/irb/test_debugger_integration.rb
new file mode 100644
index 0000000000..8b1bddea17
--- /dev/null
+++ b/test/irb/test_debugger_integration.rb
@@ -0,0 +1,496 @@
+# 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_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
diff --git a/test/irb/test_eval_history.rb b/test/irb/test_eval_history.rb
new file mode 100644
index 0000000000..54913ceff5
--- /dev/null
+++ b/test/irb/test_eval_history.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+require "irb"
+
+require_relative "helper"
+
+module TestIRB
+ class EvalHistoryTest < TestCase
+ def setup
+ save_encodings
+ IRB.instance_variable_get(:@CONF).clear
+ end
+
+ def teardown
+ restore_encodings
+ end
+
+ def execute_lines(*lines, conf: {}, main: self, irb_path: nil)
+ IRB.init_config(nil)
+ IRB.conf[:VERBOSE] = false
+ IRB.conf[:PROMPT_MODE] = :SIMPLE
+ IRB.conf[:USE_PAGER] = false
+ IRB.conf.merge!(conf)
+ input = TestInputMethod.new(lines)
+ irb = IRB::Irb.new(IRB::WorkSpace.new(main), input)
+ irb.context.return_format = "=> %s\n"
+ irb.context.irb_path = irb_path if irb_path
+ IRB.conf[:MAIN_CONTEXT] = irb.context
+ capture_output do
+ irb.eval_input
+ end
+ end
+
+ def test_eval_history_is_disabled_by_default
+ out, err = execute_lines(
+ "a = 1",
+ "__"
+ )
+
+ assert_empty(err)
+ assert_match(/undefined local variable or method (`|')__'/, out)
+ end
+
+ def test_eval_history_can_be_retrieved_with_double_underscore
+ out, err = execute_lines(
+ "a = 1",
+ "__",
+ conf: { EVAL_HISTORY: 5 }
+ )
+
+ assert_empty(err)
+ assert_match("=> 1\n" + "=> 1 1\n", out)
+ end
+
+ def test_eval_history_respects_given_limit
+ out, err = execute_lines(
+ "'foo'\n",
+ "'bar'\n",
+ "'baz'\n",
+ "'xyz'\n",
+ "__",
+ conf: { EVAL_HISTORY: 4 }
+ )
+
+ assert_empty(err)
+ # Because eval_history injects `__` into the history AND decide to ignore it, we only get <limit> - 1 results
+ assert_match("2 \"bar\"\n" + "3 \"baz\"\n" + "4 \"xyz\"\n", out)
+ end
+ end
+end
diff --git a/test/irb/test_evaluation.rb b/test/irb/test_evaluation.rb
new file mode 100644
index 0000000000..adb69b2067
--- /dev/null
+++ b/test/irb/test_evaluation.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require "tempfile"
+
+require_relative "helper"
+
+module TestIRB
+ class EchoingTest < IntegrationTestCase
+ def test_irb_echos_by_default
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "123123"
+ type "exit"
+ end
+
+ assert_include(output, "=> 123123")
+ end
+
+ def test_irb_doesnt_echo_line_with_semicolon
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "123123;"
+ type "123123 ;"
+ type "123123; "
+ type <<~RUBY
+ if true
+ 123123
+ end;
+ RUBY
+ type "'evaluation ends'"
+ type "exit"
+ end
+
+ assert_include(output, "=> \"evaluation ends\"")
+ assert_not_include(output, "=> 123123")
+ end
+ end
+end
diff --git a/test/irb/test_helper_method.rb b/test/irb/test_helper_method.rb
new file mode 100644
index 0000000000..291278c16a
--- /dev/null
+++ b/test/irb/test_helper_method.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+require "irb"
+
+require_relative "helper"
+
+module TestIRB
+ class HelperMethodTestCase < TestCase
+ def setup
+ $VERBOSE = nil
+ @verbosity = $VERBOSE
+ save_encodings
+ IRB.instance_variable_get(:@CONF).clear
+ end
+
+ def teardown
+ $VERBOSE = @verbosity
+ restore_encodings
+ end
+
+ def execute_lines(*lines, conf: {}, main: self, irb_path: nil)
+ IRB.init_config(nil)
+ IRB.conf[:VERBOSE] = false
+ IRB.conf[:PROMPT_MODE] = :SIMPLE
+ IRB.conf.merge!(conf)
+ input = TestInputMethod.new(lines)
+ irb = IRB::Irb.new(IRB::WorkSpace.new(main), input)
+ irb.context.return_format = "=> %s\n"
+ irb.context.irb_path = irb_path if irb_path
+ IRB.conf[:MAIN_CONTEXT] = irb.context
+ IRB.conf[:USE_PAGER] = false
+ capture_output do
+ irb.eval_input
+ end
+ end
+ end
+
+ module TestHelperMethod
+ class ConfTest < HelperMethodTestCase
+ def test_conf_returns_the_context_object
+ out, err = execute_lines("conf.ap_name")
+
+ assert_empty err
+ assert_include out, "=> \"irb\""
+ end
+ end
+ end
+
+ class HelperMethodIntegrationTest < IntegrationTestCase
+ def test_arguments_propogation
+ write_ruby <<~RUBY
+ require "irb/helper_method"
+
+ class MyHelper < IRB::HelperMethod::Base
+ description "This is a test helper"
+
+ def execute(
+ required_arg, optional_arg = nil, *splat_arg, required_keyword_arg:,
+ optional_keyword_arg: nil, **double_splat_arg, &block_arg
+ )
+ puts [required_arg, optional_arg, splat_arg, required_keyword_arg, optional_keyword_arg, double_splat_arg, block_arg.call].to_s
+ end
+ end
+
+ IRB::HelperMethod.register(:my_helper, MyHelper)
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type <<~INPUT
+ my_helper(
+ "required", "optional", "splat", required_keyword_arg: "required",
+ optional_keyword_arg: "optional", a: 1, b: 2
+ ) { "block" }
+ INPUT
+ type "exit"
+ end
+
+ assert_include(output, '["required", "optional", ["splat"], "required", "optional", {:a=>1, :b=>2}, "block"]')
+ end
+
+ def test_helper_method_injection_can_happen_after_irb_require
+ write_ruby <<~RUBY
+ require "irb"
+
+ class MyHelper < IRB::HelperMethod::Base
+ description "This is a test helper"
+
+ def execute
+ puts "Hello from MyHelper"
+ end
+ end
+
+ IRB::HelperMethod.register(:my_helper, MyHelper)
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "my_helper"
+ type "exit"
+ end
+
+ assert_include(output, 'Hello from MyHelper')
+ end
+
+ def test_helper_method_instances_are_memoized
+ write_ruby <<~RUBY
+ require "irb/helper_method"
+
+ class MyHelper < IRB::HelperMethod::Base
+ description "This is a test helper"
+
+ def execute(val)
+ @val ||= val
+ end
+ end
+
+ IRB::HelperMethod.register(:my_helper, MyHelper)
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "my_helper(100)"
+ type "my_helper(200)"
+ type "exit"
+ end
+
+ assert_include(output, '=> 100')
+ assert_not_include(output, '=> 200')
+ end
+ end
+end
diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb
index c74a3a2cb9..63be35fdaa 100644
--- a/test/irb/test_history.rb
+++ b/test/irb/test_history.rb
@@ -1,22 +1,45 @@
# frozen_string_literal: false
require 'irb'
-require 'irb/ext/save-history'
require 'readline'
+require "tempfile"
require_relative "helper"
+return if RUBY_PLATFORM.match?(/solaris|mswin|mingw/i)
+
module TestIRB
- class TestHistory < TestCase
+ class HistoryTest < TestCase
def setup
- IRB.conf[:RC_NAME_GENERATOR] = nil
+ @original_verbose, $VERBOSE = $VERBOSE, nil
+ @tmpdir = Dir.mktmpdir("test_irb_history_")
+ @backup_home = ENV["HOME"]
+ @backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME")
+ @backup_irbrc = ENV.delete("IRBRC")
+ @backup_default_external = Encoding.default_external
+ ENV["HOME"] = @tmpdir
+ IRB.instance_variable_set(:@existing_rc_name_generators, nil)
end
def teardown
- IRB.conf[:RC_NAME_GENERATOR] = nil
+ IRB.instance_variable_set(:@existing_rc_name_generators, nil)
+ ENV["HOME"] = @backup_home
+ ENV["XDG_CONFIG_HOME"] = @backup_xdg_config_home
+ ENV["IRBRC"] = @backup_irbrc
+ Encoding.default_external = @backup_default_external
+ $VERBOSE = @original_verbose
+ FileUtils.rm_rf(@tmpdir)
+ end
+
+ class TestInputMethodWithRelineHistory < TestInputMethod
+ # When IRB.conf[:USE_MULTILINE] is true, IRB::RelineInputMethod uses Reline::History
+ HISTORY = Reline::History.new(Reline.core.config)
+
+ include IRB::HistorySavingAbility
end
- class TestInputMethodWithHistory < TestInputMethod
- HISTORY = Array.new
+ class TestInputMethodWithReadlineHistory < TestInputMethod
+ # When IRB.conf[:USE_MULTILINE] is false, IRB::ReadlineInputMethod uses Readline::HISTORY
+ HISTORY = Readline::HISTORY
include IRB::HistorySavingAbility
end
@@ -100,10 +123,80 @@ module TestIRB
INPUT
end
- def test_history_concurrent_use
+ def test_history_concurrent_use_reline
+ omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
+ IRB.conf[:SAVE_HISTORY] = 1
+ history_concurrent_use_for_input_method(TestInputMethodWithRelineHistory)
+ end
+
+ def test_history_concurrent_use_readline
omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
IRB.conf[:SAVE_HISTORY] = 1
- assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT) do |history_file|
+ history_concurrent_use_for_input_method(TestInputMethodWithReadlineHistory)
+ end
+
+ def test_history_concurrent_use_not_present
+ IRB.conf[:LC_MESSAGES] = IRB::Locale.new
+ IRB.conf[:SAVE_HISTORY] = 1
+ io = TestInputMethodWithRelineHistory.new
+ io.class::HISTORY.clear
+ io.load_history
+ io.class::HISTORY << 'line1'
+ io.class::HISTORY << 'line2'
+
+ history_file = IRB.rc_file("_history")
+ assert_not_send [File, :file?, history_file]
+ File.write(history_file, "line0\n")
+ io.save_history
+ assert_equal(%w"line0 line1 line2", File.read(history_file).split)
+ end
+
+ def test_history_different_encodings
+ IRB.conf[:SAVE_HISTORY] = 2
+ Encoding.default_external = Encoding::US_ASCII
+ locale = IRB::Locale.new("C")
+ assert_history(<<~EXPECTED_HISTORY.encode(Encoding::US_ASCII), <<~INITIAL_HISTORY.encode(Encoding::UTF_8), <<~INPUT, locale: locale)
+ ????
+ exit
+ EXPECTED_HISTORY
+ 😀
+ INITIAL_HISTORY
+ exit
+ INPUT
+ end
+
+ def test_history_does_not_raise_when_history_file_directory_does_not_exist
+ backup_history_file = IRB.conf[:HISTORY_FILE]
+ IRB.conf[:SAVE_HISTORY] = 1
+ IRB.conf[:HISTORY_FILE] = "fake/fake/fake/history_file"
+ io = TestInputMethodWithRelineHistory.new
+
+ assert_warn(/history file does not exist/) do
+ io.save_history
+ end
+
+ # assert_warn reverts $VERBOSE to EnvUtil.original_verbose, which is true in some cases
+ # We want to keep $VERBOSE as nil until teardown is called
+ # TODO: check if this is an assert_warn issue
+ $VERBOSE = nil
+ ensure
+ IRB.conf[:HISTORY_FILE] = backup_history_file
+ end
+
+ def test_no_home_no_history_file_does_not_raise_history_save
+ ENV['HOME'] = nil
+ io = TestInputMethodWithRelineHistory.new
+ assert_nil(IRB.rc_file('_history'))
+ assert_nothing_raised do
+ io.load_history
+ io.save_history
+ end
+ end
+
+ private
+
+ def history_concurrent_use_for_input_method(input_method)
+ assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT, input_method) do |history_file|
exit
5
exit
@@ -116,7 +209,7 @@ module TestIRB
5
exit
INPUT
- assert_history(<<~EXPECTED_HISTORY2, <<~INITIAL_HISTORY2, <<~INPUT2)
+ assert_history(<<~EXPECTED_HISTORY2, <<~INITIAL_HISTORY2, <<~INPUT2, input_method)
exit
EXPECTED_HISTORY2
1
@@ -131,60 +224,31 @@ module TestIRB
end
end
- def test_history_concurrent_use_not_present
- backup_home = ENV["HOME"]
- backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME")
- backup_irbrc = ENV.delete("IRBRC")
- IRB.conf[:LC_MESSAGES] = IRB::Locale.new
- IRB.conf[:SAVE_HISTORY] = 1
- Dir.mktmpdir("test_irb_history_") do |tmpdir|
- ENV["HOME"] = tmpdir
- io = TestInputMethodWithHistory.new
- io.class::HISTORY.clear
- io.load_history
- io.class::HISTORY.concat(%w"line1 line2")
-
- history_file = IRB.rc_file("_history")
- assert_not_send [File, :file?, history_file]
- File.write(history_file, "line0\n")
- io.save_history
- assert_equal(%w"line0 line1 line2", File.read(history_file).split)
- end
- ensure
- ENV["HOME"] = backup_home
- ENV["XDG_CONFIG_HOME"] = backup_xdg_config_home
- ENV["IRBRC"] = backup_irbrc
- end
-
- private
-
- def assert_history(expected_history, initial_irb_history, input)
- backup_verbose, $VERBOSE = $VERBOSE, nil
- backup_home = ENV["HOME"]
- backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME")
- IRB.conf[:LC_MESSAGES] = IRB::Locale.new
+ def assert_history(expected_history, initial_irb_history, input, input_method = TestInputMethodWithRelineHistory, locale: IRB::Locale.new)
+ IRB.conf[:LC_MESSAGES] = locale
actual_history = nil
- Dir.mktmpdir("test_irb_history_") do |tmpdir|
- ENV["HOME"] = tmpdir
- File.open(IRB.rc_file("_history"), "w") do |f|
- f.write(initial_irb_history)
- end
+ history_file = IRB.rc_file("_history")
+ ENV["HOME"] = @tmpdir
+ File.open(history_file, "w") do |f|
+ f.write(initial_irb_history)
+ end
- io = TestInputMethodWithHistory.new
+ io = input_method.new
+ io.class::HISTORY.clear
+ io.load_history
+ if block_given?
+ previous_history = []
+ io.class::HISTORY.each { |line| previous_history << line }
+ yield history_file
io.class::HISTORY.clear
- io.load_history
- if block_given?
- history = io.class::HISTORY.dup
- yield IRB.rc_file("_history")
- io.class::HISTORY.replace(history)
- end
- io.class::HISTORY.concat(input.split)
- io.save_history
+ previous_history.each { |line| io.class::HISTORY << line }
+ end
+ input.split.each { |line| io.class::HISTORY << line }
+ io.save_history
- io.load_history
- File.open(IRB.rc_file("_history"), "r") do |f|
- actual_history = f.read
- end
+ io.load_history
+ File.open(history_file, "r") do |f|
+ actual_history = f.read
end
assert_equal(expected_history, actual_history, <<~MESSAGE)
expected:
@@ -192,10 +256,6 @@ module TestIRB
but actual:
#{actual_history}
MESSAGE
- ensure
- $VERBOSE = backup_verbose
- ENV["HOME"] = backup_home
- ENV["XDG_CONFIG_HOME"] = backup_xdg_config_home
end
def with_temp_stdio
@@ -206,4 +266,226 @@ module TestIRB
end
end
end
-end if not RUBY_PLATFORM.match?(/solaris|mswin|mingw/i)
+
+ class IRBHistoryIntegrationTest < IntegrationTestCase
+ def test_history_saving_with_debug
+ write_history ""
+
+ write_ruby <<~'RUBY'
+ def foo
+ end
+
+ binding.irb
+
+ foo
+ RUBY
+
+ output = run_ruby_file do
+ type "'irb session'"
+ type "next"
+ type "'irb:debug session'"
+ type "step"
+ type "irb_info"
+ type "puts Reline::HISTORY.to_a.to_s"
+ type "q!"
+ end
+
+ assert_include(output, "InputMethod: RelineInputMethod")
+ # check that in-memory history is preserved across sessions
+ assert_include output, %q(
+ ["'irb session'", "next", "'irb:debug session'", "step", "irb_info", "puts Reline::HISTORY.to_a.to_s"]
+ ).strip
+
+ assert_equal <<~HISTORY, @history_file.open.read
+ 'irb session'
+ next
+ 'irb:debug session'
+ step
+ irb_info
+ puts Reline::HISTORY.to_a.to_s
+ q!
+ HISTORY
+ end
+
+ def test_history_saving_with_debug_without_prior_history
+ tmpdir = Dir.mktmpdir("test_irb_history_")
+ # Intentionally not creating the file so we test the reset counter logic
+ history_file = File.join(tmpdir, "irb_history")
+
+ write_rc <<~RUBY
+ IRB.conf[:HISTORY_FILE] = "#{history_file}"
+ RUBY
+
+ write_ruby <<~'RUBY'
+ def foo
+ end
+
+ binding.irb
+
+ foo
+ RUBY
+
+ output = run_ruby_file do
+ type "'irb session'"
+ type "next"
+ type "'irb:debug session'"
+ type "step"
+ type "irb_info"
+ type "puts Reline::HISTORY.to_a.to_s"
+ type "q!"
+ end
+
+ assert_include(output, "InputMethod: RelineInputMethod")
+ # check that in-memory history is preserved across sessions
+ assert_include output, %q(
+ ["'irb session'", "next", "'irb:debug session'", "step", "irb_info", "puts Reline::HISTORY.to_a.to_s"]
+ ).strip
+
+ assert_equal <<~HISTORY, File.read(history_file)
+ 'irb session'
+ next
+ 'irb:debug session'
+ step
+ irb_info
+ puts Reline::HISTORY.to_a.to_s
+ q!
+ HISTORY
+ ensure
+ FileUtils.rm_rf(tmpdir)
+ end
+
+ def test_history_saving_with_nested_sessions
+ write_history ""
+
+ write_ruby <<~'RUBY'
+ def foo
+ binding.irb
+ end
+
+ binding.irb
+ RUBY
+
+ run_ruby_file do
+ type "'outer session'"
+ type "foo"
+ type "'inner session'"
+ type "exit"
+ type "'outer session again'"
+ type "exit"
+ end
+
+ assert_equal <<~HISTORY, @history_file.open.read
+ 'outer session'
+ foo
+ 'inner session'
+ exit
+ 'outer session again'
+ exit
+ HISTORY
+ end
+
+ def test_nested_history_saving_from_inner_session_with_exit!
+ write_history ""
+
+ write_ruby <<~'RUBY'
+ def foo
+ binding.irb
+ end
+
+ binding.irb
+ RUBY
+
+ run_ruby_file do
+ type "'outer session'"
+ type "foo"
+ type "'inner session'"
+ type "exit!"
+ end
+
+ assert_equal <<~HISTORY, @history_file.open.read
+ 'outer session'
+ foo
+ 'inner session'
+ exit!
+ HISTORY
+ end
+
+ def test_nested_history_saving_from_outer_session_with_exit!
+ write_history ""
+
+ write_ruby <<~'RUBY'
+ def foo
+ binding.irb
+ end
+
+ binding.irb
+ RUBY
+
+ run_ruby_file do
+ type "'outer session'"
+ type "foo"
+ type "'inner session'"
+ type "exit"
+ type "'outer session again'"
+ type "exit!"
+ end
+
+ assert_equal <<~HISTORY, @history_file.open.read
+ 'outer session'
+ foo
+ 'inner session'
+ exit
+ 'outer session again'
+ exit!
+ HISTORY
+ end
+
+ def test_history_saving_with_nested_sessions_and_prior_history
+ write_history <<~HISTORY
+ old_history_1
+ old_history_2
+ old_history_3
+ HISTORY
+
+ write_ruby <<~'RUBY'
+ def foo
+ binding.irb
+ end
+
+ binding.irb
+ RUBY
+
+ run_ruby_file do
+ type "'outer session'"
+ type "foo"
+ type "'inner session'"
+ type "exit"
+ type "'outer session again'"
+ type "exit"
+ end
+
+ assert_equal <<~HISTORY, @history_file.open.read
+ old_history_1
+ old_history_2
+ old_history_3
+ 'outer session'
+ foo
+ 'inner session'
+ exit
+ 'outer session again'
+ exit
+ HISTORY
+ end
+
+ private
+
+ def write_history(history)
+ @history_file = Tempfile.new('irb_history')
+ @history_file.write(history)
+ @history_file.close
+ write_rc <<~RUBY
+ IRB.conf[:HISTORY_FILE] = "#{@history_file.path}"
+ RUBY
+ end
+ end
+end
diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb
index d9e338da8d..c423fa112e 100644
--- a/test/irb/test_init.rb
+++ b/test/irb/test_init.rb
@@ -5,19 +5,24 @@ require "fileutils"
require_relative "helper"
module TestIRB
- class TestInit < TestCase
+ class InitTest < TestCase
def setup
# IRBRC is for RVM...
@backup_env = %w[HOME XDG_CONFIG_HOME IRBRC].each_with_object({}) do |env, hash|
hash[env] = ENV.delete(env)
end
- ENV["HOME"] = @tmpdir = Dir.mktmpdir("test_irb_init_#{$$}")
+ ENV["HOME"] = @tmpdir = File.realpath(Dir.mktmpdir("test_irb_init_#{$$}"))
+ end
+
+ def reset_rc_name_generators
+ IRB.instance_variable_set(:@existing_rc_name_generators, nil)
end
def teardown
ENV.update(@backup_env)
FileUtils.rm_rf(@tmpdir)
IRB.conf.delete(:SCRIPT)
+ reset_rc_name_generators
end
def test_setup_with_argv_preserves_global_argv
@@ -34,42 +39,80 @@ module TestIRB
assert_equal orig, $0
end
- def test_rc_file
+ def test_rc_files
tmpdir = @tmpdir
Dir.chdir(tmpdir) do
- ENV["XDG_CONFIG_HOME"] = "#{tmpdir}/xdg"
- IRB.conf[:RC_NAME_GENERATOR] = nil
- assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file)
- assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history"))
- assert_file.not_exist?(tmpdir+"/xdg")
- IRB.conf[:RC_NAME_GENERATOR] = nil
- FileUtils.touch(tmpdir+"/.irb#{IRB::IRBRC_EXT}")
- assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file)
- assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history"))
- assert_file.not_exist?(tmpdir+"/xdg")
+ home = ENV['HOME'] = "#{tmpdir}/home"
+ xdg_config_home = ENV['XDG_CONFIG_HOME'] = "#{tmpdir}/xdg"
+ reset_rc_name_generators
+ assert_empty(IRB.irbrc_files)
+ assert_equal("#{home}/.irb_history", IRB.rc_file('_history'))
+ FileUtils.mkdir_p(home)
+ FileUtils.mkdir_p("#{xdg_config_home}/irb")
+ FileUtils.mkdir_p("#{home}/.config/irb")
+ reset_rc_name_generators
+ assert_empty(IRB.irbrc_files)
+ assert_equal("#{xdg_config_home}/irb/irb_history", IRB.rc_file('_history'))
+ home_irbrc = "#{home}/.irbrc"
+ config_irbrc = "#{home}/.config/irb/irbrc"
+ xdg_config_irbrc = "#{xdg_config_home}/irb/irbrc"
+ [home_irbrc, config_irbrc, xdg_config_irbrc].each do |file|
+ FileUtils.touch(file)
+ end
+ current_dir_irbrcs = %w[.irbrc irbrc _irbrc $irbrc].map { |file| "#{tmpdir}/#{file}" }
+ current_dir_irbrcs.each { |file| FileUtils.touch(file) }
+ reset_rc_name_generators
+ assert_equal([xdg_config_irbrc, home_irbrc, *current_dir_irbrcs], IRB.irbrc_files)
+ assert_equal(xdg_config_irbrc.sub(/rc$/, '_history'), IRB.rc_file('_history'))
+ ENV['XDG_CONFIG_HOME'] = nil
+ reset_rc_name_generators
+ assert_equal([home_irbrc, config_irbrc, *current_dir_irbrcs], IRB.irbrc_files)
+ assert_equal(home_irbrc.sub(/rc$/, '_history'), IRB.rc_file('_history'))
+ ENV['XDG_CONFIG_HOME'] = ''
+ reset_rc_name_generators
+ assert_equal([home_irbrc, config_irbrc] + current_dir_irbrcs, IRB.irbrc_files)
+ assert_equal(home_irbrc.sub(/rc$/, '_history'), IRB.rc_file('_history'))
+ ENV['XDG_CONFIG_HOME'] = xdg_config_home
+ ENV['IRBRC'] = "#{tmpdir}/.irbrc"
+ reset_rc_name_generators
+ assert_equal([ENV['IRBRC'], xdg_config_irbrc, home_irbrc] + (current_dir_irbrcs - [ENV['IRBRC']]), IRB.irbrc_files)
+ assert_equal(ENV['IRBRC'] + '_history', IRB.rc_file('_history'))
+ ENV['IRBRC'] = ENV['HOME'] = ENV['XDG_CONFIG_HOME'] = nil
+ reset_rc_name_generators
+ assert_equal(current_dir_irbrcs, IRB.irbrc_files)
+ assert_nil(IRB.rc_file('_history'))
end
end
- def test_rc_file_in_subdir
+ def test_duplicated_rc_files
tmpdir = @tmpdir
Dir.chdir(tmpdir) do
- FileUtils.mkdir_p("#{tmpdir}/mydir")
- Dir.chdir("#{tmpdir}/mydir") do
- IRB.conf[:RC_NAME_GENERATOR] = nil
- assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file)
- assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history"))
- IRB.conf[:RC_NAME_GENERATOR] = nil
- FileUtils.touch(tmpdir+"/.irb#{IRB::IRBRC_EXT}")
- assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file)
- assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history"))
+ ENV['XDG_CONFIG_HOME'] = "#{ENV['HOME']}/.config"
+ FileUtils.mkdir_p("#{ENV['XDG_CONFIG_HOME']}/irb")
+ env_irbrc = ENV['IRBRC'] = "#{tmpdir}/_irbrc"
+ xdg_config_irbrc = "#{ENV['XDG_CONFIG_HOME']}/irb/irbrc"
+ home_irbrc = "#{ENV['HOME']}/.irbrc"
+ current_dir_irbrc = "#{tmpdir}/irbrc"
+ [env_irbrc, xdg_config_irbrc, home_irbrc, current_dir_irbrc].each do |file|
+ FileUtils.touch(file)
end
+ reset_rc_name_generators
+ assert_equal([env_irbrc, xdg_config_irbrc, home_irbrc, current_dir_irbrc], IRB.irbrc_files)
end
end
- def test_recovery_sigint
+ def test_sigint_restore_default
pend "This test gets stuck on Solaris for unknown reason; contribution is welcome" if RUBY_PLATFORM =~ /solaris/
bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
- status = assert_in_out_err(bundle_exec + %w[-W0 -rirb -e binding.irb;loop{Process.kill("SIGINT",$$)} -- -f --], "exit\n", //, //)
+ # IRB should restore SIGINT handler
+ status = assert_in_out_err(bundle_exec + %w[-W0 -rirb -e Signal.trap("SIGINT","DEFAULT");binding.irb;loop{Process.kill("SIGINT",$$)} -- -f --], "exit\n", //, //)
+ Process.kill("SIGKILL", status.pid) if !status.exited? && !status.stopped? && !status.signaled?
+ end
+
+ def test_sigint_restore_block
+ bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
+ # IRB should restore SIGINT handler
+ status = assert_in_out_err(bundle_exec + %w[-W0 -rirb -e x=false;Signal.trap("SIGINT"){x=true};binding.irb;loop{Process.kill("SIGINT",$$);if(x);break;end} -- -f --], "exit\n", //, //)
Process.kill("SIGKILL", status.pid) if !status.exited? && !status.stopped? && !status.signaled?
end
@@ -120,6 +163,50 @@ module TestIRB
IRB.conf[:USE_AUTOCOMPLETE] = orig_use_autocomplete_conf
end
+ def test_completor_environment_variable
+ orig_use_autocomplete_env = ENV['IRB_COMPLETOR']
+ orig_use_autocomplete_conf = IRB.conf[:COMPLETOR]
+
+ ENV['IRB_COMPLETOR'] = nil
+ IRB.setup(__FILE__)
+ assert_equal(:regexp, IRB.conf[:COMPLETOR])
+
+ ENV['IRB_COMPLETOR'] = 'regexp'
+ IRB.setup(__FILE__)
+ assert_equal(:regexp, IRB.conf[:COMPLETOR])
+
+ ENV['IRB_COMPLETOR'] = 'type'
+ IRB.setup(__FILE__)
+ assert_equal(:type, IRB.conf[:COMPLETOR])
+
+ ENV['IRB_COMPLETOR'] = 'regexp'
+ IRB.setup(__FILE__, argv: ['--type-completor'])
+ assert_equal :type, IRB.conf[:COMPLETOR]
+
+ ENV['IRB_COMPLETOR'] = 'type'
+ IRB.setup(__FILE__, argv: ['--regexp-completor'])
+ assert_equal :regexp, IRB.conf[:COMPLETOR]
+ ensure
+ ENV['IRB_COMPLETOR'] = orig_use_autocomplete_env
+ IRB.conf[:COMPLETOR] = orig_use_autocomplete_conf
+ end
+
+ def test_completor_setup_with_argv
+ orig_completor_conf = IRB.conf[:COMPLETOR]
+
+ # Default is :regexp
+ IRB.setup(__FILE__, argv: [])
+ assert_equal :regexp, IRB.conf[:COMPLETOR]
+
+ IRB.setup(__FILE__, argv: ['--type-completor'])
+ assert_equal :type, IRB.conf[:COMPLETOR]
+
+ IRB.setup(__FILE__, argv: ['--regexp-completor'])
+ assert_equal :regexp, IRB.conf[:COMPLETOR]
+ ensure
+ IRB.conf[:COMPLETOR] = orig_completor_conf
+ end
+
def test_noscript
argv = %w[--noscript -- -f]
IRB.setup(eval("__FILE__"), argv: argv)
@@ -164,6 +251,12 @@ module TestIRB
assert_equal(['-f'], argv)
end
+ def test_option_tracer
+ argv = %w[--tracer]
+ IRB.setup(eval("__FILE__"), argv: argv)
+ assert_equal(true, IRB.conf[:USE_TRACER])
+ end
+
private
def with_argv(argv)
@@ -174,4 +267,120 @@ module TestIRB
ARGV.replace(orig)
end
end
+
+ class ConfigValidationTest < TestCase
+ def setup
+ @original_home = ENV["HOME"]
+ @original_irbrc = ENV["IRBRC"]
+ # To prevent the test from using the user's .irbrc file
+ ENV["HOME"] = @home = Dir.mktmpdir
+ super
+ end
+
+ def teardown
+ super
+ ENV["IRBRC"] = @original_irbrc
+ ENV["HOME"] = @original_home
+ File.unlink(@irbrc)
+ Dir.rmdir(@home)
+ IRB.instance_variable_set(:@existing_rc_name_generators, nil)
+ end
+
+ def test_irb_name_converts_non_string_values_to_string
+ assert_no_irb_validation_error(<<~'RUBY')
+ IRB.conf[:IRB_NAME] = :foo
+ RUBY
+
+ assert_equal "foo", IRB.conf[:IRB_NAME]
+ end
+
+ def test_irb_rc_name_only_takes_callable_objects
+ assert_irb_validation_error(<<~'RUBY', "IRB.conf[:IRB_RC] should be a callable object. Got :foo.")
+ IRB.conf[:IRB_RC] = :foo
+ RUBY
+ end
+
+ def test_back_trace_limit_only_accepts_integers
+ assert_irb_validation_error(<<~'RUBY', "IRB.conf[:BACK_TRACE_LIMIT] should be an integer. Got \"foo\".")
+ IRB.conf[:BACK_TRACE_LIMIT] = "foo"
+ RUBY
+ end
+
+ def test_prompt_only_accepts_hash
+ assert_irb_validation_error(<<~'RUBY', "IRB.conf[:PROMPT] should be a Hash. Got \"foo\".")
+ IRB.conf[:PROMPT] = "foo"
+ RUBY
+ end
+
+ def test_eval_history_only_accepts_integers
+ assert_irb_validation_error(<<~'RUBY', "IRB.conf[:EVAL_HISTORY] should be an integer. Got \"foo\".")
+ IRB.conf[:EVAL_HISTORY] = "foo"
+ RUBY
+ end
+
+ private
+
+ def assert_irb_validation_error(rc_content, error_message)
+ write_rc rc_content
+
+ assert_raise_with_message(TypeError, error_message) do
+ IRB.setup(__FILE__)
+ end
+ end
+
+ def assert_no_irb_validation_error(rc_content)
+ write_rc rc_content
+
+ assert_nothing_raised do
+ IRB.setup(__FILE__)
+ end
+ end
+
+ def write_rc(content)
+ @irbrc = Tempfile.new('irbrc')
+ @irbrc.write(content)
+ @irbrc.close
+ ENV['IRBRC'] = @irbrc.path
+ end
+ end
+
+ class InitIntegrationTest < IntegrationTestCase
+ def setup
+ super
+
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+ end
+
+ def test_load_error_in_rc_file_is_warned
+ write_rc <<~'IRBRC'
+ require "file_that_does_not_exist"
+ IRBRC
+
+ output = run_ruby_file do
+ type "'foobar'"
+ type "exit"
+ end
+
+ # IRB session should still be started
+ assert_includes output, "foobar"
+ assert_includes output, 'cannot load such file -- file_that_does_not_exist (LoadError)'
+ end
+
+ def test_normal_errors_in_rc_file_is_warned
+ write_rc <<~'IRBRC'
+ raise "I'm an error"
+ IRBRC
+
+ output = run_ruby_file do
+ type "'foobar'"
+ type "exit"
+ end
+
+ # IRB session should still be started
+ assert_includes output, "foobar"
+ assert_includes output, 'I\'m an error (RuntimeError)'
+ end
+ end
end
diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb
index fdfb566390..ce317b4b32 100644
--- a/test/irb/test_input_method.rb
+++ b/test/irb/test_input_method.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: false
require "irb"
-
+require "rdoc"
require_relative "helper"
module TestIRB
- class TestRelineInputMethod < TestCase
+ class InputMethodTest < TestCase
def setup
@conf_backup = IRB.conf.dup
IRB.conf[:LC_MESSAGES] = IRB::Locale.new
@@ -15,16 +15,22 @@ module TestIRB
def teardown
IRB.conf.replace(@conf_backup)
restore_encodings
+ # Reset Reline configuration overridden by RelineInputMethod.
+ Reline.instance_variable_set(:@core, nil)
end
+ end
+ class RelineInputMethodTest < InputMethodTest
def test_initialization
- IRB::RelineInputMethod.new
+ Reline.completion_proc = nil
+ Reline.dig_perfect_match_proc = nil
+ IRB::RelineInputMethod.new(IRB::RegexpCompletor.new)
assert_nil Reline.completion_append_character
assert_equal '', Reline.completer_quote_characters
- assert_equal IRB::InputCompletor::BASIC_WORD_BREAK_CHARACTERS, Reline.basic_word_break_characters
- assert_equal IRB::InputCompletor::CompletionProc, Reline.completion_proc
- assert_equal IRB::InputCompletor::PerfectMatchedProc, Reline.dig_perfect_match_proc
+ assert_equal IRB::InputMethod::BASIC_WORD_BREAK_CHARACTERS, Reline.basic_word_break_characters
+ assert_not_nil Reline.completion_proc
+ assert_not_nil Reline.dig_perfect_match_proc
end
def test_initialization_without_use_autocomplete
@@ -34,7 +40,7 @@ module TestIRB
IRB.conf[:USE_AUTOCOMPLETE] = false
- IRB::RelineInputMethod.new
+ IRB::RelineInputMethod.new(IRB::RegexpCompletor.new)
refute Reline.autocompletion
assert_equal empty_proc, Reline.dialog_proc(:show_doc).dialog_proc
@@ -49,10 +55,10 @@ module TestIRB
IRB.conf[:USE_AUTOCOMPLETE] = true
- IRB::RelineInputMethod.new
+ IRB::RelineInputMethod.new(IRB::RegexpCompletor.new)
assert Reline.autocompletion
- assert_equal IRB::RelineInputMethod::SHOW_DOC_DIALOG, Reline.dialog_proc(:show_doc).dialog_proc
+ assert_not_equal empty_proc, Reline.dialog_proc(:show_doc).dialog_proc
ensure
Reline.add_dialog_proc(:show_doc, original_show_doc_proc, Reline::DEFAULT_DIALOG_CONTEXT)
end
@@ -65,7 +71,7 @@ module TestIRB
IRB.conf[:USE_AUTOCOMPLETE] = true
without_rdoc do
- IRB::RelineInputMethod.new
+ IRB::RelineInputMethod.new(IRB::RegexpCompletor.new)
end
assert Reline.autocompletion
@@ -75,5 +81,93 @@ module TestIRB
Reline.add_dialog_proc(:show_doc, original_show_doc_proc, Reline::DEFAULT_DIALOG_CONTEXT)
end
end
-end
+ class DisplayDocumentTest < InputMethodTest
+ def setup
+ super
+ @driver = RDoc::RI::Driver.new(use_stdout: true)
+ end
+
+ def display_document(target, bind, driver = nil)
+ input_method = IRB::RelineInputMethod.new(IRB::RegexpCompletor.new)
+ input_method.instance_variable_set(:@rdoc_ri_driver, driver) if driver
+ input_method.instance_variable_set(:@completion_params, ['', target, '', bind])
+ input_method.display_document(target)
+ end
+
+ def test_perfectly_matched_namespace_triggers_document_display
+ omit unless has_rdoc_content?
+
+ out, err = capture_output do
+ display_document("String", binding, @driver)
+ end
+
+ assert_empty(err)
+
+ assert_include(out, " S\bSt\btr\bri\bin\bng\bg")
+ end
+
+ def test_perfectly_matched_multiple_namespaces_triggers_document_display
+ result = nil
+ out, err = capture_output do
+ result = display_document("{}.nil?", binding, @driver)
+ end
+
+ assert_empty(err)
+
+ # check if there're rdoc contents (e.g. CI doesn't generate them)
+ if has_rdoc_content?
+ # if there's rdoc content, we can verify by checking stdout
+ # rdoc generates control characters for formatting method names
+ assert_include(out, "P\bPr\bro\boc\bc.\b.n\bni\bil\bl?\b?") # Proc.nil?
+ assert_include(out, "H\bHa\bas\bsh\bh.\b.n\bni\bil\bl?\b?") # Hash.nil?
+ else
+ # this is a hacky way to verify the rdoc rendering code path because CI doesn't have rdoc content
+ # if there are multiple namespaces to be rendered, PerfectMatchedProc renders the result with a document
+ # which always returns the bytes rendered, even if it's 0
+ assert_equal(0, result)
+ end
+ end
+
+ def test_not_matched_namespace_triggers_nothing
+ result = nil
+ out, err = capture_output do
+ result = display_document("Stri", binding, @driver)
+ end
+
+ assert_empty(err)
+ assert_empty(out)
+ assert_nil(result)
+ end
+
+ def test_perfect_matching_stops_without_rdoc
+ result = nil
+
+ out, err = capture_output do
+ without_rdoc do
+ result = display_document("String", binding)
+ end
+ end
+
+ assert_empty(err)
+ assert_not_match(/from ruby core/, out)
+ assert_nil(result)
+ end
+
+ def test_perfect_matching_handles_nil_namespace
+ out, err = capture_output do
+ # symbol literal has `nil` doc namespace so it's a good test subject
+ assert_nil(display_document(":aiueo", binding, @driver))
+ end
+
+ assert_empty(err)
+ assert_empty(out)
+ end
+
+ private
+
+ def has_rdoc_content?
+ File.exist?(RDoc::RI::Paths::BASE)
+ end
+ end
+end
diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb
new file mode 100644
index 0000000000..ece7902909
--- /dev/null
+++ b/test/irb/test_irb.rb
@@ -0,0 +1,924 @@
+# frozen_string_literal: true
+require "irb"
+
+require_relative "helper"
+
+module TestIRB
+ class InputTest < IntegrationTestCase
+ def test_symbol_aliases_are_handled_correctly
+ write_ruby <<~'RUBY'
+ class Foo
+ end
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "$ Foo"
+ type "exit!"
+ end
+
+ assert_include output, "From: #{@ruby_file.path}:1"
+ end
+
+ def test_symbol_aliases_are_handled_correctly_with_singleline_mode
+ write_rc <<~RUBY
+ IRB.conf[:USE_SINGLELINE] = true
+ RUBY
+
+ write_ruby <<~'RUBY'
+ class Foo
+ end
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "irb_info"
+ type "$ Foo"
+ type "exit!"
+ end
+
+ # Make sure it's tested in singleline mode
+ assert_include output, "InputMethod: ReadlineInputMethod"
+ assert_include output, "From: #{@ruby_file.path}:1"
+ end
+
+ def test_underscore_stores_last_result
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "1 + 1"
+ type "_ + 10"
+ type "exit!"
+ end
+
+ assert_include output, "=> 12"
+ end
+
+ def test_evaluate_with_encoding_error_without_lineno
+ if RUBY_ENGINE == 'truffleruby'
+ omit "Remove me after https://github.com/ruby/prism/issues/2129 is addressed and adopted in TruffleRuby"
+ end
+
+ if RUBY_VERSION >= "3.3."
+ omit "Now raises SyntaxError"
+ end
+
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type %q[:"\xAE"]
+ type "exit!"
+ end
+
+ assert_include output, 'invalid symbol in encoding UTF-8 :"\xAE"'
+ # EncodingError would be wrapped with ANSI escape sequences, so we assert it separately
+ assert_include output, "EncodingError"
+ end
+
+ def test_evaluate_still_emits_warning
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type %q[def foo; END {}; end]
+ type "exit!"
+ end
+
+ assert_include output, '(irb):1: warning: END in method; use at_exit'
+ end
+
+ def test_symbol_aliases_dont_affect_ruby_syntax
+ write_ruby <<~'RUBY'
+ $foo = "It's a foo"
+ @bar = "It's a bar"
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "$foo"
+ type "@bar"
+ type "exit!"
+ end
+
+ assert_include output, "=> \"It's a foo\""
+ assert_include output, "=> \"It's a bar\""
+ end
+
+ def test_empty_input_echoing_behaviour
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type ""
+ type " "
+ type "exit"
+ end
+
+ assert_not_match(/irb\(main\):001> (\r*\n)?=> nil/, output)
+ assert_match(/irb\(main\):002> (\r*\n)?=> nil/, output)
+ end
+ end
+
+ class NestedBindingIrbTest < IntegrationTestCase
+ def test_current_context_restore
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type '$ctx = IRB.CurrentContext'
+ type 'binding.irb'
+ type 'p context_changed: IRB.CurrentContext != $ctx'
+ type 'exit'
+ type 'p context_restored: IRB.CurrentContext == $ctx'
+ type 'exit'
+ end
+
+ assert_include output, '{:context_changed=>true}'
+ assert_include output, '{:context_restored=>true}'
+ end
+ end
+
+ class IrbIOConfigurationTest < TestCase
+ Row = Struct.new(:content, :current_line_spaces, :new_line_spaces, :indent_level)
+
+ class MockIO_AutoIndent
+ attr_reader :calculated_indent
+
+ def initialize(*params)
+ @params = params
+ end
+
+ def auto_indent(&block)
+ @calculated_indent = block.call(*@params)
+ end
+ end
+
+ class MockIO_DynamicPrompt
+ attr_reader :prompt_list
+
+ def initialize(params, &assertion)
+ @params = params
+ end
+
+ def dynamic_prompt(&block)
+ @prompt_list = block.call(@params)
+ end
+ end
+
+ def setup
+ save_encodings
+ @irb = build_irb
+ end
+
+ def teardown
+ restore_encodings
+ end
+
+ class AutoIndentationTest < IrbIOConfigurationTest
+ def test_auto_indent
+ input_with_correct_indents = [
+ [%q(def each_top_level_statement), 0, 2],
+ [%q( initialize_input), 2, 2],
+ [%q( catch(:TERM_INPUT) do), 2, 4],
+ [%q( loop do), 4, 6],
+ [%q( begin), 6, 8],
+ [%q( prompt), 8, 8],
+ [%q( unless l = lex), 8, 10],
+ [%q( throw :TERM_INPUT if @line == ''), 10, 10],
+ [%q( else), 8, 10],
+ [%q( @line_no += l.count("\n")), 10, 10],
+ [%q( next if l == "\n"), 10, 10],
+ [%q( @line.concat l), 10, 10],
+ [%q( if @code_block_open or @ltype or @continue or @indent > 0), 10, 12],
+ [%q( next), 12, 12],
+ [%q( end), 10, 10],
+ [%q( end), 8, 8],
+ [%q( if @line != "\n"), 8, 10],
+ [%q( @line.force_encoding(@io.encoding)), 10, 10],
+ [%q( yield @line, @exp_line_no), 10, 10],
+ [%q( end), 8, 8],
+ [%q( break if @io.eof?), 8, 8],
+ [%q( @line = ''), 8, 8],
+ [%q( @exp_line_no = @line_no), 8, 8],
+ [%q( ), nil, 8],
+ [%q( @indent = 0), 8, 8],
+ [%q( rescue TerminateLineInput), 6, 8],
+ [%q( initialize_input), 8, 8],
+ [%q( prompt), 8, 8],
+ [%q( end), 6, 6],
+ [%q( end), 4, 4],
+ [%q( end), 2, 2],
+ [%q(end), 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents)
+ end
+
+ def test_braces_on_their_own_line
+ input_with_correct_indents = [
+ [%q(if true), 0, 2],
+ [%q( [), 2, 4],
+ [%q( ]), 2, 2],
+ [%q(end), 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents)
+ end
+
+ def test_multiple_braces_in_a_line
+ input_with_correct_indents = [
+ [%q([[[), 0, 6],
+ [%q( ]), 4, 4],
+ [%q( ]), 2, 2],
+ [%q(]), 0, 0],
+ [%q([<<FOO]), 0, 0],
+ [%q(hello), 0, 0],
+ [%q(FOO), 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents)
+ end
+
+ def test_a_closed_brace_and_not_closed_brace_in_a_line
+ input_with_correct_indents = [
+ [%q(p() {), 0, 2],
+ [%q(}), 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents)
+ end
+
+ def test_symbols
+ input_with_correct_indents = [
+ [%q(:a), 0, 0],
+ [%q(:A), 0, 0],
+ [%q(:+), 0, 0],
+ [%q(:@@a), 0, 0],
+ [%q(:@a), 0, 0],
+ [%q(:$a), 0, 0],
+ [%q(:def), 0, 0],
+ [%q(:`), 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents)
+ end
+
+ def test_incomplete_coding_magic_comment
+ input_with_correct_indents = [
+ [%q(#coding:u), 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents)
+ end
+
+ def test_incomplete_encoding_magic_comment
+ input_with_correct_indents = [
+ [%q(#encoding:u), 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents)
+ end
+
+ def test_incomplete_emacs_coding_magic_comment
+ input_with_correct_indents = [
+ [%q(# -*- coding: u), 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents)
+ end
+
+ def test_incomplete_vim_coding_magic_comment
+ input_with_correct_indents = [
+ [%q(# vim:set fileencoding=u), 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents)
+ end
+
+ def test_mixed_rescue
+ input_with_correct_indents = [
+ [%q(def m), 0, 2],
+ [%q( begin), 2, 4],
+ [%q( begin), 4, 6],
+ [%q( x = a rescue 4), 6, 6],
+ [%q( y = [(a rescue 5)]), 6, 6],
+ [%q( [x, y]), 6, 6],
+ [%q( rescue => e), 4, 6],
+ [%q( raise e rescue 8), 6, 6],
+ [%q( end), 4, 4],
+ [%q( rescue), 2, 4],
+ [%q( raise rescue 11), 4, 4],
+ [%q( end), 2, 2],
+ [%q(rescue => e), 0, 2],
+ [%q( raise e rescue 14), 2, 2],
+ [%q(end), 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents)
+ end
+
+ def test_oneliner_method_definition
+ input_with_correct_indents = [
+ [%q(class A), 0, 2],
+ [%q( def foo0), 2, 4],
+ [%q( 3), 4, 4],
+ [%q( end), 2, 2],
+ [%q( def foo1()), 2, 4],
+ [%q( 3), 4, 4],
+ [%q( end), 2, 2],
+ [%q( def foo2(a, b)), 2, 4],
+ [%q( a + b), 4, 4],
+ [%q( end), 2, 2],
+ [%q( def foo3 a, b), 2, 4],
+ [%q( a + b), 4, 4],
+ [%q( end), 2, 2],
+ [%q( def bar0() = 3), 2, 2],
+ [%q( def bar1(a) = a), 2, 2],
+ [%q( def bar2(a, b) = a + b), 2, 2],
+ [%q( def bar3() = :s), 2, 2],
+ [%q( def bar4() = Time.now), 2, 2],
+ [%q(end), 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents)
+ end
+
+ def test_tlambda
+ input_with_correct_indents = [
+ [%q(if true), 0, 2, 1],
+ [%q( -> {), 2, 4, 2],
+ [%q( }), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
+ end
+
+ def test_corresponding_syntax_to_keyword_do_in_class
+ input_with_correct_indents = [
+ [%q(class C), 0, 2, 1],
+ [%q( while method_name do), 2, 4, 2],
+ [%q( 3), 4, 4, 2],
+ [%q( end), 2, 2, 1],
+ [%q( foo do), 2, 4, 2],
+ [%q( 3), 4, 4, 2],
+ [%q( end), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
+ end
+
+ def test_corresponding_syntax_to_keyword_do
+ input_with_correct_indents = [
+ [%q(while i > 0), 0, 2, 1],
+ [%q( 3), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ [%q(while true), 0, 2, 1],
+ [%q( 3), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ [%q(while ->{i > 0}.call), 0, 2, 1],
+ [%q( 3), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ [%q(while ->{true}.call), 0, 2, 1],
+ [%q( 3), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ [%q(while i > 0 do), 0, 2, 1],
+ [%q( 3), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ [%q(while true do), 0, 2, 1],
+ [%q( 3), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ [%q(while ->{i > 0}.call do), 0, 2, 1],
+ [%q( 3), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ [%q(while ->{true}.call do), 0, 2, 1],
+ [%q( 3), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ [%q(foo do), 0, 2, 1],
+ [%q( 3), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ [%q(foo true do), 0, 2, 1],
+ [%q( 3), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ [%q(foo ->{true} do), 0, 2, 1],
+ [%q( 3), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ [%q(foo ->{i > 0} do), 0, 2, 1],
+ [%q( 3), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
+ end
+
+ def test_corresponding_syntax_to_keyword_for
+ input_with_correct_indents = [
+ [%q(for i in [1]), 0, 2, 1],
+ [%q( puts i), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
+ end
+
+ def test_corresponding_syntax_to_keyword_for_with_do
+ input_with_correct_indents = [
+ [%q(for i in [1] do), 0, 2, 1],
+ [%q( puts i), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
+ end
+
+ def test_typing_incomplete_include_interpreted_as_keyword_in
+ input_with_correct_indents = [
+ [%q(module E), 0, 2, 1],
+ [%q(end), 0, 0, 0],
+ [%q(class A), 0, 2, 1],
+ [%q( in), 2, 2, 1] # scenario typing `include E`
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
+
+ end
+
+ def test_bracket_corresponding_to_times
+ input_with_correct_indents = [
+ [%q(3.times { |i|), 0, 2, 1],
+ [%q( puts i), 2, 2, 1],
+ [%q(}), 0, 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
+ end
+
+ def test_do_corresponding_to_times
+ input_with_correct_indents = [
+ [%q(3.times do |i|), 0, 2, 1],
+ [%q( puts i), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
+ end
+
+ def test_bracket_corresponding_to_loop
+ input_with_correct_indents = [
+ ['loop {', 0, 2, 1],
+ [' 3', 2, 2, 1],
+ ['}', 0, 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
+ end
+
+ def test_do_corresponding_to_loop
+ input_with_correct_indents = [
+ [%q(loop do), 0, 2, 1],
+ [%q( 3), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
+ end
+
+ def test_embdoc_indent
+ input_with_correct_indents = [
+ [%q(=begin), 0, 0, 0],
+ [%q(a), 0, 0, 0],
+ [%q( b), 1, 1, 0],
+ [%q(=end), 0, 0, 0],
+ [%q(if 1), 0, 2, 1],
+ [%q( 2), 2, 2, 1],
+ [%q(=begin), 0, 0, 0],
+ [%q(a), 0, 0, 0],
+ [%q( b), 1, 1, 0],
+ [%q(=end), 0, 2, 1],
+ [%q( 3), 2, 2, 1],
+ [%q(end), 0, 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
+ end
+
+ def test_heredoc_with_indent
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7.0')
+ pend 'This test needs Ripper::Lexer#scan to take broken tokens'
+ end
+ input_with_correct_indents = [
+ [%q(<<~Q+<<~R), 0, 2, 1],
+ [%q(a), 2, 2, 1],
+ [%q(a), 2, 2, 1],
+ [%q( b), 2, 2, 1],
+ [%q( b), 2, 2, 1],
+ [%q( Q), 0, 2, 1],
+ [%q( c), 4, 4, 1],
+ [%q( c), 4, 4, 1],
+ [%q( R), 0, 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
+ end
+
+ def test_oneliner_def_in_multiple_lines
+ input_with_correct_indents = [
+ [%q(def a()=[), 0, 2, 1],
+ [%q( 1,), 2, 2, 1],
+ [%q(].), 0, 0, 0],
+ [%q(to_s), 0, 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
+ end
+
+ def test_broken_heredoc
+ input_with_correct_indents = [
+ [%q(def foo), 0, 2, 1],
+ [%q( <<~Q), 2, 4, 2],
+ [%q( Qend), 4, 4, 2],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
+ end
+
+ def test_pasted_code_keep_base_indent_spaces
+ input_with_correct_indents = [
+ [%q( def foo), 0, 6, 1],
+ [%q( if bar), 6, 10, 2],
+ [%q( [1), 10, 12, 3],
+ [%q( ]+[["a), 10, 14, 4],
+ [%q(b" + `c), 0, 14, 4],
+ [%q(d` + /e), 0, 14, 4],
+ [%q(f/ + :"g), 0, 14, 4],
+ [%q(h".tap do), 0, 16, 5],
+ [%q( 1), 16, 16, 5],
+ [%q( end), 14, 14, 4],
+ [%q( ]), 12, 12, 3],
+ [%q( ]), 10, 10, 2],
+ [%q( end), 8, 6, 1],
+ [%q( end), 4, 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
+ end
+
+ def test_pasted_code_keep_base_indent_spaces_with_heredoc
+ input_with_correct_indents = [
+ [%q( def foo), 0, 6, 1],
+ [%q( if bar), 6, 10, 2],
+ [%q( [1), 10, 12, 3],
+ [%q( ]+[["a), 10, 14, 4],
+ [%q(b" + <<~A + <<-B + <<C), 0, 16, 5],
+ [%q( a#{), 16, 18, 6],
+ [%q( 1), 18, 18, 6],
+ [%q( }), 16, 16, 5],
+ [%q( A), 14, 16, 5],
+ [%q( b#{), 16, 18, 6],
+ [%q( 1), 18, 18, 6],
+ [%q( }), 16, 16, 5],
+ [%q( B), 14, 0, 0],
+ [%q(c#{), 0, 2, 1],
+ [%q(1), 2, 2, 1],
+ [%q(}), 0, 0, 0],
+ [%q(C), 0, 14, 4],
+ [%q( ]), 12, 12, 3],
+ [%q( ]), 10, 10, 2],
+ [%q( end), 8, 6, 1],
+ [%q( end), 4, 0, 0],
+ ]
+
+ assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
+ end
+
+ def test_heredoc_keep_indent_spaces
+ (1..4).each do |indent|
+ row = Row.new(' ' * indent, nil, [4, indent].max, 2)
+ lines = ['def foo', ' <<~Q', row.content]
+ assert_row_indenting(lines, row)
+ assert_indent_level(lines, row.indent_level)
+ end
+ end
+
+ private
+
+ def assert_row_indenting(lines, row)
+ actual_current_line_spaces = calculate_indenting(lines, false)
+
+ error_message = <<~MSG
+ Incorrect spaces calculation for line:
+
+ ```
+ > #{lines.last}
+ ```
+
+ All lines:
+
+ ```
+ #{lines.join("\n")}
+ ```
+ MSG
+ assert_equal(row.current_line_spaces, actual_current_line_spaces, error_message)
+
+ error_message = <<~MSG
+ Incorrect spaces calculation for line after the current line:
+
+ ```
+ #{lines.last}
+ >
+ ```
+
+ All lines:
+
+ ```
+ #{lines.join("\n")}
+ ```
+ MSG
+ actual_next_line_spaces = calculate_indenting(lines, true)
+ assert_equal(row.new_line_spaces, actual_next_line_spaces, error_message)
+ end
+
+ def assert_rows_with_correct_indents(rows_with_spaces, assert_indent_level: false)
+ lines = []
+ rows_with_spaces.map do |row|
+ row = Row.new(*row)
+ lines << row.content
+ assert_row_indenting(lines, row)
+
+ if assert_indent_level
+ assert_indent_level(lines, row.indent_level)
+ end
+ end
+ end
+
+ def assert_indent_level(lines, expected)
+ code = lines.map { |l| "#{l}\n" }.join # code should end with "\n"
+ _tokens, opens, _ = @irb.scanner.check_code_state(code, local_variables: [])
+ indent_level = @irb.scanner.calc_indent_level(opens)
+ error_message = "Calculated the wrong number of indent level for:\n #{lines.join("\n")}"
+ assert_equal(expected, indent_level, error_message)
+ end
+
+ def calculate_indenting(lines, add_new_line)
+ lines = lines + [""] if add_new_line
+ last_line_index = lines.length - 1
+ byte_pointer = lines.last.length
+
+ mock_io = MockIO_AutoIndent.new(lines, last_line_index, byte_pointer, add_new_line)
+ @irb.context.auto_indent_mode = true
+ @irb.context.io = mock_io
+ @irb.configure_io
+
+ mock_io.calculated_indent
+ end
+ end
+
+ class DynamicPromptTest < IrbIOConfigurationTest
+ def test_endless_range_at_end_of_line
+ input_with_prompt = [
+ ['001:0: :> ', %q(a = 3..)],
+ ['002:0: :> ', %q()],
+ ]
+
+ assert_dynamic_prompt(input_with_prompt)
+ end
+
+ def test_heredoc_with_embexpr
+ input_with_prompt = [
+ ['001:0:":* ', %q(<<A+%W[#{<<B)],
+ ['002:0:":* ', %q(#{<<C+%W[)],
+ ['003:0:":* ', %q(a)],
+ ['004:2:]:* ', %q(C)],
+ ['005:2:]:* ', %q(a)],
+ ['006:0:":* ', %q(]})],
+ ['007:0:":* ', %q(})],
+ ['008:0:":* ', %q(A)],
+ ['009:2:]:* ', %q(B)],
+ ['010:1:]:* ', %q(})],
+ ['011:0: :> ', %q(])],
+ ['012:0: :> ', %q()],
+ ]
+
+ assert_dynamic_prompt(input_with_prompt)
+ end
+
+ def test_heredoc_prompt_with_quotes
+ input_with_prompt = [
+ ["001:1:':* ", %q(<<~'A')],
+ ["002:1:':* ", %q(#{foobar})],
+ ["003:0: :> ", %q(A)],
+ ["004:1:`:* ", %q(<<~`A`)],
+ ["005:1:`:* ", %q(whoami)],
+ ["006:0: :> ", %q(A)],
+ ['007:1:":* ', %q(<<~"A")],
+ ['008:1:":* ', %q(foobar)],
+ ['009:0: :> ', %q(A)],
+ ]
+
+ assert_dynamic_prompt(input_with_prompt)
+ end
+
+ def test_backtick_method
+ input_with_prompt = [
+ ['001:0: :> ', %q(self.`(arg))],
+ ['002:0: :> ', %q()],
+ ['003:0: :> ', %q(def `(); end)],
+ ['004:0: :> ', %q()],
+ ]
+
+ assert_dynamic_prompt(input_with_prompt)
+ end
+
+ def test_dynamic_prompt
+ input_with_prompt = [
+ ['001:1: :* ', %q(def hoge)],
+ ['002:1: :* ', %q( 3)],
+ ['003:0: :> ', %q(end)],
+ ]
+
+ assert_dynamic_prompt(input_with_prompt)
+ end
+
+ def test_dynamic_prompt_with_double_newline_breaking_code
+ input_with_prompt = [
+ ['001:1: :* ', %q(if true)],
+ ['002:2: :* ', %q(%)],
+ ['003:1: :* ', %q(;end)],
+ ['004:1: :* ', %q(;hello)],
+ ['005:0: :> ', %q(end)],
+ ]
+
+ assert_dynamic_prompt(input_with_prompt)
+ end
+
+ def test_dynamic_prompt_with_multiline_literal
+ input_with_prompt = [
+ ['001:1: :* ', %q(if true)],
+ ['002:2:]:* ', %q( %w[)],
+ ['003:2:]:* ', %q( a)],
+ ['004:1: :* ', %q( ])],
+ ['005:1: :* ', %q( b)],
+ ['006:2:]:* ', %q( %w[)],
+ ['007:2:]:* ', %q( c)],
+ ['008:1: :* ', %q( ])],
+ ['009:0: :> ', %q(end)],
+ ]
+
+ assert_dynamic_prompt(input_with_prompt)
+ end
+
+ def test_dynamic_prompt_with_blank_line
+ input_with_prompt = [
+ ['001:1:]:* ', %q(%w[)],
+ ['002:1:]:* ', %q()],
+ ['003:0: :> ', %q(])],
+ ]
+
+ assert_dynamic_prompt(input_with_prompt)
+ end
+
+ def assert_dynamic_prompt(input_with_prompt)
+ expected_prompt_list, lines = input_with_prompt.transpose
+ def @irb.generate_prompt(opens, continue, line_offset)
+ ltype = @scanner.ltype_from_open_tokens(opens)
+ indent = @scanner.calc_indent_level(opens)
+ continue = opens.any? || continue
+ line_no = @line_no + line_offset
+ '%03d:%01d:%1s:%s ' % [line_no, indent, ltype, continue ? '*' : '>']
+ end
+ io = MockIO_DynamicPrompt.new(lines)
+ @irb.context.io = io
+ @irb.configure_io
+
+ error_message = <<~EOM
+ Expected dynamic prompt:
+ #{expected_prompt_list.join("\n")}
+
+ Actual dynamic prompt:
+ #{io.prompt_list.join("\n")}
+ EOM
+ assert_equal(expected_prompt_list, io.prompt_list, error_message)
+ end
+ end
+
+ private
+
+ def build_binding
+ Object.new.instance_eval { binding }
+ end
+
+ def build_irb
+ IRB.init_config(nil)
+ workspace = IRB::WorkSpace.new(build_binding)
+
+ IRB.conf[:VERBOSE] = false
+ IRB::Irb.new(workspace, TestInputMethod.new)
+ end
+ end
+
+ class BacktraceFilteringTest < TestIRB::IntegrationTestCase
+ def setup
+ super
+ # These tests are sensitive to warnings, so we disable them
+ original_rubyopt = [ENV["RUBYOPT"], @envs["RUBYOPT"]].compact.join(" ")
+ @envs["RUBYOPT"] = original_rubyopt + " -W0"
+ end
+
+ def test_backtrace_filtering
+ write_ruby <<~'RUBY'
+ def foo
+ raise "error"
+ end
+
+ def bar
+ foo
+ end
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "bar"
+ type "exit"
+ end
+
+ assert_match(/irbtest-.*\.rb:2:in (`|'Object#)foo': error \(RuntimeError\)/, output)
+ frame_traces = output.split("\n").select { |line| line.strip.match?(/from /) }.map(&:strip)
+
+ expected_traces = if RUBY_VERSION >= "3.3.0"
+ [
+ /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/,
+ /from .*\/irbtest-.*.rb\(irb\):1:in [`']<main>'/,
+ /from <internal:kernel>:\d+:in (`|'Kernel#)loop'/,
+ /from <internal:prelude>:\d+:in (`|'Binding#)irb'/,
+ /from .*\/irbtest-.*.rb:9:in [`']<main>'/
+ ]
+ else
+ [
+ /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/,
+ /from .*\/irbtest-.*.rb\(irb\):1:in [`']<main>'/,
+ /from <internal:prelude>:\d+:in (`|'Binding#)irb'/,
+ /from .*\/irbtest-.*.rb:9:in [`']<main>'/
+ ]
+ end
+
+ expected_traces.reverse! if RUBY_VERSION < "3.0.0"
+
+ expected_traces.each_with_index do |expected_trace, index|
+ assert_match(expected_trace, frame_traces[index])
+ end
+ end
+
+ def test_backtrace_filtering_with_backtrace_filter
+ write_rc <<~'RUBY'
+ class TestBacktraceFilter
+ def self.call(backtrace)
+ backtrace.reject { |line| line.include?("internal") }
+ end
+ end
+
+ IRB.conf[:BACKTRACE_FILTER] = TestBacktraceFilter
+ RUBY
+
+ write_ruby <<~'RUBY'
+ def foo
+ raise "error"
+ end
+
+ def bar
+ foo
+ end
+
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "bar"
+ type "exit"
+ end
+
+ assert_match(/irbtest-.*\.rb:2:in (`|'Object#)foo': error \(RuntimeError\)/, output)
+ frame_traces = output.split("\n").select { |line| line.strip.match?(/from /) }.map(&:strip)
+
+ expected_traces = [
+ /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/,
+ /from .*\/irbtest-.*.rb\(irb\):1:in [`']<main>'/,
+ /from .*\/irbtest-.*.rb:9:in [`']<main>'/
+ ]
+
+ expected_traces.reverse! if RUBY_VERSION < "3.0.0"
+
+ expected_traces.each_with_index do |expected_trace, index|
+ assert_match(expected_trace, frame_traces[index])
+ end
+ end
+ end
+end
diff --git a/test/irb/test_nesting_parser.rb b/test/irb/test_nesting_parser.rb
new file mode 100644
index 0000000000..2482d40081
--- /dev/null
+++ b/test/irb/test_nesting_parser.rb
@@ -0,0 +1,341 @@
+# frozen_string_literal: false
+require 'irb'
+
+require_relative "helper"
+
+module TestIRB
+ class NestingParserTest < TestCase
+ def setup
+ save_encodings
+ end
+
+ def teardown
+ restore_encodings
+ end
+
+ def parse_by_line(code)
+ IRB::NestingParser.parse_by_line(IRB::RubyLex.ripper_lex_without_warning(code))
+ end
+
+ def test_open_tokens
+ code = <<~'EOS'
+ class A
+ def f
+ if true
+ tap do
+ {
+ x: "
+ #{p(1, 2, 3
+ EOS
+ opens = IRB::NestingParser.open_tokens(IRB::RubyLex.ripper_lex_without_warning(code))
+ assert_equal(%w[class def if do { " #{ (], opens.map(&:tok))
+ end
+
+ def test_parse_by_line
+ code = <<~EOS
+ (((((1+2
+ ).to_s())).tap do (((
+ EOS
+ _tokens, prev_opens, next_opens, min_depth = parse_by_line(code).last
+ assert_equal(%w[( ( ( ( (], prev_opens.map(&:tok))
+ assert_equal(%w[( ( do ( ( (], next_opens.map(&:tok))
+ assert_equal(2, min_depth)
+ end
+
+ def test_ruby_syntax
+ code = <<~'EOS'
+ class A
+ 1 if 2
+ 1 while 2
+ 1 until 2
+ 1 unless 2
+ 1 rescue 2
+ begin; rescue; ensure; end
+ tap do; rescue; ensure; end
+ class B; end
+ module C; end
+ def f; end
+ def `; end
+ def f() = 1
+ %(); %w[]; %q(); %r{}; %i[]
+ "#{1}"; ''; /#{1}/; `#{1}`
+ :sym; :"sym"; :+; :`; :if
+ [1, 2, 3]
+ { x: 1, y: 2 }
+ (a, (*b, c), d), e = 1, 2, 3
+ ->(a){}; ->(a) do end
+ -> a = -> b = :do do end do end
+ if 1; elsif 2; else; end
+ unless 1; end
+ while 1; end
+ until 1; end
+ for i in j; end
+ case 1; when 2; end
+ puts(1, 2, 3)
+ loop{|i|}
+ loop do |i| end
+ end
+ EOS
+ line_results = parse_by_line(code)
+ assert_equal(code.lines.size, line_results.size)
+ class_open, *inner_line_results, class_close = line_results
+ assert_equal(['class'], class_open[2].map(&:tok))
+ inner_line_results.each {|result| assert_equal(['class'], result[2].map(&:tok)) }
+ assert_equal([], class_close[2].map(&:tok))
+ end
+
+ def test_multiline_string
+ code = <<~EOS
+ "
+ aaa
+ bbb
+ "
+ <<A
+ aaa
+ bbb
+ A
+ EOS
+ line_results = parse_by_line(code)
+ assert_equal(code.lines.size, line_results.size)
+ string_content_line, string_opens = line_results[1]
+ assert_equal("\naaa\nbbb\n", string_content_line.first.first.tok)
+ assert_equal("aaa\n", string_content_line.first.last)
+ assert_equal(['"'], string_opens.map(&:tok))
+ heredoc_content_line, heredoc_opens = line_results[6]
+ assert_equal("aaa\nbbb\n", heredoc_content_line.first.first.tok)
+ assert_equal("bbb\n", heredoc_content_line.first.last)
+ assert_equal(['<<A'], heredoc_opens.map(&:tok))
+ _line, _prev_opens, next_opens, _min_depth = line_results.last
+ assert_equal([], next_opens)
+ end
+
+ def test_backslash_continued_nested_symbol
+ code = <<~'EOS'
+ x = <<A, :\
+ heredoc #{
+ here
+ }
+ A
+ =begin
+ embdoc
+ =end
+ # comment
+
+ if # this is symbol :if
+ while
+ EOS
+ line_results = parse_by_line(code)
+ assert_equal(%w[: <<A #{], line_results[2][2].map(&:tok))
+ assert_equal(%w[while], line_results.last[2].map(&:tok))
+ end
+
+ def test_oneliner_def
+ code = <<~EOC
+ if true
+ # normal oneliner def
+ def f = 1
+ def f() = 1
+ def f(*) = 1
+ # keyword, backtick, op
+ def * = 1
+ def ` = 1
+ def if = 1
+ def *() = 1
+ def `() = 1
+ def if() = 1
+ # oneliner def with receiver
+ def a.* = 1
+ def $a.* = 1
+ def @a.` = 1
+ def A.` = 1
+ def ((a;b;c)).*() = 1
+ def ((a;b;c)).if() = 1
+ def ((a;b;c)).end() = 1
+ # multiline oneliner def
+ def f =
+ 1
+ def f()
+ =
+ 1
+ # oneliner def with comment and embdoc
+ def # comment
+ =begin
+ embdoc
+ =end
+ ((a;b;c))
+ . # comment
+ =begin
+ embdoc
+ =end
+ f (*) # comment
+ =begin
+ embdoc
+ =end
+ =
+ 1
+ # nested oneliner def
+ def f(x = def f() = 1) = def f() = 1
+ EOC
+ _tokens, _prev_opens, next_opens, min_depth = parse_by_line(code).last
+ assert_equal(['if'], next_opens.map(&:tok))
+ assert_equal(1, min_depth)
+ end
+
+ def test_heredoc_embexpr
+ code = <<~'EOS'
+ <<A+<<B+<<C+(<<D+(<<E)
+ #{
+ <<~F+"#{<<~G}
+ #{
+ here
+ }
+ F
+ G
+ "
+ }
+ A
+ B
+ C
+ D
+ E
+ )
+ EOS
+ line_results = parse_by_line(code)
+ last_opens = line_results.last[-2]
+ assert_equal([], last_opens)
+ _tokens, _prev_opens, next_opens, _min_depth = line_results[4]
+ assert_equal(%w[( <<E <<D <<C <<B <<A #{ " <<~G <<~F #{], next_opens.map(&:tok))
+ end
+
+ def test_for_in
+ code = <<~EOS
+ for i in j
+ here
+ end
+ for i in j do
+ here
+ end
+ for i in
+ j do
+ here
+ end
+ for
+ # comment
+ i in j do
+ here
+ end
+ for (a;b;c).d in (a;b;c) do
+ here
+ end
+ for i in :in + :do do
+ here
+ end
+ for i in -> do end do
+ here
+ end
+ EOS
+ line_results = parse_by_line(code).select { |tokens,| tokens.map(&:last).include?('here') }
+ assert_equal(7, line_results.size)
+ line_results.each do |_tokens, _prev_opens, next_opens, _min_depth|
+ assert_equal(['for'], next_opens.map(&:tok))
+ end
+ end
+
+ def test_while_until
+ base_code = <<~'EOS'
+ while_or_until true
+ here
+ end
+ while_or_until a < c
+ here
+ end
+ while_or_until true do
+ here
+ end
+ while_or_until
+ # comment
+ (a + b) <
+ # comment
+ c do
+ here
+ end
+ while_or_until :\
+ do do
+ here
+ end
+ while_or_until def do; end == :do do
+ here
+ end
+ while_or_until -> do end do
+ here
+ end
+ EOS
+ %w[while until].each do |keyword|
+ code = base_code.gsub('while_or_until', keyword)
+ line_results = parse_by_line(code).select { |tokens,| tokens.map(&:last).include?('here') }
+ assert_equal(7, line_results.size)
+ line_results.each do |_tokens, _prev_opens, next_opens, _min_depth|
+ assert_equal([keyword], next_opens.map(&:tok) )
+ end
+ end
+ end
+
+ def test_undef_alias
+ codes = [
+ 'undef foo',
+ 'alias foo bar',
+ 'undef !',
+ 'alias + -',
+ 'alias $a $b',
+ 'undef do',
+ 'alias do do',
+ 'undef :do',
+ 'alias :do :do',
+ 'undef :"#{alias do do}"',
+ 'alias :"#{undef do}" do',
+ 'alias do :"#{undef do}"'
+ ]
+ code_with_comment = <<~EOS
+ undef #
+ #
+ do #
+ alias #
+ #
+ do #
+ #
+ do #
+ EOS
+ code_with_heredoc = <<~EOS
+ <<~A; alias
+ A
+ :"#{<<~A}"
+ A
+ do
+ EOS
+ [*codes, code_with_comment, code_with_heredoc].each do |code|
+ opens = IRB::NestingParser.open_tokens(IRB::RubyLex.ripper_lex_without_warning('(' + code + "\nif"))
+ assert_equal(%w[( if], opens.map(&:tok))
+ end
+ end
+
+ def test_case_in
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7.0')
+ pend 'This test requires ruby version that supports case-in syntax'
+ end
+ code = <<~EOS
+ case 1
+ in 1
+ here
+ in
+ 2
+ here
+ end
+ EOS
+ line_results = parse_by_line(code).select { |tokens,| tokens.map(&:last).include?('here') }
+ assert_equal(2, line_results.size)
+ line_results.each do |_tokens, _prev_opens, next_opens, _min_depth|
+ assert_equal(['in'], next_opens.map(&:tok))
+ end
+ end
+ end
+end
diff --git a/test/irb/test_option.rb b/test/irb/test_option.rb
index e334cee6dd..fec31f384f 100644
--- a/test/irb/test_option.rb
+++ b/test/irb/test_option.rb
@@ -2,7 +2,7 @@
require_relative "helper"
module TestIRB
- class TestOption < TestCase
+ class OptionTest < TestCase
def test_end_of_option
bug4117 = '[ruby-core:33574]'
bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
diff --git a/test/irb/test_raise_no_backtrace_exception.rb b/test/irb/test_raise_exception.rb
index 9565419cdd..44a5ae87e1 100644
--- a/test/irb/test_raise_no_backtrace_exception.rb
+++ b/test/irb/test_raise_exception.rb
@@ -4,13 +4,31 @@ require "tmpdir"
require_relative "helper"
module TestIRB
- class TestRaiseNoBacktraceException < TestCase
- def test_raise_exception
+ class RaiseExceptionTest < TestCase
+ def test_raise_exception_with_nil_backtrace
bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
- assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, /Exception: foo/, [])
+ assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, /#<Exception: foo>/, [])
+ raise Exception.new("foo").tap {|e| def e.backtrace; nil; end }
+IRB
+ end
+
+ def test_raise_exception_with_message_exception
+ bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
+ expected = /#<Exception: foo>\nbacktraces are hidden because bar was raised when processing them/
+ assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, expected, [])
+ e = Exception.new("foo")
+ def e.message; raise 'bar'; end
+ raise e
+IRB
+ end
+
+ def test_raise_exception_with_message_inspect_exception
+ bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
+ expected = /Uninspectable exception occurred/
+ assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, expected, [])
e = Exception.new("foo")
- puts e.inspect
- def e.backtrace; nil; end
+ def e.message; raise; end
+ def e.inspect; raise; end
raise e
IRB
end
@@ -43,7 +61,7 @@ IRB
# TruffleRuby warns when the locale does not exist
env['TRUFFLERUBYOPT'] = "#{ENV['TRUFFLERUBYOPT']} --log.level=SEVERE" if RUBY_ENGINE == 'truffleruby'
args = [env] + bundle_exec + %W[-rirb -C #{tmpdir} -W0 -e IRB.start(__FILE__) -- -f --]
- error = /`raise_euc_with_invalid_byte_sequence': あ\\xFF \(RuntimeError\)/
+ error = /raise_euc_with_invalid_byte_sequence': あ\\xFF \(RuntimeError\)/
assert_in_out_err(args, <<~IRB, error, [], encoding: "UTF-8")
require_relative 'euc'
raise_euc_with_invalid_byte_sequence
diff --git a/test/irb/test_ruby_lex.rb b/test/irb/test_ruby_lex.rb
index 1530a16d6a..4e406a8ce0 100644
--- a/test/irb/test_ruby_lex.rb
+++ b/test/irb/test_ruby_lex.rb
@@ -1,27 +1,10 @@
-$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
-require 'irb'
-require 'rubygems'
-require 'ostruct'
+# frozen_string_literal: true
+require "irb"
require_relative "helper"
module TestIRB
- class TestRubyLex < TestCase
- Row = Struct.new(:content, :current_line_spaces, :new_line_spaces, :nesting_level)
-
- class MockIO_AutoIndent
- attr_reader :calculated_indent
-
- def initialize(*params)
- @params = params
- @calculated_indent
- end
-
- def auto_indent(&block)
- @calculated_indent = block.call(*@params)
- end
- end
-
+ class RubyLexTest < TestCase
def setup
save_encodings
end
@@ -30,692 +13,77 @@ module TestIRB
restore_encodings
end
- def calculate_indenting(lines, add_new_line)
- lines = lines + [""] if add_new_line
- last_line_index = lines.length - 1
- byte_pointer = lines.last.length
-
- context = build_context
- context.auto_indent_mode = true
-
- ruby_lex = RubyLex.new(context)
- mock_io = MockIO_AutoIndent.new(lines, last_line_index, byte_pointer, add_new_line)
-
- ruby_lex.configure_io(mock_io)
- mock_io.calculated_indent
- end
-
- def assert_row_indenting(lines, row)
- actual_current_line_spaces = calculate_indenting(lines, false)
-
- error_message = <<~MSG
- Incorrect spaces calculation for line:
-
- ```
- > #{lines.last}
- ```
-
- All lines:
-
- ```
- #{lines.join("\n")}
- ```
- MSG
- assert_equal(row.current_line_spaces, actual_current_line_spaces, error_message)
-
- error_message = <<~MSG
- Incorrect spaces calculation for line after the current line:
-
- ```
- #{lines.last}
- >
- ```
-
- All lines:
-
- ```
- #{lines.join("\n")}
- ```
- MSG
- actual_next_line_spaces = calculate_indenting(lines, true)
- assert_equal(row.new_line_spaces, actual_next_line_spaces, error_message)
- end
-
- def assert_nesting_level(lines, expected, local_variables: [])
- indent, _code_block_open = check_state(lines, local_variables: local_variables)
- error_message = "Calculated the wrong number of nesting level for:\n #{lines.join("\n")}"
- assert_equal(expected, indent, error_message)
- end
-
- def assert_code_block_open(lines, expected, local_variables: [])
- _indent, code_block_open = check_state(lines, local_variables: local_variables)
- error_message = "Wrong result of code_block_open for:\n #{lines.join("\n")}"
- assert_equal(expected, code_block_open, error_message)
- end
-
- def check_state(lines, local_variables: [])
- context = build_context(local_variables)
- ruby_lex = RubyLex.new(context)
- _ltype, indent, _continue, code_block_open = ruby_lex.check_code_state(lines.join("\n"))
- [indent, code_block_open]
- end
-
- def test_auto_indent
- input_with_correct_indents = [
- Row.new(%q(def each_top_level_statement), nil, 2),
- Row.new(%q( initialize_input), nil, 2),
- Row.new(%q( catch(:TERM_INPUT) do), nil, 4),
- Row.new(%q( loop do), nil, 6),
- Row.new(%q( begin), nil, 8),
- Row.new(%q( prompt), nil, 8),
- Row.new(%q( unless l = lex), nil, 10),
- Row.new(%q( throw :TERM_INPUT if @line == ''), nil, 10),
- Row.new(%q( else), 8, 10),
- Row.new(%q( @line_no += l.count("\n")), nil, 10),
- Row.new(%q( next if l == "\n"), nil, 10),
- Row.new(%q( @line.concat l), nil, 10),
- Row.new(%q( if @code_block_open or @ltype or @continue or @indent > 0), nil, 12),
- Row.new(%q( next), nil, 12),
- Row.new(%q( end), 10, 10),
- Row.new(%q( end), 8, 8),
- Row.new(%q( if @line != "\n"), nil, 10),
- Row.new(%q( @line.force_encoding(@io.encoding)), nil, 10),
- Row.new(%q( yield @line, @exp_line_no), nil, 10),
- Row.new(%q( end), 8, 8),
- Row.new(%q( break if @io.eof?), nil, 8),
- Row.new(%q( @line = ''), nil, 8),
- Row.new(%q( @exp_line_no = @line_no), nil, 8),
- Row.new(%q( ), nil, 8),
- Row.new(%q( @indent = 0), nil, 8),
- Row.new(%q( rescue TerminateLineInput), 6, 8),
- Row.new(%q( initialize_input), nil, 8),
- Row.new(%q( prompt), nil, 8),
- Row.new(%q( end), 6, 6),
- Row.new(%q( end), 4, 4),
- Row.new(%q( end), 2, 2),
- Row.new(%q(end), 0, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- end
- end
-
- def test_braces_on_their_own_line
- input_with_correct_indents = [
- Row.new(%q(if true), nil, 2),
- Row.new(%q( [), nil, 4),
- Row.new(%q( ]), 2, 2),
- Row.new(%q(end), 0, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- end
- end
-
- def test_multiple_braces_in_a_line
- input_with_correct_indents = [
- Row.new(%q([[[), nil, 6),
- Row.new(%q( ]), 4, 4),
- Row.new(%q( ]), 2, 2),
- Row.new(%q(]), 0, 0),
- Row.new(%q([<<FOO]), 0, 0),
- Row.new(%q(hello), 0, 0),
- Row.new(%q(FOO), nil, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- end
- end
-
- def test_a_closed_brace_and_not_closed_brace_in_a_line
- input_with_correct_indents = [
- Row.new(%q(p() {), nil, 2),
- Row.new(%q(}), 0, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- end
- end
-
- def test_symbols
- input_with_correct_indents = [
- Row.new(%q(:a), nil, 0),
- Row.new(%q(:A), nil, 0),
- Row.new(%q(:+), nil, 0),
- Row.new(%q(:@@a), nil, 0),
- Row.new(%q(:@a), nil, 0),
- Row.new(%q(:$a), nil, 0),
- Row.new(%q(:def), nil, 0),
- Row.new(%q(:`), nil, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- end
- end
-
- def test_endless_range_at_end_of_line
- input_with_prompt = [
- PromptRow.new('001:0: :> ', %q(a = 3..)),
- PromptRow.new('002:0: :* ', %q()),
- ]
-
- lines = input_with_prompt.map(&:content)
- expected_prompt_list = input_with_prompt.map(&:prompt)
- assert_dynamic_prompt(lines, expected_prompt_list)
- end
-
- def test_heredoc_with_embexpr
- input_with_prompt = [
- PromptRow.new('001:0:":* ', %q(<<A+%W[#{<<B)),
- PromptRow.new('002:0:":* ', %q(#{<<C+%W[)),
- PromptRow.new('003:0:":* ', %q(a)),
- PromptRow.new('004:0:]:* ', %q(C)),
- PromptRow.new('005:0:]:* ', %q(a)),
- PromptRow.new('006:0:":* ', %q(]})),
- PromptRow.new('007:0:":* ', %q(})),
- PromptRow.new('008:0:":* ', %q(A)),
- PromptRow.new('009:0:]:* ', %q(B)),
- PromptRow.new('010:0:]:* ', %q(})),
- PromptRow.new('011:0: :> ', %q(])),
- PromptRow.new('012:0: :* ', %q()),
- ]
-
- lines = input_with_prompt.map(&:content)
- expected_prompt_list = input_with_prompt.map(&:prompt)
- assert_dynamic_prompt(lines, expected_prompt_list)
- end
-
- def test_heredoc_prompt_with_quotes
- input_with_prompt = [
- PromptRow.new("001:0:':* ", %q(<<~'A')),
- PromptRow.new("002:0:':* ", %q(#{foobar})),
- PromptRow.new("003:0: :> ", %q(A)),
- PromptRow.new("004:0:`:* ", %q(<<~`A`)),
- PromptRow.new("005:0:`:* ", %q(whoami)),
- PromptRow.new("006:0: :> ", %q(A)),
- PromptRow.new('007:0:":* ', %q(<<~"A")),
- PromptRow.new('008:0:":* ', %q(foobar)),
- PromptRow.new('009:0: :> ', %q(A)),
- ]
-
- lines = input_with_prompt.map(&:content)
- expected_prompt_list = input_with_prompt.map(&:prompt)
- assert_dynamic_prompt(lines, expected_prompt_list)
- end
-
- def test_backtick_method
- input_with_prompt = [
- PromptRow.new('001:0: :> ', %q(self.`(arg))),
- PromptRow.new('002:0: :* ', %q()),
- PromptRow.new('003:0: :> ', %q(def `(); end)),
- PromptRow.new('004:0: :* ', %q()),
- ]
-
- lines = input_with_prompt.map(&:content)
- expected_prompt_list = input_with_prompt.map(&:prompt)
- assert_dynamic_prompt(lines, expected_prompt_list)
- end
-
- def test_incomplete_coding_magic_comment
- input_with_correct_indents = [
- Row.new(%q(#coding:u), nil, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- end
- end
-
- def test_incomplete_encoding_magic_comment
- input_with_correct_indents = [
- Row.new(%q(#encoding:u), nil, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- end
- end
-
- def test_incomplete_emacs_coding_magic_comment
- input_with_correct_indents = [
- Row.new(%q(# -*- coding: u), nil, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- end
- end
-
- def test_incomplete_vim_coding_magic_comment
- input_with_correct_indents = [
- Row.new(%q(# vim:set fileencoding=u), nil, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- end
- end
-
- def test_mixed_rescue
- input_with_correct_indents = [
- Row.new(%q(def m), nil, 2),
- Row.new(%q( begin), nil, 4),
- Row.new(%q( begin), nil, 6),
- Row.new(%q( x = a rescue 4), nil, 6),
- Row.new(%q( y = [(a rescue 5)]), nil, 6),
- Row.new(%q( [x, y]), nil, 6),
- Row.new(%q( rescue => e), 4, 6),
- Row.new(%q( raise e rescue 8), nil, 6),
- Row.new(%q( end), 4, 4),
- Row.new(%q( rescue), 2, 4),
- Row.new(%q( raise rescue 11), nil, 4),
- Row.new(%q( end), 2, 2),
- Row.new(%q(rescue => e), 0, 2),
- Row.new(%q( raise e rescue 14), nil, 2),
- Row.new(%q(end), 0, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- end
- end
-
- def test_oneliner_method_definition
- input_with_correct_indents = [
- Row.new(%q(class A), nil, 2),
- Row.new(%q( def foo0), nil, 4),
- Row.new(%q( 3), nil, 4),
- Row.new(%q( end), 2, 2),
- Row.new(%q( def foo1()), nil, 4),
- Row.new(%q( 3), nil, 4),
- Row.new(%q( end), 2, 2),
- Row.new(%q( def foo2(a, b)), nil, 4),
- Row.new(%q( a + b), nil, 4),
- Row.new(%q( end), 2, 2),
- Row.new(%q( def foo3 a, b), nil, 4),
- Row.new(%q( a + b), nil, 4),
- Row.new(%q( end), 2, 2),
- Row.new(%q( def bar0() = 3), nil, 2),
- Row.new(%q( def bar1(a) = a), nil, 2),
- Row.new(%q( def bar2(a, b) = a + b), nil, 2),
- Row.new(%q( def bar3() = :s), nil, 2),
- Row.new(%q( def bar4() = Time.now), nil, 2),
- Row.new(%q(end), 0, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- end
- end
-
- def test_tlambda
- input_with_correct_indents = [
- Row.new(%q(if true), nil, 2, 1),
- Row.new(%q( -> {), nil, 4, 2),
- Row.new(%q( }), 2, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- assert_nesting_level(lines, row.nesting_level)
- end
- end
-
- def test_corresponding_syntax_to_keyword_do_in_class
- input_with_correct_indents = [
- Row.new(%q(class C), nil, 2, 1),
- Row.new(%q( while method_name do), nil, 4, 2),
- Row.new(%q( 3), nil, 4, 2),
- Row.new(%q( end), 2, 2, 1),
- Row.new(%q( foo do), nil, 4, 2),
- Row.new(%q( 3), nil, 4, 2),
- Row.new(%q( end), 2, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- assert_nesting_level(lines, row.nesting_level)
- end
- end
-
- def test_corresponding_syntax_to_keyword_do
- input_with_correct_indents = [
- Row.new(%q(while i > 0), nil, 2, 1),
- Row.new(%q( 3), nil, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- Row.new(%q(while true), nil, 2, 1),
- Row.new(%q( 3), nil, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- Row.new(%q(while ->{i > 0}.call), nil, 2, 1),
- Row.new(%q( 3), nil, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- Row.new(%q(while ->{true}.call), nil, 2, 1),
- Row.new(%q( 3), nil, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- Row.new(%q(while i > 0 do), nil, 2, 1),
- Row.new(%q( 3), nil, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- Row.new(%q(while true do), nil, 2, 1),
- Row.new(%q( 3), nil, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- Row.new(%q(while ->{i > 0}.call do), nil, 2, 1),
- Row.new(%q( 3), nil, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- Row.new(%q(while ->{true}.call do), nil, 2, 1),
- Row.new(%q( 3), nil, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- Row.new(%q(foo do), nil, 2, 1),
- Row.new(%q( 3), nil, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- Row.new(%q(foo true do), nil, 2, 1),
- Row.new(%q( 3), nil, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- Row.new(%q(foo ->{true} do), nil, 2, 1),
- Row.new(%q( 3), nil, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- Row.new(%q(foo ->{i > 0} do), nil, 2, 1),
- Row.new(%q( 3), nil, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- assert_nesting_level(lines, row.nesting_level)
- end
- end
-
- def test_corresponding_syntax_to_keyword_for
- input_with_correct_indents = [
- Row.new(%q(for i in [1]), nil, 2, 1),
- Row.new(%q( puts i), nil, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- assert_nesting_level(lines, row.nesting_level)
- end
- end
-
- def test_corresponding_syntax_to_keyword_for_with_do
- input_with_correct_indents = [
- Row.new(%q(for i in [1] do), nil, 2, 1),
- Row.new(%q( puts i), nil, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- assert_nesting_level(lines, row.nesting_level)
- end
- end
-
- def test_corresponding_syntax_to_keyword_in
- input_with_correct_indents = [
- Row.new(%q(module E), nil, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- Row.new(%q(class A), nil, 2, 1),
- Row.new(%q( in), nil, 4, 1)
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- assert_nesting_level(lines, row.nesting_level)
- end
- end
-
- def test_bracket_corresponding_to_times
- input_with_correct_indents = [
- Row.new(%q(3.times { |i|), nil, 2, 1),
- Row.new(%q( puts i), nil, 2, 1),
- Row.new(%q(}), 0, 0, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- assert_nesting_level(lines, row.nesting_level)
- end
- end
-
- def test_do_corresponding_to_times
- input_with_correct_indents = [
- Row.new(%q(3.times do |i|), nil, 2, 1),
- #Row.new(%q( puts i), nil, 2, 1),
- #Row.new(%q(end), 0, 0, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- assert_nesting_level(lines, row.nesting_level)
- end
- end
-
- def test_bracket_corresponding_to_loop
- input_with_correct_indents = [
- Row.new(%q(loop {), nil, 2, 1),
- Row.new(%q( 3), nil, 2, 1),
- Row.new(%q(}), 0, 0, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- assert_nesting_level(lines, row.nesting_level)
+ def test_interpolate_token_with_heredoc_and_unclosed_embexpr
+ code = <<~'EOC'
+ ①+<<A-②
+ #{③*<<B/④
+ #{⑤&<<C|⑥
+ EOC
+ ripper_tokens = Ripper.tokenize(code)
+ rubylex_tokens = IRB::RubyLex.ripper_lex_without_warning(code)
+ # Assert no missing part
+ assert_equal(code, rubylex_tokens.map(&:tok).join)
+ # Assert ripper tokens are not removed
+ ripper_tokens.each do |tok|
+ assert(rubylex_tokens.any? { |t| t.tok == tok && t.tok != :on_ignored_by_ripper })
end
- end
-
- def test_do_corresponding_to_loop
- input_with_correct_indents = [
- Row.new(%q(loop do), nil, 2, 1),
- Row.new(%q( 3), nil, 2, 1),
- Row.new(%q(end), 0, 0, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- assert_nesting_level(lines, row.nesting_level)
+ # Assert interpolated token position
+ rubylex_tokens.each do |t|
+ row, col = t.pos
+ assert_equal t.tok, code.lines[row - 1].byteslice(col, t.tok.bytesize)
end
end
def test_local_variables_dependent_code
- pend if RUBY_ENGINE == 'truffleruby'
lines = ["a /1#/ do", "2"]
- assert_nesting_level(lines, 1)
+ assert_indent_level(lines, 1)
assert_code_block_open(lines, true)
- assert_nesting_level(lines, 0, local_variables: ['a'])
+ assert_indent_level(lines, 0, local_variables: ['a'])
assert_code_block_open(lines, false, local_variables: ['a'])
end
- def test_heredoc_with_indent
- input_with_correct_indents = [
- Row.new(%q(<<~Q), 0, 0, 0),
- Row.new(%q({), 0, 0, 0),
- Row.new(%q( #), 2, 0, 0),
- Row.new(%q(}), 0, 0, 0)
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- assert_nesting_level(lines, row.nesting_level)
- end
+ def test_literal_ends_with_space
+ assert_code_block_open(['% a'], true)
+ assert_code_block_open(['% a '], false)
end
- def test_oneliner_def_in_multiple_lines
- input_with_correct_indents = [
- Row.new(%q(def a()=[), nil, 4, 2),
- Row.new(%q( 1,), nil, 4, 1),
- Row.new(%q(].), 0, 0, 0),
- Row.new(%q(to_s), nil, 0, 0),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- assert_nesting_level(lines, row.nesting_level)
- end
+ def test_literal_ends_with_newline
+ assert_code_block_open(['%'], true)
+ assert_code_block_open(['%', ''], false)
end
- def test_broken_heredoc
- input_with_correct_indents = [
- Row.new(%q(def foo), nil, 2, 1),
- Row.new(%q( <<~Q), 2, 2, 1),
- Row.new(%q( Qend), 2, 2, 1),
- ]
-
- lines = []
- input_with_correct_indents.each do |row|
- lines << row.content
- assert_row_indenting(lines, row)
- assert_nesting_level(lines, row.nesting_level)
- end
+ def test_should_continue
+ assert_should_continue(['a'], false)
+ assert_should_continue(['/a/'], false)
+ assert_should_continue(['a;'], false)
+ assert_should_continue(['<<A', 'A'], false)
+ assert_should_continue(['a...'], false)
+ assert_should_continue(['a\\'], true)
+ assert_should_continue(['a.'], true)
+ assert_should_continue(['a+'], true)
+ assert_should_continue(['a; #comment', '', '=begin', 'embdoc', '=end', ''], false)
+ assert_should_continue(['a+ #comment', '', '=begin', 'embdoc', '=end', ''], true)
end
- PromptRow = Struct.new(:prompt, :content)
+ def test_code_block_open_with_should_continue
+ # syntax ok
+ assert_code_block_open(['a'], false) # continue: false
+ assert_code_block_open(['a\\'], true) # continue: true
- class MockIO_DynamicPrompt
- def initialize(params, &assertion)
- @params = params
- @assertion = assertion
- end
-
- def dynamic_prompt(&block)
- result = block.call(@params)
- @assertion.call(result)
- end
- end
-
- def assert_dynamic_prompt(lines, expected_prompt_list)
- pend if RUBY_ENGINE == 'truffleruby'
- context = build_context
- ruby_lex = RubyLex.new(context)
- dynamic_prompt_executed = false
- io = MockIO_DynamicPrompt.new(lines) do |prompt_list|
- error_message = <<~EOM
- Expected dynamic prompt:
- #{expected_prompt_list.join("\n")}
-
- Actual dynamic prompt:
- #{prompt_list.join("\n")}
- EOM
- dynamic_prompt_executed = true
- assert_equal(expected_prompt_list, prompt_list, error_message)
- end
- ruby_lex.set_prompt do |ltype, indent, continue, line_no|
- '%03d:%01d:%1s:%s ' % [line_no, indent, ltype, continue ? '*' : '>']
- end
- ruby_lex.configure_io(io)
- assert dynamic_prompt_executed, "dynamic_prompt's assertions were not executed."
- end
-
- def test_dynamic_prompt
- input_with_prompt = [
- PromptRow.new('001:1: :* ', %q(def hoge)),
- PromptRow.new('002:1: :* ', %q( 3)),
- PromptRow.new('003:0: :> ', %q(end)),
- ]
-
- lines = input_with_prompt.map(&:content)
- expected_prompt_list = input_with_prompt.map(&:prompt)
- assert_dynamic_prompt(lines, expected_prompt_list)
- end
-
- def test_dynamic_prompt_with_double_newline_breaking_code
- input_with_prompt = [
- PromptRow.new('001:1: :* ', %q(if true)),
- PromptRow.new('002:1: :* ', %q(%)),
- PromptRow.new('003:1: :* ', %q(;end)),
- PromptRow.new('004:1: :* ', %q(;hello)),
- PromptRow.new('005:0: :> ', %q(end)),
- ]
-
- lines = input_with_prompt.map(&:content)
- expected_prompt_list = input_with_prompt.map(&:prompt)
- assert_dynamic_prompt(lines, expected_prompt_list)
- end
+ # recoverable syntax error code is not terminated
+ assert_code_block_open(['a+'], true)
- def test_dynamic_prompt_with_multiline_literal
- input_with_prompt = [
- PromptRow.new('001:1: :* ', %q(if true)),
- PromptRow.new('002:1:]:* ', %q( %w[)),
- PromptRow.new('003:1:]:* ', %q( a)),
- PromptRow.new('004:1: :* ', %q( ])),
- PromptRow.new('005:1: :* ', %q( b)),
- PromptRow.new('006:1:]:* ', %q( %w[)),
- PromptRow.new('007:1:]:* ', %q( c)),
- PromptRow.new('008:1: :* ', %q( ])),
- PromptRow.new('009:0: :> ', %q(end)),
- ]
+ # unrecoverable syntax error code is terminated
+ assert_code_block_open(['.; a+'], false)
- lines = input_with_prompt.map(&:content)
- expected_prompt_list = input_with_prompt.map(&:prompt)
- assert_dynamic_prompt(lines, expected_prompt_list)
- end
-
- def test_dynamic_prompt_with_blank_line
- input_with_prompt = [
- PromptRow.new('001:0:]:* ', %q(%w[)),
- PromptRow.new('002:0:]:* ', %q()),
- PromptRow.new('003:0: :> ', %q(])),
- ]
-
- lines = input_with_prompt.map(&:content)
- expected_prompt_list = input_with_prompt.map(&:prompt)
- assert_dynamic_prompt(lines, expected_prompt_list)
+ # other syntax error that failed to determine if it is recoverable or not
+ assert_code_block_open(['@; a'], false)
+ assert_code_block_open(['@; a+'], true)
+ assert_code_block_open(['@; (a'], true)
end
def test_broken_percent_literal
- tokens = RubyLex.ripper_lex_without_warning('%wwww')
+ tokens = IRB::RubyLex.ripper_lex_without_warning('%wwww')
pos_to_index = {}
tokens.each_with_index { |t, i|
assert_nil(pos_to_index[t.pos], "There is already another token in the position of #{t.inspect}.")
@@ -724,7 +92,7 @@ module TestIRB
end
def test_broken_percent_literal_in_method
- tokens = RubyLex.ripper_lex_without_warning(<<~EOC.chomp)
+ tokens = IRB::RubyLex.ripper_lex_without_warning(<<~EOC.chomp)
def foo
%wwww
end
@@ -738,7 +106,7 @@ module TestIRB
def test_unterminated_code
['do', '<<A'].each do |code|
- tokens = RubyLex.ripper_lex_without_warning(code)
+ tokens = IRB::RubyLex.ripper_lex_without_warning(code)
assert_equal(code, tokens.map(&:tok).join, "Cannot reconstruct code from tokens")
error_tokens = tokens.map(&:event).grep(/error/)
assert_empty(error_tokens, 'Error tokens must be ignored if there is corresponding non-error token')
@@ -746,15 +114,14 @@ module TestIRB
end
def test_unterminated_heredoc_string_literal
- context = build_context
['<<A;<<B', "<<A;<<B\n", "%W[\#{<<A;<<B", "%W[\#{<<A;<<B\n"].each do |code|
- tokens = RubyLex.ripper_lex_without_warning(code)
- string_literal = RubyLex.new(context).check_string_literal(tokens)
+ tokens = IRB::RubyLex.ripper_lex_without_warning(code)
+ string_literal = IRB::NestingParser.open_tokens(tokens).last
assert_equal('<<A', string_literal&.tok)
end
end
- def test_corresponding_token_depth_with_heredoc_and_embdoc
+ def test_indent_level_with_heredoc_and_embdoc
reference_code = <<~EOC.chomp
if true
hello
@@ -775,64 +142,101 @@ module TestIRB
p(
)
EOC
- context = build_context
- [reference_code, code_with_heredoc, code_with_embdoc].each do |code|
- lex = RubyLex.new(context)
- lines = code.lines
- lex.instance_variable_set('@tokens', RubyLex.ripper_lex_without_warning(code))
- assert_equal 2, lex.check_corresponding_token_depth(lines, lines.size)
- end
- end
-
- def test_find_prev_spaces_with_multiline_literal
- lex = RubyLex.new(build_context)
- reference_code = <<~EOC.chomp
- if true
- 1
- hello
- 1
- world
- end
- EOC
- code_with_percent_string = <<~EOC.chomp
- if true
- %w[
- hello
- ]
- world
- end
- EOC
- code_with_quoted_string = <<~EOC.chomp
- if true
- '
- hello
- '
- world
- end
- EOC
- context = build_context
- [reference_code, code_with_percent_string, code_with_quoted_string].each do |code|
- lex = RubyLex.new(context)
- lex.instance_variable_set('@tokens', RubyLex.ripper_lex_without_warning(code))
- prev_spaces = (1..code.lines.size).map { |index| lex.find_prev_spaces index }
- assert_equal [0, 2, 2, 2, 2, 0], prev_spaces
- end
+ expected = 1
+ assert_indent_level(reference_code.lines, expected)
+ assert_indent_level(code_with_heredoc.lines, expected)
+ assert_indent_level(code_with_embdoc.lines, expected)
+ end
+
+ def test_assignment_expression
+ ruby_lex = IRB::RubyLex.new
+
+ [
+ "foo = bar",
+ "@foo = bar",
+ "$foo = bar",
+ "@@foo = bar",
+ "::Foo = bar",
+ "a::Foo = bar",
+ "Foo = bar",
+ "foo.bar = 1",
+ "foo[1] = bar",
+ "foo += bar",
+ "foo -= bar",
+ "foo ||= bar",
+ "foo &&= bar",
+ "foo, bar = 1, 2",
+ "foo.bar=(1)",
+ "foo; foo = bar",
+ "foo; foo = bar; ;\n ;",
+ "foo\nfoo = bar",
+ ].each do |exp|
+ assert(
+ ruby_lex.assignment_expression?(exp, local_variables: []),
+ "#{exp.inspect}: should be an assignment expression"
+ )
+ end
+
+ [
+ "foo",
+ "foo.bar",
+ "foo[0]",
+ "foo = bar; foo",
+ "foo = bar\nfoo",
+ ].each do |exp|
+ refute(
+ ruby_lex.assignment_expression?(exp, local_variables: []),
+ "#{exp.inspect}: should not be an assignment expression"
+ )
+ end
+ end
+
+ def test_assignment_expression_with_local_variable
+ ruby_lex = IRB::RubyLex.new
+ code = "a /1;x=1#/"
+ refute(ruby_lex.assignment_expression?(code, local_variables: []), "#{code}: should not be an assignment expression")
+ assert(ruby_lex.assignment_expression?(code, local_variables: [:a]), "#{code}: should be an assignment expression")
+ refute(ruby_lex.assignment_expression?("", local_variables: [:a]), "empty code should not be an assignment expression")
+ end
+
+ def test_initialising_the_old_top_level_ruby_lex
+ assert_in_out_err(["--disable-gems", "-W:deprecated"], <<~RUBY, [], /warning: constant ::RubyLex is deprecated/)
+ require "irb"
+ ::RubyLex.new(nil)
+ RUBY
end
private
- def build_context(local_variables = nil)
- IRB.init_config(nil)
- workspace = IRB::WorkSpace.new(TOPLEVEL_BINDING.dup)
+ def assert_indent_level(lines, expected, local_variables: [])
+ indent_level, _continue, _code_block_open = check_state(lines, local_variables: local_variables)
+ error_message = "Calculated the wrong number of indent level for:\n #{lines.join("\n")}"
+ assert_equal(expected, indent_level, error_message)
+ end
- if local_variables
- local_variables.each do |n|
- workspace.binding.local_variable_set(n, nil)
- end
+ def assert_should_continue(lines, expected, local_variables: [])
+ _indent_level, continue, _code_block_open = check_state(lines, local_variables: local_variables)
+ error_message = "Wrong result of should_continue for:\n #{lines.join("\n")}"
+ assert_equal(expected, continue, error_message)
+ end
+
+ def assert_code_block_open(lines, expected, local_variables: [])
+ if RUBY_ENGINE == 'truffleruby'
+ omit "Remove me after https://github.com/ruby/prism/issues/2129 is addressed and adopted in TruffleRuby"
end
- IRB.conf[:VERBOSE] = false
- IRB::Context.new(nil, workspace)
+ _indent_level, _continue, code_block_open = check_state(lines, local_variables: local_variables)
+ error_message = "Wrong result of code_block_open for:\n #{lines.join("\n")}"
+ assert_equal(expected, code_block_open, error_message)
+ end
+
+ def check_state(lines, local_variables: [])
+ code = lines.map { |l| "#{l}\n" }.join # code should end with "\n"
+ ruby_lex = IRB::RubyLex.new
+ tokens, opens, terminated = ruby_lex.check_code_state(code, local_variables: local_variables)
+ indent_level = ruby_lex.calc_indent_level(opens)
+ continue = ruby_lex.should_continue?(tokens)
+ [indent_level, continue, !terminated]
end
end
end
diff --git a/test/irb/test_tracer.rb b/test/irb/test_tracer.rb
new file mode 100644
index 0000000000..540f8be131
--- /dev/null
+++ b/test/irb/test_tracer.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: false
+require 'tempfile'
+require 'irb'
+
+require_relative "helper"
+
+module TestIRB
+ class ContextWithTracerIntegrationTest < IntegrationTestCase
+ def setup
+ super
+
+ omit "Tracer gem is not available when running on TruffleRuby" if RUBY_ENGINE == "truffleruby"
+
+ @envs.merge!("NO_COLOR" => "true")
+ end
+
+ def example_ruby_file
+ <<~'RUBY'
+ class Foo
+ def self.foo
+ 100
+ end
+ end
+
+ def bar(obj)
+ obj.foo
+ end
+
+ binding.irb
+ RUBY
+ end
+
+ def test_use_tracer_enabled_when_gem_is_unavailable
+ write_rc <<~RUBY
+ # Simulate the absence of the tracer gem
+ ::Kernel.send(:alias_method, :irb_original_require, :require)
+
+ ::Kernel.define_method(:require) do |name|
+ raise LoadError, "cannot load such file -- tracer (test)" if name.match?("tracer")
+ ::Kernel.send(:irb_original_require, name)
+ end
+
+ IRB.conf[:USE_TRACER] = true
+ RUBY
+
+ write_ruby example_ruby_file
+
+ output = run_ruby_file do
+ type "bar(Foo)"
+ type "exit"
+ end
+
+ assert_include(output, "Tracer extension of IRB is enabled but tracer gem wasn't found.")
+ end
+
+ def test_use_tracer_enabled_when_gem_is_available
+ write_rc <<~RUBY
+ IRB.conf[:USE_TRACER] = true
+ RUBY
+
+ write_ruby example_ruby_file
+
+ output = run_ruby_file do
+ type "bar(Foo)"
+ type "exit"
+ end
+
+ assert_include(output, "Object#bar at")
+ assert_include(output, "Foo.foo at")
+ assert_include(output, "Foo.foo #=> 100")
+ assert_include(output, "Object#bar #=> 100")
+
+ # Test that the tracer output does not include IRB's own files
+ assert_not_include(output, "irb/workspace.rb")
+ end
+
+ def test_use_tracer_is_disabled_by_default
+ write_ruby example_ruby_file
+
+ output = run_ruby_file do
+ type "bar(Foo)"
+ type "exit"
+ end
+
+ assert_not_include(output, "#depth:")
+ assert_not_include(output, "Foo.foo")
+ end
+
+ end
+end
diff --git a/test/irb/test_type_completor.rb b/test/irb/test_type_completor.rb
new file mode 100644
index 0000000000..5ed8988b34
--- /dev/null
+++ b/test/irb/test_type_completor.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+# Run test only when Ruby >= 3.0 and repl_type_completor is available
+return unless RUBY_VERSION >= '3.0.0'
+return if RUBY_ENGINE == 'truffleruby' # needs endless method definition
+begin
+ require 'repl_type_completor'
+rescue LoadError
+ return
+end
+
+require 'irb'
+require 'tempfile'
+require_relative './helper'
+
+module TestIRB
+ class TypeCompletorTest < TestCase
+ DummyContext = Struct.new(:irb_path)
+
+ def setup
+ ReplTypeCompletor.load_rbs unless ReplTypeCompletor.rbs_loaded?
+ context = DummyContext.new('(irb)')
+ @completor = IRB::TypeCompletor.new(context)
+ end
+
+ def empty_binding
+ binding
+ end
+
+ def assert_completion(preposing, target, binding: empty_binding, include: nil, exclude: nil)
+ raise ArgumentError if include.nil? && exclude.nil?
+ candidates = @completor.completion_candidates(preposing, target, '', bind: binding)
+ assert ([*include] - candidates).empty?, "Expected #{candidates} to include #{include}" if include
+ assert (candidates & [*exclude]).empty?, "Expected #{candidates} not to include #{exclude}" if exclude
+ end
+
+ def assert_doc_namespace(preposing, target, namespace, binding: empty_binding)
+ @completor.completion_candidates(preposing, target, '', bind: binding)
+ assert_equal namespace, @completor.doc_namespace(preposing, target, '', bind: binding)
+ end
+
+ def test_type_completion
+ bind = eval('num = 1; binding')
+ assert_completion('num.times.map(&:', 'ab', binding: bind, include: 'abs')
+ assert_doc_namespace('num.chr.', 'upcase', 'String#upcase', binding: bind)
+ end
+
+ def test_inspect
+ assert_match(/\AReplTypeCompletor.*\z/, @completor.inspect)
+ end
+
+ def test_empty_completion
+ candidates = @completor.completion_candidates('(', ')', '', bind: binding)
+ assert_equal [], candidates
+ assert_doc_namespace('(', ')', nil)
+ end
+
+ def test_command_completion
+ assert_include(@completor.completion_candidates('', 'show_s', '', bind: binding), 'show_source')
+ assert_not_include(@completor.completion_candidates(';', 'show_s', '', bind: binding), 'show_source')
+ end
+ end
+
+ class TypeCompletorIntegrationTest < IntegrationTestCase
+ def test_type_completor
+ write_rc <<~RUBY
+ IRB.conf[:COMPLETOR] = :type
+ RUBY
+
+ write_ruby <<~'RUBY'
+ binding.irb
+ RUBY
+
+ output = run_ruby_file do
+ type "irb_info"
+ type "sleep 0.01 until ReplTypeCompletor.rbs_loaded?"
+ type "completor = IRB.CurrentContext.io.instance_variable_get(:@completor);"
+ type "n = 10"
+ type "puts completor.completion_candidates 'a = n.abs;', 'a.b', '', bind: binding"
+ type "puts completor.doc_namespace 'a = n.chr;', 'a.encoding', '', bind: binding"
+ type "exit!"
+ end
+ assert_match(/Completion: Autocomplete, ReplTypeCompletor/, output)
+ assert_match(/a\.bit_length/, output)
+ assert_match(/String#encoding/, output)
+ end
+ end
+end
diff --git a/test/irb/test_workspace.rb b/test/irb/test_workspace.rb
index c6b8aa1e99..199ce95a37 100644
--- a/test/irb/test_workspace.rb
+++ b/test/irb/test_workspace.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: false
require 'tempfile'
-require 'rubygems'
require 'irb'
require 'irb/workspace'
require 'irb/color'
@@ -8,7 +7,7 @@ require 'irb/color'
require_relative "helper"
module TestIRB
- class TestWorkSpace < TestCase
+ class WorkSpaceTest < TestCase
def test_code_around_binding
IRB.conf[:USE_COLORIZE] = false
Tempfile.create('irb') do |f|
@@ -91,7 +90,7 @@ module TestIRB
irb_path = "#{top_srcdir}/#{dir}/irb"
File.exist?(irb_path)
end or omit 'irb command not found'
- assert_in_out_err(bundle_exec + ['-W0', "-C#{top_srcdir}", '-e', <<~RUBY , '--', '-f', '--'], 'binding.local_variables', /\[:_\]/, [], bug17623)
+ assert_in_out_err(bundle_exec + ['-W0', "-C#{top_srcdir}", '-e', <<~RUBY, '--', '-f', '--'], 'binding.local_variables', /\[:_\]/, [], bug17623)
version = 'xyz' # typical rubygems loading file
load('#{irb_path}')
RUBY
diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb
index 1586dc6504..44e07a3a12 100644
--- a/test/irb/yamatanooroti/test_rendering.rb
+++ b/test/irb/yamatanooroti/test_rendering.rb
@@ -8,8 +8,12 @@ rescue LoadError, NameError
return
end
-class IRB::TestRendering < Yamatanooroti::TestCase
+class IRB::RenderingTest < Yamatanooroti::TestCase
def setup
+ @original_term = ENV['TERM']
+ @home_backup = ENV['HOME']
+ @xdg_config_home_backup = ENV['XDG_CONFIG_HOME']
+ ENV['TERM'] = "xterm-256color"
@pwd = Dir.pwd
suffix = '%010d' % Random.rand(0..65535)
@tmpdir = File.join(File.expand_path(Dir.tmpdir), "test_irb_#{$$}_#{suffix}")
@@ -22,12 +26,16 @@ class IRB::TestRendering < Yamatanooroti::TestCase
@irbrc_backup = ENV['IRBRC']
@irbrc_file = ENV['IRBRC'] = File.join(@tmpdir, 'temporaty_irbrc')
File.unlink(@irbrc_file) if File.exist?(@irbrc_file)
+ ENV['HOME'] = File.join(@tmpdir, 'home')
+ ENV['XDG_CONFIG_HOME'] = File.join(@tmpdir, 'xdg_config_home')
end
def teardown
FileUtils.rm_rf(@tmpdir)
ENV['IRBRC'] = @irbrc_backup
- ENV.delete('RELINE_TEST_PROMPT') if ENV['RELINE_TEST_PROMPT']
+ ENV['TERM'] = @original_term
+ ENV['HOME'] = @home_backup
+ ENV['XDG_CONFIG_HOME'] = @xdg_config_home_backup
end
def test_launch
@@ -41,9 +49,74 @@ class IRB::TestRendering < Yamatanooroti::TestCase
close
assert_screen(<<~EOC)
start IRB
- irb(main):001:0> 'Hello, World!'
+ irb(main):001> 'Hello, World!'
=> "Hello, World!"
- irb(main):002:0>
+ irb(main):002>
+ EOC
+ end
+
+ def test_configuration_file_is_skipped_with_dash_f
+ write_irbrc <<~'LINES'
+ puts '.irbrc file should be ignored when -f is used'
+ LINES
+ start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb -f}, startup_message: '')
+ write(<<~EOC)
+ 'Hello, World!'
+ EOC
+ close
+ assert_screen(<<~EOC)
+ irb(main):001> 'Hello, World!'
+ => "Hello, World!"
+ irb(main):002>
+ EOC
+ end
+
+ def test_configuration_file_is_skipped_with_dash_f_for_nested_sessions
+ write_irbrc <<~'LINES'
+ puts '.irbrc file should be ignored when -f is used'
+ LINES
+ start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb -f}, startup_message: '')
+ write(<<~EOC)
+ 'Hello, World!'
+ binding.irb
+ exit!
+ EOC
+ close
+ assert_screen(<<~EOC)
+ irb(main):001> 'Hello, World!'
+ => "Hello, World!"
+ irb(main):002> binding.irb
+ irb(main):003> exit!
+ irb(main):001>
+ EOC
+ end
+
+ def test_nomultiline
+ write_irbrc <<~'LINES'
+ puts 'start IRB'
+ LINES
+ start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb --nomultiline}, startup_message: 'start IRB')
+ write(<<~EOC)
+ if true
+ if false
+ a = "hello
+ world"
+ puts a
+ end
+ end
+ EOC
+ close
+ assert_screen(<<~EOC)
+ start IRB
+ irb(main):001> if true
+ irb(main):002* if false
+ irb(main):003* a = "hello
+ irb(main):004" world"
+ irb(main):005* puts a
+ irb(main):006* end
+ irb(main):007* end
+ => nil
+ irb(main):008>
EOC
end
@@ -64,25 +137,27 @@ class IRB::TestRendering < Yamatanooroti::TestCase
a
.a
.b
+ .itself
EOC
close
assert_screen(<<~EOC)
start IRB
- irb(main):001:1* class A
- irb(main):002:1* def inspect; '#<A>'; end
- irb(main):003:1* def a; self; end
- irb(main):004:1* def b; true; end
- irb(main):005:0> end
+ irb(main):001* class A
+ irb(main):002* def inspect; '#<A>'; end
+ irb(main):003* def a; self; end
+ irb(main):004* def b; true; end
+ irb(main):005> end
=> :b
- irb(main):006:0>
- irb(main):007:0> a = A.new
+ irb(main):006>
+ irb(main):007> a = A.new
=> #<A>
- irb(main):008:0>
- irb(main):009:0> a
- irb(main):010:0> .a
- irb(main):011:0> .b
+ irb(main):008>
+ irb(main):009> a
+ irb(main):010> .a
+ irb(main):011> .b
+ irb(main):012> .itself
=> true
- irb(main):012:0>
+ irb(main):013>
EOC
end
@@ -108,7 +183,6 @@ class IRB::TestRendering < Yamatanooroti::TestCase
(a)
&.b()
-
class A def b; self; end; def c; true; end; end;
a = A.new
a
@@ -117,44 +191,44 @@ class IRB::TestRendering < Yamatanooroti::TestCase
.c
(a)
&.b()
+ .itself
EOC
close
assert_screen(<<~EOC)
start IRB
- irb(main):001:1* class A
- irb(main):002:1* def inspect; '#<A>'; end
- irb(main):003:1* def b; self; end
- irb(main):004:1* def c; true; end
- irb(main):005:0> end
+ irb(main):001* class A
+ irb(main):002* def inspect; '#<A>'; end
+ irb(main):003* def b; self; end
+ irb(main):004* def c; true; end
+ irb(main):005> end
=> :c
- irb(main):006:0>
- irb(main):007:0> a = A.new
+ irb(main):006>
+ irb(main):007> a = A.new
=> #<A>
- irb(main):008:0>
- irb(main):009:0> a
- irb(main):010:0> .b
- irb(main):011:0> # aaa
- irb(main):012:0> .c
+ irb(main):008>
+ irb(main):009> a
+ irb(main):010> .b
+ irb(main):011> # aaa
+ irb(main):012> .c
=> true
- irb(main):013:0>
- irb(main):014:0> (a)
- irb(main):015:0> &.b()
+ irb(main):013>
+ irb(main):014> (a)
+ irb(main):015> &.b()
=> #<A>
- irb(main):016:0>
- irb(main):017:0>
- irb(main):018:0> class A def b; self; end; def c; true; end; end;
- => :c
- irb(main):019:0> a = A.new
+ irb(main):016>
+ irb(main):017> class A def b; self; end; def c; true; end; end;
+ irb(main):018> a = A.new
=> #<A>
- irb(main):020:0> a
- irb(main):021:0> .b
- irb(main):022:0> # aaa
- irb(main):023:0> .c
+ irb(main):019> a
+ irb(main):020> .b
+ irb(main):021> # aaa
+ irb(main):022> .c
=> true
- irb(main):024:0> (a)
- irb(main):025:0> &.b()
+ irb(main):023> (a)
+ irb(main):024> &.b()
+ irb(main):025> .itself
=> #<A>
- irb(main):026:0>
+ irb(main):026>
EOC
end
@@ -169,41 +243,70 @@ class IRB::TestRendering < Yamatanooroti::TestCase
close
assert_screen(<<~EOC)
start IRB
- irb(main):001:0> :`
+ irb(main):001> :`
=> :`
- irb(main):002:0>
+ irb(main):002>
EOC
end
- def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_right
- pend "Needs a dummy document to show doc"
+ def test_autocomplete_with_multiple_doc_namespaces
write_irbrc <<~'LINES'
+ puts 'start IRB'
+ LINES
+ start_terminal(3, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
+ write("{}.__id_")
+ write("\C-i")
+ sleep 0.2
+ close
+ screen = result.join("\n").sub(/\n*\z/, "\n")
+ assert_match(/start\ IRB\nirb\(main\):001> {}\.__id__\n }\.__id__(?:Press )?/, screen)
+ end
+
+ def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_right
+ rdoc_dir = File.join(@tmpdir, 'rdoc')
+ system("bundle exec rdoc -r -o #{rdoc_dir}")
+ write_irbrc <<~LINES
+ IRB.conf[:EXTRA_DOC_DIRS] = ['#{rdoc_dir}']
IRB.conf[:PROMPT][:MY_PROMPT] = {
:PROMPT_I => "%03n> ",
- :PROMPT_N => "%03n> ",
:PROMPT_S => "%03n> ",
:PROMPT_C => "%03n> "
}
IRB.conf[:PROMPT_MODE] = :MY_PROMPT
puts 'start IRB'
LINES
- start_terminal(4, 19, %W{ruby -I/home/aycabta/ruby/reline/lib -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
- write("Str\C-i")
+ start_terminal(4, 19, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
+ write("IR")
+ write("\C-i")
+ sleep 0.2
close
- assert_screen(<<~EOC)
- 001> String
- StringPress A
- StructString
- of byte
- EOC
+
+ # This is because on macOS we display different shortcut for displaying the full doc
+ # 'O' is for 'Option' and 'A' is for 'Alt'
+ if RUBY_PLATFORM =~ /darwin/
+ assert_screen(<<~EOC)
+ start IRB
+ 001> IRB
+ IRBPress Opti
+ IRB
+ EOC
+ else
+ assert_screen(<<~EOC)
+ start IRB
+ 001> IRB
+ IRBPress Alt+
+ IRB
+ EOC
+ end
end
def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_left
- pend "Needs a dummy document to show doc"
- write_irbrc <<~'LINES'
+ rdoc_dir = File.join(@tmpdir, 'rdoc')
+ system("bundle exec rdoc -r -o #{rdoc_dir}")
+ write_irbrc <<~LINES
+ IRB.conf[:EXTRA_DOC_DIRS] = ['#{rdoc_dir}']
IRB.conf[:PROMPT][:MY_PROMPT] = {
:PROMPT_I => "%03n> ",
- :PROMPT_N => "%03n> ",
:PROMPT_S => "%03n> ",
:PROMPT_C => "%03n> "
}
@@ -211,13 +314,15 @@ class IRB::TestRendering < Yamatanooroti::TestCase
puts 'start IRB'
LINES
start_terminal(4, 12, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
- write("Str\C-i")
+ write("IR")
+ write("\C-i")
+ sleep 0.2
close
assert_screen(<<~EOC)
- 001> String
- PressString
- StrinStruct
- of by
+ start IRB
+ 001> IRB
+ PressIRB
+ IRB
EOC
end
@@ -232,14 +337,176 @@ class IRB::TestRendering < Yamatanooroti::TestCase
close
assert_screen(<<~EOC)
start IRB
- irb(main):001:0> #{code}
+ irb(main):001> #{code}
=>
[0,
...
- irb(main):002:0>
+ irb(main):002>
+ EOC
+ end
+
+ def test_ctrl_c_is_handled
+ write_irbrc <<~'LINES'
+ puts 'start IRB'
+ LINES
+ start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
+ # Assignment expression code that turns into non-assignment expression after evaluation
+ write("\C-c")
+ close
+ assert_screen(<<~EOC)
+ start IRB
+ irb(main):001>
+ ^C
+ irb(main):001>
EOC
end
+ def test_show_cmds_with_pager_can_quit_with_ctrl_c
+ write_irbrc <<~'LINES'
+ puts 'start IRB'
+ LINES
+ start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
+ write("help\n")
+ write("G") # move to the end of the screen
+ write("\C-c") # quit pager
+ write("'foo' + 'bar'\n") # eval something to make sure IRB resumes
+ close
+
+ screen = result.join("\n").sub(/\n*\z/, "\n")
+ # IRB::Abort should be rescued
+ assert_not_match(/IRB::Abort/, screen)
+ # IRB should resume
+ assert_match(/foobar/, screen)
+ end
+
+ def test_pager_page_content_pages_output_when_it_does_not_fit_in_the_screen_because_of_total_length
+ write_irbrc <<~'LINES'
+ puts 'start IRB'
+ require "irb/pager"
+ LINES
+ start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
+ write("IRB::Pager.page_content('a' * (80 * 8))\n")
+ write("'foo' + 'bar'\n") # eval something to make sure IRB resumes
+ close
+
+ screen = result.join("\n").sub(/\n*\z/, "\n")
+ assert_match(/a{80}/, screen)
+ # because pager is invoked, foobar will not be evaluated
+ assert_not_match(/foobar/, screen)
+ end
+
+ def test_pager_page_content_pages_output_when_it_does_not_fit_in_the_screen_because_of_screen_height
+ write_irbrc <<~'LINES'
+ puts 'start IRB'
+ require "irb/pager"
+ LINES
+ start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
+ write("IRB::Pager.page_content('a\n' * 8)\n")
+ write("'foo' + 'bar'\n") # eval something to make sure IRB resumes
+ close
+
+ screen = result.join("\n").sub(/\n*\z/, "\n")
+ assert_match(/(a\n){8}/, screen)
+ # because pager is invoked, foobar will not be evaluated
+ assert_not_match(/foobar/, screen)
+ end
+
+ def test_pager_page_content_doesnt_page_output_when_it_fits_in_the_screen
+ write_irbrc <<~'LINES'
+ puts 'start IRB'
+ require "irb/pager"
+ LINES
+ start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
+ write("IRB::Pager.page_content('a' * (80 * 7))\n")
+ write("'foo' + 'bar'\n") # eval something to make sure IRB resumes
+ close
+
+ screen = result.join("\n").sub(/\n*\z/, "\n")
+ assert_match(/a{80}/, screen)
+ # because pager is not invoked, foobar will be evaluated
+ assert_match(/foobar/, screen)
+ end
+
+ def test_long_evaluation_output_is_paged
+ write_irbrc <<~'LINES'
+ puts 'start IRB'
+ require "irb/pager"
+ LINES
+ start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
+ write("'a' * 80 * 11\n")
+ write("'foo' + 'bar'\n") # eval something to make sure IRB resumes
+ close
+
+ screen = result.join("\n").sub(/\n*\z/, "\n")
+ assert_match(/(a{80}\n){8}/, screen)
+ # because pager is invoked, foobar will not be evaluated
+ assert_not_match(/foobar/, screen)
+ end
+
+ def test_long_evaluation_output_is_preserved_after_paging
+ write_irbrc <<~'LINES'
+ puts 'start IRB'
+ require "irb/pager"
+ LINES
+ start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
+ write("'a' * 80 * 11\n")
+ write("q") # quit pager
+ write("'foo' + 'bar'\n") # eval something to make sure IRB resumes
+ close
+
+ screen = result.join("\n").sub(/\n*\z/, "\n")
+ # confirm pager has exited
+ assert_match(/foobar/, screen)
+ # confirm output is preserved
+ assert_match(/(a{80}\n){6}/, screen)
+ end
+
+ def test_debug_integration_hints_debugger_commands
+ write_irbrc <<~'LINES'
+ IRB.conf[:USE_COLORIZE] = false
+ LINES
+ script = Tempfile.create(["debug", ".rb"])
+ script.write <<~RUBY
+ puts 'start IRB'
+ binding.irb
+ RUBY
+ script.close
+ start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: 'start IRB')
+ write("debug\n")
+ write("pp 1\n")
+ write("pp 1")
+ close
+
+ screen = result.join("\n").sub(/\n*\z/, "\n")
+ # submitted input shouldn't contain hint
+ assert_include(screen, "irb:rdbg(main):002> pp 1\n")
+ # unsubmitted input should contain hint
+ assert_include(screen, "irb:rdbg(main):003> pp 1 # debug command\n")
+ ensure
+ File.unlink(script) if script
+ end
+
+ def test_debug_integration_doesnt_hint_non_debugger_commands
+ write_irbrc <<~'LINES'
+ IRB.conf[:USE_COLORIZE] = false
+ LINES
+ script = Tempfile.create(["debug", ".rb"])
+ script.write <<~RUBY
+ puts 'start IRB'
+ binding.irb
+ RUBY
+ script.close
+ start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: 'start IRB')
+ write("debug\n")
+ write("foo")
+ close
+
+ screen = result.join("\n").sub(/\n*\z/, "\n")
+ assert_include(screen, "irb:rdbg(main):002> foo\n")
+ ensure
+ File.unlink(script) if script
+ end
+
private
def write_irbrc(content)