summaryrefslogtreecommitdiff
path: root/tool/lib/core_assertions.rb
diff options
context:
space:
mode:
Diffstat (limited to 'tool/lib/core_assertions.rb')
-rw-r--r--tool/lib/core_assertions.rb192
1 files changed, 142 insertions, 50 deletions
diff --git a/tool/lib/core_assertions.rb b/tool/lib/core_assertions.rb
index 9530187db4..b456a55b34 100644
--- a/tool/lib/core_assertions.rb
+++ b/tool/lib/core_assertions.rb
@@ -1,8 +1,49 @@
# 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
@@ -16,9 +57,16 @@ module Test
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
@@ -26,6 +74,12 @@ module Test
module CoreAssertions
require_relative 'envutil'
require 'pp'
+ begin
+ require '-test-/asan'
+ rescue LoadError
+ end
+
+ nil.pretty_inspect
def mu_pp(obj) #:nodoc:
obj.pretty_inspect.chomp
@@ -99,8 +153,13 @@ module Test
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?
+ # 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?
+ # ASAN has the same problem - its shadow memory greatly increases memory usage
+ # (plus asan has better ways to detect memory leaks than this assertion)
+ pend 'assert_no_memory_leak may consider ASAN memory usage as leak' if defined?(Test::ASAN) && Test::ASAN.enabled?
require_relative 'memory_status'
raise Test::Unit::PendedError, "unsupported platform" unless defined?(Memory::Status)
@@ -165,27 +224,15 @@ module Test
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
@@ -248,7 +295,11 @@ module Test
at_exit {
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)
@@ -258,7 +309,7 @@ module Test
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
@@ -268,13 +319,14 @@ module Test
src = <<eom
# -*- coding: #{line += __LINE__; src.encoding}; -*-
BEGIN {
- require "test/unit";include Test::Unit::Assertions;include Test::Unit::CoreAssertions;require #{__FILE__.dump}
+ require "test/unit";include Test::Unit::Assertions;require #{__FILE__.dump};include Test::Unit::CoreAssertions
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
@@ -534,11 +586,11 @@ eom
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
@@ -547,11 +599,13 @@ eom
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
@@ -568,7 +622,7 @@ eom
}
assert false, msg
end
- rest = match.post_match
+ rest = post_match
anchored = true
end
}
@@ -595,14 +649,14 @@ eom
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
@@ -640,7 +694,7 @@ eom
def for(key)
@count += 1
- yield
+ yield key
rescue Exception => e
@failures[key] = [@count, e]
end
@@ -694,7 +748,7 @@ eom
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}"
@@ -729,21 +783,59 @@ eom
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
+ %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
- ary.join("\n")
- rescue Encoding::CompatibilityError
- ary.map(&:b).join("\n")
+ Process.clock_gettime(clk)
+ rescue
+ # Constants may be defined but not implemented, e.g., mingw.
+ else
+ PERFORMANCE_CLOCK = clk
end
end
- else
- super
+ 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