rb-threadframe 0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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