1require 'test/unit'
2require_relative 'envutil'
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  end
12
13  def teardown
14    set_trace_func(nil)
15    RubyVM::InstructionSequence.compile_option = @original_compile_option
16  end
17
18  def test_c_call
19    events = []
20    name = "#{self.class}\##{__method__}"
21    eval <<-EOF.gsub(/^.*?: /, ""), nil, name
22     1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
23     2:   events << [event, lineno, mid, klass] if file == name
24     3: })
25     4: x = 1 + 1
26     5: set_trace_func(nil)
27    EOF
28    assert_equal(["c-return", 1, :set_trace_func, Kernel],
29                 events.shift)
30    assert_equal(["line", 4, __method__, self.class],
31                 events.shift)
32    assert_equal(["c-call", 4, :+, Fixnum],
33                 events.shift)
34    assert_equal(["c-return", 4, :+, Fixnum],
35                 events.shift)
36    assert_equal(["line", 5, __method__, self.class],
37                 events.shift)
38    assert_equal(["c-call", 5, :set_trace_func, Kernel],
39                 events.shift)
40    assert_equal([], events)
41  end
42
43  def test_call
44    events = []
45    name = "#{self.class}\##{__method__}"
46    eval <<-EOF.gsub(/^.*?: /, ""), nil, name
47     1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
48     2:   events << [event, lineno, mid, klass] if file == name
49     3: })
50     4: def add(x, y)
51     5:   x + y
52     6: end
53     7: x = add(1, 1)
54     8: set_trace_func(nil)
55    EOF
56    assert_equal(["c-return", 1, :set_trace_func, Kernel],
57                 events.shift)
58    assert_equal(["line", 4, __method__, self.class],
59                 events.shift)
60    assert_equal(["c-call", 4, :method_added, self.class],
61                 events.shift)
62    assert_equal(["c-return", 4, :method_added, self.class],
63                 events.shift)
64    assert_equal(["line", 7, __method__, self.class],
65                 events.shift)
66    assert_equal(["call", 4, :add, self.class],
67                 events.shift)
68    assert_equal(["line", 5, :add, self.class],
69                 events.shift)
70    assert_equal(["c-call", 5, :+, Fixnum],
71                 events.shift)
72    assert_equal(["c-return", 5, :+, Fixnum],
73                 events.shift)
74    assert_equal(["return", 6, :add, self.class],
75                 events.shift)
76    assert_equal(["line", 8, __method__, self.class],
77                 events.shift)
78    assert_equal(["c-call", 8, :set_trace_func, Kernel],
79                 events.shift)
80    assert_equal([], events)
81  end
82
83  def test_class
84    events = []
85    name = "#{self.class}\##{__method__}"
86    eval <<-EOF.gsub(/^.*?: /, ""), nil, name
87     1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
88     2:   events << [event, lineno, mid, klass] if file == name
89     3: })
90     4: class Foo
91     5:   def bar
92     6:   end
93     7: end
94     8: x = Foo.new.bar
95     9: set_trace_func(nil)
96    EOF
97    assert_equal(["c-return", 1, :set_trace_func, Kernel],
98                 events.shift)
99    assert_equal(["line", 4, __method__, self.class],
100                 events.shift)
101    assert_equal(["c-call", 4, :inherited, Class],
102                 events.shift)
103    assert_equal(["c-return", 4, :inherited, Class],
104                 events.shift)
105    assert_equal(["class", 4, nil, nil],
106                 events.shift)
107    assert_equal(["line", 5, nil, nil],
108                 events.shift)
109    assert_equal(["c-call", 5, :method_added, Module],
110                 events.shift)
111    assert_equal(["c-return", 5, :method_added, Module],
112                 events.shift)
113    assert_equal(["end", 7, nil, nil],
114                 events.shift)
115    assert_equal(["line", 8, __method__, self.class],
116                 events.shift)
117    assert_equal(["c-call", 8, :new, Class],
118                 events.shift)
119    assert_equal(["c-call", 8, :initialize, BasicObject],
120                 events.shift)
121    assert_equal(["c-return", 8, :initialize, BasicObject],
122                 events.shift)
123    assert_equal(["c-return", 8, :new, Class],
124                 events.shift)
125    assert_equal(["call", 5, :bar, Foo],
126                 events.shift)
127    assert_equal(["return", 6, :bar, Foo],
128                 events.shift)
129    assert_equal(["line", 9, __method__, self.class],
130                 events.shift)
131    assert_equal(["c-call", 9, :set_trace_func, Kernel],
132                 events.shift)
133    assert_equal([], events)
134  end
135
136  def test_return # [ruby-dev:38701]
137    events = []
138    name = "#{self.class}\##{__method__}"
139    eval <<-EOF.gsub(/^.*?: /, ""), nil, name
140     1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
141     2:   events << [event, lineno, mid, klass] if file == name
142     3: })
143     4: def meth_return(a)
144     5:   return if a
145     6:   return
146     7: end
147     8: meth_return(true)
148     9: meth_return(false)
149    10: set_trace_func(nil)
150    EOF
151    assert_equal(["c-return", 1, :set_trace_func, Kernel],
152                 events.shift)
153    assert_equal(["line", 4, __method__, self.class],
154                 events.shift)
155    assert_equal(["c-call", 4, :method_added, self.class],
156                 events.shift)
157    assert_equal(["c-return", 4, :method_added, self.class],
158                 events.shift)
159    assert_equal(["line", 8, __method__, self.class],
160                 events.shift)
161    assert_equal(["call", 4, :meth_return, self.class],
162                 events.shift)
163    assert_equal(["line", 5, :meth_return, self.class],
164                 events.shift)
165    assert_equal(["return", 5, :meth_return, self.class],
166                 events.shift)
167    assert_equal(["line", 9, :test_return, self.class],
168                 events.shift)
169    assert_equal(["call", 4, :meth_return, self.class],
170                 events.shift)
171    assert_equal(["line", 5, :meth_return, self.class],
172                 events.shift)
173    assert_equal(["return", 7, :meth_return, self.class],
174                 events.shift)
175    assert_equal(["line", 10, :test_return, self.class],
176                 events.shift)
177    assert_equal(["c-call", 10, :set_trace_func, Kernel],
178                 events.shift)
179    assert_equal([], events)
180  end
181
182  def test_return2 # [ruby-core:24463]
183    events = []
184    name = "#{self.class}\##{__method__}"
185    eval <<-EOF.gsub(/^.*?: /, ""), nil, name
186     1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
187     2:   events << [event, lineno, mid, klass] if file == name
188     3: })
189     4: def meth_return2
190     5:   a = 5
191     6:   return a
192     7: end
193     8: meth_return2
194     9: set_trace_func(nil)
195    EOF
196    assert_equal(["c-return", 1, :set_trace_func, Kernel],
197                 events.shift)
198    assert_equal(["line", 4, __method__, self.class],
199                 events.shift)
200    assert_equal(["c-call", 4, :method_added, self.class],
201                 events.shift)
202    assert_equal(["c-return", 4, :method_added, self.class],
203                 events.shift)
204    assert_equal(["line", 8, __method__, self.class],
205                 events.shift)
206    assert_equal(["call", 4, :meth_return2, self.class],
207                 events.shift)
208    assert_equal(["line", 5, :meth_return2, self.class],
209                 events.shift)
210    assert_equal(["line", 6, :meth_return2, self.class],
211                 events.shift)
212    assert_equal(["return", 7, :meth_return2, self.class],
213                 events.shift)
214    assert_equal(["line", 9, :test_return2, self.class],
215                 events.shift)
216    assert_equal(["c-call", 9, :set_trace_func, Kernel],
217                 events.shift)
218    assert_equal([], events)
219  end
220
221  def test_raise
222    events = []
223    name = "#{self.class}\##{__method__}"
224    eval <<-EOF.gsub(/^.*?: /, ""), nil, name
225     1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
226     2:   events << [event, lineno, mid, klass] if file == name
227     3: })
228     4: begin
229     5:   raise TypeError, "error"
230     6: rescue TypeError
231     7: end
232     8: set_trace_func(nil)
233    EOF
234    assert_equal(["c-return", 1, :set_trace_func, Kernel],
235                 events.shift)
236    assert_equal(["line", 4, __method__, self.class],
237                 events.shift)
238    assert_equal(["line", 5, __method__, self.class],
239                 events.shift)
240    assert_equal(["c-call", 5, :raise, Kernel],
241                 events.shift)
242    assert_equal(["c-call", 5, :exception, Exception],
243                 events.shift)
244    assert_equal(["c-call", 5, :initialize, Exception],
245                 events.shift)
246    assert_equal(["c-return", 5, :initialize, Exception],
247                 events.shift)
248    assert_equal(["c-return", 5, :exception, Exception],
249                 events.shift)
250    assert_equal(["c-call", 5, :backtrace, Exception],
251                 events.shift)
252    assert_equal(["c-return", 5, :backtrace, Exception],
253                 events.shift)
254    assert_equal(["raise", 5, :test_raise, TestSetTraceFunc],
255                 events.shift)
256    assert_equal(["c-return", 5, :raise, Kernel],
257                 events.shift)
258    assert_equal(["c-call", 6, :===, Module],
259                 events.shift)
260    assert_equal(["c-return", 6, :===, Module],
261                 events.shift)
262    assert_equal(["line", 8, __method__, self.class],
263                 events.shift)
264    assert_equal(["c-call", 8, :set_trace_func, Kernel],
265                 events.shift)
266    assert_equal([], events)
267  end
268
269  def test_break # [ruby-core:27606] [Bug #2610]
270    events = []
271    name = "#{self.class}\##{__method__}"
272    eval <<-EOF.gsub(/^.*?: /, ""), nil, name
273     1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
274     2:   events << [event, lineno, mid, klass] if file == name
275     3: })
276     4: [1,2,3].any? {|n| n}
277     8: set_trace_func(nil)
278    EOF
279
280    [["c-return", 1, :set_trace_func, Kernel],
281     ["line", 4, __method__, self.class],
282     ["c-call", 4, :any?, Enumerable],
283     ["c-call", 4, :each, Array],
284     ["line", 4, __method__, self.class],
285     ["c-return", 4, :each, Array],
286     ["c-return", 4, :any?, Enumerable],
287     ["line", 5, __method__, self.class],
288     ["c-call", 5, :set_trace_func, Kernel]].each{|e|
289      assert_equal(e, events.shift)
290    }
291  end
292
293  def test_invalid_proc
294      assert_raise(TypeError) { set_trace_func(1) }
295  end
296
297  def test_raise_in_trace
298    set_trace_func proc {raise rescue nil}
299    assert_equal(42, (raise rescue 42), '[ruby-core:24118]')
300  end
301
302  def test_thread_trace
303    events = {:set => [], :add => []}
304    prc = Proc.new { |event, file, lineno, mid, binding, klass|
305      events[:set] << [event, lineno, mid, klass, :set]
306    }
307    prc = prc # suppress warning
308    prc2 = Proc.new { |event, file, lineno, mid, binding, klass|
309      events[:add] << [event, lineno, mid, klass, :add]
310    }
311    prc2 = prc2 # suppress warning
312
313    th = Thread.new do
314      th = Thread.current
315      name = "#{self.class}\##{__method__}"
316      eval <<-EOF.gsub(/^.*?: /, ""), nil, name
317       1: th.set_trace_func(prc)
318       2: th.add_trace_func(prc2)
319       3: class ThreadTraceInnerClass
320       4:   def foo
321       5:     _x = 1 + 1
322       6:   end
323       7: end
324       8: ThreadTraceInnerClass.new.foo
325       9: th.set_trace_func(nil)
326      EOF
327    end
328    th.join
329
330    [["c-return", 1, :set_trace_func, Thread, :set],
331     ["line", 2, __method__, self.class, :set],
332     ["c-call", 2, :add_trace_func, Thread, :set]].each do |e|
333      assert_equal(e, events[:set].shift)
334    end
335
336    [["c-return", 2, :add_trace_func, Thread],
337     ["line", 3, __method__, self.class],
338     ["c-call", 3, :inherited, Class],
339     ["c-return", 3, :inherited, Class],
340     ["class", 3, nil, nil],
341     ["line", 4, nil, nil],
342     ["c-call", 4, :method_added, Module],
343     ["c-return", 4, :method_added, Module],
344     ["end", 7, nil, nil],
345     ["line", 8, __method__, self.class],
346     ["c-call", 8, :new, Class],
347     ["c-call", 8, :initialize, BasicObject],
348     ["c-return", 8, :initialize, BasicObject],
349     ["c-return", 8, :new, Class],
350     ["call", 4, :foo, ThreadTraceInnerClass],
351     ["line", 5, :foo, ThreadTraceInnerClass],
352     ["c-call", 5, :+, Fixnum],
353     ["c-return", 5, :+, Fixnum],
354     ["return", 6, :foo, ThreadTraceInnerClass],
355     ["line", 9, __method__, self.class],
356     ["c-call", 9, :set_trace_func, Thread]].each do |e|
357      [:set, :add].each do |type|
358        assert_equal(e + [type], events[type].shift)
359      end
360    end
361    assert_equal([], events[:set])
362    assert_equal([], events[:add])
363  end
364
365  def test_trace_defined_method
366    events = []
367    name = "#{self.class}\##{__method__}"
368    eval <<-EOF.gsub(/^.*?: /, ""), nil, name
369     1: class FooBar; define_method(:foobar){}; end
370     2: fb = FooBar.new
371     3: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
372     4:   events << [event, lineno, mid, klass] if file == name
373     5: })
374     6: fb.foobar
375     7: set_trace_func(nil)
376    EOF
377
378    [["c-return", 3, :set_trace_func, Kernel],
379     ["line", 6, __method__, self.class],
380     ["call", 6, :foobar, FooBar],
381     ["return", 6, :foobar, FooBar],
382     ["line", 7, __method__, self.class],
383     ["c-call", 7, :set_trace_func, Kernel]].each{|e|
384      assert_equal(e, events.shift)
385    }
386  end
387
388  def test_remove_in_trace
389    bug3921 = '[ruby-dev:42350]'
390    ok = false
391    func = lambda{|e, f, l, i, b, k|
392      set_trace_func(nil)
393      ok = eval("self", b)
394    }
395
396    set_trace_func(func)
397    assert_equal(self, ok, bug3921)
398  end
399
400  def assert_security_error_safe4(block)
401    assert_raise(SecurityError) do
402      block.call
403    end
404  end
405
406  def test_set_safe4
407    func = proc do
408      $SAFE = 4
409      set_trace_func(lambda {|*|})
410    end
411    assert_security_error_safe4(func)
412  end
413
414  def test_thread_set_safe4
415    th = Thread.start {sleep}
416    func = proc do
417      $SAFE = 4
418      th.set_trace_func(lambda {|*|})
419    end
420    assert_security_error_safe4(func)
421  ensure
422    th.kill
423  end
424
425  def test_thread_add_safe4
426    th = Thread.start {sleep}
427    func = proc do
428      $SAFE = 4
429      th.add_trace_func(lambda {|*|})
430    end
431    assert_security_error_safe4(func)
432  ensure
433    th.kill
434  end
435
436  class << self
437    define_method(:method_added, Module.method(:method_added))
438  end
439
440  def trace_by_tracepoint *trace_events
441    events = []
442    trace = nil
443    xyzzy = nil
444    _local_var = :outer
445    raised_exc = nil
446    method = :trace_by_tracepoint
447    _get_data = lambda{|tp|
448      case tp.event
449      when :return, :c_return
450        tp.return_value
451      when :raise
452        tp.raised_exception
453      else
454        :nothing
455      end
456    }
457    _defined_class = lambda{|tp|
458      klass = tp.defined_class
459      begin
460        # If it is singleton method, then return original class
461        # to make compatible with set_trace_func().
462        # This is very ad-hoc hack. I hope I can make more clean test on it.
463        case klass.inspect
464        when /Class:TracePoint/; return TracePoint
465        when /Class:Exception/; return Exception
466        else klass
467        end
468      rescue Exception => e
469        e
470      end if klass
471    }
472
473    trace = nil
474    begin
475    eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy'
476    1: trace = TracePoint.trace(*trace_events){|tp|
477    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'
478    3: }
479    4: 1.times{|;_local_var| _local_var = :inner
480    5:   tap{}
481    6: }
482    7: class XYZZY
483    8:   _local_var = :XYZZY_outer
484    9:   def foo
485   10:     _local_var = :XYZZY_foo
486   11:     bar
487   12:   end
488   13:   def bar
489   14:     _local_var = :XYZZY_bar
490   15:     tap{}
491   16:   end
492   17: end
493   18: xyzzy = XYZZY.new
494   19: xyzzy.foo
495   20: begin; raise RuntimeError; rescue RuntimeError => raised_exc; end
496   21: trace.disable
497    EOF
498    self.class.class_eval{remove_const(:XYZZY)}
499    ensure
500      trace.disable if trace && trace.enabled?
501    end
502
503    answer_events = [
504     #
505     [:c_return, 1, "xyzzy", TracePoint,  :trace,           TracePoint,  :outer,  trace],
506     [:line,     4, 'xyzzy', self.class,  method,           self,        :outer, :nothing],
507     [:c_call,   4, 'xyzzy', Integer,     :times,           1,           :outer, :nothing],
508     [:line,     4, 'xyzzy', self.class,  method,           self,        nil,    :nothing],
509     [:line,     5, 'xyzzy', self.class,  method,           self,        :inner, :nothing],
510     [:c_call,   5, 'xyzzy', Kernel,      :tap,             self,        :inner, :nothing],
511     [:c_return, 5, "xyzzy", Kernel,      :tap,             self,        :inner, self],
512     [:c_return, 4, "xyzzy", Integer,     :times,           1,           :outer, 1],
513     [:line,     7, 'xyzzy', self.class,  method,           self,        :outer, :nothing],
514     [:c_call,   7, "xyzzy", Class,       :inherited,       Object,      :outer, :nothing],
515     [:c_return, 7, "xyzzy", Class,       :inherited,       Object,      :outer, nil],
516     [:class,    7, "xyzzy", nil,         nil,              xyzzy.class, nil,    :nothing],
517     [:line,     8, "xyzzy", nil,         nil,              xyzzy.class, nil,    :nothing],
518     [:line,     9, "xyzzy", nil,         nil,              xyzzy.class, :XYZZY_outer, :nothing],
519     [:c_call,   9, "xyzzy", Module,      :method_added,    xyzzy.class, :XYZZY_outer, :nothing],
520     [:c_return, 9, "xyzzy", Module,      :method_added,    xyzzy.class, :XYZZY_outer, nil],
521     [:line,    13, "xyzzy", nil,         nil,              xyzzy.class, :XYZZY_outer, :nothing],
522     [:c_call,  13, "xyzzy", Module,      :method_added,    xyzzy.class, :XYZZY_outer, :nothing],
523     [:c_return,13, "xyzzy", Module,      :method_added,    xyzzy.class, :XYZZY_outer, nil],
524     [:end,     17, "xyzzy", nil,         nil,              xyzzy.class, :XYZZY_outer, :nothing],
525     [:line,    18, "xyzzy", TestSetTraceFunc, method,      self,        :outer, :nothing],
526     [:c_call,  18, "xyzzy", Class,       :new,             xyzzy.class, :outer, :nothing],
527     [:c_call,  18, "xyzzy", BasicObject, :initialize,      xyzzy,       :outer, :nothing],
528     [:c_return,18, "xyzzy", BasicObject, :initialize,      xyzzy,       :outer, nil],
529     [:c_return,18, "xyzzy", Class,       :new,             xyzzy.class, :outer, xyzzy],
530     [:line,    19, "xyzzy", TestSetTraceFunc, method,      self, :outer, :nothing],
531     [:call,     9, "xyzzy", xyzzy.class, :foo,             xyzzy,       nil,  :nothing],
532     [:line,    10, "xyzzy", xyzzy.class, :foo,             xyzzy,       nil,  :nothing],
533     [:line,    11, "xyzzy", xyzzy.class, :foo,             xyzzy,       :XYZZY_foo, :nothing],
534     [:call,    13, "xyzzy", xyzzy.class, :bar,             xyzzy,       nil, :nothing],
535     [:line,    14, "xyzzy", xyzzy.class, :bar,             xyzzy,       nil, :nothing],
536     [:line,    15, "xyzzy", xyzzy.class, :bar,             xyzzy,       :XYZZY_bar, :nothing],
537     [:c_call,  15, "xyzzy", Kernel,      :tap,             xyzzy,       :XYZZY_bar, :nothing],
538     [:c_return,15, "xyzzy", Kernel,      :tap,             xyzzy,       :XYZZY_bar, xyzzy],
539     [:return,  16, "xyzzy", xyzzy.class, :bar,             xyzzy,       :XYZZY_bar, xyzzy],
540     [:return,  12, "xyzzy", xyzzy.class, :foo,             xyzzy,       :XYZZY_foo, xyzzy],
541     [:line,    20, "xyzzy", TestSetTraceFunc, method,      self,        :outer, :nothing],
542     [:line,    20, "xyzzy", TestSetTraceFunc, method,      self,        :outer, :nothing],
543     [:c_call,  20, "xyzzy", Kernel,      :raise,           self,        :outer, :nothing],
544     [:c_call,  20, "xyzzy", Exception,   :exception,       RuntimeError, :outer, :nothing],
545     [:c_call,  20, "xyzzy", Exception,   :initialize,      raised_exc,  :outer, :nothing],
546     [:c_return,20, "xyzzy", Exception,   :initialize,      raised_exc,  :outer, raised_exc],
547     [:c_return,20, "xyzzy", Exception,   :exception,       RuntimeError, :outer, raised_exc],
548     [:c_call,  20, "xyzzy", Exception,   :backtrace,       raised_exc,  :outer, :nothing],
549     [:c_return,20, "xyzzy", Exception,   :backtrace,       raised_exc,  :outer, nil],
550     [:raise,   20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc],
551     [:c_return,20, "xyzzy", Kernel,      :raise,           self,        :outer, nil],
552     [:c_call,  20, "xyzzy", Module,      :===,             RuntimeError,:outer, :nothing],
553     [:c_return,20, "xyzzy", Module,      :===,             RuntimeError,:outer, true],
554     [:line,    21, "xyzzy", TestSetTraceFunc, method,      self,        :outer, :nothing],
555     [:c_call,  21, "xyzzy", TracePoint,  :disable,         trace,       :outer, :nothing],
556     ]
557
558    return events, answer_events
559  end
560
561  def trace_by_set_trace_func
562    events = []
563    trace = nil
564    trace = trace
565    xyzzy = nil
566    xyzzy = xyzzy
567    _local_var = :outer
568    eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy'
569    1: set_trace_func(lambda{|event, file, line, id, binding, klass|
570    2:   events << [event, line, file, klass, id, binding.eval('self'), binding.eval("_local_var")] if file == 'xyzzy'
571    3: })
572    4: 1.times{|;_local_var| _local_var = :inner
573    5:   tap{}
574    6: }
575    7: class XYZZY
576    8:   _local_var = :XYZZY_outer
577    9:   def foo
578   10:     _local_var = :XYZZY_foo
579   11:     bar
580   12:   end
581   13:   def bar
582   14:     _local_var = :XYZZY_bar
583   15:     tap{}
584   16:   end
585   17: end
586   18: xyzzy = XYZZY.new
587   19: xyzzy.foo
588   20: begin; raise RuntimeError; rescue RuntimeError => raised_exc; end
589   21: set_trace_func(nil)
590    EOF
591    self.class.class_eval{remove_const(:XYZZY)}
592    return events
593  end
594
595  def test_tracepoint
596    events1, answer_events = *trace_by_tracepoint(:line, :class, :end, :call, :return, :c_call, :c_return, :raise)
597
598    mesg = events1.map{|e|
599      if false
600        p [:event, e[0]]
601        p [:line_file, e[1], e[2]]
602        p [:id, e[4]]
603      end
604      "#{e[0]} - #{e[2]}:#{e[1]} id: #{e[4]}"
605    }.join("\n")
606    answer_events.zip(events1){|answer, event|
607      assert_equal answer, event, mesg
608    }
609
610    events2 = trace_by_set_trace_func
611    events1.zip(events2){|ev1, ev2|
612      ev2[0] = ev2[0].sub('-', '_').to_sym
613      assert_equal ev1[0..2], ev2[0..2], ev1.inspect
614
615      # event, line, file, klass, id, binding.eval('self'), binding.eval("_local_var")
616      assert_equal ev1[3].nil?, ev2[3].nil? # klass
617      assert_equal ev1[4].nil?, ev2[4].nil? # id
618      assert_equal ev1[6], ev2[6]           # _local_var
619    }
620
621    [:line, :class, :end, :call, :return, :c_call, :c_return, :raise].each{|event|
622      events1, answer_events = *trace_by_tracepoint(event)
623      answer_events.find_all{|e| e[0] == event}.zip(events1){|answer_line, event_line|
624        assert_equal answer_line, event_line
625      }
626    }
627  end
628
629  def test_tracepoint_object_id
630    tps = []
631    trace = TracePoint.trace(){|tp|
632      tps << tp
633    }
634    tap{}
635    tap{}
636    tap{}
637    trace.disable
638
639    # passed tp is unique, `trace' object which is genereted by TracePoint.trace
640    tps.each{|tp|
641      assert_equal trace, tp
642    }
643  end
644
645  def test_tracepoint_access_from_outside
646    tp_store = nil
647    trace = TracePoint.trace(){|tp|
648      tp_store = tp
649    }
650    tap{}
651    trace.disable
652
653    assert_raise(RuntimeError){tp_store.lineno}
654    assert_raise(RuntimeError){tp_store.event}
655    assert_raise(RuntimeError){tp_store.path}
656    assert_raise(RuntimeError){tp_store.method_id}
657    assert_raise(RuntimeError){tp_store.defined_class}
658    assert_raise(RuntimeError){tp_store.binding}
659    assert_raise(RuntimeError){tp_store.self}
660    assert_raise(RuntimeError){tp_store.return_value}
661    assert_raise(RuntimeError){tp_store.raised_exception}
662  end
663
664  def foo
665  end
666
667  def test_tracepoint_enable
668    ary = []
669    trace = TracePoint.new(:call){|tp|
670      ary << tp.method_id
671    }
672    foo
673    trace.enable{
674      foo
675    }
676    foo
677    assert_equal([:foo], ary)
678
679    trace = TracePoint.new{}
680    begin
681      assert_equal(false, trace.enable)
682      assert_equal(true, trace.enable)
683      trace.enable{}
684      assert_equal(true, trace.enable)
685    ensure
686      trace.disable
687    end
688  end
689
690  def test_tracepoint_disable
691    ary = []
692    trace = TracePoint.trace(:call){|tp|
693      ary << tp.method_id
694    }
695    foo
696    trace.disable{
697      foo
698    }
699    foo
700    trace.disable
701    assert_equal([:foo, :foo], ary)
702
703    trace = TracePoint.new{}
704    trace.enable{
705      assert_equal(true, trace.disable)
706      assert_equal(false, trace.disable)
707      trace.disable{}
708      assert_equal(false, trace.disable)
709    }
710  end
711
712  def test_tracepoint_enabled
713    trace = TracePoint.trace(:call){|tp|
714      #
715    }
716    assert_equal(true, trace.enabled?)
717    trace.disable{
718      assert_equal(false, trace.enabled?)
719      trace.enable{
720        assert_equal(true, trace.enabled?)
721      }
722    }
723    trace.disable
724    assert_equal(false, trace.enabled?)
725  end
726
727  def method_test_tracepoint_return_value obj
728    obj
729  end
730
731  def test_tracepoint_return_value
732    trace = TracePoint.new(:call, :return){|tp|
733      next if tp.path != __FILE__
734      case tp.event
735      when :call
736        assert_raise(RuntimeError) {tp.return_value}
737      when :return
738        assert_equal("xyzzy", tp.return_value)
739      end
740    }
741    trace.enable{
742      method_test_tracepoint_return_value "xyzzy"
743    }
744  end
745
746  class XYZZYException < Exception; end
747  def method_test_tracepoint_raised_exception err
748    raise err
749  end
750
751  def test_tracepoint_raised_exception
752    trace = TracePoint.new(:call, :return){|tp|
753      case tp.event
754      when :call, :return
755        assert_raise(RuntimeError) { tp.raised_exception }
756      when :raise
757        assert_equal(XYZZYError, tp.raised_exception)
758      end
759    }
760    trace.enable{
761      begin
762        method_test_tracepoint_raised_exception XYZZYException
763      rescue XYZZYException
764        # ok
765      else
766        raise
767      end
768    }
769  end
770
771  def method_for_test_tracepoint_block
772    yield
773  end
774
775  def test_tracepoint_block
776    events = []
777    TracePoint.new(:call, :return, :c_call, :b_call, :c_return, :b_return){|tp|
778      events << [
779        tp.event, tp.method_id, tp.defined_class, tp.self.class,
780        /return/ =~ tp.event ? tp.return_value : nil
781      ]
782    }.enable{
783      1.times{
784        3
785      }
786      method_for_test_tracepoint_block{
787        4
788      }
789    }
790    # pp events
791    # expected_events =
792    [[:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
793     [:c_call, :times, Integer, Fixnum, nil],
794     [:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
795     [:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 3],
796     [:c_return, :times, Integer, Fixnum, 1],
797     [:call, :method_for_test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
798     [:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
799     [:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4],
800     [:return, :method_for_test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4],
801     [:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4]
802    ].zip(events){|expected, actual|
803      assert_equal(expected, actual)
804    }
805  end
806
807  def test_tracepoint_thread
808    events = []
809    thread_self = nil
810    created_thread = nil
811    TracePoint.new(:thread_begin, :thread_end){|tp|
812      events << [Thread.current,
813                 tp.event,
814                 tp.lineno,  #=> 0
815                 tp.path,    #=> nil
816                 tp.binding, #=> nil
817                 tp.defined_class, #=> nil,
818                 tp.self.class # tp.self return creating/ending thread
819                 ]
820    }.enable{
821      created_thread = Thread.new{thread_self = self}
822      created_thread.join
823    }
824    assert_equal(self, thread_self)
825    assert_equal([created_thread, :thread_begin, 0, nil, nil, nil, Thread], events[0])
826    assert_equal([created_thread, :thread_end, 0, nil, nil, nil, Thread], events[1])
827    assert_equal(2, events.size)
828  end
829
830  def test_tracepoint_inspect
831    events = []
832    trace = TracePoint.new{|tp| events << [tp.event, tp.inspect]}
833    assert_equal("#<TracePoint:disabled>", trace.inspect)
834    trace.enable{
835      assert_equal("#<TracePoint:enabled>", trace.inspect)
836      Thread.new{}.join
837    }
838    assert_equal("#<TracePoint:disabled>", trace.inspect)
839    events.each{|(ev, str)|
840      case ev
841      when :line
842        assert_match(/ in /, str)
843      when :call, :c_call
844        assert_match(/call \`/, str) # #<TracePoint:c_call `inherited'@../trunk/test.rb:11>
845      when :return, :c_return
846        assert_match(/return \`/, str) # #<TracePoint:return `m'@../trunk/test.rb:3>
847      when /thread/
848        assert_match(/\#<Thread:/, str) # #<TracePoint:thread_end of #<Thread:0x87076c0>>
849      else
850        assert_match(/\#<TracePoint:/, str)
851      end
852    }
853  end
854
855  def test_tracepoint_exception_at_line
856    assert_raise(RuntimeError) do
857      TracePoint.new(:line) {raise}.enable {
858        1
859      }
860    end
861  end
862
863  def test_tracepoint_exception_at_return
864    assert_nothing_raised(Timeout::Error, 'infinite trace') do
865      assert_normal_exit('def m; end; TracePoint.new(:return) {raise}.enable {m}', '', timeout: 3)
866    end
867  end
868
869  def test_tracepoint_with_multithreads
870    assert_nothing_raised do
871      TracePoint.new{
872        10.times{
873          Thread.pass
874        }
875      }.enable do
876        (1..10).map{
877          Thread.new{
878            1000.times{
879            }
880          }
881        }.each{|th|
882          th.join
883        }
884      end
885    end
886  end
887
888  class FOO_ERROR < RuntimeError; end
889  class BAR_ERROR < RuntimeError; end
890  def m1_test_trace_point_at_return_when_exception
891    m2_test_trace_point_at_return_when_exception
892  end
893  def m2_test_trace_point_at_return_when_exception
894    raise BAR_ERROR
895  end
896
897  def test_trace_point_at_return_when_exception
898    bug_7624 = '[ruby-core:51128] [ruby-trunk - Bug #7624]'
899    TracePoint.new{|tp|
900      if tp.event == :return &&
901        tp.method_id == :m2_test_trace_point_at_return_when_exception
902        raise FOO_ERROR
903      end
904    }.enable do
905      assert_raise(FOO_ERROR, bug_7624) do
906        m1_test_trace_point_at_return_when_exception
907      end
908    end
909
910    bug_7668 = '[Bug #7668]'
911    ary = []
912    trace = TracePoint.new{|tp|
913      ary << tp.event
914      raise
915    }
916    begin
917      trace.enable{
918        1.times{
919          raise
920        }
921      }
922    rescue
923      assert_equal([:b_call, :b_return], ary, bug_7668)
924    end
925  end
926
927  def test_trace_point_enable_safe4
928    tp = TracePoint.new {}
929    func = proc do
930      $SAFE = 4
931      tp.enable
932    end
933    assert_security_error_safe4(func)
934  end
935
936  def test_trace_point_disable_safe4
937    tp = TracePoint.new {}
938    func = proc do
939      $SAFE = 4
940      tp.disable
941    end
942    assert_security_error_safe4(func)
943  end
944
945  def m1_for_test_trace_point_binding_in_ifunc(arg)
946    arg + nil
947  rescue
948  end
949
950  def m2_for_test_trace_point_binding_in_ifunc(arg)
951    arg.inject(:+)
952  rescue
953  end
954
955  def test_trace_point_binding_in_ifunc
956    bug7774 = '[ruby-dev:46908]'
957    src = %q{
958      tp = TracePoint.new(:raise) do |tp|
959        tp.binding
960      end
961      tp.enable do
962        obj = Object.new
963        class << obj
964          include Enumerable
965          def each
966            yield 1
967          end
968        end
969        %s
970      end
971    }
972    assert_normal_exit src % %q{obj.zip({}) {}}, bug7774
973    assert_normal_exit src % %q{
974      require 'continuation'
975      begin
976        c = nil
977        obj.sort_by {|x| callcc {|c2| c ||= c2 }; x }
978        c.call
979      rescue RuntimeError
980      end
981    }, bug7774
982
983    # TracePoint
984    tp_b = nil
985    TracePoint.new(:raise) do |tp|
986      tp_b = tp.binding
987    end.enable do
988      m1_for_test_trace_point_binding_in_ifunc(0)
989      assert_equal(self, eval('self', tp_b), '[ruby-dev:46960]')
990
991      m2_for_test_trace_point_binding_in_ifunc([0, nil])
992      assert_equal(self, eval('self', tp_b), '[ruby-dev:46960]')
993    end
994
995    # set_trace_func
996    stf_b = nil
997    set_trace_func ->(event, file, line, id, binding, klass) do
998      stf_b = binding if event == 'raise'
999    end
1000    begin
1001      m1_for_test_trace_point_binding_in_ifunc(0)
1002      assert_equal(self, eval('self', stf_b), '[ruby-dev:46960]')
1003
1004      m2_for_test_trace_point_binding_in_ifunc([0, nil])
1005      assert_equal(self, eval('self', stf_b), '[ruby-dev:46960]')
1006    ensure
1007      set_trace_func(nil)
1008    end
1009  end
1010
1011  def test_tracepoint_b_return_with_next
1012    n = 0
1013    TracePoint.new(:b_return){
1014      n += 1
1015    }.enable{
1016      3.times{
1017        next
1018      } # 3 times b_retun
1019    }   # 1 time b_return
1020
1021    assert_equal 4, n
1022  end
1023end
1024