blob: 43134e98bd49beee75eab8b428596ccb6107555f [file] [log] [blame]
Alexander Belopolsky85edadb2010-07-25 15:23:24 +00001# Testing the line trace facility.
2
3from test import support
4import unittest
5import sys
6import difflib
7import gc
8
9# A very basic example. If this fails, we're in deep trouble.
10def basic():
11 return 1
12
13basic.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".
26def arigo_example():
27 x = 1
28 del x
29 while 0:
30 pass
31 x = 1
32
33arigo_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:
40def one_instr_line():
41 x = 1
42 del x
43 x = 1
44
45one_instr_line.events = [(0, 'call'),
46 (1, 'line'),
47 (2, 'line'),
48 (3, 'line'),
49 (3, 'return')]
50
51def 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
59no_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
70def no_pop_blocks():
71 y = 1
72 while not y:
73 bla
74 x = 1
75
76no_pop_blocks.events = [(0, 'call'),
77 (1, 'line'),
78 (2, 'line'),
79 (4, 'line'),
80 (4, 'return')]
81
82def called(): # line -3
83 x = 1
84
85def call(): # line 0
86 called()
87
88call.events = [(0, 'call'),
89 (1, 'line'),
90 (-3, 'call'),
91 (-2, 'line'),
92 (-2, 'return'),
93 (1, 'return')]
94
95def raises():
96 raise Exception
97
98def test_raise():
99 try:
100 raises()
101 except Exception as exc:
102 x = 1
103
104test_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
116def _settrace_and_return(tracefunc):
117 sys.settrace(tracefunc)
118 sys._getframe().f_back.f_trace = tracefunc
119def settrace_and_return(tracefunc):
120 _settrace_and_return(tracefunc)
121
122settrace_and_return.events = [(1, 'return')]
123
124def _settrace_and_raise(tracefunc):
125 sys.settrace(tracefunc)
126 sys._getframe().f_back.f_trace = tracefunc
127 raise RuntimeError
128def settrace_and_raise(tracefunc):
129 try:
130 _settrace_and_raise(tracefunc)
131 except RuntimeError as exc:
132 pass
133
134settrace_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.
147def ireturn_example():
148 a = 5
149 b = 5
150 if a == b:
151 b = a+1
152 else:
153 pass
154
155ireturn_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)
164def 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
173tightloop_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
187def tighterloop_example():
188 items = range(1, 4)
189 try:
190 i = 0
191 while 1: i = items[i]
192 except IndexError:
193 pass
194
195tighterloop_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
208def generator_function():
209 try:
210 yield True
211 "continued"
212 finally:
213 "finally"
214def 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
222generator_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
236class 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
247class 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.__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.__code__.co_firstlineno,
283 tracer.events, func.events)
284
285 def set_and_retrieve_none(self):
286 sys.settrace(None)
287 assert sys.gettrace() is None
288
289 def 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 namespace = {}
383 exec("def f():\n" + "\n" * 256 + " pass", namespace)
384 self.run_and_compare(
385 namespace["f"],
386 [(0, 'call'),
387 (257, 'line'),
388 (257, 'return')])
389
390
391class RaisingTraceFuncTestCase(unittest.TestCase):
392 def trace(self, frame, event, arg):
393 """A trace function that raises an exception in response to a
394 specific trace event."""
395 if event == self.raiseOnEvent:
396 raise ValueError # just something that isn't RuntimeError
397 else:
398 return self.trace
399
400 def f(self):
401 """The function to trace; raises an exception if that's the case
402 we're testing, so that the 'exception' trace event fires."""
403 if self.raiseOnEvent == 'exception':
404 x = 0
405 y = 1/x
406 else:
407 return 1
408
409 def run_test_for_event(self, event):
410 """Tests that an exception raised in response to the given event is
411 handled OK."""
412 self.raiseOnEvent = event
413 try:
414 for i in range(sys.getrecursionlimit() + 1):
415 sys.settrace(self.trace)
416 try:
417 self.f()
418 except ValueError:
419 pass
420 else:
421 self.fail("exception not thrown!")
422 except RuntimeError:
423 self.fail("recursion counter not reset")
424
425 # Test the handling of exceptions raised by each kind of trace event.
426 def test_call(self):
427 self.run_test_for_event('call')
428 def test_line(self):
429 self.run_test_for_event('line')
430 def test_return(self):
431 self.run_test_for_event('return')
432 def test_exception(self):
433 self.run_test_for_event('exception')
434
435 def test_trash_stack(self):
436 def f():
437 for i in range(5):
438 print(i) # line tracing will raise an exception at this line
439
440 def g(frame, why, extra):
441 if (why == 'line' and
442 frame.f_lineno == f.__code__.co_firstlineno + 2):
443 raise RuntimeError("i am crashing")
444 return g
445
446 sys.settrace(g)
447 try:
448 f()
449 except RuntimeError:
450 # the test is really that this doesn't segfault:
451 import gc
452 gc.collect()
453 else:
454 self.fail("exception not propagated")
455
456
457# 'Jump' tests: assigning to frame.f_lineno within a trace function
458# moves the execution position - it's how debuggers implement a Jump
459# command (aka. "Set next statement").
460
461class JumpTracer:
462 """Defines a trace function that jumps from one place to another,
463 with the source and destination lines of the jump being defined by
464 the 'jump' property of the function under test."""
465
466 def __init__(self, function):
467 self.function = function
468 self.jumpFrom = function.jump[0]
469 self.jumpTo = function.jump[1]
470 self.done = False
471
472 def trace(self, frame, event, arg):
473 if not self.done and frame.f_code == self.function.__code__:
474 firstLine = frame.f_code.co_firstlineno
475 if event == 'line' and frame.f_lineno == firstLine + self.jumpFrom:
476 # Cope with non-integer self.jumpTo (because of
477 # no_jump_to_non_integers below).
478 try:
479 frame.f_lineno = firstLine + self.jumpTo
480 except TypeError:
481 frame.f_lineno = self.jumpTo
482 self.done = True
483 return self.trace
484
485# The first set of 'jump' tests are for things that are allowed:
486
487def jump_simple_forwards(output):
488 output.append(1)
489 output.append(2)
490 output.append(3)
491
492jump_simple_forwards.jump = (1, 3)
493jump_simple_forwards.output = [3]
494
495def jump_simple_backwards(output):
496 output.append(1)
497 output.append(2)
498
499jump_simple_backwards.jump = (2, 1)
500jump_simple_backwards.output = [1, 1, 2]
501
502def jump_out_of_block_forwards(output):
503 for i in 1, 2:
504 output.append(2)
505 for j in [3]: # Also tests jumping over a block
506 output.append(4)
507 output.append(5)
508
509jump_out_of_block_forwards.jump = (3, 5)
510jump_out_of_block_forwards.output = [2, 5]
511
512def jump_out_of_block_backwards(output):
513 output.append(1)
514 for i in [1]:
515 output.append(3)
516 for j in [2]: # Also tests jumping over a block
517 output.append(5)
518 output.append(6)
519 output.append(7)
520
521jump_out_of_block_backwards.jump = (6, 1)
522jump_out_of_block_backwards.output = [1, 3, 5, 1, 3, 5, 6, 7]
523
524def jump_to_codeless_line(output):
525 output.append(1)
526 # Jumping to this line should skip to the next one.
527 output.append(3)
528
529jump_to_codeless_line.jump = (1, 2)
530jump_to_codeless_line.output = [3]
531
532def jump_to_same_line(output):
533 output.append(1)
534 output.append(2)
535 output.append(3)
536
537jump_to_same_line.jump = (2, 2)
538jump_to_same_line.output = [1, 2, 3]
539
540# Tests jumping within a finally block, and over one.
541def jump_in_nested_finally(output):
542 try:
543 output.append(2)
544 finally:
545 output.append(4)
546 try:
547 output.append(6)
548 finally:
549 output.append(8)
550 output.append(9)
551
552jump_in_nested_finally.jump = (4, 9)
553jump_in_nested_finally.output = [2, 9]
554
555# The second set of 'jump' tests are for things that are not allowed:
556
557def no_jump_too_far_forwards(output):
558 try:
559 output.append(2)
560 output.append(3)
561 except ValueError as e:
562 output.append('after' in str(e))
563
564no_jump_too_far_forwards.jump = (3, 6)
565no_jump_too_far_forwards.output = [2, True]
566
567def no_jump_too_far_backwards(output):
568 try:
569 output.append(2)
570 output.append(3)
571 except ValueError as e:
572 output.append('before' in str(e))
573
574no_jump_too_far_backwards.jump = (3, -1)
575no_jump_too_far_backwards.output = [2, True]
576
577# Test each kind of 'except' line.
578def no_jump_to_except_1(output):
579 try:
580 output.append(2)
581 except:
582 e = sys.exc_info()[1]
583 output.append('except' in str(e))
584
585no_jump_to_except_1.jump = (2, 3)
586no_jump_to_except_1.output = [True]
587
588def no_jump_to_except_2(output):
589 try:
590 output.append(2)
591 except ValueError:
592 e = sys.exc_info()[1]
593 output.append('except' in str(e))
594
595no_jump_to_except_2.jump = (2, 3)
596no_jump_to_except_2.output = [True]
597
598def no_jump_to_except_3(output):
599 try:
600 output.append(2)
601 except ValueError as e:
602 output.append('except' in str(e))
603
604no_jump_to_except_3.jump = (2, 3)
605no_jump_to_except_3.output = [True]
606
607def no_jump_to_except_4(output):
608 try:
609 output.append(2)
610 except (ValueError, RuntimeError) as e:
611 output.append('except' in str(e))
612
613no_jump_to_except_4.jump = (2, 3)
614no_jump_to_except_4.output = [True]
615
616def no_jump_forwards_into_block(output):
617 try:
618 output.append(2)
619 for i in 1, 2:
620 output.append(4)
621 except ValueError as e:
622 output.append('into' in str(e))
623
624no_jump_forwards_into_block.jump = (2, 4)
625no_jump_forwards_into_block.output = [True]
626
627def no_jump_backwards_into_block(output):
628 try:
629 for i in 1, 2:
630 output.append(3)
631 output.append(4)
632 except ValueError as e:
633 output.append('into' in str(e))
634
635no_jump_backwards_into_block.jump = (4, 3)
636no_jump_backwards_into_block.output = [3, 3, True]
637
638def no_jump_into_finally_block(output):
639 try:
640 try:
641 output.append(3)
642 x = 1
643 finally:
644 output.append(6)
645 except ValueError as e:
646 output.append('finally' in str(e))
647
648no_jump_into_finally_block.jump = (4, 6)
649no_jump_into_finally_block.output = [3, 6, True] # The 'finally' still runs
650
651def no_jump_out_of_finally_block(output):
652 try:
653 try:
654 output.append(3)
655 finally:
656 output.append(5)
657 output.append(6)
658 except ValueError as e:
659 output.append('finally' in str(e))
660
661no_jump_out_of_finally_block.jump = (5, 1)
662no_jump_out_of_finally_block.output = [3, True]
663
664# This verifies the line-numbers-must-be-integers rule.
665def no_jump_to_non_integers(output):
666 try:
667 output.append(2)
668 except ValueError as e:
669 output.append('integer' in str(e))
670
671no_jump_to_non_integers.jump = (2, "Spam")
672no_jump_to_non_integers.output = [True]
673
674# This verifies that you can't set f_lineno via _getframe or similar
675# trickery.
676def no_jump_without_trace_function():
677 try:
678 previous_frame = sys._getframe().f_back
679 previous_frame.f_lineno = previous_frame.f_lineno
680 except ValueError as e:
681 # This is the exception we wanted; make sure the error message
682 # talks about trace functions.
683 if 'trace' not in str(e):
684 raise
685 else:
686 # Something's wrong - the expected exception wasn't raised.
687 raise RuntimeError("Trace-function-less jump failed to fail")
688
689
690class JumpTestCase(unittest.TestCase):
691 def compare_jump_output(self, expected, received):
692 if received != expected:
693 self.fail( "Outputs don't match:\n" +
694 "Expected: " + repr(expected) + "\n" +
695 "Received: " + repr(received))
696
697 def run_test(self, func):
698 tracer = JumpTracer(func)
699 sys.settrace(tracer.trace)
700 output = []
701 func(output)
702 sys.settrace(None)
703 self.compare_jump_output(func.output, output)
704
705 def test_01_jump_simple_forwards(self):
706 self.run_test(jump_simple_forwards)
707 def test_02_jump_simple_backwards(self):
708 self.run_test(jump_simple_backwards)
709 def test_03_jump_out_of_block_forwards(self):
710 self.run_test(jump_out_of_block_forwards)
711 def test_04_jump_out_of_block_backwards(self):
712 self.run_test(jump_out_of_block_backwards)
713 def test_05_jump_to_codeless_line(self):
714 self.run_test(jump_to_codeless_line)
715 def test_06_jump_to_same_line(self):
716 self.run_test(jump_to_same_line)
717 def test_07_jump_in_nested_finally(self):
718 self.run_test(jump_in_nested_finally)
719 def test_08_no_jump_too_far_forwards(self):
720 self.run_test(no_jump_too_far_forwards)
721 def test_09_no_jump_too_far_backwards(self):
722 self.run_test(no_jump_too_far_backwards)
723 def test_10_no_jump_to_except_1(self):
724 self.run_test(no_jump_to_except_1)
725 def test_11_no_jump_to_except_2(self):
726 self.run_test(no_jump_to_except_2)
727 def test_12_no_jump_to_except_3(self):
728 self.run_test(no_jump_to_except_3)
729 def test_13_no_jump_to_except_4(self):
730 self.run_test(no_jump_to_except_4)
731 def test_14_no_jump_forwards_into_block(self):
732 self.run_test(no_jump_forwards_into_block)
733 def test_15_no_jump_backwards_into_block(self):
734 self.run_test(no_jump_backwards_into_block)
735 def test_16_no_jump_into_finally_block(self):
736 self.run_test(no_jump_into_finally_block)
737 def test_17_no_jump_out_of_finally_block(self):
738 self.run_test(no_jump_out_of_finally_block)
739 def test_18_no_jump_to_non_integers(self):
740 self.run_test(no_jump_to_non_integers)
741 def test_19_no_jump_without_trace_function(self):
742 no_jump_without_trace_function()
743
744 def test_20_large_function(self):
745 d = {}
746 exec("""def f(output): # line 0
747 x = 0 # line 1
748 y = 1 # line 2
749 ''' # line 3
750 %s # lines 4-1004
751 ''' # line 1005
752 x += 1 # line 1006
753 output.append(x) # line 1007
754 return""" % ('\n' * 1000,), d)
755 f = d['f']
756
757 f.jump = (2, 1007)
758 f.output = [0]
759 self.run_test(f)
760
761 def test_jump_to_firstlineno(self):
762 # This tests that PDB can jump back to the first line in a
763 # file. See issue #1689458. It can only be triggered in a
764 # function call if the function is defined on a single line.
765 code = compile("""
766# Comments don't count.
767output.append(2) # firstlineno is here.
768output.append(3)
769output.append(4)
770""", "<fake module>", "exec")
771 class fake_function:
772 __code__ = code
773 jump = (2, 0)
774 tracer = JumpTracer(fake_function)
775 sys.settrace(tracer.trace)
776 namespace = {"output": []}
777 exec(code, namespace)
778 sys.settrace(None)
779 self.compare_jump_output([2, 3, 2, 3, 4], namespace["output"])
780
781
782def test_main():
783 support.run_unittest(
784 TraceTestCase,
785 RaisingTraceFuncTestCase,
786 JumpTestCase
787 )
788
789if __name__ == "__main__":
790 test_main()