summaryrefslogtreecommitdiff
path: root/Lib/test/test_bdb.py
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2018-03-18 13:25:15 -0700
committerGitHub <noreply@github.com>2018-03-18 13:25:15 -0700
commitfdd8e8b4ffb68a4e8749bdc3b130fea7bbbf821e (patch)
tree2200bf5e76788cf25e3cc0ccb6afc2c7818d64b3 /Lib/test/test_bdb.py
parenta90df5085b51e8bb9de1c04c408bbef42ce6cbc3 (diff)
downloadcpython-git-fdd8e8b4ffb68a4e8749bdc3b130fea7bbbf821e.tar.gz
bpo-19417: Add test_bdb.py (GH-5217)
(cherry picked from commit 3fe33043ee83d19e15551094fc1e0984617ded3c) Co-authored-by: xdegaye <xdegaye@gmail.com>
Diffstat (limited to 'Lib/test/test_bdb.py')
-rw-r--r--Lib/test/test_bdb.py1151
1 files changed, 1151 insertions, 0 deletions
diff --git a/Lib/test/test_bdb.py b/Lib/test/test_bdb.py
new file mode 100644
index 0000000000..abefe6c4e5
--- /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)
+
+@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()