1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
|
# frozen_string_literal: true
module TracePointChecker
STATE = {
count: 0,
running: false,
}
module ZombieTraceHunter
def tracepoint_capture_stat_get
TracePoint.stat.map{|k, (activated, deleted)|
deleted = 0 unless @tracepoint_captured_singlethread
[k, activated, deleted]
}
end
def before_setup
@tracepoint_captured_singlethread = (Thread.list.size == 1)
@tracepoint_captured_stat = tracepoint_capture_stat_get()
super
end
def after_teardown
super
# detect zombie traces.
assert_equal(
@tracepoint_captured_stat,
tracepoint_capture_stat_get(),
"The number of active/deleted trace events was changed"
)
# puts "TracePoint - deleted: #{deleted}" if deleted > 0
TracePointChecker.check if STATE[:running]
end
end
MAIN_THREAD = Thread.current
TRACES = []
def self.prefix event
case event
when :call, :return
:n
when :c_call, :c_return
:c
when :b_call, :b_return
:b
end
end
def self.clear_call_stack
Thread.current[:call_stack] = []
end
def self.call_stack
stack = Thread.current[:call_stack]
stack = clear_call_stack unless stack
stack
end
def self.verbose_out label, method
puts label => call_stack, :count => STATE[:count], :method => method
end
def self.method_label tp
"#{prefix(tp.event)}##{tp.method_id}"
end
def self.start verbose: false, stop_at_failure: false
call_events = %i(a_call)
return_events = %i(a_return)
clear_call_stack
STATE[:running] = true
TRACES << TracePoint.new(*call_events){|tp|
next if Thread.current != MAIN_THREAD
method = method_label(tp)
call_stack.push method
STATE[:count] += 1
verbose_out :push, method if verbose
}
TRACES << TracePoint.new(*return_events){|tp|
next if Thread.current != MAIN_THREAD
STATE[:count] += 1
method = "#{prefix(tp.event)}##{tp.method_id}"
verbose_out :pop1, method if verbose
stored_method = call_stack.pop
next if stored_method.nil?
verbose_out :pop2, method if verbose
if stored_method != method
stop if stop_at_failure
RubyVM::SDR() if defined? RubyVM::SDR()
call_stack.clear
raise "#{stored_method} is expected, but #{method} (count: #{STATE[:count]})"
end
}
TRACES.each{|trace| trace.enable}
end
def self.stop
STATE[:running] = true
TRACES.each{|trace| trace.disable}
TRACES.clear
end
def self.check
TRACES.each{|trace|
raise "trace #{trace} should not be deactivated" unless trace.enabled?
}
end
end if defined?(TracePoint.stat)
class ::Test::Unit::TestCase
include TracePointChecker::ZombieTraceHunter
end if defined?(TracePointChecker)
# TracePointChecker.start verbose: false
|