1# frozen_string_literal: false
2require 'test/unit'
3
4class TestSetTraceFunc < Test::Unit::TestCase
5  def setup
6    @original_compile_option = RubyVM::InstructionSequence.compile_option
7    RubyVM::InstructionSequence.compile_option = {
8      :trace_instruction => true,
9      :specialized_instruction => false
10    }
11    @target_thread = Thread.current
12  end
13
14  def teardown
15    set_trace_func(nil)
16    RubyVM::InstructionSequence.compile_option = @original_compile_option
17    @target_thread = nil
18  end
19
20  def target_thread?
21    Thread.current == @target_thread
22  end
23
24  def test_c_call
25    events = []
26    name = "#{self.class}\##{__method__}"
27    eval <<-EOF.gsub(/^.*?: /, ""), nil, name
28     1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
29     2:   events << [event, lineno, mid, klass] if file == name
30     3: })
31     4: x = 1 + 1
32     5: set_trace_func(nil)
33    EOF
34    assert_equal(["c-return", 1, :set_trace_func, Kernel],
35                 events.shift)
36    assert_equal(["line", 4, __method__, self.class],
37                 events.shift)
38    assert_equal(["c-call", 4, :+, Integer],
39                 events.shift)
40    assert_equal(["c-return", 4, :+, Integer],
41                 events.shift)
42    assert_equal(["line", 5, __method__, self.class],
43                 events.shift)
44    assert_equal(["c-call", 5, :set_trace_func, Kernel],
45                 events.shift)
46    assert_equal([], events)
47  end
48
49  def test_call
50    events = []
51    name = "#{self.class}\##{__method__}"
52    eval <<-EOF.gsub(/^.*?: /, ""), nil, name
53     1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
54     2:   events << [event, lineno, mid, klass] if file == name
55     3: })
56     4: def add(x, y)
57     5:   x + y
58     6: end
59     7: x = add(1, 1)
60     8: set_trace_func(nil)
61    EOF
62    assert_equal(["c-return", 1, :set_trace_func, Kernel],
63                 events.shift)
64    assert_equal(["line", 4, __method__, self.class],
65                 events.shift)
66    assert_equal(["c-call", 4, :method_added, self.class],
67                 events.shift)
68    assert_equal(["c-return", 4, :method_added, self.class],
69                 events.shift)
70    assert_equal(["line", 7, __method__, self.class],
71                 events.shift)
72    assert_equal(["call", 4, :add, self.class],
73                 events.shift)
74    assert_equal(["line", 5, :add, self.class],
75                 events.shift)
76    assert_equal(["c-call", 5, :+, Integer],
77                 events.shift)
78    assert_equal(["c-return", 5, :+, Integer],
79                 events.shift)
80    assert_equal(["return", 6, :add, self.class],
81                 events.shift)
82    assert_equal(["line", 8, __method__, self.class],
83                 events.shift)
84    assert_equal(["c-call", 8, :set_trace_func, Kernel],
85                 events.shift)
86    assert_equal([], events)
87  end
88
89  def test_class
90    events = []
91    name = "#{self.class}\##{__method__}"
92    eval <<-EOF.gsub(/^.*?: /, ""), nil, name
93     1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
94     2:   events << [event, lineno, mid, klass] if file == name
95     3: })
96     4: class Foo
97     5:   def bar
98     6:   end
99     7: end
100     8: x = Foo.new.bar
101     9: set_trace_func(nil)
102    EOF
103    assert_equal(["c-return", 1, :set_trace_func, Kernel],
104                 events.shift)
105    assert_equal(["line", 4, __method__, self.class],
106                 events.shift)
107    assert_equal(["c-call", 4, :inherited, Class],
108                 events.shift)
109    assert_equal(["c-return", 4, :inherited, Class],
110                 events.shift)
111    assert_equal(["class", 4, nil, nil],
112                 events.shift)
113    assert_equal(["line", 5, nil, nil],
114                 events.shift)
115    assert_equal(["c-call", 5, :method_added, Module],
116                 events.shift)
117    assert_equal(["c-return", 5, :method_added, Module],
118                 events.shift)
119    assert_equal(["end", 7, nil, nil],
120                 events.shift)
121    assert_equal(["line", 8, __method__, self.class],
122                 events.shift)
123    assert_equal(["c-call", 8, :new, Class],
124                 events.shift)
125    assert_equal(["c-call", 8, :initialize, BasicObject],
126                 events.shift)
127    assert_equal(["c-return", 8, :initialize, BasicObject],
128                 events.shift)
129    assert_equal(["c-return", 8, :new, Class],
130                 events.shift)
131    assert_equal(["call", 5, :bar, Foo],
132                 events.shift)
133    assert_equal(["return", 6, :bar, Foo],
134                 events.shift)
135    assert_equal(["line", 9, __method__, self.class],
136                 events.shift)
137    assert_equal(["c-call", 9, :set_trace_func, Kernel],
138                 events.shift)
139    assert_equal([], events)
140  end
141
142  def test_return # [ruby-dev:38701]
143    events = []
144    name = "#{self.class}\##{__method__}"
145    eval <<-EOF.gsub(/^.*?: /, ""), nil, name
146     1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
147     2:   events << [event, lineno, mid, klass] if file == name
148     3: })
149     4: def meth_return(a)
150     5:   return if a
151     6:   return
152     7: end
153     8: meth_return(true)
154     9: meth_return(false)
155    10: set_trace_func(nil)
156    EOF
157    assert_equal(["c-return", 1, :set_trace_func, Kernel],
158                 events.shift)
159    assert_equal(["line", 4, __method__, self.class],
160                 events.shift)
161    assert_equal(["c-call", 4, :method_added, self.class],
162                 events.shift)
163    assert_equal(["c-return", 4, :method_added, self.class],
164                 events.shift)
165    assert_equal(["line", 8, __method__, self.class],
166                 events.shift)
167    assert_equal(["call", 4, :meth_return, self.class],
168                 events.shift)
169    assert_equal(["line", 5, :meth_return, self.class],
170                 events.shift)
171    assert_equal(["return", 5, :meth_return, self.class],
172                 events.shift)
173    assert_equal(["line", 9, :test_return, self.class],
174                 events.shift)
175    assert_equal(["call", 4, :meth_return, self.class],
176                 events.shift)
177    assert_equal(["line", 5, :meth_return, self.class],
178                 events.shift)
179    assert_equal(["return", 7, :meth_return, self.class],
180                 events.shift)
181    assert_equal(["line", 10, :test_return, self.class],
182                 events.shift)
183    assert_equal(["c-call", 10, :set_trace_func, Kernel],
184                 events.shift)
185    assert_equal([], events)
186  end
187
188  def test_return2 # [ruby-core:24463]
189    events = []
190    name = "#{self.class}\##{__method__}"
191    eval <<-EOF.gsub(/^.*?: /, ""), nil, name
192     1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
193     2:   events << [event, lineno, mid, klass] if file == name
194     3: })
195     4: def meth_return2
196     5:   a = 5
197     6:   return a
198     7: end
199     8: meth_return2
200     9: set_trace_func(nil)
201    EOF
202    assert_equal(["c-return", 1, :set_trace_func, Kernel],
203                 events.shift)
204    assert_equal(["line", 4, __method__, self.class],
205                 events.shift)
206    assert_equal(["c-call", 4, :method_added, self.class],
207                 events.shift)
208    assert_equal(["c-return", 4, :method_added, self.class],
209                 events.shift)
210    assert_equal(["line", 8, __method__, self.class],
211                 events.shift)
212    assert_equal(["call", 4, :meth_return2, self.class],
213                 events.shift)
214    assert_equal(["line", 5, :meth_return2, self.class],
215                 events.shift)
216    assert_equal(["line", 6, :meth_return2, self.class],
217                 events.shift)
218    assert_equal(["return", 7, :meth_return2, self.class],
219                 events.shift)
220    assert_equal(["line", 9, :test_return2, self.class],
221                 events.shift)
222    assert_equal(["c-call", 9, :set_trace_func, Kernel],
223                 events.shift)
224    assert_equal([], events)
225  end
226
227  def test_raise
228    events = []
229    name = "#{self.class}\##{__method__}"
230    eval <<-EOF.gsub(/^.*?: /, ""), nil, name
231     1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
232     2:   events << [event, lineno, mid, klass] if file == name
233     3: })
234     4: begin
235     5:   raise TypeError, "error"
236     6: rescue TypeError
237     7: end
238     8: set_trace_func(nil)
239    EOF
240    assert_equal(["c-return", 1, :set_trace_func, Kernel],
241                 events.shift)
242    assert_equal(["line", 5, __method__, self.class],
243                 events.shift)
244    assert_equal(["c-call", 5, :raise, Kernel],
245                 events.shift)
246    assert_equal(["c-call", 5, :exception, Exception],
247                 events.shift)
248    assert_equal(["c-call", 5, :initialize, Exception],
249                 events.shift)
250    assert_equal(["c-return", 5, :initialize, Exception],
251                 events.shift)
252    assert_equal(["c-return", 5, :exception, Exception],
253                 events.shift)
254    assert_equal(["c-return", 5, :raise, Kernel],
255                 events.shift)
256    assert_equal(["c-call", 5, :backtrace, Exception],
257                 events.shift)
258    assert_equal(["c-return", 5, :backtrace, Exception],
259                 events.shift)
260    assert_equal(["raise", 5, :test_raise, TestSetTraceFunc],
261                 events.shift)
262    assert_equal(["c-call", 6, :===, Module],
263                 events.shift)
264    assert_equal(["c-return", 6, :===, Module],
265                 events.shift)
266    assert_equal(["line", 8, __method__, self.class],
267                 events.shift)
268    assert_equal(["c-call", 8, :set_trace_func, Kernel],
269                 events.shift)
270    assert_equal([], events)
271  end
272
273  def test_break # [ruby-core:27606] [Bug #2610]
274    events = []
275    name = "#{self.class}\##{__method__}"
276    eval <<-EOF.gsub(/^.*?: /, ""), nil, name
277     1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
278     2:   events << [event, lineno, mid, klass] if file == name
279     3: })
280     4: [1,2,3].any? {|n| n}
281     8: set_trace_func(nil)
282    EOF
283
284    [["c-return", 1, :set_trace_func, Kernel],
285     ["line", 4, __method__, self.class],
286     ["c-call", 4, :any?, Array],
287     ["line", 4, __method__, self.class],
288     ["c-return", 4, :any?, Array],
289     ["line", 5, __method__, self.class],
290     ["c-call", 5, :set_trace_func, Kernel]].each.with_index{|e, i|
291       assert_equal(e, events.shift, "mismatch on #{i}th trace")
292    }
293  end
294
295  def test_invalid_proc
296      assert_raise(TypeError) { set_trace_func(1) }
297  end
298
299  def test_raise_in_trace
300    set_trace_func proc {raise rescue nil}
301    assert_equal(42, (raise rescue 42), '[ruby-core:24118]')
302  end
303
304  def test_thread_trace
305    events = {:set => [], :add => []}
306    prc = Proc.new { |event, file, lineno, mid, binding, klass|
307      events[:set] << [event, lineno, mid, klass, :set]
308    }
309    prc = prc # suppress warning
310    prc2 = Proc.new { |event, file, lineno, mid, binding, klass|
311      events[:add] << [event, lineno, mid, klass, :add]
312    }
313    prc2 = prc2 # suppress warning
314
315    th = Thread.new do
316      th = Thread.current
317      name = "#{self.class}\##{__method__}"
318      eval <<-EOF.gsub(/^.*?: /, ""), nil, name
319       1: th.set_trace_func(prc)
320       2: th.add_trace_func(prc2)
321       3: class ThreadTraceInnerClass
322       4:   def foo
323       5:     _x = 1 + 1
324       6:   end
325       7: end
326       8: ThreadTraceInnerClass.new.foo
327       9: th.set_trace_func(nil)
328      EOF
329    end
330    th.join
331
332    [["c-return", 1, :set_trace_func, Thread, :set],
333     ["line", 2, __method__, self.class, :set],
334     ["c-call", 2, :add_trace_func, Thread, :set]].each do |e|
335      assert_equal(e, events[:set].shift)
336    end
337
338    [["c-return", 2, :add_trace_func, Thread],
339     ["line", 3, __method__, self.class],
340     ["c-call", 3, :inherited, Class],
341     ["c-return", 3, :inherited, Class],
342     ["class", 3, nil, nil],
343     ["line", 4, nil, nil],
344     ["c-call", 4, :method_added, Module],
345     ["c-return", 4, :method_added, Module],
346     ["end", 7, nil, nil],
347     ["line", 8, __method__, self.class],
348     ["c-call", 8, :new, Class],
349     ["c-call", 8, :initialize, BasicObject],
350     ["c-return", 8, :initialize, BasicObject],
351     ["c-return", 8, :new, Class],
352     ["call", 4, :foo, ThreadTraceInnerClass],
353     ["line", 5, :foo, ThreadTraceInnerClass],
354     ["c-call", 5, :+, Integer],
355     ["c-return", 5, :+, Integer],
356     ["return", 6, :foo, ThreadTraceInnerClass],
357     ["line", 9, __method__, self.class],
358     ["c-call", 9, :set_trace_func, Thread]].each do |e|
359      [:set, :add].each do |type|
360        assert_equal(e + [type], events[type].shift)
361      end
362    end
363    assert_equal([], events[:set])
364    assert_equal([], events[:add])
365  end
366
367  def test_trace_defined_method
368    events = []
369    name = "#{self.class}\##{__method__}"
370    eval <<-EOF.gsub(/^.*?: /, ""), nil, name
371     1: class FooBar; define_method(:foobar){}; end
372     2: fb = FooBar.new
373     3: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
374     4:   events << [event, lineno, mid, klass] if file == name
375     5: })
376     6: fb.foobar
377     7: set_trace_func(nil)
378    EOF
379
380    [["c-return", 3, :set_trace_func, Kernel],
381     ["line", 6, __method__, self.class],
382     ["call", 1, :foobar, FooBar],
383     ["return", 6, :foobar, FooBar],
384     ["line", 7, __method__, self.class],
385     ["c-call", 7, :set_trace_func, Kernel]].each{|e|
386      assert_equal(e, events.shift)
387    }
388  end
389
390  def test_remove_in_trace
391    bug3921 = '[ruby-dev:42350]'
392    ok = false
393    func = lambda{|e, f, l, i, b, k|
394      set_trace_func(nil)
395      ok = eval("self", b)
396    }
397
398    set_trace_func(func)
399    assert_equal(self, ok, bug3921)
400  end
401
402  class << self
403    define_method(:method_added, Module.method(:method_added))
404  end
405
406  def trace_by_tracepoint *trace_events
407    events = []
408    trace = nil
409    xyzzy = nil
410    _local_var = :outer
411    raised_exc = nil
412    method = :trace_by_tracepoint
413    _get_data = lambda{|tp|
414      case tp.event
415      when :return, :c_return
416        tp.return_value
417      when :raise
418        tp.raised_exception
419      else
420        :nothing
421      end
422    }
423    _defined_class = lambda{|tp|
424      klass = tp.defined_class
425      begin
426        # If it is singleton method, then return original class
427        # to make compatible with set_trace_func().
428        # This is very ad-hoc hack. I hope I can make more clean test on it.
429        case klass.inspect
430        when /Class:TracePoint/; return TracePoint
431        when /Class:Exception/; return Exception
432        else klass
433        end
434      rescue Exception => e
435        e
436      end if klass
437    }
438
439    trace = nil
440    begin
441    eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy'
442    1: trace = TracePoint.trace(*trace_events){|tp| next if !target_thread?
443    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'
444    3: }
445    4: 1.times{|;_local_var| _local_var = :inner
446    5:   tap{}
447    6: }
448    7: class XYZZY
449    8:   _local_var = :XYZZY_outer
450    9:   def foo
451   10:     _local_var = :XYZZY_foo
452   11:     bar
453   12:   end
454   13:   def bar
455   14:     _local_var = :XYZZY_bar
456   15:     tap{}
457   16:   end
458   17: end
459   18: xyzzy = XYZZY.new
460   19: xyzzy.foo
461   20: begin; raise RuntimeError; rescue RuntimeError => raised_exc; end
462   21: trace.disable
463    EOF
464    self.class.class_eval{remove_const(:XYZZY)}
465    ensure
466      trace.disable if trace&.enabled?
467    end
468
469    answer_events = [
470     #
471     [:c_return, 1, "xyzzy", TracePoint,  :trace,           TracePoint,  :outer,  trace],
472     [:line,     4, 'xyzzy', self.class,  method,           self,        :outer, :nothing],
473     [:c_call,   4, 'xyzzy', Integer,     :times,           1,           :outer, :nothing],
474     [:line,     4, 'xyzzy', self.class,  method,           self,        nil,    :nothing],
475     [:line,     5, 'xyzzy', self.class,  method,           self,        :inner, :nothing],
476     [:c_call,   5, 'xyzzy', Kernel,      :tap,             self,        :inner, :nothing],
477     [:c_return, 5, "xyzzy", Kernel,      :tap,             self,        :inner, self],
478     [:c_return, 4, "xyzzy", Integer,     :times,           1,           :outer, 1],
479     [:line,     7, 'xyzzy', self.class,  method,           self,        :outer, :nothing],
480     [:c_call,   7, "xyzzy", Class,       :inherited,       Object,      :outer, :nothing],
481     [:c_return, 7, "xyzzy", Class,       :inherited,       Object,      :outer, nil],
482     [:class,    7, "xyzzy", nil,         nil,              xyzzy.class, nil,    :nothing],
483     [:line,     8, "xyzzy", nil,         nil,              xyzzy.class, nil,    :nothing],
484     [:line,     9, "xyzzy", nil,         nil,              xyzzy.class, :XYZZY_outer, :nothing],
485     [:c_call,   9, "xyzzy", Module,      :method_added,    xyzzy.class, :XYZZY_outer, :nothing],
486     [:c_return, 9, "xyzzy", Module,      :method_added,    xyzzy.class, :XYZZY_outer, nil],
487     [:line,    13, "xyzzy", nil,         nil,              xyzzy.class, :XYZZY_outer, :nothing],
488     [:c_call,  13, "xyzzy", Module,      :method_added,    xyzzy.class, :XYZZY_outer, :nothing],
489     [:c_return,13, "xyzzy", Module,      :method_added,    xyzzy.class, :XYZZY_outer, nil],
490     [:end,     17, "xyzzy", nil,         nil,              xyzzy.class, :XYZZY_outer, :nothing],
491     [:line,    18, "xyzzy", TestSetTraceFunc, method,      self,        :outer, :nothing],
492     [:c_call,  18, "xyzzy", Class,       :new,             xyzzy.class, :outer, :nothing],
493     [:c_call,  18, "xyzzy", BasicObject, :initialize,      xyzzy,       :outer, :nothing],
494     [:c_return,18, "xyzzy", BasicObject, :initialize,      xyzzy,       :outer, nil],
495     [:c_return,18, "xyzzy", Class,       :new,             xyzzy.class, :outer, xyzzy],
496     [:line,    19, "xyzzy", TestSetTraceFunc, method,      self, :outer, :nothing],
497     [:call,     9, "xyzzy", xyzzy.class, :foo,             xyzzy,       nil,  :nothing],
498     [:line,    10, "xyzzy", xyzzy.class, :foo,             xyzzy,       nil,  :nothing],
499     [:line,    11, "xyzzy", xyzzy.class, :foo,             xyzzy,       :XYZZY_foo, :nothing],
500     [:call,    13, "xyzzy", xyzzy.class, :bar,             xyzzy,       nil, :nothing],
501     [:line,    14, "xyzzy", xyzzy.class, :bar,             xyzzy,       nil, :nothing],
502     [:line,    15, "xyzzy", xyzzy.class, :bar,             xyzzy,       :XYZZY_bar, :nothing],
503     [:c_call,  15, "xyzzy", Kernel,      :tap,             xyzzy,       :XYZZY_bar, :nothing],
504     [:c_return,15, "xyzzy", Kernel,      :tap,             xyzzy,       :XYZZY_bar, xyzzy],
505     [:return,  16, "xyzzy", xyzzy.class, :bar,             xyzzy,       :XYZZY_bar, xyzzy],
506     [:return,  12, "xyzzy", xyzzy.class, :foo,             xyzzy,       :XYZZY_foo, xyzzy],
507     [:line,    20, "xyzzy", TestSetTraceFunc, method,      self,        :outer, :nothing],
508     [:c_call,  20, "xyzzy", Kernel,      :raise,           self,        :outer, :nothing],
509     [:c_call,  20, "xyzzy", Exception,   :exception,       RuntimeError, :outer, :nothing],
510     [:c_call,  20, "xyzzy", Exception,   :initialize,      raised_exc,  :outer, :nothing],
511     [:c_return,20, "xyzzy", Exception,   :initialize,      raised_exc,  :outer, raised_exc],
512     [:c_return,20, "xyzzy", Exception,   :exception,       RuntimeError, :outer, raised_exc],
513     [:c_return,20, "xyzzy", Kernel,      :raise,           self,        :outer, nil],
514     [:c_call,  20, "xyzzy", Exception,   :backtrace,       raised_exc,  :outer, :nothing],
515     [:c_return,20, "xyzzy", Exception,   :backtrace,       raised_exc,  :outer, nil],
516     [:raise,   20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc],
517     [:c_call,  20, "xyzzy", Module,      :===,             RuntimeError,:outer, :nothing],
518     [:c_return,20, "xyzzy", Module,      :===,             RuntimeError,:outer, true],
519     [:line,    21, "xyzzy", TestSetTraceFunc, method,      self,        :outer, :nothing],
520     [:c_call,  21, "xyzzy", TracePoint,  :disable,         trace,       :outer, :nothing],
521     ]
522
523    return events, answer_events
524  end
525
526  def trace_by_set_trace_func
527    events = []
528    trace = nil
529    trace = trace
530    xyzzy = nil
531    xyzzy = xyzzy
532    _local_var = :outer
533    eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy'
534    1: set_trace_func(lambda{|event, file, line, id, binding, klass|
535    2:   events << [event, line, file, klass, id, binding.eval('self'), binding.eval("_local_var")] if file == 'xyzzy'
536    3: })
537    4: 1.times{|;_local_var| _local_var = :inner
538    5:   tap{}
539    6: }
540    7: class XYZZY
541    8:   _local_var = :XYZZY_outer
542    9:   def foo
543   10:     _local_var = :XYZZY_foo
544   11:     bar
545   12:   end
546   13:   def bar
547   14:     _local_var = :XYZZY_bar
548   15:     tap{}
549   16:   end
550   17: end
551   18: xyzzy = XYZZY.new
552   19: xyzzy.foo
553   20: begin; raise RuntimeError; rescue RuntimeError => raised_exc; end
554   21: set_trace_func(nil)
555    EOF
556    self.class.class_eval{remove_const(:XYZZY)}
557    return events
558  end
559
560  def test_tracepoint
561    events1, answer_events = *trace_by_tracepoint(:line, :class, :end, :call, :return, :c_call, :c_return, :raise)
562
563    ms = [events1, answer_events].map{|evs|
564      evs.map{|e|
565        "#{e[0]} - #{e[2]}:#{e[1]} id: #{e[4]}"
566      }
567    }
568
569    mesg = ms[0].zip(ms[1]).map{|a, b|
570      if a != b
571        "#{a} <-> #{b}"
572      end
573    }.compact.join("\n")
574
575    answer_events.zip(events1){|answer, event|
576      assert_equal answer, event, mesg
577    }
578
579    events2 = trace_by_set_trace_func
580    events1.zip(events2){|ev1, ev2|
581      ev2[0] = ev2[0].sub('-', '_').to_sym
582      assert_equal ev1[0..2], ev2[0..2], ev1.inspect
583
584      # event, line, file, klass, id, binding.eval('self'), binding.eval("_local_var")
585      assert_equal ev1[3].nil?, ev2[3].nil? # klass
586      assert_equal ev1[4].nil?, ev2[4].nil? # id
587      assert_equal ev1[6], ev2[6]           # _local_var
588    }
589
590    [:line, :class, :end, :call, :return, :c_call, :c_return, :raise].each{|event|
591      events1, answer_events = *trace_by_tracepoint(event)
592      answer_events.find_all{|e| e[0] == event}.zip(events1){|answer_line, event_line|
593        assert_equal answer_line, event_line
594      }
595    }
596  end
597
598  def test_tracepoint_object_id
599    tps = []
600    trace = TracePoint.trace(){|tp|
601      next if !target_thread?
602      tps << tp
603    }
604    tap{}
605    tap{}
606    tap{}
607    trace.disable
608
609    # passed tp is unique, `trace' object which is generated by TracePoint.trace
610    tps.each{|tp|
611      assert_equal trace, tp
612    }
613  end
614
615  def test_tracepoint_access_from_outside
616    tp_store = nil
617    trace = TracePoint.trace(){|tp|
618      next if !target_thread?
619      tp_store = tp
620    }
621    tap{}
622    trace.disable
623
624    assert_raise(RuntimeError){tp_store.lineno}
625    assert_raise(RuntimeError){tp_store.event}
626    assert_raise(RuntimeError){tp_store.path}
627    assert_raise(RuntimeError){tp_store.method_id}
628    assert_raise(RuntimeError){tp_store.defined_class}
629    assert_raise(RuntimeError){tp_store.binding}
630    assert_raise(RuntimeError){tp_store.self}
631    assert_raise(RuntimeError){tp_store.return_value}
632    assert_raise(RuntimeError){tp_store.raised_exception}
633  end
634
635  def foo
636  end
637
638  def test_tracepoint_enable
639    ary = []
640    args = nil
641    trace = TracePoint.new(:call){|tp|
642      next if !target_thread?
643      ary << tp.method_id
644    }
645    foo
646    trace.enable{|*a|
647      args = a
648      foo
649    }
650    foo
651    assert_equal([:foo], ary)
652    assert_equal([], args)
653
654    trace = TracePoint.new{}
655    begin
656      assert_equal(false, trace.enable)
657      assert_equal(true, trace.enable)
658      trace.enable{}
659      assert_equal(true, trace.enable)
660    ensure
661      trace.disable
662    end
663  end
664
665  def test_tracepoint_disable
666    ary = []
667    args = nil
668    trace = TracePoint.trace(:call){|tp|
669      next if !target_thread?
670      ary << tp.method_id
671    }
672    foo
673    trace.disable{|*a|
674      args = a
675      foo
676    }
677    foo
678    trace.disable
679    assert_equal([:foo, :foo], ary)
680    assert_equal([], args)
681
682    trace = TracePoint.new{}
683    trace.enable{
684      assert_equal(true, trace.disable)
685      assert_equal(false, trace.disable)
686      trace.disable{}
687      assert_equal(false, trace.disable)
688    }
689  end
690
691  def test_tracepoint_enabled
692    trace = TracePoint.trace(:call){|tp|
693      #
694    }
695    assert_equal(true, trace.enabled?)
696    trace.disable{
697      assert_equal(false, trace.enabled?)
698      trace.enable{
699        assert_equal(true, trace.enabled?)
700      }
701    }
702    trace.disable
703    assert_equal(false, trace.enabled?)
704  end
705
706  def parameter_test(a, b, c)
707    yield
708  end
709
710  def test_tracepoint_parameters
711    trace = TracePoint.new(:line, :class, :end, :call, :return, :b_call, :b_return, :c_call, :c_return, :raise){|tp|
712      next if !target_thread?
713      next if tp.path != __FILE__
714      case tp.event
715      when :call, :return
716        assert_equal([[:req, :a], [:req, :b], [:req, :c]], tp.parameters)
717      when :b_call, :b_return
718        next if tp.parameters == []
719        if tp.parameters.first == [:opt, :x]
720          assert_equal([[:opt, :x], [:opt, :y], [:opt, :z]], tp.parameters)
721        else
722          assert_equal([[:req, :p], [:req, :q], [:req, :r]], tp.parameters)
723        end
724      when :c_call, :c_return
725        assert_equal([[:req]], tp.parameters) if tp.method_id == :getbyte
726      when :line, :class, :end, :raise
727        assert_raise(RuntimeError) { tp.parameters }
728      end
729    }
730    obj = Object.new
731    trace.enable{
732      parameter_test(1, 2, 3) {|x, y, z|
733      }
734      lambda {|p, q, r| }.call(4, 5, 6)
735      "".getbyte(0)
736      class << obj
737      end
738      begin
739        raise
740      rescue
741      end
742    }
743  end
744
745  def method_test_tracepoint_return_value obj
746    obj
747  end
748
749  def test_tracepoint_return_value
750    trace = TracePoint.new(:call, :return){|tp|
751      next if !target_thread?
752      next if tp.path != __FILE__
753      case tp.event
754      when :call
755        assert_raise(RuntimeError) {tp.return_value}
756      when :return
757        assert_equal("xyzzy", tp.return_value)
758      end
759    }
760    trace.enable{
761      method_test_tracepoint_return_value "xyzzy"
762    }
763  end
764
765  class XYZZYException < Exception; end
766  def method_test_tracepoint_raised_exception err
767    raise err
768  end
769
770  def test_tracepoint_raised_exception
771    trace = TracePoint.new(:call, :return, :raise){|tp|
772      next if !target_thread?
773      case tp.event
774      when :call, :return
775        assert_raise(RuntimeError) { tp.raised_exception }
776      when :raise
777        assert_kind_of(XYZZYException, tp.raised_exception)
778      end
779    }
780    trace.enable{
781      begin
782        method_test_tracepoint_raised_exception XYZZYException
783      rescue XYZZYException
784        # ok
785      else
786        raise
787      end
788    }
789  end
790
791  def method_for_test_tracepoint_block
792    yield
793  end
794
795  def test_tracepoint_block
796    events = []
797    TracePoint.new(:call, :return, :c_call, :b_call, :c_return, :b_return){|tp|
798      next if !target_thread?
799      events << [
800        tp.event, tp.method_id, tp.defined_class, tp.self.class,
801        /return/ =~ tp.event ? tp.return_value : nil
802      ]
803    }.enable{
804      1.times{
805        3
806      }
807      method_for_test_tracepoint_block{
808        4
809      }
810    }
811    # pp events
812    # expected_events =
813    [[:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
814     [:c_call, :times, Integer, Integer, nil],
815     [:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
816     [:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 3],
817     [:c_return, :times, Integer, Integer, 1],
818     [:call, :method_for_test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
819     [:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
820     [:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4],
821     [:return, :method_for_test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4],
822     [:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4]
823    ].zip(events){|expected, actual|
824      assert_equal(expected, actual)
825    }
826  end
827
828  def test_tracepoint_thread
829    events = []
830    thread_self = nil
831    created_thread = nil
832    TracePoint.new(:thread_begin, :thread_end){|tp|
833      events << [Thread.current,
834                 tp.event,
835                 tp.lineno,  #=> 0
836                 tp.path,    #=> nil
837                 tp.binding, #=> nil
838                 tp.defined_class, #=> nil,
839                 tp.self.class # tp.self return creating/ending thread
840                 ]
841    }.enable{
842      created_thread = Thread.new{thread_self = self}
843      created_thread.join
844    }
845    events.reject!{|i| i[0] != created_thread}
846    assert_equal(self, thread_self)
847    assert_equal([created_thread, :thread_begin, 0, nil, nil, nil, Thread], events[0])
848    assert_equal([created_thread, :thread_end, 0, nil, nil, nil, Thread], events[1])
849    assert_equal(2, events.size)
850  end
851
852  def test_tracepoint_inspect
853    events = []
854    th = nil
855    trace = TracePoint.new{|tp|
856      next if !target_thread? && th != Thread.current
857      events << [tp.event, tp.inspect]
858    }
859    assert_equal("#<TracePoint:disabled>", trace.inspect)
860    trace.enable{
861      assert_equal("#<TracePoint:enabled>", trace.inspect)
862      th = Thread.new{}
863      th.join
864    }
865    assert_equal("#<TracePoint:disabled>", trace.inspect)
866    events.each{|(ev, str)|
867      case ev
868      when :line
869        assert_match(/ in /, str)
870      when :call, :c_call
871        assert_match(/call \`/, str) # #<TracePoint:c_call `inherited'@../trunk/test.rb:11>
872      when :return, :c_return
873        assert_match(/return \`/, str) # #<TracePoint:return `m'@../trunk/test.rb:3>
874      when /thread/
875        assert_match(/\#<Thread:/, str) # #<TracePoint:thread_end of #<Thread:0x87076c0>>
876      else
877        assert_match(/\#<TracePoint:/, str)
878      end
879    }
880  end
881
882  def test_tracepoint_exception_at_line
883    assert_raise(RuntimeError) do
884      TracePoint.new(:line) {
885        next if !target_thread?
886        raise
887      }.enable {
888        1
889      }
890    end
891  end
892
893  def test_tracepoint_exception_at_return
894    assert_nothing_raised(Timeout::Error, 'infinite trace') do
895      assert_normal_exit('def m; end; TracePoint.new(:return) {raise}.enable {m}', '', timeout: 3)
896    end
897  end
898
899  def test_tracepoint_exception_at_c_return
900    assert_nothing_raised(Timeout::Error, 'infinite trace') do
901      assert_normal_exit %q{
902        begin
903          TracePoint.new(:c_return){|tp|
904            raise
905          }.enable{
906            tap{ itself }
907          }
908        rescue
909        end
910      }, '', timeout: 3
911    end
912  end
913
914  def test_tracepoint_with_multithreads
915    assert_nothing_raised do
916      TracePoint.new{
917        10.times{
918          Thread.pass
919        }
920      }.enable do
921        (1..10).map{
922          Thread.new{
923            1000.times{
924            }
925          }
926        }.each{|th|
927          th.join
928        }
929      end
930    end
931  end
932
933  class FOO_ERROR < RuntimeError; end
934  class BAR_ERROR < RuntimeError; end
935  def m1_test_trace_point_at_return_when_exception
936    m2_test_trace_point_at_return_when_exception
937  end
938  def m2_test_trace_point_at_return_when_exception
939    raise BAR_ERROR
940  end
941
942  def test_trace_point_at_return_when_exception
943    bug_7624 = '[ruby-core:51128] [ruby-trunk - Bug #7624]'
944    TracePoint.new{|tp|
945      next if !target_thread?
946      if tp.event == :return &&
947        tp.method_id == :m2_test_trace_point_at_return_when_exception
948        raise FOO_ERROR
949      end
950    }.enable do
951      assert_raise(FOO_ERROR, bug_7624) do
952        m1_test_trace_point_at_return_when_exception
953      end
954    end
955
956    bug_7668 = '[Bug #7668]'
957    ary = []
958    trace = TracePoint.new{|tp|
959      next if !target_thread?
960      ary << tp.event
961      raise
962    }
963    begin
964      trace.enable{
965        1.times{
966          raise
967        }
968      }
969    rescue
970      assert_equal([:b_call, :b_return], ary, bug_7668)
971    end
972  end
973
974  def m1_for_test_trace_point_binding_in_ifunc(arg)
975    arg + nil
976  rescue
977  end
978
979  def m2_for_test_trace_point_binding_in_ifunc(arg)
980    arg.inject(:+)
981  rescue
982  end
983
984  def test_trace_point_binding_in_ifunc
985    bug7774 = '[ruby-dev:46908]'
986    src = %q{
987      tp = TracePoint.new(:raise) do |tp|
988        tp.binding
989      end
990      tp.enable do
991        obj = Object.new
992        class << obj
993          include Enumerable
994          def each
995            yield 1
996          end
997        end
998        %s
999      end
1000    }
1001    assert_normal_exit src % %q{obj.zip({}) {}}, bug7774
1002    assert_normal_exit src % %q{
1003      require 'continuation'
1004      begin
1005        c = nil
1006        obj.sort_by {|x| callcc {|c2| c ||= c2 }; x }
1007        c.call
1008      rescue RuntimeError
1009      end
1010    }, bug7774
1011
1012    # TracePoint
1013    tp_b = nil
1014    TracePoint.new(:raise) do |tp|
1015      next if !target_thread?
1016      tp_b = tp.binding
1017    end.enable do
1018      m1_for_test_trace_point_binding_in_ifunc(0)
1019      assert_equal(self, eval('self', tp_b), '[ruby-dev:46960]')
1020
1021      m2_for_test_trace_point_binding_in_ifunc([0, nil])
1022      assert_equal(self, eval('self', tp_b), '[ruby-dev:46960]')
1023    end
1024
1025    # set_trace_func
1026    stf_b = nil
1027    set_trace_func ->(event, file, line, id, binding, klass) do
1028      stf_b = binding if event == 'raise'
1029    end
1030    begin
1031      m1_for_test_trace_point_binding_in_ifunc(0)
1032      assert_equal(self, eval('self', stf_b), '[ruby-dev:46960]')
1033
1034      m2_for_test_trace_point_binding_in_ifunc([0, nil])
1035      assert_equal(self, eval('self', stf_b), '[ruby-dev:46960]')
1036    ensure
1037      set_trace_func(nil)
1038    end
1039  end
1040
1041  def test_trace_point_binding_after_break
1042    bug10689 = '[ruby-dev:48797]'
1043    assert_in_out_err([], <<-INPUT, [], [], bug10689)
1044      class Bug
1045        include Enumerable
1046
1047        def each
1048          [0].each do
1049            yield
1050          end
1051        end
1052      end
1053
1054      TracePoint.trace(:c_return) do |tp|
1055        tp.binding
1056      end
1057
1058      Bug.new.all? { false }
1059    INPUT
1060  end
1061
1062  def test_tracepoint_b_return_with_next
1063    n = 0
1064    TracePoint.new(:b_return){
1065      next if !target_thread?
1066      n += 1
1067    }.enable{
1068      3.times{
1069        next
1070      } # 3 times b_retun
1071    }   # 1 time b_return
1072
1073    assert_equal 4, n
1074  end
1075
1076  def test_tracepoint_b_return_with_lambda
1077    n = 0
1078    TracePoint.new(:b_return){
1079      next if !target_thread?
1080      n+=1
1081    }.enable{
1082      lambda{
1083        return
1084      }.call     # n += 1 #=> 1
1085      3.times{
1086        lambda{
1087          return # n += 3 #=> 4
1088        }.call
1089      }          # n += 3 #=> 7
1090      begin
1091        lambda{
1092          raise
1093        }.call   # n += 1 #=> 8
1094      rescue
1095        # ignore
1096      end        # n += 1 #=> 9
1097    }
1098
1099    assert_equal 9, n
1100  end
1101
1102  def test_isolated_raise_in_trace
1103    bug9088 = '[ruby-dev:47793] [Bug #9088]'
1104    assert_in_out_err([], <<-END, [], [], bug9088)
1105    set_trace_func proc {raise rescue nil}
1106    1.times {break}
1107    END
1108  end
1109
1110  def test_a_call
1111    events = []
1112    TracePoint.new(:a_call){|tp|
1113      next if !target_thread?
1114      events << tp.event
1115    }.enable{
1116      1.times{
1117        3
1118      }
1119      method_for_test_tracepoint_block{
1120        4
1121      }
1122    }
1123    assert_equal([
1124      :b_call,
1125      :c_call,
1126      :b_call,
1127      :call,
1128      :b_call,
1129    ], events)
1130  end
1131
1132  def test_a_return
1133    events = []
1134    TracePoint.new(:a_return){|tp|
1135      next if !target_thread?
1136      events << tp.event
1137    }.enable{
1138      1.times{
1139        3
1140      }
1141      method_for_test_tracepoint_block{
1142        4
1143      }
1144    }
1145    assert_equal([
1146      :b_return,
1147      :c_return,
1148      :b_return,
1149      :return,
1150      :b_return
1151    ], events)
1152  end
1153
1154  def test_const_missing
1155    bug59398 = '[ruby-core:59398]'
1156    events = []
1157    assert !defined?(MISSING_CONSTANT_59398)
1158    TracePoint.new(:c_call, :c_return, :call, :return){|tp|
1159      next if !target_thread?
1160      next unless tp.defined_class == Module
1161      # rake/ext/module.rb aliases :const_missing and Ruby uses the aliased name
1162      # but this only happens when running the full test suite
1163      events << [tp.event,tp.method_id] if tp.method_id == :const_missing || tp.method_id == :rake_original_const_missing
1164    }.enable{
1165      MISSING_CONSTANT_59398 rescue nil
1166    }
1167    if events.map{|e|e[1]}.include?(:rake_original_const_missing)
1168      assert_equal([
1169        [:call, :const_missing],
1170        [:c_call, :rake_original_const_missing],
1171        [:c_return, :rake_original_const_missing],
1172        [:return, :const_missing],
1173      ], events, bug59398)
1174    else
1175      assert_equal([
1176        [:c_call, :const_missing],
1177        [:c_return, :const_missing]
1178      ], events, bug59398)
1179    end
1180  end
1181
1182  class AliasedRubyMethod
1183    def foo; 1; end;
1184    alias bar foo
1185  end
1186  def test_aliased_ruby_method
1187    events = []
1188    aliased = AliasedRubyMethod.new
1189    TracePoint.new(:call, :return){|tp|
1190      next if !target_thread?
1191      events << [tp.event, tp.method_id]
1192    }.enable{
1193      aliased.bar
1194    }
1195    assert_equal([
1196      [:call, :foo],
1197      [:return, :foo]
1198    ], events, "should use original method name for tracing ruby methods")
1199  end
1200  class AliasedCMethod < Hash
1201    alias original_size size
1202    def size; original_size; end
1203  end
1204
1205  def test_aliased_c_method
1206    events = []
1207    aliased = AliasedCMethod.new
1208    TracePoint.new(:call, :return, :c_call, :c_return){|tp|
1209      next if !target_thread?
1210      events << [tp.event, tp.method_id]
1211    }.enable{
1212      aliased.size
1213    }
1214    assert_equal([
1215      [:call, :size],
1216      [:c_call, :size],
1217      [:c_return, :size],
1218      [:return, :size]
1219    ], events, "should use alias method name for tracing c methods")
1220  end
1221
1222  def test_method_missing
1223    bug59398 = '[ruby-core:59398]'
1224    events = []
1225    assert !respond_to?(:missing_method_59398)
1226    TracePoint.new(:c_call, :c_return, :call, :return){|tp|
1227      next if !target_thread?
1228      next unless tp.defined_class == BasicObject
1229      # rake/ext/module.rb aliases :const_missing and Ruby uses the aliased name
1230      # but this only happens when running the full test suite
1231      events << [tp.event,tp.method_id] if tp.method_id == :method_missing
1232    }.enable{
1233      missing_method_59398 rescue nil
1234    }
1235    assert_equal([
1236      [:c_call, :method_missing],
1237      [:c_return, :method_missing]
1238    ], events, bug59398)
1239  end
1240
1241  class C9759
1242    define_method(:foo){
1243      raise
1244    }
1245  end
1246
1247  def test_define_method_on_exception
1248    events = []
1249    obj = C9759.new
1250    TracePoint.new(:call, :return){|tp|
1251      next unless target_thread?
1252      events << [tp.event, tp.method_id]
1253    }.enable{
1254      obj.foo rescue nil
1255    }
1256    assert_equal([[:call, :foo], [:return, :foo]], events, 'Bug #9759')
1257
1258    events = []
1259    begin
1260      set_trace_func(lambda{|event, file, lineno, mid, binding, klass|
1261        next unless target_thread?
1262        case event
1263        when 'call', 'return'
1264          events << [event, mid]
1265        end
1266      })
1267      obj.foo rescue nil
1268      set_trace_func(nil)
1269
1270      assert_equal([['call', :foo], ['return', :foo]], events, 'Bug #9759')
1271    ensure
1272    end
1273  end
1274
1275  class C11492
1276    define_method(:foo_return){
1277      return true
1278    }
1279    define_method(:foo_break){
1280      break true
1281    }
1282  end
1283
1284  def test_define_method_on_return
1285    # return
1286    events = []
1287    obj = C11492.new
1288    TracePoint.new(:call, :return){|tp|
1289      next unless target_thread?
1290      events << [tp.event, tp.method_id]
1291    }.enable{
1292      obj.foo_return
1293    }
1294    assert_equal([[:call, :foo_return], [:return, :foo_return]], events, 'Bug #11492')
1295
1296    # break
1297    events = []
1298    obj = C11492.new
1299    TracePoint.new(:call, :return){|tp|
1300      next unless target_thread?
1301      events << [tp.event, tp.method_id]
1302    }.enable{
1303      obj.foo_break
1304    }
1305    assert_equal([[:call, :foo_break], [:return, :foo_break]], events, 'Bug #11492')
1306
1307    # set_trace_func
1308    # return
1309    events = []
1310    begin
1311      set_trace_func(lambda{|event, file, lineno, mid, binding, klass|
1312        next unless target_thread?
1313        case event
1314        when 'call', 'return'
1315          events << [event, mid]
1316        end
1317      })
1318      obj.foo_return
1319      set_trace_func(nil)
1320
1321      assert_equal([['call', :foo_return], ['return', :foo_return]], events, 'Bug #11492')
1322    ensure
1323    end
1324
1325    # break
1326    events = []
1327    begin
1328      set_trace_func(lambda{|event, file, lineno, mid, binding, klass|
1329        next unless target_thread?
1330        case event
1331        when 'call', 'return'
1332          events << [event, mid]
1333        end
1334      })
1335      obj.foo_break
1336      set_trace_func(nil)
1337
1338      assert_equal([['call', :foo_break], ['return', :foo_break]], events, 'Bug #11492')
1339    ensure
1340    end
1341  end
1342
1343  def test_recursive
1344    assert_in_out_err([], %q{\
1345      TracePoint.new(:c_call){|tp|
1346        p tp.method_id
1347      }.enable{
1348        p 1
1349      }
1350    }, %w[:p :to_s 1], [], '[Bug #9940]')
1351  end
1352
1353  def method_prefix event
1354    case event
1355    when :call, :return
1356      :n
1357    when :c_call, :c_return
1358      :c
1359    when :b_call, :b_return
1360      :b
1361    end
1362  end
1363
1364  def method_label tp
1365    "#{method_prefix(tp.event)}##{tp.method_id}"
1366  end
1367
1368  def assert_consistent_call_return message='', check_events: nil
1369    check_events ||= %i(a_call a_return)
1370    call_stack = []
1371
1372    TracePoint.new(*check_events){|tp|
1373      next unless target_thread?
1374
1375      case tp.event.to_s
1376      when /call/
1377        call_stack << method_label(tp)
1378      when /return/
1379        frame = call_stack.pop
1380        assert_equal(frame, method_label(tp))
1381      end
1382    }.enable do
1383      yield
1384    end
1385
1386    assert_equal true, call_stack.empty?
1387  end
1388
1389  def method_test_rescue_should_not_cause_b_return
1390    begin
1391      raise
1392    rescue
1393      return
1394    end
1395  end
1396
1397  def method_test_ensure_should_not_cause_b_return
1398    begin
1399      raise
1400    ensure
1401      return
1402    end
1403  end
1404
1405  def test_rescue_and_ensure_should_not_cause_b_return
1406    assert_consistent_call_return '[Bug #9957]' do
1407      method_test_rescue_should_not_cause_b_return
1408      begin
1409        method_test_ensure_should_not_cause_b_return
1410      rescue
1411        # ignore
1412      end
1413    end
1414  end
1415
1416  define_method(:method_test_argument_error_on_bmethod){|correct_key: 1|}
1417
1418  def test_argument_error_on_bmethod
1419    assert_consistent_call_return '[Bug #9959]' do
1420      begin
1421        method_test_argument_error_on_bmethod(wrong_key: 2)
1422      rescue
1423        # ignore
1424      end
1425    end
1426  end
1427
1428  def test_rb_rescue
1429    assert_consistent_call_return '[Bug #9961]' do
1430      begin
1431        -Numeric.new
1432      rescue
1433        # ignore
1434      end
1435    end
1436  end
1437
1438  def test_b_call_with_redo
1439    assert_consistent_call_return '[Bug #9964]' do
1440      i = 0
1441      1.times{
1442        break if (i+=1) > 10
1443        redo
1444      }
1445    end
1446  end
1447
1448  def test_no_duplicate_line_events
1449    lines = []
1450    dummy = []
1451
1452    TracePoint.new(:line){|tp|
1453      next unless target_thread?
1454      lines << tp.lineno
1455    }.enable{
1456      dummy << (1) + (2)
1457      dummy << (1) + (2)
1458    }
1459    assert_equal [__LINE__ - 3, __LINE__ - 2], lines, 'Bug #10449'
1460  end
1461
1462  def test_elsif_line_event
1463    bug10763 = '[ruby-core:67720] [Bug #10763]'
1464    lines = []
1465    line = nil
1466
1467    TracePoint.new(:line){|tp|
1468      next unless target_thread?
1469      lines << tp.lineno if line
1470    }.enable{
1471      line = __LINE__
1472      if !line
1473        1
1474      elsif line
1475        2
1476      end
1477    }
1478    assert_equal [line+1, line+3, line+4], lines, bug10763
1479  end
1480
1481  class Bug10724
1482    def initialize
1483      loop{return}
1484    end
1485  end
1486
1487  def test_throwing_return_with_finish_frame
1488    target_th = Thread.current
1489    evs = []
1490
1491    TracePoint.new(:call, :return){|tp|
1492      next unless target_thread?
1493      evs << tp.event
1494    }.enable{
1495      Bug10724.new
1496    }
1497
1498    assert_equal([:call, :return], evs)
1499  end
1500
1501  require 'fiber'
1502  def test_fiber_switch
1503    # test for resume/yield
1504    evs = []
1505    TracePoint.new(:fiber_switch){|tp|
1506      next unless target_thread?
1507      evs << tp.event
1508    }.enable{
1509      f = Fiber.new{
1510        Fiber.yield
1511        Fiber.yield
1512        Fiber.yield
1513      }
1514      f.resume
1515      f.resume
1516      f.resume
1517      f.resume
1518      begin
1519        f.resume
1520      rescue FiberError
1521      end
1522    }
1523    assert_equal 8, evs.size
1524    evs.each{|ev|
1525      assert_equal ev, :fiber_switch
1526    }
1527
1528    # test for transfer
1529    evs = []
1530    TracePoint.new(:fiber_switch){|tp|
1531      next unless target_thread?
1532      evs << tp.event
1533    }.enable{
1534      f1 = f2 = nil
1535      f1 = Fiber.new{
1536        f2.transfer
1537        f2.transfer
1538        Fiber.yield :ok
1539      }
1540      f2 = Fiber.new{
1541        f1.transfer
1542        f1.transfer
1543      }
1544      assert_equal :ok, f1.resume
1545    }
1546    assert_equal 6, evs.size
1547    evs.each{|ev|
1548      assert_equal ev, :fiber_switch
1549    }
1550  end
1551
1552  def test_tracepoint_callee_id
1553    events = []
1554    capture_events = Proc.new{|tp|
1555      next unless target_thread?
1556      events << [tp.event, tp.method_id, tp.callee_id]
1557    }
1558
1559    o = Class.new{
1560      def m
1561        raise
1562      end
1563      alias alias_m m
1564    }.new
1565    TracePoint.new(:raise, :call, :return, &capture_events).enable{
1566      o.alias_m rescue nil
1567    }
1568    assert_equal [[:call, :m, :alias_m], [:raise, :m, :alias_m], [:return, :m, :alias_m]], events
1569    events.clear
1570
1571    o = Class.new{
1572      alias alias_raise raise
1573      def m
1574        alias_raise
1575      end
1576    }.new
1577    TracePoint.new(:c_return, &capture_events).enable{
1578      o.m rescue nil
1579    }
1580    assert_equal [:c_return, :raise, :alias_raise], events[0]
1581    events.clear
1582
1583    o = Class.new(String){
1584      include Enumerable
1585      alias each each_char
1586    }.new('foo')
1587    TracePoint.new(:c_return, &capture_events).enable{
1588      o.find{true}
1589    }
1590    assert_equal [:c_return, :each_char, :each], events[0]
1591    events.clear
1592
1593    o = Class.new{
1594      define_method(:m){}
1595      alias alias_m m
1596    }.new
1597    TracePoint.new(:call, :return, &capture_events).enable{
1598      o.alias_m
1599    }
1600    assert_equal [[:call, :m, :alias_m], [:return, :m, :alias_m]], events
1601    events.clear
1602
1603    o = Class.new{
1604      def m
1605        tap{return}
1606      end
1607      alias alias_m m
1608    }.new
1609    TracePoint.new(:return, &capture_events).enable{
1610      o.alias_m
1611    }
1612    assert_equal [[:return, :m, :alias_m]], events
1613    events.clear
1614
1615    o = Class.new{
1616      define_method(:m){raise}
1617      alias alias_m m
1618    }.new
1619    TracePoint.new(:b_return, :return, &capture_events).enable{
1620      o.alias_m rescue nil
1621    }
1622    assert_equal [[:b_return, :m, :alias_m], [:return, :m, :alias_m]], events[0..1]
1623    events.clear
1624
1625    o = Class.new{
1626      define_method(:m){tap{return}}
1627      alias alias_m m
1628    }.new
1629    TracePoint.new(:b_return, &capture_events).enable{
1630      o.alias_m
1631    }
1632    assert_equal [[:b_return, :m, :alias_m], [:b_return, :m, :alias_m]], events[0..1]
1633    events.clear
1634
1635    o = Class.new{
1636      alias alias_tap tap
1637      define_method(:m){alias_tap{return}}
1638    }.new
1639    TracePoint.new(:c_return, &capture_events).enable{
1640      o.m
1641    }
1642    assert_equal [[:c_return, :tap, :alias_tap]], events
1643    events.clear
1644
1645    c = Class.new{
1646      alias initialize itself
1647    }
1648    TracePoint.new(:c_call, &capture_events).enable{
1649      c.new
1650    }
1651    assert_equal [:c_call, :itself, :initialize], events[1]
1652    events.clear
1653
1654    o = Class.new{
1655      alias alias_itself itself
1656    }.new
1657    TracePoint.new(:c_call, :c_return, &capture_events).enable{
1658      o.alias_itself
1659    }
1660    assert_equal [[:c_call, :itself, :alias_itself], [:c_return, :itself, :alias_itself]], events
1661    events.clear
1662  end
1663
1664  # tests for `return_value` with non-local exit [Bug #13369]
1665
1666  def tp_return_value mid
1667    ary = []
1668    TracePoint.new(:return, :b_return){|tp| next if !target_thread?; ary << [tp.event, tp.method_id, tp.return_value]}.enable{
1669      send mid
1670    }
1671    ary.pop # last b_return event is not required.
1672    ary
1673  end
1674
1675  def f_raise
1676    raise
1677  rescue
1678    return :f_raise_return
1679  end
1680
1681  def f_iter1
1682    yield
1683    return :f_iter1_return
1684  end
1685
1686  def f_iter2
1687    yield
1688    return :f_iter2_return
1689  end
1690
1691  def f_return_in_iter
1692    f_iter1 do
1693      f_iter2 do
1694        return :f_return_in_iter_return
1695      end
1696    end
1697    2
1698  end
1699
1700  def f_break_in_iter
1701    f_iter1 do
1702      f_iter2 do
1703        break :f_break_in_iter_break
1704      end
1705      :f_iter1_block_value
1706    end
1707    :f_break_in_iter_return
1708  end
1709
1710  def test_return_value_with_rescue
1711    assert_equal [[:return,   :f_raise,          :f_raise_return]],
1712                 tp_return_value(:f_raise),
1713                 '[Bug #13369]'
1714
1715    assert_equal [[:b_return, :f_return_in_iter, nil],
1716                  [:return,   :f_iter2,          nil],
1717                  [:b_return, :f_return_in_iter, nil],
1718                  [:return,   :f_iter1,          nil],
1719                  [:return,   :f_return_in_iter, :f_return_in_iter_return]],
1720                 tp_return_value(:f_return_in_iter),
1721                 '[Bug #13369]'
1722
1723    assert_equal [[:b_return, :f_break_in_iter,  :f_break_in_iter_break],
1724                  [:return,   :f_iter2,          nil],
1725                  [:b_return, :f_break_in_iter,  :f_iter1_block_value],
1726                  [:return,   :f_iter1,          :f_iter1_return],
1727                  [:return,   :f_break_in_iter,  :f_break_in_iter_return]],
1728                 tp_return_value(:f_break_in_iter),
1729                 '[Bug #13369]'
1730  end
1731
1732  define_method(:f_last_defined) do
1733    :f_last_defined
1734  end
1735
1736  define_method(:f_return_defined) do
1737    return :f_return_defined
1738  end
1739
1740  define_method(:f_break_defined) do
1741    return :f_break_defined
1742  end
1743
1744  define_method(:f_raise_defined) do
1745    raise
1746  rescue
1747    return :f_raise_defined
1748  end
1749
1750  define_method(:f_break_in_rescue_defined) do
1751    raise
1752  rescue
1753    break :f_break_in_rescue_defined
1754  end
1755
1756  def test_return_value_with_rescue_and_defined_methods
1757    assert_equal [[:b_return, :f_last_defined, :f_last_defined],
1758                  [:return,   :f_last_defined, :f_last_defined]],
1759                 tp_return_value(:f_last_defined),
1760                 '[Bug #13369]'
1761
1762    assert_equal [[:b_return, :f_return_defined, nil], # current limitation
1763                  [:return,   :f_return_defined, :f_return_defined]],
1764                 tp_return_value(:f_return_defined),
1765                 '[Bug #13369]'
1766
1767    assert_equal [[:b_return, :f_break_defined, nil],
1768                  [:return,   :f_break_defined, :f_break_defined]],
1769                 tp_return_value(:f_break_defined),
1770                 '[Bug #13369]'
1771
1772    assert_equal [[:b_return, :f_raise_defined, nil],
1773                  [:return,   :f_raise_defined, f_raise_defined]],
1774                 tp_return_value(:f_raise_defined),
1775                 '[Bug #13369]'
1776
1777    assert_equal [[:b_return, :f_break_in_rescue_defined, nil],
1778                  [:return,   :f_break_in_rescue_defined, f_break_in_rescue_defined]],
1779                 tp_return_value(:f_break_in_rescue_defined),
1780                 '[Bug #13369]'
1781  end
1782
1783  def f_iter
1784    yield
1785  end
1786
1787  def f_break_in_rescue
1788    f_iter do
1789      begin
1790        raise
1791      rescue
1792        break :b
1793      end
1794    end
1795    :f_break_in_rescue_return_value
1796  end
1797
1798  def test_break_with_rescue
1799    assert_equal [[:b_return, :f_break_in_rescue, :b],
1800                  [:return, :f_iter, nil],
1801                  [:return, :f_break_in_rescue, :f_break_in_rescue_return_value]],
1802                 tp_return_value(:f_break_in_rescue),
1803                 '[Bug #13369]'
1804  end
1805
1806  def test_trace_point_raising_exception_in_bmethod_call
1807    bug13705 = '[ruby-dev:50162]'
1808    assert_normal_exit %q{
1809      define_method(:m) {}
1810
1811      tp = TracePoint.new(:call) do
1812        raise ''
1813      end
1814
1815      tap do
1816        tap do
1817          begin
1818            tp.enable
1819            m
1820          rescue
1821          end
1822        end
1823      end
1824    }, bug13705
1825  end
1826
1827  def test_trace_point_require_block
1828    assert_raise(ArgumentError) { TracePoint.new(:return) }
1829  end
1830
1831  def method_for_test_thread_add_trace_func
1832
1833  end
1834
1835  def test_thread_add_trace_func
1836    events = []
1837    base_line = __LINE__
1838    q = Queue.new
1839    t = Thread.new{
1840      Thread.current.add_trace_func proc{|ev, file, line, *args|
1841        events << [ev, line]
1842      } # do not stop trace. They will be stopped at Thread termination.
1843      q.push 1
1844      _x = 1
1845      method_for_test_thread_add_trace_func
1846      _y = 2
1847    }
1848    q.pop
1849    method_for_test_thread_add_trace_func
1850    t.join
1851    assert_equal ["c-return", base_line + 3], events[0]
1852    assert_equal ["line", base_line + 6],     events[1]
1853    assert_equal ["c-call", base_line + 6],   events[2]
1854    assert_equal ["c-return", base_line + 6], events[3]
1855    assert_equal ["line", base_line + 7],     events[4]
1856    assert_equal ["line", base_line + 8],     events[5]
1857    assert_equal ["call", base_line + -6],    events[6]
1858    assert_equal ["return", base_line + -4],  events[7]
1859    assert_equal ["line", base_line + 9],     events[8]
1860    assert_equal nil,                         events[9]
1861
1862    # other thread
1863    events = []
1864    m2t_q = Queue.new
1865
1866    t = Thread.new{
1867      Thread.current.abort_on_exception = true
1868      assert_equal 1, m2t_q.pop
1869      _x = 1
1870      method_for_test_thread_add_trace_func
1871      _y = 2
1872      Thread.current.set_trace_func(nil)
1873      method_for_test_thread_add_trace_func
1874    }
1875    # it is dirty hack. usually we shouldn't use such technique
1876    Thread.pass until t.status == 'sleep'
1877    # When MJIT thread exists, t.status becomes 'sleep' even if it does not reach m2t_q.pop.
1878    # This sleep forces it to reach m2t_q.pop for --jit-wait.
1879    sleep 1 if RubyVM::MJIT.enabled?
1880
1881    t.add_trace_func proc{|ev, file, line, *args|
1882      if file == __FILE__
1883        events << [ev, line]
1884      end
1885    }
1886
1887    method_for_test_thread_add_trace_func
1888
1889    m2t_q.push 1
1890    t.join
1891
1892    assert_equal ["c-return", base_line + 31], events[0]
1893    assert_equal ["line", base_line + 32],     events[1]
1894    assert_equal ["line", base_line + 33],     events[2]
1895    assert_equal ["call", base_line + -6],     events[3]
1896    assert_equal ["return", base_line + -4],   events[4]
1897    assert_equal ["line", base_line + 34],     events[5]
1898    assert_equal ["line", base_line + 35],     events[6]
1899    assert_equal ["c-call", base_line + 35],   events[7] # Thread.current
1900    assert_equal ["c-return", base_line + 35], events[8] # Thread.current
1901    assert_equal ["c-call", base_line + 35],   events[9] # Thread#set_trace_func
1902    assert_equal nil,                          events[10]
1903  end
1904
1905  def test_lineno_in_optimized_insn
1906    actual, _, _ = EnvUtil.invoke_ruby [], <<-EOF.gsub(/^.*?: */, ""), true
1907      1: class String
1908      2:   def -@
1909      3:     puts caller_locations(1, 1)[0].lineno
1910      4:   end
1911      5: end
1912      6:
1913      7: -""
1914    EOF
1915    assert_equal "7\n", actual, '[Bug #14809]'
1916  end
1917
1918  def method_for_enable_target1
1919    a = 1
1920    b = 2
1921    1.times{|i|
1922      x = i
1923    }
1924    c = a + b
1925  end
1926
1927  def method_for_enable_target2
1928    a = 1
1929    b = 2
1930    1.times{|i|
1931      x = i
1932    }
1933    c = a + b
1934  end
1935
1936  def check_with_events *trace_events
1937    all_events = [[:call, :method_for_enable_target1],
1938                  [:line, :method_for_enable_target1],
1939                  [:line, :method_for_enable_target1],
1940                  [:line, :method_for_enable_target1],
1941                  [:b_call, :method_for_enable_target1],
1942                  [:line, :method_for_enable_target1],
1943                  [:b_return, :method_for_enable_target1],
1944                  [:line, :method_for_enable_target1],
1945                  [:return, :method_for_enable_target1],
1946                  # repeat
1947                  [:call, :method_for_enable_target1],
1948                  [:line, :method_for_enable_target1],
1949                  [:line, :method_for_enable_target1],
1950                  [:line, :method_for_enable_target1],
1951                  [:b_call, :method_for_enable_target1],
1952                  [:line, :method_for_enable_target1],
1953                  [:b_return, :method_for_enable_target1],
1954                  [:line, :method_for_enable_target1],
1955                  [:return, :method_for_enable_target1],
1956                 ]
1957    events = []
1958    TracePoint.new(*trace_events) do |tp|
1959      next unless target_thread?
1960      events << [tp.event, tp.method_id]
1961    end.enable(target: method(:method_for_enable_target1)) do
1962      method_for_enable_target1
1963      method_for_enable_target2
1964      method_for_enable_target1
1965    end
1966    assert_equal all_events.find_all{|(ev, m)| trace_events.include? ev}, events
1967  end
1968
1969  def test_tracepoint_enable_target
1970    check_with_events :line
1971    check_with_events :call, :return
1972    check_with_events :line, :call, :return
1973    check_with_events :call, :return, :b_call, :b_return
1974    check_with_events :line, :call, :return, :b_call, :b_return
1975  end
1976
1977  def test_tracepoint_nested_enabled_with_target
1978    code1 = proc{
1979      a = 1
1980    }
1981    code2 = proc{
1982      b = 2
1983    }
1984
1985    ## error
1986
1987    # targetted TP and targetted TP
1988    ex = assert_raise(ArgumentError) do
1989      tp = TracePoint.new(:line){}
1990      tp.enable(target: code1){
1991        tp.enable(target: code2){}
1992      }
1993    end
1994    assert_equal "can't nest-enable a targetting TracePoint", ex.message
1995
1996    # global TP and targetted TP
1997    ex = assert_raise(ArgumentError) do
1998      tp = TracePoint.new(:line){}
1999      tp.enable{
2000        tp.enable(target: code2){}
2001      }
2002    end
2003    assert_equal "can't nest-enable a targetting TracePoint", ex.message
2004
2005    # targetted TP and global TP
2006    ex = assert_raise(ArgumentError) do
2007      tp = TracePoint.new(:line){}
2008      tp.enable(target: code1){
2009        tp.enable{}
2010      }
2011    end
2012    assert_equal "can't nest-enable a targetting TracePoint", ex.message
2013
2014    # targetted TP and disable
2015    ex = assert_raise(ArgumentError) do
2016      tp = TracePoint.new(:line){}
2017      tp.enable(target: code1){
2018        tp.disable{}
2019      }
2020    end
2021    assert_equal "can't disable a targetting TracePoint in a block", ex.message
2022
2023    ## success with two nesting targetting tracepoints
2024    events = []
2025    tp1 = TracePoint.new(:line){|tp| events << :tp1}
2026    tp2 = TracePoint.new(:line){|tp| events << :tp2}
2027    tp1.enable(target: code1) do
2028      tp2.enable(target: code1) do
2029        code1.call
2030        events << :___
2031      end
2032    end
2033    assert_equal [:tp2, :tp1, :___], events
2034
2035    # succss with two tracepoints (global/targetting)
2036    events = []
2037    tp1 = TracePoint.new(:line){|tp| events << :tp1}
2038    tp2 = TracePoint.new(:line){|tp| events << :tp2}
2039    tp1.enable do
2040      tp2.enable(target: code1) do
2041        code1.call
2042        events << :___
2043      end
2044    end
2045    assert_equal [:tp1, :tp1, :tp1, :tp1, :tp2, :tp1, :___], events
2046
2047    # succss with two tracepoints (targetting/global)
2048    events = []
2049    tp1 = TracePoint.new(:line){|tp| events << :tp1}
2050    tp2 = TracePoint.new(:line){|tp| events << :tp2}
2051    tp1.enable(target: code1) do
2052      tp2.enable do
2053        code1.call
2054        events << :___
2055      end
2056    end
2057    assert_equal [:tp2, :tp2, :tp1, :tp2, :___], events
2058  end
2059
2060  def test_tracepoint_enable_with_target_line
2061    events = []
2062    line_0 = __LINE__
2063    code1 = proc{
2064      events << 1
2065      events << 2
2066      events << 3
2067    }
2068    tp = TracePoint.new(:line) do |tp|
2069      events << :tp
2070    end
2071    tp.enable(target: code1, target_line: line_0 + 3) do
2072      code1.call
2073    end
2074    assert_equal [1, :tp, 2, 3], events
2075
2076
2077    e = assert_raise(ArgumentError) do
2078      TracePoint.new(:line){}.enable(target_line: 10){}
2079    end
2080    assert_equal 'only target_line is specified', e.message
2081
2082    e = assert_raise(ArgumentError) do
2083      TracePoint.new(:call){}.enable(target: code1, target_line: 10){}
2084    end
2085    assert_equal 'target_line is specified, but line event is not specified', e.message
2086  end
2087
2088  def test_script_compiled
2089    events = []
2090    tp = TracePoint.new(:script_compiled){|tp|
2091      next unless target_thread?
2092      events << [tp.instruction_sequence.path,
2093                 tp.eval_script]
2094    }
2095
2096    eval_script = 'a = 1'
2097    tp.enable{
2098      eval(eval_script, nil, __FILE__+"/eval")
2099      nil.instance_eval(eval_script, __FILE__+"/instance_eval")
2100      Object.class_eval(eval_script, __FILE__+"/class_eval")
2101    }
2102    assert_equal [[__FILE__+"/eval", eval_script],
2103                  [__FILE__+"/instance_eval", eval_script],
2104                  [__FILE__+"/class_eval", eval_script],
2105                 ], events
2106    events.clear
2107
2108    # TODO: test for requires
2109    return
2110
2111    tp.enable{
2112      require ''
2113      require_relative ''
2114      load ''
2115    }
2116    assert_equal [], events
2117  end
2118
2119  def test_return_event_with_rescue
2120    obj = Object.new
2121    def obj.example
2122      1 if 1 == 1
2123    rescue
2124    end
2125    ok = false
2126    tp = TracePoint.new(:return) {ok = true}
2127    tp.enable {obj.example}
2128    assert ok, "return event should be emitted"
2129  end
2130
2131  def test_disable_local_tracepoint_in_trace
2132    assert_normal_exit <<-EOS
2133    def foo
2134      trace = TracePoint.new(:b_return){|tp|
2135        tp.disable
2136      }
2137      trace.enable(target: method(:bar))
2138    end
2139    def bar
2140      100.times{|i|
2141        foo; foo
2142      }
2143    end
2144    bar
2145    EOS
2146  end
2147end
2148