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