Skip to content

Commit

Permalink
Update the latest CoreAssetion for assert_linear_performance
Browse files Browse the repository at this point in the history
  • Loading branch information
hsbt committed Sep 10, 2024
1 parent df1ca41 commit 89a2a9d
Showing 1 changed file with 106 additions and 24 deletions.
130 changes: 106 additions & 24 deletions tool/lib/core_assertions.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -33,6 +74,11 @@ def message msg = nil, ending = nil, &default
module CoreAssertions
require_relative 'envutil'
require 'pp'
begin
require '-test-/asan'
rescue LoadError
end

nil.pretty_inspect

def mu_pp(obj) #:nodoc:
Expand Down Expand Up @@ -107,8 +153,13 @@ def syntax_check(code, fname, line)
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
# 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)
Expand Down Expand Up @@ -244,7 +295,11 @@ def separated_runner(token, out = nil)
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)
Expand All @@ -254,7 +309,7 @@ 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
Expand Down Expand Up @@ -531,11 +586,11 @@ def assert_not_respond_to(obj, (meth, *priv), msg = nil)
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 @@ -544,11 +599,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 @@ -565,7 +622,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 @@ -592,14 +649,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 @@ -691,7 +748,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 @@ -726,35 +783,60 @@ def assert_all_assertions_foreach(msg = nil, *keys, &block)
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
st = Process.clock_gettime(Process::CLOCK_MONOTONIC)
yield(*arg)
t = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - st)
assert_operator 0, :<=, t
t.nonzero?
measure[arg, "rehearsal"].nonzero?
end
times.compact!
tmin, tmax = times.minmax
tmax *= tmax / tmin
tmax = 10**Math.log10(tmax).ceil

# safe_factor * tmax * rehearsal_time_variance_factor(equals to 1 when variance is small)
tbase = 10 * tmax * [(tmax / tmin) ** 2 / 4, 1].max
info = "(tmin: #{tmin}, tmax: #{tmax}, tbase: #{tbase})"

seq.each do |i|
next if i == first
t = tmax * i.fdiv(first)
t = tbase * i.fdiv(first)
*arg = pre.call(i)
message = "[#{i}]: in #{t}s"
message = "[#{i}]: in #{t}s #{info}"
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
measure[arg, message]
end
end
end
Expand Down

0 comments on commit 89a2a9d

Please sign in to comment.