Coverage for /usr/local/lib/python3.7/site-packages/_pytest/_code/code.py : 2%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import inspect
2import re
3import sys
4import traceback
5from inspect import CO_VARARGS
6from inspect import CO_VARKEYWORDS
7from io import StringIO
8from traceback import format_exception_only
9from types import CodeType
10from types import FrameType
11from types import TracebackType
12from typing import Any
13from typing import Callable
14from typing import Dict
15from typing import Generic
16from typing import Iterable
17from typing import List
18from typing import Optional
19from typing import Pattern
20from typing import Sequence
21from typing import Set
22from typing import Tuple
23from typing import TypeVar
24from typing import Union
25from weakref import ref
27import attr
28import pluggy
29import py
31import _pytest
32from _pytest._io.saferepr import safeformat
33from _pytest._io.saferepr import saferepr
34from _pytest.compat import overload
35from _pytest.compat import TYPE_CHECKING
37if TYPE_CHECKING:
38 from typing import Type
39 from typing_extensions import Literal
40 from weakref import ReferenceType # noqa: F401
42 from _pytest._code import Source
44 _TracebackStyle = Literal["long", "short", "line", "no", "native"]
47class Code:
48 """ wrapper around Python code objects """
50 def __init__(self, rawcode) -> None:
51 if not hasattr(rawcode, "co_filename"):
52 rawcode = getrawcode(rawcode)
53 if not isinstance(rawcode, CodeType):
54 raise TypeError("not a code object: {!r}".format(rawcode))
55 self.filename = rawcode.co_filename
56 self.firstlineno = rawcode.co_firstlineno - 1
57 self.name = rawcode.co_name
58 self.raw = rawcode
60 def __eq__(self, other):
61 return self.raw == other.raw
63 # Ignore type because of https://github.com/python/mypy/issues/4266.
64 __hash__ = None # type: ignore
66 def __ne__(self, other):
67 return not self == other
69 @property
70 def path(self) -> Union[py.path.local, str]:
71 """ return a path object pointing to source code (note that it
72 might not point to an actually existing file). """
73 try:
74 p = py.path.local(self.raw.co_filename)
75 # maybe don't try this checking
76 if not p.check():
77 raise OSError("py.path check failed.")
78 except OSError:
79 # XXX maybe try harder like the weird logic
80 # in the standard lib [linecache.updatecache] does?
81 p = self.raw.co_filename
83 return p
85 @property
86 def fullsource(self) -> Optional["Source"]:
87 """ return a _pytest._code.Source object for the full source file of the code
88 """
89 from _pytest._code import source
91 full, _ = source.findsource(self.raw)
92 return full
94 def source(self) -> "Source":
95 """ return a _pytest._code.Source object for the code object's source only
96 """
97 # return source only for that part of code
98 import _pytest._code
100 return _pytest._code.Source(self.raw)
102 def getargs(self, var: bool = False) -> Tuple[str, ...]:
103 """ return a tuple with the argument names for the code object
105 if 'var' is set True also return the names of the variable and
106 keyword arguments when present
107 """
108 # handfull shortcut for getting args
109 raw = self.raw
110 argcount = raw.co_argcount
111 if var:
112 argcount += raw.co_flags & CO_VARARGS
113 argcount += raw.co_flags & CO_VARKEYWORDS
114 return raw.co_varnames[:argcount]
117class Frame:
118 """Wrapper around a Python frame holding f_locals and f_globals
119 in which expressions can be evaluated."""
121 def __init__(self, frame: FrameType) -> None:
122 self.lineno = frame.f_lineno - 1
123 self.f_globals = frame.f_globals
124 self.f_locals = frame.f_locals
125 self.raw = frame
126 self.code = Code(frame.f_code)
128 @property
129 def statement(self) -> "Source":
130 """ statement this frame is at """
131 import _pytest._code
133 if self.code.fullsource is None:
134 return _pytest._code.Source("")
135 return self.code.fullsource.getstatement(self.lineno)
137 def eval(self, code, **vars):
138 """ evaluate 'code' in the frame
140 'vars' are optional additional local variables
142 returns the result of the evaluation
143 """
144 f_locals = self.f_locals.copy()
145 f_locals.update(vars)
146 return eval(code, self.f_globals, f_locals)
148 def exec_(self, code, **vars) -> None:
149 """ exec 'code' in the frame
151 'vars' are optional; additional local variables
152 """
153 f_locals = self.f_locals.copy()
154 f_locals.update(vars)
155 exec(code, self.f_globals, f_locals)
157 def repr(self, object: object) -> str:
158 """ return a 'safe' (non-recursive, one-line) string repr for 'object'
159 """
160 return saferepr(object)
162 def is_true(self, object):
163 return object
165 def getargs(self, var: bool = False):
166 """ return a list of tuples (name, value) for all arguments
168 if 'var' is set True also include the variable and keyword
169 arguments when present
170 """
171 retval = []
172 for arg in self.code.getargs(var):
173 try:
174 retval.append((arg, self.f_locals[arg]))
175 except KeyError:
176 pass # this can occur when using Psyco
177 return retval
180class TracebackEntry:
181 """ a single entry in a traceback """
183 _repr_style = None # type: Optional[Literal["short", "long"]]
184 exprinfo = None
186 def __init__(self, rawentry: TracebackType, excinfo=None) -> None:
187 self._excinfo = excinfo
188 self._rawentry = rawentry
189 self.lineno = rawentry.tb_lineno - 1
191 def set_repr_style(self, mode: "Literal['short', 'long']") -> None:
192 assert mode in ("short", "long")
193 self._repr_style = mode
195 @property
196 def frame(self) -> Frame:
197 return Frame(self._rawentry.tb_frame)
199 @property
200 def relline(self) -> int:
201 return self.lineno - self.frame.code.firstlineno
203 def __repr__(self) -> str:
204 return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1)
206 @property
207 def statement(self) -> "Source":
208 """ _pytest._code.Source object for the current statement """
209 source = self.frame.code.fullsource
210 assert source is not None
211 return source.getstatement(self.lineno)
213 @property
214 def path(self):
215 """ path to the source code """
216 return self.frame.code.path
218 @property
219 def locals(self) -> Dict[str, Any]:
220 """ locals of underlying frame """
221 return self.frame.f_locals
223 def getfirstlinesource(self) -> int:
224 return self.frame.code.firstlineno
226 def getsource(self, astcache=None) -> Optional["Source"]:
227 """ return failing source code. """
228 # we use the passed in astcache to not reparse asttrees
229 # within exception info printing
230 from _pytest._code.source import getstatementrange_ast
232 source = self.frame.code.fullsource
233 if source is None:
234 return None
235 key = astnode = None
236 if astcache is not None:
237 key = self.frame.code.path
238 if key is not None:
239 astnode = astcache.get(key, None)
240 start = self.getfirstlinesource()
241 try:
242 astnode, _, end = getstatementrange_ast(
243 self.lineno, source, astnode=astnode
244 )
245 except SyntaxError:
246 end = self.lineno + 1
247 else:
248 if key is not None:
249 astcache[key] = astnode
250 return source[start:end]
252 source = property(getsource)
254 def ishidden(self):
255 """ return True if the current frame has a var __tracebackhide__
256 resolving to True.
258 If __tracebackhide__ is a callable, it gets called with the
259 ExceptionInfo instance and can decide whether to hide the traceback.
261 mostly for internal use
262 """
263 f = self.frame
264 tbh = f.f_locals.get(
265 "__tracebackhide__", f.f_globals.get("__tracebackhide__", False)
266 )
267 if tbh and callable(tbh):
268 return tbh(None if self._excinfo is None else self._excinfo())
269 return tbh
271 def __str__(self) -> str:
272 try:
273 fn = str(self.path)
274 except py.error.Error:
275 fn = "???"
276 name = self.frame.code.name
277 try:
278 line = str(self.statement).lstrip()
279 except KeyboardInterrupt:
280 raise
281 except: # noqa
282 line = "???"
283 return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line)
285 @property
286 def name(self) -> str:
287 """ co_name of underlying code """
288 return self.frame.code.raw.co_name
291class Traceback(List[TracebackEntry]):
292 """ Traceback objects encapsulate and offer higher level
293 access to Traceback entries.
294 """
296 def __init__(
297 self,
298 tb: Union[TracebackType, Iterable[TracebackEntry]],
299 excinfo: Optional["ReferenceType[ExceptionInfo]"] = None,
300 ) -> None:
301 """ initialize from given python traceback object and ExceptionInfo """
302 self._excinfo = excinfo
303 if isinstance(tb, TracebackType):
305 def f(cur: TracebackType) -> Iterable[TracebackEntry]:
306 cur_ = cur # type: Optional[TracebackType]
307 while cur_ is not None:
308 yield TracebackEntry(cur_, excinfo=excinfo)
309 cur_ = cur_.tb_next
311 super().__init__(f(tb))
312 else:
313 super().__init__(tb)
315 def cut(
316 self,
317 path=None,
318 lineno: Optional[int] = None,
319 firstlineno: Optional[int] = None,
320 excludepath=None,
321 ) -> "Traceback":
322 """ return a Traceback instance wrapping part of this Traceback
324 by providing any combination of path, lineno and firstlineno, the
325 first frame to start the to-be-returned traceback is determined
327 this allows cutting the first part of a Traceback instance e.g.
328 for formatting reasons (removing some uninteresting bits that deal
329 with handling of the exception/traceback)
330 """
331 for x in self:
332 code = x.frame.code
333 codepath = code.path
334 if (
335 (path is None or codepath == path)
336 and (
337 excludepath is None
338 or not isinstance(codepath, py.path.local)
339 or not codepath.relto(excludepath)
340 )
341 and (lineno is None or x.lineno == lineno)
342 and (firstlineno is None or x.frame.code.firstlineno == firstlineno)
343 ):
344 return Traceback(x._rawentry, self._excinfo)
345 return self
347 @overload
348 def __getitem__(self, key: int) -> TracebackEntry:
349 raise NotImplementedError()
351 @overload # noqa: F811
352 def __getitem__(self, key: slice) -> "Traceback": # noqa: F811
353 raise NotImplementedError()
355 def __getitem__( # noqa: F811
356 self, key: Union[int, slice]
357 ) -> Union[TracebackEntry, "Traceback"]:
358 if isinstance(key, slice):
359 return self.__class__(super().__getitem__(key))
360 else:
361 return super().__getitem__(key)
363 def filter(
364 self, fn: Callable[[TracebackEntry], bool] = lambda x: not x.ishidden()
365 ) -> "Traceback":
366 """ return a Traceback instance with certain items removed
368 fn is a function that gets a single argument, a TracebackEntry
369 instance, and should return True when the item should be added
370 to the Traceback, False when not
372 by default this removes all the TracebackEntries which are hidden
373 (see ishidden() above)
374 """
375 return Traceback(filter(fn, self), self._excinfo)
377 def getcrashentry(self) -> TracebackEntry:
378 """ return last non-hidden traceback entry that lead
379 to the exception of a traceback.
380 """
381 for i in range(-1, -len(self) - 1, -1):
382 entry = self[i]
383 if not entry.ishidden():
384 return entry
385 return self[-1]
387 def recursionindex(self) -> Optional[int]:
388 """ return the index of the frame/TracebackEntry where recursion
389 originates if appropriate, None if no recursion occurred
390 """
391 cache = {} # type: Dict[Tuple[Any, int, int], List[Dict[str, Any]]]
392 for i, entry in enumerate(self):
393 # id for the code.raw is needed to work around
394 # the strange metaprogramming in the decorator lib from pypi
395 # which generates code objects that have hash/value equality
396 # XXX needs a test
397 key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
398 # print "checking for recursion at", key
399 values = cache.setdefault(key, [])
400 if values:
401 f = entry.frame
402 loc = f.f_locals
403 for otherloc in values:
404 if f.is_true(
405 f.eval(
406 co_equal,
407 __recursioncache_locals_1=loc,
408 __recursioncache_locals_2=otherloc,
409 )
410 ):
411 return i
412 values.append(entry.frame.f_locals)
413 return None
416co_equal = compile(
417 "__recursioncache_locals_1 == __recursioncache_locals_2", "?", "eval"
418)
421_E = TypeVar("_E", bound=BaseException)
424@attr.s(repr=False)
425class ExceptionInfo(Generic[_E]):
426 """ wraps sys.exc_info() objects and offers
427 help for navigating the traceback.
428 """
430 _assert_start_repr = "AssertionError('assert "
432 _excinfo = attr.ib(type=Optional[Tuple["Type[_E]", "_E", TracebackType]])
433 _striptext = attr.ib(type=str, default="")
434 _traceback = attr.ib(type=Optional[Traceback], default=None)
436 @classmethod
437 def from_exc_info(
438 cls,
439 exc_info: Tuple["Type[_E]", "_E", TracebackType],
440 exprinfo: Optional[str] = None,
441 ) -> "ExceptionInfo[_E]":
442 """returns an ExceptionInfo for an existing exc_info tuple.
444 .. warning::
446 Experimental API
449 :param exprinfo: a text string helping to determine if we should
450 strip ``AssertionError`` from the output, defaults
451 to the exception message/``__str__()``
452 """
453 _striptext = ""
454 if exprinfo is None and isinstance(exc_info[1], AssertionError):
455 exprinfo = getattr(exc_info[1], "msg", None)
456 if exprinfo is None:
457 exprinfo = saferepr(exc_info[1])
458 if exprinfo and exprinfo.startswith(cls._assert_start_repr):
459 _striptext = "AssertionError: "
461 return cls(exc_info, _striptext)
463 @classmethod
464 def from_current(
465 cls, exprinfo: Optional[str] = None
466 ) -> "ExceptionInfo[BaseException]":
467 """returns an ExceptionInfo matching the current traceback
469 .. warning::
471 Experimental API
474 :param exprinfo: a text string helping to determine if we should
475 strip ``AssertionError`` from the output, defaults
476 to the exception message/``__str__()``
477 """
478 tup = sys.exc_info()
479 assert tup[0] is not None, "no current exception"
480 assert tup[1] is not None, "no current exception"
481 assert tup[2] is not None, "no current exception"
482 exc_info = (tup[0], tup[1], tup[2])
483 return ExceptionInfo.from_exc_info(exc_info, exprinfo)
485 @classmethod
486 def for_later(cls) -> "ExceptionInfo[_E]":
487 """return an unfilled ExceptionInfo
488 """
489 return cls(None)
491 def fill_unfilled(self, exc_info: Tuple["Type[_E]", _E, TracebackType]) -> None:
492 """fill an unfilled ExceptionInfo created with for_later()"""
493 assert self._excinfo is None, "ExceptionInfo was already filled"
494 self._excinfo = exc_info
496 @property
497 def type(self) -> "Type[_E]":
498 """the exception class"""
499 assert (
500 self._excinfo is not None
501 ), ".type can only be used after the context manager exits"
502 return self._excinfo[0]
504 @property
505 def value(self) -> _E:
506 """the exception value"""
507 assert (
508 self._excinfo is not None
509 ), ".value can only be used after the context manager exits"
510 return self._excinfo[1]
512 @property
513 def tb(self) -> TracebackType:
514 """the exception raw traceback"""
515 assert (
516 self._excinfo is not None
517 ), ".tb can only be used after the context manager exits"
518 return self._excinfo[2]
520 @property
521 def typename(self) -> str:
522 """the type name of the exception"""
523 assert (
524 self._excinfo is not None
525 ), ".typename can only be used after the context manager exits"
526 return self.type.__name__
528 @property
529 def traceback(self) -> Traceback:
530 """the traceback"""
531 if self._traceback is None:
532 self._traceback = Traceback(self.tb, excinfo=ref(self))
533 return self._traceback
535 @traceback.setter
536 def traceback(self, value: Traceback) -> None:
537 self._traceback = value
539 def __repr__(self) -> str:
540 if self._excinfo is None:
541 return "<ExceptionInfo for raises contextmanager>"
542 return "<{} {} tblen={}>".format(
543 self.__class__.__name__, saferepr(self._excinfo[1]), len(self.traceback)
544 )
546 def exconly(self, tryshort: bool = False) -> str:
547 """ return the exception as a string
549 when 'tryshort' resolves to True, and the exception is a
550 _pytest._code._AssertionError, only the actual exception part of
551 the exception representation is returned (so 'AssertionError: ' is
552 removed from the beginning)
553 """
554 lines = format_exception_only(self.type, self.value)
555 text = "".join(lines)
556 text = text.rstrip()
557 if tryshort:
558 if text.startswith(self._striptext):
559 text = text[len(self._striptext) :]
560 return text
562 def errisinstance(
563 self, exc: Union["Type[BaseException]", Tuple["Type[BaseException]", ...]]
564 ) -> bool:
565 """ return True if the exception is an instance of exc """
566 return isinstance(self.value, exc)
568 def _getreprcrash(self) -> "ReprFileLocation":
569 exconly = self.exconly(tryshort=True)
570 entry = self.traceback.getcrashentry()
571 path, lineno = entry.frame.code.raw.co_filename, entry.lineno
572 return ReprFileLocation(path, lineno + 1, exconly)
574 def getrepr(
575 self,
576 showlocals: bool = False,
577 style: "_TracebackStyle" = "long",
578 abspath: bool = False,
579 tbfilter: bool = True,
580 funcargs: bool = False,
581 truncate_locals: bool = True,
582 chain: bool = True,
583 ) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]:
584 """
585 Return str()able representation of this exception info.
587 :param bool showlocals:
588 Show locals per traceback entry.
589 Ignored if ``style=="native"``.
591 :param str style: long|short|no|native traceback style
593 :param bool abspath:
594 If paths should be changed to absolute or left unchanged.
596 :param bool tbfilter:
597 Hide entries that contain a local variable ``__tracebackhide__==True``.
598 Ignored if ``style=="native"``.
600 :param bool funcargs:
601 Show fixtures ("funcargs" for legacy purposes) per traceback entry.
603 :param bool truncate_locals:
604 With ``showlocals==True``, make sure locals can be safely represented as strings.
606 :param bool chain: if chained exceptions in Python 3 should be shown.
608 .. versionchanged:: 3.9
610 Added the ``chain`` parameter.
611 """
612 if style == "native":
613 return ReprExceptionInfo(
614 ReprTracebackNative(
615 traceback.format_exception(
616 self.type, self.value, self.traceback[0]._rawentry
617 )
618 ),
619 self._getreprcrash(),
620 )
622 fmt = FormattedExcinfo(
623 showlocals=showlocals,
624 style=style,
625 abspath=abspath,
626 tbfilter=tbfilter,
627 funcargs=funcargs,
628 truncate_locals=truncate_locals,
629 chain=chain,
630 )
631 return fmt.repr_excinfo(self)
633 def match(self, regexp: "Union[str, Pattern]") -> bool:
634 """
635 Check whether the regular expression 'regexp' is found in the string
636 representation of the exception using ``re.search``. If it matches
637 then True is returned (so that it is possible to write
638 ``assert excinfo.match()``). If it doesn't match an AssertionError is
639 raised.
640 """
641 __tracebackhide__ = True
642 if not re.search(regexp, str(self.value)):
643 assert 0, "Pattern {!r} not found in {!r}".format(regexp, str(self.value))
644 return True
647@attr.s
648class FormattedExcinfo:
649 """ presenting information about failing Functions and Generators. """
651 # for traceback entries
652 flow_marker = ">"
653 fail_marker = "E"
655 showlocals = attr.ib(type=bool, default=False)
656 style = attr.ib(type="_TracebackStyle", default="long")
657 abspath = attr.ib(type=bool, default=True)
658 tbfilter = attr.ib(type=bool, default=True)
659 funcargs = attr.ib(type=bool, default=False)
660 truncate_locals = attr.ib(type=bool, default=True)
661 chain = attr.ib(type=bool, default=True)
662 astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False)
664 def _getindent(self, source: "Source") -> int:
665 # figure out indent for given source
666 try:
667 s = str(source.getstatement(len(source) - 1))
668 except KeyboardInterrupt:
669 raise
670 except: # noqa
671 try:
672 s = str(source[-1])
673 except KeyboardInterrupt:
674 raise
675 except: # noqa
676 return 0
677 return 4 + (len(s) - len(s.lstrip()))
679 def _getentrysource(self, entry: TracebackEntry) -> Optional["Source"]:
680 source = entry.getsource(self.astcache)
681 if source is not None:
682 source = source.deindent()
683 return source
685 def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]:
686 if self.funcargs:
687 args = []
688 for argname, argvalue in entry.frame.getargs(var=True):
689 args.append((argname, saferepr(argvalue)))
690 return ReprFuncArgs(args)
691 return None
693 def get_source(
694 self,
695 source: "Source",
696 line_index: int = -1,
697 excinfo: Optional[ExceptionInfo] = None,
698 short: bool = False,
699 ) -> List[str]:
700 """ return formatted and marked up source lines. """
701 import _pytest._code
703 lines = []
704 if source is None or line_index >= len(source.lines):
705 source = _pytest._code.Source("???")
706 line_index = 0
707 if line_index < 0:
708 line_index += len(source)
709 space_prefix = " "
710 if short:
711 lines.append(space_prefix + source.lines[line_index].strip())
712 else:
713 for line in source.lines[:line_index]:
714 lines.append(space_prefix + line)
715 lines.append(self.flow_marker + " " + source.lines[line_index])
716 for line in source.lines[line_index + 1 :]:
717 lines.append(space_prefix + line)
718 if excinfo is not None:
719 indent = 4 if short else self._getindent(source)
720 lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
721 return lines
723 def get_exconly(
724 self, excinfo: ExceptionInfo, indent: int = 4, markall: bool = False
725 ) -> List[str]:
726 lines = []
727 indentstr = " " * indent
728 # get the real exception information out
729 exlines = excinfo.exconly(tryshort=True).split("\n")
730 failindent = self.fail_marker + indentstr[1:]
731 for line in exlines:
732 lines.append(failindent + line)
733 if not markall:
734 failindent = indentstr
735 return lines
737 def repr_locals(self, locals: Dict[str, object]) -> Optional["ReprLocals"]:
738 if self.showlocals:
739 lines = []
740 keys = [loc for loc in locals if loc[0] != "@"]
741 keys.sort()
742 for name in keys:
743 value = locals[name]
744 if name == "__builtins__":
745 lines.append("__builtins__ = <builtins>")
746 else:
747 # This formatting could all be handled by the
748 # _repr() function, which is only reprlib.Repr in
749 # disguise, so is very configurable.
750 if self.truncate_locals:
751 str_repr = saferepr(value)
752 else:
753 str_repr = safeformat(value)
754 # if len(str_repr) < 70 or not isinstance(value,
755 # (list, tuple, dict)):
756 lines.append("{:<10} = {}".format(name, str_repr))
757 # else:
758 # self._line("%-10s =\\" % (name,))
759 # # XXX
760 # pprint.pprint(value, stream=self.excinfowriter)
761 return ReprLocals(lines)
762 return None
764 def repr_traceback_entry(
765 self, entry: TracebackEntry, excinfo: Optional[ExceptionInfo] = None
766 ) -> "ReprEntry":
767 import _pytest._code
769 source = self._getentrysource(entry)
770 if source is None:
771 source = _pytest._code.Source("???")
772 line_index = 0
773 else:
774 line_index = entry.lineno - entry.getfirstlinesource()
776 lines = [] # type: List[str]
777 style = entry._repr_style if entry._repr_style is not None else self.style
778 if style in ("short", "long"):
779 short = style == "short"
780 reprargs = self.repr_args(entry) if not short else None
781 s = self.get_source(source, line_index, excinfo, short=short)
782 lines.extend(s)
783 if short:
784 message = "in %s" % (entry.name)
785 else:
786 message = excinfo and excinfo.typename or ""
787 path = self._makepath(entry.path)
788 filelocrepr = ReprFileLocation(path, entry.lineno + 1, message)
789 localsrepr = None
790 if not short:
791 localsrepr = self.repr_locals(entry.locals)
792 return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
793 if excinfo:
794 lines.extend(self.get_exconly(excinfo, indent=4))
795 return ReprEntry(lines, None, None, None, style)
797 def _makepath(self, path):
798 if not self.abspath:
799 try:
800 np = py.path.local().bestrelpath(path)
801 except OSError:
802 return path
803 if len(np) < len(str(path)):
804 path = np
805 return path
807 def repr_traceback(self, excinfo: ExceptionInfo) -> "ReprTraceback":
808 traceback = excinfo.traceback
809 if self.tbfilter:
810 traceback = traceback.filter()
812 if excinfo.errisinstance(RecursionError):
813 traceback, extraline = self._truncate_recursive_traceback(traceback)
814 else:
815 extraline = None
817 last = traceback[-1]
818 entries = []
819 for index, entry in enumerate(traceback):
820 einfo = (last == entry) and excinfo or None
821 reprentry = self.repr_traceback_entry(entry, einfo)
822 entries.append(reprentry)
823 return ReprTraceback(entries, extraline, style=self.style)
825 def _truncate_recursive_traceback(
826 self, traceback: Traceback
827 ) -> Tuple[Traceback, Optional[str]]:
828 """
829 Truncate the given recursive traceback trying to find the starting point
830 of the recursion.
832 The detection is done by going through each traceback entry and finding the
833 point in which the locals of the frame are equal to the locals of a previous frame (see ``recursionindex()``.
835 Handle the situation where the recursion process might raise an exception (for example
836 comparing numpy arrays using equality raises a TypeError), in which case we do our best to
837 warn the user of the error and show a limited traceback.
838 """
839 try:
840 recursionindex = traceback.recursionindex()
841 except Exception as e:
842 max_frames = 10
843 extraline = (
844 "!!! Recursion error detected, but an error occurred locating the origin of recursion.\n"
845 " The following exception happened when comparing locals in the stack frame:\n"
846 " {exc_type}: {exc_msg}\n"
847 " Displaying first and last {max_frames} stack frames out of {total}."
848 ).format(
849 exc_type=type(e).__name__,
850 exc_msg=str(e),
851 max_frames=max_frames,
852 total=len(traceback),
853 ) # type: Optional[str]
854 # Type ignored because adding two instaces of a List subtype
855 # currently incorrectly has type List instead of the subtype.
856 traceback = traceback[:max_frames] + traceback[-max_frames:] # type: ignore
857 else:
858 if recursionindex is not None:
859 extraline = "!!! Recursion detected (same locals & position)"
860 traceback = traceback[: recursionindex + 1]
861 else:
862 extraline = None
864 return traceback, extraline
866 def repr_excinfo(self, excinfo: ExceptionInfo) -> "ExceptionChainRepr":
867 repr_chain = (
868 []
869 ) # type: List[Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]]]
870 e = excinfo.value
871 excinfo_ = excinfo # type: Optional[ExceptionInfo]
872 descr = None
873 seen = set() # type: Set[int]
874 while e is not None and id(e) not in seen:
875 seen.add(id(e))
876 if excinfo_:
877 reprtraceback = self.repr_traceback(excinfo_)
878 reprcrash = excinfo_._getreprcrash() # type: Optional[ReprFileLocation]
879 else:
880 # fallback to native repr if the exception doesn't have a traceback:
881 # ExceptionInfo objects require a full traceback to work
882 reprtraceback = ReprTracebackNative(
883 traceback.format_exception(type(e), e, None)
884 )
885 reprcrash = None
887 repr_chain += [(reprtraceback, reprcrash, descr)]
888 if e.__cause__ is not None and self.chain:
889 e = e.__cause__
890 excinfo_ = (
891 ExceptionInfo((type(e), e, e.__traceback__))
892 if e.__traceback__
893 else None
894 )
895 descr = "The above exception was the direct cause of the following exception:"
896 elif (
897 e.__context__ is not None and not e.__suppress_context__ and self.chain
898 ):
899 e = e.__context__
900 excinfo_ = (
901 ExceptionInfo((type(e), e, e.__traceback__))
902 if e.__traceback__
903 else None
904 )
905 descr = "During handling of the above exception, another exception occurred:"
906 else:
907 e = None
908 repr_chain.reverse()
909 return ExceptionChainRepr(repr_chain)
912class TerminalRepr:
913 def __str__(self) -> str:
914 # FYI this is called from pytest-xdist's serialization of exception
915 # information.
916 io = StringIO()
917 tw = py.io.TerminalWriter(file=io)
918 self.toterminal(tw)
919 return io.getvalue().strip()
921 def __repr__(self) -> str:
922 return "<{} instance at {:0x}>".format(self.__class__, id(self))
924 def toterminal(self, tw: py.io.TerminalWriter) -> None:
925 raise NotImplementedError()
928class ExceptionRepr(TerminalRepr):
929 def __init__(self) -> None:
930 self.sections = [] # type: List[Tuple[str, str, str]]
932 def addsection(self, name: str, content: str, sep: str = "-") -> None:
933 self.sections.append((name, content, sep))
935 def toterminal(self, tw: py.io.TerminalWriter) -> None:
936 for name, content, sep in self.sections:
937 tw.sep(sep, name)
938 tw.line(content)
941class ExceptionChainRepr(ExceptionRepr):
942 def __init__(
943 self,
944 chain: Sequence[
945 Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]
946 ],
947 ) -> None:
948 super().__init__()
949 self.chain = chain
950 # reprcrash and reprtraceback of the outermost (the newest) exception
951 # in the chain
952 self.reprtraceback = chain[-1][0]
953 self.reprcrash = chain[-1][1]
955 def toterminal(self, tw: py.io.TerminalWriter) -> None:
956 for element in self.chain:
957 element[0].toterminal(tw)
958 if element[2] is not None:
959 tw.line("")
960 tw.line(element[2], yellow=True)
961 super().toterminal(tw)
964class ReprExceptionInfo(ExceptionRepr):
965 def __init__(
966 self, reprtraceback: "ReprTraceback", reprcrash: "ReprFileLocation"
967 ) -> None:
968 super().__init__()
969 self.reprtraceback = reprtraceback
970 self.reprcrash = reprcrash
972 def toterminal(self, tw: py.io.TerminalWriter) -> None:
973 self.reprtraceback.toterminal(tw)
974 super().toterminal(tw)
977class ReprTraceback(TerminalRepr):
978 entrysep = "_ "
980 def __init__(
981 self,
982 reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]],
983 extraline: Optional[str],
984 style: "_TracebackStyle",
985 ) -> None:
986 self.reprentries = reprentries
987 self.extraline = extraline
988 self.style = style
990 def toterminal(self, tw: py.io.TerminalWriter) -> None:
991 # the entries might have different styles
992 for i, entry in enumerate(self.reprentries):
993 if entry.style == "long":
994 tw.line("")
995 entry.toterminal(tw)
996 if i < len(self.reprentries) - 1:
997 next_entry = self.reprentries[i + 1]
998 if (
999 entry.style == "long"
1000 or entry.style == "short"
1001 and next_entry.style == "long"
1002 ):
1003 tw.sep(self.entrysep)
1005 if self.extraline:
1006 tw.line(self.extraline)
1009class ReprTracebackNative(ReprTraceback):
1010 def __init__(self, tblines: Sequence[str]) -> None:
1011 self.style = "native"
1012 self.reprentries = [ReprEntryNative(tblines)]
1013 self.extraline = None
1016class ReprEntryNative(TerminalRepr):
1017 style = "native" # type: _TracebackStyle
1019 def __init__(self, tblines: Sequence[str]) -> None:
1020 self.lines = tblines
1022 def toterminal(self, tw: py.io.TerminalWriter) -> None:
1023 tw.write("".join(self.lines))
1026class ReprEntry(TerminalRepr):
1027 def __init__(
1028 self,
1029 lines: Sequence[str],
1030 reprfuncargs: Optional["ReprFuncArgs"],
1031 reprlocals: Optional["ReprLocals"],
1032 filelocrepr: Optional["ReprFileLocation"],
1033 style: "_TracebackStyle",
1034 ) -> None:
1035 self.lines = lines
1036 self.reprfuncargs = reprfuncargs
1037 self.reprlocals = reprlocals
1038 self.reprfileloc = filelocrepr
1039 self.style = style
1041 def toterminal(self, tw: py.io.TerminalWriter) -> None:
1042 if self.style == "short":
1043 assert self.reprfileloc is not None
1044 self.reprfileloc.toterminal(tw)
1045 for line in self.lines:
1046 red = line.startswith("E ")
1047 tw.line(line, bold=True, red=red)
1048 return
1049 if self.reprfuncargs:
1050 self.reprfuncargs.toterminal(tw)
1051 for line in self.lines:
1052 red = line.startswith("E ")
1053 tw.line(line, bold=True, red=red)
1054 if self.reprlocals:
1055 tw.line("")
1056 self.reprlocals.toterminal(tw)
1057 if self.reprfileloc:
1058 if self.lines:
1059 tw.line("")
1060 self.reprfileloc.toterminal(tw)
1062 def __str__(self) -> str:
1063 return "{}\n{}\n{}".format(
1064 "\n".join(self.lines), self.reprlocals, self.reprfileloc
1065 )
1068class ReprFileLocation(TerminalRepr):
1069 def __init__(self, path, lineno: int, message: str) -> None:
1070 self.path = str(path)
1071 self.lineno = lineno
1072 self.message = message
1074 def toterminal(self, tw: py.io.TerminalWriter) -> None:
1075 # filename and lineno output for each entry,
1076 # using an output format that most editors understand
1077 msg = self.message
1078 i = msg.find("\n")
1079 if i != -1:
1080 msg = msg[:i]
1081 tw.write(self.path, bold=True, red=True)
1082 tw.line(":{}: {}".format(self.lineno, msg))
1085class ReprLocals(TerminalRepr):
1086 def __init__(self, lines: Sequence[str]) -> None:
1087 self.lines = lines
1089 def toterminal(self, tw: py.io.TerminalWriter) -> None:
1090 for line in self.lines:
1091 tw.line(line)
1094class ReprFuncArgs(TerminalRepr):
1095 def __init__(self, args: Sequence[Tuple[str, object]]) -> None:
1096 self.args = args
1098 def toterminal(self, tw: py.io.TerminalWriter) -> None:
1099 if self.args:
1100 linesofar = ""
1101 for name, value in self.args:
1102 ns = "{} = {}".format(name, value)
1103 if len(ns) + len(linesofar) + 2 > tw.fullwidth:
1104 if linesofar:
1105 tw.line(linesofar)
1106 linesofar = ns
1107 else:
1108 if linesofar:
1109 linesofar += ", " + ns
1110 else:
1111 linesofar = ns
1112 if linesofar:
1113 tw.line(linesofar)
1114 tw.line("")
1117def getrawcode(obj, trycall: bool = True):
1118 """ return code object for given function. """
1119 try:
1120 return obj.__code__
1121 except AttributeError:
1122 obj = getattr(obj, "f_code", obj)
1123 obj = getattr(obj, "__code__", obj)
1124 if trycall and not hasattr(obj, "co_firstlineno"):
1125 if hasattr(obj, "__call__") and not inspect.isclass(obj):
1126 x = getrawcode(obj.__call__, trycall=False)
1127 if hasattr(x, "co_firstlineno"):
1128 return x
1129 return obj
1132# relative paths that we use to filter traceback entries from appearing to the user;
1133# see filter_traceback
1134# note: if we need to add more paths than what we have now we should probably use a list
1135# for better maintenance
1137_PLUGGY_DIR = py.path.local(pluggy.__file__.rstrip("oc"))
1138# pluggy is either a package or a single module depending on the version
1139if _PLUGGY_DIR.basename == "__init__.py":
1140 _PLUGGY_DIR = _PLUGGY_DIR.dirpath()
1141_PYTEST_DIR = py.path.local(_pytest.__file__).dirpath()
1142_PY_DIR = py.path.local(py.__file__).dirpath()
1145def filter_traceback(entry: TracebackEntry) -> bool:
1146 """Return True if a TracebackEntry instance should be removed from tracebacks:
1147 * dynamically generated code (no code to show up for it);
1148 * internal traceback from pytest or its internal libraries, py and pluggy.
1149 """
1150 # entry.path might sometimes return a str object when the entry
1151 # points to dynamically generated code
1152 # see https://bitbucket.org/pytest-dev/py/issues/71
1153 raw_filename = entry.frame.code.raw.co_filename
1154 is_generated = "<" in raw_filename and ">" in raw_filename
1155 if is_generated:
1156 return False
1157 # entry.path might point to a non-existing file, in which case it will
1158 # also return a str object. see #1133
1159 p = py.path.local(entry.path)
1160 return (
1161 not p.relto(_PLUGGY_DIR) and not p.relto(_PYTEST_DIR) and not p.relto(_PY_DIR)
1162 )