blob: bda74a2be775076fe65aa3a87c6fb02ab1be7161 [file] [log] [blame]
Miss Islington (bot)fdd8e8b2018-03-18 13:25:15 -07001""" Test the bdb module.
2
3 A test defines a list of tuples that may be seen as paired tuples, each
4 pair being defined by 'expect_tuple, set_tuple' as follows:
5
6 ([event, [lineno[, co_name[, eargs]]]]), (set_type, [sargs])
7
8 * 'expect_tuple' describes the expected current state of the Bdb instance.
9 It may be the empty tuple and no check is done in that case.
10 * 'set_tuple' defines the set_*() method to be invoked when the Bdb
11 instance reaches this state.
12
13 Example of an 'expect_tuple, set_tuple' pair:
14
15 ('line', 2, 'tfunc_main'), ('step', )
16
17 Definitions of the members of the 'expect_tuple':
18 event:
19 Name of the trace event. The set methods that do not give back
20 control to the tracer [1] do not trigger a tracer event and in
21 that case the next 'event' may be 'None' by convention, its value
22 is not checked.
23 [1] Methods that trigger a trace event are set_step(), set_next(),
24 set_return(), set_until() and set_continue().
25 lineno:
26 Line number. Line numbers are relative to the start of the
27 function when tracing a function in the test_bdb module (i.e. this
28 module).
29 co_name:
30 Name of the function being currently traced.
31 eargs:
32 A tuple:
33 * On an 'exception' event the tuple holds a class object, the
34 current exception must be an instance of this class.
35 * On a 'line' event, the tuple holds a dictionary and a list. The
36 dictionary maps each breakpoint number that has been hit on this
37 line to its hits count. The list holds the list of breakpoint
38 number temporaries that are being deleted.
39
40 Definitions of the members of the 'set_tuple':
41 set_type:
42 The type of the set method to be invoked. This may
43 be the type of one of the Bdb set methods: 'step', 'next',
44 'until', 'return', 'continue', 'break', 'quit', or the type of one
45 of the set methods added by test_bdb.Bdb: 'ignore', 'enable',
46 'disable', 'clear', 'up', 'down'.
47 sargs:
48 The arguments of the set method if any, packed in a tuple.
49"""
50
51import bdb as _bdb
52import sys
53import os
54import unittest
55import textwrap
56import importlib
57import linecache
58from contextlib import contextmanager
59from itertools import islice, repeat
60import test.support
61
62class BdbException(Exception): pass
63class BdbError(BdbException): """Error raised by the Bdb instance."""
64class BdbSyntaxError(BdbException): """Syntax error in the test case."""
65class BdbNotExpectedError(BdbException): """Unexpected result."""
66
67# When 'dry_run' is set to true, expect tuples are ignored and the actual
68# state of the tracer is printed after running each set_*() method of the test
69# case. The full list of breakpoints and their attributes is also printed
70# after each 'line' event where a breakpoint has been hit.
71dry_run = 0
72
73def reset_Breakpoint():
74 _bdb.Breakpoint.next = 1
75 _bdb.Breakpoint.bplist = {}
76 _bdb.Breakpoint.bpbynumber = [None]
77
78def info_breakpoints():
79 bp_list = [bp for bp in _bdb.Breakpoint.bpbynumber if bp]
80 if not bp_list:
81 return ''
82
83 header_added = False
84 for bp in bp_list:
85 if not header_added:
86 info = 'BpNum Temp Enb Hits Ignore Where\n'
87 header_added = True
88
89 disp = 'yes ' if bp.temporary else 'no '
90 enab = 'yes' if bp.enabled else 'no '
91 info += ('%-5d %s %s %-4d %-6d at %s:%d' %
92 (bp.number, disp, enab, bp.hits, bp.ignore,
93 os.path.basename(bp.file), bp.line))
94 if bp.cond:
95 info += '\n\tstop only if %s' % (bp.cond,)
96 info += '\n'
97 return info
98
99class Bdb(_bdb.Bdb):
100 """Extend Bdb to enhance test coverage."""
101
102 def trace_dispatch(self, frame, event, arg):
103 self.currentbp = None
104 return super().trace_dispatch(frame, event, arg)
105
106 def set_break(self, filename, lineno, temporary=False, cond=None,
107 funcname=None):
108 if isinstance(funcname, str):
109 if filename == __file__:
110 globals_ = globals()
111 else:
112 module = importlib.import_module(filename[:-3])
113 globals_ = module.__dict__
114 func = eval(funcname, globals_)
115 code = func.__code__
116 filename = code.co_filename
117 lineno = code.co_firstlineno
118 funcname = code.co_name
119
120 res = super().set_break(filename, lineno, temporary=temporary,
121 cond=cond, funcname=funcname)
122 if isinstance(res, str):
123 raise BdbError(res)
124 return res
125
126 def get_stack(self, f, t):
127 self.stack, self.index = super().get_stack(f, t)
128 self.frame = self.stack[self.index][0]
129 return self.stack, self.index
130
131 def set_ignore(self, bpnum):
132 """Increment the ignore count of Breakpoint number 'bpnum'."""
133 bp = self.get_bpbynumber(bpnum)
134 bp.ignore += 1
135
136 def set_enable(self, bpnum):
137 bp = self.get_bpbynumber(bpnum)
138 bp.enabled = True
139
140 def set_disable(self, bpnum):
141 bp = self.get_bpbynumber(bpnum)
142 bp.enabled = False
143
144 def set_clear(self, fname, lineno):
145 err = self.clear_break(fname, lineno)
146 if err:
147 raise BdbError(err)
148
149 def set_up(self):
150 """Move up in the frame stack."""
151 if not self.index:
152 raise BdbError('Oldest frame')
153 self.index -= 1
154 self.frame = self.stack[self.index][0]
155
156 def set_down(self):
157 """Move down in the frame stack."""
158 if self.index + 1 == len(self.stack):
159 raise BdbError('Newest frame')
160 self.index += 1
161 self.frame = self.stack[self.index][0]
162
163class Tracer(Bdb):
164 """A tracer for testing the bdb module."""
165
166 def __init__(self, expect_set, skip=None, dry_run=False, test_case=None):
167 super().__init__(skip=skip)
168 self.expect_set = expect_set
169 self.dry_run = dry_run
170 self.header = ('Dry-run results for %s:' % test_case if
171 test_case is not None else None)
172 self.init_test()
173
174 def init_test(self):
175 self.cur_except = None
176 self.expect_set_no = 0
177 self.breakpoint_hits = None
178 self.expected_list = list(islice(self.expect_set, 0, None, 2))
179 self.set_list = list(islice(self.expect_set, 1, None, 2))
180
181 def trace_dispatch(self, frame, event, arg):
182 # On an 'exception' event, call_exc_trace() in Python/ceval.c discards
183 # a BdbException raised by the Tracer instance, so we raise it on the
184 # next trace_dispatch() call that occurs unless the set_quit() or
185 # set_continue() method has been invoked on the 'exception' event.
186 if self.cur_except is not None:
187 raise self.cur_except
188
189 if event == 'exception':
190 try:
191 res = super().trace_dispatch(frame, event, arg)
192 return res
193 except BdbException as e:
194 self.cur_except = e
195 return self.trace_dispatch
196 else:
197 return super().trace_dispatch(frame, event, arg)
198
199 def user_call(self, frame, argument_list):
200 # Adopt the same behavior as pdb and, as a side effect, skip also the
201 # first 'call' event when the Tracer is started with Tracer.runcall()
202 # which may be possibly considered as a bug.
203 if not self.stop_here(frame):
204 return
205 self.process_event('call', frame, argument_list)
206 self.next_set_method()
207
208 def user_line(self, frame):
209 self.process_event('line', frame)
210
211 if self.dry_run and self.breakpoint_hits:
212 info = info_breakpoints().strip('\n')
213 # Indent each line.
214 for line in info.split('\n'):
215 print(' ' + line)
216 self.delete_temporaries()
217 self.breakpoint_hits = None
218
219 self.next_set_method()
220
221 def user_return(self, frame, return_value):
222 self.process_event('return', frame, return_value)
223 self.next_set_method()
224
225 def user_exception(self, frame, exc_info):
226 self.exc_info = exc_info
227 self.process_event('exception', frame)
228 self.next_set_method()
229
230 def do_clear(self, arg):
231 # The temporary breakpoints are deleted in user_line().
232 bp_list = [self.currentbp]
233 self.breakpoint_hits = (bp_list, bp_list)
234
235 def delete_temporaries(self):
236 if self.breakpoint_hits:
237 for n in self.breakpoint_hits[1]:
238 self.clear_bpbynumber(n)
239
240 def pop_next(self):
241 self.expect_set_no += 1
242 try:
243 self.expect = self.expected_list.pop(0)
244 except IndexError:
245 raise BdbNotExpectedError(
246 'expect_set list exhausted, cannot pop item %d' %
247 self.expect_set_no)
248 self.set_tuple = self.set_list.pop(0)
249
250 def process_event(self, event, frame, *args):
251 # Call get_stack() to enable walking the stack with set_up() and
252 # set_down().
253 tb = None
254 if event == 'exception':
255 tb = self.exc_info[2]
256 self.get_stack(frame, tb)
257
258 # A breakpoint has been hit and it is not a temporary.
259 if self.currentbp is not None and not self.breakpoint_hits:
260 bp_list = [self.currentbp]
261 self.breakpoint_hits = (bp_list, [])
262
263 # Pop next event.
264 self.event= event
265 self.pop_next()
266 if self.dry_run:
267 self.print_state(self.header)
268 return
269
270 # Validate the expected results.
271 if self.expect:
272 self.check_equal(self.expect[0], event, 'Wrong event type')
273 self.check_lno_name()
274
275 if event in ('call', 'return'):
276 self.check_expect_max_size(3)
277 elif len(self.expect) > 3:
278 if event == 'line':
279 bps, temporaries = self.expect[3]
280 bpnums = sorted(bps.keys())
281 if not self.breakpoint_hits:
282 self.raise_not_expected(
283 'No breakpoints hit at expect_set item %d' %
284 self.expect_set_no)
285 self.check_equal(bpnums, self.breakpoint_hits[0],
286 'Breakpoint numbers do not match')
287 self.check_equal([bps[n] for n in bpnums],
288 [self.get_bpbynumber(n).hits for
289 n in self.breakpoint_hits[0]],
290 'Wrong breakpoint hit count')
291 self.check_equal(sorted(temporaries), self.breakpoint_hits[1],
292 'Wrong temporary breakpoints')
293
294 elif event == 'exception':
295 if not isinstance(self.exc_info[1], self.expect[3]):
296 self.raise_not_expected(
297 "Wrong exception at expect_set item %d, got '%s'" %
298 (self.expect_set_no, self.exc_info))
299
300 def check_equal(self, expected, result, msg):
301 if expected == result:
302 return
303 self.raise_not_expected("%s at expect_set item %d, got '%s'" %
304 (msg, self.expect_set_no, result))
305
306 def check_lno_name(self):
307 """Check the line number and function co_name."""
308 s = len(self.expect)
309 if s > 1:
310 lineno = self.lno_abs2rel()
311 self.check_equal(self.expect[1], lineno, 'Wrong line number')
312 if s > 2:
313 self.check_equal(self.expect[2], self.frame.f_code.co_name,
314 'Wrong function name')
315
316 def check_expect_max_size(self, size):
317 if len(self.expect) > size:
318 raise BdbSyntaxError('Invalid size of the %s expect tuple: %s' %
319 (self.event, self.expect))
320
321 def lno_abs2rel(self):
322 fname = self.canonic(self.frame.f_code.co_filename)
323 lineno = self.frame.f_lineno
324 return ((lineno - self.frame.f_code.co_firstlineno + 1)
325 if fname == self.canonic(__file__) else lineno)
326
327 def lno_rel2abs(self, fname, lineno):
328 return (self.frame.f_code.co_firstlineno + lineno - 1
329 if (lineno and self.canonic(fname) == self.canonic(__file__))
330 else lineno)
331
332 def get_state(self):
333 lineno = self.lno_abs2rel()
334 co_name = self.frame.f_code.co_name
335 state = "('%s', %d, '%s'" % (self.event, lineno, co_name)
336 if self.breakpoint_hits:
337 bps = '{'
338 for n in self.breakpoint_hits[0]:
339 if bps != '{':
340 bps += ', '
341 bps += '%s: %s' % (n, self.get_bpbynumber(n).hits)
342 bps += '}'
343 bps = '(' + bps + ', ' + str(self.breakpoint_hits[1]) + ')'
344 state += ', ' + bps
345 elif self.event == 'exception':
346 state += ', ' + self.exc_info[0].__name__
347 state += '), '
348 return state.ljust(32) + str(self.set_tuple) + ','
349
350 def print_state(self, header=None):
351 if header is not None and self.expect_set_no == 1:
352 print()
353 print(header)
354 print('%d: %s' % (self.expect_set_no, self.get_state()))
355
356 def raise_not_expected(self, msg):
357 msg += '\n'
358 msg += ' Expected: %s\n' % str(self.expect)
359 msg += ' Got: ' + self.get_state()
360 raise BdbNotExpectedError(msg)
361
362 def next_set_method(self):
363 set_type = self.set_tuple[0]
364 args = self.set_tuple[1] if len(self.set_tuple) == 2 else None
365 set_method = getattr(self, 'set_' + set_type)
366
367 # The following set methods give back control to the tracer.
368 if set_type in ('step', 'continue', 'quit'):
369 set_method()
370 return
371 elif set_type in ('next', 'return'):
372 set_method(self.frame)
373 return
374 elif set_type == 'until':
375 lineno = None
376 if args:
377 lineno = self.lno_rel2abs(self.frame.f_code.co_filename,
378 args[0])
379 set_method(self.frame, lineno)
380 return
381
382 # The following set methods do not give back control to the tracer and
383 # next_set_method() is called recursively.
384 if (args and set_type in ('break', 'clear', 'ignore', 'enable',
385 'disable')) or set_type in ('up', 'down'):
386 if set_type in ('break', 'clear'):
387 fname, lineno, *remain = args
388 lineno = self.lno_rel2abs(fname, lineno)
389 args = [fname, lineno]
390 args.extend(remain)
391 set_method(*args)
392 elif set_type in ('ignore', 'enable', 'disable'):
393 set_method(*args)
394 elif set_type in ('up', 'down'):
395 set_method()
396
397 # Process the next expect_set item.
398 # It is not expected that a test may reach the recursion limit.
399 self.event= None
400 self.pop_next()
401 if self.dry_run:
402 self.print_state()
403 else:
404 if self.expect:
405 self.check_lno_name()
406 self.check_expect_max_size(3)
407 self.next_set_method()
408 else:
409 raise BdbSyntaxError('"%s" is an invalid set_tuple' %
410 self.set_tuple)
411
412class TracerRun():
413 """Provide a context for running a Tracer instance with a test case."""
414
415 def __init__(self, test_case, skip=None):
416 self.test_case = test_case
417 self.dry_run = test_case.dry_run
418 self.tracer = Tracer(test_case.expect_set, skip=skip,
419 dry_run=self.dry_run, test_case=test_case.id())
420
421 def __enter__(self):
422 # test_pdb does not reset Breakpoint class attributes on exit :-(
423 reset_Breakpoint()
424 return self.tracer
425
426 def __exit__(self, type_=None, value=None, traceback=None):
427 reset_Breakpoint()
428 sys.settrace(None)
429
430 not_empty = ''
431 if self.tracer.set_list:
432 not_empty += 'All paired tuples have not been processed, '
433 not_empty += ('the last one was number %d' %
434 self.tracer.expect_set_no)
435
436 # Make a BdbNotExpectedError a unittest failure.
437 if type_ is not None and issubclass(BdbNotExpectedError, type_):
438 if isinstance(value, BaseException) and value.args:
439 err_msg = value.args[0]
440 if not_empty:
441 err_msg += '\n' + not_empty
442 if self.dry_run:
443 print(err_msg)
444 return True
445 else:
446 self.test_case.fail(err_msg)
447 else:
448 assert False, 'BdbNotExpectedError with empty args'
449
450 if not_empty:
451 if self.dry_run:
452 print(not_empty)
453 else:
454 self.test_case.fail(not_empty)
455
456def run_test(modules, set_list, skip=None):
457 """Run a test and print the dry-run results.
458
459 'modules': A dictionary mapping module names to their source code as a
460 string. The dictionary MUST include one module named
461 'test_module' with a main() function.
462 'set_list': A list of set_type tuples to be run on the module.
463
464 For example, running the following script outputs the following results:
465
466 ***************************** SCRIPT ********************************
467
468 from test.test_bdb import run_test, break_in_func
469
470 code = '''
471 def func():
472 lno = 3
473
474 def main():
475 func()
476 lno = 7
477 '''
478
479 set_list = [
480 break_in_func('func', 'test_module.py'),
481 ('continue', ),
482 ('step', ),
483 ('step', ),
484 ('step', ),
485 ('quit', ),
486 ]
487
488 modules = { 'test_module': code }
489 run_test(modules, set_list)
490
491 **************************** results ********************************
492
493 1: ('line', 2, 'tfunc_import'), ('next',),
494 2: ('line', 3, 'tfunc_import'), ('step',),
495 3: ('call', 5, 'main'), ('break', ('test_module.py', None, False, None, 'func')),
496 4: ('None', 5, 'main'), ('continue',),
497 5: ('line', 3, 'func', ({1: 1}, [])), ('step',),
498 BpNum Temp Enb Hits Ignore Where
499 1 no yes 1 0 at test_module.py:2
500 6: ('return', 3, 'func'), ('step',),
501 7: ('line', 7, 'main'), ('step',),
502 8: ('return', 7, 'main'), ('quit',),
503
504 *************************************************************************
505
506 """
507 def gen(a, b):
508 try:
509 while 1:
510 x = next(a)
511 y = next(b)
512 yield x
513 yield y
514 except StopIteration:
515 return
516
517 # Step over the import statement in tfunc_import using 'next' and step
518 # into main() in test_module.
519 sl = [('next', ), ('step', )]
520 sl.extend(set_list)
521
522 test = BaseTestCase()
523 test.dry_run = True
524 test.id = lambda : None
525 test.expect_set = list(gen(repeat(()), iter(sl)))
526 with create_modules(modules):
Miss Islington (bot)fdd8e8b2018-03-18 13:25:15 -0700527 with TracerRun(test, skip=skip) as tracer:
528 tracer.runcall(tfunc_import)
529
530@contextmanager
531def create_modules(modules):
532 with test.support.temp_cwd():
Nick Coghlanee378452018-03-25 23:43:50 +1000533 sys.path.append(os.getcwd())
Miss Islington (bot)fdd8e8b2018-03-18 13:25:15 -0700534 try:
535 for m in modules:
536 fname = m + '.py'
537 with open(fname, 'w') as f:
538 f.write(textwrap.dedent(modules[m]))
539 linecache.checkcache(fname)
540 importlib.invalidate_caches()
541 yield
542 finally:
543 for m in modules:
544 test.support.forget(m)
Nick Coghlanee378452018-03-25 23:43:50 +1000545 sys.path.pop()
Miss Islington (bot)fdd8e8b2018-03-18 13:25:15 -0700546
547def break_in_func(funcname, fname=__file__, temporary=False, cond=None):
548 return 'break', (fname, None, temporary, cond, funcname)
549
550TEST_MODULE = 'test_module'
551TEST_MODULE_FNAME = TEST_MODULE + '.py'
552def tfunc_import():
553 import test_module
554 test_module.main()
555
556def tfunc_main():
557 lno = 2
558 tfunc_first()
559 tfunc_second()
560 lno = 5
561 lno = 6
562 lno = 7
563
564def tfunc_first():
565 lno = 2
566 lno = 3
567 lno = 4
568
569def tfunc_second():
570 lno = 2
571
572class BaseTestCase(unittest.TestCase):
573 """Base class for all tests."""
574
575 dry_run = dry_run
576
577 def fail(self, msg=None):
578 # Override fail() to use 'raise from None' to avoid repetition of the
579 # error message and traceback.
580 raise self.failureException(msg) from None
581
582class StateTestCase(BaseTestCase):
583 """Test the step, next, return, until and quit 'set_' methods."""
584
585 def test_step(self):
586 self.expect_set = [
587 ('line', 2, 'tfunc_main'), ('step', ),
588 ('line', 3, 'tfunc_main'), ('step', ),
589 ('call', 1, 'tfunc_first'), ('step', ),
590 ('line', 2, 'tfunc_first'), ('quit', ),
591 ]
592 with TracerRun(self) as tracer:
593 tracer.runcall(tfunc_main)
594
595 def test_step_next_on_last_statement(self):
596 for set_type in ('step', 'next'):
597 with self.subTest(set_type=set_type):
598 self.expect_set = [
599 ('line', 2, 'tfunc_main'), ('step', ),
600 ('line', 3, 'tfunc_main'), ('step', ),
601 ('call', 1, 'tfunc_first'), ('break', (__file__, 3)),
602 ('None', 1, 'tfunc_first'), ('continue', ),
603 ('line', 3, 'tfunc_first', ({1:1}, [])), (set_type, ),
604 ('line', 4, 'tfunc_first'), ('quit', ),
605 ]
606 with TracerRun(self) as tracer:
607 tracer.runcall(tfunc_main)
608
609 def test_next(self):
610 self.expect_set = [
611 ('line', 2, 'tfunc_main'), ('step', ),
612 ('line', 3, 'tfunc_main'), ('next', ),
613 ('line', 4, 'tfunc_main'), ('step', ),
614 ('call', 1, 'tfunc_second'), ('step', ),
615 ('line', 2, 'tfunc_second'), ('quit', ),
616 ]
617 with TracerRun(self) as tracer:
618 tracer.runcall(tfunc_main)
619
620 def test_next_over_import(self):
621 code = """
622 def main():
623 lno = 3
624 """
625 modules = { TEST_MODULE: code }
626 with create_modules(modules):
627 self.expect_set = [
628 ('line', 2, 'tfunc_import'), ('next', ),
629 ('line', 3, 'tfunc_import'), ('quit', ),
630 ]
631 with TracerRun(self) as tracer:
632 tracer.runcall(tfunc_import)
633
634 def test_next_on_plain_statement(self):
635 # Check that set_next() is equivalent to set_step() on a plain
636 # statement.
637 self.expect_set = [
638 ('line', 2, 'tfunc_main'), ('step', ),
639 ('line', 3, 'tfunc_main'), ('step', ),
640 ('call', 1, 'tfunc_first'), ('next', ),
641 ('line', 2, 'tfunc_first'), ('quit', ),
642 ]
643 with TracerRun(self) as tracer:
644 tracer.runcall(tfunc_main)
645
646 def test_next_in_caller_frame(self):
647 # Check that set_next() in the caller frame causes the tracer
648 # to stop next in the caller frame.
649 self.expect_set = [
650 ('line', 2, 'tfunc_main'), ('step', ),
651 ('line', 3, 'tfunc_main'), ('step', ),
652 ('call', 1, 'tfunc_first'), ('up', ),
653 ('None', 3, 'tfunc_main'), ('next', ),
654 ('line', 4, 'tfunc_main'), ('quit', ),
655 ]
656 with TracerRun(self) as tracer:
657 tracer.runcall(tfunc_main)
658
659 def test_return(self):
660 self.expect_set = [
661 ('line', 2, 'tfunc_main'), ('step', ),
662 ('line', 3, 'tfunc_main'), ('step', ),
663 ('call', 1, 'tfunc_first'), ('step', ),
664 ('line', 2, 'tfunc_first'), ('return', ),
665 ('return', 4, 'tfunc_first'), ('step', ),
666 ('line', 4, 'tfunc_main'), ('quit', ),
667 ]
668 with TracerRun(self) as tracer:
669 tracer.runcall(tfunc_main)
670
671 def test_return_in_caller_frame(self):
672 self.expect_set = [
673 ('line', 2, 'tfunc_main'), ('step', ),
674 ('line', 3, 'tfunc_main'), ('step', ),
675 ('call', 1, 'tfunc_first'), ('up', ),
676 ('None', 3, 'tfunc_main'), ('return', ),
677 ('return', 7, 'tfunc_main'), ('quit', ),
678 ]
679 with TracerRun(self) as tracer:
680 tracer.runcall(tfunc_main)
681
682 def test_until(self):
683 self.expect_set = [
684 ('line', 2, 'tfunc_main'), ('step', ),
685 ('line', 3, 'tfunc_main'), ('step', ),
686 ('call', 1, 'tfunc_first'), ('step', ),
687 ('line', 2, 'tfunc_first'), ('until', (4, )),
688 ('line', 4, 'tfunc_first'), ('quit', ),
689 ]
690 with TracerRun(self) as tracer:
691 tracer.runcall(tfunc_main)
692
693 def test_until_with_too_large_count(self):
694 self.expect_set = [
695 ('line', 2, 'tfunc_main'), break_in_func('tfunc_first'),
696 ('None', 2, 'tfunc_main'), ('continue', ),
697 ('line', 2, 'tfunc_first', ({1:1}, [])), ('until', (9999, )),
698 ('return', 4, 'tfunc_first'), ('quit', ),
699 ]
700 with TracerRun(self) as tracer:
701 tracer.runcall(tfunc_main)
702
703 def test_until_in_caller_frame(self):
704 self.expect_set = [
705 ('line', 2, 'tfunc_main'), ('step', ),
706 ('line', 3, 'tfunc_main'), ('step', ),
707 ('call', 1, 'tfunc_first'), ('up', ),
708 ('None', 3, 'tfunc_main'), ('until', (6, )),
709 ('line', 6, 'tfunc_main'), ('quit', ),
710 ]
711 with TracerRun(self) as tracer:
712 tracer.runcall(tfunc_main)
713
714 def test_skip(self):
715 # Check that tracing is skipped over the import statement in
716 # 'tfunc_import()'.
717 code = """
718 def main():
719 lno = 3
720 """
721 modules = { TEST_MODULE: code }
722 with create_modules(modules):
723 self.expect_set = [
724 ('line', 2, 'tfunc_import'), ('step', ),
725 ('line', 3, 'tfunc_import'), ('quit', ),
726 ]
727 skip = ('importlib*', TEST_MODULE)
728 with TracerRun(self, skip=skip) as tracer:
729 tracer.runcall(tfunc_import)
730
731 def test_down(self):
732 # Check that set_down() raises BdbError at the newest frame.
733 self.expect_set = [
734 ('line', 2, 'tfunc_main'), ('down', ),
735 ]
736 with TracerRun(self) as tracer:
737 self.assertRaises(BdbError, tracer.runcall, tfunc_main)
738
739 def test_up(self):
740 self.expect_set = [
741 ('line', 2, 'tfunc_main'), ('step', ),
742 ('line', 3, 'tfunc_main'), ('step', ),
743 ('call', 1, 'tfunc_first'), ('up', ),
744 ('None', 3, 'tfunc_main'), ('quit', ),
745 ]
746 with TracerRun(self) as tracer:
747 tracer.runcall(tfunc_main)
748
749class BreakpointTestCase(BaseTestCase):
750 """Test the breakpoint set method."""
751
752 def test_bp_on_non_existent_module(self):
753 self.expect_set = [
754 ('line', 2, 'tfunc_import'), ('break', ('/non/existent/module.py', 1))
755 ]
756 with TracerRun(self) as tracer:
757 self.assertRaises(BdbError, tracer.runcall, tfunc_import)
758
759 def test_bp_after_last_statement(self):
760 code = """
761 def main():
762 lno = 3
763 """
764 modules = { TEST_MODULE: code }
765 with create_modules(modules):
766 self.expect_set = [
767 ('line', 2, 'tfunc_import'), ('break', (TEST_MODULE_FNAME, 4))
768 ]
769 with TracerRun(self) as tracer:
770 self.assertRaises(BdbError, tracer.runcall, tfunc_import)
771
772 def test_temporary_bp(self):
773 code = """
774 def func():
775 lno = 3
776
777 def main():
778 for i in range(2):
779 func()
780 """
781 modules = { TEST_MODULE: code }
782 with create_modules(modules):
783 self.expect_set = [
784 ('line', 2, 'tfunc_import'),
785 break_in_func('func', TEST_MODULE_FNAME, True),
786 ('None', 2, 'tfunc_import'),
787 break_in_func('func', TEST_MODULE_FNAME, True),
788 ('None', 2, 'tfunc_import'), ('continue', ),
789 ('line', 3, 'func', ({1:1}, [1])), ('continue', ),
790 ('line', 3, 'func', ({2:1}, [2])), ('quit', ),
791 ]
792 with TracerRun(self) as tracer:
793 tracer.runcall(tfunc_import)
794
795 def test_disabled_temporary_bp(self):
796 code = """
797 def func():
798 lno = 3
799
800 def main():
801 for i in range(3):
802 func()
803 """
804 modules = { TEST_MODULE: code }
805 with create_modules(modules):
806 self.expect_set = [
807 ('line', 2, 'tfunc_import'),
808 break_in_func('func', TEST_MODULE_FNAME),
809 ('None', 2, 'tfunc_import'),
810 break_in_func('func', TEST_MODULE_FNAME, True),
811 ('None', 2, 'tfunc_import'), ('disable', (2, )),
812 ('None', 2, 'tfunc_import'), ('continue', ),
813 ('line', 3, 'func', ({1:1}, [])), ('enable', (2, )),
814 ('None', 3, 'func'), ('disable', (1, )),
815 ('None', 3, 'func'), ('continue', ),
816 ('line', 3, 'func', ({2:1}, [2])), ('enable', (1, )),
817 ('None', 3, 'func'), ('continue', ),
818 ('line', 3, 'func', ({1:2}, [])), ('quit', ),
819 ]
820 with TracerRun(self) as tracer:
821 tracer.runcall(tfunc_import)
822
823 def test_bp_condition(self):
824 code = """
825 def func(a):
826 lno = 3
827
828 def main():
829 for i in range(3):
830 func(i)
831 """
832 modules = { TEST_MODULE: code }
833 with create_modules(modules):
834 self.expect_set = [
835 ('line', 2, 'tfunc_import'),
836 break_in_func('func', TEST_MODULE_FNAME, False, 'a == 2'),
837 ('None', 2, 'tfunc_import'), ('continue', ),
838 ('line', 3, 'func', ({1:3}, [])), ('quit', ),
839 ]
840 with TracerRun(self) as tracer:
841 tracer.runcall(tfunc_import)
842
843 def test_bp_exception_on_condition_evaluation(self):
844 code = """
845 def func(a):
846 lno = 3
847
848 def main():
849 func(0)
850 """
851 modules = { TEST_MODULE: code }
852 with create_modules(modules):
853 self.expect_set = [
854 ('line', 2, 'tfunc_import'),
855 break_in_func('func', TEST_MODULE_FNAME, False, '1 / 0'),
856 ('None', 2, 'tfunc_import'), ('continue', ),
857 ('line', 3, 'func', ({1:1}, [])), ('quit', ),
858 ]
859 with TracerRun(self) as tracer:
860 tracer.runcall(tfunc_import)
861
862 def test_bp_ignore_count(self):
863 code = """
864 def func():
865 lno = 3
866
867 def main():
868 for i in range(2):
869 func()
870 """
871 modules = { TEST_MODULE: code }
872 with create_modules(modules):
873 self.expect_set = [
874 ('line', 2, 'tfunc_import'),
875 break_in_func('func', TEST_MODULE_FNAME),
876 ('None', 2, 'tfunc_import'), ('ignore', (1, )),
877 ('None', 2, 'tfunc_import'), ('continue', ),
878 ('line', 3, 'func', ({1:2}, [])), ('quit', ),
879 ]
880 with TracerRun(self) as tracer:
881 tracer.runcall(tfunc_import)
882
883 def test_ignore_count_on_disabled_bp(self):
884 code = """
885 def func():
886 lno = 3
887
888 def main():
889 for i in range(3):
890 func()
891 """
892 modules = { TEST_MODULE: code }
893 with create_modules(modules):
894 self.expect_set = [
895 ('line', 2, 'tfunc_import'),
896 break_in_func('func', TEST_MODULE_FNAME),
897 ('None', 2, 'tfunc_import'),
898 break_in_func('func', TEST_MODULE_FNAME),
899 ('None', 2, 'tfunc_import'), ('ignore', (1, )),
900 ('None', 2, 'tfunc_import'), ('disable', (1, )),
901 ('None', 2, 'tfunc_import'), ('continue', ),
902 ('line', 3, 'func', ({2:1}, [])), ('enable', (1, )),
903 ('None', 3, 'func'), ('continue', ),
904 ('line', 3, 'func', ({2:2}, [])), ('continue', ),
905 ('line', 3, 'func', ({1:2}, [])), ('quit', ),
906 ]
907 with TracerRun(self) as tracer:
908 tracer.runcall(tfunc_import)
909
910 def test_clear_two_bp_on_same_line(self):
911 code = """
912 def func():
913 lno = 3
914 lno = 4
915
916 def main():
917 for i in range(3):
918 func()
919 """
920 modules = { TEST_MODULE: code }
921 with create_modules(modules):
922 self.expect_set = [
923 ('line', 2, 'tfunc_import'), ('break', (TEST_MODULE_FNAME, 3)),
924 ('None', 2, 'tfunc_import'), ('break', (TEST_MODULE_FNAME, 3)),
925 ('None', 2, 'tfunc_import'), ('break', (TEST_MODULE_FNAME, 4)),
926 ('None', 2, 'tfunc_import'), ('continue', ),
927 ('line', 3, 'func', ({1:1}, [])), ('continue', ),
928 ('line', 4, 'func', ({3:1}, [])), ('clear', (TEST_MODULE_FNAME, 3)),
929 ('None', 4, 'func'), ('continue', ),
930 ('line', 4, 'func', ({3:2}, [])), ('quit', ),
931 ]
932 with TracerRun(self) as tracer:
933 tracer.runcall(tfunc_import)
934
935 def test_clear_at_no_bp(self):
936 self.expect_set = [
937 ('line', 2, 'tfunc_import'), ('clear', (__file__, 1))
938 ]
939 with TracerRun(self) as tracer:
940 self.assertRaises(BdbError, tracer.runcall, tfunc_import)
941
942class RunTestCase(BaseTestCase):
943 """Test run, runeval and set_trace."""
944
945 def test_run_step(self):
946 # Check that the bdb 'run' method stops at the first line event.
947 code = """
948 lno = 2
949 """
950 self.expect_set = [
951 ('line', 2, '<module>'), ('step', ),
952 ('return', 2, '<module>'), ('quit', ),
953 ]
954 with TracerRun(self) as tracer:
955 tracer.run(compile(textwrap.dedent(code), '<string>', 'exec'))
956
957 def test_runeval_step(self):
958 # Test bdb 'runeval'.
959 code = """
960 def main():
961 lno = 3
962 """
963 modules = { TEST_MODULE: code }
964 with create_modules(modules):
965 self.expect_set = [
966 ('line', 1, '<module>'), ('step', ),
967 ('call', 2, 'main'), ('step', ),
968 ('line', 3, 'main'), ('step', ),
969 ('return', 3, 'main'), ('step', ),
970 ('return', 1, '<module>'), ('quit', ),
971 ]
972 import test_module
973 with TracerRun(self) as tracer:
974 tracer.runeval('test_module.main()', globals(), locals())
975
976class IssuesTestCase(BaseTestCase):
977 """Test fixed bdb issues."""
978
979 def test_step_at_return_with_no_trace_in_caller(self):
980 # Issue #13183.
981 # Check that the tracer does step into the caller frame when the
982 # trace function is not set in that frame.
983 code_1 = """
984 from test_module_2 import func
985 def main():
986 func()
987 lno = 5
988 """
989 code_2 = """
990 def func():
991 lno = 3
992 """
993 modules = {
994 TEST_MODULE: code_1,
995 'test_module_2': code_2,
996 }
997 with create_modules(modules):
998 self.expect_set = [
999 ('line', 2, 'tfunc_import'),
1000 break_in_func('func', 'test_module_2.py'),
1001 ('None', 2, 'tfunc_import'), ('continue', ),
1002 ('line', 3, 'func', ({1:1}, [])), ('step', ),
1003 ('return', 3, 'func'), ('step', ),
1004 ('line', 5, 'main'), ('quit', ),
1005 ]
1006 with TracerRun(self) as tracer:
1007 tracer.runcall(tfunc_import)
1008
1009 def test_next_until_return_in_generator(self):
1010 # Issue #16596.
1011 # Check that set_next(), set_until() and set_return() do not treat the
1012 # `yield` and `yield from` statements as if they were returns and stop
1013 # instead in the current frame.
1014 code = """
1015 def test_gen():
1016 yield 0
1017 lno = 4
1018 return 123
1019
1020 def main():
1021 it = test_gen()
1022 next(it)
1023 next(it)
1024 lno = 11
1025 """
1026 modules = { TEST_MODULE: code }
1027 for set_type in ('next', 'until', 'return'):
1028 with self.subTest(set_type=set_type):
1029 with create_modules(modules):
1030 self.expect_set = [
1031 ('line', 2, 'tfunc_import'),
1032 break_in_func('test_gen', TEST_MODULE_FNAME),
1033 ('None', 2, 'tfunc_import'), ('continue', ),
1034 ('line', 3, 'test_gen', ({1:1}, [])), (set_type, ),
1035 ]
1036
1037 if set_type == 'return':
1038 self.expect_set.extend(
1039 [('exception', 10, 'main', StopIteration), ('step',),
1040 ('return', 10, 'main'), ('quit', ),
1041 ]
1042 )
1043 else:
1044 self.expect_set.extend(
1045 [('line', 4, 'test_gen'), ('quit', ),]
1046 )
1047 with TracerRun(self) as tracer:
1048 tracer.runcall(tfunc_import)
1049
1050 def test_next_command_in_generator_for_loop(self):
1051 # Issue #16596.
1052 code = """
1053 def test_gen():
1054 yield 0
1055 lno = 4
1056 yield 1
1057 return 123
1058
1059 def main():
1060 for i in test_gen():
1061 lno = 10
1062 lno = 11
1063 """
1064 modules = { TEST_MODULE: code }
1065 with create_modules(modules):
1066 self.expect_set = [
1067 ('line', 2, 'tfunc_import'),
1068 break_in_func('test_gen', TEST_MODULE_FNAME),
1069 ('None', 2, 'tfunc_import'), ('continue', ),
1070 ('line', 3, 'test_gen', ({1:1}, [])), ('next', ),
1071 ('line', 4, 'test_gen'), ('next', ),
1072 ('line', 5, 'test_gen'), ('next', ),
1073 ('line', 6, 'test_gen'), ('next', ),
1074 ('exception', 9, 'main', StopIteration), ('step', ),
1075 ('line', 11, 'main'), ('quit', ),
1076
1077 ]
1078 with TracerRun(self) as tracer:
1079 tracer.runcall(tfunc_import)
1080
1081 def test_next_command_in_generator_with_subiterator(self):
1082 # Issue #16596.
1083 code = """
1084 def test_subgen():
1085 yield 0
1086 return 123
1087
1088 def test_gen():
1089 x = yield from test_subgen()
1090 return 456
1091
1092 def main():
1093 for i in test_gen():
1094 lno = 12
1095 lno = 13
1096 """
1097 modules = { TEST_MODULE: code }
1098 with create_modules(modules):
1099 self.expect_set = [
1100 ('line', 2, 'tfunc_import'),
1101 break_in_func('test_gen', TEST_MODULE_FNAME),
1102 ('None', 2, 'tfunc_import'), ('continue', ),
1103 ('line', 7, 'test_gen', ({1:1}, [])), ('next', ),
1104 ('line', 8, 'test_gen'), ('next', ),
1105 ('exception', 11, 'main', StopIteration), ('step', ),
1106 ('line', 13, 'main'), ('quit', ),
1107
1108 ]
1109 with TracerRun(self) as tracer:
1110 tracer.runcall(tfunc_import)
1111
1112 def test_return_command_in_generator_with_subiterator(self):
1113 # Issue #16596.
1114 code = """
1115 def test_subgen():
1116 yield 0
1117 return 123
1118
1119 def test_gen():
1120 x = yield from test_subgen()
1121 return 456
1122
1123 def main():
1124 for i in test_gen():
1125 lno = 12
1126 lno = 13
1127 """
1128 modules = { TEST_MODULE: code }
1129 with create_modules(modules):
1130 self.expect_set = [
1131 ('line', 2, 'tfunc_import'),
1132 break_in_func('test_subgen', TEST_MODULE_FNAME),
1133 ('None', 2, 'tfunc_import'), ('continue', ),
1134 ('line', 3, 'test_subgen', ({1:1}, [])), ('return', ),
1135 ('exception', 7, 'test_gen', StopIteration), ('return', ),
1136 ('exception', 11, 'main', StopIteration), ('step', ),
1137 ('line', 13, 'main'), ('quit', ),
1138
1139 ]
1140 with TracerRun(self) as tracer:
1141 tracer.runcall(tfunc_import)
1142
1143def test_main():
1144 test.support.run_unittest(
1145 StateTestCase,
1146 RunTestCase,
1147 BreakpointTestCase,
1148 IssuesTestCase,
1149 )
1150
1151if __name__ == "__main__":
1152 test_main()