diff --git a/Gemfile b/Gemfile
index b5b5fb5664..df26e09887 100644
--- a/Gemfile
+++ b/Gemfile
@@ -7,6 +7,7 @@ group :development do
gem 'racc', '> 1.4.10'
gem 'kpeg', '>= 1.3.3'
gem 'test-unit'
+ gem 'test-unit-ruby-core'
gem 'rubocop', '>= 1.31.0'
gem 'gettext'
end
diff --git a/rakelib/sync_tool.rake b/rakelib/sync_tool.rake
deleted file mode 100644
index 8ea8cb0ad2..0000000000
--- a/rakelib/sync_tool.rake
+++ /dev/null
@@ -1,17 +0,0 @@
-task :sync_tool, [:from] do |t, from: nil|
- from ||= (File.identical?(__dir__, "rakelib") ? "../ruby/tool" : File.dirname(__dir__))
-
- require 'fileutils'
-
- {
- "rakelib/sync_tool.rake" => "rakelib",
- "lib/core_assertions.rb" => "test/lib",
- "lib/envutil.rb" => "test/lib",
- "lib/find_executable.rb" => "test/lib",
- "lib/helper.rb" => "test/lib",
- }.each do |src, dest|
- FileUtils.mkpath(dest)
- FileUtils.cp "#{from}/#{src}", dest
- rescue Errno::ENOENT
- end
-end
diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb
deleted file mode 100644
index 4887d944c5..0000000000
--- a/test/lib/core_assertions.rb
+++ /dev/null
@@ -1,817 +0,0 @@
-# frozen_string_literal: true
-
-module Test
- module Unit
- module Assertions
- def assert_raises(*exp, &b)
- raise NoMethodError, "use assert_raise", caller
- end
-
- def _assertions= n # :nodoc:
- @_assertions = n
- end
-
- def _assertions # :nodoc:
- @_assertions ||= 0
- end
-
- ##
- # Returns a proc that will output +msg+ along with the default message.
-
- def message msg = nil, ending = nil, &default
- proc {
- ending ||= (ending_pattern = /(? 0 and b > 0
- assert_operator(a.fdiv(b), :<, limit, message(message) {"#{n}: #{b} => #{a}"})
- end
- rescue LoadError
- pend
- end
-
- # :call-seq:
- # assert_nothing_raised( *args, &block )
- #
- #If any exceptions are given as arguments, the assertion will
- #fail if one of those exceptions are raised. Otherwise, the test fails
- #if any exceptions are raised.
- #
- #The final argument may be a failure message.
- #
- # assert_nothing_raised RuntimeError do
- # raise Exception #Assertion passes, Exception is not a RuntimeError
- # end
- #
- # assert_nothing_raised do
- # raise Exception #Assertion fails
- # end
- def assert_nothing_raised(*args)
- self._assertions += 1
- if Module === args.last
- msg = nil
- else
- msg = args.pop
- end
- begin
- yield
- rescue Test::Unit::PendedError, *(Test::Unit::AssertionFailedError if args.empty?)
- raise
- rescue *(args.empty? ? Exception : args) => e
- msg = message(msg) {
- "Exception raised:\n<#{mu_pp(e)}>\n""Backtrace:\n" <<
- Test.filter_backtrace(e.backtrace).map{|frame| " #{frame}"}.join("\n")
- }
- raise Test::Unit::AssertionFailedError, msg.call, e.backtrace
- end
- end
-
- def prepare_syntax_check(code, fname = nil, mesg = nil, verbose: nil)
- fname ||= caller_locations(2, 1)[0]
- mesg ||= fname.to_s
- verbose, $VERBOSE = $VERBOSE, verbose
- case
- when Array === fname
- fname, line = *fname
- when defined?(fname.path) && defined?(fname.lineno)
- fname, line = fname.path, fname.lineno
- else
- line = 1
- end
- yield(code, fname, line, message(mesg) {
- if code.end_with?("\n")
- "```\n#{code}```\n"
- else
- "```\n#{code}\n```\n""no-newline"
- end
- })
- ensure
- $VERBOSE = verbose
- end
-
- def assert_valid_syntax(code, *args, **opt)
- prepare_syntax_check(code, *args, **opt) do |src, fname, line, mesg|
- yield if defined?(yield)
- assert_nothing_raised(SyntaxError, mesg) do
- assert_equal(:ok, syntax_check(src, fname, line), mesg)
- end
- end
- end
-
- def assert_normal_exit(testsrc, message = '', child_env: nil, **opt)
- assert_valid_syntax(testsrc, caller_locations(1, 1)[0])
- if child_env
- child_env = [child_env]
- else
- child_env = []
- end
- out, _, status = EnvUtil.invoke_ruby(child_env + %W'-W0', testsrc, true, :merge_to_stdout, **opt)
- assert !status.signaled?, FailDesc[status, message, out]
- end
-
- def assert_ruby_status(args, test_stdin="", message=nil, **opt)
- out, _, status = EnvUtil.invoke_ruby(args, test_stdin, true, :merge_to_stdout, **opt)
- desc = FailDesc[status, message, out]
- assert(!status.signaled?, desc)
- message ||= "ruby exit status is not success:"
- assert(status.success?, desc)
- end
-
- ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV TERM")
-
- def separated_runner(token, out = nil)
- include(*Test::Unit::TestCase.ancestors.select {|c| !c.is_a?(Class) })
- out = out ? IO.new(out, 'w') : STDOUT
- at_exit {
- out.puts "#{token}", [Marshal.dump($!)].pack('m'), "#{token}", "#{token}assertions=#{self._assertions}"
- }
- if defined?(Test::Unit::Runner)
- Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true)
- elsif defined?(Test::Unit::AutoRunner)
- Test::Unit::AutoRunner.need_auto_run = false
- end
- end
-
- def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **opt)
- unless file and line
- loc, = caller_locations(1,1)
- file ||= loc.path
- line ||= loc.lineno
- end
- capture_stdout = true
- unless /mswin|mingw/ =~ RbConfig::CONFIG['host_os']
- capture_stdout = false
- opt[:out] = Test::Unit::Runner.output if defined?(Test::Unit::Runner)
- res_p, res_c = IO.pipe
- opt[:ios] = [res_c]
- end
- token_dump, token_re = new_test_token
- src = <\n\K.*\n(?=#{token_re}<\/error>$)/m].unpack1("m"))
- rescue => marshal_error
- ignore_stderr = nil
- res = nil
- end
- if res and !(SystemExit === res)
- if bt = res.backtrace
- bt.each do |l|
- l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"}
- end
- bt.concat(caller)
- else
- res.set_backtrace(caller)
- end
- raise res
- end
-
- # really is it succeed?
- unless ignore_stderr
- # the body of assert_separately must not output anything to detect error
- assert(stderr.empty?, FailDesc[status, "assert_separately failed with error message", stderr])
- end
- assert(status.success?, FailDesc[status, "assert_separately failed", stderr])
- raise marshal_error if marshal_error
- end
-
- # Run Ractor-related test without influencing the main test suite
- def assert_ractor(src, args: [], require: nil, require_relative: nil, file: nil, line: nil, ignore_stderr: nil, **opt)
- return unless defined?(Ractor)
-
- require = "require #{require.inspect}" if require
- if require_relative
- dir = File.dirname(caller_locations[0,1][0].absolute_path)
- full_path = File.expand_path(require_relative, dir)
- require = "#{require}; require #{full_path.inspect}"
- end
-
- assert_separately(args, file, line, <<~RUBY, ignore_stderr: ignore_stderr, **opt)
- #{require}
- previous_verbose = $VERBOSE
- $VERBOSE = nil
- Ractor.new {} # trigger initial warning
- $VERBOSE = previous_verbose
- #{src}
- RUBY
- end
-
- # :call-seq:
- # assert_throw( tag, failure_message = nil, &block )
- #
- #Fails unless the given block throws +tag+, returns the caught
- #value otherwise.
- #
- #An optional failure message may be provided as the final argument.
- #
- # tag = Object.new
- # assert_throw(tag, "#{tag} was not thrown!") do
- # throw tag
- # end
- def assert_throw(tag, msg = nil)
- ret = catch(tag) do
- begin
- yield(tag)
- rescue UncaughtThrowError => e
- thrown = e.tag
- end
- msg = message(msg) {
- "Expected #{mu_pp(tag)} to have been thrown"\
- "#{%Q[, not #{thrown}] if thrown}"
- }
- assert(false, msg)
- end
- assert(true)
- ret
- end
-
- # :call-seq:
- # assert_raise( *args, &block )
- #
- #Tests if the given block raises an exception. Acceptable exception
- #types may be given as optional arguments. If the last argument is a
- #String, it will be used as the error message.
- #
- # assert_raise do #Fails, no Exceptions are raised
- # end
- #
- # assert_raise NameError do
- # puts x #Raises NameError, so assertion succeeds
- # end
- def assert_raise(*exp, &b)
- case exp.last
- when String, Proc
- msg = exp.pop
- end
-
- begin
- yield
- rescue Test::Unit::PendedError => e
- return e if exp.include? Test::Unit::PendedError
- raise e
- rescue Exception => e
- expected = exp.any? { |ex|
- if ex.instance_of? Module then
- e.kind_of? ex
- else
- e.instance_of? ex
- end
- }
-
- assert expected, proc {
- flunk(message(msg) {"#{mu_pp(exp)} exception expected, not #{mu_pp(e)}"})
- }
-
- return e
- ensure
- unless e
- exp = exp.first if exp.size == 1
-
- flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"})
- end
- end
- end
-
- # :call-seq:
- # assert_raise_with_message(exception, expected, msg = nil, &block)
- #
- #Tests if the given block raises an exception with the expected
- #message.
- #
- # assert_raise_with_message(RuntimeError, "foo") do
- # nil #Fails, no Exceptions are raised
- # end
- #
- # assert_raise_with_message(RuntimeError, "foo") do
- # raise ArgumentError, "foo" #Fails, different Exception is raised
- # end
- #
- # assert_raise_with_message(RuntimeError, "foo") do
- # raise "bar" #Fails, RuntimeError is raised but the message differs
- # end
- #
- # assert_raise_with_message(RuntimeError, "foo") do
- # raise "foo" #Raises RuntimeError with the message, so assertion succeeds
- # end
- def assert_raise_with_message(exception, expected, msg = nil, &block)
- case expected
- when String
- assert = :assert_equal
- when Regexp
- assert = :assert_match
- else
- raise TypeError, "Expected #{expected.inspect} to be a kind of String or Regexp, not #{expected.class}"
- end
-
- ex = m = nil
- EnvUtil.with_default_internal(expected.encoding) do
- ex = assert_raise(exception, msg || proc {"Exception(#{exception}) with message matches to #{expected.inspect}"}) do
- yield
- end
- m = ex.message
- end
- msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"}
-
- if assert == :assert_equal
- assert_equal(expected, m, msg)
- else
- msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp m}" }
- assert expected =~ m, msg
- block.binding.eval("proc{|_|$~=_}").call($~)
- end
- ex
- end
-
- TEST_DIR = File.join(__dir__, "test/unit") #:nodoc:
-
- # :call-seq:
- # assert(test, [failure_message])
- #
- #Tests if +test+ is true.
- #
- #+msg+ may be a String or a Proc. If +msg+ is a String, it will be used
- #as the failure message. Otherwise, the result of calling +msg+ will be
- #used as the message if the assertion fails.
- #
- #If no +msg+ is given, a default message will be used.
- #
- # assert(false, "This was expected to be true")
- def assert(test, *msgs)
- case msg = msgs.first
- when String, Proc
- when nil
- msgs.shift
- else
- bt = caller.reject { |s| s.start_with?(TEST_DIR) }
- raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt
- end unless msgs.empty?
- super
- end
-
- # :call-seq:
- # assert_respond_to( object, method, failure_message = nil )
- #
- #Tests if the given Object responds to +method+.
- #
- #An optional failure message may be provided as the final argument.
- #
- # assert_respond_to("hello", :reverse) #Succeeds
- # assert_respond_to("hello", :does_not_exist) #Fails
- def assert_respond_to(obj, (meth, *priv), msg = nil)
- unless priv.empty?
- msg = message(msg) {
- "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv[0]}"
- }
- return assert obj.respond_to?(meth, *priv), msg
- end
- #get rid of overcounting
- if caller_locations(1, 1)[0].path.start_with?(TEST_DIR)
- return if obj.respond_to?(meth)
- end
- super(obj, meth, msg)
- end
-
- # :call-seq:
- # assert_not_respond_to( object, method, failure_message = nil )
- #
- #Tests if the given Object does not respond to +method+.
- #
- #An optional failure message may be provided as the final argument.
- #
- # assert_not_respond_to("hello", :reverse) #Fails
- # assert_not_respond_to("hello", :does_not_exist) #Succeeds
- def assert_not_respond_to(obj, (meth, *priv), msg = nil)
- unless priv.empty?
- msg = message(msg) {
- "Expected #{mu_pp(obj)} (#{obj.class}) to not respond to ##{meth}#{" privately" if priv[0]}"
- }
- return assert !obj.respond_to?(meth, *priv), msg
- end
- #get rid of overcounting
- if caller_locations(1, 1)[0].path.start_with?(TEST_DIR)
- return unless obj.respond_to?(meth)
- end
- refute_respond_to(obj, meth, msg)
- end
-
- # pattern_list is an array which contains regexp, string and :*.
- # :* means any sequence.
- #
- # pattern_list is anchored.
- # Use [:*, regexp/string, :*] for non-anchored match.
- def assert_pattern_list(pattern_list, actual, message=nil)
- rest = actual
- anchored = true
- pattern_list.each_with_index {|pattern, i|
- if pattern == :*
- anchored = false
- else
- if anchored
- match = rest.rindex(pattern, 0)
- else
- match = rest.index(pattern)
- end
- if match
- post_match = $~ ? $~.post_match : rest[match+pattern.size..-1]
- else
- msg = message(msg) {
- expect_msg = "Expected #{mu_pp pattern}\n"
- if /\n[^\n]/ =~ rest
- actual_mesg = +"to match\n"
- rest.scan(/.*\n+/) {
- actual_mesg << ' ' << $&.inspect << "+\n"
- }
- actual_mesg.sub!(/\+\n\z/, '')
- else
- actual_mesg = "to match " + mu_pp(rest)
- end
- actual_mesg << "\nafter #{i} patterns with #{actual.length - rest.length} characters"
- expect_msg + actual_mesg
- }
- assert false, msg
- end
- rest = post_match
- anchored = true
- end
- }
- if anchored
- assert_equal("", rest)
- end
- end
-
- def assert_warning(pat, msg = nil)
- result = nil
- stderr = EnvUtil.with_default_internal(pat.encoding) {
- EnvUtil.verbose_warning {
- result = yield
- }
- }
- msg = message(msg) {diff pat, stderr}
- assert(pat === stderr, msg)
- result
- end
-
- def assert_warn(*args)
- assert_warning(*args) {$VERBOSE = false; yield}
- end
-
- def assert_deprecated_warning(mesg = /deprecated/)
- assert_warning(mesg) do
- Warning[:deprecated] = true if Warning.respond_to?(:[]=)
- yield
- end
- end
-
- def assert_deprecated_warn(mesg = /deprecated/)
- assert_warn(mesg) do
- Warning[:deprecated] = true if Warning.respond_to?(:[]=)
- yield
- end
- end
-
- class << (AssertFile = Struct.new(:failure_message).new)
- include Assertions
- include CoreAssertions
- def assert_file_predicate(predicate, *args)
- if /\Anot_/ =~ predicate
- predicate = $'
- neg = " not"
- end
- result = File.__send__(predicate, *args)
- result = !result if neg
- mesg = "Expected file ".dup << args.shift.inspect
- mesg << "#{neg} to be #{predicate}"
- mesg << mu_pp(args).sub(/\A\[(.*)\]\z/m, '(\1)') unless args.empty?
- mesg << " #{failure_message}" if failure_message
- assert(result, mesg)
- end
- alias method_missing assert_file_predicate
-
- def for(message)
- clone.tap {|a| a.failure_message = message}
- end
- end
-
- class AllFailures
- attr_reader :failures
-
- def initialize
- @count = 0
- @failures = {}
- end
-
- def for(key)
- @count += 1
- yield key
- rescue Exception => e
- @failures[key] = [@count, e]
- end
-
- def foreach(*keys)
- keys.each do |key|
- @count += 1
- begin
- yield key
- rescue Exception => e
- @failures[key] = [@count, e]
- end
- end
- end
-
- def message
- i = 0
- total = @count.to_s
- fmt = "%#{total.size}d"
- @failures.map {|k, (n, v)|
- v = v.message
- "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.b.gsub(/^/, ' | ').force_encoding(v.encoding)}"
- }.join("\n")
- end
-
- def pass?
- @failures.empty?
- end
- end
-
- # threads should respond to shift method.
- # Array can be used.
- def assert_join_threads(threads, message = nil)
- errs = []
- values = []
- while th = threads.shift
- begin
- values << th.value
- rescue Exception
- errs << [th, $!]
- th = nil
- end
- end
- values
- ensure
- if th&.alive?
- th.raise(Timeout::Error.new)
- th.join rescue errs << [th, $!]
- end
- if !errs.empty?
- msg = "exceptions on #{errs.length} threads:\n" +
- errs.map {|t, err|
- "#{t.inspect}:\n" +
- (err.respond_to?(:full_message) ? err.full_message(highlight: false, order: :top) : err.message)
- }.join("\n---\n")
- if message
- msg = "#{message}\n#{msg}"
- end
- raise Test::Unit::AssertionFailedError, msg
- end
- end
-
- def assert_all?(obj, m = nil, &blk)
- failed = []
- obj.each do |*a, &b|
- unless blk.call(*a, &b)
- failed << (a.size > 1 ? a : a[0])
- end
- end
- assert(failed.empty?, message(m) {failed.pretty_inspect})
- end
-
- def assert_all_assertions(msg = nil)
- all = AllFailures.new
- yield all
- ensure
- assert(all.pass?, message(msg) {all.message.chomp(".")})
- end
- alias all_assertions assert_all_assertions
-
- def assert_all_assertions_foreach(msg = nil, *keys, &block)
- all = AllFailures.new
- all.foreach(*keys, &block)
- ensure
- assert(all.pass?, message(msg) {all.message.chomp(".")})
- end
- alias all_assertions_foreach assert_all_assertions_foreach
-
- %w[
- CLOCK_THREAD_CPUTIME_ID CLOCK_PROCESS_CPUTIME_ID
- CLOCK_MONOTONIC
- ].find do |clk|
- if Process.const_defined?(clk)
- [clk.to_sym, Process.const_get(clk)].find do |clk|
- Process.clock_gettime(clk)
- rescue
- # Constants may be defined but not implemented, e.g., mingw.
- else
- PERFORMANCE_CLOCK = clk
- end
- end
- end
-
- # Expect +seq+ to respond to +first+ and +each+ methods, e.g.,
- # Array, Range, Enumerator::ArithmeticSequence and other
- # Enumerable-s, and each elements should be size factors.
- #
- # :yield: each elements of +seq+.
- def assert_linear_performance(seq, rehearsal: nil, pre: ->(n) {n})
- pend "No PERFORMANCE_CLOCK found" unless defined?(PERFORMANCE_CLOCK)
-
- # Timeout testing generally doesn't work when RJIT compilation happens.
- rjit_enabled = defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
- measure = proc do |arg, message|
- st = Process.clock_gettime(PERFORMANCE_CLOCK)
- yield(*arg)
- t = (Process.clock_gettime(PERFORMANCE_CLOCK) - st)
- assert_operator 0, :<=, t, message unless rjit_enabled
- t
- end
-
- first = seq.first
- *arg = pre.call(first)
- times = (0..(rehearsal || (2 * first))).map do
- measure[arg, "rehearsal"].nonzero?
- end
- times.compact!
- tmin, tmax = times.minmax
- tbase = 10 ** Math.log10(tmax * ([(tmax / tmin), 2].max ** 2)).ceil
- info = "(tmin: #{tmin}, tmax: #{tmax}, tbase: #{tbase})"
-
- seq.each do |i|
- next if i == first
- t = tbase * i.fdiv(first)
- *arg = pre.call(i)
- message = "[#{i}]: in #{t}s #{info}"
- Timeout.timeout(t, Timeout::Error, message) do
- measure[arg, message]
- end
- end
- end
-
- def diff(exp, act)
- require 'pp'
- q = PP.new(+"")
- q.guard_inspect_key do
- q.group(2, "expected: ") do
- q.pp exp
- end
- q.text q.newline
- q.group(2, "actual: ") do
- q.pp act
- end
- q.flush
- end
- q.output
- end
-
- def new_test_token
- token = "\e[7;1m#{$$.to_s}:#{Time.now.strftime('%s.%L')}:#{rand(0x10000).to_s(16)}:\e[m"
- return token.dump, Regexp.quote(token)
- end
- end
- end
-end
diff --git a/test/lib/envutil.rb b/test/lib/envutil.rb
deleted file mode 100644
index 728ca7059b..0000000000
--- a/test/lib/envutil.rb
+++ /dev/null
@@ -1,380 +0,0 @@
-# -*- coding: us-ascii -*-
-# frozen_string_literal: true
-require "open3"
-require "timeout"
-require_relative "find_executable"
-begin
- require 'rbconfig'
-rescue LoadError
-end
-begin
- require "rbconfig/sizeof"
-rescue LoadError
-end
-
-module EnvUtil
- def rubybin
- if ruby = ENV["RUBY"]
- return ruby
- end
- ruby = "ruby"
- exeext = RbConfig::CONFIG["EXEEXT"]
- rubyexe = (ruby + exeext if exeext and !exeext.empty?)
- 3.times do
- if File.exist? ruby and File.executable? ruby and !File.directory? ruby
- return File.expand_path(ruby)
- end
- if rubyexe and File.exist? rubyexe and File.executable? rubyexe
- return File.expand_path(rubyexe)
- end
- ruby = File.join("..", ruby)
- end
- if defined?(RbConfig.ruby)
- RbConfig.ruby
- else
- "ruby"
- end
- end
- module_function :rubybin
-
- LANG_ENVS = %w"LANG LC_ALL LC_CTYPE"
-
- DEFAULT_SIGNALS = Signal.list
- DEFAULT_SIGNALS.delete("TERM") if /mswin|mingw/ =~ RUBY_PLATFORM
-
- RUBYLIB = ENV["RUBYLIB"]
-
- class << self
- attr_accessor :timeout_scale
- attr_reader :original_internal_encoding, :original_external_encoding,
- :original_verbose, :original_warning
-
- def capture_global_values
- @original_internal_encoding = Encoding.default_internal
- @original_external_encoding = Encoding.default_external
- @original_verbose = $VERBOSE
- @original_warning = defined?(Warning.[]) ? %i[deprecated experimental].to_h {|i| [i, Warning[i]]} : nil
- end
- end
-
- def apply_timeout_scale(t)
- if scale = EnvUtil.timeout_scale
- t * scale
- else
- t
- end
- end
- module_function :apply_timeout_scale
-
- def timeout(sec, klass = nil, message = nil, &blk)
- return yield(sec) if sec == nil or sec.zero?
- sec = apply_timeout_scale(sec)
- Timeout.timeout(sec, klass, message, &blk)
- end
- module_function :timeout
-
- def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1)
- reprieve = apply_timeout_scale(reprieve) if reprieve
-
- signals = Array(signal).select do |sig|
- DEFAULT_SIGNALS[sig.to_s] or
- DEFAULT_SIGNALS[Signal.signame(sig)] rescue false
- end
- signals |= [:ABRT, :KILL]
- case pgroup
- when 0, true
- pgroup = -pid
- when nil, false
- pgroup = pid
- end
-
- lldb = true if /darwin/ =~ RUBY_PLATFORM
-
- while signal = signals.shift
-
- if lldb and [:ABRT, :KILL].include?(signal)
- lldb = false
- # sudo -n: --non-interactive
- # lldb -p: attach
- # -o: run command
- system(*%W[sudo -n lldb -p #{pid} --batch -o bt\ all -o call\ rb_vmdebug_stack_dump_all_threads() -o quit])
- true
- end
-
- begin
- Process.kill signal, pgroup
- rescue Errno::EINVAL
- next
- rescue Errno::ESRCH
- break
- end
- if signals.empty? or !reprieve
- Process.wait(pid)
- else
- begin
- Timeout.timeout(reprieve) {Process.wait(pid)}
- rescue Timeout::Error
- else
- break
- end
- end
- end
- $?
- end
- module_function :terminate
-
- def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false,
- encoding: nil, timeout: 10, reprieve: 1, timeout_error: Timeout::Error,
- stdout_filter: nil, stderr_filter: nil, ios: nil,
- signal: :TERM,
- rubybin: EnvUtil.rubybin, precommand: nil,
- **opt)
- timeout = apply_timeout_scale(timeout)
-
- in_c, in_p = IO.pipe
- out_p, out_c = IO.pipe if capture_stdout
- err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout
- opt[:in] = in_c
- opt[:out] = out_c if capture_stdout
- opt[:err] = capture_stderr == :merge_to_stdout ? out_c : err_c if capture_stderr
- if encoding
- out_p.set_encoding(encoding) if out_p
- err_p.set_encoding(encoding) if err_p
- end
- ios.each {|i, o = i|opt[i] = o} if ios
-
- c = "C"
- child_env = {}
- LANG_ENVS.each {|lc| child_env[lc] = c}
- if Array === args and Hash === args.first
- child_env.update(args.shift)
- end
- if RUBYLIB and lib = child_env["RUBYLIB"]
- child_env["RUBYLIB"] = [lib, RUBYLIB].join(File::PATH_SEPARATOR)
- end
-
- # remain env
- %w(ASAN_OPTIONS RUBY_ON_BUG).each{|name|
- child_env[name] = ENV[name] if ENV[name]
- }
-
- args = [args] if args.kind_of?(String)
- pid = spawn(child_env, *precommand, rubybin, *args, opt)
- in_c.close
- out_c&.close
- out_c = nil
- err_c&.close
- err_c = nil
- if block_given?
- return yield in_p, out_p, err_p, pid
- else
- th_stdout = Thread.new { out_p.read } if capture_stdout
- th_stderr = Thread.new { err_p.read } if capture_stderr && capture_stderr != :merge_to_stdout
- in_p.write stdin_data.to_str unless stdin_data.empty?
- in_p.close
- if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout))
- timeout_error = nil
- else
- status = terminate(pid, signal, opt[:pgroup], reprieve)
- terminated = Time.now
- end
- stdout = th_stdout.value if capture_stdout
- stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout
- out_p.close if capture_stdout
- err_p.close if capture_stderr && capture_stderr != :merge_to_stdout
- status ||= Process.wait2(pid)[1]
- stdout = stdout_filter.call(stdout) if stdout_filter
- stderr = stderr_filter.call(stderr) if stderr_filter
- if timeout_error
- bt = caller_locations
- msg = "execution of #{bt.shift.label} expired timeout (#{timeout} sec)"
- msg = failure_description(status, terminated, msg, [stdout, stderr].join("\n"))
- raise timeout_error, msg, bt.map(&:to_s)
- end
- return stdout, stderr, status
- end
- ensure
- [th_stdout, th_stderr].each do |th|
- th.kill if th
- end
- [in_c, in_p, out_c, out_p, err_c, err_p].each do |io|
- io&.close
- end
- [th_stdout, th_stderr].each do |th|
- th.join if th
- end
- end
- module_function :invoke_ruby
-
- def verbose_warning
- class << (stderr = "".dup)
- alias write concat
- def flush; end
- end
- stderr, $stderr = $stderr, stderr
- $VERBOSE = true
- yield stderr
- return $stderr
- ensure
- stderr, $stderr = $stderr, stderr
- $VERBOSE = EnvUtil.original_verbose
- EnvUtil.original_warning&.each {|i, v| Warning[i] = v}
- end
- module_function :verbose_warning
-
- def default_warning
- $VERBOSE = false
- yield
- ensure
- $VERBOSE = EnvUtil.original_verbose
- end
- module_function :default_warning
-
- def suppress_warning
- $VERBOSE = nil
- yield
- ensure
- $VERBOSE = EnvUtil.original_verbose
- end
- module_function :suppress_warning
-
- def under_gc_stress(stress = true)
- stress, GC.stress = GC.stress, stress
- yield
- ensure
- GC.stress = stress
- end
- module_function :under_gc_stress
-
- def with_default_external(enc)
- suppress_warning { Encoding.default_external = enc }
- yield
- ensure
- suppress_warning { Encoding.default_external = EnvUtil.original_external_encoding }
- end
- module_function :with_default_external
-
- def with_default_internal(enc)
- suppress_warning { Encoding.default_internal = enc }
- yield
- ensure
- suppress_warning { Encoding.default_internal = EnvUtil.original_internal_encoding }
- end
- module_function :with_default_internal
-
- def labeled_module(name, &block)
- Module.new do
- singleton_class.class_eval {
- define_method(:to_s) {name}
- alias inspect to_s
- alias name to_s
- }
- class_eval(&block) if block
- end
- end
- module_function :labeled_module
-
- def labeled_class(name, superclass = Object, &block)
- Class.new(superclass) do
- singleton_class.class_eval {
- define_method(:to_s) {name}
- alias inspect to_s
- alias name to_s
- }
- class_eval(&block) if block
- end
- end
- module_function :labeled_class
-
- if /darwin/ =~ RUBY_PLATFORM
- DIAGNOSTIC_REPORTS_PATH = File.expand_path("~/Library/Logs/DiagnosticReports")
- DIAGNOSTIC_REPORTS_TIMEFORMAT = '%Y-%m-%d-%H%M%S'
- @ruby_install_name = RbConfig::CONFIG['RUBY_INSTALL_NAME']
-
- def self.diagnostic_reports(signame, pid, now)
- return unless %w[ABRT QUIT SEGV ILL TRAP].include?(signame)
- cmd = File.basename(rubybin)
- cmd = @ruby_install_name if "ruby-runner#{RbConfig::CONFIG["EXEEXT"]}" == cmd
- path = DIAGNOSTIC_REPORTS_PATH
- timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT
- pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.{crash,ips}"
- first = true
- 30.times do
- first ? (first = false) : sleep(0.1)
- Dir.glob(pat) do |name|
- log = File.read(name) rescue next
- case name
- when /\.crash\z/
- if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log
- File.unlink(name)
- File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil
- return log
- end
- when /\.ips\z/
- if /^ *"pid" *: *#{pid},/ =~ log
- File.unlink(name)
- return log
- end
- end
- end
- end
- nil
- end
- else
- def self.diagnostic_reports(signame, pid, now)
- end
- end
-
- def self.failure_description(status, now, message = "", out = "")
- pid = status.pid
- if signo = status.termsig
- signame = Signal.signame(signo)
- sigdesc = "signal #{signo}"
- end
- log = diagnostic_reports(signame, pid, now)
- if signame
- sigdesc = "SIG#{signame} (#{sigdesc})"
- end
- if status.coredump?
- sigdesc = "#{sigdesc} (core dumped)"
- end
- full_message = ''.dup
- message = message.call if Proc === message
- if message and !message.empty?
- full_message << message << "\n"
- end
- full_message << "pid #{pid}"
- full_message << " exit #{status.exitstatus}" if status.exited?
- full_message << " killed by #{sigdesc}" if sigdesc
- if out and !out.empty?
- full_message << "\n" << out.b.gsub(/^/, '| ')
- full_message.sub!(/(?