Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | # Testing the line trace facility. |
2 | ||
3 | from test import test_support | |
4 | import unittest | |
5 | import sys | |
6 | import difflib | |
7 | ||
8 | # A very basic example. If this fails, we're in deep trouble. | |
9 | def basic(): | |
10 | return 1 | |
11 | ||
12 | basic.events = [(0, 'call'), | |
13 | (1, 'line'), | |
14 | (1, 'return')] | |
15 | ||
16 | # Armin Rigo's failing example: | |
17 | def arigo_example(): | |
18 | x = 1 | |
19 | del x | |
20 | while 0: | |
21 | pass | |
22 | x = 1 | |
23 | ||
24 | arigo_example.events = [(0, 'call'), | |
25 | (1, 'line'), | |
26 | (2, 'line'), | |
27 | (3, 'line'), | |
28 | (5, 'line'), | |
29 | (5, 'return')] | |
30 | ||
31 | # check that lines consisting of just one instruction get traced: | |
32 | def one_instr_line(): | |
33 | x = 1 | |
34 | del x | |
35 | x = 1 | |
36 | ||
37 | one_instr_line.events = [(0, 'call'), | |
38 | (1, 'line'), | |
39 | (2, 'line'), | |
40 | (3, 'line'), | |
41 | (3, 'return')] | |
42 | ||
43 | def no_pop_tops(): # 0 | |
44 | x = 1 # 1 | |
45 | for a in range(2): # 2 | |
46 | if a: # 3 | |
47 | x = 1 # 4 | |
48 | else: # 5 | |
49 | x = 1 # 6 | |
50 | ||
51 | no_pop_tops.events = [(0, 'call'), | |
52 | (1, 'line'), | |
53 | (2, 'line'), | |
54 | (3, 'line'), | |
55 | (6, 'line'), | |
56 | (2, 'line'), | |
57 | (3, 'line'), | |
58 | (4, 'line'), | |
59 | (2, 'line'), | |
60 | (2, 'return')] | |
61 | ||
62 | def no_pop_blocks(): | |
63 | while 0: | |
64 | bla | |
65 | x = 1 | |
66 | ||
67 | no_pop_blocks.events = [(0, 'call'), | |
68 | (1, 'line'), | |
69 | (3, 'line'), | |
70 | (3, 'return')] | |
71 | ||
72 | def called(): # line -3 | |
73 | x = 1 | |
74 | ||
75 | def call(): # line 0 | |
76 | called() | |
77 | ||
78 | call.events = [(0, 'call'), | |
79 | (1, 'line'), | |
80 | (-3, 'call'), | |
81 | (-2, 'line'), | |
82 | (-2, 'return'), | |
83 | (1, 'return')] | |
84 | ||
85 | def raises(): | |
86 | raise Exception | |
87 | ||
88 | def test_raise(): | |
89 | try: | |
90 | raises() | |
91 | except Exception, exc: | |
92 | x = 1 | |
93 | ||
94 | test_raise.events = [(0, 'call'), | |
95 | (1, 'line'), | |
96 | (2, 'line'), | |
97 | (-3, 'call'), | |
98 | (-2, 'line'), | |
99 | (-2, 'exception'), | |
100 | (-2, 'return'), | |
101 | (2, 'exception'), | |
102 | (3, 'line'), | |
103 | (4, 'line'), | |
104 | (4, 'return')] | |
105 | ||
106 | def _settrace_and_return(tracefunc): | |
107 | sys.settrace(tracefunc) | |
108 | sys._getframe().f_back.f_trace = tracefunc | |
109 | def settrace_and_return(tracefunc): | |
110 | _settrace_and_return(tracefunc) | |
111 | ||
112 | settrace_and_return.events = [(1, 'return')] | |
113 | ||
114 | def _settrace_and_raise(tracefunc): | |
115 | sys.settrace(tracefunc) | |
116 | sys._getframe().f_back.f_trace = tracefunc | |
117 | raise RuntimeError | |
118 | def settrace_and_raise(tracefunc): | |
119 | try: | |
120 | _settrace_and_raise(tracefunc) | |
121 | except RuntimeError, exc: | |
122 | pass | |
123 | ||
124 | settrace_and_raise.events = [(2, 'exception'), | |
125 | (3, 'line'), | |
126 | (4, 'line'), | |
127 | (4, 'return')] | |
128 | ||
129 | # implicit return example | |
130 | def ireturn_example(): | |
131 | a = 5 | |
132 | b = 5 | |
133 | if a == b: | |
134 | b = a+1 | |
135 | else: | |
136 | pass | |
137 | ||
138 | ireturn_example.events = [(0, 'call'), | |
139 | (1, 'line'), | |
140 | (2, 'line'), | |
141 | (3, 'line'), | |
142 | (4, 'line'), | |
143 | (4, 'return')] | |
144 | ||
145 | # Tight loop with while(1) example (SF #765624) | |
146 | def tightloop_example(): | |
147 | items = range(0, 3) | |
148 | try: | |
149 | i = 0 | |
150 | while 1: | |
151 | b = items[i]; i+=1 | |
152 | except IndexError: | |
153 | pass | |
154 | ||
155 | tightloop_example.events = [(0, 'call'), | |
156 | (1, 'line'), | |
157 | (2, 'line'), | |
158 | (3, 'line'), | |
159 | (4, 'line'), | |
160 | (5, 'line'), | |
161 | (5, 'line'), | |
162 | (5, 'line'), | |
163 | (5, 'line'), | |
164 | (5, 'exception'), | |
165 | (6, 'line'), | |
166 | (7, 'line'), | |
167 | (7, 'return')] | |
168 | ||
169 | def tighterloop_example(): | |
170 | items = range(1, 4) | |
171 | try: | |
172 | i = 0 | |
173 | while 1: i = items[i] | |
174 | except IndexError: | |
175 | pass | |
176 | ||
177 | tighterloop_example.events = [(0, 'call'), | |
178 | (1, 'line'), | |
179 | (2, 'line'), | |
180 | (3, 'line'), | |
181 | (4, 'line'), | |
182 | (4, 'line'), | |
183 | (4, 'line'), | |
184 | (4, 'line'), | |
185 | (4, 'exception'), | |
186 | (5, 'line'), | |
187 | (6, 'line'), | |
188 | (6, 'return')] | |
189 | ||
190 | class Tracer: | |
191 | def __init__(self): | |
192 | self.events = [] | |
193 | def trace(self, frame, event, arg): | |
194 | self.events.append((frame.f_lineno, event)) | |
195 | return self.trace | |
196 | ||
197 | class TraceTestCase(unittest.TestCase): | |
198 | def compare_events(self, line_offset, events, expected_events): | |
199 | events = [(l - line_offset, e) for (l, e) in events] | |
200 | if events != expected_events: | |
201 | self.fail( | |
202 | "events did not match expectation:\n" + | |
203 | "\n".join(difflib.ndiff(map(str, expected_events), | |
204 | map(str, events)))) | |
205 | ||
206 | ||
207 | def run_test(self, func): | |
208 | tracer = Tracer() | |
209 | sys.settrace(tracer.trace) | |
210 | func() | |
211 | sys.settrace(None) | |
212 | self.compare_events(func.func_code.co_firstlineno, | |
213 | tracer.events, func.events) | |
214 | ||
215 | def run_test2(self, func): | |
216 | tracer = Tracer() | |
217 | func(tracer.trace) | |
218 | sys.settrace(None) | |
219 | self.compare_events(func.func_code.co_firstlineno, | |
220 | tracer.events, func.events) | |
221 | ||
222 | def test_01_basic(self): | |
223 | self.run_test(basic) | |
224 | def test_02_arigo(self): | |
225 | self.run_test(arigo_example) | |
226 | def test_03_one_instr(self): | |
227 | self.run_test(one_instr_line) | |
228 | def test_04_no_pop_blocks(self): | |
229 | self.run_test(no_pop_blocks) | |
230 | def test_05_no_pop_tops(self): | |
231 | self.run_test(no_pop_tops) | |
232 | def test_06_call(self): | |
233 | self.run_test(call) | |
234 | def test_07_raise(self): | |
235 | self.run_test(test_raise) | |
236 | ||
237 | def test_08_settrace_and_return(self): | |
238 | self.run_test2(settrace_and_return) | |
239 | def test_09_settrace_and_raise(self): | |
240 | self.run_test2(settrace_and_raise) | |
241 | def test_10_ireturn(self): | |
242 | self.run_test(ireturn_example) | |
243 | def test_11_tightloop(self): | |
244 | self.run_test(tightloop_example) | |
245 | def test_12_tighterloop(self): | |
246 | self.run_test(tighterloop_example) | |
247 | ||
248 | class RaisingTraceFuncTestCase(unittest.TestCase): | |
249 | def trace(self, frame, event, arg): | |
250 | """A trace function that raises an exception in response to a | |
251 | specific trace event.""" | |
252 | if event == self.raiseOnEvent: | |
253 | raise ValueError # just something that isn't RuntimeError | |
254 | else: | |
255 | return self.trace | |
256 | ||
257 | def f(self): | |
258 | """The function to trace; raises an exception if that's the case | |
259 | we're testing, so that the 'exception' trace event fires.""" | |
260 | if self.raiseOnEvent == 'exception': | |
261 | x = 0 | |
262 | y = 1/x | |
263 | else: | |
264 | return 1 | |
265 | ||
266 | def run_test_for_event(self, event): | |
267 | """Tests that an exception raised in response to the given event is | |
268 | handled OK.""" | |
269 | self.raiseOnEvent = event | |
270 | try: | |
271 | for i in xrange(sys.getrecursionlimit() + 1): | |
272 | sys.settrace(self.trace) | |
273 | try: | |
274 | self.f() | |
275 | except ValueError: | |
276 | pass | |
277 | else: | |
278 | self.fail("exception not thrown!") | |
279 | except RuntimeError: | |
280 | self.fail("recursion counter not reset") | |
281 | ||
282 | # Test the handling of exceptions raised by each kind of trace event. | |
283 | def test_call(self): | |
284 | self.run_test_for_event('call') | |
285 | def test_line(self): | |
286 | self.run_test_for_event('line') | |
287 | def test_return(self): | |
288 | self.run_test_for_event('return') | |
289 | def test_exception(self): | |
290 | self.run_test_for_event('exception') | |
291 | ||
292 | def test_trash_stack(self): | |
293 | def f(): | |
294 | for i in range(5): | |
295 | print i # line tracing will raise an exception at this line | |
296 | ||
297 | def g(frame, why, extra): | |
298 | if (why == 'line' and | |
299 | frame.f_lineno == f.func_code.co_firstlineno + 2): | |
300 | raise RuntimeError, "i am crashing" | |
301 | return g | |
302 | ||
303 | sys.settrace(g) | |
304 | try: | |
305 | f() | |
306 | except RuntimeError: | |
307 | # the test is really that this doesn't segfault: | |
308 | import gc | |
309 | gc.collect() | |
310 | else: | |
311 | self.fail("exception not propagated") | |
312 | ||
313 | ||
314 | # 'Jump' tests: assigning to frame.f_lineno within a trace function | |
315 | # moves the execution position - it's how debuggers implement a Jump | |
316 | # command (aka. "Set next statement"). | |
317 | ||
318 | class JumpTracer: | |
319 | """Defines a trace function that jumps from one place to another, | |
320 | with the source and destination lines of the jump being defined by | |
321 | the 'jump' property of the function under test.""" | |
322 | ||
323 | def __init__(self, function): | |
324 | self.function = function | |
325 | self.jumpFrom = function.jump[0] | |
326 | self.jumpTo = function.jump[1] | |
327 | self.done = False | |
328 | ||
329 | def trace(self, frame, event, arg): | |
330 | if not self.done and frame.f_code == self.function.func_code: | |
331 | firstLine = frame.f_code.co_firstlineno | |
332 | if frame.f_lineno == firstLine + self.jumpFrom: | |
333 | # Cope with non-integer self.jumpTo (because of | |
334 | # no_jump_to_non_integers below). | |
335 | try: | |
336 | frame.f_lineno = firstLine + self.jumpTo | |
337 | except TypeError: | |
338 | frame.f_lineno = self.jumpTo | |
339 | self.done = True | |
340 | return self.trace | |
341 | ||
342 | # The first set of 'jump' tests are for things that are allowed: | |
343 | ||
344 | def jump_simple_forwards(output): | |
345 | output.append(1) | |
346 | output.append(2) | |
347 | output.append(3) | |
348 | ||
349 | jump_simple_forwards.jump = (1, 3) | |
350 | jump_simple_forwards.output = [3] | |
351 | ||
352 | def jump_simple_backwards(output): | |
353 | output.append(1) | |
354 | output.append(2) | |
355 | ||
356 | jump_simple_backwards.jump = (2, 1) | |
357 | jump_simple_backwards.output = [1, 1, 2] | |
358 | ||
359 | def jump_out_of_block_forwards(output): | |
360 | for i in 1, 2: | |
361 | output.append(2) | |
362 | for j in [3]: # Also tests jumping over a block | |
363 | output.append(4) | |
364 | output.append(5) | |
365 | ||
366 | jump_out_of_block_forwards.jump = (3, 5) | |
367 | jump_out_of_block_forwards.output = [2, 5] | |
368 | ||
369 | def jump_out_of_block_backwards(output): | |
370 | output.append(1) | |
371 | for i in [1]: | |
372 | output.append(3) | |
373 | for j in [2]: # Also tests jumping over a block | |
374 | output.append(5) | |
375 | output.append(6) | |
376 | output.append(7) | |
377 | ||
378 | jump_out_of_block_backwards.jump = (6, 1) | |
379 | jump_out_of_block_backwards.output = [1, 3, 5, 1, 3, 5, 6, 7] | |
380 | ||
381 | def jump_to_codeless_line(output): | |
382 | output.append(1) | |
383 | # Jumping to this line should skip to the next one. | |
384 | output.append(3) | |
385 | ||
386 | jump_to_codeless_line.jump = (1, 2) | |
387 | jump_to_codeless_line.output = [3] | |
388 | ||
389 | def jump_to_same_line(output): | |
390 | output.append(1) | |
391 | output.append(2) | |
392 | output.append(3) | |
393 | ||
394 | jump_to_same_line.jump = (2, 2) | |
395 | jump_to_same_line.output = [1, 2, 3] | |
396 | ||
397 | # Tests jumping within a finally block, and over one. | |
398 | def jump_in_nested_finally(output): | |
399 | try: | |
400 | output.append(2) | |
401 | finally: | |
402 | output.append(4) | |
403 | try: | |
404 | output.append(6) | |
405 | finally: | |
406 | output.append(8) | |
407 | output.append(9) | |
408 | ||
409 | jump_in_nested_finally.jump = (4, 9) | |
410 | jump_in_nested_finally.output = [2, 9] | |
411 | ||
412 | # The second set of 'jump' tests are for things that are not allowed: | |
413 | ||
414 | def no_jump_too_far_forwards(output): | |
415 | try: | |
416 | output.append(2) | |
417 | output.append(3) | |
418 | except ValueError, e: | |
419 | output.append('after' in str(e)) | |
420 | ||
421 | no_jump_too_far_forwards.jump = (3, 6) | |
422 | no_jump_too_far_forwards.output = [2, True] | |
423 | ||
424 | def no_jump_too_far_backwards(output): | |
425 | try: | |
426 | output.append(2) | |
427 | output.append(3) | |
428 | except ValueError, e: | |
429 | output.append('before' in str(e)) | |
430 | ||
431 | no_jump_too_far_backwards.jump = (3, -1) | |
432 | no_jump_too_far_backwards.output = [2, True] | |
433 | ||
434 | # Test each kind of 'except' line. | |
435 | def no_jump_to_except_1(output): | |
436 | try: | |
437 | output.append(2) | |
438 | except: | |
439 | e = sys.exc_info()[1] | |
440 | output.append('except' in str(e)) | |
441 | ||
442 | no_jump_to_except_1.jump = (2, 3) | |
443 | no_jump_to_except_1.output = [True] | |
444 | ||
445 | def no_jump_to_except_2(output): | |
446 | try: | |
447 | output.append(2) | |
448 | except ValueError: | |
449 | e = sys.exc_info()[1] | |
450 | output.append('except' in str(e)) | |
451 | ||
452 | no_jump_to_except_2.jump = (2, 3) | |
453 | no_jump_to_except_2.output = [True] | |
454 | ||
455 | def no_jump_to_except_3(output): | |
456 | try: | |
457 | output.append(2) | |
458 | except ValueError, e: | |
459 | output.append('except' in str(e)) | |
460 | ||
461 | no_jump_to_except_3.jump = (2, 3) | |
462 | no_jump_to_except_3.output = [True] | |
463 | ||
464 | def no_jump_to_except_4(output): | |
465 | try: | |
466 | output.append(2) | |
467 | except (ValueError, RuntimeError), e: | |
468 | output.append('except' in str(e)) | |
469 | ||
470 | no_jump_to_except_4.jump = (2, 3) | |
471 | no_jump_to_except_4.output = [True] | |
472 | ||
473 | def no_jump_forwards_into_block(output): | |
474 | try: | |
475 | output.append(2) | |
476 | for i in 1, 2: | |
477 | output.append(4) | |
478 | except ValueError, e: | |
479 | output.append('into' in str(e)) | |
480 | ||
481 | no_jump_forwards_into_block.jump = (2, 4) | |
482 | no_jump_forwards_into_block.output = [True] | |
483 | ||
484 | def no_jump_backwards_into_block(output): | |
485 | try: | |
486 | for i in 1, 2: | |
487 | output.append(3) | |
488 | output.append(4) | |
489 | except ValueError, e: | |
490 | output.append('into' in str(e)) | |
491 | ||
492 | no_jump_backwards_into_block.jump = (4, 3) | |
493 | no_jump_backwards_into_block.output = [3, 3, True] | |
494 | ||
495 | def no_jump_into_finally_block(output): | |
496 | try: | |
497 | try: | |
498 | output.append(3) | |
499 | x = 1 | |
500 | finally: | |
501 | output.append(6) | |
502 | except ValueError, e: | |
503 | output.append('finally' in str(e)) | |
504 | ||
505 | no_jump_into_finally_block.jump = (4, 6) | |
506 | no_jump_into_finally_block.output = [3, 6, True] # The 'finally' still runs | |
507 | ||
508 | def no_jump_out_of_finally_block(output): | |
509 | try: | |
510 | try: | |
511 | output.append(3) | |
512 | finally: | |
513 | output.append(5) | |
514 | output.append(6) | |
515 | except ValueError, e: | |
516 | output.append('finally' in str(e)) | |
517 | ||
518 | no_jump_out_of_finally_block.jump = (5, 1) | |
519 | no_jump_out_of_finally_block.output = [3, True] | |
520 | ||
521 | # This verifies the line-numbers-must-be-integers rule. | |
522 | def no_jump_to_non_integers(output): | |
523 | try: | |
524 | output.append(2) | |
525 | except ValueError, e: | |
526 | output.append('integer' in str(e)) | |
527 | ||
528 | no_jump_to_non_integers.jump = (2, "Spam") | |
529 | no_jump_to_non_integers.output = [True] | |
530 | ||
531 | # This verifies that you can't set f_lineno via _getframe or similar | |
532 | # trickery. | |
533 | def no_jump_without_trace_function(): | |
534 | try: | |
535 | previous_frame = sys._getframe().f_back | |
536 | previous_frame.f_lineno = previous_frame.f_lineno | |
537 | except ValueError, e: | |
538 | # This is the exception we wanted; make sure the error message | |
539 | # talks about trace functions. | |
540 | if 'trace' not in str(e): | |
541 | raise | |
542 | else: | |
543 | # Something's wrong - the expected exception wasn't raised. | |
544 | raise RuntimeError, "Trace-function-less jump failed to fail" | |
545 | ||
546 | ||
547 | class JumpTestCase(unittest.TestCase): | |
548 | def compare_jump_output(self, expected, received): | |
549 | if received != expected: | |
550 | self.fail( "Outputs don't match:\n" + | |
551 | "Expected: " + repr(expected) + "\n" + | |
552 | "Received: " + repr(received)) | |
553 | ||
554 | def run_test(self, func): | |
555 | tracer = JumpTracer(func) | |
556 | sys.settrace(tracer.trace) | |
557 | output = [] | |
558 | func(output) | |
559 | sys.settrace(None) | |
560 | self.compare_jump_output(func.output, output) | |
561 | ||
562 | def test_01_jump_simple_forwards(self): | |
563 | self.run_test(jump_simple_forwards) | |
564 | def test_02_jump_simple_backwards(self): | |
565 | self.run_test(jump_simple_backwards) | |
566 | def test_03_jump_out_of_block_forwards(self): | |
567 | self.run_test(jump_out_of_block_forwards) | |
568 | def test_04_jump_out_of_block_backwards(self): | |
569 | self.run_test(jump_out_of_block_backwards) | |
570 | def test_05_jump_to_codeless_line(self): | |
571 | self.run_test(jump_to_codeless_line) | |
572 | def test_06_jump_to_same_line(self): | |
573 | self.run_test(jump_to_same_line) | |
574 | def test_07_jump_in_nested_finally(self): | |
575 | self.run_test(jump_in_nested_finally) | |
576 | def test_08_no_jump_too_far_forwards(self): | |
577 | self.run_test(no_jump_too_far_forwards) | |
578 | def test_09_no_jump_too_far_backwards(self): | |
579 | self.run_test(no_jump_too_far_backwards) | |
580 | def test_10_no_jump_to_except_1(self): | |
581 | self.run_test(no_jump_to_except_1) | |
582 | def test_11_no_jump_to_except_2(self): | |
583 | self.run_test(no_jump_to_except_2) | |
584 | def test_12_no_jump_to_except_3(self): | |
585 | self.run_test(no_jump_to_except_3) | |
586 | def test_13_no_jump_to_except_4(self): | |
587 | self.run_test(no_jump_to_except_4) | |
588 | def test_14_no_jump_forwards_into_block(self): | |
589 | self.run_test(no_jump_forwards_into_block) | |
590 | def test_15_no_jump_backwards_into_block(self): | |
591 | self.run_test(no_jump_backwards_into_block) | |
592 | def test_16_no_jump_into_finally_block(self): | |
593 | self.run_test(no_jump_into_finally_block) | |
594 | def test_17_no_jump_out_of_finally_block(self): | |
595 | self.run_test(no_jump_out_of_finally_block) | |
596 | def test_18_no_jump_to_non_integers(self): | |
597 | self.run_test(no_jump_to_non_integers) | |
598 | def test_19_no_jump_without_trace_function(self): | |
599 | no_jump_without_trace_function() | |
600 | ||
601 | def test_main(): | |
602 | test_support.run_unittest( | |
603 | TraceTestCase, | |
604 | RaisingTraceFuncTestCase, | |
605 | JumpTestCase | |
606 | ) | |
607 | ||
608 | if __name__ == "__main__": | |
609 | test_main() |