diff options
Diffstat (limited to 'test/irb/helper.rb')
-rw-r--r-- | test/irb/helper.rb | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/test/irb/helper.rb b/test/irb/helper.rb new file mode 100644 index 0000000000..acaf6277f3 --- /dev/null +++ b/test/irb/helper.rb @@ -0,0 +1,221 @@ +require "test/unit" +require "pathname" +require "rubygems" + +begin + require_relative "../lib/helper" + require_relative "../lib/envutil" +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 = []) + @line_no = 0 + @list = list + end + + def gets + @list[@line_no]&.tap {@line_no += 1} + end + + def eof? + @line_no >= @list.size + end + + def encoding + Encoding.default_external + end + + def reset + @line_no = 0 + end + end + + def ruby_core? + !Pathname(__dir__).join("../../", "irb.gemspec").exist? + end + + def save_encodings + @default_encoding = [Encoding.default_external, Encoding.default_internal] + @stdio_encodings = [STDIN, STDOUT, STDERR].map {|io| [io.external_encoding, io.internal_encoding] } + end + + def restore_encodings + EnvUtil.suppress_warning do + Encoding.default_external, Encoding.default_internal = *@default_encoding + [STDIN, STDOUT, STDERR].zip(@stdio_encodings) do |io, encs| + io.set_encoding(*encs) + end + end + end + + def without_rdoc(&block) + ::Kernel.send(:alias_method, :irb_original_require, :require) + + ::Kernel.define_method(:require) do |name| + raise LoadError, "cannot load such file -- rdoc (test)" if name.match?("rdoc") || name.match?(/^rdoc\/.*/) + ::Kernel.send(:irb_original_require, name) + end + + yield + ensure + EnvUtil.suppress_warning { + ::Kernel.send(:alias_method, :require, :irb_original_require) + ::Kernel.undef_method :irb_original_require + } + 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 |