blob: 616c3a8649840c46828eaa91e3f351403851ab78 [file] [log] [blame]
xdegaye3fe33042018-03-18 21:02:47 +01001""" 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())
Steve Dowere5f41d22018-05-16 17:50:29 -0400420 self._original_tracer = None
xdegaye3fe33042018-03-18 21:02:47 +0100421
422 def __enter__(self):
423 # test_pdb does not reset Breakpoint class attributes on exit :-(
424 reset_Breakpoint()
Steve Dowere5f41d22018-05-16 17:50:29 -0400425 self._original_tracer = sys.gettrace()
xdegaye3fe33042018-03-18 21:02:47 +0100426 return self.tracer
427
428 def __exit__(self, type_=None, value=None, traceback=None):
429 reset_Breakpoint()
Steve Dowere5f41d22018-05-16 17:50:29 -0400430 sys.settrace(self._original_tracer)
xdegaye3fe33042018-03-18 21:02:47 +0100431
432 not_empty = ''
433 if self.tracer.set_list:
434 not_empty += 'All paired tuples have not been processed, '
435 not_empty += ('the last one was number %d' %
436 self.tracer.expect_set_no)
437
438 # Make a BdbNotExpectedError a unittest failure.
439 if type_ is not None and issubclass(BdbNotExpectedError, type_):
440 if isinstance(value, BaseException) and value.args:
441 err_msg = value.args[0]
442 if not_empty:
443 err_msg += '\n' + not_empty
444 if self.dry_run:
445 print(err_msg)
446 return True
447 else:
448 self.test_case.fail(err_msg)
449 else:
450 assert False, 'BdbNotExpectedError with empty args'
451
452 if not_empty:
453 if self.dry_run:
454 print(not_empty)
455 else:
456 self.test_case.fail(not_empty)
457
458def run_test(modules, set_list, skip=None):
459 """Run a test and print the dry-run results.
460
461 'modules': A dictionary mapping module names to their source code as a
462 string. The dictionary MUST include one module named
463 'test_module' with a main() function.
464 'set_list': A list of set_type tuples to be run on the module.
465
466 For example, running the following script outputs the following results:
467
468 ***************************** SCRIPT ********************************
469
470 from test.test_bdb import run_test, break_in_func
471
472 code = '''
473 def func():
474 lno = 3
475
476 def main():
477 func()
478 lno = 7
479 '''
480
481 set_list = [
482 break_in_func('func', 'test_module.py'),
483 ('continue', ),
484 ('step', ),
485 ('step', ),
486 ('step', ),
487 ('quit', ),
488 ]
489
490 modules = { 'test_module': code }
491 run_test(modules, set_list)
492
493 **************************** results ********************************
494
495 1: ('line', 2, 'tfunc_import'), ('next',),
496 2: ('line', 3, 'tfunc_import'), ('step',),
497 3: ('call', 5, 'main'), ('break', ('test_module.py', None, False, None, 'func')),
498 4: ('None', 5, 'main'), ('continue',),
499 5: ('line', 3, 'func', ({1: 1}, [])), ('step',),
500 BpNum Temp Enb Hits Ignore Where
501 1 no yes 1 0 at test_module.py:2
502 6: ('return', 3, 'func'), ('step',),
503 7: ('line', 7, 'main'), ('step',),
504 8: ('return', 7, 'main'), ('quit',),
505
506 *************************************************************************
507
508 """
509 def gen(a, b):
510 try:
511 while 1:
512 x = next(a)
513 y = next(b)
514 yield x
515 yield y
516 except StopIteration:
517 return
518
519 # Step over the import statement in tfunc_import using 'next' and step
520 # into main() in test_module.
521 sl = [('next', ), ('step', )]
522 sl.extend(set_list)
523
524 test = BaseTestCase()
525 test.dry_run = True
526 test.id = lambda : None
527 test.expect_set = list(gen(repeat(()), iter(sl)))
528 with create_modules(modules):
xdegaye3fe33042018-03-18 21:02:47 +0100529 with TracerRun(test, skip=skip) as tracer:
530 tracer.runcall(tfunc_import)
531
532@contextmanager
533def create_modules(modules):
534 with test.support.temp_cwd():
Nick Coghland5d9e022018-03-25 23:03:10 +1000535 sys.path.append(os.getcwd())
xdegaye3fe33042018-03-18 21:02:47 +0100536 try:
537 for m in modules:
538 fname = m + '.py'
539 with open(fname, 'w') as f:
540 f.write(textwrap.dedent(modules[m]))
541 linecache.checkcache(fname)
542 importlib.invalidate_caches()
543 yield
544 finally:
545 for m in modules:
546 test.support.forget(m)
Nick Coghland5d9e022018-03-25 23:03:10 +1000547 sys.path.pop()
xdegaye3fe33042018-03-18 21:02:47 +0100548
549def break_in_func(funcname, fname=__file__, temporary=False, cond=None):
550 return 'break', (fname, None, temporary, cond, funcname)
551
552TEST_MODULE = 'test_module'
553TEST_MODULE_FNAME = TEST_MODULE + '.py'
554def tfunc_import():
555 import test_module
556 test_module.main()
557
558def tfunc_main():
559 lno = 2
560 tfunc_first()
561 tfunc_second()
562 lno = 5
563 lno = 6
564 lno = 7
565
566def tfunc_first():
567 lno = 2
568 lno = 3
569 lno = 4
570
571def tfunc_second():
572 lno = 2
573
574class BaseTestCase(unittest.TestCase):
575 """Base class for all tests."""
576
577 dry_run = dry_run
578
579 def fail(self, msg=None):
580 # Override fail() to use 'raise from None' to avoid repetition of the
581 # error message and traceback.
582 raise self.failureException(msg) from None
583
584class StateTestCase(BaseTestCase):
585 """Test the step, next, return, until and quit 'set_' methods."""
586
587 def test_step(self):
588 self.expect_set = [
589 ('line', 2, 'tfunc_main'), ('step', ),
590 ('line', 3, 'tfunc_main'), ('step', ),
591 ('call', 1, 'tfunc_first'), ('step', ),
592 ('line', 2, 'tfunc_first'), ('quit', ),
593 ]
594 with TracerRun(self) as tracer:
595 tracer.runcall(tfunc_main)
596
597 def test_step_next_on_last_statement(self):
598 for set_type in ('step', 'next'):
599 with self.subTest(set_type=set_type):
600 self.expect_set = [
601 ('line', 2, 'tfunc_main'), ('step', ),
602 ('line', 3, 'tfunc_main'), ('step', ),
603 ('call', 1, 'tfunc_first'), ('break', (__file__, 3)),
604 ('None', 1, 'tfunc_first'), ('continue', ),
605 ('line', 3, 'tfunc_first', ({1:1}, [])), (set_type, ),
606 ('line', 4, 'tfunc_first'), ('quit', ),
607 ]
608 with TracerRun(self) as tracer:
609 tracer.runcall(tfunc_main)
610
611 def test_next(self):
612 self.expect_set = [
613 ('line', 2, 'tfunc_main'), ('step', ),
614 ('line', 3, 'tfunc_main'), ('next', ),
615 ('line', 4, 'tfunc_main'), ('step', ),
616 ('call', 1, 'tfunc_second'), ('step', ),
617 ('line', 2, 'tfunc_second'), ('quit', ),
618 ]
619 with TracerRun(self) as tracer:
620 tracer.runcall(tfunc_main)
621
622 def test_next_over_import(self):
623 code = """
624 def main():
625 lno = 3
626 """
627 modules = { TEST_MODULE: code }
628 with create_modules(modules):
629 self.expect_set = [
630 ('line', 2, 'tfunc_import'), ('next', ),
631 ('line', 3, 'tfunc_import'), ('quit', ),
632 ]
633 with TracerRun(self) as tracer:
634 tracer.runcall(tfunc_import)
635
636 def test_next_on_plain_statement(self):
637 # Check that set_next() is equivalent to set_step() on a plain
638 # statement.
639 self.expect_set = [
640 ('line', 2, 'tfunc_main'), ('step', ),
641 ('line', 3, 'tfunc_main'), ('step', ),
642 ('call', 1, 'tfunc_first'), ('next', ),
643 ('line', 2, 'tfunc_first'), ('quit', ),
644 ]
645 with TracerRun(self) as tracer:
646 tracer.runcall(tfunc_main)
647
648 def test_next_in_caller_frame(self):
649 # Check that set_next() in the caller frame causes the tracer
650 # to stop next in the caller frame.
651 self.expect_set = [
652 ('line', 2, 'tfunc_main'), ('step', ),
653 ('line', 3, 'tfunc_main'), ('step', ),
654 ('call', 1, 'tfunc_first'), ('up', ),
655 ('None', 3, 'tfunc_main'), ('next', ),
656 ('line', 4, 'tfunc_main'), ('quit', ),
657 ]
658 with TracerRun(self) as tracer:
659 tracer.runcall(tfunc_main)
660
661 def test_return(self):
662 self.expect_set = [
663 ('line', 2, 'tfunc_main'), ('step', ),
664 ('line', 3, 'tfunc_main'), ('step', ),
665 ('call', 1, 'tfunc_first'), ('step', ),
666 ('line', 2, 'tfunc_first'), ('return', ),
667 ('return', 4, 'tfunc_first'), ('step', ),
668 ('line', 4, 'tfunc_main'), ('quit', ),
669 ]
670 with TracerRun(self) as tracer:
671 tracer.runcall(tfunc_main)
672
673 def test_return_in_caller_frame(self):
674 self.expect_set = [
675 ('line', 2, 'tfunc_main'), ('step', ),
676 ('line', 3, 'tfunc_main'), ('step', ),
677 ('call', 1, 'tfunc_first'), ('up', ),
678 ('None', 3, 'tfunc_main'), ('return', ),
679 ('return', 7, 'tfunc_main'), ('quit', ),
680 ]
681 with TracerRun(self) as tracer:
682 tracer.runcall(tfunc_main)
683
684 def test_until(self):
685 self.expect_set = [
686 ('line', 2, 'tfunc_main'), ('step', ),
687 ('line', 3, 'tfunc_main'), ('step', ),
688 ('call', 1, 'tfunc_first'), ('step', ),
689 ('line', 2, 'tfunc_first'), ('until', (4, )),
690 ('line', 4, 'tfunc_first'), ('quit', ),
691 ]
692 with TracerRun(self) as tracer:
693 tracer.runcall(tfunc_main)
694
695 def test_until_with_too_large_count(self):
696 self.expect_set = [
697 ('line', 2, 'tfunc_main'), break_in_func('tfunc_first'),
698 ('None', 2, 'tfunc_main'), ('continue', ),
699 ('line', 2, 'tfunc_first', ({1:1}, [])), ('until', (9999, )),
700 ('return', 4, 'tfunc_first'), ('quit', ),
701 ]
702 with TracerRun(self) as tracer:
703 tracer.runcall(tfunc_main)
704
705 def test_until_in_caller_frame(self):
706 self.expect_set = [
707 ('line', 2, 'tfunc_main'), ('step', ),
708 ('line', 3, 'tfunc_main'), ('step', ),
709 ('call', 1, 'tfunc_first'), ('up', ),
710 ('None', 3, 'tfunc_main'), ('until', (6, )),
711 ('line', 6, 'tfunc_main'), ('quit', ),
712 ]
713 with TracerRun(self) as tracer:
714 tracer.runcall(tfunc_main)
715
716 def test_skip(self):
717 # Check that tracing is skipped over the import statement in
718 # 'tfunc_import()'.
719 code = """
720 def main():
721 lno = 3
722 """
723 modules = { TEST_MODULE: code }
724 with create_modules(modules):
725 self.expect_set = [
726 ('line', 2, 'tfunc_import'), ('step', ),
727 ('line', 3, 'tfunc_import'), ('quit', ),
728 ]
729 skip = ('importlib*', TEST_MODULE)
730 with TracerRun(self, skip=skip) as tracer:
731 tracer.runcall(tfunc_import)
732
733 def test_down(self):
734 # Check that set_down() raises BdbError at the newest frame.
735 self.expect_set = [
736 ('line', 2, 'tfunc_main'), ('down', ),
737 ]
738 with TracerRun(self) as tracer:
739 self.assertRaises(BdbError, tracer.runcall, tfunc_main)
740
741 def test_up(self):
742 self.expect_set = [
743 ('line', 2, 'tfunc_main'), ('step', ),
744 ('line', 3, 'tfunc_main'), ('step', ),
745 ('call', 1, 'tfunc_first'), ('up', ),
746 ('None', 3, 'tfunc_main'), ('quit', ),
747 ]
748 with TracerRun(self) as tracer:
749 tracer.runcall(tfunc_main)
750
751class BreakpointTestCase(BaseTestCase):
752 """Test the breakpoint set method."""
753
754 def test_bp_on_non_existent_module(self):
755 self.expect_set = [
756 ('line', 2, 'tfunc_import'), ('break', ('/non/existent/module.py', 1))
757 ]
758 with TracerRun(self) as tracer:
759 self.assertRaises(BdbError, tracer.runcall, tfunc_import)
760
761 def test_bp_after_last_statement(self):
762 code = """
763 def main():
764 lno = 3
765 """
766 modules = { TEST_MODULE: code }
767 with create_modules(modules):
768 self.expect_set = [
769 ('line', 2, 'tfunc_import'), ('break', (TEST_MODULE_FNAME, 4))
770 ]
771 with TracerRun(self) as tracer:
772 self.assertRaises(BdbError, tracer.runcall, tfunc_import)
773
774 def test_temporary_bp(self):
775 code = """
776 def func():
777 lno = 3
778
779 def main():
780 for i in range(2):
781 func()
782 """
783 modules = { TEST_MODULE: code }
784 with create_modules(modules):
785 self.expect_set = [
786 ('line', 2, 'tfunc_import'),
787 break_in_func('func', TEST_MODULE_FNAME, True),
788 ('None', 2, 'tfunc_import'),
789 break_in_func('func', TEST_MODULE_FNAME, True),
790 ('None', 2, 'tfunc_import'), ('continue', ),
791 ('line', 3, 'func', ({1:1}, [1])), ('continue', ),
792 ('line', 3, 'func', ({2:1}, [2])), ('quit', ),
793 ]
794 with TracerRun(self) as tracer:
795 tracer.runcall(tfunc_import)
796
797 def test_disabled_temporary_bp(self):
798 code = """
799 def func():
800 lno = 3
801
802 def main():
803 for i in range(3):
804 func()
805 """
806 modules = { TEST_MODULE: code }
807 with create_modules(modules):
808 self.expect_set = [
809 ('line', 2, 'tfunc_import'),
810 break_in_func('func', TEST_MODULE_FNAME),
811 ('None', 2, 'tfunc_import'),
812 break_in_func('func', TEST_MODULE_FNAME, True),
813 ('None', 2, 'tfunc_import'), ('disable', (2, )),
814 ('None', 2, 'tfunc_import'), ('continue', ),
815 ('line', 3, 'func', ({1:1}, [])), ('enable', (2, )),
816 ('None', 3, 'func'), ('disable', (1, )),
817 ('None', 3, 'func'), ('continue', ),
818 ('line', 3, 'func', ({2:1}, [2])), ('enable', (1, )),
819 ('None', 3, 'func'), ('continue', ),
820 ('line', 3, 'func', ({1:2}, [])), ('quit', ),
821 ]
822 with TracerRun(self) as tracer:
823 tracer.runcall(tfunc_import)
824
825 def test_bp_condition(self):
826 code = """
827 def func(a):
828 lno = 3
829
830 def main():
831 for i in range(3):
832 func(i)
833 """
834 modules = { TEST_MODULE: code }
835 with create_modules(modules):
836 self.expect_set = [
837 ('line', 2, 'tfunc_import'),
838 break_in_func('func', TEST_MODULE_FNAME, False, 'a == 2'),
839 ('None', 2, 'tfunc_import'), ('continue', ),
840 ('line', 3, 'func', ({1:3}, [])), ('quit', ),
841 ]
842 with TracerRun(self) as tracer:
843 tracer.runcall(tfunc_import)
844
845 def test_bp_exception_on_condition_evaluation(self):
846 code = """
847 def func(a):
848 lno = 3
849
850 def main():
851 func(0)
852 """
853 modules = { TEST_MODULE: code }
854 with create_modules(modules):
855 self.expect_set = [
856 ('line', 2, 'tfunc_import'),
857 break_in_func('func', TEST_MODULE_FNAME, False, '1 / 0'),
858 ('None', 2, 'tfunc_import'), ('continue', ),
859 ('line', 3, 'func', ({1:1}, [])), ('quit', ),
860 ]
861 with TracerRun(self) as tracer:
862 tracer.runcall(tfunc_import)
863
864 def test_bp_ignore_count(self):
865 code = """
866 def func():
867 lno = 3
868
869 def main():
870 for i in range(2):
871 func()
872 """
873 modules = { TEST_MODULE: code }
874 with create_modules(modules):
875 self.expect_set = [
876 ('line', 2, 'tfunc_import'),
877 break_in_func('func', TEST_MODULE_FNAME),
878 ('None', 2, 'tfunc_import'), ('ignore', (1, )),
879 ('None', 2, 'tfunc_import'), ('continue', ),
880 ('line', 3, 'func', ({1:2}, [])), ('quit', ),
881 ]
882 with TracerRun(self) as tracer:
883 tracer.runcall(tfunc_import)
884
885 def test_ignore_count_on_disabled_bp(self):
886 code = """
887 def func():
888 lno = 3
889
890 def main():
891 for i in range(3):
892 func()
893 """
894 modules = { TEST_MODULE: code }
895 with create_modules(modules):
896 self.expect_set = [
897 ('line', 2, 'tfunc_import'),
898 break_in_func('func', TEST_MODULE_FNAME),
899 ('None', 2, 'tfunc_import'),
900 break_in_func('func', TEST_MODULE_FNAME),
901 ('None', 2, 'tfunc_import'), ('ignore', (1, )),
902 ('None', 2, 'tfunc_import'), ('disable', (1, )),
903 ('None', 2, 'tfunc_import'), ('continue', ),
904 ('line', 3, 'func', ({2:1}, [])), ('enable', (1, )),
905 ('None', 3, 'func'), ('continue', ),
906 ('line', 3, 'func', ({2:2}, [])), ('continue', ),
907 ('line', 3, 'func', ({1:2}, [])), ('quit', ),
908 ]
909 with TracerRun(self) as tracer:
910 tracer.runcall(tfunc_import)
911
912 def test_clear_two_bp_on_same_line(self):
913 code = """
914 def func():
915 lno = 3
916 lno = 4
917
918 def main():
919 for i in range(3):
920 func()
921 """
922 modules = { TEST_MODULE: code }
923 with create_modules(modules):
924 self.expect_set = [
925 ('line', 2, 'tfunc_import'), ('break', (TEST_MODULE_FNAME, 3)),
926 ('None', 2, 'tfunc_import'), ('break', (TEST_MODULE_FNAME, 3)),
927 ('None', 2, 'tfunc_import'), ('break', (TEST_MODULE_FNAME, 4)),
928 ('None', 2, 'tfunc_import'), ('continue', ),
929 ('line', 3, 'func', ({1:1}, [])), ('continue', ),
930 ('line', 4, 'func', ({3:1}, [])), ('clear', (TEST_MODULE_FNAME, 3)),
931 ('None', 4, 'func'), ('continue', ),
932 ('line', 4, 'func', ({3:2}, [])), ('quit', ),
933 ]
934 with TracerRun(self) as tracer:
935 tracer.runcall(tfunc_import)
936
937 def test_clear_at_no_bp(self):
938 self.expect_set = [
939 ('line', 2, 'tfunc_import'), ('clear', (__file__, 1))
940 ]
941 with TracerRun(self) as tracer:
942 self.assertRaises(BdbError, tracer.runcall, tfunc_import)
943
944class RunTestCase(BaseTestCase):
945 """Test run, runeval and set_trace."""
946
947 def test_run_step(self):
948 # Check that the bdb 'run' method stops at the first line event.
949 code = """
950 lno = 2
951 """
952 self.expect_set = [
953 ('line', 2, '<module>'), ('step', ),
954 ('return', 2, '<module>'), ('quit', ),
955 ]
956 with TracerRun(self) as tracer:
957 tracer.run(compile(textwrap.dedent(code), '<string>', 'exec'))
958
959 def test_runeval_step(self):
960 # Test bdb 'runeval'.
961 code = """
962 def main():
963 lno = 3
964 """
965 modules = { TEST_MODULE: code }
966 with create_modules(modules):
967 self.expect_set = [
968 ('line', 1, '<module>'), ('step', ),
969 ('call', 2, 'main'), ('step', ),
970 ('line', 3, 'main'), ('step', ),
971 ('return', 3, 'main'), ('step', ),
972 ('return', 1, '<module>'), ('quit', ),
973 ]
974 import test_module
975 with TracerRun(self) as tracer:
976 tracer.runeval('test_module.main()', globals(), locals())
977
978class IssuesTestCase(BaseTestCase):
979 """Test fixed bdb issues."""
980
981 def test_step_at_return_with_no_trace_in_caller(self):
982 # Issue #13183.
983 # Check that the tracer does step into the caller frame when the
984 # trace function is not set in that frame.
985 code_1 = """
986 from test_module_2 import func
987 def main():
988 func()
989 lno = 5
990 """
991 code_2 = """
992 def func():
993 lno = 3
994 """
995 modules = {
996 TEST_MODULE: code_1,
997 'test_module_2': code_2,
998 }
999 with create_modules(modules):
1000 self.expect_set = [
1001 ('line', 2, 'tfunc_import'),
1002 break_in_func('func', 'test_module_2.py'),
1003 ('None', 2, 'tfunc_import'), ('continue', ),
1004 ('line', 3, 'func', ({1:1}, [])), ('step', ),
1005 ('return', 3, 'func'), ('step', ),
1006 ('line', 5, 'main'), ('quit', ),
1007 ]
1008 with TracerRun(self) as tracer:
1009 tracer.runcall(tfunc_import)
1010
1011 def test_next_until_return_in_generator(self):
1012 # Issue #16596.
1013 # Check that set_next(), set_until() and set_return() do not treat the
1014 # `yield` and `yield from` statements as if they were returns and stop
1015 # instead in the current frame.
1016 code = """
1017 def test_gen():
1018 yield 0
1019 lno = 4
1020 return 123
1021
1022 def main():
1023 it = test_gen()
1024 next(it)
1025 next(it)
1026 lno = 11
1027 """
1028 modules = { TEST_MODULE: code }
1029 for set_type in ('next', 'until', 'return'):
1030 with self.subTest(set_type=set_type):
1031 with create_modules(modules):
1032 self.expect_set = [
1033 ('line', 2, 'tfunc_import'),
1034 break_in_func('test_gen', TEST_MODULE_FNAME),
1035 ('None', 2, 'tfunc_import'), ('continue', ),
1036 ('line', 3, 'test_gen', ({1:1}, [])), (set_type, ),
1037 ]
1038
1039 if set_type == 'return':
1040 self.expect_set.extend(
1041 [('exception', 10, 'main', StopIteration), ('step',),
1042 ('return', 10, 'main'), ('quit', ),
1043 ]
1044 )
1045 else:
1046 self.expect_set.extend(
1047 [('line', 4, 'test_gen'), ('quit', ),]
1048 )
1049 with TracerRun(self) as tracer:
1050 tracer.runcall(tfunc_import)
1051
1052 def test_next_command_in_generator_for_loop(self):
1053 # Issue #16596.
1054 code = """
1055 def test_gen():
1056 yield 0
1057 lno = 4
1058 yield 1
1059 return 123
1060
1061 def main():
1062 for i in test_gen():
1063 lno = 10
1064 lno = 11
1065 """
1066 modules = { TEST_MODULE: code }
1067 with create_modules(modules):
1068 self.expect_set = [
1069 ('line', 2, 'tfunc_import'),
1070 break_in_func('test_gen', TEST_MODULE_FNAME),
1071 ('None', 2, 'tfunc_import'), ('continue', ),
1072 ('line', 3, 'test_gen', ({1:1}, [])), ('next', ),
1073 ('line', 4, 'test_gen'), ('next', ),
1074 ('line', 5, 'test_gen'), ('next', ),
1075 ('line', 6, 'test_gen'), ('next', ),
1076 ('exception', 9, 'main', StopIteration), ('step', ),
1077 ('line', 11, 'main'), ('quit', ),
1078
1079 ]
1080 with TracerRun(self) as tracer:
1081 tracer.runcall(tfunc_import)
1082
1083 def test_next_command_in_generator_with_subiterator(self):
1084 # Issue #16596.
1085 code = """
1086 def test_subgen():
1087 yield 0
1088 return 123
1089
1090 def test_gen():
1091 x = yield from test_subgen()
1092 return 456
1093
1094 def main():
1095 for i in test_gen():
1096 lno = 12
1097 lno = 13
1098 """
1099 modules = { TEST_MODULE: code }
1100 with create_modules(modules):
1101 self.expect_set = [
1102 ('line', 2, 'tfunc_import'),
1103 break_in_func('test_gen', TEST_MODULE_FNAME),
1104 ('None', 2, 'tfunc_import'), ('continue', ),
1105 ('line', 7, 'test_gen', ({1:1}, [])), ('next', ),
1106 ('line', 8, 'test_gen'), ('next', ),
1107 ('exception', 11, 'main', StopIteration), ('step', ),
1108 ('line', 13, 'main'), ('quit', ),
1109
1110 ]
1111 with TracerRun(self) as tracer:
1112 tracer.runcall(tfunc_import)
1113
1114 def test_return_command_in_generator_with_subiterator(self):
1115 # Issue #16596.
1116 code = """
1117 def test_subgen():
1118 yield 0
1119 return 123
1120
1121 def test_gen():
1122 x = yield from test_subgen()
1123 return 456
1124
1125 def main():
1126 for i in test_gen():
1127 lno = 12
1128 lno = 13
1129 """
1130 modules = { TEST_MODULE: code }
1131 with create_modules(modules):
1132 self.expect_set = [
1133 ('line', 2, 'tfunc_import'),
1134 break_in_func('test_subgen', TEST_MODULE_FNAME),
1135 ('None', 2, 'tfunc_import'), ('continue', ),
1136 ('line', 3, 'test_subgen', ({1:1}, [])), ('return', ),
1137 ('exception', 7, 'test_gen', StopIteration), ('return', ),
1138 ('exception', 11, 'main', StopIteration), ('step', ),
1139 ('line', 13, 'main'), ('quit', ),
1140
1141 ]
1142 with TracerRun(self) as tracer:
1143 tracer.runcall(tfunc_import)
1144
1145def test_main():
1146 test.support.run_unittest(
1147 StateTestCase,
1148 RunTestCase,
1149 BreakpointTestCase,
1150 IssuesTestCase,
1151 )
1152
1153if __name__ == "__main__":
1154 test_main()