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