| """ 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 | |
| from test.support import import_helper | |
| from test.support import os_helper | |
| from test.support import patch_list | |
| 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.clearBreakpoints() | |
| 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 user_opcode(self, frame): | |
| self.process_event('opcode', 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', 'stepinstr', '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()) | |
| self._original_tracer = None | |
| def __enter__(self): | |
| # test_pdb does not reset Breakpoint class attributes on exit :-( | |
| reset_Breakpoint() | |
| self._original_tracer = sys.gettrace() | |
| return self.tracer | |
| def __exit__(self, type_=None, value=None, traceback=None): | |
| reset_Breakpoint() | |
| sys.settrace(self._original_tracer) | |
| not_empty = '' | |
| if self.tracer.set_list: | |
| not_empty += 'All paired tuples have not been processed, ' | |
| not_empty += ('the last one was number %d\n' % | |
| self.tracer.expect_set_no) | |
| not_empty += repr(self.tracer.set_list) | |
| # 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): | |
| with TracerRun(test, skip=skip) as tracer: | |
| tracer.runcall(tfunc_import) | |
| @contextmanager | |
| def create_modules(modules): | |
| with os_helper.temp_cwd(): | |
| sys.path.append(os.getcwd()) | |
| try: | |
| for m in modules: | |
| fname = m + '.py' | |
| with open(fname, 'w', encoding="utf-8") as f: | |
| f.write(textwrap.dedent(modules[m])) | |
| linecache.checkcache(fname) | |
| importlib.invalidate_caches() | |
| yield | |
| finally: | |
| for m in modules: | |
| import_helper.forget(m) | |
| sys.path.pop() | |
| def break_in_func(funcname, fname=__file__, temporary=False, cond=None): | |
| return 'break', (fname, None, temporary, cond, funcname) | |
| TEST_MODULE = 'test_module_for_bdb' | |
| TEST_MODULE_FNAME = TEST_MODULE + '.py' | |
| def tfunc_import(): | |
| import test_module_for_bdb | |
| test_module_for_bdb.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_stepinstr(self): | |
| self.expect_set = [ | |
| ('line', 2, 'tfunc_main'), ('stepinstr', ), | |
| ('opcode', 2, 'tfunc_main'), ('next', ), | |
| ('line', 3, 'tfunc_main'), ('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) | |
| @patch_list(sys.meta_path) | |
| def test_skip(self): | |
| # Check that tracing is skipped over the import statement in | |
| # 'tfunc_import()'. | |
| # Remove all but the standard importers. | |
| sys.meta_path[:] = ( | |
| item | |
| for item in sys.meta_path | |
| if item.__module__.startswith('_frozen_importlib') | |
| ) | |
| 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*', 'zipimport', 'encodings.*', TEST_MODULE) | |
| with TracerRun(self, skip=skip) as tracer: | |
| tracer.runcall(tfunc_import) | |
| def test_skip_with_no_name_module(self): | |
| # some frames have `globals` with no `__name__` | |
| # for instance the second frame in this traceback | |
| # exec(compile('raise ValueError()', '', 'exec'), {}) | |
| bdb = Bdb(skip=['anything*']) | |
| self.assertIs(bdb.is_skipped_module(None), False) | |
| 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_all_file_breaks_same_line(self): | |
| reset_Breakpoint() | |
| self.addCleanup(reset_Breakpoint) | |
| debugger = Bdb() | |
| for _ in range(3): | |
| self.assertIsNone(debugger.set_break(__file__, 1)) | |
| self.assertIsNone(debugger.clear_all_file_breaks(__file__)) | |
| self.assertEqual(debugger.get_all_breaks(), {}) | |
| self.assertEqual(_bdb.Breakpoint.bplist, {}) | |
| self.assertTrue(all(bp is None for bp in _bdb.Breakpoint.bpbynumber)) | |
| 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) | |
| def test_load_bps_from_previous_Bdb_instance(self): | |
| reset_Breakpoint() | |
| db1 = Bdb() | |
| fname = db1.canonic(__file__) | |
| db1.set_break(__file__, 1) | |
| self.assertEqual(db1.get_all_breaks(), {fname: [1]}) | |
| db2 = Bdb() | |
| db2.set_break(__file__, 2) | |
| db2.set_break(__file__, 3) | |
| db2.set_break(__file__, 4) | |
| self.assertEqual(db1.get_all_breaks(), {fname: [1]}) | |
| self.assertEqual(db2.get_all_breaks(), {fname: [1, 2, 3, 4]}) | |
| db2.clear_break(__file__, 1) | |
| self.assertEqual(db1.get_all_breaks(), {fname: [1]}) | |
| self.assertEqual(db2.get_all_breaks(), {fname: [2, 3, 4]}) | |
| db3 = Bdb() | |
| self.assertEqual(db1.get_all_breaks(), {fname: [1]}) | |
| self.assertEqual(db2.get_all_breaks(), {fname: [2, 3, 4]}) | |
| self.assertEqual(db3.get_all_breaks(), {fname: [2, 3, 4]}) | |
| db2.clear_break(__file__, 2) | |
| self.assertEqual(db1.get_all_breaks(), {fname: [1]}) | |
| self.assertEqual(db2.get_all_breaks(), {fname: [3, 4]}) | |
| self.assertEqual(db3.get_all_breaks(), {fname: [2, 3, 4]}) | |
| db4 = Bdb() | |
| db4.set_break(__file__, 5) | |
| self.assertEqual(db1.get_all_breaks(), {fname: [1]}) | |
| self.assertEqual(db2.get_all_breaks(), {fname: [3, 4]}) | |
| self.assertEqual(db3.get_all_breaks(), {fname: [2, 3, 4]}) | |
| self.assertEqual(db4.get_all_breaks(), {fname: [3, 4, 5]}) | |
| reset_Breakpoint() | |
| db5 = Bdb() | |
| db5.set_break(__file__, 6) | |
| self.assertEqual(db1.get_all_breaks(), {fname: [1]}) | |
| self.assertEqual(db2.get_all_breaks(), {fname: [3, 4]}) | |
| self.assertEqual(db3.get_all_breaks(), {fname: [2, 3, 4]}) | |
| self.assertEqual(db4.get_all_breaks(), {fname: [3, 4, 5]}) | |
| self.assertEqual(db5.get_all_breaks(), {fname: [6]}) | |
| 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_for_bdb | |
| ns = {'test_module_for_bdb': test_module_for_bdb} | |
| with TracerRun(self) as tracer: | |
| tracer.runeval('test_module_for_bdb.main()', ns, ns) | |
| 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_for_bdb_2 import func | |
| def main(): | |
| func() | |
| lno = 5 | |
| """ | |
| code_2 = """ | |
| def func(): | |
| lno = 3 | |
| """ | |
| modules = { | |
| TEST_MODULE: code_1, | |
| 'test_module_for_bdb_2': code_2, | |
| } | |
| with create_modules(modules): | |
| self.expect_set = [ | |
| ('line', 2, 'tfunc_import'), | |
| break_in_func('func', 'test_module_for_bdb_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_next_to_botframe(self): | |
| # gh-125422 | |
| # Check that next command won't go to the bottom frame. | |
| code = """ | |
| lno = 2 | |
| """ | |
| self.expect_set = [ | |
| ('line', 2, '<module>'), ('step', ), | |
| ('return', 2, '<module>'), ('next', ), | |
| ] | |
| with TracerRun(self) as tracer: | |
| tracer.run(compile(textwrap.dedent(code), '<string>', 'exec')) | |
| class TestRegressions(unittest.TestCase): | |
| def test_format_stack_entry_no_lineno(self): | |
| # See gh-101517 | |
| self.assertIn('Warning: lineno is None', | |
| Bdb().format_stack_entry((sys._getframe(), None))) | |
| def test_bdb_quit_base_exception(self): | |
| # gh-149309 | |
| try: | |
| raise _bdb.BdbQuit() | |
| except Exception: | |
| self.fail(f'BdbQuit should not be caught by Exception') | |
| except BaseException: | |
| pass | |
| if __name__ == "__main__": | |
| unittest.main() |