rb-threadframe 0.32

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ # Boilerplate to pull shared object and helper routines.
2
+ require_relative '../ext/thread_frame'
3
+ require_relative 'iseq_extra'
@@ -0,0 +1,11 @@
1
+ def cfunc_loc
2
+ ftype, file = [nil, nil]
3
+ 1.times do
4
+ 1.times do
5
+ f = RubyVM::ThreadFrame::current.prev
6
+ ftype = f.type
7
+ file = f.source_container[1]
8
+ end
9
+ end
10
+ return [ftype, file]
11
+ end
@@ -0,0 +1,45 @@
1
+ require 'test/unit'
2
+ require_relative '../../ext/thread_frame'
3
+
4
+ class TestARGC < Test::Unit::TestCase
5
+
6
+ def setup
7
+ @original_compile_option = RubyVM::InstructionSequence.compile_option
8
+ RubyVM::InstructionSequence.compile_option = {
9
+ :trace_instruction => false,
10
+ :specialized_instruction => false
11
+ }
12
+ end
13
+
14
+ def teardown
15
+ set_trace_func(nil)
16
+ RubyVM::InstructionSequence.compile_option = @original_compile_option
17
+ end
18
+
19
+ def test_C_argc
20
+ cmd='File.basename("/tmp/foo.rb");File.basename("/tmp/foo.rb",".rb")'
21
+ iseq = RubyVM::InstructionSequence.compile(cmd)
22
+ events = []
23
+ all_events = []
24
+ eval <<-EOF.gsub(/^.*?: /, "")
25
+ 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
26
+ 2: tf = RubyVM::ThreadFrame.current.prev(1)
27
+ 3: all_events << [tf.argc, tf.arity, tf.type, mid]
28
+ 4: if :basename == mid
29
+ 5: events << [tf.argc, tf.arity, tf.type, mid]
30
+ 6: end
31
+ 7: })
32
+ 8: iseq.eval
33
+ 9: set_trace_func(nil)
34
+ EOF
35
+ # p all_events
36
+ assert_equal([[1, -1, "CFUNC", :basename], # 1-arg c-call
37
+ [1, -1, "CFUNC", :basename], # 1-arg c-return
38
+ [2, -1, "CFUNC", :basename], # 2-arg c-call
39
+ [2, -1, "CFUNC", :basename] # 2-arg c-return
40
+ ], events)
41
+ end
42
+ end
43
+
44
+ # We want to double-check we didn't mess up any pointers somewhere.
45
+ at_exit { GC.start }
@@ -0,0 +1,44 @@
1
+ require 'test/unit'
2
+ require_relative '../../ext/thread_frame'
3
+
4
+ def outside(a)
5
+ return eval('a', RubyVM::ThreadFrame.new(Thread::current).binding)
6
+ end
7
+
8
+ $a = 10
9
+
10
+ class TestBinding < Test::Unit::TestCase
11
+ def test_basic
12
+ a = 1
13
+ c = 0
14
+ assert_equal(5, outside(5))
15
+ tf = RubyVM::ThreadFrame.new(Thread::current)
16
+ b = tf.binding
17
+ assert_equal(1, eval('a', b))
18
+ assert_equal(10, eval('$a', b))
19
+ assert_equal(self, tf.self)
20
+ 1.times do |i;a|
21
+ tf2 = Thread::current.threadframe()
22
+ b2 = tf2.binding
23
+ a = 2
24
+ assert_equal(2, eval('a', b2))
25
+ assert_equal(0, eval('c', b2))
26
+
27
+ # Times is C inline so prev we can't get a binding for it
28
+ # But we can for use the instruction sequence before that.
29
+ assert_equal(1, eval('a', tf2.prev(2).binding))
30
+ end
31
+ def inner(a)
32
+ tf3 = Thread::current.threadframe()
33
+ b3 = tf3.binding
34
+ if a == 4
35
+ assert_equal(4, eval('a', b3))
36
+ inner(a-1)
37
+ else
38
+ assert_equal(3, eval('a', b3))
39
+ assert_equal(4, eval('a', tf3.prev.binding))
40
+ end
41
+ end
42
+ inner(4)
43
+ end
44
+ end
@@ -0,0 +1,40 @@
1
+ require 'test/unit'
2
+ require_relative '../../ext/thread_frame'
3
+
4
+ class TestProc < Test::Unit::TestCase
5
+ def test_basic
6
+ @tf = RubyVM::ThreadFrame::current
7
+ assert_equal(false, @tf.invalid?,
8
+ 'Frame should be valid right after ThreadFrame::current')
9
+ def notgood(test_tf=nil)
10
+ # FIXME
11
+ # if test_tf
12
+ # assert_equal(test_tf != @tf, test_tf.invalid?)
13
+ # end
14
+ return RubyVM::ThreadFrame::current
15
+ end
16
+
17
+ def inner_fn(tf)
18
+ tf.invalid?
19
+ end
20
+
21
+ invalid_tf = notgood
22
+ # FIXME:
23
+ # assert_equal(true, invalid_tf.invalid?,
24
+ # 'current thread frame should not be returned from a fn')
25
+ # begin
26
+ # b = invalid_tf.binding
27
+ # assert false, 'Should have raised an ThreadFrameError'
28
+ # rescue ThreadFrameError
29
+ # assert true
30
+ # end
31
+ # Add a new local variable
32
+ x = 5
33
+ assert_equal(false, @tf.invalid?,
34
+ 'Frame should still be valid after adding more locals')
35
+ assert_equal(false, inner_fn(@tf),
36
+ 'outer thread frame should ok inside a called fn')
37
+ notgood(invalid_tf)
38
+ notgood(@tf)
39
+ end
40
+ end
@@ -0,0 +1,61 @@
1
+ require 'test/unit'
2
+ require_relative '../../ext/thread_frame'
3
+
4
+ class TestISeqBrkpt < Test::Unit::TestCase
5
+
6
+ def test_iseq_brkpt
7
+ iseq = RubyVM::ThreadFrame.current.iseq
8
+ assert iseq
9
+ assert_equal(nil, iseq.brkpts)
10
+ assert_equal(true, iseq.brkpt_alloc)
11
+ assert_equal([], iseq.brkpts)
12
+ assert_equal(false, iseq.brkpt_alloc)
13
+
14
+ offlines = iseq.offsetlines
15
+ offsets = offlines.values
16
+ [offsets.min[0], offsets.max[0]].each do |offset|
17
+ assert_equal(true, iseq.brkpt_set(offset))
18
+ assert_equal(true, iseq.brkpt_set(offset))
19
+ assert_equal(true, iseq.brkpt_get(offset),
20
+ "Offset %d should be set" % offset)
21
+ assert_equal(true, iseq.brkpt_unset(offset),
22
+ "Offset %d should be unset" % offset)
23
+ assert_equal(false, iseq.brkpt_get(offset),
24
+ "Offset %d should be unset now" % offset)
25
+ assert_equal(true, iseq.brkpt_unset(offset),
26
+ "Offset %d should be unset again" % offset)
27
+ iseq.brkpt_set(offset) # For test below
28
+ end
29
+ assert_equal(2, iseq.brkpts.size)
30
+
31
+ max_offset = offsets.max[0]
32
+
33
+ assert_raises TypeError do iseq.brkpt_get(iseq.iseq_size) end
34
+
35
+ assert_equal(true, iseq.brkpt_dealloc)
36
+ assert_equal(false, iseq.brkpt_dealloc)
37
+ assert_equal(true, iseq.brkpt_unset(max_offset),
38
+ "Offset %d should be unset even when deallocated" % max_offset)
39
+
40
+ assert_raises TypeError do iseq.brkpt_set('a') end
41
+ end
42
+
43
+ def test_iseq_brkpt_set
44
+ add_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
45
+ if 'brkpt' == event
46
+ $saw_brkpt = true
47
+ end
48
+ })
49
+
50
+ $saw_brkpt = false
51
+ tf = RubyVM::ThreadFrame.current
52
+ tf.iseq.offsetlines.keys.each do |offset|
53
+ tf.iseq.brkpt_set(offset)
54
+ end
55
+ assert_equal(true, $saw_brkpt)
56
+ clear_trace_func
57
+ end
58
+ end
59
+
60
+ # We want to double-check we didn't mess up any pointers somewhere.
61
+ at_exit { GC.start }
@@ -0,0 +1,121 @@
1
+ require 'test/unit'
2
+ require_relative '../../ext/thread_frame'
3
+
4
+ class TestISeq < Test::Unit::TestCase
5
+
6
+ class C
7
+ def initialize(test_obj, optional=true)
8
+ iseq = RubyVM::ThreadFrame::current.iseq
9
+ test_obj.assert_equal('test_obj', iseq.local_name(0))
10
+ test_obj.assert_equal(1, iseq.arity)
11
+ test_obj.assert_equal(-1, iseq.arg_block)
12
+ test_obj.assert_equal(1, iseq.argc)
13
+ end
14
+ end
15
+
16
+ def test_fields
17
+ start_lineno = __LINE__ - 1;
18
+ iseq = RubyVM::ThreadFrame::current.iseq
19
+ assert iseq
20
+ assert_equal('test_fields', iseq.name)
21
+ ## FIXME: Why does this fail?
22
+ ## assert_equal(start_lineno, iseq.lineno, 'iseq.lineno')
23
+ assert_equal(0, iseq.arity)
24
+ assert_equal(-1, iseq.arg_block)
25
+ assert_equal(0, iseq.argc)
26
+ assert_equal(0, iseq.arg_opts)
27
+ assert_equal(4, iseq.local_table_size)
28
+ x = lambda do |x,y|
29
+ iseq = RubyVM::ThreadFrame::current.iseq
30
+ assert iseq
31
+ assert_equal(2, iseq.arity)
32
+ assert_equal(-1, iseq.arg_block)
33
+ assert_equal(2, iseq.argc)
34
+ assert_equal(0, iseq.arg_opts)
35
+ assert_equal(3, iseq.local_table_size)
36
+ ['x', 'y'].each_with_index do |expect, i|
37
+ assert_equal(expect, iseq.local_name(i))
38
+ end
39
+
40
+ assert_equal('x', iseq.local_name(-1))
41
+ assert_raise IndexError do
42
+ x = iseq.local_name(10)
43
+ end
44
+ end
45
+ x.call(1,2)
46
+
47
+ x = Proc.new do |a|
48
+ iseq = RubyVM::ThreadFrame::current.iseq
49
+ assert iseq
50
+ assert_equal(1, iseq.arity)
51
+ assert_equal(-1, iseq.arg_block)
52
+ assert_equal(1, iseq.argc)
53
+ assert_equal(0, iseq.arg_opts)
54
+ assert_equal(1, iseq.local_table_size)
55
+ ['a'].each_with_index do |expect, i|
56
+ assert_equal(expect, iseq.local_name(i))
57
+ end
58
+ assert_raises IndexError do
59
+ x = iseq.local_name(100)
60
+ end
61
+ assert_raises TypeError do
62
+ p iseq.local_name('a')
63
+ end
64
+ end
65
+ x.call(1,2)
66
+ C.new(self, 5)
67
+ end_lineno = __LINE__ + 3
68
+ assert_equal((start_lineno..end_lineno),
69
+ method(:test_fields).iseq.line_range, 'line range')
70
+ end
71
+
72
+ def test_iseq_equal
73
+ tf = RubyVM::ThreadFrame.current
74
+ tf2 = RubyVM::ThreadFrame.current
75
+ while !tf.iseq do
76
+ tf = tf.prev
77
+ tf2 = tf2.prev
78
+ end
79
+ assert_equal(false, tf.iseq.equal?(nil))
80
+ assert_equal(true, tf.iseq.equal?(tf.iseq))
81
+ assert_equal(true, tf.iseq.equal?(tf2.iseq))
82
+ tf2 = tf2.prev
83
+ while !tf2.iseq do tf2 = tf2.prev end
84
+ assert_equal(false, tf.iseq.equal?(tf2.iseq))
85
+ assert_raises TypeError do
86
+ tf.iseq.equal?(tf)
87
+ end
88
+ end
89
+
90
+ # FIXME: killcache interface will probably change. Try make less sensitive
91
+ # to compile sequence
92
+ def test_iseq_killcache
93
+ iseq = RubyVM::ThreadFrame.current.iseq
94
+ count = iseq.killcache
95
+ if 0 != count
96
+ assert_equal(0, iseq.killcache,
97
+ 'Doing killcache a second time should do nothing')
98
+ end
99
+ end
100
+
101
+ def test_offsetlines
102
+ start = __LINE__ - 1
103
+ tf = RubyVM::ThreadFrame::current
104
+ iseq = tf.iseq
105
+ offlines = iseq.offsetlines
106
+ pc = tf.pc_offset
107
+ assert_equal(__LINE__, offlines[pc][0]+1)
108
+ offlines.values.each do |value|
109
+ assert(value[0] >= start, "#{value[0]} should be not less than starting line #{start}")
110
+ # Rough count of # of lines is less than 20
111
+ assert(value[0] < start + 20, "#{value[0]} should be less than starting line #{start}")
112
+ end
113
+ offlines.keys.each do |offset|
114
+ assert_equal offlines[offset][0], iseq.offset2lines(offset)[0]
115
+ end
116
+ end
117
+
118
+ end
119
+
120
+ # We want to double-check we didn't mess up any pointers somewhere.
121
+ at_exit { GC.start }
@@ -0,0 +1,57 @@
1
+ require 'test/unit'
2
+ require_relative '../../lib/iseq_extra'
3
+
4
+ class TestLibISeqExtra < Test::Unit::TestCase
5
+
6
+ def test_basic
7
+ tf = RubyVM::ThreadFrame.current
8
+ iseq = tf.iseq
9
+ # See that we get the same line numbers
10
+ assert_equal(iseq.offsetlines.values.flatten.uniq.sort,
11
+ iseq.lineoffsets.keys.sort)
12
+ # See that we get the same offsets
13
+ assert_equal(iseq.lineoffsets.values.flatten.uniq.sort,
14
+ iseq.offsetlines.keys.sort)
15
+
16
+ assert_equal(iseq.lineoffsets[__LINE__].sort,
17
+ iseq.line2offsets(__LINE__-1).sort)
18
+
19
+ assert_equal([], iseq.line2offsets(__LINE__+100))
20
+ top_iseq = tf.prev(-1).iseq
21
+ assert_equal('method', RubyVM::InstructionSequence::TYPE2STR[top_iseq.type])
22
+
23
+ iseq2 = tf.iseq
24
+ # Different object ids...
25
+ assert_not_equal(iseq.object_id, iseq2.object_id)
26
+ # but same SHA1s..
27
+ assert_equal(iseq.sha1, iseq2.sha1)
28
+ # and equal instruction sequences
29
+ assert_equal(true, iseq.equal?(iseq2))
30
+
31
+ # Now try two different iseqs
32
+ tf2 = tf.prev
33
+ tf2 = tf2.prev until !tf2.prev || tf2.prev.iseq
34
+ if tf2
35
+ assert_not_equal(iseq.sha1, tf2.iseq.sha1)
36
+ assert_equal(false, iseq.equal?(tf2.iseq))
37
+ end
38
+ end
39
+
40
+ def test_format_args
41
+ # These prototypes are from IRB
42
+ def evaluate(context, statements, file = __FILE__, line = __LINE__); end
43
+ def def_extend_command(cmd_name, load_file, *aliases); end
44
+
45
+ assert_equal('context, statements; file, line',
46
+ method(:evaluate).iseq.format_args)
47
+ assert_equal('cmd_name, load_file; aliases',
48
+ method(:def_extend_command).iseq.format_args)
49
+ end
50
+
51
+ def test_sha1
52
+ sha1 = proc{ 5 }.iseq.sha1
53
+ assert_equal(40, sha1.size)
54
+ assert_equal(0, sha1 =~ /^[0-9a-f]+$/)
55
+ end
56
+
57
+ end
@@ -0,0 +1,54 @@
1
+ require 'test/unit'
2
+ require_relative '../../ext/thread_frame'
3
+
4
+ class TestThread < Test::Unit::TestCase
5
+ def test_stack_size_with_prev
6
+ tf = RubyVM::ThreadFrame.new(Thread::current)
7
+
8
+ # valid prev counts are -stack_size .. stack_size-1
9
+ n = tf.stack_size
10
+ assert_equal(nil, tf.prev(n),
11
+ 'Should have accessed past the top of the stack')
12
+ top_frame = tf.prev(n-1)
13
+ assert(top_frame,
14
+ 'Should have gotten the top frame')
15
+ assert_equal(tf, tf.prev(-n),
16
+ 'tf.prev(tf.stack_size) == tf')
17
+ assert(top_frame)
18
+ assert_equal('TOP', top_frame.type,
19
+ 'The type of the top frame should be "TOP"')
20
+ end
21
+
22
+ def test_prev
23
+
24
+ assert RubyVM::ThreadFrame::prev(Thread::current, 0)
25
+ assert(RubyVM::ThreadFrame::prev(Thread::current, 2),
26
+ 'There should be at least two prior frames')
27
+
28
+ top_frame = RubyVM::ThreadFrame::prev(Thread::current, -1)
29
+ assert(top_frame, 'Should give back the top frame')
30
+ assert_equal('TOP', top_frame.type,
31
+ 'The type of the top frame should be "TOP"')
32
+
33
+ assert_equal(nil, RubyVM::ThreadFrame::prev(Thread::current, 1000))
34
+
35
+ tf = RubyVM::ThreadFrame::current.prev
36
+
37
+ assert tf.prev(2)
38
+ assert_equal(tf, tf.prev(0),
39
+ 'tf.prev(0) is defined as be tf')
40
+ assert tf.prev(-1)
41
+ assert_equal(nil, tf.prev(1000))
42
+
43
+ assert_raises TypeError do
44
+ tf.prev('a')
45
+ end
46
+ assert_raises TypeError do
47
+ RubyVM::ThreadFrame::prev([1])
48
+ end
49
+ assert_raises TypeError do
50
+ RubyVM::ThreadFrame::prev(RubyVM::ThreadFrame::current, [1])
51
+ end
52
+
53
+ end
54
+ end