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