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