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