diff options
author | shadchin <shadchin@yandex-team.ru> | 2022-02-10 16:44:39 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:44:39 +0300 |
commit | e9656aae26e0358d5378e5b63dcac5c8dbe0e4d0 (patch) | |
tree | 64175d5cadab313b3e7039ebaa06c5bc3295e274 /contrib/python/pytest/py3/_pytest/_code | |
parent | 2598ef1d0aee359b4b6d5fdd1758916d5907d04f (diff) | |
download | ydb-e9656aae26e0358d5378e5b63dcac5c8dbe0e4d0.tar.gz |
Restoring authorship annotation for <shadchin@yandex-team.ru>. Commit 2 of 2.
Diffstat (limited to 'contrib/python/pytest/py3/_pytest/_code')
-rw-r--r-- | contrib/python/pytest/py3/_pytest/_code/__init__.py | 44 | ||||
-rw-r--r-- | contrib/python/pytest/py3/_pytest/_code/code.py | 1490 | ||||
-rw-r--r-- | contrib/python/pytest/py3/_pytest/_code/source.py | 192 |
3 files changed, 863 insertions, 863 deletions
diff --git a/contrib/python/pytest/py3/_pytest/_code/__init__.py b/contrib/python/pytest/py3/_pytest/_code/__init__.py index 2f13335d10..511d0dde66 100644 --- a/contrib/python/pytest/py3/_pytest/_code/__init__.py +++ b/contrib/python/pytest/py3/_pytest/_code/__init__.py @@ -1,22 +1,22 @@ -"""Python inspection/code generation API.""" -from .code import Code -from .code import ExceptionInfo -from .code import filter_traceback -from .code import Frame -from .code import getfslineno -from .code import Traceback -from .code import TracebackEntry -from .source import getrawcode -from .source import Source - -__all__ = [ - "Code", - "ExceptionInfo", - "filter_traceback", - "Frame", - "getfslineno", - "getrawcode", - "Traceback", - "TracebackEntry", - "Source", -] +"""Python inspection/code generation API.""" +from .code import Code +from .code import ExceptionInfo +from .code import filter_traceback +from .code import Frame +from .code import getfslineno +from .code import Traceback +from .code import TracebackEntry +from .source import getrawcode +from .source import Source + +__all__ = [ + "Code", + "ExceptionInfo", + "filter_traceback", + "Frame", + "getfslineno", + "getrawcode", + "Traceback", + "TracebackEntry", + "Source", +] diff --git a/contrib/python/pytest/py3/_pytest/_code/code.py b/contrib/python/pytest/py3/_pytest/_code/code.py index 6e3e270b1c..423069330a 100644 --- a/contrib/python/pytest/py3/_pytest/_code/code.py +++ b/contrib/python/pytest/py3/_pytest/_code/code.py @@ -4,29 +4,29 @@ import sys import traceback from inspect import CO_VARARGS from inspect import CO_VARKEYWORDS -from io import StringIO -from pathlib import Path -from traceback import format_exception_only -from types import CodeType -from types import FrameType -from types import TracebackType -from typing import Any -from typing import Callable -from typing import Dict -from typing import Generic -from typing import Iterable -from typing import List -from typing import Mapping -from typing import Optional -from typing import overload -from typing import Pattern -from typing import Sequence -from typing import Set -from typing import Tuple -from typing import Type -from typing import TYPE_CHECKING -from typing import TypeVar -from typing import Union +from io import StringIO +from pathlib import Path +from traceback import format_exception_only +from types import CodeType +from types import FrameType +from types import TracebackType +from typing import Any +from typing import Callable +from typing import Dict +from typing import Generic +from typing import Iterable +from typing import List +from typing import Mapping +from typing import Optional +from typing import overload +from typing import Pattern +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union from weakref import ref import attr @@ -34,84 +34,84 @@ import pluggy import py import _pytest -from _pytest._code.source import findsource -from _pytest._code.source import getrawcode -from _pytest._code.source import getstatementrange_ast -from _pytest._code.source import Source -from _pytest._io import TerminalWriter -from _pytest._io.saferepr import safeformat -from _pytest._io.saferepr import saferepr -from _pytest.compat import final -from _pytest.compat import get_real_func - -if TYPE_CHECKING: - from typing_extensions import Literal - from weakref import ReferenceType - - _TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] - - -class Code: - """Wrapper around Python code objects.""" - - __slots__ = ("raw",) - - def __init__(self, obj: CodeType) -> None: - self.raw = obj - - @classmethod - def from_function(cls, obj: object) -> "Code": - return cls(getrawcode(obj)) - +from _pytest._code.source import findsource +from _pytest._code.source import getrawcode +from _pytest._code.source import getstatementrange_ast +from _pytest._code.source import Source +from _pytest._io import TerminalWriter +from _pytest._io.saferepr import safeformat +from _pytest._io.saferepr import saferepr +from _pytest.compat import final +from _pytest.compat import get_real_func + +if TYPE_CHECKING: + from typing_extensions import Literal + from weakref import ReferenceType + + _TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] + + +class Code: + """Wrapper around Python code objects.""" + + __slots__ = ("raw",) + + def __init__(self, obj: CodeType) -> None: + self.raw = obj + + @classmethod + def from_function(cls, obj: object) -> "Code": + return cls(getrawcode(obj)) + def __eq__(self, other): return self.raw == other.raw - # Ignore type because of https://github.com/python/mypy/issues/4266. - __hash__ = None # type: ignore + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore - @property - def firstlineno(self) -> int: - return self.raw.co_firstlineno - 1 + @property + def firstlineno(self) -> int: + return self.raw.co_firstlineno - 1 + + @property + def name(self) -> str: + return self.raw.co_name @property - def name(self) -> str: - return self.raw.co_name - - @property - def path(self) -> Union[py.path.local, str]: - """Return a path object pointing to source code, or an ``str`` in - case of ``OSError`` / non-existing file.""" - if not self.raw.co_filename: - return "" + def path(self) -> Union[py.path.local, str]: + """Return a path object pointing to source code, or an ``str`` in + case of ``OSError`` / non-existing file.""" + if not self.raw.co_filename: + return "" 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.") - return p + return p except OSError: # XXX maybe try harder like the weird logic # in the standard lib [linecache.updatecache] does? - return self.raw.co_filename + return self.raw.co_filename @property - def fullsource(self) -> Optional["Source"]: - """Return a _pytest._code.Source object for the full source file of the code.""" - full, _ = findsource(self.raw) + def fullsource(self) -> Optional["Source"]: + """Return a _pytest._code.Source object for the full source file of the code.""" + full, _ = findsource(self.raw) return full - def source(self) -> "Source": - """Return a _pytest._code.Source object for the code object's source only.""" + def source(self) -> "Source": + """Return a _pytest._code.Source object for the code object's source only.""" # return source only for that part of code - return Source(self.raw) + return Source(self.raw) - def getargs(self, var: bool = False) -> Tuple[str, ...]: - """Return a tuple with the argument names for the code object. + def getargs(self, var: bool = False) -> Tuple[str, ...]: + """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. + If 'var' is set True also return the names of the variable and + keyword arguments when present. """ - # Handy shortcut for getting args. + # Handy shortcut for getting args. raw = self.raw argcount = raw.co_argcount if var: @@ -120,58 +120,58 @@ class Code: return raw.co_varnames[:argcount] -class Frame: +class Frame: """Wrapper around a Python frame holding f_locals and f_globals in which expressions can be evaluated.""" - __slots__ = ("raw",) - - def __init__(self, frame: FrameType) -> None: + __slots__ = ("raw",) + + def __init__(self, frame: FrameType) -> None: self.raw = frame @property - def lineno(self) -> int: - return self.raw.f_lineno - 1 - - @property - def f_globals(self) -> Dict[str, Any]: - return self.raw.f_globals - - @property - def f_locals(self) -> Dict[str, Any]: - return self.raw.f_locals - - @property - def code(self) -> Code: - return Code(self.raw.f_code) - - @property - def statement(self) -> "Source": - """Statement this frame is at.""" + def lineno(self) -> int: + return self.raw.f_lineno - 1 + + @property + def f_globals(self) -> Dict[str, Any]: + return self.raw.f_globals + + @property + def f_locals(self) -> Dict[str, Any]: + return self.raw.f_locals + + @property + def code(self) -> Code: + return Code(self.raw.f_code) + + @property + def statement(self) -> "Source": + """Statement this frame is at.""" if self.code.fullsource is None: - return Source("") + return Source("") return self.code.fullsource.getstatement(self.lineno) def eval(self, code, **vars): - """Evaluate 'code' in the frame. + """Evaluate 'code' in the frame. - 'vars' are optional additional local variables. + 'vars' are optional additional local variables. - Returns the result of the evaluation. + 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 repr(self, object: object) -> str: - """Return a 'safe' (non-recursive, one-line) string repr for 'object'.""" - return saferepr(object) + def repr(self, object: object) -> str: + """Return a 'safe' (non-recursive, one-line) string repr for 'object'.""" + return saferepr(object) - def getargs(self, var: bool = False): - """Return a list of tuples (name, value) for all arguments. + def getargs(self, var: bool = 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. + If 'var' is set True, also include the variable and keyword arguments + when present. """ retval = [] for arg in self.code.getargs(var): @@ -182,61 +182,61 @@ class Frame: return retval -class TracebackEntry: - """A single entry in a Traceback.""" +class TracebackEntry: + """A single entry in a Traceback.""" - __slots__ = ("_rawentry", "_excinfo", "_repr_style") + __slots__ = ("_rawentry", "_excinfo", "_repr_style") - def __init__( - self, - rawentry: TracebackType, - excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None, - ) -> None: - self._rawentry = rawentry + def __init__( + self, + rawentry: TracebackType, + excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None, + ) -> None: + self._rawentry = rawentry self._excinfo = excinfo - self._repr_style: Optional['Literal["short", "long"]'] = None + self._repr_style: Optional['Literal["short", "long"]'] = None + + @property + def lineno(self) -> int: + return self._rawentry.tb_lineno - 1 - @property - def lineno(self) -> int: - return self._rawentry.tb_lineno - 1 - - def set_repr_style(self, mode: "Literal['short', 'long']") -> None: + def set_repr_style(self, mode: "Literal['short', 'long']") -> None: assert mode in ("short", "long") self._repr_style = mode @property - def frame(self) -> Frame: - return Frame(self._rawentry.tb_frame) + def frame(self) -> Frame: + return Frame(self._rawentry.tb_frame) @property - def relline(self) -> int: + def relline(self) -> int: return self.lineno - self.frame.code.firstlineno - def __repr__(self) -> str: + def __repr__(self) -> str: return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1) @property - def statement(self) -> "Source": - """_pytest._code.Source object for the current statement.""" + def statement(self) -> "Source": + """_pytest._code.Source object for the current statement.""" source = self.frame.code.fullsource - assert source is not None + assert source is not None return source.getstatement(self.lineno) @property - def path(self) -> Union[py.path.local, str]: - """Path to the source code.""" + def path(self) -> Union[py.path.local, str]: + """Path to the source code.""" return self.frame.code.path - @property - def locals(self) -> Dict[str, Any]: - """Locals of underlying frame.""" + @property + def locals(self) -> Dict[str, Any]: + """Locals of underlying frame.""" return self.frame.f_locals - def getfirstlinesource(self) -> int: - return self.frame.code.firstlineno + def getfirstlinesource(self) -> int: + return self.frame.code.firstlineno - def getsource(self, astcache=None) -> Optional["Source"]: - """Return failing source code.""" + def getsource(self, astcache=None) -> Optional["Source"]: + """Return failing source code.""" # we use the passed in astcache to not reparse asttrees # within exception info printing source = self.frame.code.fullsource @@ -261,94 +261,94 @@ class TracebackEntry: source = property(getsource) - def ishidden(self) -> bool: - """Return True if the current frame has a var __tracebackhide__ - resolving to True. + def ishidden(self) -> bool: + """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. + If __tracebackhide__ is a callable, it gets called with the + ExceptionInfo instance and can decide whether to hide the traceback. - Mostly for internal use. + Mostly for internal use. """ - tbh: Union[bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]] = ( - False - ) - for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals): - # in normal cases, f_locals and f_globals are dictionaries - # however via `exec(...)` / `eval(...)` they can be other types - # (even incorrect types!). - # as such, we suppress all exceptions while accessing __tracebackhide__ - try: - tbh = maybe_ns_dct["__tracebackhide__"] - except Exception: - pass - else: - break - if tbh and callable(tbh): + tbh: Union[bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]] = ( + False + ) + for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals): + # in normal cases, f_locals and f_globals are dictionaries + # however via `exec(...)` / `eval(...)` they can be other types + # (even incorrect types!). + # as such, we suppress all exceptions while accessing __tracebackhide__ + try: + tbh = maybe_ns_dct["__tracebackhide__"] + except Exception: + pass + else: + break + if tbh and callable(tbh): return tbh(None if self._excinfo is None else self._excinfo()) - return tbh + return tbh - def __str__(self) -> str: + def __str__(self) -> str: name = self.frame.code.name try: line = str(self.statement).lstrip() except KeyboardInterrupt: raise - except BaseException: + except BaseException: line = "???" - # This output does not quite match Python's repr for traceback entries, - # but changing it to do so would break certain plugins. See - # https://github.com/pytest-dev/pytest/pull/7535/ for details. - return " File %r:%d in %s\n %s\n" % ( - str(self.path), - self.lineno + 1, - name, - line, - ) - - @property - def name(self) -> str: - """co_name of underlying code.""" + # This output does not quite match Python's repr for traceback entries, + # but changing it to do so would break certain plugins. See + # https://github.com/pytest-dev/pytest/pull/7535/ for details. + return " File %r:%d in %s\n %s\n" % ( + str(self.path), + self.lineno + 1, + name, + line, + ) + + @property + def name(self) -> str: + """co_name of underlying code.""" return self.frame.code.raw.co_name -class Traceback(List[TracebackEntry]): - """Traceback objects encapsulate and offer higher level access to Traceback entries.""" +class Traceback(List[TracebackEntry]): + """Traceback objects encapsulate and offer higher level access to Traceback entries.""" - def __init__( - self, - tb: Union[TracebackType, Iterable[TracebackEntry]], - excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None, - ) -> None: - """Initialize from given python traceback object and ExceptionInfo.""" + def __init__( + self, + tb: Union[TracebackType, Iterable[TracebackEntry]], + excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None, + ) -> None: + """Initialize from given python traceback object and ExceptionInfo.""" self._excinfo = excinfo - if isinstance(tb, TracebackType): + if isinstance(tb, TracebackType): - def f(cur: TracebackType) -> Iterable[TracebackEntry]: - cur_: Optional[TracebackType] = cur - while cur_ is not None: - yield TracebackEntry(cur_, excinfo=excinfo) - cur_ = cur_.tb_next + def f(cur: TracebackType) -> Iterable[TracebackEntry]: + cur_: Optional[TracebackType] = cur + while cur_ is not None: + yield TracebackEntry(cur_, excinfo=excinfo) + cur_ = cur_.tb_next - super().__init__(f(tb)) + super().__init__(f(tb)) else: - super().__init__(tb) - - def cut( - self, - path=None, - lineno: Optional[int] = None, - firstlineno: Optional[int] = None, - excludepath: Optional[py.path.local] = None, - ) -> "Traceback": - """Return a Traceback instance wrapping part of this Traceback. - - By providing 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). + super().__init__(tb) + + def cut( + self, + path=None, + lineno: Optional[int] = None, + firstlineno: Optional[int] = None, + excludepath: Optional[py.path.local] = None, + ) -> "Traceback": + """Return a Traceback instance wrapping part of this Traceback. + + By providing 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 @@ -357,7 +357,7 @@ class Traceback(List[TracebackEntry]): (path is None or codepath == path) and ( excludepath is None - or not isinstance(codepath, py.path.local) + or not isinstance(codepath, py.path.local) or not codepath.relto(excludepath) ) and (lineno is None or x.lineno == lineno) @@ -366,46 +366,46 @@ class Traceback(List[TracebackEntry]): return Traceback(x._rawentry, self._excinfo) return self - @overload - def __getitem__(self, key: int) -> TracebackEntry: - ... - - @overload - def __getitem__(self, key: slice) -> "Traceback": - ... - - def __getitem__(self, key: Union[int, slice]) -> Union[TracebackEntry, "Traceback"]: - if isinstance(key, slice): - return self.__class__(super().__getitem__(key)) - else: - return super().__getitem__(key) - - def filter( - self, fn: Callable[[TracebackEntry], bool] = lambda x: not x.ishidden() - ) -> "Traceback": - """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). + @overload + def __getitem__(self, key: int) -> TracebackEntry: + ... + + @overload + def __getitem__(self, key: slice) -> "Traceback": + ... + + def __getitem__(self, key: Union[int, slice]) -> Union[TracebackEntry, "Traceback"]: + if isinstance(key, slice): + return self.__class__(super().__getitem__(key)) + else: + return super().__getitem__(key) + + def filter( + self, fn: Callable[[TracebackEntry], bool] = lambda x: not x.ishidden() + ) -> "Traceback": + """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) -> TracebackEntry: - """Return last non-hidden traceback entry that lead to the exception of a traceback.""" + def getcrashentry(self) -> TracebackEntry: + """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) -> Optional[int]: - """Return the index of the frame/TracebackEntry where recursion originates if - appropriate, None if no recursion occurred.""" - cache: Dict[Tuple[Any, int, int], List[Dict[str, Any]]] = {} + def recursionindex(self) -> Optional[int]: + """Return the index of the frame/TracebackEntry where recursion originates if + appropriate, None if no recursion occurred.""" + cache: Dict[Tuple[Any, int, int], List[Dict[str, Any]]] = {} 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 @@ -418,10 +418,10 @@ class Traceback(List[TracebackEntry]): f = entry.frame loc = f.f_locals for otherloc in values: - if f.eval( - co_equal, - __recursioncache_locals_1=loc, - __recursioncache_locals_2=otherloc, + if f.eval( + co_equal, + __recursioncache_locals_1=loc, + __recursioncache_locals_2=otherloc, ): return i values.append(entry.frame.f_locals) @@ -433,136 +433,136 @@ co_equal = compile( ) -_E = TypeVar("_E", bound=BaseException, covariant=True) - - -@final -@attr.s(repr=False) -class ExceptionInfo(Generic[_E]): - """Wraps sys.exc_info() objects and offers help for navigating the traceback.""" - - _assert_start_repr = "AssertionError('assert " - - _excinfo = attr.ib(type=Optional[Tuple[Type["_E"], "_E", TracebackType]]) - _striptext = attr.ib(type=str, default="") - _traceback = attr.ib(type=Optional[Traceback], default=None) - - @classmethod - def from_exc_info( - cls, - exc_info: Tuple[Type[_E], _E, TracebackType], - exprinfo: Optional[str] = None, - ) -> "ExceptionInfo[_E]": - """Return an ExceptionInfo for an existing exc_info tuple. - - .. 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__()``. - """ - _striptext = "" - if exprinfo is None and isinstance(exc_info[1], AssertionError): - exprinfo = getattr(exc_info[1], "msg", None) - if exprinfo is None: - exprinfo = saferepr(exc_info[1]) - if exprinfo and exprinfo.startswith(cls._assert_start_repr): - _striptext = "AssertionError: " - - return cls(exc_info, _striptext) - - @classmethod - def from_current( - cls, exprinfo: Optional[str] = None - ) -> "ExceptionInfo[BaseException]": - """Return 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" - assert tup[1] is not None, "no current exception" - assert tup[2] is not None, "no current exception" - exc_info = (tup[0], tup[1], tup[2]) - return ExceptionInfo.from_exc_info(exc_info, exprinfo) - - @classmethod - def for_later(cls) -> "ExceptionInfo[_E]": - """Return an unfilled ExceptionInfo.""" - return cls(None) - - def fill_unfilled(self, exc_info: Tuple[Type[_E], _E, TracebackType]) -> None: - """Fill an unfilled ExceptionInfo created with ``for_later()``.""" - assert self._excinfo is None, "ExceptionInfo was already filled" - self._excinfo = exc_info - - @property - def type(self) -> Type[_E]: - """The exception class.""" - assert ( - self._excinfo is not None - ), ".type can only be used after the context manager exits" - return self._excinfo[0] - - @property - def value(self) -> _E: - """The exception value.""" - assert ( - self._excinfo is not None - ), ".value can only be used after the context manager exits" - return self._excinfo[1] - - @property - def tb(self) -> TracebackType: - """The exception raw traceback.""" - assert ( - self._excinfo is not None - ), ".tb can only be used after the context manager exits" - return self._excinfo[2] - - @property - def typename(self) -> str: - """The type name of the exception.""" - assert ( - self._excinfo is not None - ), ".typename can only be used after the context manager exits" - return self.type.__name__ - - @property - def traceback(self) -> Traceback: - """The traceback.""" - if self._traceback is None: - self._traceback = Traceback(self.tb, excinfo=ref(self)) - return self._traceback - - @traceback.setter - def traceback(self, value: Traceback) -> None: - self._traceback = value - - def __repr__(self) -> str: - if self._excinfo is None: - return "<ExceptionInfo for raises contextmanager>" - return "<{} {} tblen={}>".format( - self.__class__.__name__, saferepr(self._excinfo[1]), len(self.traceback) - ) - - def exconly(self, tryshort: bool = False) -> str: - """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). +_E = TypeVar("_E", bound=BaseException, covariant=True) + + +@final +@attr.s(repr=False) +class ExceptionInfo(Generic[_E]): + """Wraps sys.exc_info() objects and offers help for navigating the traceback.""" + + _assert_start_repr = "AssertionError('assert " + + _excinfo = attr.ib(type=Optional[Tuple[Type["_E"], "_E", TracebackType]]) + _striptext = attr.ib(type=str, default="") + _traceback = attr.ib(type=Optional[Traceback], default=None) + + @classmethod + def from_exc_info( + cls, + exc_info: Tuple[Type[_E], _E, TracebackType], + exprinfo: Optional[str] = None, + ) -> "ExceptionInfo[_E]": + """Return an ExceptionInfo for an existing exc_info tuple. + + .. 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__()``. + """ + _striptext = "" + if exprinfo is None and isinstance(exc_info[1], AssertionError): + exprinfo = getattr(exc_info[1], "msg", None) + if exprinfo is None: + exprinfo = saferepr(exc_info[1]) + if exprinfo and exprinfo.startswith(cls._assert_start_repr): + _striptext = "AssertionError: " + + return cls(exc_info, _striptext) + + @classmethod + def from_current( + cls, exprinfo: Optional[str] = None + ) -> "ExceptionInfo[BaseException]": + """Return 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" + assert tup[1] is not None, "no current exception" + assert tup[2] is not None, "no current exception" + exc_info = (tup[0], tup[1], tup[2]) + return ExceptionInfo.from_exc_info(exc_info, exprinfo) + + @classmethod + def for_later(cls) -> "ExceptionInfo[_E]": + """Return an unfilled ExceptionInfo.""" + return cls(None) + + def fill_unfilled(self, exc_info: Tuple[Type[_E], _E, TracebackType]) -> None: + """Fill an unfilled ExceptionInfo created with ``for_later()``.""" + assert self._excinfo is None, "ExceptionInfo was already filled" + self._excinfo = exc_info + + @property + def type(self) -> Type[_E]: + """The exception class.""" + assert ( + self._excinfo is not None + ), ".type can only be used after the context manager exits" + return self._excinfo[0] + + @property + def value(self) -> _E: + """The exception value.""" + assert ( + self._excinfo is not None + ), ".value can only be used after the context manager exits" + return self._excinfo[1] + + @property + def tb(self) -> TracebackType: + """The exception raw traceback.""" + assert ( + self._excinfo is not None + ), ".tb can only be used after the context manager exits" + return self._excinfo[2] + + @property + def typename(self) -> str: + """The type name of the exception.""" + assert ( + self._excinfo is not None + ), ".typename can only be used after the context manager exits" + return self.type.__name__ + + @property + def traceback(self) -> Traceback: + """The traceback.""" + if self._traceback is None: + self._traceback = Traceback(self.tb, excinfo=ref(self)) + return self._traceback + + @traceback.setter + def traceback(self, value: Traceback) -> None: + self._traceback = value + + def __repr__(self) -> str: + if self._excinfo is None: + return "<ExceptionInfo for raises contextmanager>" + return "<{} {} tblen={}>".format( + self.__class__.__name__, saferepr(self._excinfo[1]), len(self.traceback) + ) + + def exconly(self, tryshort: bool = False) -> str: + """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) @@ -572,16 +572,16 @@ class ExceptionInfo(Generic[_E]): text = text[len(self._striptext) :] return text - def errisinstance( - self, exc: Union[Type[BaseException], Tuple[Type[BaseException], ...]] - ) -> bool: - """Return True if the exception is an instance of exc. - - Consider using ``isinstance(excinfo.value, exc)`` instead. - """ + def errisinstance( + self, exc: Union[Type[BaseException], Tuple[Type[BaseException], ...]] + ) -> bool: + """Return True if the exception is an instance of exc. + + Consider using ``isinstance(excinfo.value, exc)`` instead. + """ return isinstance(self.value, exc) - def _getreprcrash(self) -> "ReprFileLocation": + def _getreprcrash(self) -> "ReprFileLocation": exconly = self.exconly(tryshort=True) entry = self.traceback.getcrashentry() path, lineno = entry.frame.code.raw.co_filename, entry.lineno @@ -589,22 +589,22 @@ class ExceptionInfo(Generic[_E]): def getrepr( self, - showlocals: bool = False, - style: "_TracebackStyle" = "long", - abspath: bool = False, - tbfilter: bool = True, - funcargs: bool = False, - truncate_locals: bool = True, - chain: bool = True, - ) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]: - """Return str()able representation of this exception info. + showlocals: bool = False, + style: "_TracebackStyle" = "long", + abspath: bool = False, + tbfilter: bool = True, + funcargs: bool = False, + truncate_locals: bool = True, + chain: bool = True, + ) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]: + """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|value traceback style. + :param str style: + long|short|no|native|value traceback style. :param bool abspath: If paths should be changed to absolute or left unchanged. @@ -619,8 +619,8 @@ class ExceptionInfo(Generic[_E]): :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. + :param bool chain: + If chained exceptions in Python 3 should be shown. .. versionchanged:: 3.9 @@ -647,78 +647,78 @@ class ExceptionInfo(Generic[_E]): ) return fmt.repr_excinfo(self) - def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]": - """Check whether the regular expression `regexp` matches the string - representation of the exception using :func:`python:re.search`. - - If it matches `True` is returned, otherwise an `AssertionError` is raised. + def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]": + """Check whether the regular expression `regexp` matches the string + representation of the exception using :func:`python:re.search`. + + If it matches `True` is returned, otherwise an `AssertionError` is raised. """ __tracebackhide__ = True - msg = "Regex pattern {!r} does not match {!r}." - if regexp == str(self.value): - msg += " Did you mean to `re.escape()` the regex?" - assert re.search(regexp, str(self.value)), msg.format(regexp, str(self.value)) - # Return True to allow for "assert excinfo.match()". + msg = "Regex pattern {!r} does not match {!r}." + if regexp == str(self.value): + msg += " Did you mean to `re.escape()` the regex?" + assert re.search(regexp, str(self.value)), msg.format(regexp, str(self.value)) + # Return True to allow for "assert excinfo.match()". return True @attr.s -class FormattedExcinfo: - """Presenting information about failing Functions and Generators.""" +class FormattedExcinfo: + """Presenting information about failing Functions and Generators.""" # for traceback entries flow_marker = ">" fail_marker = "E" - showlocals = attr.ib(type=bool, default=False) - style = attr.ib(type="_TracebackStyle", default="long") - abspath = attr.ib(type=bool, default=True) - tbfilter = attr.ib(type=bool, default=True) - funcargs = attr.ib(type=bool, default=False) - truncate_locals = attr.ib(type=bool, default=True) - chain = attr.ib(type=bool, default=True) + showlocals = attr.ib(type=bool, default=False) + style = attr.ib(type="_TracebackStyle", default="long") + abspath = attr.ib(type=bool, default=True) + tbfilter = attr.ib(type=bool, default=True) + funcargs = attr.ib(type=bool, default=False) + truncate_locals = attr.ib(type=bool, default=True) + chain = attr.ib(type=bool, default=True) astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False) - def _getindent(self, source: "Source") -> int: - # Figure out indent for the given source. + def _getindent(self, source: "Source") -> int: + # Figure out indent for the given source. try: s = str(source.getstatement(len(source) - 1)) except KeyboardInterrupt: raise - except BaseException: + except BaseException: try: s = str(source[-1]) except KeyboardInterrupt: raise - except BaseException: + except BaseException: return 0 return 4 + (len(s) - len(s.lstrip())) - def _getentrysource(self, entry: TracebackEntry) -> Optional["Source"]: + def _getentrysource(self, entry: TracebackEntry) -> Optional["Source"]: source = entry.getsource(self.astcache) if source is not None: source = source.deindent() return source - def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]: + def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]: if self.funcargs: args = [] for argname, argvalue in entry.frame.getargs(var=True): - args.append((argname, saferepr(argvalue))) + args.append((argname, saferepr(argvalue))) return ReprFuncArgs(args) - return None - - def get_source( - self, - source: Optional["Source"], - line_index: int = -1, - excinfo: Optional[ExceptionInfo[BaseException]] = None, - short: bool = False, - ) -> List[str]: - """Return formatted and marked up source lines.""" + return None + + def get_source( + self, + source: Optional["Source"], + line_index: int = -1, + excinfo: Optional[ExceptionInfo[BaseException]] = None, + short: bool = False, + ) -> List[str]: + """Return formatted and marked up source lines.""" lines = [] if source is None or line_index >= len(source.lines): - source = Source("???") + source = Source("???") line_index = 0 if line_index < 0: line_index += len(source) @@ -736,24 +736,24 @@ class FormattedExcinfo: lines.extend(self.get_exconly(excinfo, indent=indent, markall=True)) return lines - def get_exconly( - self, - excinfo: ExceptionInfo[BaseException], - indent: int = 4, - markall: bool = False, - ) -> List[str]: + def get_exconly( + self, + excinfo: ExceptionInfo[BaseException], + indent: int = 4, + markall: bool = False, + ) -> List[str]: lines = [] - indentstr = " " * indent - # Get the real exception information out. + indentstr = " " * indent + # Get the real exception information out. exlines = excinfo.exconly(tryshort=True).split("\n") - failindent = self.fail_marker + indentstr[1:] + failindent = self.fail_marker + indentstr[1:] for line in exlines: lines.append(failindent + line) if not markall: - failindent = indentstr + failindent = indentstr return lines - def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]: + def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]: if self.showlocals: lines = [] keys = [loc for loc in locals if loc[0] != "@"] @@ -767,32 +767,32 @@ class FormattedExcinfo: # _repr() function, which is only reprlib.Repr in # disguise, so is very configurable. if self.truncate_locals: - str_repr = saferepr(value) + str_repr = saferepr(value) else: - str_repr = safeformat(value) - # if len(str_repr) < 70 or not isinstance(value, (list, tuple, dict)): - lines.append(f"{name:<10} = {str_repr}") + str_repr = safeformat(value) + # if len(str_repr) < 70 or not isinstance(value, (list, tuple, dict)): + lines.append(f"{name:<10} = {str_repr}") # else: # self._line("%-10s =\\" % (name,)) # # XXX # pprint.pprint(value, stream=self.excinfowriter) return ReprLocals(lines) - return None - - def repr_traceback_entry( - self, - entry: TracebackEntry, - excinfo: Optional[ExceptionInfo[BaseException]] = None, - ) -> "ReprEntry": - lines: List[str] = [] - style = entry._repr_style if entry._repr_style is not None else self.style + return None + + def repr_traceback_entry( + self, + entry: TracebackEntry, + excinfo: Optional[ExceptionInfo[BaseException]] = None, + ) -> "ReprEntry": + lines: List[str] = [] + style = entry._repr_style if entry._repr_style is not None else self.style if style in ("short", "long"): - source = self._getentrysource(entry) - if source is None: - source = Source("???") - line_index = 0 - else: - line_index = entry.lineno - entry.getfirstlinesource() + source = self._getentrysource(entry) + if source is None: + source = Source("???") + line_index = 0 + else: + line_index = entry.lineno - entry.getfirstlinesource() short = style == "short" reprargs = self.repr_args(entry) if not short else None s = self.get_source(source, line_index, excinfo, short=short) @@ -802,17 +802,17 @@ class FormattedExcinfo: else: message = excinfo and excinfo.typename or "" path = self._makepath(entry.path) - reprfileloc = ReprFileLocation(path, entry.lineno + 1, message) - localsrepr = self.repr_locals(entry.locals) - return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style) - elif style == "value": - if excinfo: - lines.extend(str(excinfo.value).split("\n")) - return ReprEntry(lines, None, None, None, style) - else: - if excinfo: - lines.extend(self.get_exconly(excinfo, indent=4)) - return ReprEntry(lines, None, None, None, style) + reprfileloc = ReprFileLocation(path, entry.lineno + 1, message) + localsrepr = self.repr_locals(entry.locals) + return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style) + elif style == "value": + if excinfo: + lines.extend(str(excinfo.value).split("\n")) + return ReprEntry(lines, None, None, None, style) + else: + 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: @@ -824,62 +824,62 @@ class FormattedExcinfo: path = np return path - def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback": + def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback": traceback = excinfo.traceback if self.tbfilter: traceback = traceback.filter() - if isinstance(excinfo.value, RecursionError): + if isinstance(excinfo.value, RecursionError): traceback, extraline = self._truncate_recursive_traceback(traceback) else: extraline = None last = traceback[-1] entries = [] - if self.style == "value": - reprentry = self.repr_traceback_entry(last, excinfo) - entries.append(reprentry) - return ReprTraceback(entries, None, style=self.style) - + if self.style == "value": + reprentry = self.repr_traceback_entry(last, excinfo) + entries.append(reprentry) + return ReprTraceback(entries, None, style=self.style) + 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: Traceback - ) -> Tuple[Traceback, Optional[str]]: - """Truncate the given recursive traceback trying to find the starting - point of the recursion. + def _truncate_recursive_traceback( + self, traceback: Traceback + ) -> Tuple[Traceback, Optional[str]]: + """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()``). + 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. + 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: Optional[str] = ( + extraline: Optional[str] = ( "!!! 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=str(e), + exc_msg=str(e), max_frames=max_frames, total=len(traceback), - ) - # Type ignored because adding two instaces of a List subtype - # currently incorrectly has type List instead of the subtype. - traceback = traceback[:max_frames] + traceback[-max_frames:] # type: ignore + ) + # Type ignored because adding two instaces of a List subtype + # currently incorrectly has type List instead of the subtype. + traceback = traceback[:max_frames] + traceback[-max_frames:] # type: ignore else: if recursionindex is not None: extraline = "!!! Recursion detected (same locals & position)" @@ -889,136 +889,136 @@ class FormattedExcinfo: return traceback, extraline - def repr_excinfo( - self, excinfo: ExceptionInfo[BaseException] - ) -> "ExceptionChainRepr": - repr_chain: List[ - Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]] - ] = [] - e: Optional[BaseException] = excinfo.value - excinfo_: Optional[ExceptionInfo[BaseException]] = excinfo - descr = None - seen: Set[int] = set() - while e is not None and id(e) not in seen: - seen.add(id(e)) - if excinfo_: - reprtraceback = self.repr_traceback(excinfo_) - reprcrash: Optional[ReprFileLocation] = ( - excinfo_._getreprcrash() if self.style != "value" else None - ) - 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) - - -@attr.s(eq=False) -class TerminalRepr: - def __str__(self) -> str: + def repr_excinfo( + self, excinfo: ExceptionInfo[BaseException] + ) -> "ExceptionChainRepr": + repr_chain: List[ + Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]] + ] = [] + e: Optional[BaseException] = excinfo.value + excinfo_: Optional[ExceptionInfo[BaseException]] = excinfo + descr = None + seen: Set[int] = set() + while e is not None and id(e) not in seen: + seen.add(id(e)) + if excinfo_: + reprtraceback = self.repr_traceback(excinfo_) + reprcrash: Optional[ReprFileLocation] = ( + excinfo_._getreprcrash() if self.style != "value" else None + ) + 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) + + +@attr.s(eq=False) +class TerminalRepr: + def __str__(self) -> str: # FYI this is called from pytest-xdist's serialization of exception # information. - io = StringIO() - tw = TerminalWriter(file=io) + io = StringIO() + tw = TerminalWriter(file=io) self.toterminal(tw) return io.getvalue().strip() - def __repr__(self) -> str: - return "<{} instance at {:0x}>".format(self.__class__, id(self)) + def __repr__(self) -> str: + return "<{} instance at {:0x}>".format(self.__class__, id(self)) + + def toterminal(self, tw: TerminalWriter) -> None: + raise NotImplementedError() - def toterminal(self, tw: TerminalWriter) -> None: - raise NotImplementedError() - -# This class is abstract -- only subclasses are instantiated. -@attr.s(eq=False) +# This class is abstract -- only subclasses are instantiated. +@attr.s(eq=False) class ExceptionRepr(TerminalRepr): - # Provided by subclasses. - reprcrash: Optional["ReprFileLocation"] - reprtraceback: "ReprTraceback" - - def __attrs_post_init__(self) -> None: - self.sections: List[Tuple[str, str, str]] = [] - - def addsection(self, name: str, content: str, sep: str = "-") -> None: + # Provided by subclasses. + reprcrash: Optional["ReprFileLocation"] + reprtraceback: "ReprTraceback" + + def __attrs_post_init__(self) -> None: + self.sections: List[Tuple[str, str, str]] = [] + + def addsection(self, name: str, content: str, sep: str = "-") -> None: self.sections.append((name, content, sep)) - def toterminal(self, tw: TerminalWriter) -> None: + def toterminal(self, tw: TerminalWriter) -> None: for name, content, sep in self.sections: tw.sep(sep, name) tw.line(content) -@attr.s(eq=False) +@attr.s(eq=False) class ExceptionChainRepr(ExceptionRepr): - chain = attr.ib( - type=Sequence[ - Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]] - ] - ) - - def __attrs_post_init__(self) -> None: - super().__attrs_post_init__() + chain = attr.ib( + type=Sequence[ + Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]] + ] + ) + + def __attrs_post_init__(self) -> None: + super().__attrs_post_init__() # reprcrash and reprtraceback of the outermost (the newest) exception - # in the chain. - self.reprtraceback = self.chain[-1][0] - self.reprcrash = self.chain[-1][1] + # in the chain. + self.reprtraceback = self.chain[-1][0] + self.reprcrash = self.chain[-1][1] - def toterminal(self, tw: TerminalWriter) -> None: + def toterminal(self, tw: TerminalWriter) -> None: for element in self.chain: element[0].toterminal(tw) if element[2] is not None: tw.line("") tw.line(element[2], yellow=True) - super().toterminal(tw) + super().toterminal(tw) -@attr.s(eq=False) +@attr.s(eq=False) class ReprExceptionInfo(ExceptionRepr): - reprtraceback = attr.ib(type="ReprTraceback") - reprcrash = attr.ib(type="ReprFileLocation") + reprtraceback = attr.ib(type="ReprTraceback") + reprcrash = attr.ib(type="ReprFileLocation") - def toterminal(self, tw: TerminalWriter) -> None: + def toterminal(self, tw: TerminalWriter) -> None: self.reprtraceback.toterminal(tw) - super().toterminal(tw) + super().toterminal(tw) -@attr.s(eq=False) +@attr.s(eq=False) class ReprTraceback(TerminalRepr): - reprentries = attr.ib(type=Sequence[Union["ReprEntry", "ReprEntryNative"]]) - extraline = attr.ib(type=Optional[str]) - style = attr.ib(type="_TracebackStyle") - + reprentries = attr.ib(type=Sequence[Union["ReprEntry", "ReprEntryNative"]]) + extraline = attr.ib(type=Optional[str]) + style = attr.ib(type="_TracebackStyle") + entrysep = "_ " - def toterminal(self, tw: TerminalWriter) -> None: - # The entries might have different styles. + def toterminal(self, tw: TerminalWriter) -> None: + # The entries might have different styles. for i, entry in enumerate(self.reprentries): if entry.style == "long": tw.line("") @@ -1037,87 +1037,87 @@ class ReprTraceback(TerminalRepr): class ReprTracebackNative(ReprTraceback): - def __init__(self, tblines: Sequence[str]) -> None: + def __init__(self, tblines: Sequence[str]) -> None: self.style = "native" self.reprentries = [ReprEntryNative(tblines)] self.extraline = None -@attr.s(eq=False) +@attr.s(eq=False) class ReprEntryNative(TerminalRepr): - lines = attr.ib(type=Sequence[str]) - style: "_TracebackStyle" = "native" + lines = attr.ib(type=Sequence[str]) + style: "_TracebackStyle" = "native" - def toterminal(self, tw: TerminalWriter) -> None: + def toterminal(self, tw: TerminalWriter) -> None: tw.write("".join(self.lines)) -@attr.s(eq=False) +@attr.s(eq=False) class ReprEntry(TerminalRepr): - lines = attr.ib(type=Sequence[str]) - reprfuncargs = attr.ib(type=Optional["ReprFuncArgs"]) - reprlocals = attr.ib(type=Optional["ReprLocals"]) - reprfileloc = attr.ib(type=Optional["ReprFileLocation"]) - style = attr.ib(type="_TracebackStyle") - - def _write_entry_lines(self, tw: TerminalWriter) -> None: - """Write the source code portions of a list of traceback entries with syntax highlighting. - - Usually entries are lines like these: - - " x = 1" - "> assert x == 2" - "E assert 1 == 2" - - This function takes care of rendering the "source" portions of it (the lines without - the "E" prefix) using syntax highlighting, taking care to not highlighting the ">" - character, as doing so might break line continuations. - """ - - if not self.lines: - return - - # separate indents and source lines that are not failures: we want to - # highlight the code but not the indentation, which may contain markers - # such as "> assert 0" - fail_marker = f"{FormattedExcinfo.fail_marker} " - indent_size = len(fail_marker) - indents: List[str] = [] - source_lines: List[str] = [] - failure_lines: List[str] = [] - for index, line in enumerate(self.lines): - is_failure_line = line.startswith(fail_marker) - if is_failure_line: - # from this point on all lines are considered part of the failure - failure_lines.extend(self.lines[index:]) - break - else: - if self.style == "value": - source_lines.append(line) - else: - indents.append(line[:indent_size]) - source_lines.append(line[indent_size:]) - - tw._write_source(source_lines, indents) - - # failure lines are always completely red and bold - for line in failure_lines: - tw.line(line, bold=True, red=True) - - def toterminal(self, tw: TerminalWriter) -> None: + lines = attr.ib(type=Sequence[str]) + reprfuncargs = attr.ib(type=Optional["ReprFuncArgs"]) + reprlocals = attr.ib(type=Optional["ReprLocals"]) + reprfileloc = attr.ib(type=Optional["ReprFileLocation"]) + style = attr.ib(type="_TracebackStyle") + + def _write_entry_lines(self, tw: TerminalWriter) -> None: + """Write the source code portions of a list of traceback entries with syntax highlighting. + + Usually entries are lines like these: + + " x = 1" + "> assert x == 2" + "E assert 1 == 2" + + This function takes care of rendering the "source" portions of it (the lines without + the "E" prefix) using syntax highlighting, taking care to not highlighting the ">" + character, as doing so might break line continuations. + """ + + if not self.lines: + return + + # separate indents and source lines that are not failures: we want to + # highlight the code but not the indentation, which may contain markers + # such as "> assert 0" + fail_marker = f"{FormattedExcinfo.fail_marker} " + indent_size = len(fail_marker) + indents: List[str] = [] + source_lines: List[str] = [] + failure_lines: List[str] = [] + for index, line in enumerate(self.lines): + is_failure_line = line.startswith(fail_marker) + if is_failure_line: + # from this point on all lines are considered part of the failure + failure_lines.extend(self.lines[index:]) + break + else: + if self.style == "value": + source_lines.append(line) + else: + indents.append(line[:indent_size]) + source_lines.append(line[indent_size:]) + + tw._write_source(source_lines, indents) + + # failure lines are always completely red and bold + for line in failure_lines: + tw.line(line, bold=True, red=True) + + def toterminal(self, tw: TerminalWriter) -> None: if self.style == "short": - assert self.reprfileloc is not None + assert self.reprfileloc is not None self.reprfileloc.toterminal(tw) - self._write_entry_lines(tw) - if self.reprlocals: - self.reprlocals.toterminal(tw, indent=" " * 8) + self._write_entry_lines(tw) + if self.reprlocals: + self.reprlocals.toterminal(tw, indent=" " * 8) return - + if self.reprfuncargs: self.reprfuncargs.toterminal(tw) - - self._write_entry_lines(tw) - + + self._write_entry_lines(tw) + if self.reprlocals: tw.line("") self.reprlocals.toterminal(tw) @@ -1126,47 +1126,47 @@ class ReprEntry(TerminalRepr): tw.line("") self.reprfileloc.toterminal(tw) - def __str__(self) -> str: - return "{}\n{}\n{}".format( - "\n".join(self.lines), self.reprlocals, self.reprfileloc - ) + def __str__(self) -> str: + return "{}\n{}\n{}".format( + "\n".join(self.lines), self.reprlocals, self.reprfileloc + ) -@attr.s(eq=False) +@attr.s(eq=False) class ReprFileLocation(TerminalRepr): - path = attr.ib(type=str, converter=str) - lineno = attr.ib(type=int) - message = attr.ib(type=str) + path = attr.ib(type=str, converter=str) + lineno = attr.ib(type=int) + message = attr.ib(type=str) - def toterminal(self, tw: TerminalWriter) -> None: - # Filename and lineno output for each entry, using an output format - # that most editors understand. + def toterminal(self, tw: TerminalWriter) -> None: + # Filename and lineno output for each entry, using an output format + # that most editors understand. msg = self.message i = msg.find("\n") if i != -1: msg = msg[:i] tw.write(self.path, bold=True, red=True) - tw.line(f":{self.lineno}: {msg}") + tw.line(f":{self.lineno}: {msg}") -@attr.s(eq=False) +@attr.s(eq=False) class ReprLocals(TerminalRepr): - lines = attr.ib(type=Sequence[str]) + lines = attr.ib(type=Sequence[str]) - def toterminal(self, tw: TerminalWriter, indent="") -> None: + def toterminal(self, tw: TerminalWriter, indent="") -> None: for line in self.lines: - tw.line(indent + line) + tw.line(indent + line) -@attr.s(eq=False) +@attr.s(eq=False) class ReprFuncArgs(TerminalRepr): - args = attr.ib(type=Sequence[Tuple[str, object]]) + args = attr.ib(type=Sequence[Tuple[str, object]]) - def toterminal(self, tw: TerminalWriter) -> None: + def toterminal(self, tw: TerminalWriter) -> None: if self.args: linesofar = "" for name, value in self.args: - ns = f"{name} = {value}" + ns = f"{name} = {value}" if len(ns) + len(linesofar) + 2 > tw.fullwidth: if linesofar: tw.line(linesofar) @@ -1181,79 +1181,79 @@ class ReprFuncArgs(TerminalRepr): tw.line("") -def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]: - """Return source location (path, lineno) for the given object. - - If the source cannot be determined return ("", -1). - - The line number is 0-based. - """ - # xxx let decorators etc specify a sane ordering - # NOTE: this used to be done in _pytest.compat.getfslineno, initially added - # in 6ec13a2b9. It ("place_as") appears to be something very custom. - obj = get_real_func(obj) - if hasattr(obj, "place_as"): - obj = obj.place_as # type: ignore[attr-defined] - +def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]: + """Return source location (path, lineno) for the given object. + + If the source cannot be determined return ("", -1). + + The line number is 0-based. + """ + # xxx let decorators etc specify a sane ordering + # NOTE: this used to be done in _pytest.compat.getfslineno, initially added + # in 6ec13a2b9. It ("place_as") appears to be something very custom. + obj = get_real_func(obj) + if hasattr(obj, "place_as"): + obj = obj.place_as # type: ignore[attr-defined] + try: - code = Code.from_function(obj) - except TypeError: - try: - fn = inspect.getsourcefile(obj) or inspect.getfile(obj) # type: ignore[arg-type] - except TypeError: - return "", -1 - - fspath = fn and py.path.local(fn) or "" - lineno = -1 - if fspath: - try: - _, lineno = findsource(obj) - except OSError: - pass - return fspath, lineno - - return code.path, code.firstlineno - - -# Relative paths that we use to filter traceback entries from appearing to the user; -# see filter_traceback. + code = Code.from_function(obj) + except TypeError: + try: + fn = inspect.getsourcefile(obj) or inspect.getfile(obj) # type: ignore[arg-type] + except TypeError: + return "", -1 + + fspath = fn and py.path.local(fn) or "" + lineno = -1 + if fspath: + try: + _, lineno = findsource(obj) + except OSError: + pass + return fspath, lineno + + return code.path, code.firstlineno + + +# 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. +# for better maintenance. -_PLUGGY_DIR = Path(pluggy.__file__.rstrip("oc")) +_PLUGGY_DIR = Path(pluggy.__file__.rstrip("oc")) # pluggy is either a package or a single module depending on the version -if _PLUGGY_DIR.name == "__init__.py": - _PLUGGY_DIR = _PLUGGY_DIR.parent -_PYTEST_DIR = Path(_pytest.__file__).parent -_PY_DIR = Path(py.__file__).parent +if _PLUGGY_DIR.name == "__init__.py": + _PLUGGY_DIR = _PLUGGY_DIR.parent +_PYTEST_DIR = Path(_pytest.__file__).parent +_PY_DIR = Path(py.__file__).parent + +def filter_traceback(entry: TracebackEntry) -> bool: + """Return True if a TracebackEntry instance should be included in tracebacks. + + We hide traceback entries of: -def filter_traceback(entry: TracebackEntry) -> bool: - """Return True if a TracebackEntry instance should be included in tracebacks. - - We hide traceback entries of: - * 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. + # 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 = Path(entry.path) - - parents = p.parents - if _PLUGGY_DIR in parents: - return False - if _PYTEST_DIR in parents: - return False - if _PY_DIR in parents: - return False - - return True + # also return a str object. See #1133. + p = Path(entry.path) + + parents = p.parents + if _PLUGGY_DIR in parents: + return False + if _PYTEST_DIR in parents: + return False + if _PY_DIR in parents: + return False + + return True diff --git a/contrib/python/pytest/py3/_pytest/_code/source.py b/contrib/python/pytest/py3/_pytest/_code/source.py index 84bac0a8a8..6f54057c0a 100644 --- a/contrib/python/pytest/py3/_pytest/_code/source.py +++ b/contrib/python/pytest/py3/_pytest/_code/source.py @@ -2,58 +2,58 @@ import ast import inspect import textwrap import tokenize -import types +import types import warnings from bisect import bisect_right -from typing import Iterable -from typing import Iterator -from typing import List -from typing import Optional -from typing import overload -from typing import Tuple -from typing import Union +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Optional +from typing import overload +from typing import Tuple +from typing import Union -class Source: - """An immutable object holding a source code fragment. +class Source: + """An immutable object holding a source code fragment. - When using Source(...), the source lines are deindented. + When using Source(...), the source lines are deindented. """ - def __init__(self, obj: object = None) -> None: - if not obj: - self.lines: List[str] = [] - elif isinstance(obj, Source): - self.lines = obj.lines - elif isinstance(obj, (tuple, list)): - self.lines = deindent(x.rstrip("\n") for x in obj) - elif isinstance(obj, str): - self.lines = deindent(obj.split("\n")) - else: - try: - rawcode = getrawcode(obj) - src = inspect.getsource(rawcode) - except TypeError: - src = inspect.getsource(obj) # type: ignore[arg-type] - self.lines = deindent(src.split("\n")) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, Source): - return NotImplemented - return self.lines == other.lines - - # Ignore type because of https://github.com/python/mypy/issues/4266. - __hash__ = None # type: ignore - - @overload - def __getitem__(self, key: int) -> str: - ... - - @overload - def __getitem__(self, key: slice) -> "Source": - ... - - def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]: + def __init__(self, obj: object = None) -> None: + if not obj: + self.lines: List[str] = [] + elif isinstance(obj, Source): + self.lines = obj.lines + elif isinstance(obj, (tuple, list)): + self.lines = deindent(x.rstrip("\n") for x in obj) + elif isinstance(obj, str): + self.lines = deindent(obj.split("\n")) + else: + try: + rawcode = getrawcode(obj) + src = inspect.getsource(rawcode) + except TypeError: + src = inspect.getsource(obj) # type: ignore[arg-type] + self.lines = deindent(src.split("\n")) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Source): + return NotImplemented + return self.lines == other.lines + + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore + + @overload + def __getitem__(self, key: int) -> str: + ... + + @overload + def __getitem__(self, key: slice) -> "Source": + ... + + def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]: if isinstance(key, int): return self.lines[key] else: @@ -63,14 +63,14 @@ class Source: newsource.lines = self.lines[key.start : key.stop] return newsource - def __iter__(self) -> Iterator[str]: - return iter(self.lines) - - def __len__(self) -> int: + def __iter__(self) -> Iterator[str]: + return iter(self.lines) + + def __len__(self) -> int: return len(self.lines) - def strip(self) -> "Source": - """Return new Source object with trailing and leading blank lines removed.""" + def strip(self) -> "Source": + """Return new Source object with trailing and leading blank lines removed.""" start, end = 0, len(self) while start < end and not self.lines[start].strip(): start += 1 @@ -80,80 +80,80 @@ class Source: source.lines[:] = self.lines[start:end] return source - def indent(self, indent: str = " " * 4) -> "Source": - """Return a copy of the source object with all lines indented by the - given indent-string.""" + def indent(self, indent: str = " " * 4) -> "Source": + """Return a copy of the source object with all lines indented by the + given indent-string.""" newsource = Source() newsource.lines = [(indent + line) for line in self.lines] return newsource - def getstatement(self, lineno: int) -> "Source": - """Return Source statement which contains the given linenumber - (counted from 0).""" + def getstatement(self, lineno: int) -> "Source": + """Return Source statement which contains the given linenumber + (counted from 0).""" start, end = self.getstatementrange(lineno) return self[start:end] - def getstatementrange(self, lineno: int) -> Tuple[int, int]: - """Return (start, end) tuple which spans the minimal statement region - which containing the given lineno.""" + def getstatementrange(self, lineno: int) -> Tuple[int, int]: + """Return (start, end) tuple which spans the minimal statement region + which containing the given lineno.""" if not (0 <= lineno < len(self)): raise IndexError("lineno out of range") ast, start, end = getstatementrange_ast(lineno, self) return start, end - def deindent(self) -> "Source": - """Return a new Source object deindented.""" + def deindent(self) -> "Source": + """Return a new Source object deindented.""" newsource = Source() newsource.lines[:] = deindent(self.lines) return newsource - def __str__(self) -> str: + def __str__(self) -> str: return "\n".join(self.lines) - + # # helper functions # -def findsource(obj) -> Tuple[Optional[Source], int]: +def findsource(obj) -> Tuple[Optional[Source], int]: try: sourcelines, lineno = inspect.findsource(obj) - except Exception: + except Exception: return None, -1 source = Source() source.lines = [line.rstrip() for line in sourcelines] return source, lineno -def getrawcode(obj: object, trycall: bool = True) -> types.CodeType: - """Return code object for given function.""" +def getrawcode(obj: object, trycall: bool = True) -> types.CodeType: + """Return code object for given function.""" try: - return obj.__code__ # type: ignore[attr-defined,no-any-return] - except AttributeError: - pass - if trycall: - call = getattr(obj, "__call__", None) - if call and not isinstance(obj, type): - return getrawcode(call, trycall=False) - raise TypeError(f"could not get code object for {obj!r}") + return obj.__code__ # type: ignore[attr-defined,no-any-return] + except AttributeError: + pass + if trycall: + call = getattr(obj, "__call__", None) + if call and not isinstance(obj, type): + return getrawcode(call, trycall=False) + raise TypeError(f"could not get code object for {obj!r}") -def deindent(lines: Iterable[str]) -> List[str]: +def deindent(lines: Iterable[str]) -> List[str]: return textwrap.dedent("\n".join(lines)).splitlines() -def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[int]]: - # Flatten all statements and except handlers into one lineno-list. - # AST's line numbers start indexing at 1. - values: List[int] = [] +def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[int]]: + # Flatten all statements and except handlers into one lineno-list. + # AST's line numbers start indexing at 1. + values: List[int] = [] for x in ast.walk(node): if isinstance(x, (ast.stmt, ast.ExceptHandler)): values.append(x.lineno - 1) for name in ("finalbody", "orelse"): - val: Optional[List[ast.stmt]] = getattr(x, name, None) + val: Optional[List[ast.stmt]] = getattr(x, name, None) if val: - # Treat the finally/orelse part as its own statement. + # Treat the finally/orelse part as its own statement. values.append(val[0].lineno - 1 - 1) values.sort() insert_index = bisect_right(values, lineno) @@ -165,22 +165,22 @@ def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[i return start, end -def getstatementrange_ast( - lineno: int, - source: Source, - assertion: bool = False, - astnode: Optional[ast.AST] = None, -) -> Tuple[ast.AST, int, int]: +def getstatementrange_ast( + lineno: int, + source: Source, + assertion: bool = False, + astnode: Optional[ast.AST] = None, +) -> Tuple[ast.AST, int, int]: if astnode is None: content = str(source) # See #4260: - # Don't produce duplicate warnings when compiling source to find AST. + # Don't produce duplicate warnings when compiling source to find AST. with warnings.catch_warnings(): warnings.simplefilter("ignore") - astnode = ast.parse(content, "source", "exec") + astnode = ast.parse(content, "source", "exec") start, end = get_statement_startend2(lineno, astnode) - # We need to correct the end: + # We need to correct the end: # - ast-parsing strips comments # - there might be empty lines # - we might have lesser indented code blocks at the end @@ -188,10 +188,10 @@ def getstatementrange_ast( end = len(source.lines) if end > start + 1: - # Make sure we don't span differently indented code blocks - # by using the BlockFinder helper used which inspect.getsource() uses itself. + # Make sure we don't span differently indented code blocks + # by using the BlockFinder helper used which inspect.getsource() uses itself. block_finder = inspect.BlockFinder() - # If we start with an indented line, put blockfinder to "started" mode. + # If we start with an indented line, put blockfinder to "started" mode. block_finder.started = source.lines[start][0].isspace() it = ((x + "\n") for x in source.lines[start:end]) try: @@ -202,7 +202,7 @@ def getstatementrange_ast( except Exception: pass - # The end might still point to a comment or empty line, correct it. + # The end might still point to a comment or empty line, correct it. while end: line = source.lines[end - 1].lstrip() if line.startswith("#") or not line: |