1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093 |
- # -*- coding: utf-8 -*-
- from __future__ import absolute_import
- from __future__ import division
- from __future__ import print_function
- import inspect
- import re
- import sys
- import traceback
- from inspect import CO_VARARGS
- from inspect import CO_VARKEYWORDS
- from weakref import ref
- import attr
- import pluggy
- import py
- from six import text_type
- import _pytest
- from _pytest._io.saferepr import safeformat
- from _pytest._io.saferepr import saferepr
- from _pytest.compat import _PY2
- from _pytest.compat import _PY3
- from _pytest.compat import PY35
- from _pytest.compat import safe_str
- if _PY3:
- from traceback import format_exception_only
- else:
- from ._py2traceback import format_exception_only
- class Code(object):
- """ wrapper around Python code objects """
- def __init__(self, rawcode):
- if not hasattr(rawcode, "co_filename"):
- rawcode = getrawcode(rawcode)
- try:
- self.filename = rawcode.co_filename
- self.firstlineno = rawcode.co_firstlineno - 1
- self.name = rawcode.co_name
- except AttributeError:
- raise TypeError("not a code object: %r" % (rawcode,))
- self.raw = rawcode
- def __eq__(self, other):
- return self.raw == other.raw
- __hash__ = None
- def __ne__(self, other):
- return not self == other
- @property
- def path(self):
- """ return a path object pointing to source code (note that it
- might not point to an actually existing file). """
- try:
- p = py.path.local(self.raw.co_filename)
- # maybe don't try this checking
- if not p.check():
- raise OSError("py.path check failed.")
- except OSError:
- # XXX maybe try harder like the weird logic
- # in the standard lib [linecache.updatecache] does?
- p = self.raw.co_filename
- return p
- @property
- def fullsource(self):
- """ return a _pytest._code.Source object for the full source file of the code
- """
- from _pytest._code import source
- full, _ = source.findsource(self.raw)
- return full
- def source(self):
- """ return a _pytest._code.Source object for the code object's source only
- """
- # return source only for that part of code
- import _pytest._code
- return _pytest._code.Source(self.raw)
- def getargs(self, var=False):
- """ return a tuple with the argument names for the code object
- if 'var' is set True also return the names of the variable and
- keyword arguments when present
- """
- # handfull shortcut for getting args
- raw = self.raw
- argcount = raw.co_argcount
- if var:
- argcount += raw.co_flags & CO_VARARGS
- argcount += raw.co_flags & CO_VARKEYWORDS
- return raw.co_varnames[:argcount]
- class Frame(object):
- """Wrapper around a Python frame holding f_locals and f_globals
- in which expressions can be evaluated."""
- def __init__(self, frame):
- self.lineno = frame.f_lineno - 1
- self.f_globals = frame.f_globals
- self.f_locals = frame.f_locals
- self.raw = frame
- self.code = Code(frame.f_code)
- @property
- def statement(self):
- """ statement this frame is at """
- import _pytest._code
- if self.code.fullsource is None:
- return _pytest._code.Source("")
- return self.code.fullsource.getstatement(self.lineno)
- def eval(self, code, **vars):
- """ evaluate 'code' in the frame
- 'vars' are optional additional local variables
- returns the result of the evaluation
- """
- f_locals = self.f_locals.copy()
- f_locals.update(vars)
- return eval(code, self.f_globals, f_locals)
- def exec_(self, code, **vars):
- """ exec 'code' in the frame
- 'vars' are optiona; additional local variables
- """
- f_locals = self.f_locals.copy()
- f_locals.update(vars)
- exec(code, self.f_globals, f_locals)
- def repr(self, object):
- """ return a 'safe' (non-recursive, one-line) string repr for 'object'
- """
- return saferepr(object)
- def is_true(self, object):
- return object
- def getargs(self, var=False):
- """ return a list of tuples (name, value) for all arguments
- if 'var' is set True also include the variable and keyword
- arguments when present
- """
- retval = []
- for arg in self.code.getargs(var):
- try:
- retval.append((arg, self.f_locals[arg]))
- except KeyError:
- pass # this can occur when using Psyco
- return retval
- class TracebackEntry(object):
- """ a single entry in a traceback """
- _repr_style = None
- exprinfo = None
- def __init__(self, rawentry, excinfo=None):
- self._excinfo = excinfo
- self._rawentry = rawentry
- self.lineno = rawentry.tb_lineno - 1
- def set_repr_style(self, mode):
- assert mode in ("short", "long")
- self._repr_style = mode
- @property
- def frame(self):
- import _pytest._code
- return _pytest._code.Frame(self._rawentry.tb_frame)
- @property
- def relline(self):
- return self.lineno - self.frame.code.firstlineno
- def __repr__(self):
- return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1)
- @property
- def statement(self):
- """ _pytest._code.Source object for the current statement """
- source = self.frame.code.fullsource
- return source.getstatement(self.lineno)
- @property
- def path(self):
- """ path to the source code """
- return self.frame.code.path
- def getlocals(self):
- return self.frame.f_locals
- locals = property(getlocals, None, None, "locals of underlaying frame")
- def getfirstlinesource(self):
- # on Jython this firstlineno can be -1 apparently
- return max(self.frame.code.firstlineno, 0)
- def getsource(self, astcache=None):
- """ return failing source code. """
- # we use the passed in astcache to not reparse asttrees
- # within exception info printing
- from _pytest._code.source import getstatementrange_ast
- source = self.frame.code.fullsource
- if source is None:
- return None
- key = astnode = None
- if astcache is not None:
- key = self.frame.code.path
- if key is not None:
- astnode = astcache.get(key, None)
- start = self.getfirstlinesource()
- try:
- astnode, _, end = getstatementrange_ast(
- self.lineno, source, astnode=astnode
- )
- except SyntaxError:
- end = self.lineno + 1
- else:
- if key is not None:
- astcache[key] = astnode
- return source[start:end]
- source = property(getsource)
- def ishidden(self):
- """ return True if the current frame has a var __tracebackhide__
- resolving to True.
- If __tracebackhide__ is a callable, it gets called with the
- ExceptionInfo instance and can decide whether to hide the traceback.
- mostly for internal use
- """
- f = self.frame
- tbh = f.f_locals.get(
- "__tracebackhide__", f.f_globals.get("__tracebackhide__", False)
- )
- if tbh and callable(tbh):
- return tbh(None if self._excinfo is None else self._excinfo())
- return tbh
- def __str__(self):
- try:
- fn = str(self.path)
- except py.error.Error:
- fn = "???"
- name = self.frame.code.name
- try:
- line = str(self.statement).lstrip()
- except KeyboardInterrupt:
- raise
- except: # noqa
- line = "???"
- return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line)
- def name(self):
- return self.frame.code.raw.co_name
- name = property(name, None, None, "co_name of underlaying code")
- class Traceback(list):
- """ Traceback objects encapsulate and offer higher level
- access to Traceback entries.
- """
- Entry = TracebackEntry
- def __init__(self, tb, excinfo=None):
- """ initialize from given python traceback object and ExceptionInfo """
- self._excinfo = excinfo
- if hasattr(tb, "tb_next"):
- def f(cur):
- while cur is not None:
- yield self.Entry(cur, excinfo=excinfo)
- cur = cur.tb_next
- list.__init__(self, f(tb))
- else:
- list.__init__(self, tb)
- def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
- """ return a Traceback instance wrapping part of this Traceback
- by provding any combination of path, lineno and firstlineno, the
- first frame to start the to-be-returned traceback is determined
- this allows cutting the first part of a Traceback instance e.g.
- for formatting reasons (removing some uninteresting bits that deal
- with handling of the exception/traceback)
- """
- for x in self:
- code = x.frame.code
- codepath = code.path
- if (
- (path is None or codepath == path)
- and (
- excludepath is None
- or not hasattr(codepath, "relto")
- or not codepath.relto(excludepath)
- )
- and (lineno is None or x.lineno == lineno)
- and (firstlineno is None or x.frame.code.firstlineno == firstlineno)
- ):
- return Traceback(x._rawentry, self._excinfo)
- return self
- def __getitem__(self, key):
- val = super(Traceback, self).__getitem__(key)
- if isinstance(key, type(slice(0))):
- val = self.__class__(val)
- return val
- def filter(self, fn=lambda x: not x.ishidden()):
- """ return a Traceback instance with certain items removed
- fn is a function that gets a single argument, a TracebackEntry
- instance, and should return True when the item should be added
- to the Traceback, False when not
- by default this removes all the TracebackEntries which are hidden
- (see ishidden() above)
- """
- return Traceback(filter(fn, self), self._excinfo)
- def getcrashentry(self):
- """ return last non-hidden traceback entry that lead
- to the exception of a traceback.
- """
- for i in range(-1, -len(self) - 1, -1):
- entry = self[i]
- if not entry.ishidden():
- return entry
- return self[-1]
- def recursionindex(self):
- """ return the index of the frame/TracebackEntry where recursion
- originates if appropriate, None if no recursion occurred
- """
- cache = {}
- for i, entry in enumerate(self):
- # id for the code.raw is needed to work around
- # the strange metaprogramming in the decorator lib from pypi
- # which generates code objects that have hash/value equality
- # XXX needs a test
- key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
- # print "checking for recursion at", key
- values = cache.setdefault(key, [])
- if values:
- f = entry.frame
- loc = f.f_locals
- for otherloc in values:
- if f.is_true(
- f.eval(
- co_equal,
- __recursioncache_locals_1=loc,
- __recursioncache_locals_2=otherloc,
- )
- ):
- return i
- values.append(entry.frame.f_locals)
- return None
- co_equal = compile(
- "__recursioncache_locals_1 == __recursioncache_locals_2", "?", "eval"
- )
- @attr.s(repr=False)
- class ExceptionInfo(object):
- """ wraps sys.exc_info() objects and offers
- help for navigating the traceback.
- """
- _assert_start_repr = (
- "AssertionError(u'assert " if _PY2 else "AssertionError('assert "
- )
- _excinfo = attr.ib()
- _striptext = attr.ib(default="")
- _traceback = attr.ib(default=None)
- @classmethod
- def from_current(cls, exprinfo=None):
- """returns an ExceptionInfo matching the current traceback
- .. warning::
- Experimental API
- :param exprinfo: a text string helping to determine if we should
- strip ``AssertionError`` from the output, defaults
- to the exception message/``__str__()``
- """
- tup = sys.exc_info()
- assert tup[0] is not None, "no current exception"
- _striptext = ""
- if exprinfo is None and isinstance(tup[1], AssertionError):
- exprinfo = getattr(tup[1], "msg", None)
- if exprinfo is None:
- exprinfo = saferepr(tup[1])
- if exprinfo and exprinfo.startswith(cls._assert_start_repr):
- _striptext = "AssertionError: "
- return cls(tup, _striptext)
- @classmethod
- def for_later(cls):
- """return an unfilled ExceptionInfo
- """
- return cls(None)
- @property
- def type(self):
- """the exception class"""
- return self._excinfo[0]
- @property
- def value(self):
- """the exception value"""
- return self._excinfo[1]
- @property
- def tb(self):
- """the exception raw traceback"""
- return self._excinfo[2]
- @property
- def typename(self):
- """the type name of the exception"""
- return self.type.__name__
- @property
- def traceback(self):
- """the traceback"""
- if self._traceback is None:
- self._traceback = Traceback(self.tb, excinfo=ref(self))
- return self._traceback
- @traceback.setter
- def traceback(self, value):
- self._traceback = value
- def __repr__(self):
- if self._excinfo is None:
- return "<ExceptionInfo for raises contextmanager>"
- return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
- def exconly(self, tryshort=False):
- """ return the exception as a string
- when 'tryshort' resolves to True, and the exception is a
- _pytest._code._AssertionError, only the actual exception part of
- the exception representation is returned (so 'AssertionError: ' is
- removed from the beginning)
- """
- lines = format_exception_only(self.type, self.value)
- text = "".join(lines)
- text = text.rstrip()
- if tryshort:
- if text.startswith(self._striptext):
- text = text[len(self._striptext) :]
- return text
- def errisinstance(self, exc):
- """ return True if the exception is an instance of exc """
- return isinstance(self.value, exc)
- def _getreprcrash(self):
- exconly = self.exconly(tryshort=True)
- entry = self.traceback.getcrashentry()
- path, lineno = entry.frame.code.raw.co_filename, entry.lineno
- return ReprFileLocation(path, lineno + 1, exconly)
- def getrepr(
- self,
- showlocals=False,
- style="long",
- abspath=False,
- tbfilter=True,
- funcargs=False,
- truncate_locals=True,
- chain=True,
- ):
- """
- Return str()able representation of this exception info.
- :param bool showlocals:
- Show locals per traceback entry.
- Ignored if ``style=="native"``.
- :param str style: long|short|no|native traceback style
- :param bool abspath:
- If paths should be changed to absolute or left unchanged.
- :param bool tbfilter:
- Hide entries that contain a local variable ``__tracebackhide__==True``.
- Ignored if ``style=="native"``.
- :param bool funcargs:
- Show fixtures ("funcargs" for legacy purposes) per traceback entry.
- :param bool truncate_locals:
- With ``showlocals==True``, make sure locals can be safely represented as strings.
- :param bool chain: if chained exceptions in Python 3 should be shown.
- .. versionchanged:: 3.9
- Added the ``chain`` parameter.
- """
- if style == "native":
- return ReprExceptionInfo(
- ReprTracebackNative(
- traceback.format_exception(
- self.type, self.value, self.traceback[0]._rawentry
- )
- ),
- self._getreprcrash(),
- )
- fmt = FormattedExcinfo(
- showlocals=showlocals,
- style=style,
- abspath=abspath,
- tbfilter=tbfilter,
- funcargs=funcargs,
- truncate_locals=truncate_locals,
- chain=chain,
- )
- return fmt.repr_excinfo(self)
- def __str__(self):
- if self._excinfo is None:
- return repr(self)
- entry = self.traceback[-1]
- loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
- return str(loc)
- def __unicode__(self):
- entry = self.traceback[-1]
- loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
- return text_type(loc)
- def match(self, regexp):
- """
- Check whether the regular expression 'regexp' is found in the string
- representation of the exception using ``re.search``. If it matches
- then True is returned (so that it is possible to write
- ``assert excinfo.match()``). If it doesn't match an AssertionError is
- raised.
- """
- __tracebackhide__ = True
- value = (
- text_type(self.value) if isinstance(regexp, text_type) else str(self.value)
- )
- if not re.search(regexp, value):
- raise AssertionError(
- u"Pattern {!r} not found in {!r}".format(regexp, value)
- )
- return True
- @attr.s
- class FormattedExcinfo(object):
- """ presenting information about failing Functions and Generators. """
- # for traceback entries
- flow_marker = ">"
- fail_marker = "E"
- showlocals = attr.ib(default=False)
- style = attr.ib(default="long")
- abspath = attr.ib(default=True)
- tbfilter = attr.ib(default=True)
- funcargs = attr.ib(default=False)
- truncate_locals = attr.ib(default=True)
- chain = attr.ib(default=True)
- astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False)
- def _getindent(self, source):
- # figure out indent for given source
- try:
- s = str(source.getstatement(len(source) - 1))
- except KeyboardInterrupt:
- raise
- except: # noqa
- try:
- s = str(source[-1])
- except KeyboardInterrupt:
- raise
- except: # noqa
- return 0
- return 4 + (len(s) - len(s.lstrip()))
- def _getentrysource(self, entry):
- source = entry.getsource(self.astcache)
- if source is not None:
- source = source.deindent()
- return source
- def repr_args(self, entry):
- if self.funcargs:
- args = []
- for argname, argvalue in entry.frame.getargs(var=True):
- args.append((argname, saferepr(argvalue)))
- return ReprFuncArgs(args)
- def get_source(self, source, line_index=-1, excinfo=None, short=False):
- """ return formatted and marked up source lines. """
- import _pytest._code
- lines = []
- if source is None or line_index >= len(source.lines):
- source = _pytest._code.Source("???")
- line_index = 0
- if line_index < 0:
- line_index += len(source)
- space_prefix = " "
- if short:
- lines.append(space_prefix + source.lines[line_index].strip())
- else:
- for line in source.lines[:line_index]:
- lines.append(space_prefix + line)
- lines.append(self.flow_marker + " " + source.lines[line_index])
- for line in source.lines[line_index + 1 :]:
- lines.append(space_prefix + line)
- if excinfo is not None:
- indent = 4 if short else self._getindent(source)
- lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
- return lines
- def get_exconly(self, excinfo, indent=4, markall=False):
- lines = []
- indent = " " * indent
- # get the real exception information out
- exlines = excinfo.exconly(tryshort=True).split("\n")
- failindent = self.fail_marker + indent[1:]
- for line in exlines:
- lines.append(failindent + line)
- if not markall:
- failindent = indent
- return lines
- def repr_locals(self, locals):
- if self.showlocals:
- lines = []
- keys = [loc for loc in locals if loc[0] != "@"]
- keys.sort()
- for name in keys:
- value = locals[name]
- if name == "__builtins__":
- lines.append("__builtins__ = <builtins>")
- else:
- # This formatting could all be handled by the
- # _repr() function, which is only reprlib.Repr in
- # disguise, so is very configurable.
- if self.truncate_locals:
- str_repr = saferepr(value)
- else:
- str_repr = safeformat(value)
- # if len(str_repr) < 70 or not isinstance(value,
- # (list, tuple, dict)):
- lines.append("%-10s = %s" % (name, str_repr))
- # else:
- # self._line("%-10s =\\" % (name,))
- # # XXX
- # pprint.pprint(value, stream=self.excinfowriter)
- return ReprLocals(lines)
- def repr_traceback_entry(self, entry, excinfo=None):
- import _pytest._code
- source = self._getentrysource(entry)
- if source is None:
- source = _pytest._code.Source("???")
- line_index = 0
- else:
- # entry.getfirstlinesource() can be -1, should be 0 on jython
- line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
- lines = []
- style = entry._repr_style
- if style is None:
- style = self.style
- if style in ("short", "long"):
- short = style == "short"
- reprargs = self.repr_args(entry) if not short else None
- s = self.get_source(source, line_index, excinfo, short=short)
- lines.extend(s)
- if short:
- message = "in %s" % (entry.name)
- else:
- message = excinfo and excinfo.typename or ""
- path = self._makepath(entry.path)
- filelocrepr = ReprFileLocation(path, entry.lineno + 1, message)
- localsrepr = None
- if not short:
- localsrepr = self.repr_locals(entry.locals)
- return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
- if excinfo:
- lines.extend(self.get_exconly(excinfo, indent=4))
- return ReprEntry(lines, None, None, None, style)
- def _makepath(self, path):
- if not self.abspath:
- try:
- np = py.path.local().bestrelpath(path)
- except OSError:
- return path
- if len(np) < len(str(path)):
- path = np
- return path
- def repr_traceback(self, excinfo):
- traceback = excinfo.traceback
- if self.tbfilter:
- traceback = traceback.filter()
- if is_recursion_error(excinfo):
- traceback, extraline = self._truncate_recursive_traceback(traceback)
- else:
- extraline = None
- last = traceback[-1]
- entries = []
- for index, entry in enumerate(traceback):
- einfo = (last == entry) and excinfo or None
- reprentry = self.repr_traceback_entry(entry, einfo)
- entries.append(reprentry)
- return ReprTraceback(entries, extraline, style=self.style)
- def _truncate_recursive_traceback(self, traceback):
- """
- Truncate the given recursive traceback trying to find the starting point
- of the recursion.
- The detection is done by going through each traceback entry and finding the
- point in which the locals of the frame are equal to the locals of a previous frame (see ``recursionindex()``.
- Handle the situation where the recursion process might raise an exception (for example
- comparing numpy arrays using equality raises a TypeError), in which case we do our best to
- warn the user of the error and show a limited traceback.
- """
- try:
- recursionindex = traceback.recursionindex()
- except Exception as e:
- max_frames = 10
- extraline = (
- "!!! Recursion error detected, but an error occurred locating the origin of recursion.\n"
- " The following exception happened when comparing locals in the stack frame:\n"
- " {exc_type}: {exc_msg}\n"
- " Displaying first and last {max_frames} stack frames out of {total}."
- ).format(
- exc_type=type(e).__name__,
- exc_msg=safe_str(e),
- max_frames=max_frames,
- total=len(traceback),
- )
- traceback = traceback[:max_frames] + traceback[-max_frames:]
- else:
- if recursionindex is not None:
- extraline = "!!! Recursion detected (same locals & position)"
- traceback = traceback[: recursionindex + 1]
- else:
- extraline = None
- return traceback, extraline
- def repr_excinfo(self, excinfo):
- if _PY2:
- reprtraceback = self.repr_traceback(excinfo)
- reprcrash = excinfo._getreprcrash()
- return ReprExceptionInfo(reprtraceback, reprcrash)
- else:
- repr_chain = []
- e = excinfo.value
- descr = None
- seen = set()
- while e is not None and id(e) not in seen:
- seen.add(id(e))
- if excinfo:
- reprtraceback = self.repr_traceback(excinfo)
- reprcrash = excinfo._getreprcrash()
- else:
- # fallback to native repr if the exception doesn't have a traceback:
- # ExceptionInfo objects require a full traceback to work
- reprtraceback = ReprTracebackNative(
- traceback.format_exception(type(e), e, None)
- )
- reprcrash = None
- repr_chain += [(reprtraceback, reprcrash, descr)]
- if e.__cause__ is not None and self.chain:
- e = e.__cause__
- excinfo = (
- ExceptionInfo((type(e), e, e.__traceback__))
- if e.__traceback__
- else None
- )
- descr = "The above exception was the direct cause of the following exception:"
- elif (
- e.__context__ is not None
- and not e.__suppress_context__
- and self.chain
- ):
- e = e.__context__
- excinfo = (
- ExceptionInfo((type(e), e, e.__traceback__))
- if e.__traceback__
- else None
- )
- descr = "During handling of the above exception, another exception occurred:"
- else:
- e = None
- repr_chain.reverse()
- return ExceptionChainRepr(repr_chain)
- class TerminalRepr(object):
- def __str__(self):
- s = self.__unicode__()
- if _PY2:
- s = s.encode("utf-8")
- return s
- def __unicode__(self):
- # FYI this is called from pytest-xdist's serialization of exception
- # information.
- io = py.io.TextIO()
- tw = py.io.TerminalWriter(file=io)
- self.toterminal(tw)
- return io.getvalue().strip()
- def __repr__(self):
- return "<%s instance at %0x>" % (self.__class__, id(self))
- class ExceptionRepr(TerminalRepr):
- def __init__(self):
- self.sections = []
- def addsection(self, name, content, sep="-"):
- self.sections.append((name, content, sep))
- def toterminal(self, tw):
- for name, content, sep in self.sections:
- tw.sep(sep, name)
- tw.line(content)
- class ExceptionChainRepr(ExceptionRepr):
- def __init__(self, chain):
- super(ExceptionChainRepr, self).__init__()
- self.chain = chain
- # reprcrash and reprtraceback of the outermost (the newest) exception
- # in the chain
- self.reprtraceback = chain[-1][0]
- self.reprcrash = chain[-1][1]
- def toterminal(self, tw):
- for element in self.chain:
- element[0].toterminal(tw)
- if element[2] is not None:
- tw.line("")
- tw.line(element[2], yellow=True)
- super(ExceptionChainRepr, self).toterminal(tw)
- class ReprExceptionInfo(ExceptionRepr):
- def __init__(self, reprtraceback, reprcrash):
- super(ReprExceptionInfo, self).__init__()
- self.reprtraceback = reprtraceback
- self.reprcrash = reprcrash
- def toterminal(self, tw):
- self.reprtraceback.toterminal(tw)
- super(ReprExceptionInfo, self).toterminal(tw)
- class ReprTraceback(TerminalRepr):
- entrysep = "_ "
- def __init__(self, reprentries, extraline, style):
- self.reprentries = reprentries
- self.extraline = extraline
- self.style = style
- def toterminal(self, tw):
- # the entries might have different styles
- for i, entry in enumerate(self.reprentries):
- if entry.style == "long":
- tw.line("")
- entry.toterminal(tw)
- if i < len(self.reprentries) - 1:
- next_entry = self.reprentries[i + 1]
- if (
- entry.style == "long"
- or entry.style == "short"
- and next_entry.style == "long"
- ):
- tw.sep(self.entrysep)
- if self.extraline:
- tw.line(self.extraline)
- class ReprTracebackNative(ReprTraceback):
- def __init__(self, tblines):
- self.style = "native"
- self.reprentries = [ReprEntryNative(tblines)]
- self.extraline = None
- class ReprEntryNative(TerminalRepr):
- style = "native"
- def __init__(self, tblines):
- self.lines = tblines
- def toterminal(self, tw):
- tw.write("".join(self.lines))
- class ReprEntry(TerminalRepr):
- def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
- self.lines = lines
- self.reprfuncargs = reprfuncargs
- self.reprlocals = reprlocals
- self.reprfileloc = filelocrepr
- self.style = style
- def toterminal(self, tw):
- if self.style == "short":
- self.reprfileloc.toterminal(tw)
- for line in self.lines:
- red = line.startswith("E ")
- tw.line(line, bold=True, red=red)
- # tw.line("")
- return
- if self.reprfuncargs:
- self.reprfuncargs.toterminal(tw)
- for line in self.lines:
- red = line.startswith("E ")
- tw.line(line, bold=True, red=red)
- if self.reprlocals:
- tw.line("")
- self.reprlocals.toterminal(tw)
- if self.reprfileloc:
- if self.lines:
- tw.line("")
- self.reprfileloc.toterminal(tw)
- def __str__(self):
- return "%s\n%s\n%s" % ("\n".join(self.lines), self.reprlocals, self.reprfileloc)
- class ReprFileLocation(TerminalRepr):
- def __init__(self, path, lineno, message):
- self.path = str(path)
- self.lineno = lineno
- self.message = message
- def toterminal(self, tw):
- # filename and lineno output for each entry,
- # using an output format that most editors unterstand
- msg = self.message
- i = msg.find("\n")
- if i != -1:
- msg = msg[:i]
- tw.write(self.path, bold=True, red=True)
- tw.line(":%s: %s" % (self.lineno, msg))
- class ReprLocals(TerminalRepr):
- def __init__(self, lines):
- self.lines = lines
- def toterminal(self, tw):
- for line in self.lines:
- tw.line(line)
- class ReprFuncArgs(TerminalRepr):
- def __init__(self, args):
- self.args = args
- def toterminal(self, tw):
- if self.args:
- linesofar = ""
- for name, value in self.args:
- ns = "%s = %s" % (safe_str(name), safe_str(value))
- if len(ns) + len(linesofar) + 2 > tw.fullwidth:
- if linesofar:
- tw.line(linesofar)
- linesofar = ns
- else:
- if linesofar:
- linesofar += ", " + ns
- else:
- linesofar = ns
- if linesofar:
- tw.line(linesofar)
- tw.line("")
- def getrawcode(obj, trycall=True):
- """ return code object for given function. """
- try:
- return obj.__code__
- except AttributeError:
- obj = getattr(obj, "im_func", obj)
- obj = getattr(obj, "func_code", obj)
- obj = getattr(obj, "f_code", obj)
- obj = getattr(obj, "__code__", obj)
- if trycall and not hasattr(obj, "co_firstlineno"):
- if hasattr(obj, "__call__") and not inspect.isclass(obj):
- x = getrawcode(obj.__call__, trycall=False)
- if hasattr(x, "co_firstlineno"):
- return x
- return obj
- if PY35: # RecursionError introduced in 3.5
- def is_recursion_error(excinfo):
- return excinfo.errisinstance(RecursionError) # noqa
- else:
- def is_recursion_error(excinfo):
- if not excinfo.errisinstance(RuntimeError):
- return False
- try:
- return "maximum recursion depth exceeded" in str(excinfo.value)
- except UnicodeError:
- return False
- # relative paths that we use to filter traceback entries from appearing to the user;
- # see filter_traceback
- # note: if we need to add more paths than what we have now we should probably use a list
- # for better maintenance
- _PLUGGY_DIR = py.path.local(pluggy.__file__.rstrip("oc"))
- # pluggy is either a package or a single module depending on the version
- if _PLUGGY_DIR.basename == "__init__.py":
- _PLUGGY_DIR = _PLUGGY_DIR.dirpath()
- _PYTEST_DIR = py.path.local(_pytest.__file__).dirpath()
- _PY_DIR = py.path.local(py.__file__).dirpath()
- def filter_traceback(entry):
- """Return True if a TracebackEntry instance should be removed from tracebacks:
- * dynamically generated code (no code to show up for it);
- * internal traceback from pytest or its internal libraries, py and pluggy.
- """
- # entry.path might sometimes return a str object when the entry
- # points to dynamically generated code
- # see https://bitbucket.org/pytest-dev/py/issues/71
- raw_filename = entry.frame.code.raw.co_filename
- is_generated = "<" in raw_filename and ">" in raw_filename
- if is_generated:
- return False
- # entry.path might point to a non-existing file, in which case it will
- # also return a str object. see #1133
- p = py.path.local(entry.path)
- return (
- not p.relto(_PLUGGY_DIR) and not p.relto(_PYTEST_DIR) and not p.relto(_PY_DIR)
- )
|