[Python-checkins] bpo-19417: Add test_bdb.py (GH-5217)

Miss Islington (bot) webhook-mailer at python.org
Sun Mar 18 16:25:18 EDT 2018


https://github.com/python/cpython/commit/fdd8e8b4ffb68a4e8749bdc3b130fea7bbbf821e
commit: fdd8e8b4ffb68a4e8749bdc3b130fea7bbbf821e
branch: 3.7
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: GitHub <noreply at github.com>
date: 2018-03-18T13:25:15-07:00
summary:

bpo-19417: Add test_bdb.py (GH-5217)

(cherry picked from commit 3fe33043ee83d19e15551094fc1e0984617ded3c)

Co-authored-by: xdegaye <xdegaye at gmail.com>

files:
A Lib/test/test_bdb.py
A Misc/NEWS.d/next/Tests/2018-01-08-13-33-47.bpo-19417.2asoXy.rst
M Lib/test/test_sundry.py

diff --git a/Lib/test/test_bdb.py b/Lib/test/test_bdb.py
new file mode 100644
index 000000000000..abefe6c4e57a
--- /dev/null
+++ b/Lib/test/test_bdb.py
@@ -0,0 +1,1151 @@
+""" Test the bdb module.
+
+    A test defines a list of tuples that may be seen as paired tuples, each
+    pair being defined by 'expect_tuple, set_tuple' as follows:
+
+        ([event, [lineno[, co_name[, eargs]]]]), (set_type, [sargs])
+
+    * 'expect_tuple' describes the expected current state of the Bdb instance.
+      It may be the empty tuple and no check is done in that case.
+    * 'set_tuple' defines the set_*() method to be invoked when the Bdb
+      instance reaches this state.
+
+    Example of an 'expect_tuple, set_tuple' pair:
+
+        ('line', 2, 'tfunc_main'), ('step', )
+
+    Definitions of the members of the 'expect_tuple':
+        event:
+            Name of the trace event. The set methods that do not give back
+            control to the tracer [1] do not trigger a tracer event and in
+            that case the next 'event' may be 'None' by convention, its value
+            is not checked.
+            [1] Methods that trigger a trace event are set_step(), set_next(),
+            set_return(), set_until() and set_continue().
+        lineno:
+            Line number. Line numbers are relative to the start of the
+            function when tracing a function in the test_bdb module (i.e. this
+            module).
+        co_name:
+            Name of the function being currently traced.
+        eargs:
+            A tuple:
+            * On an 'exception' event the tuple holds a class object, the
+              current exception must be an instance of this class.
+            * On a 'line' event, the tuple holds a dictionary and a list. The
+              dictionary maps each breakpoint number that has been hit on this
+              line to its hits count. The list holds the list of breakpoint
+              number temporaries that are being deleted.
+
+    Definitions of the members of the 'set_tuple':
+        set_type:
+            The type of the set method to be invoked. This may
+            be the type of one of the Bdb set methods: 'step', 'next',
+            'until', 'return', 'continue', 'break', 'quit', or the type of one
+            of the set methods added by test_bdb.Bdb: 'ignore', 'enable',
+            'disable', 'clear', 'up', 'down'.
+        sargs:
+            The arguments of the set method if any, packed in a tuple.
+"""
+
+import bdb as _bdb
+import sys
+import os
+import unittest
+import textwrap
+import importlib
+import linecache
+from contextlib import contextmanager
+from itertools import islice, repeat
+import test.support
+
+class BdbException(Exception): pass
+class BdbError(BdbException): """Error raised by the Bdb instance."""
+class BdbSyntaxError(BdbException): """Syntax error in the test case."""
+class BdbNotExpectedError(BdbException): """Unexpected result."""
+
+# When 'dry_run' is set to true, expect tuples are ignored and the actual
+# state of the tracer is printed after running each set_*() method of the test
+# case. The full list of breakpoints and their attributes is also printed
+# after each 'line' event where a breakpoint has been hit.
+dry_run = 0
+
+def reset_Breakpoint():
+    _bdb.Breakpoint.next = 1
+    _bdb.Breakpoint.bplist = {}
+    _bdb.Breakpoint.bpbynumber = [None]
+
+def info_breakpoints():
+    bp_list = [bp for  bp in _bdb.Breakpoint.bpbynumber if bp]
+    if not bp_list:
+        return ''
+
+    header_added = False
+    for bp in bp_list:
+        if not header_added:
+            info = 'BpNum Temp Enb Hits Ignore Where\n'
+            header_added = True
+
+        disp = 'yes ' if bp.temporary else 'no  '
+        enab = 'yes' if bp.enabled else 'no '
+        info += ('%-5d %s %s %-4d %-6d at %s:%d' %
+                    (bp.number, disp, enab, bp.hits, bp.ignore,
+                     os.path.basename(bp.file), bp.line))
+        if bp.cond:
+            info += '\n\tstop only if %s' % (bp.cond,)
+        info += '\n'
+    return info
+
+class Bdb(_bdb.Bdb):
+    """Extend Bdb to enhance test coverage."""
+
+    def trace_dispatch(self, frame, event, arg):
+        self.currentbp = None
+        return super().trace_dispatch(frame, event, arg)
+
+    def set_break(self, filename, lineno, temporary=False, cond=None,
+                  funcname=None):
+        if isinstance(funcname, str):
+            if filename == __file__:
+                globals_ = globals()
+            else:
+                module = importlib.import_module(filename[:-3])
+                globals_ = module.__dict__
+            func = eval(funcname, globals_)
+            code = func.__code__
+            filename = code.co_filename
+            lineno = code.co_firstlineno
+            funcname = code.co_name
+
+        res = super().set_break(filename, lineno, temporary=temporary,
+                                 cond=cond, funcname=funcname)
+        if isinstance(res, str):
+            raise BdbError(res)
+        return res
+
+    def get_stack(self, f, t):
+        self.stack, self.index = super().get_stack(f, t)
+        self.frame = self.stack[self.index][0]
+        return self.stack, self.index
+
+    def set_ignore(self, bpnum):
+        """Increment the ignore count of Breakpoint number 'bpnum'."""
+        bp = self.get_bpbynumber(bpnum)
+        bp.ignore += 1
+
+    def set_enable(self, bpnum):
+        bp = self.get_bpbynumber(bpnum)
+        bp.enabled = True
+
+    def set_disable(self, bpnum):
+        bp = self.get_bpbynumber(bpnum)
+        bp.enabled = False
+
+    def set_clear(self, fname, lineno):
+        err = self.clear_break(fname, lineno)
+        if err:
+            raise BdbError(err)
+
+    def set_up(self):
+        """Move up in the frame stack."""
+        if not self.index:
+            raise BdbError('Oldest frame')
+        self.index -= 1
+        self.frame = self.stack[self.index][0]
+
+    def set_down(self):
+        """Move down in the frame stack."""
+        if self.index + 1 == len(self.stack):
+            raise BdbError('Newest frame')
+        self.index += 1
+        self.frame = self.stack[self.index][0]
+
+class Tracer(Bdb):
+    """A tracer for testing the bdb module."""
+
+    def __init__(self, expect_set, skip=None, dry_run=False, test_case=None):
+        super().__init__(skip=skip)
+        self.expect_set = expect_set
+        self.dry_run = dry_run
+        self.header = ('Dry-run results for %s:' % test_case if
+                       test_case is not None else None)
+        self.init_test()
+
+    def init_test(self):
+        self.cur_except = None
+        self.expect_set_no = 0
+        self.breakpoint_hits = None
+        self.expected_list = list(islice(self.expect_set, 0, None, 2))
+        self.set_list = list(islice(self.expect_set, 1, None, 2))
+
+    def trace_dispatch(self, frame, event, arg):
+        # On an 'exception' event, call_exc_trace() in Python/ceval.c discards
+        # a BdbException raised by the Tracer instance, so we raise it on the
+        # next trace_dispatch() call that occurs unless the set_quit() or
+        # set_continue() method has been invoked on the 'exception' event.
+        if self.cur_except is not None:
+            raise self.cur_except
+
+        if event == 'exception':
+            try:
+                res = super().trace_dispatch(frame, event, arg)
+                return res
+            except BdbException as e:
+                self.cur_except = e
+                return self.trace_dispatch
+        else:
+            return super().trace_dispatch(frame, event, arg)
+
+    def user_call(self, frame, argument_list):
+        # Adopt the same behavior as pdb and, as a side effect, skip also the
+        # first 'call' event when the Tracer is started with Tracer.runcall()
+        # which may be possibly considered as a bug.
+        if not self.stop_here(frame):
+            return
+        self.process_event('call', frame, argument_list)
+        self.next_set_method()
+
+    def user_line(self, frame):
+        self.process_event('line', frame)
+
+        if self.dry_run and self.breakpoint_hits:
+            info = info_breakpoints().strip('\n')
+            # Indent each line.
+            for line in info.split('\n'):
+                print('  ' + line)
+        self.delete_temporaries()
+        self.breakpoint_hits = None
+
+        self.next_set_method()
+
+    def user_return(self, frame, return_value):
+        self.process_event('return', frame, return_value)
+        self.next_set_method()
+
+    def user_exception(self, frame, exc_info):
+        self.exc_info = exc_info
+        self.process_event('exception', frame)
+        self.next_set_method()
+
+    def do_clear(self, arg):
+        # The temporary breakpoints are deleted in user_line().
+        bp_list = [self.currentbp]
+        self.breakpoint_hits = (bp_list, bp_list)
+
+    def delete_temporaries(self):
+        if self.breakpoint_hits:
+            for n in self.breakpoint_hits[1]:
+                self.clear_bpbynumber(n)
+
+    def pop_next(self):
+        self.expect_set_no += 1
+        try:
+            self.expect = self.expected_list.pop(0)
+        except IndexError:
+            raise BdbNotExpectedError(
+                'expect_set list exhausted, cannot pop item %d' %
+                self.expect_set_no)
+        self.set_tuple = self.set_list.pop(0)
+
+    def process_event(self, event, frame, *args):
+        # Call get_stack() to enable walking the stack with set_up() and
+        # set_down().
+        tb = None
+        if event == 'exception':
+            tb = self.exc_info[2]
+        self.get_stack(frame, tb)
+
+        # A breakpoint has been hit and it is not a temporary.
+        if self.currentbp is not None and not self.breakpoint_hits:
+            bp_list = [self.currentbp]
+            self.breakpoint_hits = (bp_list, [])
+
+        # Pop next event.
+        self.event= event
+        self.pop_next()
+        if self.dry_run:
+            self.print_state(self.header)
+            return
+
+        # Validate the expected results.
+        if self.expect:
+            self.check_equal(self.expect[0], event, 'Wrong event type')
+            self.check_lno_name()
+
+        if event in ('call', 'return'):
+            self.check_expect_max_size(3)
+        elif len(self.expect) > 3:
+            if event == 'line':
+                bps, temporaries = self.expect[3]
+                bpnums = sorted(bps.keys())
+                if not self.breakpoint_hits:
+                    self.raise_not_expected(
+                        'No breakpoints hit at expect_set item %d' %
+                        self.expect_set_no)
+                self.check_equal(bpnums, self.breakpoint_hits[0],
+                    'Breakpoint numbers do not match')
+                self.check_equal([bps[n] for n in bpnums],
+                    [self.get_bpbynumber(n).hits for
+                        n in self.breakpoint_hits[0]],
+                    'Wrong breakpoint hit count')
+                self.check_equal(sorted(temporaries), self.breakpoint_hits[1],
+                    'Wrong temporary breakpoints')
+
+            elif event == 'exception':
+                if not isinstance(self.exc_info[1], self.expect[3]):
+                    self.raise_not_expected(
+                        "Wrong exception at expect_set item %d, got '%s'" %
+                        (self.expect_set_no, self.exc_info))
+
+    def check_equal(self, expected, result, msg):
+        if expected == result:
+            return
+        self.raise_not_expected("%s at expect_set item %d, got '%s'" %
+                                (msg, self.expect_set_no, result))
+
+    def check_lno_name(self):
+        """Check the line number and function co_name."""
+        s = len(self.expect)
+        if s > 1:
+            lineno = self.lno_abs2rel()
+            self.check_equal(self.expect[1], lineno, 'Wrong line number')
+        if s > 2:
+            self.check_equal(self.expect[2], self.frame.f_code.co_name,
+                                                'Wrong function name')
+
+    def check_expect_max_size(self, size):
+        if len(self.expect) > size:
+            raise BdbSyntaxError('Invalid size of the %s expect tuple: %s' %
+                                 (self.event, self.expect))
+
+    def lno_abs2rel(self):
+        fname = self.canonic(self.frame.f_code.co_filename)
+        lineno = self.frame.f_lineno
+        return ((lineno - self.frame.f_code.co_firstlineno + 1)
+            if fname == self.canonic(__file__) else lineno)
+
+    def lno_rel2abs(self, fname, lineno):
+        return (self.frame.f_code.co_firstlineno + lineno - 1
+            if (lineno and self.canonic(fname) == self.canonic(__file__))
+            else lineno)
+
+    def get_state(self):
+        lineno = self.lno_abs2rel()
+        co_name = self.frame.f_code.co_name
+        state = "('%s', %d, '%s'" % (self.event, lineno, co_name)
+        if self.breakpoint_hits:
+            bps = '{'
+            for n in self.breakpoint_hits[0]:
+                if bps != '{':
+                    bps += ', '
+                bps += '%s: %s' % (n, self.get_bpbynumber(n).hits)
+            bps += '}'
+            bps = '(' + bps + ', ' + str(self.breakpoint_hits[1]) + ')'
+            state += ', ' + bps
+        elif self.event == 'exception':
+            state += ', ' + self.exc_info[0].__name__
+        state += '), '
+        return state.ljust(32) + str(self.set_tuple) + ','
+
+    def print_state(self, header=None):
+        if header is not None and self.expect_set_no == 1:
+            print()
+            print(header)
+        print('%d: %s' % (self.expect_set_no, self.get_state()))
+
+    def raise_not_expected(self, msg):
+        msg += '\n'
+        msg += '  Expected: %s\n' % str(self.expect)
+        msg += '  Got:      ' + self.get_state()
+        raise BdbNotExpectedError(msg)
+
+    def next_set_method(self):
+        set_type = self.set_tuple[0]
+        args = self.set_tuple[1] if len(self.set_tuple) == 2 else None
+        set_method = getattr(self, 'set_' + set_type)
+
+        # The following set methods give back control to the tracer.
+        if set_type in ('step', 'continue', 'quit'):
+            set_method()
+            return
+        elif set_type in ('next', 'return'):
+            set_method(self.frame)
+            return
+        elif set_type == 'until':
+            lineno = None
+            if args:
+                lineno = self.lno_rel2abs(self.frame.f_code.co_filename,
+                                          args[0])
+            set_method(self.frame, lineno)
+            return
+
+        # The following set methods do not give back control to the tracer and
+        # next_set_method() is called recursively.
+        if (args and set_type in ('break', 'clear', 'ignore', 'enable',
+                                    'disable')) or set_type in ('up', 'down'):
+            if set_type in ('break', 'clear'):
+                fname, lineno, *remain = args
+                lineno = self.lno_rel2abs(fname, lineno)
+                args = [fname, lineno]
+                args.extend(remain)
+                set_method(*args)
+            elif set_type in ('ignore', 'enable', 'disable'):
+                set_method(*args)
+            elif set_type in ('up', 'down'):
+                set_method()
+
+            # Process the next expect_set item.
+            # It is not expected that a test may reach the recursion limit.
+            self.event= None
+            self.pop_next()
+            if self.dry_run:
+                self.print_state()
+            else:
+                if self.expect:
+                    self.check_lno_name()
+                self.check_expect_max_size(3)
+            self.next_set_method()
+        else:
+            raise BdbSyntaxError('"%s" is an invalid set_tuple' %
+                                 self.set_tuple)
+
+class TracerRun():
+    """Provide a context for running a Tracer instance with a test case."""
+
+    def __init__(self, test_case, skip=None):
+        self.test_case = test_case
+        self.dry_run = test_case.dry_run
+        self.tracer = Tracer(test_case.expect_set, skip=skip,
+                             dry_run=self.dry_run, test_case=test_case.id())
+
+    def __enter__(self):
+        # test_pdb does not reset Breakpoint class attributes on exit :-(
+        reset_Breakpoint()
+        return self.tracer
+
+    def __exit__(self, type_=None, value=None, traceback=None):
+        reset_Breakpoint()
+        sys.settrace(None)
+
+        not_empty = ''
+        if self.tracer.set_list:
+            not_empty += 'All paired tuples have not been processed, '
+            not_empty += ('the last one was number %d' %
+                          self.tracer.expect_set_no)
+
+        # Make a BdbNotExpectedError a unittest failure.
+        if type_ is not None and issubclass(BdbNotExpectedError, type_):
+            if isinstance(value, BaseException) and value.args:
+                err_msg = value.args[0]
+                if not_empty:
+                    err_msg += '\n' + not_empty
+                if self.dry_run:
+                    print(err_msg)
+                    return True
+                else:
+                    self.test_case.fail(err_msg)
+            else:
+                assert False, 'BdbNotExpectedError with empty args'
+
+        if not_empty:
+            if self.dry_run:
+                print(not_empty)
+            else:
+                self.test_case.fail(not_empty)
+
+def run_test(modules, set_list, skip=None):
+    """Run a test and print the dry-run results.
+
+    'modules':  A dictionary mapping module names to their source code as a
+                string. The dictionary MUST include one module named
+                'test_module' with a main() function.
+    'set_list': A list of set_type tuples to be run on the module.
+
+    For example, running the following script outputs the following results:
+
+    *****************************   SCRIPT   ********************************
+
+    from test.test_bdb import run_test, break_in_func
+
+    code = '''
+        def func():
+            lno = 3
+
+        def main():
+            func()
+            lno = 7
+    '''
+
+    set_list = [
+                break_in_func('func', 'test_module.py'),
+                ('continue', ),
+                ('step', ),
+                ('step', ),
+                ('step', ),
+                ('quit', ),
+            ]
+
+    modules = { 'test_module': code }
+    run_test(modules, set_list)
+
+    ****************************   results   ********************************
+
+    1: ('line', 2, 'tfunc_import'),    ('next',),
+    2: ('line', 3, 'tfunc_import'),    ('step',),
+    3: ('call', 5, 'main'),            ('break', ('test_module.py', None, False, None, 'func')),
+    4: ('None', 5, 'main'),            ('continue',),
+    5: ('line', 3, 'func', ({1: 1}, [])), ('step',),
+      BpNum Temp Enb Hits Ignore Where
+      1     no   yes 1    0      at test_module.py:2
+    6: ('return', 3, 'func'),          ('step',),
+    7: ('line', 7, 'main'),            ('step',),
+    8: ('return', 7, 'main'),          ('quit',),
+
+    *************************************************************************
+
+    """
+    def gen(a, b):
+        try:
+            while 1:
+                x = next(a)
+                y = next(b)
+                yield x
+                yield y
+        except StopIteration:
+            return
+
+    # Step over the import statement in tfunc_import using 'next' and step
+    # into main() in test_module.
+    sl = [('next', ), ('step', )]
+    sl.extend(set_list)
+
+    test = BaseTestCase()
+    test.dry_run = True
+    test.id = lambda : None
+    test.expect_set = list(gen(repeat(()), iter(sl)))
+    with create_modules(modules):
+        sys.path.append(os.getcwd())
+        with TracerRun(test, skip=skip) as tracer:
+            tracer.runcall(tfunc_import)
+
+ at contextmanager
+def create_modules(modules):
+    with test.support.temp_cwd():
+        try:
+            for m in modules:
+                fname = m + '.py'
+                with open(fname, 'w') as f:
+                    f.write(textwrap.dedent(modules[m]))
+                linecache.checkcache(fname)
+            importlib.invalidate_caches()
+            yield
+        finally:
+            for m in modules:
+                test.support.forget(m)
+
+def break_in_func(funcname, fname=__file__, temporary=False, cond=None):
+    return 'break', (fname, None, temporary, cond, funcname)
+
+TEST_MODULE = 'test_module'
+TEST_MODULE_FNAME = TEST_MODULE + '.py'
+def tfunc_import():
+    import test_module
+    test_module.main()
+
+def tfunc_main():
+    lno = 2
+    tfunc_first()
+    tfunc_second()
+    lno = 5
+    lno = 6
+    lno = 7
+
+def tfunc_first():
+    lno = 2
+    lno = 3
+    lno = 4
+
+def tfunc_second():
+    lno = 2
+
+class BaseTestCase(unittest.TestCase):
+    """Base class for all tests."""
+
+    dry_run = dry_run
+
+    def fail(self, msg=None):
+        # Override fail() to use 'raise from None' to avoid repetition of the
+        # error message and traceback.
+        raise self.failureException(msg) from None
+
+class StateTestCase(BaseTestCase):
+    """Test the step, next, return, until and quit 'set_' methods."""
+
+    def test_step(self):
+        self.expect_set = [
+            ('line', 2, 'tfunc_main'),  ('step', ),
+            ('line', 3, 'tfunc_main'),  ('step', ),
+            ('call', 1, 'tfunc_first'), ('step', ),
+            ('line', 2, 'tfunc_first'), ('quit', ),
+        ]
+        with TracerRun(self) as tracer:
+            tracer.runcall(tfunc_main)
+
+    def test_step_next_on_last_statement(self):
+        for set_type in ('step', 'next'):
+            with self.subTest(set_type=set_type):
+                self.expect_set = [
+                    ('line', 2, 'tfunc_main'),               ('step', ),
+                    ('line', 3, 'tfunc_main'),               ('step', ),
+                    ('call', 1, 'tfunc_first'),              ('break', (__file__, 3)),
+                    ('None', 1, 'tfunc_first'),              ('continue', ),
+                    ('line', 3, 'tfunc_first', ({1:1}, [])), (set_type, ),
+                    ('line', 4, 'tfunc_first'),              ('quit', ),
+                ]
+                with TracerRun(self) as tracer:
+                    tracer.runcall(tfunc_main)
+
+    def test_next(self):
+        self.expect_set = [
+            ('line', 2, 'tfunc_main'),   ('step', ),
+            ('line', 3, 'tfunc_main'),   ('next', ),
+            ('line', 4, 'tfunc_main'),   ('step', ),
+            ('call', 1, 'tfunc_second'), ('step', ),
+            ('line', 2, 'tfunc_second'), ('quit', ),
+        ]
+        with TracerRun(self) as tracer:
+            tracer.runcall(tfunc_main)
+
+    def test_next_over_import(self):
+        code = """
+            def main():
+                lno = 3
+        """
+        modules = { TEST_MODULE: code }
+        with create_modules(modules):
+            self.expect_set = [
+                ('line', 2, 'tfunc_import'), ('next', ),
+                ('line', 3, 'tfunc_import'), ('quit', ),
+            ]
+            with TracerRun(self) as tracer:
+                tracer.runcall(tfunc_import)
+
+    def test_next_on_plain_statement(self):
+        # Check that set_next() is equivalent to set_step() on a plain
+        # statement.
+        self.expect_set = [
+            ('line', 2, 'tfunc_main'),  ('step', ),
+            ('line', 3, 'tfunc_main'),  ('step', ),
+            ('call', 1, 'tfunc_first'), ('next', ),
+            ('line', 2, 'tfunc_first'), ('quit', ),
+        ]
+        with TracerRun(self) as tracer:
+            tracer.runcall(tfunc_main)
+
+    def test_next_in_caller_frame(self):
+        # Check that set_next() in the caller frame causes the tracer
+        # to stop next in the caller frame.
+        self.expect_set = [
+            ('line', 2, 'tfunc_main'),  ('step', ),
+            ('line', 3, 'tfunc_main'),  ('step', ),
+            ('call', 1, 'tfunc_first'), ('up', ),
+            ('None', 3, 'tfunc_main'),  ('next', ),
+            ('line', 4, 'tfunc_main'),  ('quit', ),
+        ]
+        with TracerRun(self) as tracer:
+            tracer.runcall(tfunc_main)
+
+    def test_return(self):
+        self.expect_set = [
+            ('line', 2, 'tfunc_main'),    ('step', ),
+            ('line', 3, 'tfunc_main'),    ('step', ),
+            ('call', 1, 'tfunc_first'),   ('step', ),
+            ('line', 2, 'tfunc_first'),   ('return', ),
+            ('return', 4, 'tfunc_first'), ('step', ),
+            ('line', 4, 'tfunc_main'),    ('quit', ),
+        ]
+        with TracerRun(self) as tracer:
+            tracer.runcall(tfunc_main)
+
+    def test_return_in_caller_frame(self):
+        self.expect_set = [
+            ('line', 2, 'tfunc_main'),   ('step', ),
+            ('line', 3, 'tfunc_main'),   ('step', ),
+            ('call', 1, 'tfunc_first'),  ('up', ),
+            ('None', 3, 'tfunc_main'),   ('return', ),
+            ('return', 7, 'tfunc_main'), ('quit', ),
+        ]
+        with TracerRun(self) as tracer:
+            tracer.runcall(tfunc_main)
+
+    def test_until(self):
+        self.expect_set = [
+            ('line', 2, 'tfunc_main'),  ('step', ),
+            ('line', 3, 'tfunc_main'),  ('step', ),
+            ('call', 1, 'tfunc_first'), ('step', ),
+            ('line', 2, 'tfunc_first'), ('until', (4, )),
+            ('line', 4, 'tfunc_first'), ('quit', ),
+        ]
+        with TracerRun(self) as tracer:
+            tracer.runcall(tfunc_main)
+
+    def test_until_with_too_large_count(self):
+        self.expect_set = [
+            ('line', 2, 'tfunc_main'),               break_in_func('tfunc_first'),
+            ('None', 2, 'tfunc_main'),               ('continue', ),
+            ('line', 2, 'tfunc_first', ({1:1}, [])), ('until', (9999, )),
+            ('return', 4, 'tfunc_first'),            ('quit', ),
+        ]
+        with TracerRun(self) as tracer:
+            tracer.runcall(tfunc_main)
+
+    def test_until_in_caller_frame(self):
+        self.expect_set = [
+            ('line', 2, 'tfunc_main'),  ('step', ),
+            ('line', 3, 'tfunc_main'),  ('step', ),
+            ('call', 1, 'tfunc_first'), ('up', ),
+            ('None', 3, 'tfunc_main'),  ('until', (6, )),
+            ('line', 6, 'tfunc_main'),  ('quit', ),
+        ]
+        with TracerRun(self) as tracer:
+            tracer.runcall(tfunc_main)
+
+    def test_skip(self):
+        # Check that tracing is skipped over the import statement in
+        # 'tfunc_import()'.
+        code = """
+            def main():
+                lno = 3
+        """
+        modules = { TEST_MODULE: code }
+        with create_modules(modules):
+            self.expect_set = [
+                ('line', 2, 'tfunc_import'), ('step', ),
+                ('line', 3, 'tfunc_import'), ('quit', ),
+            ]
+            skip = ('importlib*', TEST_MODULE)
+            with TracerRun(self, skip=skip) as tracer:
+                tracer.runcall(tfunc_import)
+
+    def test_down(self):
+        # Check that set_down() raises BdbError at the newest frame.
+        self.expect_set = [
+            ('line', 2, 'tfunc_main'), ('down', ),
+        ]
+        with TracerRun(self) as tracer:
+            self.assertRaises(BdbError, tracer.runcall, tfunc_main)
+
+    def test_up(self):
+        self.expect_set = [
+            ('line', 2, 'tfunc_main'),  ('step', ),
+            ('line', 3, 'tfunc_main'),  ('step', ),
+            ('call', 1, 'tfunc_first'), ('up', ),
+            ('None', 3, 'tfunc_main'),  ('quit', ),
+        ]
+        with TracerRun(self) as tracer:
+            tracer.runcall(tfunc_main)
+
+class BreakpointTestCase(BaseTestCase):
+    """Test the breakpoint set method."""
+
+    def test_bp_on_non_existent_module(self):
+        self.expect_set = [
+            ('line', 2, 'tfunc_import'), ('break', ('/non/existent/module.py', 1))
+        ]
+        with TracerRun(self) as tracer:
+            self.assertRaises(BdbError, tracer.runcall, tfunc_import)
+
+    def test_bp_after_last_statement(self):
+        code = """
+            def main():
+                lno = 3
+        """
+        modules = { TEST_MODULE: code }
+        with create_modules(modules):
+            self.expect_set = [
+                ('line', 2, 'tfunc_import'), ('break', (TEST_MODULE_FNAME, 4))
+            ]
+            with TracerRun(self) as tracer:
+                self.assertRaises(BdbError, tracer.runcall, tfunc_import)
+
+    def test_temporary_bp(self):
+        code = """
+            def func():
+                lno = 3
+
+            def main():
+                for i in range(2):
+                    func()
+        """
+        modules = { TEST_MODULE: code }
+        with create_modules(modules):
+            self.expect_set = [
+                ('line', 2, 'tfunc_import'),
+                    break_in_func('func', TEST_MODULE_FNAME, True),
+                ('None', 2, 'tfunc_import'),
+                    break_in_func('func', TEST_MODULE_FNAME, True),
+                ('None', 2, 'tfunc_import'),       ('continue', ),
+                ('line', 3, 'func', ({1:1}, [1])), ('continue', ),
+                ('line', 3, 'func', ({2:1}, [2])), ('quit', ),
+            ]
+            with TracerRun(self) as tracer:
+                tracer.runcall(tfunc_import)
+
+    def test_disabled_temporary_bp(self):
+        code = """
+            def func():
+                lno = 3
+
+            def main():
+                for i in range(3):
+                    func()
+        """
+        modules = { TEST_MODULE: code }
+        with create_modules(modules):
+            self.expect_set = [
+                ('line', 2, 'tfunc_import'),
+                    break_in_func('func', TEST_MODULE_FNAME),
+                ('None', 2, 'tfunc_import'),
+                    break_in_func('func', TEST_MODULE_FNAME, True),
+                ('None', 2, 'tfunc_import'),       ('disable', (2, )),
+                ('None', 2, 'tfunc_import'),       ('continue', ),
+                ('line', 3, 'func', ({1:1}, [])),  ('enable', (2, )),
+                ('None', 3, 'func'),               ('disable', (1, )),
+                ('None', 3, 'func'),               ('continue', ),
+                ('line', 3, 'func', ({2:1}, [2])), ('enable', (1, )),
+                ('None', 3, 'func'),               ('continue', ),
+                ('line', 3, 'func', ({1:2}, [])),  ('quit', ),
+            ]
+            with TracerRun(self) as tracer:
+                tracer.runcall(tfunc_import)
+
+    def test_bp_condition(self):
+        code = """
+            def func(a):
+                lno = 3
+
+            def main():
+                for i in range(3):
+                    func(i)
+        """
+        modules = { TEST_MODULE: code }
+        with create_modules(modules):
+            self.expect_set = [
+                ('line', 2, 'tfunc_import'),
+                    break_in_func('func', TEST_MODULE_FNAME, False, 'a == 2'),
+                ('None', 2, 'tfunc_import'),       ('continue', ),
+                ('line', 3, 'func', ({1:3}, [])),  ('quit', ),
+            ]
+            with TracerRun(self) as tracer:
+                tracer.runcall(tfunc_import)
+
+    def test_bp_exception_on_condition_evaluation(self):
+        code = """
+            def func(a):
+                lno = 3
+
+            def main():
+                func(0)
+        """
+        modules = { TEST_MODULE: code }
+        with create_modules(modules):
+            self.expect_set = [
+                ('line', 2, 'tfunc_import'),
+                    break_in_func('func', TEST_MODULE_FNAME, False, '1 / 0'),
+                ('None', 2, 'tfunc_import'),       ('continue', ),
+                ('line', 3, 'func', ({1:1}, [])),  ('quit', ),
+            ]
+            with TracerRun(self) as tracer:
+                tracer.runcall(tfunc_import)
+
+    def test_bp_ignore_count(self):
+        code = """
+            def func():
+                lno = 3
+
+            def main():
+                for i in range(2):
+                    func()
+        """
+        modules = { TEST_MODULE: code }
+        with create_modules(modules):
+            self.expect_set = [
+                ('line', 2, 'tfunc_import'),
+                    break_in_func('func', TEST_MODULE_FNAME),
+                ('None', 2, 'tfunc_import'),      ('ignore', (1, )),
+                ('None', 2, 'tfunc_import'),      ('continue', ),
+                ('line', 3, 'func', ({1:2}, [])), ('quit', ),
+            ]
+            with TracerRun(self) as tracer:
+                tracer.runcall(tfunc_import)
+
+    def test_ignore_count_on_disabled_bp(self):
+        code = """
+            def func():
+                lno = 3
+
+            def main():
+                for i in range(3):
+                    func()
+        """
+        modules = { TEST_MODULE: code }
+        with create_modules(modules):
+            self.expect_set = [
+                ('line', 2, 'tfunc_import'),
+                    break_in_func('func', TEST_MODULE_FNAME),
+                ('None', 2, 'tfunc_import'),
+                    break_in_func('func', TEST_MODULE_FNAME),
+                ('None', 2, 'tfunc_import'),      ('ignore', (1, )),
+                ('None', 2, 'tfunc_import'),      ('disable', (1, )),
+                ('None', 2, 'tfunc_import'),      ('continue', ),
+                ('line', 3, 'func', ({2:1}, [])), ('enable', (1, )),
+                ('None', 3, 'func'),              ('continue', ),
+                ('line', 3, 'func', ({2:2}, [])), ('continue', ),
+                ('line', 3, 'func', ({1:2}, [])), ('quit', ),
+            ]
+            with TracerRun(self) as tracer:
+                tracer.runcall(tfunc_import)
+
+    def test_clear_two_bp_on_same_line(self):
+        code = """
+            def func():
+                lno = 3
+                lno = 4
+
+            def main():
+                for i in range(3):
+                    func()
+        """
+        modules = { TEST_MODULE: code }
+        with create_modules(modules):
+            self.expect_set = [
+                ('line', 2, 'tfunc_import'),      ('break', (TEST_MODULE_FNAME, 3)),
+                ('None', 2, 'tfunc_import'),      ('break', (TEST_MODULE_FNAME, 3)),
+                ('None', 2, 'tfunc_import'),      ('break', (TEST_MODULE_FNAME, 4)),
+                ('None', 2, 'tfunc_import'),      ('continue', ),
+                ('line', 3, 'func', ({1:1}, [])), ('continue', ),
+                ('line', 4, 'func', ({3:1}, [])), ('clear', (TEST_MODULE_FNAME, 3)),
+                ('None', 4, 'func'),              ('continue', ),
+                ('line', 4, 'func', ({3:2}, [])), ('quit', ),
+            ]
+            with TracerRun(self) as tracer:
+                tracer.runcall(tfunc_import)
+
+    def test_clear_at_no_bp(self):
+        self.expect_set = [
+            ('line', 2, 'tfunc_import'), ('clear', (__file__, 1))
+        ]
+        with TracerRun(self) as tracer:
+            self.assertRaises(BdbError, tracer.runcall, tfunc_import)
+
+class RunTestCase(BaseTestCase):
+    """Test run, runeval and set_trace."""
+
+    def test_run_step(self):
+        # Check that the bdb 'run' method stops at the first line event.
+        code = """
+            lno = 2
+        """
+        self.expect_set = [
+            ('line', 2, '<module>'),   ('step', ),
+            ('return', 2, '<module>'), ('quit', ),
+        ]
+        with TracerRun(self) as tracer:
+            tracer.run(compile(textwrap.dedent(code), '<string>', 'exec'))
+
+    def test_runeval_step(self):
+        # Test bdb 'runeval'.
+        code = """
+            def main():
+                lno = 3
+        """
+        modules = { TEST_MODULE: code }
+        with create_modules(modules):
+            self.expect_set = [
+                ('line', 1, '<module>'),   ('step', ),
+                ('call', 2, 'main'),       ('step', ),
+                ('line', 3, 'main'),       ('step', ),
+                ('return', 3, 'main'),     ('step', ),
+                ('return', 1, '<module>'), ('quit', ),
+            ]
+            import test_module
+            with TracerRun(self) as tracer:
+                tracer.runeval('test_module.main()', globals(), locals())
+
+class IssuesTestCase(BaseTestCase):
+    """Test fixed bdb issues."""
+
+    def test_step_at_return_with_no_trace_in_caller(self):
+        # Issue #13183.
+        # Check that the tracer does step into the caller frame when the
+        # trace function is not set in that frame.
+        code_1 = """
+            from test_module_2 import func
+            def main():
+                func()
+                lno = 5
+        """
+        code_2 = """
+            def func():
+                lno = 3
+        """
+        modules = {
+            TEST_MODULE: code_1,
+            'test_module_2': code_2,
+        }
+        with create_modules(modules):
+            self.expect_set = [
+                ('line', 2, 'tfunc_import'),
+                    break_in_func('func', 'test_module_2.py'),
+                ('None', 2, 'tfunc_import'),      ('continue', ),
+                ('line', 3, 'func', ({1:1}, [])), ('step', ),
+                ('return', 3, 'func'),            ('step', ),
+                ('line', 5, 'main'),              ('quit', ),
+            ]
+            with TracerRun(self) as tracer:
+                tracer.runcall(tfunc_import)
+
+    def test_next_until_return_in_generator(self):
+        # Issue #16596.
+        # Check that set_next(), set_until() and set_return() do not treat the
+        # `yield` and `yield from` statements as if they were returns and stop
+        # instead in the current frame.
+        code = """
+            def test_gen():
+                yield 0
+                lno = 4
+                return 123
+
+            def main():
+                it = test_gen()
+                next(it)
+                next(it)
+                lno = 11
+        """
+        modules = { TEST_MODULE: code }
+        for set_type in ('next', 'until', 'return'):
+            with self.subTest(set_type=set_type):
+                with create_modules(modules):
+                    self.expect_set = [
+                        ('line', 2, 'tfunc_import'),
+                            break_in_func('test_gen', TEST_MODULE_FNAME),
+                        ('None', 2, 'tfunc_import'),          ('continue', ),
+                        ('line', 3, 'test_gen', ({1:1}, [])), (set_type, ),
+                    ]
+
+                    if set_type == 'return':
+                        self.expect_set.extend(
+                            [('exception', 10, 'main', StopIteration), ('step',),
+                             ('return', 10, 'main'),                   ('quit', ),
+                            ]
+                        )
+                    else:
+                        self.expect_set.extend(
+                            [('line', 4, 'test_gen'), ('quit', ),]
+                        )
+                    with TracerRun(self) as tracer:
+                        tracer.runcall(tfunc_import)
+
+    def test_next_command_in_generator_for_loop(self):
+        # Issue #16596.
+        code = """
+            def test_gen():
+                yield 0
+                lno = 4
+                yield 1
+                return 123
+
+            def main():
+                for i in test_gen():
+                    lno = 10
+                lno = 11
+        """
+        modules = { TEST_MODULE: code }
+        with create_modules(modules):
+            self.expect_set = [
+                ('line', 2, 'tfunc_import'),
+                    break_in_func('test_gen', TEST_MODULE_FNAME),
+                ('None', 2, 'tfunc_import'),             ('continue', ),
+                ('line', 3, 'test_gen', ({1:1}, [])),    ('next', ),
+                ('line', 4, 'test_gen'),                 ('next', ),
+                ('line', 5, 'test_gen'),                 ('next', ),
+                ('line', 6, 'test_gen'),                 ('next', ),
+                ('exception', 9, 'main', StopIteration), ('step', ),
+                ('line', 11, 'main'),                    ('quit', ),
+
+            ]
+            with TracerRun(self) as tracer:
+                tracer.runcall(tfunc_import)
+
+    def test_next_command_in_generator_with_subiterator(self):
+        # Issue #16596.
+        code = """
+            def test_subgen():
+                yield 0
+                return 123
+
+            def test_gen():
+                x = yield from test_subgen()
+                return 456
+
+            def main():
+                for i in test_gen():
+                    lno = 12
+                lno = 13
+        """
+        modules = { TEST_MODULE: code }
+        with create_modules(modules):
+            self.expect_set = [
+                ('line', 2, 'tfunc_import'),
+                    break_in_func('test_gen', TEST_MODULE_FNAME),
+                ('None', 2, 'tfunc_import'),              ('continue', ),
+                ('line', 7, 'test_gen', ({1:1}, [])),     ('next', ),
+                ('line', 8, 'test_gen'),                  ('next', ),
+                ('exception', 11, 'main', StopIteration), ('step', ),
+                ('line', 13, 'main'),                     ('quit', ),
+
+            ]
+            with TracerRun(self) as tracer:
+                tracer.runcall(tfunc_import)
+
+    def test_return_command_in_generator_with_subiterator(self):
+        # Issue #16596.
+        code = """
+            def test_subgen():
+                yield 0
+                return 123
+
+            def test_gen():
+                x = yield from test_subgen()
+                return 456
+
+            def main():
+                for i in test_gen():
+                    lno = 12
+                lno = 13
+        """
+        modules = { TEST_MODULE: code }
+        with create_modules(modules):
+            self.expect_set = [
+                ('line', 2, 'tfunc_import'),
+                    break_in_func('test_subgen', TEST_MODULE_FNAME),
+                ('None', 2, 'tfunc_import'),                  ('continue', ),
+                ('line', 3, 'test_subgen', ({1:1}, [])),      ('return', ),
+                ('exception', 7, 'test_gen', StopIteration),  ('return', ),
+                ('exception', 11, 'main', StopIteration),     ('step', ),
+                ('line', 13, 'main'),                         ('quit', ),
+
+            ]
+            with TracerRun(self) as tracer:
+                tracer.runcall(tfunc_import)
+
+def test_main():
+    test.support.run_unittest(
+        StateTestCase,
+        RunTestCase,
+        BreakpointTestCase,
+        IssuesTestCase,
+    )
+
+if __name__ == "__main__":
+    test_main()
diff --git a/Lib/test/test_sundry.py b/Lib/test/test_sundry.py
index dddf3265525a..4025c2354a6c 100644
--- a/Lib/test/test_sundry.py
+++ b/Lib/test/test_sundry.py
@@ -6,7 +6,7 @@
 
 class TestUntestedModules(unittest.TestCase):
     def test_untested_modules_can_be_imported(self):
-        untested = ('bdb', 'encodings', 'formatter', 'tabnanny')
+        untested = ('encodings', 'formatter', 'tabnanny')
         with support.check_warnings(quiet=True):
             for name in untested:
                 try:
diff --git a/Misc/NEWS.d/next/Tests/2018-01-08-13-33-47.bpo-19417.2asoXy.rst b/Misc/NEWS.d/next/Tests/2018-01-08-13-33-47.bpo-19417.2asoXy.rst
new file mode 100644
index 000000000000..739352fcdd67
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2018-01-08-13-33-47.bpo-19417.2asoXy.rst
@@ -0,0 +1 @@
+Add test_bdb.py.



More information about the Python-checkins mailing list