1 # Testing the line trace facility. 2 3 from test import test_support 4 import unittest 5 import sys 6 import difflib 7 import gc 8 9 # A very basic example. If this fails, we're in deep trouble. 10 def basic(): 11 return 1 12 13 basic.events = [(0, 'call'), 14 (1, 'line'), 15 (1, 'return')] 16 17 # Many of the tests below are tricky because they involve pass statements. 18 # If there is implicit control flow around a pass statement (in an except 19 # clause or else caluse) under what conditions do you set a line number 20 # following that clause? 21 22 23 # The entire "while 0:" statement is optimized away. No code 24 # exists for it, so the line numbers skip directly from "del x" 25 # to "x = 1". 26 def arigo_example(): 27 x = 1 28 del x 29 while 0: 30 pass 31 x = 1 32 33 arigo_example.events = [(0, 'call'), 34 (1, 'line'), 35 (2, 'line'), 36 (5, 'line'), 37 (5, 'return')] 38 39 # check that lines consisting of just one instruction get traced: 40 def one_instr_line(): 41 x = 1 42 del x 43 x = 1 44 45 one_instr_line.events = [(0, 'call'), 46 (1, 'line'), 47 (2, 'line'), 48 (3, 'line'), 49 (3, 'return')] 50 51 def no_pop_tops(): # 0 52 x = 1 # 1 53 for a in range(2): # 2 54 if a: # 3 55 x = 1 # 4 56 else: # 5 57 x = 1 # 6 58 59 no_pop_tops.events = [(0, 'call'), 60 (1, 'line'), 61 (2, 'line'), 62 (3, 'line'), 63 (6, 'line'), 64 (2, 'line'), 65 (3, 'line'), 66 (4, 'line'), 67 (2, 'line'), 68 (2, 'return')] 69 70 def no_pop_blocks(): 71 y = 1 72 while not y: 73 bla 74 x = 1 75 76 no_pop_blocks.events = [(0, 'call'), 77 (1, 'line'), 78 (2, 'line'), 79 (4, 'line'), 80 (4, 'return')] 81 82 def called(): # line -3 83 x = 1 84 85 def call(): # line 0 86 called() 87 88 call.events = [(0, 'call'), 89 (1, 'line'), 90 (-3, 'call'), 91 (-2, 'line'), 92 (-2, 'return'), 93 (1, 'return')] 94 95 def raises(): 96 raise Exception 97 98 def test_raise(): 99 try: 100 raises() 101 except Exception, exc: 102 x = 1 103 104 test_raise.events = [(0, 'call'), 105 (1, 'line'), 106 (2, 'line'), 107 (-3, 'call'), 108 (-2, 'line'), 109 (-2, 'exception'), 110 (-2, 'return'), 111 (2, 'exception'), 112 (3, 'line'), 113 (4, 'line'), 114 (4, 'return')] 115 116 def _settrace_and_return(tracefunc): 117 sys.settrace(tracefunc) 118 sys._getframe().f_back.f_trace = tracefunc 119 def settrace_and_return(tracefunc): 120 _settrace_and_return(tracefunc) 121 122 settrace_and_return.events = [(1, 'return')] 123 124 def _settrace_and_raise(tracefunc): 125 sys.settrace(tracefunc) 126 sys._getframe().f_back.f_trace = tracefunc 127 raise RuntimeError 128 def settrace_and_raise(tracefunc): 129 try: 130 _settrace_and_raise(tracefunc) 131 except RuntimeError, exc: 132 pass 133 134 settrace_and_raise.events = [(2, 'exception'), 135 (3, 'line'), 136 (4, 'line'), 137 (4, 'return')] 138 139 # implicit return example 140 # This test is interesting because of the else: pass 141 # part of the code. The code generate for the true 142 # part of the if contains a jump past the else branch. 143 # The compiler then generates an implicit "return None" 144 # Internally, the compiler visits the pass statement 145 # and stores its line number for use on the next instruction. 146 # The next instruction is the implicit return None. 147 def ireturn_example(): 148 a = 5 149 b = 5 150 if a == b: 151 b = a+1 152 else: 153 pass 154 155 ireturn_example.events = [(0, 'call'), 156 (1, 'line'), 157 (2, 'line'), 158 (3, 'line'), 159 (4, 'line'), 160 (6, 'line'), 161 (6, 'return')] 162 163 # Tight loop with while(1) example (SF #765624) 164 def tightloop_example(): 165 items = range(0, 3) 166 try: 167 i = 0 168 while 1: 169 b = items[i]; i+=1 170 except IndexError: 171 pass 172 173 tightloop_example.events = [(0, 'call'), 174 (1, 'line'), 175 (2, 'line'), 176 (3, 'line'), 177 (4, 'line'), 178 (5, 'line'), 179 (5, 'line'), 180 (5, 'line'), 181 (5, 'line'), 182 (5, 'exception'), 183 (6, 'line'), 184 (7, 'line'), 185 (7, 'return')] 186 187 def tighterloop_example(): 188 items = range(1, 4) 189 try: 190 i = 0 191 while 1: i = items[i] 192 except IndexError: 193 pass 194 195 tighterloop_example.events = [(0, 'call'), 196 (1, 'line'), 197 (2, 'line'), 198 (3, 'line'), 199 (4, 'line'), 200 (4, 'line'), 201 (4, 'line'), 202 (4, 'line'), 203 (4, 'exception'), 204 (5, 'line'), 205 (6, 'line'), 206 (6, 'return')] 207 208 def generator_function(): 209 try: 210 yield True 211 "continued" 212 finally: 213 "finally" 214 def generator_example(): 215 # any() will leave the generator before its end 216 x = any(generator_function()) 217 218 # the following lines were not traced 219 for x in range(10): 220 y = x 221 222 generator_example.events = ([(0, 'call'), 223 (2, 'line'), 224 (-6, 'call'), 225 (-5, 'line'), 226 (-4, 'line'), 227 (-4, 'return'), 228 (-4, 'call'), 229 (-4, 'exception'), 230 (-1, 'line'), 231 (-1, 'return')] + 232 [(5, 'line'), (6, 'line')] * 10 + 233 [(5, 'line'), (5, 'return')]) 234 235 236 class Tracer: 237 def __init__(self): 238 self.events = [] 239 def trace(self, frame, event, arg): 240 self.events.append((frame.f_lineno, event)) 241 return self.trace 242 def traceWithGenexp(self, frame, event, arg): 243 (o for o in [1]) 244 self.events.append((frame.f_lineno, event)) 245 return self.trace 246 247 class TraceTestCase(unittest.TestCase): 248 249 # Disable gc collection when tracing, otherwise the 250 # deallocators may be traced as well. 251 def setUp(self): 252 self.using_gc = gc.isenabled() 253 gc.disable() 254 255 def tearDown(self): 256 if self.using_gc: 257 gc.enable() 258 259 def compare_events(self, line_offset, events, expected_events): 260 events = [(l - line_offset, e) for (l, e) in events] 261 if events != expected_events: 262 self.fail( 263 "events did not match expectation:\n" + 264 "\n".join(difflib.ndiff([str(x) for x in expected_events], 265 [str(x) for x in events]))) 266 267 def run_and_compare(self, func, events): 268 tracer = Tracer() 269 sys.settrace(tracer.trace) 270 func() 271 sys.settrace(None) 272 self.compare_events(func.func_code.co_firstlineno, 273 tracer.events, events) 274 275 def run_test(self, func): 276 self.run_and_compare(func, func.events) 277 278 def run_test2(self, func): 279 tracer = Tracer() 280 func(tracer.trace) 281 sys.settrace(None) 282 self.compare_events(func.func_code.co_firstlineno, 283 tracer.events, func.events) 284 285 def test_set_and_retrieve_none(self): 286 sys.settrace(None) 287 assert sys.gettrace() is None 288 289 def test_set_and_retrieve_func(self): 290 def fn(*args): 291 pass 292 293 sys.settrace(fn) 294 try: 295 assert sys.gettrace() is fn 296 finally: 297 sys.settrace(None) 298 299 def test_01_basic(self): 300 self.run_test(basic) 301 def test_02_arigo(self): 302 self.run_test(arigo_example) 303 def test_03_one_instr(self): 304 self.run_test(one_instr_line) 305 def test_04_no_pop_blocks(self): 306 self.run_test(no_pop_blocks) 307 def test_05_no_pop_tops(self): 308 self.run_test(no_pop_tops) 309 def test_06_call(self): 310 self.run_test(call) 311 def test_07_raise(self): 312 self.run_test(test_raise) 313 314 def test_08_settrace_and_return(self): 315 self.run_test2(settrace_and_return) 316 def test_09_settrace_and_raise(self): 317 self.run_test2(settrace_and_raise) 318 def test_10_ireturn(self): 319 self.run_test(ireturn_example) 320 def test_11_tightloop(self): 321 self.run_test(tightloop_example) 322 def test_12_tighterloop(self): 323 self.run_test(tighterloop_example) 324 325 def test_13_genexp(self): 326 self.run_test(generator_example) 327 # issue1265: if the trace function contains a generator, 328 # and if the traced function contains another generator 329 # that is not completely exhausted, the trace stopped. 330 # Worse: the 'finally' clause was not invoked. 331 tracer = Tracer() 332 sys.settrace(tracer.traceWithGenexp) 333 generator_example() 334 sys.settrace(None) 335 self.compare_events(generator_example.__code__.co_firstlineno, 336 tracer.events, generator_example.events) 337 338 def test_14_onliner_if(self): 339 def onliners(): 340 if True: False 341 else: True 342 return 0 343 self.run_and_compare( 344 onliners, 345 [(0, 'call'), 346 (1, 'line'), 347 (3, 'line'), 348 (3, 'return')]) 349 350 def test_15_loops(self): 351 # issue1750076: "while" expression is skipped by debugger 352 def for_example(): 353 for x in range(2): 354 pass 355 self.run_and_compare( 356 for_example, 357 [(0, 'call'), 358 (1, 'line'), 359 (2, 'line'), 360 (1, 'line'), 361 (2, 'line'), 362 (1, 'line'), 363 (1, 'return')]) 364 365 def while_example(): 366 # While expression should be traced on every loop 367 x = 2 368 while x > 0: 369 x -= 1 370 self.run_and_compare( 371 while_example, 372 [(0, 'call'), 373 (2, 'line'), 374 (3, 'line'), 375 (4, 'line'), 376 (3, 'line'), 377 (4, 'line'), 378 (3, 'line'), 379 (3, 'return')]) 380 381 def test_16_blank_lines(self): 382 exec("def f():\n" + "\n" * 256 + " pass") 383 self.run_and_compare( 384 f, 385 [(0, 'call'), 386 (257, 'line'), 387 (257, 'return')]) 388 389 390 class RaisingTraceFuncTestCase(unittest.TestCase): 391 def trace(self, frame, event, arg): 392 """A trace function that raises an exception in response to a 393 specific trace event.""" 394 if event == self.raiseOnEvent: 395 raise ValueError # just something that isn't RuntimeError 396 else: 397 return self.trace 398 399 def f(self): 400 """The function to trace; raises an exception if that's the case 401 we're testing, so that the 'exception' trace event fires.""" 402 if self.raiseOnEvent == 'exception': 403 x = 0 404 y = 1 // x 405 else: 406 return 1 407 408 def run_test_for_event(self, event): 409 """Tests that an exception raised in response to the given event is 410 handled OK.""" 411 self.raiseOnEvent = event 412 try: 413 for i in xrange(sys.getrecursionlimit() + 1): 414 sys.settrace(self.trace) 415 try: 416 self.f() 417 except ValueError: 418 pass 419 else: 420 self.fail("exception not raised!") 421 except RuntimeError: 422 self.fail("recursion counter not reset") 423 424 # Test the handling of exceptions raised by each kind of trace event. 425 def test_call(self): 426 self.run_test_for_event('call') 427 def test_line(self): 428 self.run_test_for_event('line') 429 def test_return(self): 430 self.run_test_for_event('return') 431 def test_exception(self): 432 self.run_test_for_event('exception') 433 434 def test_trash_stack(self): 435 def f(): 436 for i in range(5): 437 print i # line tracing will raise an exception at this line 438 439 def g(frame, why, extra): 440 if (why == 'line' and 441 frame.f_lineno == f.func_code.co_firstlineno + 2): 442 raise RuntimeError, "i am crashing" 443 return g 444 445 sys.settrace(g) 446 try: 447 f() 448 except RuntimeError: 449 # the test is really that this doesn't segfault: 450 import gc 451 gc.collect() 452 else: 453 self.fail("exception not propagated") 454 455 456 # 'Jump' tests: assigning to frame.f_lineno within a trace function 457 # moves the execution position - it's how debuggers implement a Jump 458 # command (aka. "Set next statement"). 459 460 class JumpTracer: 461 """Defines a trace function that jumps from one place to another, 462 with the source and destination lines of the jump being defined by 463 the 'jump' property of the function under test.""" 464 465 def __init__(self, function): 466 self.function = function 467 self.jumpFrom = function.jump[0] 468 self.jumpTo = function.jump[1] 469 self.done = False 470 471 def trace(self, frame, event, arg): 472 if not self.done and frame.f_code == self.function.func_code: 473 firstLine = frame.f_code.co_firstlineno 474 if event == 'line' and frame.f_lineno == firstLine + self.jumpFrom: 475 # Cope with non-integer self.jumpTo (because of 476 # no_jump_to_non_integers below). 477 try: 478 frame.f_lineno = firstLine + self.jumpTo 479 except TypeError: 480 frame.f_lineno = self.jumpTo 481 self.done = True 482 return self.trace 483 484 # The first set of 'jump' tests are for things that are allowed: 485 486 def jump_simple_forwards(output): 487 output.append(1) 488 output.append(2) 489 output.append(3) 490 491 jump_simple_forwards.jump = (1, 3) 492 jump_simple_forwards.output = [3] 493 494 def jump_simple_backwards(output): 495 output.append(1) 496 output.append(2) 497 498 jump_simple_backwards.jump = (2, 1) 499 jump_simple_backwards.output = [1, 1, 2] 500 501 def jump_out_of_block_forwards(output): 502 for i in 1, 2: 503 output.append(2) 504 for j in [3]: # Also tests jumping over a block 505 output.append(4) 506 output.append(5) 507 508 jump_out_of_block_forwards.jump = (3, 5) 509 jump_out_of_block_forwards.output = [2, 5] 510 511 def jump_out_of_block_backwards(output): 512 output.append(1) 513 for i in [1]: 514 output.append(3) 515 for j in [2]: # Also tests jumping over a block 516 output.append(5) 517 output.append(6) 518 output.append(7) 519 520 jump_out_of_block_backwards.jump = (6, 1) 521 jump_out_of_block_backwards.output = [1, 3, 5, 1, 3, 5, 6, 7] 522 523 def jump_to_codeless_line(output): 524 output.append(1) 525 # Jumping to this line should skip to the next one. 526 output.append(3) 527 528 jump_to_codeless_line.jump = (1, 2) 529 jump_to_codeless_line.output = [3] 530 531 def jump_to_same_line(output): 532 output.append(1) 533 output.append(2) 534 output.append(3) 535 536 jump_to_same_line.jump = (2, 2) 537 jump_to_same_line.output = [1, 2, 3] 538 539 # Tests jumping within a finally block, and over one. 540 def jump_in_nested_finally(output): 541 try: 542 output.append(2) 543 finally: 544 output.append(4) 545 try: 546 output.append(6) 547 finally: 548 output.append(8) 549 output.append(9) 550 551 jump_in_nested_finally.jump = (4, 9) 552 jump_in_nested_finally.output = [2, 9] 553 554 # The second set of 'jump' tests are for things that are not allowed: 555 556 def no_jump_too_far_forwards(output): 557 try: 558 output.append(2) 559 output.append(3) 560 except ValueError, e: 561 output.append('after' in str(e)) 562 563 no_jump_too_far_forwards.jump = (3, 6) 564 no_jump_too_far_forwards.output = [2, True] 565 566 def no_jump_too_far_backwards(output): 567 try: 568 output.append(2) 569 output.append(3) 570 except ValueError, e: 571 output.append('before' in str(e)) 572 573 no_jump_too_far_backwards.jump = (3, -1) 574 no_jump_too_far_backwards.output = [2, True] 575 576 # Test each kind of 'except' line. 577 def no_jump_to_except_1(output): 578 try: 579 output.append(2) 580 except: 581 e = sys.exc_info()[1] 582 output.append('except' in str(e)) 583 584 no_jump_to_except_1.jump = (2, 3) 585 no_jump_to_except_1.output = [True] 586 587 def no_jump_to_except_2(output): 588 try: 589 output.append(2) 590 except ValueError: 591 e = sys.exc_info()[1] 592 output.append('except' in str(e)) 593 594 no_jump_to_except_2.jump = (2, 3) 595 no_jump_to_except_2.output = [True] 596 597 def no_jump_to_except_3(output): 598 try: 599 output.append(2) 600 except ValueError, e: 601 output.append('except' in str(e)) 602 603 no_jump_to_except_3.jump = (2, 3) 604 no_jump_to_except_3.output = [True] 605 606 def no_jump_to_except_4(output): 607 try: 608 output.append(2) 609 except (ValueError, RuntimeError), e: 610 output.append('except' in str(e)) 611 612 no_jump_to_except_4.jump = (2, 3) 613 no_jump_to_except_4.output = [True] 614 615 def no_jump_forwards_into_block(output): 616 try: 617 output.append(2) 618 for i in 1, 2: 619 output.append(4) 620 except ValueError, e: 621 output.append('into' in str(e)) 622 623 no_jump_forwards_into_block.jump = (2, 4) 624 no_jump_forwards_into_block.output = [True] 625 626 def no_jump_backwards_into_block(output): 627 try: 628 for i in 1, 2: 629 output.append(3) 630 output.append(4) 631 except ValueError, e: 632 output.append('into' in str(e)) 633 634 no_jump_backwards_into_block.jump = (4, 3) 635 no_jump_backwards_into_block.output = [3, 3, True] 636 637 def no_jump_into_finally_block(output): 638 try: 639 try: 640 output.append(3) 641 x = 1 642 finally: 643 output.append(6) 644 except ValueError, e: 645 output.append('finally' in str(e)) 646 647 no_jump_into_finally_block.jump = (4, 6) 648 no_jump_into_finally_block.output = [3, 6, True] # The 'finally' still runs 649 650 def no_jump_out_of_finally_block(output): 651 try: 652 try: 653 output.append(3) 654 finally: 655 output.append(5) 656 output.append(6) 657 except ValueError, e: 658 output.append('finally' in str(e)) 659 660 no_jump_out_of_finally_block.jump = (5, 1) 661 no_jump_out_of_finally_block.output = [3, True] 662 663 # This verifies the line-numbers-must-be-integers rule. 664 def no_jump_to_non_integers(output): 665 try: 666 output.append(2) 667 except ValueError, e: 668 output.append('integer' in str(e)) 669 670 no_jump_to_non_integers.jump = (2, "Spam") 671 no_jump_to_non_integers.output = [True] 672 673 def jump_across_with(output): 674 with open(test_support.TESTFN, "wb") as fp: 675 pass 676 with open(test_support.TESTFN, "wb") as fp: 677 pass 678 jump_across_with.jump = (1, 3) 679 jump_across_with.output = [] 680 681 # This verifies that you can't set f_lineno via _getframe or similar 682 # trickery. 683 def no_jump_without_trace_function(): 684 try: 685 previous_frame = sys._getframe().f_back 686 previous_frame.f_lineno = previous_frame.f_lineno 687 except ValueError, e: 688 # This is the exception we wanted; make sure the error message 689 # talks about trace functions. 690 if 'trace' not in str(e): 691 raise 692 else: 693 # Something's wrong - the expected exception wasn't raised. 694 raise RuntimeError, "Trace-function-less jump failed to fail" 695 696 697 class JumpTestCase(unittest.TestCase): 698 def compare_jump_output(self, expected, received): 699 if received != expected: 700 self.fail( "Outputs don't match:\n" + 701 "Expected: " + repr(expected) + "\n" + 702 "Received: " + repr(received)) 703 704 def run_test(self, func): 705 tracer = JumpTracer(func) 706 sys.settrace(tracer.trace) 707 output = [] 708 func(output) 709 sys.settrace(None) 710 self.compare_jump_output(func.output, output) 711 712 def test_01_jump_simple_forwards(self): 713 self.run_test(jump_simple_forwards) 714 def test_02_jump_simple_backwards(self): 715 self.run_test(jump_simple_backwards) 716 def test_03_jump_out_of_block_forwards(self): 717 self.run_test(jump_out_of_block_forwards) 718 def test_04_jump_out_of_block_backwards(self): 719 self.run_test(jump_out_of_block_backwards) 720 def test_05_jump_to_codeless_line(self): 721 self.run_test(jump_to_codeless_line) 722 def test_06_jump_to_same_line(self): 723 self.run_test(jump_to_same_line) 724 def test_07_jump_in_nested_finally(self): 725 self.run_test(jump_in_nested_finally) 726 def test_08_no_jump_too_far_forwards(self): 727 self.run_test(no_jump_too_far_forwards) 728 def test_09_no_jump_too_far_backwards(self): 729 self.run_test(no_jump_too_far_backwards) 730 def test_10_no_jump_to_except_1(self): 731 self.run_test(no_jump_to_except_1) 732 def test_11_no_jump_to_except_2(self): 733 self.run_test(no_jump_to_except_2) 734 def test_12_no_jump_to_except_3(self): 735 self.run_test(no_jump_to_except_3) 736 def test_13_no_jump_to_except_4(self): 737 self.run_test(no_jump_to_except_4) 738 def test_14_no_jump_forwards_into_block(self): 739 self.run_test(no_jump_forwards_into_block) 740 def test_15_no_jump_backwards_into_block(self): 741 self.run_test(no_jump_backwards_into_block) 742 def test_16_no_jump_into_finally_block(self): 743 self.run_test(no_jump_into_finally_block) 744 def test_17_no_jump_out_of_finally_block(self): 745 self.run_test(no_jump_out_of_finally_block) 746 def test_18_no_jump_to_non_integers(self): 747 self.run_test(no_jump_to_non_integers) 748 def test_19_no_jump_without_trace_function(self): 749 no_jump_without_trace_function() 750 def test_jump_across_with(self): 751 self.addCleanup(test_support.unlink, test_support.TESTFN) 752 self.run_test(jump_across_with) 753 754 def test_20_large_function(self): 755 d = {} 756 exec("""def f(output): # line 0 757 x = 0 # line 1 758 y = 1 # line 2 759 ''' # line 3 760 %s # lines 4-1004 761 ''' # line 1005 762 x += 1 # line 1006 763 output.append(x) # line 1007 764 return""" % ('\n' * 1000,), d) 765 f = d['f'] 766 767 f.jump = (2, 1007) 768 f.output = [0] 769 self.run_test(f) 770 771 def test_jump_to_firstlineno(self): 772 # This tests that PDB can jump back to the first line in a 773 # file. See issue #1689458. It can only be triggered in a 774 # function call if the function is defined on a single line. 775 code = compile(""" 776 # Comments don't count. 777 output.append(2) # firstlineno is here. 778 output.append(3) 779 output.append(4) 780 """, "<fake module>", "exec") 781 class fake_function: 782 func_code = code 783 jump = (2, 0) 784 tracer = JumpTracer(fake_function) 785 sys.settrace(tracer.trace) 786 namespace = {"output": []} 787 exec code in namespace 788 sys.settrace(None) 789 self.compare_jump_output([2, 3, 2, 3, 4], namespace["output"]) 790 791 792 def test_main(): 793 test_support.run_unittest( 794 TraceTestCase, 795 RaisingTraceFuncTestCase, 796 JumpTestCase 797 ) 798 799 if __name__ == "__main__": 800 test_main() 801