1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/test/ruby/test_settracefunc.rb
k0kubun ed935aa5be mjit_compile.c: merge initial JIT compiler
which has been developed by Takashi Kokubun <takashikkbn@gmail> as
YARV-MJIT. Many of its bugs are fixed by wanabe <s.wanabe@gmail.com>.

This JIT compiler is designed to be a safe migration path to introduce
JIT compiler to MRI. So this commit does not include any bytecode
changes or dynamic instruction modifications, which are done in original
MJIT.

This commit even strips off some aggressive optimizations from
YARV-MJIT, and thus it's slower than YARV-MJIT too. But it's still
fairly faster than Ruby 2.5 in some benchmarks (attached below).

Note that this JIT compiler passes `make test`, `make test-all`, `make
test-spec` without JIT, and even with JIT. Not only it's perfectly safe
with JIT disabled because it does not replace VM instructions unlike
MJIT, but also with JIT enabled it stably runs Ruby applications
including Rails applications.

I'm expecting this version as just "initial" JIT compiler. I have many
optimization ideas which are skipped for initial merging, and you may
easily replace this JIT compiler with a faster one by just replacing
mjit_compile.c. `mjit_compile` interface is designed for the purpose.

common.mk: update dependencies for mjit_compile.c.

internal.h: declare `rb_vm_insn_addr2insn` for MJIT.

vm.c: exclude some definitions if `-DMJIT_HEADER` is provided to
compiler. This avoids to include some functions which take a long time
to compile, e.g. vm_exec_core. Some of the purpose is achieved in
transform_mjit_header.rb (see `IGNORED_FUNCTIONS`) but others are
manually resolved for now. Load mjit_helper.h for MJIT header.
mjit_helper.h: New. This is a file used only by JIT-ed code. I'll
refactor `mjit_call_cfunc` later.
vm_eval.c: add some #ifdef switches to skip compiling some functions
like Init_vm_eval.

win32/mkexports.rb: export thread/ec functions, which are used by MJIT.

include/ruby/defines.h: add MJIT_FUNC_EXPORTED macro alis to clarify
that a function is exported only for MJIT.

array.c: export a function used by MJIT.
bignum.c: ditto.
class.c: ditto.
compile.c: ditto.
error.c: ditto.
gc.c: ditto.
hash.c: ditto.
iseq.c: ditto.
numeric.c: ditto.
object.c: ditto.
proc.c: ditto.
re.c: ditto.
st.c: ditto.
string.c: ditto.
thread.c: ditto.
variable.c: ditto.
vm_backtrace.c: ditto.
vm_insnhelper.c: ditto.
vm_method.c: ditto.

I would like to improve maintainability of function exports, but I
believe this way is acceptable as initial merging if we clarify the
new exports are for MJIT (so that we can use them as TODO list to fix)
and add unit tests to detect unresolved symbols.
I'll add unit tests of JIT compilations in succeeding commits.

Author: Takashi Kokubun <takashikkbn@gmail.com>
Contributor: wanabe <s.wanabe@gmail.com>

Part of [Feature #14235]

---

* Known issues
  * Code generated by gcc is faster than clang. The benchmark may be worse
    in macOS. Following benchmark result is provided by gcc w/ Linux.
  * Performance is decreased when Google Chrome is running
  * JIT can work on MinGW, but it doesn't improve performance at least
    in short running benchmark.
  * Currently it doesn't perform well with Rails. We'll try to fix this
    before release.

---

* Benchmark reslts

Benchmarked with:
Intel 4.0GHz i7-4790K with 16GB memory under x86-64 Ubuntu 8 Cores

- 2.0.0-p0: Ruby 2.0.0-p0
- r62186: Ruby trunk (early 2.6.0), before MJIT changes
- JIT off: On this commit, but without `--jit` option
- JIT on: On this commit, and with `--jit` option

** Optcarrot fps

Benchmark: https://github.com/mame/optcarrot

|         |2.0.0-p0 |r62186   |JIT off  |JIT on   |
|:--------|:--------|:--------|:--------|:--------|
|fps      |37.32    |51.46    |51.31    |58.88    |
|vs 2.0.0 |1.00x    |1.38x    |1.37x    |1.58x    |

** MJIT benchmarks

Benchmark: https://github.com/benchmark-driver/mjit-benchmarks
(Original: https://github.com/vnmakarov/ruby/tree/rtl_mjit_branch/MJIT-benchmarks)

|           |2.0.0-p0 |r62186   |JIT off  |JIT on   |
|:----------|:--------|:--------|:--------|:--------|
|aread      |1.00     |1.09     |1.07     |2.19     |
|aref       |1.00     |1.13     |1.11     |2.22     |
|aset       |1.00     |1.50     |1.45     |2.64     |
|awrite     |1.00     |1.17     |1.13     |2.20     |
|call       |1.00     |1.29     |1.26     |2.02     |
|const2     |1.00     |1.10     |1.10     |2.19     |
|const      |1.00     |1.11     |1.10     |2.19     |
|fannk      |1.00     |1.04     |1.02     |1.00     |
|fib        |1.00     |1.32     |1.31     |1.84     |
|ivread     |1.00     |1.13     |1.12     |2.43     |
|ivwrite    |1.00     |1.23     |1.21     |2.40     |
|mandelbrot |1.00     |1.13     |1.16     |1.28     |
|meteor     |1.00     |2.97     |2.92     |3.17     |
|nbody      |1.00     |1.17     |1.15     |1.49     |
|nest-ntimes|1.00     |1.22     |1.20     |1.39     |
|nest-while |1.00     |1.10     |1.10     |1.37     |
|norm       |1.00     |1.18     |1.16     |1.24     |
|nsvb       |1.00     |1.16     |1.16     |1.17     |
|red-black  |1.00     |1.02     |0.99     |1.12     |
|sieve      |1.00     |1.30     |1.28     |1.62     |
|trees      |1.00     |1.14     |1.13     |1.19     |
|while      |1.00     |1.12     |1.11     |2.41     |

** Discourse's script/bench.rb

Benchmark: https://github.com/discourse/discourse/blob/v1.8.7/script/bench.rb

NOTE: Rails performance was somehow a little degraded with JIT for now.
We should fix this.
(At least I know opt_aref is performing badly in JIT and I have an idea
 to fix it. Please wait for the fix.)

*** JIT off
Your Results: (note for timings- percentile is first, duration is second in millisecs)

categories_admin:
  50: 17
  75: 18
  90: 22
  99: 29
home_admin:
  50: 21
  75: 21
  90: 27
  99: 40
topic_admin:
  50: 17
  75: 18
  90: 22
  99: 32
categories:
  50: 35
  75: 41
  90: 43
  99: 77
home:
  50: 39
  75: 46
  90: 49
  99: 95
topic:
  50: 46
  75: 52
  90: 56
  99: 101

*** JIT on
Your Results: (note for timings- percentile is first, duration is second in millisecs)

categories_admin:
  50: 19
  75: 21
  90: 25
  99: 33
home_admin:
  50: 24
  75: 26
  90: 30
  99: 35
topic_admin:
  50: 19
  75: 20
  90: 25
  99: 30
categories:
  50: 40
  75: 44
  90: 48
  99: 76
home:
  50: 42
  75: 48
  90: 51
  99: 89
topic:
  50: 49
  75: 55
  90: 58
  99: 99

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@62197 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2018-02-04 11:22:28 +00:00

1849 lines
52 KiB
Ruby

# frozen_string_literal: false
require 'test/unit'
class TestSetTraceFunc < Test::Unit::TestCase
def setup
@original_compile_option = RubyVM::InstructionSequence.compile_option
RubyVM::InstructionSequence.compile_option = {
:trace_instruction => true,
:specialized_instruction => false
}
@target_thread = Thread.current
end
def teardown
set_trace_func(nil)
RubyVM::InstructionSequence.compile_option = @original_compile_option
@target_thread = nil
end
def target_thread?
Thread.current == @target_thread
end
def test_c_call
events = []
name = "#{self.class}\##{__method__}"
eval <<-EOF.gsub(/^.*?: /, ""), nil, name
1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
2: events << [event, lineno, mid, klass] if file == name
3: })
4: x = 1 + 1
5: set_trace_func(nil)
EOF
assert_equal(["c-return", 1, :set_trace_func, Kernel],
events.shift)
assert_equal(["line", 4, __method__, self.class],
events.shift)
assert_equal(["c-call", 4, :+, Integer],
events.shift)
assert_equal(["c-return", 4, :+, Integer],
events.shift)
assert_equal(["line", 5, __method__, self.class],
events.shift)
assert_equal(["c-call", 5, :set_trace_func, Kernel],
events.shift)
assert_equal([], events)
end
def test_call
events = []
name = "#{self.class}\##{__method__}"
eval <<-EOF.gsub(/^.*?: /, ""), nil, name
1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
2: events << [event, lineno, mid, klass] if file == name
3: })
4: def add(x, y)
5: x + y
6: end
7: x = add(1, 1)
8: set_trace_func(nil)
EOF
assert_equal(["c-return", 1, :set_trace_func, Kernel],
events.shift)
assert_equal(["line", 4, __method__, self.class],
events.shift)
assert_equal(["c-call", 4, :method_added, self.class],
events.shift)
assert_equal(["c-return", 4, :method_added, self.class],
events.shift)
assert_equal(["line", 7, __method__, self.class],
events.shift)
assert_equal(["call", 4, :add, self.class],
events.shift)
assert_equal(["line", 5, :add, self.class],
events.shift)
assert_equal(["c-call", 5, :+, Integer],
events.shift)
assert_equal(["c-return", 5, :+, Integer],
events.shift)
assert_equal(["return", 6, :add, self.class],
events.shift)
assert_equal(["line", 8, __method__, self.class],
events.shift)
assert_equal(["c-call", 8, :set_trace_func, Kernel],
events.shift)
assert_equal([], events)
end
def test_class
events = []
name = "#{self.class}\##{__method__}"
eval <<-EOF.gsub(/^.*?: /, ""), nil, name
1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
2: events << [event, lineno, mid, klass] if file == name
3: })
4: class Foo
5: def bar
6: end
7: end
8: x = Foo.new.bar
9: set_trace_func(nil)
EOF
assert_equal(["c-return", 1, :set_trace_func, Kernel],
events.shift)
assert_equal(["line", 4, __method__, self.class],
events.shift)
assert_equal(["c-call", 4, :inherited, Class],
events.shift)
assert_equal(["c-return", 4, :inherited, Class],
events.shift)
assert_equal(["class", 4, nil, nil],
events.shift)
assert_equal(["line", 5, nil, nil],
events.shift)
assert_equal(["c-call", 5, :method_added, Module],
events.shift)
assert_equal(["c-return", 5, :method_added, Module],
events.shift)
assert_equal(["end", 7, nil, nil],
events.shift)
assert_equal(["line", 8, __method__, self.class],
events.shift)
assert_equal(["c-call", 8, :new, Class],
events.shift)
assert_equal(["c-call", 8, :initialize, BasicObject],
events.shift)
assert_equal(["c-return", 8, :initialize, BasicObject],
events.shift)
assert_equal(["c-return", 8, :new, Class],
events.shift)
assert_equal(["call", 5, :bar, Foo],
events.shift)
assert_equal(["return", 6, :bar, Foo],
events.shift)
assert_equal(["line", 9, __method__, self.class],
events.shift)
assert_equal(["c-call", 9, :set_trace_func, Kernel],
events.shift)
assert_equal([], events)
end
def test_return # [ruby-dev:38701]
events = []
name = "#{self.class}\##{__method__}"
eval <<-EOF.gsub(/^.*?: /, ""), nil, name
1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
2: events << [event, lineno, mid, klass] if file == name
3: })
4: def meth_return(a)
5: return if a
6: return
7: end
8: meth_return(true)
9: meth_return(false)
10: set_trace_func(nil)
EOF
assert_equal(["c-return", 1, :set_trace_func, Kernel],
events.shift)
assert_equal(["line", 4, __method__, self.class],
events.shift)
assert_equal(["c-call", 4, :method_added, self.class],
events.shift)
assert_equal(["c-return", 4, :method_added, self.class],
events.shift)
assert_equal(["line", 8, __method__, self.class],
events.shift)
assert_equal(["call", 4, :meth_return, self.class],
events.shift)
assert_equal(["line", 5, :meth_return, self.class],
events.shift)
assert_equal(["return", 5, :meth_return, self.class],
events.shift)
assert_equal(["line", 9, :test_return, self.class],
events.shift)
assert_equal(["call", 4, :meth_return, self.class],
events.shift)
assert_equal(["line", 5, :meth_return, self.class],
events.shift)
assert_equal(["return", 7, :meth_return, self.class],
events.shift)
assert_equal(["line", 10, :test_return, self.class],
events.shift)
assert_equal(["c-call", 10, :set_trace_func, Kernel],
events.shift)
assert_equal([], events)
end
def test_return2 # [ruby-core:24463]
events = []
name = "#{self.class}\##{__method__}"
eval <<-EOF.gsub(/^.*?: /, ""), nil, name
1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
2: events << [event, lineno, mid, klass] if file == name
3: })
4: def meth_return2
5: a = 5
6: return a
7: end
8: meth_return2
9: set_trace_func(nil)
EOF
assert_equal(["c-return", 1, :set_trace_func, Kernel],
events.shift)
assert_equal(["line", 4, __method__, self.class],
events.shift)
assert_equal(["c-call", 4, :method_added, self.class],
events.shift)
assert_equal(["c-return", 4, :method_added, self.class],
events.shift)
assert_equal(["line", 8, __method__, self.class],
events.shift)
assert_equal(["call", 4, :meth_return2, self.class],
events.shift)
assert_equal(["line", 5, :meth_return2, self.class],
events.shift)
assert_equal(["line", 6, :meth_return2, self.class],
events.shift)
assert_equal(["return", 7, :meth_return2, self.class],
events.shift)
assert_equal(["line", 9, :test_return2, self.class],
events.shift)
assert_equal(["c-call", 9, :set_trace_func, Kernel],
events.shift)
assert_equal([], events)
end
def test_raise
events = []
name = "#{self.class}\##{__method__}"
eval <<-EOF.gsub(/^.*?: /, ""), nil, name
1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
2: events << [event, lineno, mid, klass] if file == name
3: })
4: begin
5: raise TypeError, "error"
6: rescue TypeError
7: end
8: set_trace_func(nil)
EOF
assert_equal(["c-return", 1, :set_trace_func, Kernel],
events.shift)
assert_equal(["line", 5, __method__, self.class],
events.shift)
assert_equal(["c-call", 5, :raise, Kernel],
events.shift)
assert_equal(["c-call", 5, :exception, Exception],
events.shift)
assert_equal(["c-call", 5, :initialize, Exception],
events.shift)
assert_equal(["c-return", 5, :initialize, Exception],
events.shift)
assert_equal(["c-return", 5, :exception, Exception],
events.shift)
assert_equal(["c-return", 5, :raise, Kernel],
events.shift)
assert_equal(["c-call", 5, :backtrace, Exception],
events.shift)
assert_equal(["c-return", 5, :backtrace, Exception],
events.shift)
assert_equal(["raise", 5, :test_raise, TestSetTraceFunc],
events.shift)
assert_equal(["c-call", 6, :===, Module],
events.shift)
assert_equal(["c-return", 6, :===, Module],
events.shift)
assert_equal(["line", 8, __method__, self.class],
events.shift)
assert_equal(["c-call", 8, :set_trace_func, Kernel],
events.shift)
assert_equal([], events)
end
def test_break # [ruby-core:27606] [Bug #2610]
events = []
name = "#{self.class}\##{__method__}"
eval <<-EOF.gsub(/^.*?: /, ""), nil, name
1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
2: events << [event, lineno, mid, klass] if file == name
3: })
4: [1,2,3].any? {|n| n}
8: set_trace_func(nil)
EOF
[["c-return", 1, :set_trace_func, Kernel],
["line", 4, __method__, self.class],
["c-call", 4, :any?, Array],
["line", 4, __method__, self.class],
["c-return", 4, :any?, Array],
["line", 5, __method__, self.class],
["c-call", 5, :set_trace_func, Kernel]].each.with_index{|e, i|
assert_equal(e, events.shift, "mismatch on #{i}th trace")
}
end
def test_invalid_proc
assert_raise(TypeError) { set_trace_func(1) }
end
def test_raise_in_trace
set_trace_func proc {raise rescue nil}
assert_equal(42, (raise rescue 42), '[ruby-core:24118]')
end
def test_thread_trace
events = {:set => [], :add => []}
prc = Proc.new { |event, file, lineno, mid, binding, klass|
events[:set] << [event, lineno, mid, klass, :set]
}
prc = prc # suppress warning
prc2 = Proc.new { |event, file, lineno, mid, binding, klass|
events[:add] << [event, lineno, mid, klass, :add]
}
prc2 = prc2 # suppress warning
th = Thread.new do
th = Thread.current
name = "#{self.class}\##{__method__}"
eval <<-EOF.gsub(/^.*?: /, ""), nil, name
1: th.set_trace_func(prc)
2: th.add_trace_func(prc2)
3: class ThreadTraceInnerClass
4: def foo
5: _x = 1 + 1
6: end
7: end
8: ThreadTraceInnerClass.new.foo
9: th.set_trace_func(nil)
EOF
end
th.join
[["c-return", 1, :set_trace_func, Thread, :set],
["line", 2, __method__, self.class, :set],
["c-call", 2, :add_trace_func, Thread, :set]].each do |e|
assert_equal(e, events[:set].shift)
end
[["c-return", 2, :add_trace_func, Thread],
["line", 3, __method__, self.class],
["c-call", 3, :inherited, Class],
["c-return", 3, :inherited, Class],
["class", 3, nil, nil],
["line", 4, nil, nil],
["c-call", 4, :method_added, Module],
["c-return", 4, :method_added, Module],
["end", 7, nil, nil],
["line", 8, __method__, self.class],
["c-call", 8, :new, Class],
["c-call", 8, :initialize, BasicObject],
["c-return", 8, :initialize, BasicObject],
["c-return", 8, :new, Class],
["call", 4, :foo, ThreadTraceInnerClass],
["line", 5, :foo, ThreadTraceInnerClass],
["c-call", 5, :+, Integer],
["c-return", 5, :+, Integer],
["return", 6, :foo, ThreadTraceInnerClass],
["line", 9, __method__, self.class],
["c-call", 9, :set_trace_func, Thread]].each do |e|
[:set, :add].each do |type|
assert_equal(e + [type], events[type].shift)
end
end
assert_equal([], events[:set])
assert_equal([], events[:add])
end
def test_trace_defined_method
events = []
name = "#{self.class}\##{__method__}"
eval <<-EOF.gsub(/^.*?: /, ""), nil, name
1: class FooBar; define_method(:foobar){}; end
2: fb = FooBar.new
3: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
4: events << [event, lineno, mid, klass] if file == name
5: })
6: fb.foobar
7: set_trace_func(nil)
EOF
[["c-return", 3, :set_trace_func, Kernel],
["line", 6, __method__, self.class],
["call", 1, :foobar, FooBar],
["return", 6, :foobar, FooBar],
["line", 7, __method__, self.class],
["c-call", 7, :set_trace_func, Kernel]].each{|e|
assert_equal(e, events.shift)
}
end
def test_remove_in_trace
bug3921 = '[ruby-dev:42350]'
ok = false
func = lambda{|e, f, l, i, b, k|
set_trace_func(nil)
ok = eval("self", b)
}
set_trace_func(func)
assert_equal(self, ok, bug3921)
end
class << self
define_method(:method_added, Module.method(:method_added))
end
def trace_by_tracepoint *trace_events
events = []
trace = nil
xyzzy = nil
_local_var = :outer
raised_exc = nil
method = :trace_by_tracepoint
_get_data = lambda{|tp|
case tp.event
when :return, :c_return
tp.return_value
when :raise
tp.raised_exception
else
:nothing
end
}
_defined_class = lambda{|tp|
klass = tp.defined_class
begin
# If it is singleton method, then return original class
# to make compatible with set_trace_func().
# This is very ad-hoc hack. I hope I can make more clean test on it.
case klass.inspect
when /Class:TracePoint/; return TracePoint
when /Class:Exception/; return Exception
else klass
end
rescue Exception => e
e
end if klass
}
trace = nil
begin
eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy'
1: trace = TracePoint.trace(*trace_events){|tp| next if !target_thread?
2: events << [tp.event, tp.lineno, tp.path, _defined_class.(tp), tp.method_id, tp.self, tp.binding.eval("_local_var"), _get_data.(tp)] if tp.path == 'xyzzy'
3: }
4: 1.times{|;_local_var| _local_var = :inner
5: tap{}
6: }
7: class XYZZY
8: _local_var = :XYZZY_outer
9: def foo
10: _local_var = :XYZZY_foo
11: bar
12: end
13: def bar
14: _local_var = :XYZZY_bar
15: tap{}
16: end
17: end
18: xyzzy = XYZZY.new
19: xyzzy.foo
20: begin; raise RuntimeError; rescue RuntimeError => raised_exc; end
21: trace.disable
EOF
self.class.class_eval{remove_const(:XYZZY)}
ensure
trace.disable if trace&.enabled?
end
answer_events = [
#
[:c_return, 1, "xyzzy", TracePoint, :trace, TracePoint, :outer, trace],
[:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing],
[:c_call, 4, 'xyzzy', Integer, :times, 1, :outer, :nothing],
[:line, 4, 'xyzzy', self.class, method, self, nil, :nothing],
[:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing],
[:c_call, 5, 'xyzzy', Kernel, :tap, self, :inner, :nothing],
[:c_return, 5, "xyzzy", Kernel, :tap, self, :inner, self],
[:c_return, 4, "xyzzy", Integer, :times, 1, :outer, 1],
[:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing],
[:c_call, 7, "xyzzy", Class, :inherited, Object, :outer, :nothing],
[:c_return, 7, "xyzzy", Class, :inherited, Object, :outer, nil],
[:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
[:line, 8, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
[:line, 9, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
[:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing],
[:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil],
[:line, 13, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
[:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing],
[:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil],
[:end, 17, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
[:line, 18, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
[:c_call, 18, "xyzzy", Class, :new, xyzzy.class, :outer, :nothing],
[:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, :nothing],
[:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, nil],
[:c_return,18, "xyzzy", Class, :new, xyzzy.class, :outer, xyzzy],
[:line, 19, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
[:call, 9, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing],
[:line, 10, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing],
[:line, 11, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, :nothing],
[:call, 13, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing],
[:line, 14, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing],
[:line, 15, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, :nothing],
[:c_call, 15, "xyzzy", Kernel, :tap, xyzzy, :XYZZY_bar, :nothing],
[:c_return,15, "xyzzy", Kernel, :tap, xyzzy, :XYZZY_bar, xyzzy],
[:return, 16, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, xyzzy],
[:return, 12, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, xyzzy],
[:line, 20, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
[:c_call, 20, "xyzzy", Kernel, :raise, self, :outer, :nothing],
[:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, :outer, :nothing],
[:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, :outer, :nothing],
[:c_return,20, "xyzzy", Exception, :initialize, raised_exc, :outer, raised_exc],
[:c_return,20, "xyzzy", Exception, :exception, RuntimeError, :outer, raised_exc],
[:c_return,20, "xyzzy", Kernel, :raise, self, :outer, nil],
[:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, :outer, :nothing],
[:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, :outer, nil],
[:raise, 20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc],
[:c_call, 20, "xyzzy", Module, :===, RuntimeError,:outer, :nothing],
[:c_return,20, "xyzzy", Module, :===, RuntimeError,:outer, true],
[:line, 21, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
[:c_call, 21, "xyzzy", TracePoint, :disable, trace, :outer, :nothing],
]
return events, answer_events
end
def trace_by_set_trace_func
events = []
trace = nil
trace = trace
xyzzy = nil
xyzzy = xyzzy
_local_var = :outer
eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy'
1: set_trace_func(lambda{|event, file, line, id, binding, klass|
2: events << [event, line, file, klass, id, binding.eval('self'), binding.eval("_local_var")] if file == 'xyzzy'
3: })
4: 1.times{|;_local_var| _local_var = :inner
5: tap{}
6: }
7: class XYZZY
8: _local_var = :XYZZY_outer
9: def foo
10: _local_var = :XYZZY_foo
11: bar
12: end
13: def bar
14: _local_var = :XYZZY_bar
15: tap{}
16: end
17: end
18: xyzzy = XYZZY.new
19: xyzzy.foo
20: begin; raise RuntimeError; rescue RuntimeError => raised_exc; end
21: set_trace_func(nil)
EOF
self.class.class_eval{remove_const(:XYZZY)}
return events
end
def test_tracepoint
events1, answer_events = *trace_by_tracepoint(:line, :class, :end, :call, :return, :c_call, :c_return, :raise)
ms = [events1, answer_events].map{|evs|
evs.map{|e|
"#{e[0]} - #{e[2]}:#{e[1]} id: #{e[4]}"
}
}
mesg = ms[0].zip(ms[1]).map{|a, b|
if a != b
"#{a} <-> #{b}"
end
}.compact.join("\n")
answer_events.zip(events1){|answer, event|
assert_equal answer, event, mesg
}
events2 = trace_by_set_trace_func
events1.zip(events2){|ev1, ev2|
ev2[0] = ev2[0].sub('-', '_').to_sym
assert_equal ev1[0..2], ev2[0..2], ev1.inspect
# event, line, file, klass, id, binding.eval('self'), binding.eval("_local_var")
assert_equal ev1[3].nil?, ev2[3].nil? # klass
assert_equal ev1[4].nil?, ev2[4].nil? # id
assert_equal ev1[6], ev2[6] # _local_var
}
[:line, :class, :end, :call, :return, :c_call, :c_return, :raise].each{|event|
events1, answer_events = *trace_by_tracepoint(event)
answer_events.find_all{|e| e[0] == event}.zip(events1){|answer_line, event_line|
assert_equal answer_line, event_line
}
}
end
def test_tracepoint_object_id
tps = []
trace = TracePoint.trace(){|tp|
next if !target_thread?
tps << tp
}
tap{}
tap{}
tap{}
trace.disable
# passed tp is unique, `trace' object which is generated by TracePoint.trace
tps.each{|tp|
assert_equal trace, tp
}
end
def test_tracepoint_access_from_outside
tp_store = nil
trace = TracePoint.trace(){|tp|
next if !target_thread?
tp_store = tp
}
tap{}
trace.disable
assert_raise(RuntimeError){tp_store.lineno}
assert_raise(RuntimeError){tp_store.event}
assert_raise(RuntimeError){tp_store.path}
assert_raise(RuntimeError){tp_store.method_id}
assert_raise(RuntimeError){tp_store.defined_class}
assert_raise(RuntimeError){tp_store.binding}
assert_raise(RuntimeError){tp_store.self}
assert_raise(RuntimeError){tp_store.return_value}
assert_raise(RuntimeError){tp_store.raised_exception}
end
def foo
end
def test_tracepoint_enable
ary = []
args = nil
trace = TracePoint.new(:call){|tp|
next if !target_thread?
ary << tp.method_id
}
foo
trace.enable{|*a|
args = a
foo
}
foo
assert_equal([:foo], ary)
assert_equal([], args)
trace = TracePoint.new{}
begin
assert_equal(false, trace.enable)
assert_equal(true, trace.enable)
trace.enable{}
assert_equal(true, trace.enable)
ensure
trace.disable
end
end
def test_tracepoint_disable
ary = []
args = nil
trace = TracePoint.trace(:call){|tp|
next if !target_thread?
ary << tp.method_id
}
foo
trace.disable{|*a|
args = a
foo
}
foo
trace.disable
assert_equal([:foo, :foo], ary)
assert_equal([], args)
trace = TracePoint.new{}
trace.enable{
assert_equal(true, trace.disable)
assert_equal(false, trace.disable)
trace.disable{}
assert_equal(false, trace.disable)
}
end
def test_tracepoint_enabled
trace = TracePoint.trace(:call){|tp|
#
}
assert_equal(true, trace.enabled?)
trace.disable{
assert_equal(false, trace.enabled?)
trace.enable{
assert_equal(true, trace.enabled?)
}
}
trace.disable
assert_equal(false, trace.enabled?)
end
def method_test_tracepoint_return_value obj
obj
end
def test_tracepoint_return_value
trace = TracePoint.new(:call, :return){|tp|
next if !target_thread?
next if tp.path != __FILE__
case tp.event
when :call
assert_raise(RuntimeError) {tp.return_value}
when :return
assert_equal("xyzzy", tp.return_value)
end
}
trace.enable{
method_test_tracepoint_return_value "xyzzy"
}
end
class XYZZYException < Exception; end
def method_test_tracepoint_raised_exception err
raise err
end
def test_tracepoint_raised_exception
trace = TracePoint.new(:call, :return){|tp|
next if !target_thread?
case tp.event
when :call, :return
assert_raise(RuntimeError) { tp.raised_exception }
when :raise
assert_equal(XYZZYError, tp.raised_exception)
end
}
trace.enable{
begin
method_test_tracepoint_raised_exception XYZZYException
rescue XYZZYException
# ok
else
raise
end
}
end
def method_for_test_tracepoint_block
yield
end
def test_tracepoint_block
events = []
TracePoint.new(:call, :return, :c_call, :b_call, :c_return, :b_return){|tp|
next if !target_thread?
events << [
tp.event, tp.method_id, tp.defined_class, tp.self.class,
/return/ =~ tp.event ? tp.return_value : nil
]
}.enable{
1.times{
3
}
method_for_test_tracepoint_block{
4
}
}
# pp events
# expected_events =
[[:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
[:c_call, :times, Integer, Integer, nil],
[:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
[:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 3],
[:c_return, :times, Integer, Integer, 1],
[:call, :method_for_test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
[:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil],
[:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4],
[:return, :method_for_test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4],
[:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4]
].zip(events){|expected, actual|
assert_equal(expected, actual)
}
end
def test_tracepoint_thread
events = []
thread_self = nil
created_thread = nil
TracePoint.new(:thread_begin, :thread_end){|tp|
events << [Thread.current,
tp.event,
tp.lineno, #=> 0
tp.path, #=> nil
tp.binding, #=> nil
tp.defined_class, #=> nil,
tp.self.class # tp.self return creating/ending thread
]
}.enable{
created_thread = Thread.new{thread_self = self}
created_thread.join
}
events.reject!{|i| i[0] != created_thread}
assert_equal(self, thread_self)
assert_equal([created_thread, :thread_begin, 0, nil, nil, nil, Thread], events[0])
assert_equal([created_thread, :thread_end, 0, nil, nil, nil, Thread], events[1])
assert_equal(2, events.size)
end
def test_tracepoint_inspect
events = []
trace = TracePoint.new{|tp|
next if !target_thread?
events << [tp.event, tp.inspect]
}
assert_equal("#<TracePoint:disabled>", trace.inspect)
trace.enable{
assert_equal("#<TracePoint:enabled>", trace.inspect)
Thread.new{}.join
}
assert_equal("#<TracePoint:disabled>", trace.inspect)
events.each{|(ev, str)|
case ev
when :line
assert_match(/ in /, str)
when :call, :c_call
assert_match(/call \`/, str) # #<TracePoint:c_call `inherited'@../trunk/test.rb:11>
when :return, :c_return
assert_match(/return \`/, str) # #<TracePoint:return `m'@../trunk/test.rb:3>
when /thread/
assert_match(/\#<Thread:/, str) # #<TracePoint:thread_end of #<Thread:0x87076c0>>
else
assert_match(/\#<TracePoint:/, str)
end
}
end
def test_tracepoint_exception_at_line
assert_raise(RuntimeError) do
TracePoint.new(:line) {
next if !target_thread?
raise
}.enable {
1
}
end
end
def test_tracepoint_exception_at_return
assert_nothing_raised(Timeout::Error, 'infinite trace') do
assert_normal_exit('def m; end; TracePoint.new(:return) {raise}.enable {m}', '', timeout: 3)
end
end
def test_tracepoint_with_multithreads
assert_nothing_raised do
TracePoint.new{
10.times{
Thread.pass
}
}.enable do
(1..10).map{
Thread.new{
1000.times{
}
}
}.each{|th|
th.join
}
end
end
end
class FOO_ERROR < RuntimeError; end
class BAR_ERROR < RuntimeError; end
def m1_test_trace_point_at_return_when_exception
m2_test_trace_point_at_return_when_exception
end
def m2_test_trace_point_at_return_when_exception
raise BAR_ERROR
end
def test_trace_point_at_return_when_exception
bug_7624 = '[ruby-core:51128] [ruby-trunk - Bug #7624]'
TracePoint.new{|tp|
next if !target_thread?
if tp.event == :return &&
tp.method_id == :m2_test_trace_point_at_return_when_exception
raise FOO_ERROR
end
}.enable do
assert_raise(FOO_ERROR, bug_7624) do
m1_test_trace_point_at_return_when_exception
end
end
bug_7668 = '[Bug #7668]'
ary = []
trace = TracePoint.new{|tp|
next if !target_thread?
ary << tp.event
raise
}
begin
trace.enable{
1.times{
raise
}
}
rescue
assert_equal([:b_call, :b_return], ary, bug_7668)
end
end
def m1_for_test_trace_point_binding_in_ifunc(arg)
arg + nil
rescue
end
def m2_for_test_trace_point_binding_in_ifunc(arg)
arg.inject(:+)
rescue
end
def test_trace_point_binding_in_ifunc
bug7774 = '[ruby-dev:46908]'
src = %q{
tp = TracePoint.new(:raise) do |tp|
tp.binding
end
tp.enable do
obj = Object.new
class << obj
include Enumerable
def each
yield 1
end
end
%s
end
}
assert_normal_exit src % %q{obj.zip({}) {}}, bug7774
assert_normal_exit src % %q{
require 'continuation'
begin
c = nil
obj.sort_by {|x| callcc {|c2| c ||= c2 }; x }
c.call
rescue RuntimeError
end
}, bug7774
# TracePoint
tp_b = nil
TracePoint.new(:raise) do |tp|
next if !target_thread?
tp_b = tp.binding
end.enable do
m1_for_test_trace_point_binding_in_ifunc(0)
assert_equal(self, eval('self', tp_b), '[ruby-dev:46960]')
m2_for_test_trace_point_binding_in_ifunc([0, nil])
assert_equal(self, eval('self', tp_b), '[ruby-dev:46960]')
end
# set_trace_func
stf_b = nil
set_trace_func ->(event, file, line, id, binding, klass) do
stf_b = binding if event == 'raise'
end
begin
m1_for_test_trace_point_binding_in_ifunc(0)
assert_equal(self, eval('self', stf_b), '[ruby-dev:46960]')
m2_for_test_trace_point_binding_in_ifunc([0, nil])
assert_equal(self, eval('self', stf_b), '[ruby-dev:46960]')
ensure
set_trace_func(nil)
end
end
def test_trace_point_binding_after_break
bug10689 = '[ruby-dev:48797]'
assert_in_out_err([], <<-INPUT, [], [], bug10689)
class Bug
include Enumerable
def each
[0].each do
yield
end
end
end
TracePoint.trace(:c_return) do |tp|
tp.binding
end
Bug.new.all? { false }
INPUT
end
def test_tracepoint_b_return_with_next
n = 0
TracePoint.new(:b_return){
next if !target_thread?
n += 1
}.enable{
3.times{
next
} # 3 times b_retun
} # 1 time b_return
assert_equal 4, n
end
def test_tracepoint_b_return_with_lambda
n = 0
TracePoint.new(:b_return){
next if !target_thread?
n+=1
}.enable{
lambda{
return
}.call # n += 1 #=> 1
3.times{
lambda{
return # n += 3 #=> 4
}.call
} # n += 3 #=> 7
begin
lambda{
raise
}.call # n += 1 #=> 8
rescue
# ignore
end # n += 1 #=> 9
}
assert_equal 9, n
end
def test_isolated_raise_in_trace
bug9088 = '[ruby-dev:47793] [Bug #9088]'
assert_in_out_err([], <<-END, [], [], bug9088)
set_trace_func proc {raise rescue nil}
1.times {break}
END
end
def test_a_call
events = []
TracePoint.new(:a_call){|tp|
next if !target_thread?
events << tp.event
}.enable{
1.times{
3
}
method_for_test_tracepoint_block{
4
}
}
assert_equal([
:b_call,
:c_call,
:b_call,
:call,
:b_call,
], events)
end
def test_a_return
events = []
TracePoint.new(:a_return){|tp|
next if !target_thread?
events << tp.event
}.enable{
1.times{
3
}
method_for_test_tracepoint_block{
4
}
}
assert_equal([
:b_return,
:c_return,
:b_return,
:return,
:b_return
], events)
end
def test_const_missing
bug59398 = '[ruby-core:59398]'
events = []
assert !defined?(MISSING_CONSTANT_59398)
TracePoint.new(:c_call, :c_return, :call, :return){|tp|
next if !target_thread?
next unless tp.defined_class == Module
# rake/ext/module.rb aliases :const_missing and Ruby uses the aliased name
# but this only happens when running the full test suite
events << [tp.event,tp.method_id] if tp.method_id == :const_missing || tp.method_id == :rake_original_const_missing
}.enable{
MISSING_CONSTANT_59398 rescue nil
}
if events.map{|e|e[1]}.include?(:rake_original_const_missing)
assert_equal([
[:call, :const_missing],
[:c_call, :rake_original_const_missing],
[:c_return, :rake_original_const_missing],
[:return, :const_missing],
], events, bug59398)
else
assert_equal([
[:c_call, :const_missing],
[:c_return, :const_missing]
], events, bug59398)
end
end
class AliasedRubyMethod
def foo; 1; end;
alias bar foo
end
def test_aliased_ruby_method
events = []
aliased = AliasedRubyMethod.new
TracePoint.new(:call, :return){|tp|
next if !target_thread?
events << [tp.event, tp.method_id]
}.enable{
aliased.bar
}
assert_equal([
[:call, :foo],
[:return, :foo]
], events, "should use original method name for tracing ruby methods")
end
class AliasedCMethod < Hash
alias original_size size
def size; original_size; end
end
def test_aliased_c_method
events = []
aliased = AliasedCMethod.new
TracePoint.new(:call, :return, :c_call, :c_return){|tp|
next if !target_thread?
events << [tp.event, tp.method_id]
}.enable{
aliased.size
}
assert_equal([
[:call, :size],
[:c_call, :size],
[:c_return, :size],
[:return, :size]
], events, "should use alias method name for tracing c methods")
end
def test_method_missing
bug59398 = '[ruby-core:59398]'
events = []
assert !respond_to?(:missing_method_59398)
TracePoint.new(:c_call, :c_return, :call, :return){|tp|
next if !target_thread?
next unless tp.defined_class == BasicObject
# rake/ext/module.rb aliases :const_missing and Ruby uses the aliased name
# but this only happens when running the full test suite
events << [tp.event,tp.method_id] if tp.method_id == :method_missing
}.enable{
missing_method_59398 rescue nil
}
assert_equal([
[:c_call, :method_missing],
[:c_return, :method_missing]
], events, bug59398)
end
class C9759
define_method(:foo){
raise
}
end
def test_define_method_on_exception
events = []
obj = C9759.new
TracePoint.new(:call, :return){|tp|
next unless target_thread?
events << [tp.event, tp.method_id]
}.enable{
obj.foo rescue nil
}
assert_equal([[:call, :foo], [:return, :foo]], events, 'Bug #9759')
events = []
begin
set_trace_func(lambda{|event, file, lineno, mid, binding, klass|
next unless target_thread?
case event
when 'call', 'return'
events << [event, mid]
end
})
obj.foo rescue nil
set_trace_func(nil)
assert_equal([['call', :foo], ['return', :foo]], events, 'Bug #9759')
ensure
end
end
class C11492
define_method(:foo_return){
return true
}
define_method(:foo_break){
break true
}
end
def test_define_method_on_return
# return
events = []
obj = C11492.new
TracePoint.new(:call, :return){|tp|
next unless target_thread?
events << [tp.event, tp.method_id]
}.enable{
obj.foo_return
}
assert_equal([[:call, :foo_return], [:return, :foo_return]], events, 'Bug #11492')
# break
events = []
obj = C11492.new
TracePoint.new(:call, :return){|tp|
next unless target_thread?
events << [tp.event, tp.method_id]
}.enable{
obj.foo_break
}
assert_equal([[:call, :foo_break], [:return, :foo_break]], events, 'Bug #11492')
# set_trace_func
# return
events = []
begin
set_trace_func(lambda{|event, file, lineno, mid, binding, klass|
next unless target_thread?
case event
when 'call', 'return'
events << [event, mid]
end
})
obj.foo_return
set_trace_func(nil)
assert_equal([['call', :foo_return], ['return', :foo_return]], events, 'Bug #11492')
ensure
end
# break
events = []
begin
set_trace_func(lambda{|event, file, lineno, mid, binding, klass|
next unless target_thread?
case event
when 'call', 'return'
events << [event, mid]
end
})
obj.foo_break
set_trace_func(nil)
assert_equal([['call', :foo_break], ['return', :foo_break]], events, 'Bug #11492')
ensure
end
end
def test_recursive
assert_in_out_err([], %q{\
TracePoint.new(:c_call){|tp|
p tp.method_id
}.enable{
p 1
}
}, %w[:p :to_s 1], [], '[Bug #9940]')
end
def method_prefix event
case event
when :call, :return
:n
when :c_call, :c_return
:c
when :b_call, :b_return
:b
end
end
def method_label tp
"#{method_prefix(tp.event)}##{tp.method_id}"
end
def assert_consistent_call_return message='', check_events: nil
check_events ||= %i(a_call a_return)
call_stack = []
TracePoint.new(*check_events){|tp|
next unless target_thread?
case tp.event.to_s
when /call/
call_stack << method_label(tp)
when /return/
frame = call_stack.pop
assert_equal(frame, method_label(tp))
end
}.enable do
yield
end
assert_equal true, call_stack.empty?
end
def method_test_rescue_should_not_cause_b_return
begin
raise
rescue
return
end
end
def method_test_ensure_should_not_cause_b_return
begin
raise
ensure
return
end
end
def test_rescue_and_ensure_should_not_cause_b_return
assert_consistent_call_return '[Bug #9957]' do
method_test_rescue_should_not_cause_b_return
begin
method_test_ensure_should_not_cause_b_return
rescue
# ignore
end
end
end
define_method(:method_test_argument_error_on_bmethod){|correct_key: 1|}
def test_argument_error_on_bmethod
assert_consistent_call_return '[Bug #9959]' do
begin
method_test_argument_error_on_bmethod(wrong_key: 2)
rescue
# ignore
end
end
end
def test_rb_rescue
assert_consistent_call_return '[Bug #9961]' do
begin
-Numeric.new
rescue
# ignore
end
end
end
def test_b_call_with_redo
assert_consistent_call_return '[Bug #9964]' do
i = 0
1.times{
break if (i+=1) > 10
redo
}
end
end
def test_no_duplicate_line_events
lines = []
dummy = []
TracePoint.new(:line){|tp|
next unless target_thread?
lines << tp.lineno
}.enable{
dummy << (1) + (2)
dummy << (1) + (2)
}
assert_equal [__LINE__ - 3, __LINE__ - 2], lines, 'Bug #10449'
end
def test_elsif_line_event
bug10763 = '[ruby-core:67720] [Bug #10763]'
lines = []
line = nil
TracePoint.new(:line){|tp|
next unless target_thread?
lines << tp.lineno if line
}.enable{
line = __LINE__
if !line
1
elsif line
2
end
}
assert_equal [line+1, line+3, line+4], lines, bug10763
end
class Bug10724
def initialize
loop{return}
end
end
def test_throwing_return_with_finish_frame
target_th = Thread.current
evs = []
TracePoint.new(:call, :return){|tp|
next unless target_thread?
evs << tp.event
}.enable{
Bug10724.new
}
assert_equal([:call, :return], evs)
end
require 'fiber'
def test_fiber_switch
# test for resume/yield
evs = []
TracePoint.new(:fiber_switch){|tp|
next unless target_thread?
evs << tp.event
}.enable{
f = Fiber.new{
Fiber.yield
Fiber.yield
Fiber.yield
}
f.resume
f.resume
f.resume
f.resume
begin
f.resume
rescue FiberError
end
}
assert_equal 8, evs.size
evs.each{|ev|
assert_equal ev, :fiber_switch
}
# test for transfer
evs = []
TracePoint.new(:fiber_switch){|tp|
next unless target_thread?
evs << tp.event
}.enable{
f1 = f2 = nil
f1 = Fiber.new{
f2.transfer
f2.transfer
Fiber.yield :ok
}
f2 = Fiber.new{
f1.transfer
f1.transfer
}
assert_equal :ok, f1.resume
}
assert_equal 6, evs.size
evs.each{|ev|
assert_equal ev, :fiber_switch
}
end
def test_tracepoint_callee_id
events = []
capture_events = Proc.new{|tp|
next unless target_thread?
events << [tp.event, tp.method_id, tp.callee_id]
}
o = Class.new{
def m
raise
end
alias alias_m m
}.new
TracePoint.new(:raise, :call, :return, &capture_events).enable{
o.alias_m rescue nil
}
assert_equal [[:call, :m, :alias_m], [:raise, :m, :alias_m], [:return, :m, :alias_m]], events
events.clear
o = Class.new{
alias alias_raise raise
def m
alias_raise
end
}.new
TracePoint.new(:c_return, &capture_events).enable{
o.m rescue nil
}
assert_equal [:c_return, :raise, :alias_raise], events[0]
events.clear
o = Class.new(String){
include Enumerable
alias each each_char
}.new('foo')
TracePoint.new(:c_return, &capture_events).enable{
o.find{true}
}
assert_equal [:c_return, :each_char, :each], events[0]
events.clear
o = Class.new{
define_method(:m){}
alias alias_m m
}.new
TracePoint.new(:call, :return, &capture_events).enable{
o.alias_m
}
assert_equal [[:call, :m, :alias_m], [:return, :m, :alias_m]], events
events.clear
o = Class.new{
def m
tap{return}
end
alias alias_m m
}.new
TracePoint.new(:return, &capture_events).enable{
o.alias_m
}
assert_equal [[:return, :m, :alias_m]], events
events.clear
o = Class.new{
define_method(:m){raise}
alias alias_m m
}.new
TracePoint.new(:b_return, :return, &capture_events).enable{
o.alias_m rescue nil
}
assert_equal [[:b_return, :m, :alias_m], [:return, :m, :alias_m]], events[0..1]
events.clear
o = Class.new{
define_method(:m){tap{return}}
alias alias_m m
}.new
TracePoint.new(:b_return, &capture_events).enable{
o.alias_m
}
assert_equal [[:b_return, :m, :alias_m], [:b_return, :m, :alias_m]], events[0..1]
events.clear
o = Class.new{
alias alias_tap tap
define_method(:m){alias_tap{return}}
}.new
TracePoint.new(:c_return, &capture_events).enable{
o.m
}
assert_equal [[:c_return, :tap, :alias_tap]], events
events.clear
c = Class.new{
alias initialize itself
}
TracePoint.new(:c_call, &capture_events).enable{
c.new
}
assert_equal [:c_call, :itself, :initialize], events[1]
events.clear
o = Class.new{
alias alias_itself itself
}.new
TracePoint.new(:c_call, :c_return, &capture_events).enable{
o.alias_itself
}
assert_equal [[:c_call, :itself, :alias_itself], [:c_return, :itself, :alias_itself]], events
events.clear
end
# tests for `return_value` with non-local exit [Bug #13369]
def tp_return_value mid
ary = []
TracePoint.new(:return, :b_return){|tp| next if !target_thread?; ary << [tp.event, tp.method_id, tp.return_value]}.enable{
send mid
}
ary.pop # last b_return event is not required.
ary
end
def f_raise
raise
rescue
return :f_raise_return
end
def f_iter1
yield
return :f_iter1_return
end
def f_iter2
yield
return :f_iter2_return
end
def f_return_in_iter
f_iter1 do
f_iter2 do
return :f_return_in_iter_return
end
end
2
end
def f_break_in_iter
f_iter1 do
f_iter2 do
break :f_break_in_iter_break
end
:f_iter1_block_value
end
:f_break_in_iter_return
end
def test_return_value_with_rescue
assert_equal [[:return, :f_raise, :f_raise_return]],
tp_return_value(:f_raise),
'[Bug #13369]'
assert_equal [[:b_return, :f_return_in_iter, nil],
[:return, :f_iter2, nil],
[:b_return, :f_return_in_iter, nil],
[:return, :f_iter1, nil],
[:return, :f_return_in_iter, :f_return_in_iter_return]],
tp_return_value(:f_return_in_iter),
'[Bug #13369]'
assert_equal [[:b_return, :f_break_in_iter, :f_break_in_iter_break],
[:return, :f_iter2, nil],
[:b_return, :f_break_in_iter, :f_iter1_block_value],
[:return, :f_iter1, :f_iter1_return],
[:return, :f_break_in_iter, :f_break_in_iter_return]],
tp_return_value(:f_break_in_iter),
'[Bug #13369]'
end
define_method(:f_last_defined) do
:f_last_defined
end
define_method(:f_return_defined) do
return :f_return_defined
end
define_method(:f_break_defined) do
return :f_break_defined
end
define_method(:f_raise_defined) do
raise
rescue
return :f_raise_defined
end
define_method(:f_break_in_rescue_defined) do
raise
rescue
break :f_break_in_rescue_defined
end
def test_return_value_with_rescue_and_defined_methods
assert_equal [[:b_return, :f_last_defined, :f_last_defined],
[:return, :f_last_defined, :f_last_defined]],
tp_return_value(:f_last_defined),
'[Bug #13369]'
assert_equal [[:b_return, :f_return_defined, nil], # current limitation
[:return, :f_return_defined, :f_return_defined]],
tp_return_value(:f_return_defined),
'[Bug #13369]'
assert_equal [[:b_return, :f_break_defined, nil],
[:return, :f_break_defined, :f_break_defined]],
tp_return_value(:f_break_defined),
'[Bug #13369]'
assert_equal [[:b_return, :f_raise_defined, nil],
[:return, :f_raise_defined, f_raise_defined]],
tp_return_value(:f_raise_defined),
'[Bug #13369]'
assert_equal [[:b_return, :f_break_in_rescue_defined, nil],
[:return, :f_break_in_rescue_defined, f_break_in_rescue_defined]],
tp_return_value(:f_break_in_rescue_defined),
'[Bug #13369]'
end
def f_iter
yield
end
def f_break_in_rescue
f_iter do
begin
raise
rescue
break :b
end
end
:f_break_in_rescue_return_value
end
def test_break_with_rescue
assert_equal [[:b_return, :f_break_in_rescue, :b],
[:return, :f_iter, nil],
[:return, :f_break_in_rescue, :f_break_in_rescue_return_value]],
tp_return_value(:f_break_in_rescue),
'[Bug #13369]'
end
def test_trace_point_raising_exception_in_bmethod_call
bug13705 = '[ruby-dev:50162]'
assert_normal_exit %q{
define_method(:m) {}
tp = TracePoint.new(:call) do
next unless target_thread?
raise ''
end
tap do
tap do
begin
tp.enable
m
rescue
end
end
end
}, bug13705
end
def test_trace_point_require_block
assert_raise(ArgumentError) { TracePoint.new(:return) }
end
def method_for_test_thread_add_trace_func
end
def test_thread_add_trace_func
events = []
base_line = __LINE__
q = Queue.new
t = Thread.new{
Thread.current.add_trace_func proc{|ev, file, line, *args|
events << [ev, line]
} # do not stop trace. They will be stopped at Thread termination.
q.push 1
_x = 1
method_for_test_thread_add_trace_func
_y = 2
}
q.pop
method_for_test_thread_add_trace_func
t.join
assert_equal ["c-return", base_line + 3], events[0]
assert_equal ["line", base_line + 6], events[1]
assert_equal ["c-call", base_line + 6], events[2]
assert_equal ["c-return", base_line + 6], events[3]
assert_equal ["line", base_line + 7], events[4]
assert_equal ["line", base_line + 8], events[5]
assert_equal ["call", base_line + -6], events[6]
assert_equal ["return", base_line + -4], events[7]
assert_equal ["line", base_line + 9], events[8]
assert_equal nil, events[9]
# other thread
events = []
m2t_q = Queue.new
t = Thread.new{
Thread.current.abort_on_exception = true
assert_equal 1, m2t_q.pop
_x = 1
method_for_test_thread_add_trace_func
_y = 2
Thread.current.set_trace_func(nil)
method_for_test_thread_add_trace_func
}
# it is dirty hack. usually we shouldn't use such technique
Thread.pass until t.status == 'sleep'
# When MJIT thread exists, t.status becomes 'sleep' even if it does not reach m2t_q.pop.
# This sleep forces it to reach m2t_q.pop for --jit-wait.
sleep 1 if RubyVM::MJIT.enabled?
t.add_trace_func proc{|ev, file, line, *args|
if file == __FILE__
events << [ev, line]
end
}
method_for_test_thread_add_trace_func
m2t_q.push 1
t.join
assert_equal ["c-return", base_line + 31], events[0]
assert_equal ["line", base_line + 32], events[1]
assert_equal ["line", base_line + 33], events[2]
assert_equal ["call", base_line + -6], events[3]
assert_equal ["return", base_line + -4], events[4]
assert_equal ["line", base_line + 34], events[5]
assert_equal ["line", base_line + 35], events[6]
assert_equal ["c-call", base_line + 35], events[7] # Thread.current
assert_equal ["c-return", base_line + 35], events[8] # Thread.current
assert_equal ["c-call", base_line + 35], events[9] # Thread#set_trace_func
assert_equal nil, events[10]
end
end