1# frozen_string_literal: false 2require 'test/unit' 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 @target_thread = Thread.current 12 end 13 14 def teardown 15 set_trace_func(nil) 16 RubyVM::InstructionSequence.compile_option = @original_compile_option 17 @target_thread = nil 18 end 19 20 def target_thread? 21 Thread.current == @target_thread 22 end 23 24 def test_c_call 25 events = [] 26 name = "#{self.class}\##{__method__}" 27 eval <<-EOF.gsub(/^.*?: /, ""), nil, name 28 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| 29 2: events << [event, lineno, mid, klass] if file == name 30 3: }) 31 4: x = 1 + 1 32 5: set_trace_func(nil) 33 EOF 34 assert_equal(["c-return", 1, :set_trace_func, Kernel], 35 events.shift) 36 assert_equal(["line", 4, __method__, self.class], 37 events.shift) 38 assert_equal(["c-call", 4, :+, Integer], 39 events.shift) 40 assert_equal(["c-return", 4, :+, Integer], 41 events.shift) 42 assert_equal(["line", 5, __method__, self.class], 43 events.shift) 44 assert_equal(["c-call", 5, :set_trace_func, Kernel], 45 events.shift) 46 assert_equal([], events) 47 end 48 49 def test_call 50 events = [] 51 name = "#{self.class}\##{__method__}" 52 eval <<-EOF.gsub(/^.*?: /, ""), nil, name 53 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| 54 2: events << [event, lineno, mid, klass] if file == name 55 3: }) 56 4: def add(x, y) 57 5: x + y 58 6: end 59 7: x = add(1, 1) 60 8: set_trace_func(nil) 61 EOF 62 assert_equal(["c-return", 1, :set_trace_func, Kernel], 63 events.shift) 64 assert_equal(["line", 4, __method__, self.class], 65 events.shift) 66 assert_equal(["c-call", 4, :method_added, self.class], 67 events.shift) 68 assert_equal(["c-return", 4, :method_added, self.class], 69 events.shift) 70 assert_equal(["line", 7, __method__, self.class], 71 events.shift) 72 assert_equal(["call", 4, :add, self.class], 73 events.shift) 74 assert_equal(["line", 5, :add, self.class], 75 events.shift) 76 assert_equal(["c-call", 5, :+, Integer], 77 events.shift) 78 assert_equal(["c-return", 5, :+, Integer], 79 events.shift) 80 assert_equal(["return", 6, :add, self.class], 81 events.shift) 82 assert_equal(["line", 8, __method__, self.class], 83 events.shift) 84 assert_equal(["c-call", 8, :set_trace_func, Kernel], 85 events.shift) 86 assert_equal([], events) 87 end 88 89 def test_class 90 events = [] 91 name = "#{self.class}\##{__method__}" 92 eval <<-EOF.gsub(/^.*?: /, ""), nil, name 93 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| 94 2: events << [event, lineno, mid, klass] if file == name 95 3: }) 96 4: class Foo 97 5: def bar 98 6: end 99 7: end 100 8: x = Foo.new.bar 101 9: set_trace_func(nil) 102 EOF 103 assert_equal(["c-return", 1, :set_trace_func, Kernel], 104 events.shift) 105 assert_equal(["line", 4, __method__, self.class], 106 events.shift) 107 assert_equal(["c-call", 4, :inherited, Class], 108 events.shift) 109 assert_equal(["c-return", 4, :inherited, Class], 110 events.shift) 111 assert_equal(["class", 4, nil, nil], 112 events.shift) 113 assert_equal(["line", 5, nil, nil], 114 events.shift) 115 assert_equal(["c-call", 5, :method_added, Module], 116 events.shift) 117 assert_equal(["c-return", 5, :method_added, Module], 118 events.shift) 119 assert_equal(["end", 7, nil, nil], 120 events.shift) 121 assert_equal(["line", 8, __method__, self.class], 122 events.shift) 123 assert_equal(["c-call", 8, :new, Class], 124 events.shift) 125 assert_equal(["c-call", 8, :initialize, BasicObject], 126 events.shift) 127 assert_equal(["c-return", 8, :initialize, BasicObject], 128 events.shift) 129 assert_equal(["c-return", 8, :new, Class], 130 events.shift) 131 assert_equal(["call", 5, :bar, Foo], 132 events.shift) 133 assert_equal(["return", 6, :bar, Foo], 134 events.shift) 135 assert_equal(["line", 9, __method__, self.class], 136 events.shift) 137 assert_equal(["c-call", 9, :set_trace_func, Kernel], 138 events.shift) 139 assert_equal([], events) 140 end 141 142 def test_return # [ruby-dev:38701] 143 events = [] 144 name = "#{self.class}\##{__method__}" 145 eval <<-EOF.gsub(/^.*?: /, ""), nil, name 146 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| 147 2: events << [event, lineno, mid, klass] if file == name 148 3: }) 149 4: def meth_return(a) 150 5: return if a 151 6: return 152 7: end 153 8: meth_return(true) 154 9: meth_return(false) 155 10: set_trace_func(nil) 156 EOF 157 assert_equal(["c-return", 1, :set_trace_func, Kernel], 158 events.shift) 159 assert_equal(["line", 4, __method__, self.class], 160 events.shift) 161 assert_equal(["c-call", 4, :method_added, self.class], 162 events.shift) 163 assert_equal(["c-return", 4, :method_added, self.class], 164 events.shift) 165 assert_equal(["line", 8, __method__, self.class], 166 events.shift) 167 assert_equal(["call", 4, :meth_return, self.class], 168 events.shift) 169 assert_equal(["line", 5, :meth_return, self.class], 170 events.shift) 171 assert_equal(["return", 5, :meth_return, self.class], 172 events.shift) 173 assert_equal(["line", 9, :test_return, self.class], 174 events.shift) 175 assert_equal(["call", 4, :meth_return, self.class], 176 events.shift) 177 assert_equal(["line", 5, :meth_return, self.class], 178 events.shift) 179 assert_equal(["return", 7, :meth_return, self.class], 180 events.shift) 181 assert_equal(["line", 10, :test_return, self.class], 182 events.shift) 183 assert_equal(["c-call", 10, :set_trace_func, Kernel], 184 events.shift) 185 assert_equal([], events) 186 end 187 188 def test_return2 # [ruby-core:24463] 189 events = [] 190 name = "#{self.class}\##{__method__}" 191 eval <<-EOF.gsub(/^.*?: /, ""), nil, name 192 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| 193 2: events << [event, lineno, mid, klass] if file == name 194 3: }) 195 4: def meth_return2 196 5: a = 5 197 6: return a 198 7: end 199 8: meth_return2 200 9: set_trace_func(nil) 201 EOF 202 assert_equal(["c-return", 1, :set_trace_func, Kernel], 203 events.shift) 204 assert_equal(["line", 4, __method__, self.class], 205 events.shift) 206 assert_equal(["c-call", 4, :method_added, self.class], 207 events.shift) 208 assert_equal(["c-return", 4, :method_added, self.class], 209 events.shift) 210 assert_equal(["line", 8, __method__, self.class], 211 events.shift) 212 assert_equal(["call", 4, :meth_return2, self.class], 213 events.shift) 214 assert_equal(["line", 5, :meth_return2, self.class], 215 events.shift) 216 assert_equal(["line", 6, :meth_return2, self.class], 217 events.shift) 218 assert_equal(["return", 7, :meth_return2, self.class], 219 events.shift) 220 assert_equal(["line", 9, :test_return2, self.class], 221 events.shift) 222 assert_equal(["c-call", 9, :set_trace_func, Kernel], 223 events.shift) 224 assert_equal([], events) 225 end 226 227 def test_raise 228 events = [] 229 name = "#{self.class}\##{__method__}" 230 eval <<-EOF.gsub(/^.*?: /, ""), nil, name 231 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| 232 2: events << [event, lineno, mid, klass] if file == name 233 3: }) 234 4: begin 235 5: raise TypeError, "error" 236 6: rescue TypeError 237 7: end 238 8: set_trace_func(nil) 239 EOF 240 assert_equal(["c-return", 1, :set_trace_func, Kernel], 241 events.shift) 242 assert_equal(["line", 5, __method__, self.class], 243 events.shift) 244 assert_equal(["c-call", 5, :raise, Kernel], 245 events.shift) 246 assert_equal(["c-call", 5, :exception, Exception], 247 events.shift) 248 assert_equal(["c-call", 5, :initialize, Exception], 249 events.shift) 250 assert_equal(["c-return", 5, :initialize, Exception], 251 events.shift) 252 assert_equal(["c-return", 5, :exception, Exception], 253 events.shift) 254 assert_equal(["c-return", 5, :raise, Kernel], 255 events.shift) 256 assert_equal(["c-call", 5, :backtrace, Exception], 257 events.shift) 258 assert_equal(["c-return", 5, :backtrace, Exception], 259 events.shift) 260 assert_equal(["raise", 5, :test_raise, TestSetTraceFunc], 261 events.shift) 262 assert_equal(["c-call", 6, :===, Module], 263 events.shift) 264 assert_equal(["c-return", 6, :===, Module], 265 events.shift) 266 assert_equal(["line", 8, __method__, self.class], 267 events.shift) 268 assert_equal(["c-call", 8, :set_trace_func, Kernel], 269 events.shift) 270 assert_equal([], events) 271 end 272 273 def test_break # [ruby-core:27606] [Bug #2610] 274 events = [] 275 name = "#{self.class}\##{__method__}" 276 eval <<-EOF.gsub(/^.*?: /, ""), nil, name 277 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| 278 2: events << [event, lineno, mid, klass] if file == name 279 3: }) 280 4: [1,2,3].any? {|n| n} 281 8: set_trace_func(nil) 282 EOF 283 284 [["c-return", 1, :set_trace_func, Kernel], 285 ["line", 4, __method__, self.class], 286 ["c-call", 4, :any?, Array], 287 ["line", 4, __method__, self.class], 288 ["c-return", 4, :any?, Array], 289 ["line", 5, __method__, self.class], 290 ["c-call", 5, :set_trace_func, Kernel]].each.with_index{|e, i| 291 assert_equal(e, events.shift, "mismatch on #{i}th trace") 292 } 293 end 294 295 def test_invalid_proc 296 assert_raise(TypeError) { set_trace_func(1) } 297 end 298 299 def test_raise_in_trace 300 set_trace_func proc {raise rescue nil} 301 assert_equal(42, (raise rescue 42), '[ruby-core:24118]') 302 end 303 304 def test_thread_trace 305 events = {:set => [], :add => []} 306 prc = Proc.new { |event, file, lineno, mid, binding, klass| 307 events[:set] << [event, lineno, mid, klass, :set] 308 } 309 prc = prc # suppress warning 310 prc2 = Proc.new { |event, file, lineno, mid, binding, klass| 311 events[:add] << [event, lineno, mid, klass, :add] 312 } 313 prc2 = prc2 # suppress warning 314 315 th = Thread.new do 316 th = Thread.current 317 name = "#{self.class}\##{__method__}" 318 eval <<-EOF.gsub(/^.*?: /, ""), nil, name 319 1: th.set_trace_func(prc) 320 2: th.add_trace_func(prc2) 321 3: class ThreadTraceInnerClass 322 4: def foo 323 5: _x = 1 + 1 324 6: end 325 7: end 326 8: ThreadTraceInnerClass.new.foo 327 9: th.set_trace_func(nil) 328 EOF 329 end 330 th.join 331 332 [["c-return", 1, :set_trace_func, Thread, :set], 333 ["line", 2, __method__, self.class, :set], 334 ["c-call", 2, :add_trace_func, Thread, :set]].each do |e| 335 assert_equal(e, events[:set].shift) 336 end 337 338 [["c-return", 2, :add_trace_func, Thread], 339 ["line", 3, __method__, self.class], 340 ["c-call", 3, :inherited, Class], 341 ["c-return", 3, :inherited, Class], 342 ["class", 3, nil, nil], 343 ["line", 4, nil, nil], 344 ["c-call", 4, :method_added, Module], 345 ["c-return", 4, :method_added, Module], 346 ["end", 7, nil, nil], 347 ["line", 8, __method__, self.class], 348 ["c-call", 8, :new, Class], 349 ["c-call", 8, :initialize, BasicObject], 350 ["c-return", 8, :initialize, BasicObject], 351 ["c-return", 8, :new, Class], 352 ["call", 4, :foo, ThreadTraceInnerClass], 353 ["line", 5, :foo, ThreadTraceInnerClass], 354 ["c-call", 5, :+, Integer], 355 ["c-return", 5, :+, Integer], 356 ["return", 6, :foo, ThreadTraceInnerClass], 357 ["line", 9, __method__, self.class], 358 ["c-call", 9, :set_trace_func, Thread]].each do |e| 359 [:set, :add].each do |type| 360 assert_equal(e + [type], events[type].shift) 361 end 362 end 363 assert_equal([], events[:set]) 364 assert_equal([], events[:add]) 365 end 366 367 def test_trace_defined_method 368 events = [] 369 name = "#{self.class}\##{__method__}" 370 eval <<-EOF.gsub(/^.*?: /, ""), nil, name 371 1: class FooBar; define_method(:foobar){}; end 372 2: fb = FooBar.new 373 3: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| 374 4: events << [event, lineno, mid, klass] if file == name 375 5: }) 376 6: fb.foobar 377 7: set_trace_func(nil) 378 EOF 379 380 [["c-return", 3, :set_trace_func, Kernel], 381 ["line", 6, __method__, self.class], 382 ["call", 1, :foobar, FooBar], 383 ["return", 6, :foobar, FooBar], 384 ["line", 7, __method__, self.class], 385 ["c-call", 7, :set_trace_func, Kernel]].each{|e| 386 assert_equal(e, events.shift) 387 } 388 end 389 390 def test_remove_in_trace 391 bug3921 = '[ruby-dev:42350]' 392 ok = false 393 func = lambda{|e, f, l, i, b, k| 394 set_trace_func(nil) 395 ok = eval("self", b) 396 } 397 398 set_trace_func(func) 399 assert_equal(self, ok, bug3921) 400 end 401 402 class << self 403 define_method(:method_added, Module.method(:method_added)) 404 end 405 406 def trace_by_tracepoint *trace_events 407 events = [] 408 trace = nil 409 xyzzy = nil 410 _local_var = :outer 411 raised_exc = nil 412 method = :trace_by_tracepoint 413 _get_data = lambda{|tp| 414 case tp.event 415 when :return, :c_return 416 tp.return_value 417 when :raise 418 tp.raised_exception 419 else 420 :nothing 421 end 422 } 423 _defined_class = lambda{|tp| 424 klass = tp.defined_class 425 begin 426 # If it is singleton method, then return original class 427 # to make compatible with set_trace_func(). 428 # This is very ad-hoc hack. I hope I can make more clean test on it. 429 case klass.inspect 430 when /Class:TracePoint/; return TracePoint 431 when /Class:Exception/; return Exception 432 else klass 433 end 434 rescue Exception => e 435 e 436 end if klass 437 } 438 439 trace = nil 440 begin 441 eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy' 442 1: trace = TracePoint.trace(*trace_events){|tp| next if !target_thread? 443 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' 444 3: } 445 4: 1.times{|;_local_var| _local_var = :inner 446 5: tap{} 447 6: } 448 7: class XYZZY 449 8: _local_var = :XYZZY_outer 450 9: def foo 451 10: _local_var = :XYZZY_foo 452 11: bar 453 12: end 454 13: def bar 455 14: _local_var = :XYZZY_bar 456 15: tap{} 457 16: end 458 17: end 459 18: xyzzy = XYZZY.new 460 19: xyzzy.foo 461 20: begin; raise RuntimeError; rescue RuntimeError => raised_exc; end 462 21: trace.disable 463 EOF 464 self.class.class_eval{remove_const(:XYZZY)} 465 ensure 466 trace.disable if trace&.enabled? 467 end 468 469 answer_events = [ 470 # 471 [:c_return, 1, "xyzzy", TracePoint, :trace, TracePoint, :outer, trace], 472 [:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing], 473 [:c_call, 4, 'xyzzy', Integer, :times, 1, :outer, :nothing], 474 [:line, 4, 'xyzzy', self.class, method, self, nil, :nothing], 475 [:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing], 476 [:c_call, 5, 'xyzzy', Kernel, :tap, self, :inner, :nothing], 477 [:c_return, 5, "xyzzy", Kernel, :tap, self, :inner, self], 478 [:c_return, 4, "xyzzy", Integer, :times, 1, :outer, 1], 479 [:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing], 480 [:c_call, 7, "xyzzy", Class, :inherited, Object, :outer, :nothing], 481 [:c_return, 7, "xyzzy", Class, :inherited, Object, :outer, nil], 482 [:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing], 483 [:line, 8, "xyzzy", nil, nil, xyzzy.class, nil, :nothing], 484 [:line, 9, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], 485 [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing], 486 [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil], 487 [:line, 13, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], 488 [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing], 489 [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil], 490 [:end, 17, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], 491 [:line, 18, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], 492 [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, :outer, :nothing], 493 [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, :nothing], 494 [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, nil], 495 [:c_return,18, "xyzzy", Class, :new, xyzzy.class, :outer, xyzzy], 496 [:line, 19, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], 497 [:call, 9, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing], 498 [:line, 10, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing], 499 [:line, 11, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, :nothing], 500 [:call, 13, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing], 501 [:line, 14, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing], 502 [:line, 15, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, :nothing], 503 [:c_call, 15, "xyzzy", Kernel, :tap, xyzzy, :XYZZY_bar, :nothing], 504 [:c_return,15, "xyzzy", Kernel, :tap, xyzzy, :XYZZY_bar, xyzzy], 505 [:return, 16, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, xyzzy], 506 [:return, 12, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, xyzzy], 507 [:line, 20, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], 508 [:c_call, 20, "xyzzy", Kernel, :raise, self, :outer, :nothing], 509 [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, :outer, :nothing], 510 [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, :outer, :nothing], 511 [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, :outer, raised_exc], 512 [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, :outer, raised_exc], 513 [:c_return,20, "xyzzy", Kernel, :raise, self, :outer, nil], 514 [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, :outer, :nothing], 515 [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, :outer, nil], 516 [:raise, 20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc], 517 [:c_call, 20, "xyzzy", Module, :===, RuntimeError,:outer, :nothing], 518 [:c_return,20, "xyzzy", Module, :===, RuntimeError,:outer, true], 519 [:line, 21, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], 520 [:c_call, 21, "xyzzy", TracePoint, :disable, trace, :outer, :nothing], 521 ] 522 523 return events, answer_events 524 end 525 526 def trace_by_set_trace_func 527 events = [] 528 trace = nil 529 trace = trace 530 xyzzy = nil 531 xyzzy = xyzzy 532 _local_var = :outer 533 eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy' 534 1: set_trace_func(lambda{|event, file, line, id, binding, klass| 535 2: events << [event, line, file, klass, id, binding.eval('self'), binding.eval("_local_var")] if file == 'xyzzy' 536 3: }) 537 4: 1.times{|;_local_var| _local_var = :inner 538 5: tap{} 539 6: } 540 7: class XYZZY 541 8: _local_var = :XYZZY_outer 542 9: def foo 543 10: _local_var = :XYZZY_foo 544 11: bar 545 12: end 546 13: def bar 547 14: _local_var = :XYZZY_bar 548 15: tap{} 549 16: end 550 17: end 551 18: xyzzy = XYZZY.new 552 19: xyzzy.foo 553 20: begin; raise RuntimeError; rescue RuntimeError => raised_exc; end 554 21: set_trace_func(nil) 555 EOF 556 self.class.class_eval{remove_const(:XYZZY)} 557 return events 558 end 559 560 def test_tracepoint 561 events1, answer_events = *trace_by_tracepoint(:line, :class, :end, :call, :return, :c_call, :c_return, :raise) 562 563 ms = [events1, answer_events].map{|evs| 564 evs.map{|e| 565 "#{e[0]} - #{e[2]}:#{e[1]} id: #{e[4]}" 566 } 567 } 568 569 mesg = ms[0].zip(ms[1]).map{|a, b| 570 if a != b 571 "#{a} <-> #{b}" 572 end 573 }.compact.join("\n") 574 575 answer_events.zip(events1){|answer, event| 576 assert_equal answer, event, mesg 577 } 578 579 events2 = trace_by_set_trace_func 580 events1.zip(events2){|ev1, ev2| 581 ev2[0] = ev2[0].sub('-', '_').to_sym 582 assert_equal ev1[0..2], ev2[0..2], ev1.inspect 583 584 # event, line, file, klass, id, binding.eval('self'), binding.eval("_local_var") 585 assert_equal ev1[3].nil?, ev2[3].nil? # klass 586 assert_equal ev1[4].nil?, ev2[4].nil? # id 587 assert_equal ev1[6], ev2[6] # _local_var 588 } 589 590 [:line, :class, :end, :call, :return, :c_call, :c_return, :raise].each{|event| 591 events1, answer_events = *trace_by_tracepoint(event) 592 answer_events.find_all{|e| e[0] == event}.zip(events1){|answer_line, event_line| 593 assert_equal answer_line, event_line 594 } 595 } 596 end 597 598 def test_tracepoint_object_id 599 tps = [] 600 trace = TracePoint.trace(){|tp| 601 next if !target_thread? 602 tps << tp 603 } 604 tap{} 605 tap{} 606 tap{} 607 trace.disable 608 609 # passed tp is unique, `trace' object which is generated by TracePoint.trace 610 tps.each{|tp| 611 assert_equal trace, tp 612 } 613 end 614 615 def test_tracepoint_access_from_outside 616 tp_store = nil 617 trace = TracePoint.trace(){|tp| 618 next if !target_thread? 619 tp_store = tp 620 } 621 tap{} 622 trace.disable 623 624 assert_raise(RuntimeError){tp_store.lineno} 625 assert_raise(RuntimeError){tp_store.event} 626 assert_raise(RuntimeError){tp_store.path} 627 assert_raise(RuntimeError){tp_store.method_id} 628 assert_raise(RuntimeError){tp_store.defined_class} 629 assert_raise(RuntimeError){tp_store.binding} 630 assert_raise(RuntimeError){tp_store.self} 631 assert_raise(RuntimeError){tp_store.return_value} 632 assert_raise(RuntimeError){tp_store.raised_exception} 633 end 634 635 def foo 636 end 637 638 def test_tracepoint_enable 639 ary = [] 640 args = nil 641 trace = TracePoint.new(:call){|tp| 642 next if !target_thread? 643 ary << tp.method_id 644 } 645 foo 646 trace.enable{|*a| 647 args = a 648 foo 649 } 650 foo 651 assert_equal([:foo], ary) 652 assert_equal([], args) 653 654 trace = TracePoint.new{} 655 begin 656 assert_equal(false, trace.enable) 657 assert_equal(true, trace.enable) 658 trace.enable{} 659 assert_equal(true, trace.enable) 660 ensure 661 trace.disable 662 end 663 end 664 665 def test_tracepoint_disable 666 ary = [] 667 args = nil 668 trace = TracePoint.trace(:call){|tp| 669 next if !target_thread? 670 ary << tp.method_id 671 } 672 foo 673 trace.disable{|*a| 674 args = a 675 foo 676 } 677 foo 678 trace.disable 679 assert_equal([:foo, :foo], ary) 680 assert_equal([], args) 681 682 trace = TracePoint.new{} 683 trace.enable{ 684 assert_equal(true, trace.disable) 685 assert_equal(false, trace.disable) 686 trace.disable{} 687 assert_equal(false, trace.disable) 688 } 689 end 690 691 def test_tracepoint_enabled 692 trace = TracePoint.trace(:call){|tp| 693 # 694 } 695 assert_equal(true, trace.enabled?) 696 trace.disable{ 697 assert_equal(false, trace.enabled?) 698 trace.enable{ 699 assert_equal(true, trace.enabled?) 700 } 701 } 702 trace.disable 703 assert_equal(false, trace.enabled?) 704 end 705 706 def parameter_test(a, b, c) 707 yield 708 end 709 710 def test_tracepoint_parameters 711 trace = TracePoint.new(:line, :class, :end, :call, :return, :b_call, :b_return, :c_call, :c_return, :raise){|tp| 712 next if !target_thread? 713 next if tp.path != __FILE__ 714 case tp.event 715 when :call, :return 716 assert_equal([[:req, :a], [:req, :b], [:req, :c]], tp.parameters) 717 when :b_call, :b_return 718 next if tp.parameters == [] 719 if tp.parameters.first == [:opt, :x] 720 assert_equal([[:opt, :x], [:opt, :y], [:opt, :z]], tp.parameters) 721 else 722 assert_equal([[:req, :p], [:req, :q], [:req, :r]], tp.parameters) 723 end 724 when :c_call, :c_return 725 assert_equal([[:req]], tp.parameters) if tp.method_id == :getbyte 726 when :line, :class, :end, :raise 727 assert_raise(RuntimeError) { tp.parameters } 728 end 729 } 730 obj = Object.new 731 trace.enable{ 732 parameter_test(1, 2, 3) {|x, y, z| 733 } 734 lambda {|p, q, r| }.call(4, 5, 6) 735 "".getbyte(0) 736 class << obj 737 end 738 begin 739 raise 740 rescue 741 end 742 } 743 end 744 745 def method_test_tracepoint_return_value obj 746 obj 747 end 748 749 def test_tracepoint_return_value 750 trace = TracePoint.new(:call, :return){|tp| 751 next if !target_thread? 752 next if tp.path != __FILE__ 753 case tp.event 754 when :call 755 assert_raise(RuntimeError) {tp.return_value} 756 when :return 757 assert_equal("xyzzy", tp.return_value) 758 end 759 } 760 trace.enable{ 761 method_test_tracepoint_return_value "xyzzy" 762 } 763 end 764 765 class XYZZYException < Exception; end 766 def method_test_tracepoint_raised_exception err 767 raise err 768 end 769 770 def test_tracepoint_raised_exception 771 trace = TracePoint.new(:call, :return, :raise){|tp| 772 next if !target_thread? 773 case tp.event 774 when :call, :return 775 assert_raise(RuntimeError) { tp.raised_exception } 776 when :raise 777 assert_kind_of(XYZZYException, tp.raised_exception) 778 end 779 } 780 trace.enable{ 781 begin 782 method_test_tracepoint_raised_exception XYZZYException 783 rescue XYZZYException 784 # ok 785 else 786 raise 787 end 788 } 789 end 790 791 def method_for_test_tracepoint_block 792 yield 793 end 794 795 def test_tracepoint_block 796 events = [] 797 TracePoint.new(:call, :return, :c_call, :b_call, :c_return, :b_return){|tp| 798 next if !target_thread? 799 events << [ 800 tp.event, tp.method_id, tp.defined_class, tp.self.class, 801 /return/ =~ tp.event ? tp.return_value : nil 802 ] 803 }.enable{ 804 1.times{ 805 3 806 } 807 method_for_test_tracepoint_block{ 808 4 809 } 810 } 811 # pp events 812 # expected_events = 813 [[:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil], 814 [:c_call, :times, Integer, Integer, nil], 815 [:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil], 816 [:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 3], 817 [:c_return, :times, Integer, Integer, 1], 818 [:call, :method_for_test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil], 819 [:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil], 820 [:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4], 821 [:return, :method_for_test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4], 822 [:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4] 823 ].zip(events){|expected, actual| 824 assert_equal(expected, actual) 825 } 826 end 827 828 def test_tracepoint_thread 829 events = [] 830 thread_self = nil 831 created_thread = nil 832 TracePoint.new(:thread_begin, :thread_end){|tp| 833 events << [Thread.current, 834 tp.event, 835 tp.lineno, #=> 0 836 tp.path, #=> nil 837 tp.binding, #=> nil 838 tp.defined_class, #=> nil, 839 tp.self.class # tp.self return creating/ending thread 840 ] 841 }.enable{ 842 created_thread = Thread.new{thread_self = self} 843 created_thread.join 844 } 845 events.reject!{|i| i[0] != created_thread} 846 assert_equal(self, thread_self) 847 assert_equal([created_thread, :thread_begin, 0, nil, nil, nil, Thread], events[0]) 848 assert_equal([created_thread, :thread_end, 0, nil, nil, nil, Thread], events[1]) 849 assert_equal(2, events.size) 850 end 851 852 def test_tracepoint_inspect 853 events = [] 854 th = nil 855 trace = TracePoint.new{|tp| 856 next if !target_thread? && th != Thread.current 857 events << [tp.event, tp.inspect] 858 } 859 assert_equal("#<TracePoint:disabled>", trace.inspect) 860 trace.enable{ 861 assert_equal("#<TracePoint:enabled>", trace.inspect) 862 th = Thread.new{} 863 th.join 864 } 865 assert_equal("#<TracePoint:disabled>", trace.inspect) 866 events.each{|(ev, str)| 867 case ev 868 when :line 869 assert_match(/ in /, str) 870 when :call, :c_call 871 assert_match(/call \`/, str) # #<TracePoint:c_call `inherited'@../trunk/test.rb:11> 872 when :return, :c_return 873 assert_match(/return \`/, str) # #<TracePoint:return `m'@../trunk/test.rb:3> 874 when /thread/ 875 assert_match(/\#<Thread:/, str) # #<TracePoint:thread_end of #<Thread:0x87076c0>> 876 else 877 assert_match(/\#<TracePoint:/, str) 878 end 879 } 880 end 881 882 def test_tracepoint_exception_at_line 883 assert_raise(RuntimeError) do 884 TracePoint.new(:line) { 885 next if !target_thread? 886 raise 887 }.enable { 888 1 889 } 890 end 891 end 892 893 def test_tracepoint_exception_at_return 894 assert_nothing_raised(Timeout::Error, 'infinite trace') do 895 assert_normal_exit('def m; end; TracePoint.new(:return) {raise}.enable {m}', '', timeout: 3) 896 end 897 end 898 899 def test_tracepoint_exception_at_c_return 900 assert_nothing_raised(Timeout::Error, 'infinite trace') do 901 assert_normal_exit %q{ 902 begin 903 TracePoint.new(:c_return){|tp| 904 raise 905 }.enable{ 906 tap{ itself } 907 } 908 rescue 909 end 910 }, '', timeout: 3 911 end 912 end 913 914 def test_tracepoint_with_multithreads 915 assert_nothing_raised do 916 TracePoint.new{ 917 10.times{ 918 Thread.pass 919 } 920 }.enable do 921 (1..10).map{ 922 Thread.new{ 923 1000.times{ 924 } 925 } 926 }.each{|th| 927 th.join 928 } 929 end 930 end 931 end 932 933 class FOO_ERROR < RuntimeError; end 934 class BAR_ERROR < RuntimeError; end 935 def m1_test_trace_point_at_return_when_exception 936 m2_test_trace_point_at_return_when_exception 937 end 938 def m2_test_trace_point_at_return_when_exception 939 raise BAR_ERROR 940 end 941 942 def test_trace_point_at_return_when_exception 943 bug_7624 = '[ruby-core:51128] [ruby-trunk - Bug #7624]' 944 TracePoint.new{|tp| 945 next if !target_thread? 946 if tp.event == :return && 947 tp.method_id == :m2_test_trace_point_at_return_when_exception 948 raise FOO_ERROR 949 end 950 }.enable do 951 assert_raise(FOO_ERROR, bug_7624) do 952 m1_test_trace_point_at_return_when_exception 953 end 954 end 955 956 bug_7668 = '[Bug #7668]' 957 ary = [] 958 trace = TracePoint.new{|tp| 959 next if !target_thread? 960 ary << tp.event 961 raise 962 } 963 begin 964 trace.enable{ 965 1.times{ 966 raise 967 } 968 } 969 rescue 970 assert_equal([:b_call, :b_return], ary, bug_7668) 971 end 972 end 973 974 def m1_for_test_trace_point_binding_in_ifunc(arg) 975 arg + nil 976 rescue 977 end 978 979 def m2_for_test_trace_point_binding_in_ifunc(arg) 980 arg.inject(:+) 981 rescue 982 end 983 984 def test_trace_point_binding_in_ifunc 985 bug7774 = '[ruby-dev:46908]' 986 src = %q{ 987 tp = TracePoint.new(:raise) do |tp| 988 tp.binding 989 end 990 tp.enable do 991 obj = Object.new 992 class << obj 993 include Enumerable 994 def each 995 yield 1 996 end 997 end 998 %s 999 end 1000 } 1001 assert_normal_exit src % %q{obj.zip({}) {}}, bug7774 1002 assert_normal_exit src % %q{ 1003 require 'continuation' 1004 begin 1005 c = nil 1006 obj.sort_by {|x| callcc {|c2| c ||= c2 }; x } 1007 c.call 1008 rescue RuntimeError 1009 end 1010 }, bug7774 1011 1012 # TracePoint 1013 tp_b = nil 1014 TracePoint.new(:raise) do |tp| 1015 next if !target_thread? 1016 tp_b = tp.binding 1017 end.enable do 1018 m1_for_test_trace_point_binding_in_ifunc(0) 1019 assert_equal(self, eval('self', tp_b), '[ruby-dev:46960]') 1020 1021 m2_for_test_trace_point_binding_in_ifunc([0, nil]) 1022 assert_equal(self, eval('self', tp_b), '[ruby-dev:46960]') 1023 end 1024 1025 # set_trace_func 1026 stf_b = nil 1027 set_trace_func ->(event, file, line, id, binding, klass) do 1028 stf_b = binding if event == 'raise' 1029 end 1030 begin 1031 m1_for_test_trace_point_binding_in_ifunc(0) 1032 assert_equal(self, eval('self', stf_b), '[ruby-dev:46960]') 1033 1034 m2_for_test_trace_point_binding_in_ifunc([0, nil]) 1035 assert_equal(self, eval('self', stf_b), '[ruby-dev:46960]') 1036 ensure 1037 set_trace_func(nil) 1038 end 1039 end 1040 1041 def test_trace_point_binding_after_break 1042 bug10689 = '[ruby-dev:48797]' 1043 assert_in_out_err([], <<-INPUT, [], [], bug10689) 1044 class Bug 1045 include Enumerable 1046 1047 def each 1048 [0].each do 1049 yield 1050 end 1051 end 1052 end 1053 1054 TracePoint.trace(:c_return) do |tp| 1055 tp.binding 1056 end 1057 1058 Bug.new.all? { false } 1059 INPUT 1060 end 1061 1062 def test_tracepoint_b_return_with_next 1063 n = 0 1064 TracePoint.new(:b_return){ 1065 next if !target_thread? 1066 n += 1 1067 }.enable{ 1068 3.times{ 1069 next 1070 } # 3 times b_retun 1071 } # 1 time b_return 1072 1073 assert_equal 4, n 1074 end 1075 1076 def test_tracepoint_b_return_with_lambda 1077 n = 0 1078 TracePoint.new(:b_return){ 1079 next if !target_thread? 1080 n+=1 1081 }.enable{ 1082 lambda{ 1083 return 1084 }.call # n += 1 #=> 1 1085 3.times{ 1086 lambda{ 1087 return # n += 3 #=> 4 1088 }.call 1089 } # n += 3 #=> 7 1090 begin 1091 lambda{ 1092 raise 1093 }.call # n += 1 #=> 8 1094 rescue 1095 # ignore 1096 end # n += 1 #=> 9 1097 } 1098 1099 assert_equal 9, n 1100 end 1101 1102 def test_isolated_raise_in_trace 1103 bug9088 = '[ruby-dev:47793] [Bug #9088]' 1104 assert_in_out_err([], <<-END, [], [], bug9088) 1105 set_trace_func proc {raise rescue nil} 1106 1.times {break} 1107 END 1108 end 1109 1110 def test_a_call 1111 events = [] 1112 TracePoint.new(:a_call){|tp| 1113 next if !target_thread? 1114 events << tp.event 1115 }.enable{ 1116 1.times{ 1117 3 1118 } 1119 method_for_test_tracepoint_block{ 1120 4 1121 } 1122 } 1123 assert_equal([ 1124 :b_call, 1125 :c_call, 1126 :b_call, 1127 :call, 1128 :b_call, 1129 ], events) 1130 end 1131 1132 def test_a_return 1133 events = [] 1134 TracePoint.new(:a_return){|tp| 1135 next if !target_thread? 1136 events << tp.event 1137 }.enable{ 1138 1.times{ 1139 3 1140 } 1141 method_for_test_tracepoint_block{ 1142 4 1143 } 1144 } 1145 assert_equal([ 1146 :b_return, 1147 :c_return, 1148 :b_return, 1149 :return, 1150 :b_return 1151 ], events) 1152 end 1153 1154 def test_const_missing 1155 bug59398 = '[ruby-core:59398]' 1156 events = [] 1157 assert !defined?(MISSING_CONSTANT_59398) 1158 TracePoint.new(:c_call, :c_return, :call, :return){|tp| 1159 next if !target_thread? 1160 next unless tp.defined_class == Module 1161 # rake/ext/module.rb aliases :const_missing and Ruby uses the aliased name 1162 # but this only happens when running the full test suite 1163 events << [tp.event,tp.method_id] if tp.method_id == :const_missing || tp.method_id == :rake_original_const_missing 1164 }.enable{ 1165 MISSING_CONSTANT_59398 rescue nil 1166 } 1167 if events.map{|e|e[1]}.include?(:rake_original_const_missing) 1168 assert_equal([ 1169 [:call, :const_missing], 1170 [:c_call, :rake_original_const_missing], 1171 [:c_return, :rake_original_const_missing], 1172 [:return, :const_missing], 1173 ], events, bug59398) 1174 else 1175 assert_equal([ 1176 [:c_call, :const_missing], 1177 [:c_return, :const_missing] 1178 ], events, bug59398) 1179 end 1180 end 1181 1182 class AliasedRubyMethod 1183 def foo; 1; end; 1184 alias bar foo 1185 end 1186 def test_aliased_ruby_method 1187 events = [] 1188 aliased = AliasedRubyMethod.new 1189 TracePoint.new(:call, :return){|tp| 1190 next if !target_thread? 1191 events << [tp.event, tp.method_id] 1192 }.enable{ 1193 aliased.bar 1194 } 1195 assert_equal([ 1196 [:call, :foo], 1197 [:return, :foo] 1198 ], events, "should use original method name for tracing ruby methods") 1199 end 1200 class AliasedCMethod < Hash 1201 alias original_size size 1202 def size; original_size; end 1203 end 1204 1205 def test_aliased_c_method 1206 events = [] 1207 aliased = AliasedCMethod.new 1208 TracePoint.new(:call, :return, :c_call, :c_return){|tp| 1209 next if !target_thread? 1210 events << [tp.event, tp.method_id] 1211 }.enable{ 1212 aliased.size 1213 } 1214 assert_equal([ 1215 [:call, :size], 1216 [:c_call, :size], 1217 [:c_return, :size], 1218 [:return, :size] 1219 ], events, "should use alias method name for tracing c methods") 1220 end 1221 1222 def test_method_missing 1223 bug59398 = '[ruby-core:59398]' 1224 events = [] 1225 assert !respond_to?(:missing_method_59398) 1226 TracePoint.new(:c_call, :c_return, :call, :return){|tp| 1227 next if !target_thread? 1228 next unless tp.defined_class == BasicObject 1229 # rake/ext/module.rb aliases :const_missing and Ruby uses the aliased name 1230 # but this only happens when running the full test suite 1231 events << [tp.event,tp.method_id] if tp.method_id == :method_missing 1232 }.enable{ 1233 missing_method_59398 rescue nil 1234 } 1235 assert_equal([ 1236 [:c_call, :method_missing], 1237 [:c_return, :method_missing] 1238 ], events, bug59398) 1239 end 1240 1241 class C9759 1242 define_method(:foo){ 1243 raise 1244 } 1245 end 1246 1247 def test_define_method_on_exception 1248 events = [] 1249 obj = C9759.new 1250 TracePoint.new(:call, :return){|tp| 1251 next unless target_thread? 1252 events << [tp.event, tp.method_id] 1253 }.enable{ 1254 obj.foo rescue nil 1255 } 1256 assert_equal([[:call, :foo], [:return, :foo]], events, 'Bug #9759') 1257 1258 events = [] 1259 begin 1260 set_trace_func(lambda{|event, file, lineno, mid, binding, klass| 1261 next unless target_thread? 1262 case event 1263 when 'call', 'return' 1264 events << [event, mid] 1265 end 1266 }) 1267 obj.foo rescue nil 1268 set_trace_func(nil) 1269 1270 assert_equal([['call', :foo], ['return', :foo]], events, 'Bug #9759') 1271 ensure 1272 end 1273 end 1274 1275 class C11492 1276 define_method(:foo_return){ 1277 return true 1278 } 1279 define_method(:foo_break){ 1280 break true 1281 } 1282 end 1283 1284 def test_define_method_on_return 1285 # return 1286 events = [] 1287 obj = C11492.new 1288 TracePoint.new(:call, :return){|tp| 1289 next unless target_thread? 1290 events << [tp.event, tp.method_id] 1291 }.enable{ 1292 obj.foo_return 1293 } 1294 assert_equal([[:call, :foo_return], [:return, :foo_return]], events, 'Bug #11492') 1295 1296 # break 1297 events = [] 1298 obj = C11492.new 1299 TracePoint.new(:call, :return){|tp| 1300 next unless target_thread? 1301 events << [tp.event, tp.method_id] 1302 }.enable{ 1303 obj.foo_break 1304 } 1305 assert_equal([[:call, :foo_break], [:return, :foo_break]], events, 'Bug #11492') 1306 1307 # set_trace_func 1308 # return 1309 events = [] 1310 begin 1311 set_trace_func(lambda{|event, file, lineno, mid, binding, klass| 1312 next unless target_thread? 1313 case event 1314 when 'call', 'return' 1315 events << [event, mid] 1316 end 1317 }) 1318 obj.foo_return 1319 set_trace_func(nil) 1320 1321 assert_equal([['call', :foo_return], ['return', :foo_return]], events, 'Bug #11492') 1322 ensure 1323 end 1324 1325 # break 1326 events = [] 1327 begin 1328 set_trace_func(lambda{|event, file, lineno, mid, binding, klass| 1329 next unless target_thread? 1330 case event 1331 when 'call', 'return' 1332 events << [event, mid] 1333 end 1334 }) 1335 obj.foo_break 1336 set_trace_func(nil) 1337 1338 assert_equal([['call', :foo_break], ['return', :foo_break]], events, 'Bug #11492') 1339 ensure 1340 end 1341 end 1342 1343 def test_recursive 1344 assert_in_out_err([], %q{\ 1345 TracePoint.new(:c_call){|tp| 1346 p tp.method_id 1347 }.enable{ 1348 p 1 1349 } 1350 }, %w[:p :to_s 1], [], '[Bug #9940]') 1351 end 1352 1353 def method_prefix event 1354 case event 1355 when :call, :return 1356 :n 1357 when :c_call, :c_return 1358 :c 1359 when :b_call, :b_return 1360 :b 1361 end 1362 end 1363 1364 def method_label tp 1365 "#{method_prefix(tp.event)}##{tp.method_id}" 1366 end 1367 1368 def assert_consistent_call_return message='', check_events: nil 1369 check_events ||= %i(a_call a_return) 1370 call_stack = [] 1371 1372 TracePoint.new(*check_events){|tp| 1373 next unless target_thread? 1374 1375 case tp.event.to_s 1376 when /call/ 1377 call_stack << method_label(tp) 1378 when /return/ 1379 frame = call_stack.pop 1380 assert_equal(frame, method_label(tp)) 1381 end 1382 }.enable do 1383 yield 1384 end 1385 1386 assert_equal true, call_stack.empty? 1387 end 1388 1389 def method_test_rescue_should_not_cause_b_return 1390 begin 1391 raise 1392 rescue 1393 return 1394 end 1395 end 1396 1397 def method_test_ensure_should_not_cause_b_return 1398 begin 1399 raise 1400 ensure 1401 return 1402 end 1403 end 1404 1405 def test_rescue_and_ensure_should_not_cause_b_return 1406 assert_consistent_call_return '[Bug #9957]' do 1407 method_test_rescue_should_not_cause_b_return 1408 begin 1409 method_test_ensure_should_not_cause_b_return 1410 rescue 1411 # ignore 1412 end 1413 end 1414 end 1415 1416 define_method(:method_test_argument_error_on_bmethod){|correct_key: 1|} 1417 1418 def test_argument_error_on_bmethod 1419 assert_consistent_call_return '[Bug #9959]' do 1420 begin 1421 method_test_argument_error_on_bmethod(wrong_key: 2) 1422 rescue 1423 # ignore 1424 end 1425 end 1426 end 1427 1428 def test_rb_rescue 1429 assert_consistent_call_return '[Bug #9961]' do 1430 begin 1431 -Numeric.new 1432 rescue 1433 # ignore 1434 end 1435 end 1436 end 1437 1438 def test_b_call_with_redo 1439 assert_consistent_call_return '[Bug #9964]' do 1440 i = 0 1441 1.times{ 1442 break if (i+=1) > 10 1443 redo 1444 } 1445 end 1446 end 1447 1448 def test_no_duplicate_line_events 1449 lines = [] 1450 dummy = [] 1451 1452 TracePoint.new(:line){|tp| 1453 next unless target_thread? 1454 lines << tp.lineno 1455 }.enable{ 1456 dummy << (1) + (2) 1457 dummy << (1) + (2) 1458 } 1459 assert_equal [__LINE__ - 3, __LINE__ - 2], lines, 'Bug #10449' 1460 end 1461 1462 def test_elsif_line_event 1463 bug10763 = '[ruby-core:67720] [Bug #10763]' 1464 lines = [] 1465 line = nil 1466 1467 TracePoint.new(:line){|tp| 1468 next unless target_thread? 1469 lines << tp.lineno if line 1470 }.enable{ 1471 line = __LINE__ 1472 if !line 1473 1 1474 elsif line 1475 2 1476 end 1477 } 1478 assert_equal [line+1, line+3, line+4], lines, bug10763 1479 end 1480 1481 class Bug10724 1482 def initialize 1483 loop{return} 1484 end 1485 end 1486 1487 def test_throwing_return_with_finish_frame 1488 target_th = Thread.current 1489 evs = [] 1490 1491 TracePoint.new(:call, :return){|tp| 1492 next unless target_thread? 1493 evs << tp.event 1494 }.enable{ 1495 Bug10724.new 1496 } 1497 1498 assert_equal([:call, :return], evs) 1499 end 1500 1501 require 'fiber' 1502 def test_fiber_switch 1503 # test for resume/yield 1504 evs = [] 1505 TracePoint.new(:fiber_switch){|tp| 1506 next unless target_thread? 1507 evs << tp.event 1508 }.enable{ 1509 f = Fiber.new{ 1510 Fiber.yield 1511 Fiber.yield 1512 Fiber.yield 1513 } 1514 f.resume 1515 f.resume 1516 f.resume 1517 f.resume 1518 begin 1519 f.resume 1520 rescue FiberError 1521 end 1522 } 1523 assert_equal 8, evs.size 1524 evs.each{|ev| 1525 assert_equal ev, :fiber_switch 1526 } 1527 1528 # test for transfer 1529 evs = [] 1530 TracePoint.new(:fiber_switch){|tp| 1531 next unless target_thread? 1532 evs << tp.event 1533 }.enable{ 1534 f1 = f2 = nil 1535 f1 = Fiber.new{ 1536 f2.transfer 1537 f2.transfer 1538 Fiber.yield :ok 1539 } 1540 f2 = Fiber.new{ 1541 f1.transfer 1542 f1.transfer 1543 } 1544 assert_equal :ok, f1.resume 1545 } 1546 assert_equal 6, evs.size 1547 evs.each{|ev| 1548 assert_equal ev, :fiber_switch 1549 } 1550 end 1551 1552 def test_tracepoint_callee_id 1553 events = [] 1554 capture_events = Proc.new{|tp| 1555 next unless target_thread? 1556 events << [tp.event, tp.method_id, tp.callee_id] 1557 } 1558 1559 o = Class.new{ 1560 def m 1561 raise 1562 end 1563 alias alias_m m 1564 }.new 1565 TracePoint.new(:raise, :call, :return, &capture_events).enable{ 1566 o.alias_m rescue nil 1567 } 1568 assert_equal [[:call, :m, :alias_m], [:raise, :m, :alias_m], [:return, :m, :alias_m]], events 1569 events.clear 1570 1571 o = Class.new{ 1572 alias alias_raise raise 1573 def m 1574 alias_raise 1575 end 1576 }.new 1577 TracePoint.new(:c_return, &capture_events).enable{ 1578 o.m rescue nil 1579 } 1580 assert_equal [:c_return, :raise, :alias_raise], events[0] 1581 events.clear 1582 1583 o = Class.new(String){ 1584 include Enumerable 1585 alias each each_char 1586 }.new('foo') 1587 TracePoint.new(:c_return, &capture_events).enable{ 1588 o.find{true} 1589 } 1590 assert_equal [:c_return, :each_char, :each], events[0] 1591 events.clear 1592 1593 o = Class.new{ 1594 define_method(:m){} 1595 alias alias_m m 1596 }.new 1597 TracePoint.new(:call, :return, &capture_events).enable{ 1598 o.alias_m 1599 } 1600 assert_equal [[:call, :m, :alias_m], [:return, :m, :alias_m]], events 1601 events.clear 1602 1603 o = Class.new{ 1604 def m 1605 tap{return} 1606 end 1607 alias alias_m m 1608 }.new 1609 TracePoint.new(:return, &capture_events).enable{ 1610 o.alias_m 1611 } 1612 assert_equal [[:return, :m, :alias_m]], events 1613 events.clear 1614 1615 o = Class.new{ 1616 define_method(:m){raise} 1617 alias alias_m m 1618 }.new 1619 TracePoint.new(:b_return, :return, &capture_events).enable{ 1620 o.alias_m rescue nil 1621 } 1622 assert_equal [[:b_return, :m, :alias_m], [:return, :m, :alias_m]], events[0..1] 1623 events.clear 1624 1625 o = Class.new{ 1626 define_method(:m){tap{return}} 1627 alias alias_m m 1628 }.new 1629 TracePoint.new(:b_return, &capture_events).enable{ 1630 o.alias_m 1631 } 1632 assert_equal [[:b_return, :m, :alias_m], [:b_return, :m, :alias_m]], events[0..1] 1633 events.clear 1634 1635 o = Class.new{ 1636 alias alias_tap tap 1637 define_method(:m){alias_tap{return}} 1638 }.new 1639 TracePoint.new(:c_return, &capture_events).enable{ 1640 o.m 1641 } 1642 assert_equal [[:c_return, :tap, :alias_tap]], events 1643 events.clear 1644 1645 c = Class.new{ 1646 alias initialize itself 1647 } 1648 TracePoint.new(:c_call, &capture_events).enable{ 1649 c.new 1650 } 1651 assert_equal [:c_call, :itself, :initialize], events[1] 1652 events.clear 1653 1654 o = Class.new{ 1655 alias alias_itself itself 1656 }.new 1657 TracePoint.new(:c_call, :c_return, &capture_events).enable{ 1658 o.alias_itself 1659 } 1660 assert_equal [[:c_call, :itself, :alias_itself], [:c_return, :itself, :alias_itself]], events 1661 events.clear 1662 end 1663 1664 # tests for `return_value` with non-local exit [Bug #13369] 1665 1666 def tp_return_value mid 1667 ary = [] 1668 TracePoint.new(:return, :b_return){|tp| next if !target_thread?; ary << [tp.event, tp.method_id, tp.return_value]}.enable{ 1669 send mid 1670 } 1671 ary.pop # last b_return event is not required. 1672 ary 1673 end 1674 1675 def f_raise 1676 raise 1677 rescue 1678 return :f_raise_return 1679 end 1680 1681 def f_iter1 1682 yield 1683 return :f_iter1_return 1684 end 1685 1686 def f_iter2 1687 yield 1688 return :f_iter2_return 1689 end 1690 1691 def f_return_in_iter 1692 f_iter1 do 1693 f_iter2 do 1694 return :f_return_in_iter_return 1695 end 1696 end 1697 2 1698 end 1699 1700 def f_break_in_iter 1701 f_iter1 do 1702 f_iter2 do 1703 break :f_break_in_iter_break 1704 end 1705 :f_iter1_block_value 1706 end 1707 :f_break_in_iter_return 1708 end 1709 1710 def test_return_value_with_rescue 1711 assert_equal [[:return, :f_raise, :f_raise_return]], 1712 tp_return_value(:f_raise), 1713 '[Bug #13369]' 1714 1715 assert_equal [[:b_return, :f_return_in_iter, nil], 1716 [:return, :f_iter2, nil], 1717 [:b_return, :f_return_in_iter, nil], 1718 [:return, :f_iter1, nil], 1719 [:return, :f_return_in_iter, :f_return_in_iter_return]], 1720 tp_return_value(:f_return_in_iter), 1721 '[Bug #13369]' 1722 1723 assert_equal [[:b_return, :f_break_in_iter, :f_break_in_iter_break], 1724 [:return, :f_iter2, nil], 1725 [:b_return, :f_break_in_iter, :f_iter1_block_value], 1726 [:return, :f_iter1, :f_iter1_return], 1727 [:return, :f_break_in_iter, :f_break_in_iter_return]], 1728 tp_return_value(:f_break_in_iter), 1729 '[Bug #13369]' 1730 end 1731 1732 define_method(:f_last_defined) do 1733 :f_last_defined 1734 end 1735 1736 define_method(:f_return_defined) do 1737 return :f_return_defined 1738 end 1739 1740 define_method(:f_break_defined) do 1741 return :f_break_defined 1742 end 1743 1744 define_method(:f_raise_defined) do 1745 raise 1746 rescue 1747 return :f_raise_defined 1748 end 1749 1750 define_method(:f_break_in_rescue_defined) do 1751 raise 1752 rescue 1753 break :f_break_in_rescue_defined 1754 end 1755 1756 def test_return_value_with_rescue_and_defined_methods 1757 assert_equal [[:b_return, :f_last_defined, :f_last_defined], 1758 [:return, :f_last_defined, :f_last_defined]], 1759 tp_return_value(:f_last_defined), 1760 '[Bug #13369]' 1761 1762 assert_equal [[:b_return, :f_return_defined, nil], # current limitation 1763 [:return, :f_return_defined, :f_return_defined]], 1764 tp_return_value(:f_return_defined), 1765 '[Bug #13369]' 1766 1767 assert_equal [[:b_return, :f_break_defined, nil], 1768 [:return, :f_break_defined, :f_break_defined]], 1769 tp_return_value(:f_break_defined), 1770 '[Bug #13369]' 1771 1772 assert_equal [[:b_return, :f_raise_defined, nil], 1773 [:return, :f_raise_defined, f_raise_defined]], 1774 tp_return_value(:f_raise_defined), 1775 '[Bug #13369]' 1776 1777 assert_equal [[:b_return, :f_break_in_rescue_defined, nil], 1778 [:return, :f_break_in_rescue_defined, f_break_in_rescue_defined]], 1779 tp_return_value(:f_break_in_rescue_defined), 1780 '[Bug #13369]' 1781 end 1782 1783 def f_iter 1784 yield 1785 end 1786 1787 def f_break_in_rescue 1788 f_iter do 1789 begin 1790 raise 1791 rescue 1792 break :b 1793 end 1794 end 1795 :f_break_in_rescue_return_value 1796 end 1797 1798 def test_break_with_rescue 1799 assert_equal [[:b_return, :f_break_in_rescue, :b], 1800 [:return, :f_iter, nil], 1801 [:return, :f_break_in_rescue, :f_break_in_rescue_return_value]], 1802 tp_return_value(:f_break_in_rescue), 1803 '[Bug #13369]' 1804 end 1805 1806 def test_trace_point_raising_exception_in_bmethod_call 1807 bug13705 = '[ruby-dev:50162]' 1808 assert_normal_exit %q{ 1809 define_method(:m) {} 1810 1811 tp = TracePoint.new(:call) do 1812 raise '' 1813 end 1814 1815 tap do 1816 tap do 1817 begin 1818 tp.enable 1819 m 1820 rescue 1821 end 1822 end 1823 end 1824 }, bug13705 1825 end 1826 1827 def test_trace_point_require_block 1828 assert_raise(ArgumentError) { TracePoint.new(:return) } 1829 end 1830 1831 def method_for_test_thread_add_trace_func 1832 1833 end 1834 1835 def test_thread_add_trace_func 1836 events = [] 1837 base_line = __LINE__ 1838 q = Queue.new 1839 t = Thread.new{ 1840 Thread.current.add_trace_func proc{|ev, file, line, *args| 1841 events << [ev, line] 1842 } # do not stop trace. They will be stopped at Thread termination. 1843 q.push 1 1844 _x = 1 1845 method_for_test_thread_add_trace_func 1846 _y = 2 1847 } 1848 q.pop 1849 method_for_test_thread_add_trace_func 1850 t.join 1851 assert_equal ["c-return", base_line + 3], events[0] 1852 assert_equal ["line", base_line + 6], events[1] 1853 assert_equal ["c-call", base_line + 6], events[2] 1854 assert_equal ["c-return", base_line + 6], events[3] 1855 assert_equal ["line", base_line + 7], events[4] 1856 assert_equal ["line", base_line + 8], events[5] 1857 assert_equal ["call", base_line + -6], events[6] 1858 assert_equal ["return", base_line + -4], events[7] 1859 assert_equal ["line", base_line + 9], events[8] 1860 assert_equal nil, events[9] 1861 1862 # other thread 1863 events = [] 1864 m2t_q = Queue.new 1865 1866 t = Thread.new{ 1867 Thread.current.abort_on_exception = true 1868 assert_equal 1, m2t_q.pop 1869 _x = 1 1870 method_for_test_thread_add_trace_func 1871 _y = 2 1872 Thread.current.set_trace_func(nil) 1873 method_for_test_thread_add_trace_func 1874 } 1875 # it is dirty hack. usually we shouldn't use such technique 1876 Thread.pass until t.status == 'sleep' 1877 # When MJIT thread exists, t.status becomes 'sleep' even if it does not reach m2t_q.pop. 1878 # This sleep forces it to reach m2t_q.pop for --jit-wait. 1879 sleep 1 if RubyVM::MJIT.enabled? 1880 1881 t.add_trace_func proc{|ev, file, line, *args| 1882 if file == __FILE__ 1883 events << [ev, line] 1884 end 1885 } 1886 1887 method_for_test_thread_add_trace_func 1888 1889 m2t_q.push 1 1890 t.join 1891 1892 assert_equal ["c-return", base_line + 31], events[0] 1893 assert_equal ["line", base_line + 32], events[1] 1894 assert_equal ["line", base_line + 33], events[2] 1895 assert_equal ["call", base_line + -6], events[3] 1896 assert_equal ["return", base_line + -4], events[4] 1897 assert_equal ["line", base_line + 34], events[5] 1898 assert_equal ["line", base_line + 35], events[6] 1899 assert_equal ["c-call", base_line + 35], events[7] # Thread.current 1900 assert_equal ["c-return", base_line + 35], events[8] # Thread.current 1901 assert_equal ["c-call", base_line + 35], events[9] # Thread#set_trace_func 1902 assert_equal nil, events[10] 1903 end 1904 1905 def test_lineno_in_optimized_insn 1906 actual, _, _ = EnvUtil.invoke_ruby [], <<-EOF.gsub(/^.*?: */, ""), true 1907 1: class String 1908 2: def -@ 1909 3: puts caller_locations(1, 1)[0].lineno 1910 4: end 1911 5: end 1912 6: 1913 7: -"" 1914 EOF 1915 assert_equal "7\n", actual, '[Bug #14809]' 1916 end 1917 1918 def method_for_enable_target1 1919 a = 1 1920 b = 2 1921 1.times{|i| 1922 x = i 1923 } 1924 c = a + b 1925 end 1926 1927 def method_for_enable_target2 1928 a = 1 1929 b = 2 1930 1.times{|i| 1931 x = i 1932 } 1933 c = a + b 1934 end 1935 1936 def check_with_events *trace_events 1937 all_events = [[:call, :method_for_enable_target1], 1938 [:line, :method_for_enable_target1], 1939 [:line, :method_for_enable_target1], 1940 [:line, :method_for_enable_target1], 1941 [:b_call, :method_for_enable_target1], 1942 [:line, :method_for_enable_target1], 1943 [:b_return, :method_for_enable_target1], 1944 [:line, :method_for_enable_target1], 1945 [:return, :method_for_enable_target1], 1946 # repeat 1947 [:call, :method_for_enable_target1], 1948 [:line, :method_for_enable_target1], 1949 [:line, :method_for_enable_target1], 1950 [:line, :method_for_enable_target1], 1951 [:b_call, :method_for_enable_target1], 1952 [:line, :method_for_enable_target1], 1953 [:b_return, :method_for_enable_target1], 1954 [:line, :method_for_enable_target1], 1955 [:return, :method_for_enable_target1], 1956 ] 1957 events = [] 1958 TracePoint.new(*trace_events) do |tp| 1959 next unless target_thread? 1960 events << [tp.event, tp.method_id] 1961 end.enable(target: method(:method_for_enable_target1)) do 1962 method_for_enable_target1 1963 method_for_enable_target2 1964 method_for_enable_target1 1965 end 1966 assert_equal all_events.find_all{|(ev, m)| trace_events.include? ev}, events 1967 end 1968 1969 def test_tracepoint_enable_target 1970 check_with_events :line 1971 check_with_events :call, :return 1972 check_with_events :line, :call, :return 1973 check_with_events :call, :return, :b_call, :b_return 1974 check_with_events :line, :call, :return, :b_call, :b_return 1975 end 1976 1977 def test_tracepoint_nested_enabled_with_target 1978 code1 = proc{ 1979 a = 1 1980 } 1981 code2 = proc{ 1982 b = 2 1983 } 1984 1985 ## error 1986 1987 # targetted TP and targetted TP 1988 ex = assert_raise(ArgumentError) do 1989 tp = TracePoint.new(:line){} 1990 tp.enable(target: code1){ 1991 tp.enable(target: code2){} 1992 } 1993 end 1994 assert_equal "can't nest-enable a targetting TracePoint", ex.message 1995 1996 # global TP and targetted TP 1997 ex = assert_raise(ArgumentError) do 1998 tp = TracePoint.new(:line){} 1999 tp.enable{ 2000 tp.enable(target: code2){} 2001 } 2002 end 2003 assert_equal "can't nest-enable a targetting TracePoint", ex.message 2004 2005 # targetted TP and global TP 2006 ex = assert_raise(ArgumentError) do 2007 tp = TracePoint.new(:line){} 2008 tp.enable(target: code1){ 2009 tp.enable{} 2010 } 2011 end 2012 assert_equal "can't nest-enable a targetting TracePoint", ex.message 2013 2014 # targetted TP and disable 2015 ex = assert_raise(ArgumentError) do 2016 tp = TracePoint.new(:line){} 2017 tp.enable(target: code1){ 2018 tp.disable{} 2019 } 2020 end 2021 assert_equal "can't disable a targetting TracePoint in a block", ex.message 2022 2023 ## success with two nesting targetting tracepoints 2024 events = [] 2025 tp1 = TracePoint.new(:line){|tp| events << :tp1} 2026 tp2 = TracePoint.new(:line){|tp| events << :tp2} 2027 tp1.enable(target: code1) do 2028 tp2.enable(target: code1) do 2029 code1.call 2030 events << :___ 2031 end 2032 end 2033 assert_equal [:tp2, :tp1, :___], events 2034 2035 # succss with two tracepoints (global/targetting) 2036 events = [] 2037 tp1 = TracePoint.new(:line){|tp| events << :tp1} 2038 tp2 = TracePoint.new(:line){|tp| events << :tp2} 2039 tp1.enable do 2040 tp2.enable(target: code1) do 2041 code1.call 2042 events << :___ 2043 end 2044 end 2045 assert_equal [:tp1, :tp1, :tp1, :tp1, :tp2, :tp1, :___], events 2046 2047 # succss with two tracepoints (targetting/global) 2048 events = [] 2049 tp1 = TracePoint.new(:line){|tp| events << :tp1} 2050 tp2 = TracePoint.new(:line){|tp| events << :tp2} 2051 tp1.enable(target: code1) do 2052 tp2.enable do 2053 code1.call 2054 events << :___ 2055 end 2056 end 2057 assert_equal [:tp2, :tp2, :tp1, :tp2, :___], events 2058 end 2059 2060 def test_tracepoint_enable_with_target_line 2061 events = [] 2062 line_0 = __LINE__ 2063 code1 = proc{ 2064 events << 1 2065 events << 2 2066 events << 3 2067 } 2068 tp = TracePoint.new(:line) do |tp| 2069 events << :tp 2070 end 2071 tp.enable(target: code1, target_line: line_0 + 3) do 2072 code1.call 2073 end 2074 assert_equal [1, :tp, 2, 3], events 2075 2076 2077 e = assert_raise(ArgumentError) do 2078 TracePoint.new(:line){}.enable(target_line: 10){} 2079 end 2080 assert_equal 'only target_line is specified', e.message 2081 2082 e = assert_raise(ArgumentError) do 2083 TracePoint.new(:call){}.enable(target: code1, target_line: 10){} 2084 end 2085 assert_equal 'target_line is specified, but line event is not specified', e.message 2086 end 2087 2088 def test_script_compiled 2089 events = [] 2090 tp = TracePoint.new(:script_compiled){|tp| 2091 next unless target_thread? 2092 events << [tp.instruction_sequence.path, 2093 tp.eval_script] 2094 } 2095 2096 eval_script = 'a = 1' 2097 tp.enable{ 2098 eval(eval_script, nil, __FILE__+"/eval") 2099 nil.instance_eval(eval_script, __FILE__+"/instance_eval") 2100 Object.class_eval(eval_script, __FILE__+"/class_eval") 2101 } 2102 assert_equal [[__FILE__+"/eval", eval_script], 2103 [__FILE__+"/instance_eval", eval_script], 2104 [__FILE__+"/class_eval", eval_script], 2105 ], events 2106 events.clear 2107 2108 # TODO: test for requires 2109 return 2110 2111 tp.enable{ 2112 require '' 2113 require_relative '' 2114 load '' 2115 } 2116 assert_equal [], events 2117 end 2118 2119 def test_return_event_with_rescue 2120 obj = Object.new 2121 def obj.example 2122 1 if 1 == 1 2123 rescue 2124 end 2125 ok = false 2126 tp = TracePoint.new(:return) {ok = true} 2127 tp.enable {obj.example} 2128 assert ok, "return event should be emitted" 2129 end 2130 2131 def test_disable_local_tracepoint_in_trace 2132 assert_normal_exit <<-EOS 2133 def foo 2134 trace = TracePoint.new(:b_return){|tp| 2135 tp.disable 2136 } 2137 trace.enable(target: method(:bar)) 2138 end 2139 def bar 2140 100.times{|i| 2141 foo; foo 2142 } 2143 end 2144 bar 2145 EOS 2146 end 2147end 2148