Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update test libraries from ruby/ruby 2023-03-24 #132

Merged
merged 1 commit into from
Mar 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 89 additions & 61 deletions test/lib/core_assertions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
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
Expand All @@ -16,16 +20,24 @@ def _assertions # :nodoc:

def message msg = nil, ending = nil, &default
proc {
msg = msg.call.chomp(".") if Proc === msg
custom_message = "#{msg}.\n" unless msg.nil? or msg.to_s.empty?
"#{custom_message}#{default.call}#{ending || "."}"
ending ||= (ending_pattern = /(?<!\.)\z/; ".")
ending_pattern ||= /(?<!#{Regexp.quote(ending)})\z/
msg = msg.call if Proc === msg
ary = [msg, (default.call if default)].compact.reject(&:empty?)
ary.map! {|str| str.to_s.sub(ending_pattern, ending) }
begin
ary.join("\n")
rescue Encoding::CompatibilityError
ary.map(&:b).join("\n")
end
}
end
end

module CoreAssertions
require_relative 'envutil'
require 'pp'
nil.pretty_inspect

def mu_pp(obj) #:nodoc:
obj.pretty_inspect.chomp
Expand Down Expand Up @@ -101,13 +113,13 @@ def syntax_check(code, fname, line)
def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt)
# TODO: consider choosing some appropriate limit for RJIT and stop skipping this once it does not randomly fail
pend 'assert_no_memory_leak may consider RJIT memory usage as leak' if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
# For previous versions which implemented MJIT
pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?

require_relative 'memory_status'
raise Test::Unit::PendedError, "unsupported platform" unless defined?(Memory::Status)

token = "\e[7;1m#{$$.to_s}:#{Time.now.strftime('%s.%L')}:#{rand(0x10000).to_s(16)}:\e[m"
token_dump = token.dump
token_re = Regexp.quote(token)
token_dump, token_re = new_test_token
envs = args.shift if Array === args and Hash === args.first
args = [
"--disable=gems",
Expand Down Expand Up @@ -167,27 +179,15 @@ def assert_nothing_raised(*args)
msg = args.pop
end
begin
line = __LINE__; yield
rescue Test::Unit::PendedError
yield
rescue Test::Unit::PendedError, *(Test::Unit::AssertionFailedError if args.empty?)
raise
rescue Exception => e
bt = e.backtrace
as = e.instance_of?(Test::Unit::AssertionFailedError)
if as
ans = /\A#{Regexp.quote(__FILE__)}:#{line}:in /o
bt.reject! {|ln| ans =~ ln}
end
if ((args.empty? && !as) ||
args.any? {|a| a.instance_of?(Module) ? e.is_a?(a) : e.class == a })
msg = message(msg) {
"Exception raised:\n<#{mu_pp(e)}>\n" +
"Backtrace:\n" +
e.backtrace.map{|frame| " #{frame}"}.join("\n")
}
raise Test::Unit::AssertionFailedError, msg.call, bt
else
raise
end
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

Expand Down Expand Up @@ -244,13 +244,17 @@ def assert_ruby_status(args, test_stdin="", message=nil, **opt)

ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV TERM")

def separated_runner(out = nil)
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 [Marshal.dump($!)].pack('m'), "assertions=#{self._assertions}"
out.puts "#{token}<error>", [Marshal.dump($!)].pack('m'), "#{token}</error>", "#{token}assertions=#{self._assertions}"
}
Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true) if defined?(Test::Unit::Runner)
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)
Expand All @@ -260,22 +264,24 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o
line ||= loc.lineno
end
capture_stdout = true
unless /mswin|mingw/ =~ RUBY_PLATFORM
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 = <<eom
# -*- coding: #{line += __LINE__; src.encoding}; -*-
BEGIN {
require "test/unit";include Test::Unit::Assertions;require #{__FILE__.dump};include Test::Unit::CoreAssertions
separated_runner #{res_c&.fileno}
separated_runner #{token_dump}, #{res_c&.fileno || 'nil'}
}
#{line -= __LINE__; src}
eom
args = args.dup
args.insert((Hash === args.first ? 1 : 0), "-w", "--disable=gems", *$:.map {|l| "-I#{l}"})
args << "--debug" if RUBY_ENGINE == 'jruby' # warning: tracing (e.g. set_trace_func) will not capture all events without --debug flag
stdout, stderr, status = EnvUtil.invoke_ruby(args, src, capture_stdout, true, **opt)
ensure
if res_c
Expand All @@ -288,9 +294,9 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o
raise if $!
abort = status.coredump? || (status.signaled? && ABORT_SIGNALS.include?(status.termsig))
assert(!abort, FailDesc[status, nil, stderr])
self._assertions += res[/^assertions=(\d+)/, 1].to_i
self._assertions += res[/^#{token_re}assertions=(\d+)/, 1].to_i
begin
res = Marshal.load(res.unpack1("m"))
res = Marshal.load(res[/^#{token_re}<error>\n\K.*\n(?=#{token_re}<\/error>$)/m].unpack1("m"))
rescue => marshal_error
ignore_stderr = nil
res = nil
Expand Down Expand Up @@ -463,7 +469,7 @@ def assert_raise_with_message(exception, expected, msg = nil, &block)
ex
end

MINI_DIR = File.join(File.dirname(File.expand_path(__FILE__)), "minitest") #:nodoc:
TEST_DIR = File.join(__dir__, "test/unit") #:nodoc:

# :call-seq:
# assert(test, [failure_message])
Expand All @@ -483,7 +489,7 @@ def assert(test, *msgs)
when nil
msgs.shift
else
bt = caller.reject { |s| s.start_with?(MINI_DIR) }
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
Expand All @@ -506,7 +512,7 @@ def assert_respond_to(obj, (meth, *priv), msg = nil)
return assert obj.respond_to?(meth, *priv), msg
end
#get rid of overcounting
if caller_locations(1, 1)[0].path.start_with?(MINI_DIR)
if caller_locations(1, 1)[0].path.start_with?(TEST_DIR)
return if obj.respond_to?(meth)
end
super(obj, meth, msg)
Expand All @@ -529,17 +535,17 @@ def assert_not_respond_to(obj, (meth, *priv), msg = nil)
return assert !obj.respond_to?(meth, *priv), msg
end
#get rid of overcounting
if caller_locations(1, 1)[0].path.start_with?(MINI_DIR)
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 and :*.
# pattern_list is an array which contains regexp, string and :*.
# :* means any sequence.
#
# pattern_list is anchored.
# Use [:*, regexp, :*] for non-anchored match.
# Use [:*, regexp/string, :*] for non-anchored match.
def assert_pattern_list(pattern_list, actual, message=nil)
rest = actual
anchored = true
Expand All @@ -548,11 +554,13 @@ def assert_pattern_list(pattern_list, actual, message=nil)
anchored = false
else
if anchored
match = /\A#{pattern}/.match(rest)
match = rest.rindex(pattern, 0)
else
match = pattern.match(rest)
match = rest.index(pattern)
end
unless match
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
Expand All @@ -569,7 +577,7 @@ def assert_pattern_list(pattern_list, actual, message=nil)
}
assert false, msg
end
rest = match.post_match
rest = post_match
anchored = true
end
}
Expand All @@ -596,14 +604,14 @@ def assert_warn(*args)

def assert_deprecated_warning(mesg = /deprecated/)
assert_warning(mesg) do
Warning[:deprecated] = true
Warning[:deprecated] = true if Warning.respond_to?(:[]=)
yield
end
end

def assert_deprecated_warn(mesg = /deprecated/)
assert_warn(mesg) do
Warning[:deprecated] = true
Warning[:deprecated] = true if Warning.respond_to?(:[]=)
yield
end
end
Expand Down Expand Up @@ -641,7 +649,7 @@ def initialize

def for(key)
@count += 1
yield
yield key
rescue Exception => e
@failures[key] = [@count, e]
end
Expand Down Expand Up @@ -695,7 +703,7 @@ def assert_join_threads(threads, message = nil)
msg = "exceptions on #{errs.length} threads:\n" +
errs.map {|t, err|
"#{t.inspect}:\n" +
RUBY_VERSION >= "2.5.0" ? err.full_message(highlight: false, order: :top) : err.message
(err.respond_to?(:full_message) ? err.full_message(highlight: false, order: :top) : err.message)
}.join("\n---\n")
if message
msg = "#{message}\n#{msg}"
Expand Down Expand Up @@ -730,21 +738,36 @@ def assert_all_assertions_foreach(msg = nil, *keys, &block)
end
alias all_assertions_foreach assert_all_assertions_foreach

def message(msg = nil, *args, &default) # :nodoc:
if Proc === msg
super(nil, *args) do
ary = [msg.call, (default.call if default)].compact.reject(&:empty?)
if 1 < ary.length
ary[0...-1] = ary[0...-1].map {|str| str.sub(/(?<!\.)\z/, '.') }
end
begin
ary.join("\n")
rescue Encoding::CompatibilityError
ary.map(&:b).join("\n")
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})
first = seq.first
*arg = pre.call(first)
times = (0..(rehearsal || (2 * first))).map do
st = Process.clock_gettime(Process::CLOCK_MONOTONIC)
yield(*arg)
t = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - st)
assert_operator 0, :<=, t
t.nonzero?
end
times.compact!
tmin, tmax = times.minmax
tmax *= tmax / tmin
tmax = 10**Math.log10(tmax).ceil

seq.each do |i|
next if i == first
t = tmax * i.fdiv(first)
*arg = pre.call(i)
message = "[#{i}]: in #{t}s"
Timeout.timeout(t, Timeout::Error, message) do
st = Process.clock_gettime(Process::CLOCK_MONOTONIC)
yield(*arg)
assert_operator (Process.clock_gettime(Process::CLOCK_MONOTONIC) - st), :<=, t, message
end
else
super
end
end

Expand All @@ -763,6 +786,11 @@ def diff(exp, act)
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
25 changes: 19 additions & 6 deletions test/lib/envutil.rb
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,12 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr =
if RUBYLIB and lib = child_env["RUBYLIB"]
child_env["RUBYLIB"] = [lib, RUBYLIB].join(File::PATH_SEPARATOR)
end
child_env['ASAN_OPTIONS'] = ENV['ASAN_OPTIONS'] if ENV['ASAN_OPTIONS']

# 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
Expand Down Expand Up @@ -292,16 +297,24 @@ def self.diagnostic_reports(signame, pid, now)
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"
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
if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log
File.unlink(name)
File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil
return log
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
Expand Down