| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207 |
- """ 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 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())
- 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' %
- 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):
- 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_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_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
- with TracerRun(self) as tracer:
- tracer.runeval('test_module_for_bdb.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_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)
- if __name__ == "__main__":
- unittest.main()
|