• 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
60import test.support
61
62class BdbException(Exception): pass
63class BdbError(BdbException): """Error raised by the Bdb instance."""
64class BdbSyntaxError(BdbException): """Syntax error in the test case."""
65class BdbNotExpectedError(BdbException): """Unexpected result."""
66
67# When 'dry_run' is set to true, expect tuples are ignored and the actual
68# state of the tracer is printed after running each set_*() method of the test
69# case. The full list of breakpoints and their attributes is also printed
70# after each 'line' event where a breakpoint has been hit.
71dry_run = 0
72
73def reset_Breakpoint():
74    _bdb.Breakpoint.next = 1
75    _bdb.Breakpoint.bplist = {}
76    _bdb.Breakpoint.bpbynumber = [None]
77
78def info_breakpoints():
79    bp_list = [bp for  bp in _bdb.Breakpoint.bpbynumber if bp]
80    if not bp_list:
81        return ''
82
83    header_added = False
84    for bp in bp_list:
85        if not header_added:
86            info = 'BpNum Temp Enb Hits Ignore Where\n'
87            header_added = True
88
89        disp = 'yes ' if bp.temporary else 'no  '
90        enab = 'yes' if bp.enabled else 'no '
91        info += ('%-5d %s %s %-4d %-6d at %s:%d' %
92                    (bp.number, disp, enab, bp.hits, bp.ignore,
93                     os.path.basename(bp.file), bp.line))
94        if bp.cond:
95            info += '\n\tstop only if %s' % (bp.cond,)
96        info += '\n'
97    return info
98
99class Bdb(_bdb.Bdb):
100    """Extend Bdb to enhance test coverage."""
101
102    def trace_dispatch(self, frame, event, arg):
103        self.currentbp = None
104        return super().trace_dispatch(frame, event, arg)
105
106    def set_break(self, filename, lineno, temporary=False, cond=None,
107                  funcname=None):
108        if isinstance(funcname, str):
109            if filename == __file__:
110                globals_ = globals()
111            else:
112                module = importlib.import_module(filename[:-3])
113                globals_ = module.__dict__
114            func = eval(funcname, globals_)
115            code = func.__code__
116            filename = code.co_filename
117            lineno = code.co_firstlineno
118            funcname = code.co_name
119
120        res = super().set_break(filename, lineno, temporary=temporary,
121                                 cond=cond, funcname=funcname)
122        if isinstance(res, str):
123            raise BdbError(res)
124        return res
125
126    def get_stack(self, f, t):
127        self.stack, self.index = super().get_stack(f, t)
128        self.frame = self.stack[self.index][0]
129        return self.stack, self.index
130
131    def set_ignore(self, bpnum):
132        """Increment the ignore count of Breakpoint number 'bpnum'."""
133        bp = self.get_bpbynumber(bpnum)
134        bp.ignore += 1
135
136    def set_enable(self, bpnum):
137        bp = self.get_bpbynumber(bpnum)
138        bp.enabled = True
139
140    def set_disable(self, bpnum):
141        bp = self.get_bpbynumber(bpnum)
142        bp.enabled = False
143
144    def set_clear(self, fname, lineno):
145        err = self.clear_break(fname, lineno)
146        if err:
147            raise BdbError(err)
148
149    def set_up(self):
150        """Move up in the frame stack."""
151        if not self.index:
152            raise BdbError('Oldest frame')
153        self.index -= 1
154        self.frame = self.stack[self.index][0]
155
156    def set_down(self):
157        """Move down in the frame stack."""
158        if self.index + 1 == len(self.stack):
159            raise BdbError('Newest frame')
160        self.index += 1
161        self.frame = self.stack[self.index][0]
162
163class Tracer(Bdb):
164    """A tracer for testing the bdb module."""
165
166    def __init__(self, expect_set, skip=None, dry_run=False, test_case=None):
167        super().__init__(skip=skip)
168        self.expect_set = expect_set
169        self.dry_run = dry_run
170        self.header = ('Dry-run results for %s:' % test_case if
171                       test_case is not None else None)
172        self.init_test()
173
174    def init_test(self):
175        self.cur_except = None
176        self.expect_set_no = 0
177        self.breakpoint_hits = None
178        self.expected_list = list(islice(self.expect_set, 0, None, 2))
179        self.set_list = list(islice(self.expect_set, 1, None, 2))
180
181    def trace_dispatch(self, frame, event, arg):
182        # On an 'exception' event, call_exc_trace() in Python/ceval.c discards
183        # a BdbException raised by the Tracer instance, so we raise it on the
184        # next trace_dispatch() call that occurs unless the set_quit() or
185        # set_continue() method has been invoked on the 'exception' event.
186        if self.cur_except is not None:
187            raise self.cur_except
188
189        if event == 'exception':
190            try:
191                res = super().trace_dispatch(frame, event, arg)
192                return res
193            except BdbException as e:
194                self.cur_except = e
195                return self.trace_dispatch
196        else:
197            return super().trace_dispatch(frame, event, arg)
198
199    def user_call(self, frame, argument_list):
200        # Adopt the same behavior as pdb and, as a side effect, skip also the
201        # first 'call' event when the Tracer is started with Tracer.runcall()
202        # which may be possibly considered as a bug.
203        if not self.stop_here(frame):
204            return
205        self.process_event('call', frame, argument_list)
206        self.next_set_method()
207
208    def user_line(self, frame):
209        self.process_event('line', frame)
210
211        if self.dry_run and self.breakpoint_hits:
212            info = info_breakpoints().strip('\n')
213            # Indent each line.
214            for line in info.split('\n'):
215                print('  ' + line)
216        self.delete_temporaries()
217        self.breakpoint_hits = None
218
219        self.next_set_method()
220
221    def user_return(self, frame, return_value):
222        self.process_event('return', frame, return_value)
223        self.next_set_method()
224
225    def user_exception(self, frame, exc_info):
226        self.exc_info = exc_info
227        self.process_event('exception', frame)
228        self.next_set_method()
229
230    def do_clear(self, arg):
231        # The temporary breakpoints are deleted in user_line().
232        bp_list = [self.currentbp]
233        self.breakpoint_hits = (bp_list, bp_list)
234
235    def delete_temporaries(self):
236        if self.breakpoint_hits:
237            for n in self.breakpoint_hits[1]:
238                self.clear_bpbynumber(n)
239
240    def pop_next(self):
241        self.expect_set_no += 1
242        try:
243            self.expect = self.expected_list.pop(0)
244        except IndexError:
245            raise BdbNotExpectedError(
246                'expect_set list exhausted, cannot pop item %d' %
247                self.expect_set_no)
248        self.set_tuple = self.set_list.pop(0)
249
250    def process_event(self, event, frame, *args):
251        # Call get_stack() to enable walking the stack with set_up() and
252        # set_down().
253        tb = None
254        if event == 'exception':
255            tb = self.exc_info[2]
256        self.get_stack(frame, tb)
257
258        # A breakpoint has been hit and it is not a temporary.
259        if self.currentbp is not None and not self.breakpoint_hits:
260            bp_list = [self.currentbp]
261            self.breakpoint_hits = (bp_list, [])
262
263        # Pop next event.
264        self.event= event
265        self.pop_next()
266        if self.dry_run:
267            self.print_state(self.header)
268            return
269
270        # Validate the expected results.
271        if self.expect:
272            self.check_equal(self.expect[0], event, 'Wrong event type')
273            self.check_lno_name()
274
275        if event in ('call', 'return'):
276            self.check_expect_max_size(3)
277        elif len(self.expect) > 3:
278            if event == 'line':
279                bps, temporaries = self.expect[3]
280                bpnums = sorted(bps.keys())
281                if not self.breakpoint_hits:
282                    self.raise_not_expected(
283                        'No breakpoints hit at expect_set item %d' %
284                        self.expect_set_no)
285                self.check_equal(bpnums, self.breakpoint_hits[0],
286                    'Breakpoint numbers do not match')
287                self.check_equal([bps[n] for n in bpnums],
288                    [self.get_bpbynumber(n).hits for
289                        n in self.breakpoint_hits[0]],
290                    'Wrong breakpoint hit count')
291                self.check_equal(sorted(temporaries), self.breakpoint_hits[1],
292                    'Wrong temporary breakpoints')
293
294            elif event == 'exception':
295                if not isinstance(self.exc_info[1], self.expect[3]):
296                    self.raise_not_expected(
297                        "Wrong exception at expect_set item %d, got '%s'" %
298                        (self.expect_set_no, self.exc_info))
299
300    def check_equal(self, expected, result, msg):
301        if expected == result:
302            return
303        self.raise_not_expected("%s at expect_set item %d, got '%s'" %
304                                (msg, self.expect_set_no, result))
305
306    def check_lno_name(self):
307        """Check the line number and function co_name."""
308        s = len(self.expect)
309        if s > 1:
310            lineno = self.lno_abs2rel()
311            self.check_equal(self.expect[1], lineno, 'Wrong line number')
312        if s > 2:
313            self.check_equal(self.expect[2], self.frame.f_code.co_name,
314                                                'Wrong function name')
315
316    def check_expect_max_size(self, size):
317        if len(self.expect) > size:
318            raise BdbSyntaxError('Invalid size of the %s expect tuple: %s' %
319                                 (self.event, self.expect))
320
321    def lno_abs2rel(self):
322        fname = self.canonic(self.frame.f_code.co_filename)
323        lineno = self.frame.f_lineno
324        return ((lineno - self.frame.f_code.co_firstlineno + 1)
325            if fname == self.canonic(__file__) else lineno)
326
327    def lno_rel2abs(self, fname, lineno):
328        return (self.frame.f_code.co_firstlineno + lineno - 1
329            if (lineno and self.canonic(fname) == self.canonic(__file__))
330            else lineno)
331
332    def get_state(self):
333        lineno = self.lno_abs2rel()
334        co_name = self.frame.f_code.co_name
335        state = "('%s', %d, '%s'" % (self.event, lineno, co_name)
336        if self.breakpoint_hits:
337            bps = '{'
338            for n in self.breakpoint_hits[0]:
339                if bps != '{':
340                    bps += ', '
341                bps += '%s: %s' % (n, self.get_bpbynumber(n).hits)
342            bps += '}'
343            bps = '(' + bps + ', ' + str(self.breakpoint_hits[1]) + ')'
344            state += ', ' + bps
345        elif self.event == 'exception':
346            state += ', ' + self.exc_info[0].__name__
347        state += '), '
348        return state.ljust(32) + str(self.set_tuple) + ','
349
350    def print_state(self, header=None):
351        if header is not None and self.expect_set_no == 1:
352            print()
353            print(header)
354        print('%d: %s' % (self.expect_set_no, self.get_state()))
355
356    def raise_not_expected(self, msg):
357        msg += '\n'
358        msg += '  Expected: %s\n' % str(self.expect)
359        msg += '  Got:      ' + self.get_state()
360        raise BdbNotExpectedError(msg)
361
362    def next_set_method(self):
363        set_type = self.set_tuple[0]
364        args = self.set_tuple[1] if len(self.set_tuple) == 2 else None
365        set_method = getattr(self, 'set_' + set_type)
366
367        # The following set methods give back control to the tracer.
368        if set_type in ('step', 'continue', 'quit'):
369            set_method()
370            return
371        elif set_type in ('next', 'return'):
372            set_method(self.frame)
373            return
374        elif set_type == 'until':
375            lineno = None
376            if args:
377                lineno = self.lno_rel2abs(self.frame.f_code.co_filename,
378                                          args[0])
379            set_method(self.frame, lineno)
380            return
381
382        # The following set methods do not give back control to the tracer and
383        # next_set_method() is called recursively.
384        if (args and set_type in ('break', 'clear', 'ignore', 'enable',
385                                    'disable')) or set_type in ('up', 'down'):
386            if set_type in ('break', 'clear'):
387                fname, lineno, *remain = args
388                lineno = self.lno_rel2abs(fname, lineno)
389                args = [fname, lineno]
390                args.extend(remain)
391                set_method(*args)
392            elif set_type in ('ignore', 'enable', 'disable'):
393                set_method(*args)
394            elif set_type in ('up', 'down'):
395                set_method()
396
397            # Process the next expect_set item.
398            # It is not expected that a test may reach the recursion limit.
399            self.event= None
400            self.pop_next()
401            if self.dry_run:
402                self.print_state()
403            else:
404                if self.expect:
405                    self.check_lno_name()
406                self.check_expect_max_size(3)
407            self.next_set_method()
408        else:
409            raise BdbSyntaxError('"%s" is an invalid set_tuple' %
410                                 self.set_tuple)
411
412class TracerRun():
413    """Provide a context for running a Tracer instance with a test case."""
414
415    def __init__(self, test_case, skip=None):
416        self.test_case = test_case
417        self.dry_run = test_case.dry_run
418        self.tracer = Tracer(test_case.expect_set, skip=skip,
419                             dry_run=self.dry_run, test_case=test_case.id())
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 test.support.temp_cwd():
535        sys.path.append(os.getcwd())
536        try:
537            for m in modules:
538                fname = m + '.py'
539                with open(fname, 'w') as f:
540                    f.write(textwrap.dedent(modules[m]))
541                linecache.checkcache(fname)
542            importlib.invalidate_caches()
543            yield
544        finally:
545            for m in modules:
546                test.support.forget(m)
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*', TEST_MODULE)
730            with TracerRun(self, skip=skip) as tracer:
731                tracer.runcall(tfunc_import)
732
733    def test_down(self):
734        # Check that set_down() raises BdbError at the newest frame.
735        self.expect_set = [
736            ('line', 2, 'tfunc_main'), ('down', ),
737        ]
738        with TracerRun(self) as tracer:
739            self.assertRaises(BdbError, tracer.runcall, tfunc_main)
740
741    def test_up(self):
742        self.expect_set = [
743            ('line', 2, 'tfunc_main'),  ('step', ),
744            ('line', 3, 'tfunc_main'),  ('step', ),
745            ('call', 1, 'tfunc_first'), ('up', ),
746            ('None', 3, 'tfunc_main'),  ('quit', ),
747        ]
748        with TracerRun(self) as tracer:
749            tracer.runcall(tfunc_main)
750
751class BreakpointTestCase(BaseTestCase):
752    """Test the breakpoint set method."""
753
754    def test_bp_on_non_existent_module(self):
755        self.expect_set = [
756            ('line', 2, 'tfunc_import'), ('break', ('/non/existent/module.py', 1))
757        ]
758        with TracerRun(self) as tracer:
759            self.assertRaises(BdbError, tracer.runcall, tfunc_import)
760
761    def test_bp_after_last_statement(self):
762        code = """
763            def main():
764                lno = 3
765        """
766        modules = { TEST_MODULE: code }
767        with create_modules(modules):
768            self.expect_set = [
769                ('line', 2, 'tfunc_import'), ('break', (TEST_MODULE_FNAME, 4))
770            ]
771            with TracerRun(self) as tracer:
772                self.assertRaises(BdbError, tracer.runcall, tfunc_import)
773
774    def test_temporary_bp(self):
775        code = """
776            def func():
777                lno = 3
778
779            def main():
780                for i in range(2):
781                    func()
782        """
783        modules = { TEST_MODULE: code }
784        with create_modules(modules):
785            self.expect_set = [
786                ('line', 2, 'tfunc_import'),
787                    break_in_func('func', TEST_MODULE_FNAME, True),
788                ('None', 2, 'tfunc_import'),
789                    break_in_func('func', TEST_MODULE_FNAME, True),
790                ('None', 2, 'tfunc_import'),       ('continue', ),
791                ('line', 3, 'func', ({1:1}, [1])), ('continue', ),
792                ('line', 3, 'func', ({2:1}, [2])), ('quit', ),
793            ]
794            with TracerRun(self) as tracer:
795                tracer.runcall(tfunc_import)
796
797    def test_disabled_temporary_bp(self):
798        code = """
799            def func():
800                lno = 3
801
802            def main():
803                for i in range(3):
804                    func()
805        """
806        modules = { TEST_MODULE: code }
807        with create_modules(modules):
808            self.expect_set = [
809                ('line', 2, 'tfunc_import'),
810                    break_in_func('func', TEST_MODULE_FNAME),
811                ('None', 2, 'tfunc_import'),
812                    break_in_func('func', TEST_MODULE_FNAME, True),
813                ('None', 2, 'tfunc_import'),       ('disable', (2, )),
814                ('None', 2, 'tfunc_import'),       ('continue', ),
815                ('line', 3, 'func', ({1:1}, [])),  ('enable', (2, )),
816                ('None', 3, 'func'),               ('disable', (1, )),
817                ('None', 3, 'func'),               ('continue', ),
818                ('line', 3, 'func', ({2:1}, [2])), ('enable', (1, )),
819                ('None', 3, 'func'),               ('continue', ),
820                ('line', 3, 'func', ({1:2}, [])),  ('quit', ),
821            ]
822            with TracerRun(self) as tracer:
823                tracer.runcall(tfunc_import)
824
825    def test_bp_condition(self):
826        code = """
827            def func(a):
828                lno = 3
829
830            def main():
831                for i in range(3):
832                    func(i)
833        """
834        modules = { TEST_MODULE: code }
835        with create_modules(modules):
836            self.expect_set = [
837                ('line', 2, 'tfunc_import'),
838                    break_in_func('func', TEST_MODULE_FNAME, False, 'a == 2'),
839                ('None', 2, 'tfunc_import'),       ('continue', ),
840                ('line', 3, 'func', ({1:3}, [])),  ('quit', ),
841            ]
842            with TracerRun(self) as tracer:
843                tracer.runcall(tfunc_import)
844
845    def test_bp_exception_on_condition_evaluation(self):
846        code = """
847            def func(a):
848                lno = 3
849
850            def main():
851                func(0)
852        """
853        modules = { TEST_MODULE: code }
854        with create_modules(modules):
855            self.expect_set = [
856                ('line', 2, 'tfunc_import'),
857                    break_in_func('func', TEST_MODULE_FNAME, False, '1 / 0'),
858                ('None', 2, 'tfunc_import'),       ('continue', ),
859                ('line', 3, 'func', ({1:1}, [])),  ('quit', ),
860            ]
861            with TracerRun(self) as tracer:
862                tracer.runcall(tfunc_import)
863
864    def test_bp_ignore_count(self):
865        code = """
866            def func():
867                lno = 3
868
869            def main():
870                for i in range(2):
871                    func()
872        """
873        modules = { TEST_MODULE: code }
874        with create_modules(modules):
875            self.expect_set = [
876                ('line', 2, 'tfunc_import'),
877                    break_in_func('func', TEST_MODULE_FNAME),
878                ('None', 2, 'tfunc_import'),      ('ignore', (1, )),
879                ('None', 2, 'tfunc_import'),      ('continue', ),
880                ('line', 3, 'func', ({1:2}, [])), ('quit', ),
881            ]
882            with TracerRun(self) as tracer:
883                tracer.runcall(tfunc_import)
884
885    def test_ignore_count_on_disabled_bp(self):
886        code = """
887            def func():
888                lno = 3
889
890            def main():
891                for i in range(3):
892                    func()
893        """
894        modules = { TEST_MODULE: code }
895        with create_modules(modules):
896            self.expect_set = [
897                ('line', 2, 'tfunc_import'),
898                    break_in_func('func', TEST_MODULE_FNAME),
899                ('None', 2, 'tfunc_import'),
900                    break_in_func('func', TEST_MODULE_FNAME),
901                ('None', 2, 'tfunc_import'),      ('ignore', (1, )),
902                ('None', 2, 'tfunc_import'),      ('disable', (1, )),
903                ('None', 2, 'tfunc_import'),      ('continue', ),
904                ('line', 3, 'func', ({2:1}, [])), ('enable', (1, )),
905                ('None', 3, 'func'),              ('continue', ),
906                ('line', 3, 'func', ({2:2}, [])), ('continue', ),
907                ('line', 3, 'func', ({1:2}, [])), ('quit', ),
908            ]
909            with TracerRun(self) as tracer:
910                tracer.runcall(tfunc_import)
911
912    def test_clear_two_bp_on_same_line(self):
913        code = """
914            def func():
915                lno = 3
916                lno = 4
917
918            def main():
919                for i in range(3):
920                    func()
921        """
922        modules = { TEST_MODULE: code }
923        with create_modules(modules):
924            self.expect_set = [
925                ('line', 2, 'tfunc_import'),      ('break', (TEST_MODULE_FNAME, 3)),
926                ('None', 2, 'tfunc_import'),      ('break', (TEST_MODULE_FNAME, 3)),
927                ('None', 2, 'tfunc_import'),      ('break', (TEST_MODULE_FNAME, 4)),
928                ('None', 2, 'tfunc_import'),      ('continue', ),
929                ('line', 3, 'func', ({1:1}, [])), ('continue', ),
930                ('line', 4, 'func', ({3:1}, [])), ('clear', (TEST_MODULE_FNAME, 3)),
931                ('None', 4, 'func'),              ('continue', ),
932                ('line', 4, 'func', ({3:2}, [])), ('quit', ),
933            ]
934            with TracerRun(self) as tracer:
935                tracer.runcall(tfunc_import)
936
937    def test_clear_at_no_bp(self):
938        self.expect_set = [
939            ('line', 2, 'tfunc_import'), ('clear', (__file__, 1))
940        ]
941        with TracerRun(self) as tracer:
942            self.assertRaises(BdbError, tracer.runcall, tfunc_import)
943
944class RunTestCase(BaseTestCase):
945    """Test run, runeval and set_trace."""
946
947    def test_run_step(self):
948        # Check that the bdb 'run' method stops at the first line event.
949        code = """
950            lno = 2
951        """
952        self.expect_set = [
953            ('line', 2, '<module>'),   ('step', ),
954            ('return', 2, '<module>'), ('quit', ),
955        ]
956        with TracerRun(self) as tracer:
957            tracer.run(compile(textwrap.dedent(code), '<string>', 'exec'))
958
959    def test_runeval_step(self):
960        # Test bdb 'runeval'.
961        code = """
962            def main():
963                lno = 3
964        """
965        modules = { TEST_MODULE: code }
966        with create_modules(modules):
967            self.expect_set = [
968                ('line', 1, '<module>'),   ('step', ),
969                ('call', 2, 'main'),       ('step', ),
970                ('line', 3, 'main'),       ('step', ),
971                ('return', 3, 'main'),     ('step', ),
972                ('return', 1, '<module>'), ('quit', ),
973            ]
974            import test_module_for_bdb
975            with TracerRun(self) as tracer:
976                tracer.runeval('test_module_for_bdb.main()', globals(), locals())
977
978class IssuesTestCase(BaseTestCase):
979    """Test fixed bdb issues."""
980
981    def test_step_at_return_with_no_trace_in_caller(self):
982        # Issue #13183.
983        # Check that the tracer does step into the caller frame when the
984        # trace function is not set in that frame.
985        code_1 = """
986            from test_module_for_bdb_2 import func
987            def main():
988                func()
989                lno = 5
990        """
991        code_2 = """
992            def func():
993                lno = 3
994        """
995        modules = {
996            TEST_MODULE: code_1,
997            'test_module_for_bdb_2': code_2,
998        }
999        with create_modules(modules):
1000            self.expect_set = [
1001                ('line', 2, 'tfunc_import'),
1002                    break_in_func('func', 'test_module_for_bdb_2.py'),
1003                ('None', 2, 'tfunc_import'),      ('continue', ),
1004                ('line', 3, 'func', ({1:1}, [])), ('step', ),
1005                ('return', 3, 'func'),            ('step', ),
1006                ('line', 5, 'main'),              ('quit', ),
1007            ]
1008            with TracerRun(self) as tracer:
1009                tracer.runcall(tfunc_import)
1010
1011    def test_next_until_return_in_generator(self):
1012        # Issue #16596.
1013        # Check that set_next(), set_until() and set_return() do not treat the
1014        # `yield` and `yield from` statements as if they were returns and stop
1015        # instead in the current frame.
1016        code = """
1017            def test_gen():
1018                yield 0
1019                lno = 4
1020                return 123
1021
1022            def main():
1023                it = test_gen()
1024                next(it)
1025                next(it)
1026                lno = 11
1027        """
1028        modules = { TEST_MODULE: code }
1029        for set_type in ('next', 'until', 'return'):
1030            with self.subTest(set_type=set_type):
1031                with create_modules(modules):
1032                    self.expect_set = [
1033                        ('line', 2, 'tfunc_import'),
1034                            break_in_func('test_gen', TEST_MODULE_FNAME),
1035                        ('None', 2, 'tfunc_import'),          ('continue', ),
1036                        ('line', 3, 'test_gen', ({1:1}, [])), (set_type, ),
1037                    ]
1038
1039                    if set_type == 'return':
1040                        self.expect_set.extend(
1041                            [('exception', 10, 'main', StopIteration), ('step',),
1042                             ('return', 10, 'main'),                   ('quit', ),
1043                            ]
1044                        )
1045                    else:
1046                        self.expect_set.extend(
1047                            [('line', 4, 'test_gen'), ('quit', ),]
1048                        )
1049                    with TracerRun(self) as tracer:
1050                        tracer.runcall(tfunc_import)
1051
1052    def test_next_command_in_generator_for_loop(self):
1053        # Issue #16596.
1054        code = """
1055            def test_gen():
1056                yield 0
1057                lno = 4
1058                yield 1
1059                return 123
1060
1061            def main():
1062                for i in test_gen():
1063                    lno = 10
1064                lno = 11
1065        """
1066        modules = { TEST_MODULE: code }
1067        with create_modules(modules):
1068            self.expect_set = [
1069                ('line', 2, 'tfunc_import'),
1070                    break_in_func('test_gen', TEST_MODULE_FNAME),
1071                ('None', 2, 'tfunc_import'),             ('continue', ),
1072                ('line', 3, 'test_gen', ({1:1}, [])),    ('next', ),
1073                ('line', 4, 'test_gen'),                 ('next', ),
1074                ('line', 5, 'test_gen'),                 ('next', ),
1075                ('line', 6, 'test_gen'),                 ('next', ),
1076                ('exception', 9, 'main', StopIteration), ('step', ),
1077                ('line', 11, 'main'),                    ('quit', ),
1078
1079            ]
1080            with TracerRun(self) as tracer:
1081                tracer.runcall(tfunc_import)
1082
1083    def test_next_command_in_generator_with_subiterator(self):
1084        # Issue #16596.
1085        code = """
1086            def test_subgen():
1087                yield 0
1088                return 123
1089
1090            def test_gen():
1091                x = yield from test_subgen()
1092                return 456
1093
1094            def main():
1095                for i in test_gen():
1096                    lno = 12
1097                lno = 13
1098        """
1099        modules = { TEST_MODULE: code }
1100        with create_modules(modules):
1101            self.expect_set = [
1102                ('line', 2, 'tfunc_import'),
1103                    break_in_func('test_gen', TEST_MODULE_FNAME),
1104                ('None', 2, 'tfunc_import'),              ('continue', ),
1105                ('line', 7, 'test_gen', ({1:1}, [])),     ('next', ),
1106                ('line', 8, 'test_gen'),                  ('next', ),
1107                ('exception', 11, 'main', StopIteration), ('step', ),
1108                ('line', 13, 'main'),                     ('quit', ),
1109
1110            ]
1111            with TracerRun(self) as tracer:
1112                tracer.runcall(tfunc_import)
1113
1114    def test_return_command_in_generator_with_subiterator(self):
1115        # Issue #16596.
1116        code = """
1117            def test_subgen():
1118                yield 0
1119                return 123
1120
1121            def test_gen():
1122                x = yield from test_subgen()
1123                return 456
1124
1125            def main():
1126                for i in test_gen():
1127                    lno = 12
1128                lno = 13
1129        """
1130        modules = { TEST_MODULE: code }
1131        with create_modules(modules):
1132            self.expect_set = [
1133                ('line', 2, 'tfunc_import'),
1134                    break_in_func('test_subgen', TEST_MODULE_FNAME),
1135                ('None', 2, 'tfunc_import'),                  ('continue', ),
1136                ('line', 3, 'test_subgen', ({1:1}, [])),      ('return', ),
1137                ('exception', 7, 'test_gen', StopIteration),  ('return', ),
1138                ('exception', 11, 'main', StopIteration),     ('step', ),
1139                ('line', 13, 'main'),                         ('quit', ),
1140
1141            ]
1142            with TracerRun(self) as tracer:
1143                tracer.runcall(tfunc_import)
1144
1145def test_main():
1146    test.support.run_unittest(
1147        StateTestCase,
1148        RunTestCase,
1149        BreakpointTestCase,
1150        IssuesTestCase,
1151    )
1152
1153if __name__ == "__main__":
1154    test_main()
1155