require 'irb'
begin
require 'yamatanooroti'
rescue LoadError, NameError
# On Ruby repository, this test suite doesn't run because Ruby repo doesn't
# have the yamatanooroti gem.
return
end
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}")
begin
Dir.mkdir(@tmpdir)
rescue Errno::EEXIST
FileUtils.rm_rf(@tmpdir)
Dir.mkdir(@tmpdir)
end
@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['TERM'] = @original_term
ENV['HOME'] = @home_backup
ENV['XDG_CONFIG_HOME'] = @xdg_config_home_backup
end
def test_launch
write_irbrc <<~'LINES'
puts 'start IRB'
LINES
start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
write(<<~EOC)
'Hello, World!'
EOC
close
assert_screen(<<~EOC)
start IRB
irb(main):001> 'Hello, World!'
=> "Hello, World!"
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
def test_multiline_paste
write_irbrc <<~'LINES'
puts 'start IRB'
LINES
start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
write(<<~EOC)
class A
def inspect; '#'; end
def a; self; end
def b; true; end
end
a = A.new
a
.a
.b
.itself
EOC
close
assert_screen(<<~EOC)
start IRB
irb(main):001* class A
irb(main):002* def inspect; '#'; end
irb(main):003* def a; self; end
irb(main):004* def b; true; end
irb(main):005> end
=> :b
irb(main):006>
irb(main):007> a = A.new
=> #
irb(main):008>
irb(main):009> a
irb(main):010> .a
irb(main):011> .b
irb(main):012> .itself
=> true
irb(main):013>
EOC
end
def test_evaluate_each_toplevel_statement_by_multiline_paste
write_irbrc <<~'LINES'
puts 'start IRB'
LINES
start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
write(<<~EOC)
class A
def inspect; '#'; end
def b; self; end
def c; true; end
end
a = A.new
a
.b
# aaa
.c
(a)
&.b()
class A def b; self; end; def c; true; end; end;
a = A.new
a
.b
# aaa
.c
(a)
&.b()
.itself
EOC
close
assert_screen(<<~EOC)
start IRB
irb(main):001* class A
irb(main):002* def inspect; '#'; end
irb(main):003* def b; self; end
irb(main):004* def c; true; end
irb(main):005> end
=> :c
irb(main):006>
irb(main):007> a = A.new
=> #
irb(main):008>
irb(main):009> a
irb(main):010> .b
irb(main):011> # aaa
irb(main):012> .c
=> true
irb(main):013>
irb(main):014> (a)
irb(main):015> &.b()
=> #
irb(main):016>
irb(main):017> class A def b; self; end; def c; true; end; end;
irb(main):018> a = A.new
=> #
irb(main):019> a
irb(main):020> .b
irb(main):021> # aaa
irb(main):022> .c
=> true
irb(main):023> (a)
irb(main):024> &.b()
irb(main):025> .itself
=> #
irb(main):026>
EOC
end
def test_symbol_with_backtick
write_irbrc <<~'LINES'
puts 'start IRB'
LINES
start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
write(<<~EOC)
:`
EOC
close
assert_screen(<<~EOC)
start IRB
irb(main):001> :`
=> :`
irb(main):002>
EOC
end
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_S => "%03n> ",
:PROMPT_C => "%03n> "
}
IRB.conf[:PROMPT_MODE] = :MY_PROMPT
puts 'start IRB'
LINES
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
# 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
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_S => "%03n> ",
:PROMPT_C => "%03n> "
}
IRB.conf[:PROMPT_MODE] = :MY_PROMPT
puts 'start IRB'
LINES
start_terminal(4, 12, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
write("IR")
write("\C-i")
sleep 0.2
close
assert_screen(<<~EOC)
start IRB
001> IRB
PressIRB
IRB
EOC
end
def test_assignment_expression_truncate
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
code = "a /'/i if false; a=1; x=1000.times.to_a#'.size"
write(code + "\n")
close
assert_screen(<<~EOC)
start IRB
irb(main):001> #{code}
=>
[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)
File.open(@irbrc_file, 'w') do |f|
f.write content
end
end
end