diff options
Diffstat (limited to 'test/ruby/test_settracefunc.rb')
-rw-r--r-- | test/ruby/test_settracefunc.rb | 429 |
1 files changed, 367 insertions, 62 deletions
diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb index b43f9c114d..1251f8879f 100644 --- a/test/ruby/test_settracefunc.rb +++ b/test/ruby/test_settracefunc.rb @@ -1,5 +1,6 @@ # frozen_string_literal: false require 'test/unit' +EnvUtil.suppress_warning {require 'continuation'} class TestSetTraceFunc < Test::Unit::TestCase def setup @@ -50,6 +51,49 @@ class TestSetTraceFunc < Test::Unit::TestCase assert_equal([], events) end + def test_c_return_no_binding + binding = :none + TracePoint.new(:c_return){|tp| + binding = tp.binding + }.enable{ + 1.object_id + } + assert_nil(binding) + end + + def test_c_call_no_binding + binding = :none + TracePoint.new(:c_call){|tp| + binding = tp.binding + }.enable{ + 1.object_id + } + assert_nil(binding) + end + + def test_c_call_removed_method + # [Bug #19305] + klass = Class.new do + attr_writer :bar + alias_method :set_bar, :bar= + remove_method :bar= + end + + obj = klass.new + method_id = nil + parameters = nil + + TracePoint.new(:c_call) { |tp| + method_id = tp.method_id + parameters = tp.parameters + }.enable { + obj.set_bar(1) + } + + assert_equal(:bar=, method_id) + assert_equal([[:req]], parameters) + end + def test_call events = [] name = "#{self.class}\##{__method__}" @@ -315,18 +359,18 @@ class TestSetTraceFunc < Test::Unit::TestCase def test_thread_trace events = {:set => [], :add => []} + name = "#{self.class}\##{__method__}" prc = Proc.new { |event, file, lineno, mid, binding, klass| - events[:set] << [event, lineno, mid, klass, :set] + events[:set] << [event, lineno, mid, klass, :set] if file == name } prc = prc # suppress warning prc2 = Proc.new { |event, file, lineno, mid, binding, klass| - events[:add] << [event, lineno, mid, klass, :add] + events[:add] << [event, lineno, mid, klass, :add] if file == name } prc2 = prc2 # suppress warning th = Thread.new do th = Thread.current - name = "#{self.class}\##{__method__}" eval <<-EOF.gsub(/^.*?: /, ""), nil, name 1: th.set_trace_func(prc) 2: th.add_trace_func(prc2) @@ -459,9 +503,9 @@ class TestSetTraceFunc < Test::Unit::TestCase begin eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy' 1: trace = TracePoint.trace(*trace_events){|tp| next if !target_thread? - 2: events << [tp.event, tp.lineno, tp.path, _defined_class.(tp), tp.method_id, tp.self, tp.binding.eval("_local_var"), _get_data.(tp)] if tp.path == 'xyzzy' + 2: events << [tp.event, tp.lineno, tp.path, _defined_class.(tp), tp.method_id, tp.self, tp.binding&.eval("_local_var"), _get_data.(tp)] if tp.path == 'xyzzy' 3: } - 4: 1.times{|;_local_var| _local_var = :inner + 4: [1].reverse_each{|;_local_var| _local_var = :inner 5: tap{} 6: } 7: class XYZZY @@ -488,29 +532,29 @@ class TestSetTraceFunc < Test::Unit::TestCase answer_events = [ # [:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing], - [:c_call, 4, 'xyzzy', Integer, :times, 1, :outer, :nothing], + [:c_call, 4, 'xyzzy', Array, :reverse_each, [1], nil, :nothing], [:line, 4, 'xyzzy', self.class, method, self, nil, :nothing], [:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing], - [:c_return, 4, "xyzzy", Integer, :times, 1, :outer, 1], + [:c_return, 4, "xyzzy", Array, :reverse_each, [1], nil, [1]], [:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing], - [:c_call, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, :outer, :nothing], - [:c_return, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, :outer, nil], - [:c_call, 7, "xyzzy", Class, :inherited, Object, :outer, :nothing], - [:c_return, 7, "xyzzy", Class, :inherited, Object, :outer, nil], + [:c_call, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, :nothing], + [:c_return, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, nil], + [:c_call, 7, "xyzzy", Class, :inherited, Object, nil, :nothing], + [:c_return, 7, "xyzzy", Class, :inherited, Object, nil, nil], [:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing], [:line, 8, "xyzzy", nil, nil, xyzzy.class, nil, :nothing], [:line, 9, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], - [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing], - [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil], + [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, :nothing], + [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, nil], [:line, 13, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], - [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing], - [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil], + [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, nil, :nothing], + [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, nil, nil], [:end, 17, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], [:line, 18, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], - [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, :outer, :nothing], - [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, :nothing], - [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, nil], - [:c_return,18, "xyzzy", Class, :new, xyzzy.class, :outer, xyzzy], + [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, nil, :nothing], + [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, nil, :nothing], + [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, nil, nil], + [:c_return,18, "xyzzy", Class, :new, xyzzy.class, nil, xyzzy], [:line, 19, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], [:call, 9, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing], [:line, 10, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing], @@ -521,17 +565,17 @@ class TestSetTraceFunc < Test::Unit::TestCase [:return, 16, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, xyzzy], [:return, 12, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, xyzzy], [:line, 20, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], - [:c_call, 20, "xyzzy", Kernel, :raise, self, :outer, :nothing], - [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, :outer, :nothing], - [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, :outer, :nothing], - [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, :outer, raised_exc], - [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, :outer, raised_exc], - [:c_return,20, "xyzzy", Kernel, :raise, self, :outer, nil], - [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, :outer, :nothing], - [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, :outer, nil], + [:c_call, 20, "xyzzy", Kernel, :raise, self, nil, :nothing], + [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, nil, :nothing], + [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, nil, :nothing], + [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, nil, raised_exc], + [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, nil, raised_exc], + [:c_return,20, "xyzzy", Kernel, :raise, self, nil, nil], + [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, nil, :nothing], + [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, nil, nil], [:raise, 20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc], - [:c_call, 20, "xyzzy", Module, :===, RuntimeError,:outer, :nothing], - [:c_return,20, "xyzzy", Module, :===, RuntimeError,:outer, true], + [:c_call, 20, "xyzzy", Module, :===, RuntimeError, nil, :nothing], + [:c_return,20, "xyzzy", Module, :===, RuntimeError, nil, true], [:line, 21, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], ] @@ -582,6 +626,19 @@ PREP CODE end + def test_tracepoint_bmethod_memory_leak + assert_no_memory_leak([], '', "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #20194]", rss: true) + obj = Object.new + obj.define_singleton_method(:foo) {} + bmethod = obj.method(:foo) + tp = TracePoint.new(:return) {} + begin; + 1_000_000.times do + tp.enable(target: bmethod) {} + end + end; + end + def trace_by_set_trace_func events = [] trace = nil @@ -596,7 +653,7 @@ CODE 1: set_trace_func(lambda{|event, file, line, id, binding, klass| 2: events << [event, line, file, klass, id, binding&.eval('self'), binding&.eval("_local_var")] if file == 'xyzzy' 3: }) - 4: 1.times{|;_local_var| _local_var = :inner + 4: [1].map{|;_local_var| _local_var = :inner 5: tap{} 6: } 7: class XYZZY @@ -912,6 +969,55 @@ CODE assert_equal(expected*2, events) end + def test_tracepoint_struct + c = Struct.new(:x) do + alias y x + alias y= x= + end + obj = c.new + + ar_meth = obj.method(:x) + aw_meth = obj.method(:x=) + aar_meth = obj.method(:y) + aaw_meth = obj.method(:y=) + events = [] + trace = TracePoint.new(:c_call, :c_return){|tp| + next if !target_thread? + next if tp.path != __FILE__ + next if tp.method_id == :call + case tp.event + when :c_call + assert_raise(RuntimeError) {tp.return_value} + events << [tp.event, tp.method_id, tp.callee_id] + when :c_return + events << [tp.event, tp.method_id, tp.callee_id, tp.return_value] + end + } + test_proc = proc do + obj.x = 1 + obj.x + obj.y = 2 + obj.y + aw_meth.call(1) + ar_meth.call + aaw_meth.call(2) + aar_meth.call + end + test_proc.call # populate call caches + trace.enable(&test_proc) + expected = [ + [:c_call, :x=, :x=], + [:c_return, :x=, :x=, 1], + [:c_call, :x, :x], + [:c_return, :x, :x, 1], + [:c_call, :x=, :y=], + [:c_return, :x=, :y=, 2], + [:c_call, :x, :y], + [:c_return, :x, :y, 2], + ] + assert_equal(expected*2, events) + end + class XYZZYException < Exception; end def method_test_tracepoint_raised_exception err raise err @@ -951,7 +1057,7 @@ CODE /return/ =~ tp.event ? tp.return_value : nil ] }.enable{ - 1.times{ + [1].map{ 3 } method_for_test_tracepoint_block{ @@ -961,10 +1067,10 @@ CODE # pp events # expected_events = [[:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil], - [:c_call, :times, Integer, Integer, nil], + [:c_call, :map, Array, Array, nil], [:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil], [:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 3], - [:c_return, :times, Integer, Integer, 1], + [:c_return, :map, Array, Array, [3]], [:call, :method_for_test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil], [:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil], [:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4], @@ -1018,9 +1124,9 @@ CODE when :line assert_match(/ in /, str) when :call, :c_call - assert_match(/call \`/, str) # #<TracePoint:c_call `inherited' ../trunk/test.rb:11> + assert_match(/call \'/, str) # #<TracePoint:c_call 'inherited' ../trunk/test.rb:11> when :return, :c_return - assert_match(/return \`/, str) # #<TracePoint:return `m' ../trunk/test.rb:3> + assert_match(/return \'/, str) # #<TracePoint:return 'm' ../trunk/test.rb:3> when /thread/ assert_match(/\#<Thread:/, str) # #<TracePoint:thread_end of #<Thread:0x87076c0>> else @@ -1153,15 +1259,17 @@ CODE end } assert_normal_exit src % %q{obj.zip({}) {}}, bug7774 - assert_normal_exit src % %q{ - require 'continuation' - begin - c = nil - obj.sort_by {|x| callcc {|c2| c ||= c2 }; x } - c.call - rescue RuntimeError - end - }, bug7774 + if respond_to?(:callcc) + assert_normal_exit src % %q{ + require 'continuation' + begin + c = nil + obj.sort_by {|x| callcc {|c2| c ||= c2 }; x } + c.call + rescue RuntimeError + end + }, bug7774 + end # TracePoint tp_b = nil @@ -1267,7 +1375,7 @@ CODE next if !target_thread? events << tp.event }.enable{ - 1.times{ + [1].map{ 3 } method_for_test_tracepoint_block{ @@ -1289,7 +1397,7 @@ CODE next if !target_thread? events << tp.event }.enable{ - 1.times{ + [1].map{ 3 } method_for_test_tracepoint_block{ @@ -1648,7 +1756,7 @@ CODE Bug10724.new } - assert_equal([:call, :return], evs) + assert_equal([:call, :call, :return, :return], evs) end require 'fiber' @@ -1880,7 +1988,11 @@ CODE def tp_return_value mid ary = [] - TracePoint.new(:return, :b_return){|tp| next if !target_thread?; ary << [tp.event, tp.method_id, tp.return_value]}.enable{ + TracePoint.new(:return, :b_return){|tp| + next if !target_thread? + next if tp.path != __FILE__ + ary << [tp.event, tp.method_id, tp.return_value] + }.enable{ send mid } ary.pop # last b_return event is not required. @@ -2089,7 +2201,7 @@ CODE q = Thread::Queue.new t = Thread.new{ Thread.current.add_trace_func proc{|ev, file, line, *args| - events << [ev, line] + events << [ev, line] if file == __FILE__ } # do not stop trace. They will be stopped at Thread termination. q.push 1 _x = 1 @@ -2125,9 +2237,9 @@ CODE } # it is dirty hack. usually we shouldn't use such technique Thread.pass until t.status == 'sleep' - # When MJIT thread exists, t.status becomes 'sleep' even if it does not reach m2t_q.pop. + # When RJIT thread exists, t.status becomes 'sleep' even if it does not reach m2t_q.pop. # This sleep forces it to reach m2t_q.pop for --jit-wait. - sleep 1 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? + sleep 1 if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? t.add_trace_func proc{|ev, file, line, *args| if file == __FILE__ @@ -2140,17 +2252,16 @@ CODE m2t_q.push 1 t.join - assert_equal ["c-return", base_line + 31], events[0] - assert_equal ["line", base_line + 32], events[1] - assert_equal ["line", base_line + 33], events[2] - assert_equal ["call", base_line + -6], events[3] - assert_equal ["return", base_line + -4], events[4] - assert_equal ["line", base_line + 34], events[5] - assert_equal ["line", base_line + 35], events[6] - assert_equal ["c-call", base_line + 35], events[7] # Thread.current - assert_equal ["c-return", base_line + 35], events[8] # Thread.current - assert_equal ["c-call", base_line + 35], events[9] # Thread#set_trace_func - assert_equal nil, events[10] + assert_equal ["line", base_line + 32], events[0] + assert_equal ["line", base_line + 33], events[1] + assert_equal ["call", base_line + -6], events[2] + assert_equal ["return", base_line + -4], events[3] + assert_equal ["line", base_line + 34], events[4] + assert_equal ["line", base_line + 35], events[5] + assert_equal ["c-call", base_line + 35], events[6] # Thread.current + assert_equal ["c-return", base_line + 35], events[7] # Thread.current + assert_equal ["c-call", base_line + 35], events[8] # Thread#set_trace_func + assert_equal nil, events[9] end def test_lineno_in_optimized_insn @@ -2364,6 +2475,40 @@ CODE assert_equal [:tp1, 1, 2, :tp2, 3], events end + def test_multiple_enable + ary = [] + trace = TracePoint.new(:call) do |tp| + ary << tp.method_id + end + trace.enable + trace.enable + foo + trace.disable + assert_equal(1, ary.count(:foo), '[Bug #19114]') + end + + def test_multiple_tracepoints_same_bmethod + events = [] + tp1 = TracePoint.new(:return) do |tp| + events << :tp1 + end + tp2 = TracePoint.new(:return) do |tp| + events << :tp2 + end + + obj = Object.new + obj.define_singleton_method(:foo) {} + bmethod = obj.method(:foo) + + tp1.enable(target: bmethod) do + tp2.enable(target: bmethod) do + obj.foo + end + end + + assert_equal([:tp2, :tp1], events, '[Bug #18031]') + end + def test_script_compiled events = [] tp = TracePoint.new(:script_compiled){|tp| @@ -2561,6 +2706,20 @@ CODE end bar EOS + + assert_normal_exit(<<-EOS, 'Bug #18730') + def bar + 42 + end + tp_line = TracePoint.new(:line) do |tp0| + tp_multi1 = TracePoint.new(:return, :b_return, :line) do |tp| + tp0.disable + end + tp_multi1.enable + end + tp_line.enable(target: method(:bar)) + bar + EOS end def test_stat_exists @@ -2620,4 +2779,150 @@ CODE TracePoint.allow_reentry{} end end + + def test_raising_from_b_return_tp_tracing_bmethod + assert_normal_exit(<<~RUBY, '[Bug #18060]', timeout: 3) + class Foo + define_singleton_method(:foo) { return } # a bmethod + end + + TracePoint.trace(:b_return) do |tp| + raise + end + + Foo.foo + RUBY + + # Same thing but with a target + assert_normal_exit(<<~RUBY, '[Bug #18060]', timeout: 3) + class Foo + define_singleton_method(:foo) { return } # a bmethod + end + + TracePoint.new(:b_return) do |tp| + raise + end.enable(target: Foo.method(:foo)) + + Foo.foo + RUBY + end + + def helper_cant_rescue + begin + raise SyntaxError + rescue + cant_rescue + end + end + + def test_tp_rescue + lines = [] + TracePoint.new(:line){|tp| + next unless target_thread? + lines << tp.lineno + }.enable{ + begin + helper_cant_rescue + rescue SyntaxError + end + } + _call_line = lines.shift + _raise_line = lines.shift + assert_equal [], lines + end + + def helper_can_rescue + begin + raise __LINE__.to_s + rescue SyntaxError + :ng + rescue + :ok + end + end + + def helper_can_rescue_empty_body + begin + raise __LINE__.to_s + rescue SyntaxError + :ng + rescue + end + end + + def test_tp_rescue_event + lines = [] + TracePoint.new(:rescue){|tp| + next unless target_thread? + lines << [tp.lineno, tp.raised_exception] + }.enable{ + helper_can_rescue + } + + line, err, = lines.pop + assert_equal [], lines + assert err.kind_of?(RuntimeError) + assert_equal err.message.to_i + 4, line + + lines = [] + TracePoint.new(:rescue){|tp| + next unless target_thread? + lines << [tp.lineno, tp.raised_exception] + }.enable{ + helper_can_rescue_empty_body + } + + line, err, = lines.pop + assert_equal [], lines + assert err.kind_of?(RuntimeError) + assert_equal err.message.to_i + 3, line + end + + def test_tracepoint_thread_begin + target_thread = nil + + trace = TracePoint.new(:thread_begin) do |tp| + target_thread = tp.self + end + + trace.enable(target_thread: nil) do + Thread.new{}.join + end + + assert_kind_of(Thread, target_thread) + end + + def test_tracepoint_thread_end + target_thread = nil + + trace = TracePoint.new(:thread_end) do |tp| + target_thread = tp.self + end + + trace.enable(target_thread: nil) do + Thread.new{}.join + end + + assert_kind_of(Thread, target_thread) + end + + def test_tracepoint_thread_end_with_exception + target_thread = nil + + trace = TracePoint.new(:thread_end) do |tp| + target_thread = tp.self + end + + trace.enable(target_thread: nil) do + thread = Thread.new do + Thread.current.report_on_exception = false + raise + end + + # Ignore the exception raised by the thread: + thread.join rescue nil + end + + assert_kind_of(Thread, target_thread) + end end |