Skip to content

Commit

Permalink
Bump up the latest version of CoreAssertions
Browse files Browse the repository at this point in the history
hsbt committed Sep 11, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 8c9486e commit 46af6e9
Showing 3 changed files with 426 additions and 46 deletions.
7 changes: 7 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -74,6 +74,13 @@ else
task :compile => 'lib/racc/parser-text.rb'
end

task :sync_tool do
require 'fileutils'
FileUtils.cp "../ruby/tool/lib/core_assertions.rb", "./test/lib"
FileUtils.cp "../ruby/tool/lib/envutil.rb", "./test/lib"
FileUtils.cp "../ruby/tool/lib/find_executable.rb", "./test/lib"
end

task :build => "lib/racc/parser-text.rb"

task :test => :compile
416 changes: 382 additions & 34 deletions test/lib/core_assertions.rb
Original file line number Diff line number Diff line change
@@ -24,23 +24,8 @@ def message msg = nil, ending = nil, &default
end

module CoreAssertions
if defined?(MiniTest)
require_relative '../../envutil'
# for ruby core testing
include MiniTest::Assertions

# Compatibility hack for assert_raise
Test::Unit::AssertionFailedError = MiniTest::Assertion
else
module MiniTest
class Assertion < Exception; end
class Skip < Assertion; end
end

require 'pp'
require_relative 'envutil'
include Test::Unit::Assertions
end
require_relative 'envutil'
require 'pp'

def mu_pp(obj) #:nodoc:
obj.pretty_inspect.chomp
@@ -94,6 +79,161 @@ def assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [],
end
end

if defined?(RubyVM::InstructionSequence)
def syntax_check(code, fname, line)
code = code.dup.force_encoding(Encoding::UTF_8)
RubyVM::InstructionSequence.compile(code, fname, fname, line)
:ok
ensure
raise if SyntaxError === $!
end
else
def syntax_check(code, fname, line)
code = code.b
code.sub!(/\A(?:\xef\xbb\xbf)?(\s*\#.*$)*(\n)?/n) {
"#$&#{"\n" if $1 && !$2}BEGIN{throw tag, :ok}\n"
}
code = code.force_encoding(Encoding::UTF_8)
catch {|tag| eval(code, binding, fname, line - 1)}
end
end

def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt)
# TODO: consider choosing some appropriate limit for MJIT and stop skipping this once it does not randomly fail
pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::JIT) && RubyVM::JIT.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)
envs = args.shift if Array === args and Hash === args.first
args = [
"--disable=gems",
"-r", File.expand_path("../memory_status", __FILE__),
*args,
"-v", "-",
]
if defined? Memory::NO_MEMORY_LEAK_ENVS then
envs ||= {}
newenvs = envs.merge(Memory::NO_MEMORY_LEAK_ENVS) { |_, _, _| break }
envs = newenvs if newenvs
end
args.unshift(envs) if envs
cmd = [
'END {STDERR.puts '"#{token_dump}"'"FINAL=#{Memory::Status.new}"}',
prepare,
'STDERR.puts('"#{token_dump}"'"START=#{$initial_status = Memory::Status.new}")',
'$initial_size = $initial_status.size',
code,
'GC.start',
].join("\n")
_, err, status = EnvUtil.invoke_ruby(args, cmd, true, true, **opt)
before = err.sub!(/^#{token_re}START=(\{.*\})\n/, '') && Memory::Status.parse($1)
after = err.sub!(/^#{token_re}FINAL=(\{.*\})\n/, '') && Memory::Status.parse($1)
assert(status.success?, FailDesc[status, message, err])
([:size, (rss && :rss)] & after.members).each do |n|
b = before[n]
a = after[n]
next unless a > 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
line = __LINE__; yield
rescue Test::Unit::PendedError
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
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]
@@ -104,35 +244,58 @@ 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)
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}"
}
Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true) if defined?(Test::Unit::Runner)
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/ =~ RUBY_PLATFORM
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
src = <<eom
# -*- coding: #{line += __LINE__; src.encoding}; -*-
require "test/unit";include Test::Unit::Assertions;require #{(__dir__ + "/core_assertions").dump};include Test::Unit::CoreAssertions
END {
puts [Marshal.dump($!)].pack('m'), "assertions=\#{self._assertions}"
}
BEGIN {
require "test/unit";include Test::Unit::Assertions;require #{__FILE__.dump};include Test::Unit::CoreAssertions
separated_runner #{res_c&.fileno}
}
#{line -= __LINE__; src}
class Test::Unit::Runner
@@stop_auto_run = true
end
eom
args = args.dup
args.insert((Hash === args.first ? 1 : 0), "-w", "--disable=gems", *$:.map {|l| "-I#{l}"})
stdout, stderr, status = EnvUtil.invoke_ruby(args, src, true, true, **opt)
stdout, stderr, status = EnvUtil.invoke_ruby(args, src, capture_stdout, true, **opt)
ensure
if res_c
res_c.close
res = res_p.read
res_p.close
else
res = stdout
end
raise if $!
abort = status.coredump? || (status.signaled? && ABORT_SIGNALS.include?(status.termsig))
assert(!abort, FailDesc[status, nil, stderr])
self._assertions += stdout[/^assertions=(\d+)/, 1].to_i
self._assertions += res[/^assertions=(\d+)/, 1].to_i
begin
res = Marshal.load(stdout.unpack("m")[0])
res = Marshal.load(res.unpack1("m"))
rescue => marshal_error
ignore_stderr = nil
res = nil
end
if res
if res and !(SystemExit === res)
if bt = res.backtrace
bt.each do |l|
l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"}
@@ -141,7 +304,7 @@ class Test::Unit::Runner
else
res.set_backtrace(caller)
end
raise res unless SystemExit === res
raise res
end

# really is it succeed?
@@ -153,6 +316,27 @@ class Test::Unit::Runner
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 )
#
@@ -203,8 +387,8 @@ def assert_raise(*exp, &b)

begin
yield
rescue MiniTest::Skip => e
return e if exp.include? MiniTest::Skip
rescue Test::Unit::PendedError => e
return e if exp.include? Test::Unit::PendedError
raise e
rescue Exception => e
expected = exp.any? { |ex|
@@ -216,7 +400,7 @@ def assert_raise(*exp, &b)
}

assert expected, proc {
exception_details(e, message(msg) {"#{mu_pp(exp)} exception expected, not"}.call)
flunk(message(msg) {"#{mu_pp(exp)} exception expected, not #{mu_pp(e)}"})
}

return e
@@ -279,6 +463,121 @@ def assert_raise_with_message(exception, expected, msg = nil, &block)
ex
end

MINI_DIR = File.join(File.dirname(File.expand_path(__FILE__)), "minitest") #: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?(MINI_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?(MINI_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?(MINI_DIR)
return unless obj.respond_to?(meth)
end
refute_respond_to(obj, meth, msg)
end

# pattern_list is an array which contains regexp and :*.
# :* means any sequence.
#
# pattern_list is anchored.
# Use [:*, regexp, :*] 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 = /\A#{pattern}/.match(rest)
else
match = pattern.match(rest)
end
unless match
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 = match.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) {
@@ -295,7 +594,22 @@ def assert_warn(*args)
assert_warning(*args) {$VERBOSE = false; yield}
end

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

def assert_deprecated_warn(mesg = /deprecated/)
assert_warn(mesg) do
Warning[:deprecated] = true
yield
end
end

class << (AssertFile = Struct.new(:failure_message).new)
include Assertions
include CoreAssertions
def assert_file_predicate(predicate, *args)
if /\Anot_/ =~ predicate
@@ -381,13 +695,23 @@ def assert_join_threads(threads, message = nil)
msg = "exceptions on #{errs.length} threads:\n" +
errs.map {|t, err|
"#{t.inspect}:\n" +
err.full_message(highlight: false, order: :top)
RUBY_VERSION >= "2.5.0" ? err.full_message(highlight: false, order: :top) : err.message
}.join("\n---\n")
if message
msg = "#{message}\n#{msg}"
end
raise MiniTest::Assertion, msg
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)
@@ -398,6 +722,14 @@ def assert_all_assertions(msg = nil)
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

def message(msg = nil, *args, &default) # :nodoc:
if Proc === msg
super(nil, *args) do
@@ -415,6 +747,22 @@ def message(msg = nil, *args, &default) # :nodoc:
super
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
end
end
end
49 changes: 37 additions & 12 deletions test/lib/envutil.rb
Original file line number Diff line number Diff line change
@@ -47,12 +47,13 @@ def rubybin
class << self
attr_accessor :timeout_scale
attr_reader :original_internal_encoding, :original_external_encoding,
:original_verbose
: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

@@ -86,7 +87,20 @@ def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1)
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
@@ -100,6 +114,8 @@ def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1)
begin
Timeout.timeout(reprieve) {Process.wait(pid)}
rescue Timeout::Error
else
break
end
end
end
@@ -109,7 +125,7 @@ def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1)

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,
stdout_filter: nil, stderr_filter: nil, ios: nil,
signal: :TERM,
rubybin: EnvUtil.rubybin, precommand: nil,
**opt)
@@ -125,6 +141,8 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr =
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}
@@ -134,11 +152,14 @@ 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']
args = [args] if args.kind_of?(String)
pid = spawn(child_env, *precommand, rubybin, *args, **opt)
pid = spawn(child_env, *precommand, rubybin, *args, opt)
in_c.close
out_c.close if capture_stdout
err_c.close if capture_stderr && capture_stderr != :merge_to_stdout
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
@@ -180,11 +201,6 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr =
end
module_function :invoke_ruby

alias rubyexec invoke_ruby
class << self
alias rubyexec invoke_ruby
end

def verbose_warning
class << (stderr = "".dup)
alias write concat
@@ -197,6 +213,7 @@ def flush; end
ensure
stderr, $stderr = $stderr, stderr
$VERBOSE = EnvUtil.original_verbose
EnvUtil.original_warning&.each {|i, v| Warning[i] = v}
end
module_function :verbose_warning

@@ -242,15 +259,23 @@ def with_default_internal(enc)

def labeled_module(name, &block)
Module.new do
singleton_class.class_eval {define_method(:to_s) {name}; alias inspect to_s}
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}
singleton_class.class_eval {
define_method(:to_s) {name}
alias inspect to_s
alias name to_s
}
class_eval(&block) if block
end
end

0 comments on commit 46af6e9

Please sign in to comment.