pax_global_header00006660000000000000000000000064146260422170014516gustar00rootroot0000000000000052 comment=ed528a1aca605cd9c848dfdd03ed55505cc8ada5 test-unit-ruby-core-1.0.6/000077500000000000000000000000001462604221700153635ustar00rootroot00000000000000test-unit-ruby-core-1.0.6/.gitignore000066400000000000000000000001111462604221700173440ustar00rootroot00000000000000/.bundle/ /.yardoc /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ test-unit-ruby-core-1.0.6/BSDL000066400000000000000000000024131462604221700160320ustar00rootroot00000000000000Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. test-unit-ruby-core-1.0.6/COPYING000066400000000000000000000045731462604221700164270ustar00rootroot00000000000000Ruby is copyrighted free software by Yukihiro Matsumoto . You can redistribute it and/or modify it under either the terms of the 2-clause BSDL (see the file BSDL), or the conditions below: 1. You may make and give away verbatim copies of the source form of the software without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may modify your copy of the software in any way, provided that you do at least ONE of the following: a. place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or by allowing the author to include your modifications in the software. b. use the modified software only within your corporation or organization. c. give non-standard binaries non-standard names, with instructions on where to get the original software distribution. d. make other distribution arrangements with the author. 3. You may distribute the software in object code or binary form, provided that you do at least ONE of the following: a. distribute the binaries and library files of the software, together with instructions (in the manual page or equivalent) on where to get the original distribution. b. accompany the distribution with the machine-readable source of the software. c. give non-standard binaries non-standard names, with instructions on where to get the original software distribution. d. make other distribution arrangements with the author. 4. You may modify and include the part of the software into any other software (possibly commercial). But some files in the distribution are not written by the author, so that they are not under these terms. For the list of those files and their copying conditions, see the file LEGAL. 5. The scripts and library files supplied as input to or produced as output from the software do not automatically fall under the copyright of the software, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this software. 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. test-unit-ruby-core-1.0.6/README.md000066400000000000000000000012751462604221700166470ustar00rootroot00000000000000# test-unit-ruby-core ## Installation Install the gem and add to the application's Gemfile by executing: $ bundle add test-unit-ruby-core If bundler is not being used to manage dependencies, install the gem by executing: $ gem install test-unit-ruby-core ## Usage Use `core_assertions` with `test/unit` as follows: ```ruby require "test/unit" require "core_assertions" Test::Unit::TestCase.include Test::Unit::CoreAssertions ``` ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/test-unit-ruby-core. ## License The gem is available as open source under the terms of the [Ruby's License](https://www.ruby-lang.org/en/about/license.txt) test-unit-ruby-core-1.0.6/Rakefile000066400000000000000000000004331462604221700170300ustar00rootroot00000000000000# frozen_string_literal: true require "bundler/gem_tasks" task :sync_tool do require 'fileutils' FileUtils.cp "../ruby/tool/lib/core_assertions.rb", "./lib" FileUtils.cp "../ruby/tool/lib/envutil.rb", "./lib" FileUtils.cp "../ruby/tool/lib/find_executable.rb", "./lib" end test-unit-ruby-core-1.0.6/lib/000077500000000000000000000000001462604221700161315ustar00rootroot00000000000000test-unit-ruby-core-1.0.6/lib/core_assertions.rb000066400000000000000000000675721462604221700217010ustar00rootroot00000000000000# frozen_string_literal: true module Test class << self ## # Filter object for backtraces. attr_accessor :backtrace_filter end class BacktraceFilter # :nodoc: def filter bt return ["No backtrace"] unless bt new_bt = [] pattern = %r[/(?:lib\/test/|core_assertions\.rb:)] unless $DEBUG then bt.each do |line| break if pattern.match?(line) new_bt << line end new_bt = bt.reject { |line| pattern.match?(line) } if new_bt.empty? new_bt = bt.dup if new_bt.empty? else new_bt = bt.dup end new_bt end end self.backtrace_filter = BacktraceFilter.new def self.filter_backtrace bt # :nodoc: backtrace_filter.filter bt end 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 |c| if Process.const_defined?(c) [c.to_sym, Process.const_get(c)].find do |clk| begin Process.clock_gettime(clk) rescue # Constants may be defined but not implemented, e.g., mingw. else PERFORMANCE_CLOCK = clk end 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 test-unit-ruby-core-1.0.6/lib/envutil.rb000066400000000000000000000263441462604221700201550ustar00rootroot00000000000000# -*- 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"] ruby elsif defined?(RbConfig.ruby) RbConfig.ruby else 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 "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 = if defined?(Warning.categories) Warning.categories.to_h {|i| [i, Warning[i]]} elsif defined?(Warning.[]) # 2.7+ %i[deprecated experimental performance].to_h do |i| [i, begin Warning[i]; rescue ArgumentError; end] end.compact end 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 !child_env.key?(name) and ENV.key?(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 under_gc_compact_stress(val = :empty, &block) raise "compaction doesn't work well on s390x. Omit the test in the caller." if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 auto_compact = GC.auto_compact GC.auto_compact = val under_gc_stress(&block) ensure GC.auto_compact = auto_compact end module_function :under_gc_compact_stress def without_gc prev_disabled = GC.disable yield ensure GC.enable unless prev_disabled end module_function :without_gc 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!(/(?= 2.3" spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage spec.files = %w[ lib/core_assertions.rb lib/envutil.rb lib/find_executable.rb ] spec.require_paths = ["lib"] spec.add_runtime_dependency("test-unit") end