mirror of https://github.com/pytest-dev/pytest.git
Merge pull request #10816 from pytest-dev/backport-10772-to-7.2.x
[7.2.x] Correctly handle tracebackhide for chained exceptions
This commit is contained in:
commit
f530a765a6
|
@ -2,17 +2,17 @@ default_language_version:
|
||||||
python: "3.10"
|
python: "3.10"
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 22.12.0
|
rev: 23.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: [--safe, --quiet]
|
args: [--safe, --quiet]
|
||||||
- repo: https://github.com/asottile/blacken-docs
|
- repo: https://github.com/asottile/blacken-docs
|
||||||
rev: v1.12.1
|
rev: 1.13.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: blacken-docs
|
- id: blacken-docs
|
||||||
additional_dependencies: [black==20.8b1]
|
additional_dependencies: [black==23.1.0]
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.3.0
|
rev: v4.4.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
|
@ -23,7 +23,7 @@ repos:
|
||||||
exclude: _pytest/(debugging|hookspec).py
|
exclude: _pytest/(debugging|hookspec).py
|
||||||
language_version: python3
|
language_version: python3
|
||||||
- repo: https://github.com/PyCQA/autoflake
|
- repo: https://github.com/PyCQA/autoflake
|
||||||
rev: v1.7.6
|
rev: v2.0.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: autoflake
|
- id: autoflake
|
||||||
name: autoflake
|
name: autoflake
|
||||||
|
@ -31,7 +31,7 @@ repos:
|
||||||
language: python
|
language: python
|
||||||
files: \.py$
|
files: \.py$
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
rev: 5.0.4
|
rev: 6.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
language_version: python3
|
language_version: python3
|
||||||
|
@ -39,7 +39,7 @@ repos:
|
||||||
- flake8-typing-imports==1.12.0
|
- flake8-typing-imports==1.12.0
|
||||||
- flake8-docstrings==1.5.0
|
- flake8-docstrings==1.5.0
|
||||||
- repo: https://github.com/asottile/reorder_python_imports
|
- repo: https://github.com/asottile/reorder_python_imports
|
||||||
rev: v3.8.5
|
rev: v3.9.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: reorder-python-imports
|
- id: reorder-python-imports
|
||||||
args: ['--application-directories=.:src', --py37-plus]
|
args: ['--application-directories=.:src', --py37-plus]
|
||||||
|
@ -49,16 +49,16 @@ repos:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py37-plus]
|
args: [--py37-plus]
|
||||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||||
rev: v2.1.0
|
rev: v2.2.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: setup-cfg-fmt
|
- id: setup-cfg-fmt
|
||||||
args: ["--max-py-version=3.11", "--include-version-classifiers"]
|
args: ["--max-py-version=3.11", "--include-version-classifiers"]
|
||||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||||
rev: v1.9.0
|
rev: v1.10.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: python-use-type-annotations
|
- id: python-use-type-annotations
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: v0.982
|
rev: v1.1.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
files: ^(src/|testing/)
|
files: ^(src/|testing/)
|
||||||
|
|
1
AUTHORS
1
AUTHORS
|
@ -126,6 +126,7 @@ Erik M. Bray
|
||||||
Evan Kepner
|
Evan Kepner
|
||||||
Fabien Zarifian
|
Fabien Zarifian
|
||||||
Fabio Zadrozny
|
Fabio Zadrozny
|
||||||
|
Felix Hofstätter
|
||||||
Felix Nieuwenhuizen
|
Felix Nieuwenhuizen
|
||||||
Feng Ma
|
Feng Ma
|
||||||
Florian Bruhin
|
Florian Bruhin
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Correctly handle ``__tracebackhide__`` for chained exceptions.
|
|
@ -1052,7 +1052,7 @@ that are then turned into proper test methods. Example:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def check(x, y):
|
def check(x, y):
|
||||||
assert x ** x == y
|
assert x**x == y
|
||||||
|
|
||||||
|
|
||||||
def test_squared():
|
def test_squared():
|
||||||
|
@ -1067,7 +1067,7 @@ This form of test function doesn't support fixtures properly, and users should s
|
||||||
|
|
||||||
@pytest.mark.parametrize("x, y", [(2, 4), (3, 9)])
|
@pytest.mark.parametrize("x, y", [(2, 4), (3, 9)])
|
||||||
def test_squared(x, y):
|
def test_squared(x, y):
|
||||||
assert x ** x == y
|
assert x**x == y
|
||||||
|
|
||||||
.. _internal classes accessed through node deprecated:
|
.. _internal classes accessed through node deprecated:
|
||||||
|
|
||||||
|
|
|
@ -1237,7 +1237,6 @@ If the data created by the factory requires managing, the fixture can take care
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def make_customer_record():
|
def make_customer_record():
|
||||||
|
|
||||||
created_records = []
|
created_records = []
|
||||||
|
|
||||||
def _make_customer_record(name):
|
def _make_customer_record(name):
|
||||||
|
|
|
@ -135,10 +135,10 @@ This can be done in our test file by defining a class to represent ``r``.
|
||||||
# this is the previous code block example
|
# this is the previous code block example
|
||||||
import app
|
import app
|
||||||
|
|
||||||
|
|
||||||
# custom class to be the mock return value
|
# custom class to be the mock return value
|
||||||
# will override the requests.Response returned from requests.get
|
# will override the requests.Response returned from requests.get
|
||||||
class MockResponse:
|
class MockResponse:
|
||||||
|
|
||||||
# mock json() method always returns a specific testing dictionary
|
# mock json() method always returns a specific testing dictionary
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def json():
|
def json():
|
||||||
|
@ -146,7 +146,6 @@ This can be done in our test file by defining a class to represent ``r``.
|
||||||
|
|
||||||
|
|
||||||
def test_get_json(monkeypatch):
|
def test_get_json(monkeypatch):
|
||||||
|
|
||||||
# Any arguments may be passed and mock_get() will always return our
|
# Any arguments may be passed and mock_get() will always return our
|
||||||
# mocked object, which only has the .json() method.
|
# mocked object, which only has the .json() method.
|
||||||
def mock_get(*args, **kwargs):
|
def mock_get(*args, **kwargs):
|
||||||
|
@ -181,6 +180,7 @@ This mock can be shared across tests using a ``fixture``:
|
||||||
# app.py that includes the get_json() function
|
# app.py that includes the get_json() function
|
||||||
import app
|
import app
|
||||||
|
|
||||||
|
|
||||||
# custom class to be the mock return value of requests.get()
|
# custom class to be the mock return value of requests.get()
|
||||||
class MockResponse:
|
class MockResponse:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -358,7 +358,6 @@ For testing purposes we can patch the ``DEFAULT_CONFIG`` dictionary to specific
|
||||||
|
|
||||||
|
|
||||||
def test_connection(monkeypatch):
|
def test_connection(monkeypatch):
|
||||||
|
|
||||||
# Patch the values of DEFAULT_CONFIG to specific
|
# Patch the values of DEFAULT_CONFIG to specific
|
||||||
# testing values only for this test.
|
# testing values only for this test.
|
||||||
monkeypatch.setitem(app.DEFAULT_CONFIG, "user", "test_user")
|
monkeypatch.setitem(app.DEFAULT_CONFIG, "user", "test_user")
|
||||||
|
@ -383,7 +382,6 @@ You can use the :py:meth:`monkeypatch.delitem <MonkeyPatch.delitem>` to remove v
|
||||||
|
|
||||||
|
|
||||||
def test_missing_user(monkeypatch):
|
def test_missing_user(monkeypatch):
|
||||||
|
|
||||||
# patch the DEFAULT_CONFIG t be missing the 'user' key
|
# patch the DEFAULT_CONFIG t be missing the 'user' key
|
||||||
monkeypatch.delitem(app.DEFAULT_CONFIG, "user", raising=False)
|
monkeypatch.delitem(app.DEFAULT_CONFIG, "user", raising=False)
|
||||||
|
|
||||||
|
@ -404,6 +402,7 @@ separate fixtures for each potential mock and reference them in the needed tests
|
||||||
# app.py with the connection string function
|
# app.py with the connection string function
|
||||||
import app
|
import app
|
||||||
|
|
||||||
|
|
||||||
# all of the mocks are moved into separated fixtures
|
# all of the mocks are moved into separated fixtures
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_test_user(monkeypatch):
|
def mock_test_user(monkeypatch):
|
||||||
|
@ -425,7 +424,6 @@ separate fixtures for each potential mock and reference them in the needed tests
|
||||||
|
|
||||||
# tests reference only the fixture mocks that are needed
|
# tests reference only the fixture mocks that are needed
|
||||||
def test_connection(mock_test_user, mock_test_database):
|
def test_connection(mock_test_user, mock_test_database):
|
||||||
|
|
||||||
expected = "User Id=test_user; Location=test_db;"
|
expected = "User Id=test_user; Location=test_db;"
|
||||||
|
|
||||||
result = app.create_connection_string()
|
result = app.create_connection_string()
|
||||||
|
@ -433,7 +431,6 @@ separate fixtures for each potential mock and reference them in the needed tests
|
||||||
|
|
||||||
|
|
||||||
def test_missing_user(mock_missing_default_user):
|
def test_missing_user(mock_missing_default_user):
|
||||||
|
|
||||||
with pytest.raises(KeyError):
|
with pytest.raises(KeyError):
|
||||||
_ = app.create_connection_string()
|
_ = app.create_connection_string()
|
||||||
|
|
||||||
|
|
|
@ -249,6 +249,7 @@ and use pytest_addoption as follows:
|
||||||
|
|
||||||
# contents of hooks.py
|
# contents of hooks.py
|
||||||
|
|
||||||
|
|
||||||
# Use firstresult=True because we only want one plugin to define this
|
# Use firstresult=True because we only want one plugin to define this
|
||||||
# default value
|
# default value
|
||||||
@hookspec(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
|
|
|
@ -411,13 +411,13 @@ class Traceback(List[TracebackEntry]):
|
||||||
"""
|
"""
|
||||||
return Traceback(filter(fn, self), self._excinfo)
|
return Traceback(filter(fn, self), self._excinfo)
|
||||||
|
|
||||||
def getcrashentry(self) -> TracebackEntry:
|
def getcrashentry(self) -> Optional[TracebackEntry]:
|
||||||
"""Return last non-hidden traceback entry that lead to the exception of a traceback."""
|
"""Return last non-hidden traceback entry that lead to the exception of a traceback."""
|
||||||
for i in range(-1, -len(self) - 1, -1):
|
for i in range(-1, -len(self) - 1, -1):
|
||||||
entry = self[i]
|
entry = self[i]
|
||||||
if not entry.ishidden():
|
if not entry.ishidden():
|
||||||
return entry
|
return entry
|
||||||
return self[-1]
|
return None
|
||||||
|
|
||||||
def recursionindex(self) -> Optional[int]:
|
def recursionindex(self) -> Optional[int]:
|
||||||
"""Return the index of the frame/TracebackEntry where recursion originates if
|
"""Return the index of the frame/TracebackEntry where recursion originates if
|
||||||
|
@ -602,11 +602,13 @@ class ExceptionInfo(Generic[E]):
|
||||||
"""
|
"""
|
||||||
return isinstance(self.value, exc)
|
return isinstance(self.value, exc)
|
||||||
|
|
||||||
def _getreprcrash(self) -> "ReprFileLocation":
|
def _getreprcrash(self) -> Optional["ReprFileLocation"]:
|
||||||
exconly = self.exconly(tryshort=True)
|
exconly = self.exconly(tryshort=True)
|
||||||
entry = self.traceback.getcrashentry()
|
entry = self.traceback.getcrashentry()
|
||||||
path, lineno = entry.frame.code.raw.co_filename, entry.lineno
|
if entry:
|
||||||
return ReprFileLocation(path, lineno + 1, exconly)
|
path, lineno = entry.frame.code.raw.co_filename, entry.lineno
|
||||||
|
return ReprFileLocation(path, lineno + 1, exconly)
|
||||||
|
return None
|
||||||
|
|
||||||
def getrepr(
|
def getrepr(
|
||||||
self,
|
self,
|
||||||
|
@ -942,9 +944,14 @@ class FormattedExcinfo:
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
reprtraceback = self.repr_traceback(excinfo_)
|
reprtraceback = self.repr_traceback(excinfo_)
|
||||||
reprcrash: Optional[ReprFileLocation] = (
|
|
||||||
excinfo_._getreprcrash() if self.style != "value" else None
|
# will be None if all traceback entries are hidden
|
||||||
)
|
reprcrash: Optional[ReprFileLocation] = excinfo_._getreprcrash()
|
||||||
|
if reprcrash:
|
||||||
|
if self.style == "value":
|
||||||
|
repr_chain += [(reprtraceback, None, descr)]
|
||||||
|
else:
|
||||||
|
repr_chain += [(reprtraceback, reprcrash, descr)]
|
||||||
else:
|
else:
|
||||||
# Fallback to native repr if the exception doesn't have a traceback:
|
# Fallback to native repr if the exception doesn't have a traceback:
|
||||||
# ExceptionInfo objects require a full traceback to work.
|
# ExceptionInfo objects require a full traceback to work.
|
||||||
|
@ -952,8 +959,8 @@ class FormattedExcinfo:
|
||||||
traceback.format_exception(type(e), e, None)
|
traceback.format_exception(type(e), e, None)
|
||||||
)
|
)
|
||||||
reprcrash = None
|
reprcrash = None
|
||||||
|
repr_chain += [(reprtraceback, reprcrash, descr)]
|
||||||
|
|
||||||
repr_chain += [(reprtraceback, reprcrash, descr)]
|
|
||||||
if e.__cause__ is not None and self.chain:
|
if e.__cause__ is not None and self.chain:
|
||||||
e = e.__cause__
|
e = e.__cause__
|
||||||
excinfo_ = (
|
excinfo_ = (
|
||||||
|
@ -1037,7 +1044,7 @@ class ExceptionChainRepr(ExceptionRepr):
|
||||||
@attr.s(eq=False, auto_attribs=True)
|
@attr.s(eq=False, auto_attribs=True)
|
||||||
class ReprExceptionInfo(ExceptionRepr):
|
class ReprExceptionInfo(ExceptionRepr):
|
||||||
reprtraceback: "ReprTraceback"
|
reprtraceback: "ReprTraceback"
|
||||||
reprcrash: "ReprFileLocation"
|
reprcrash: Optional["ReprFileLocation"]
|
||||||
|
|
||||||
def toterminal(self, tw: TerminalWriter) -> None:
|
def toterminal(self, tw: TerminalWriter) -> None:
|
||||||
self.reprtraceback.toterminal(tw)
|
self.reprtraceback.toterminal(tw)
|
||||||
|
|
|
@ -24,6 +24,7 @@ from stat import S_ISLNK
|
||||||
from stat import S_ISREG
|
from stat import S_ISREG
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
from typing import cast
|
||||||
from typing import overload
|
from typing import overload
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
@ -146,7 +147,7 @@ class Visitor:
|
||||||
self.fil = fil
|
self.fil = fil
|
||||||
self.ignore = ignore
|
self.ignore = ignore
|
||||||
self.breadthfirst = bf
|
self.breadthfirst = bf
|
||||||
self.optsort = sort and sorted or (lambda x: x)
|
self.optsort = cast(Callable[[Any], Any], sorted) if sort else (lambda x: x)
|
||||||
|
|
||||||
def gen(self, path):
|
def gen(self, path):
|
||||||
try:
|
try:
|
||||||
|
@ -224,7 +225,7 @@ class Stat:
|
||||||
raise NotImplementedError("XXX win32")
|
raise NotImplementedError("XXX win32")
|
||||||
import pwd
|
import pwd
|
||||||
|
|
||||||
entry = error.checked_call(pwd.getpwuid, self.uid)
|
entry = error.checked_call(pwd.getpwuid, self.uid) # type:ignore[attr-defined]
|
||||||
return entry[0]
|
return entry[0]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -234,7 +235,7 @@ class Stat:
|
||||||
raise NotImplementedError("XXX win32")
|
raise NotImplementedError("XXX win32")
|
||||||
import grp
|
import grp
|
||||||
|
|
||||||
entry = error.checked_call(grp.getgrgid, self.gid)
|
entry = error.checked_call(grp.getgrgid, self.gid) # type:ignore[attr-defined]
|
||||||
return entry[0]
|
return entry[0]
|
||||||
|
|
||||||
def isdir(self):
|
def isdir(self):
|
||||||
|
@ -252,7 +253,7 @@ def getuserid(user):
|
||||||
import pwd
|
import pwd
|
||||||
|
|
||||||
if not isinstance(user, int):
|
if not isinstance(user, int):
|
||||||
user = pwd.getpwnam(user)[2]
|
user = pwd.getpwnam(user)[2] # type:ignore[attr-defined]
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@ -260,7 +261,7 @@ def getgroupid(group):
|
||||||
import grp
|
import grp
|
||||||
|
|
||||||
if not isinstance(group, int):
|
if not isinstance(group, int):
|
||||||
group = grp.getgrnam(group)[2]
|
group = grp.getgrnam(group)[2] # type:ignore[attr-defined]
|
||||||
return group
|
return group
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -278,7 +278,6 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
if sys.version_info >= (3, 10):
|
if sys.version_info >= (3, 10):
|
||||||
|
|
||||||
if sys.version_info >= (3, 12):
|
if sys.version_info >= (3, 12):
|
||||||
from importlib.resources.abc import TraversableResources
|
from importlib.resources.abc import TraversableResources
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -253,7 +253,6 @@ class NoCapture:
|
||||||
|
|
||||||
|
|
||||||
class SysCaptureBinary:
|
class SysCaptureBinary:
|
||||||
|
|
||||||
EMPTY_BUFFER = b""
|
EMPTY_BUFFER = b""
|
||||||
|
|
||||||
def __init__(self, fd: int, tmpfile=None, *, tee: bool = False) -> None:
|
def __init__(self, fd: int, tmpfile=None, *, tee: bool = False) -> None:
|
||||||
|
|
|
@ -62,7 +62,6 @@ from _pytest.warning_types import PytestConfigWarning
|
||||||
from _pytest.warning_types import warn_explicit_for
|
from _pytest.warning_types import warn_explicit_for
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
from _pytest._code.code import _TracebackStyle
|
from _pytest._code.code import _TracebackStyle
|
||||||
from _pytest.terminal import TerminalReporter
|
from _pytest.terminal import TerminalReporter
|
||||||
from .argparsing import Argument
|
from .argparsing import Argument
|
||||||
|
@ -1059,7 +1058,6 @@ class Config:
|
||||||
try:
|
try:
|
||||||
self.parse(args)
|
self.parse(args)
|
||||||
except UsageError:
|
except UsageError:
|
||||||
|
|
||||||
# Handle --version and --help here in a minimal fashion.
|
# Handle --version and --help here in a minimal fashion.
|
||||||
# This gets done via helpconfig normally, but its
|
# This gets done via helpconfig normally, but its
|
||||||
# pytest_cmdline_main is not called in case of errors.
|
# pytest_cmdline_main is not called in case of errors.
|
||||||
|
|
|
@ -43,7 +43,6 @@ class PathAwareHookProxy:
|
||||||
|
|
||||||
@_wraps(hook)
|
@_wraps(hook)
|
||||||
def fixed_hook(**kw):
|
def fixed_hook(**kw):
|
||||||
|
|
||||||
path_value: Optional[Path] = kw.pop(path_var, None)
|
path_value: Optional[Path] = kw.pop(path_var, None)
|
||||||
fspath_value: Optional[LEGACY_PATH] = kw.pop(fspath_var, None)
|
fspath_value: Optional[LEGACY_PATH] = kw.pop(fspath_var, None)
|
||||||
if fspath_value is not None:
|
if fspath_value is not None:
|
||||||
|
|
|
@ -531,7 +531,6 @@ class DoctestModule(Module):
|
||||||
if _is_mocked(obj):
|
if _is_mocked(obj):
|
||||||
return
|
return
|
||||||
with _patch_unwrap_mock_aware():
|
with _patch_unwrap_mock_aware():
|
||||||
|
|
||||||
# Type ignored because this is a private function.
|
# Type ignored because this is a private function.
|
||||||
super()._find( # type:ignore[misc]
|
super()._find( # type:ignore[misc]
|
||||||
tests, obj, name, module, source_lines, globs, seen
|
tests, obj, name, module, source_lines, globs, seen
|
||||||
|
|
|
@ -738,7 +738,7 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def pytest_report_header(
|
def pytest_report_header( # type:ignore[empty-body]
|
||||||
config: "Config", start_path: Path, startdir: "LEGACY_PATH"
|
config: "Config", start_path: Path, startdir: "LEGACY_PATH"
|
||||||
) -> Union[str, List[str]]:
|
) -> Union[str, List[str]]:
|
||||||
"""Return a string or list of strings to be displayed as header info for terminal reporting.
|
"""Return a string or list of strings to be displayed as header info for terminal reporting.
|
||||||
|
@ -767,7 +767,7 @@ def pytest_report_header(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def pytest_report_collectionfinish(
|
def pytest_report_collectionfinish( # type:ignore[empty-body]
|
||||||
config: "Config",
|
config: "Config",
|
||||||
start_path: Path,
|
start_path: Path,
|
||||||
startdir: "LEGACY_PATH",
|
startdir: "LEGACY_PATH",
|
||||||
|
@ -800,7 +800,7 @@ def pytest_report_collectionfinish(
|
||||||
|
|
||||||
|
|
||||||
@hookspec(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_report_teststatus(
|
def pytest_report_teststatus( # type:ignore[empty-body]
|
||||||
report: Union["CollectReport", "TestReport"], config: "Config"
|
report: Union["CollectReport", "TestReport"], config: "Config"
|
||||||
) -> Tuple[str, str, Union[str, Mapping[str, bool]]]:
|
) -> Tuple[str, str, Union[str, Mapping[str, bool]]]:
|
||||||
"""Return result-category, shortletter and verbose word for status
|
"""Return result-category, shortletter and verbose word for status
|
||||||
|
@ -880,7 +880,9 @@ def pytest_warning_recorded(
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def pytest_markeval_namespace(config: "Config") -> Dict[str, Any]:
|
def pytest_markeval_namespace( # type:ignore[empty-body]
|
||||||
|
config: "Config",
|
||||||
|
) -> Dict[str, Any]:
|
||||||
"""Called when constructing the globals dictionary used for
|
"""Called when constructing the globals dictionary used for
|
||||||
evaluating string conditions in xfail/skipif markers.
|
evaluating string conditions in xfail/skipif markers.
|
||||||
|
|
||||||
|
|
|
@ -223,7 +223,6 @@ def _resolve_msg_to_reason(
|
||||||
"""
|
"""
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
if msg is not None:
|
if msg is not None:
|
||||||
|
|
||||||
if reason:
|
if reason:
|
||||||
from pytest import UsageError
|
from pytest import UsageError
|
||||||
|
|
||||||
|
|
|
@ -790,7 +790,8 @@ def _call_with_optional_argument(func, arg) -> None:
|
||||||
|
|
||||||
def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> Optional[object]:
|
def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> Optional[object]:
|
||||||
"""Return the attribute from the given object to be used as a setup/teardown
|
"""Return the attribute from the given object to be used as a setup/teardown
|
||||||
xunit-style function, but only if not marked as a fixture to avoid calling it twice."""
|
xunit-style function, but only if not marked as a fixture to avoid calling it twice.
|
||||||
|
"""
|
||||||
for name in names:
|
for name in names:
|
||||||
meth: Optional[object] = getattr(obj, name, None)
|
meth: Optional[object] = getattr(obj, name, None)
|
||||||
if meth is not None and fixtures.getfixturemarker(meth) is None:
|
if meth is not None and fixtures.getfixturemarker(meth) is None:
|
||||||
|
|
|
@ -337,6 +337,10 @@ class TestReport(BaseReport):
|
||||||
elif isinstance(excinfo.value, skip.Exception):
|
elif isinstance(excinfo.value, skip.Exception):
|
||||||
outcome = "skipped"
|
outcome = "skipped"
|
||||||
r = excinfo._getreprcrash()
|
r = excinfo._getreprcrash()
|
||||||
|
if r is None:
|
||||||
|
raise ValueError(
|
||||||
|
"There should always be a traceback entry for skipping a test."
|
||||||
|
)
|
||||||
if excinfo.value._use_item_location:
|
if excinfo.value._use_item_location:
|
||||||
path, line = item.reportinfo()[:2]
|
path, line = item.reportinfo()[:2]
|
||||||
assert line is not None
|
assert line is not None
|
||||||
|
@ -573,7 +577,6 @@ def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
and "reprcrash" in reportdict["longrepr"]
|
and "reprcrash" in reportdict["longrepr"]
|
||||||
and "reprtraceback" in reportdict["longrepr"]
|
and "reprtraceback" in reportdict["longrepr"]
|
||||||
):
|
):
|
||||||
|
|
||||||
reprtraceback = deserialize_repr_traceback(
|
reprtraceback = deserialize_repr_traceback(
|
||||||
reportdict["longrepr"]["reprtraceback"]
|
reportdict["longrepr"]["reprtraceback"]
|
||||||
)
|
)
|
||||||
|
|
|
@ -803,7 +803,7 @@ class TestLocalPath(CommonFSTests):
|
||||||
# depending on how the paths are used), but > 4096 (which is the
|
# depending on how the paths are used), but > 4096 (which is the
|
||||||
# Linux' limitation) - the behaviour of paths with names > 4096 chars
|
# Linux' limitation) - the behaviour of paths with names > 4096 chars
|
||||||
# is undetermined
|
# is undetermined
|
||||||
newfilename = "/test" * 60
|
newfilename = "/test" * 60 # type:ignore[unreachable]
|
||||||
l1 = tmpdir.join(newfilename)
|
l1 = tmpdir.join(newfilename)
|
||||||
l1.ensure(file=True)
|
l1.ensure(file=True)
|
||||||
l1.write("foo")
|
l1.write("foo")
|
||||||
|
@ -1344,8 +1344,8 @@ class TestPOSIXLocalPath:
|
||||||
assert realpath.basename == "file"
|
assert realpath.basename == "file"
|
||||||
|
|
||||||
def test_owner(self, path1, tmpdir):
|
def test_owner(self, path1, tmpdir):
|
||||||
from pwd import getpwuid
|
from pwd import getpwuid # type:ignore[attr-defined]
|
||||||
from grp import getgrgid
|
from grp import getgrgid # type:ignore[attr-defined]
|
||||||
|
|
||||||
stat = path1.stat()
|
stat = path1.stat()
|
||||||
assert stat.path == path1
|
assert stat.path == path1
|
||||||
|
|
|
@ -879,7 +879,6 @@ class TestDurations:
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_calls_show_2(self, pytester: Pytester, mock_timing) -> None:
|
def test_calls_show_2(self, pytester: Pytester, mock_timing) -> None:
|
||||||
|
|
||||||
pytester.makepyfile(self.source)
|
pytester.makepyfile(self.source)
|
||||||
result = pytester.runpytest_inprocess("--durations=2")
|
result = pytester.runpytest_inprocess("--durations=2")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
|
@ -294,6 +294,7 @@ class TestTraceback_f_g_h:
|
||||||
excinfo = pytest.raises(ValueError, f)
|
excinfo = pytest.raises(ValueError, f)
|
||||||
tb = excinfo.traceback
|
tb = excinfo.traceback
|
||||||
entry = tb.getcrashentry()
|
entry = tb.getcrashentry()
|
||||||
|
assert entry is not None
|
||||||
co = _pytest._code.Code.from_function(h)
|
co = _pytest._code.Code.from_function(h)
|
||||||
assert entry.frame.code.path == co.path
|
assert entry.frame.code.path == co.path
|
||||||
assert entry.lineno == co.firstlineno + 1
|
assert entry.lineno == co.firstlineno + 1
|
||||||
|
@ -311,10 +312,7 @@ class TestTraceback_f_g_h:
|
||||||
excinfo = pytest.raises(ValueError, f)
|
excinfo = pytest.raises(ValueError, f)
|
||||||
tb = excinfo.traceback
|
tb = excinfo.traceback
|
||||||
entry = tb.getcrashentry()
|
entry = tb.getcrashentry()
|
||||||
co = _pytest._code.Code.from_function(g)
|
assert entry is None
|
||||||
assert entry.frame.code.path == co.path
|
|
||||||
assert entry.lineno == co.firstlineno + 2
|
|
||||||
assert entry.frame.code.name == "g"
|
|
||||||
|
|
||||||
|
|
||||||
def test_excinfo_exconly():
|
def test_excinfo_exconly():
|
||||||
|
|
|
@ -196,7 +196,6 @@ def mock_timing(monkeypatch: MonkeyPatch):
|
||||||
|
|
||||||
@attr.s
|
@attr.s
|
||||||
class MockTiming:
|
class MockTiming:
|
||||||
|
|
||||||
_current_time = attr.ib(default=1590150050.0)
|
_current_time = attr.ib(default=1590150050.0)
|
||||||
|
|
||||||
def sleep(self, seconds):
|
def sleep(self, seconds):
|
||||||
|
|
|
@ -494,7 +494,6 @@ class TestLastFailed:
|
||||||
def test_lastfailed_collectfailure(
|
def test_lastfailed_collectfailure(
|
||||||
self, pytester: Pytester, monkeypatch: MonkeyPatch
|
self, pytester: Pytester, monkeypatch: MonkeyPatch
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
pytester.makepyfile(
|
pytester.makepyfile(
|
||||||
test_maybe="""
|
test_maybe="""
|
||||||
import os
|
import os
|
||||||
|
|
|
@ -1236,7 +1236,6 @@ class TestDoctestSkips:
|
||||||
|
|
||||||
|
|
||||||
class TestDoctestAutoUseFixtures:
|
class TestDoctestAutoUseFixtures:
|
||||||
|
|
||||||
SCOPES = ["module", "session", "class", "function"]
|
SCOPES = ["module", "session", "class", "function"]
|
||||||
|
|
||||||
def test_doctest_module_session_fixture(self, pytester: Pytester):
|
def test_doctest_module_session_fixture(self, pytester: Pytester):
|
||||||
|
@ -1379,7 +1378,6 @@ class TestDoctestAutoUseFixtures:
|
||||||
|
|
||||||
|
|
||||||
class TestDoctestNamespaceFixture:
|
class TestDoctestNamespaceFixture:
|
||||||
|
|
||||||
SCOPES = ["module", "session", "class", "function"]
|
SCOPES = ["module", "session", "class", "function"]
|
||||||
|
|
||||||
@pytest.mark.parametrize("scope", SCOPES)
|
@pytest.mark.parametrize("scope", SCOPES)
|
||||||
|
|
|
@ -253,7 +253,6 @@ class TestPython:
|
||||||
duration_report: str,
|
duration_report: str,
|
||||||
run_and_parse: RunAndParse,
|
run_and_parse: RunAndParse,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
# mock LogXML.node_reporter so it always sets a known duration to each test report object
|
# mock LogXML.node_reporter so it always sets a known duration to each test report object
|
||||||
original_node_reporter = LogXML.node_reporter
|
original_node_reporter = LogXML.node_reporter
|
||||||
|
|
||||||
|
@ -603,7 +602,6 @@ class TestPython:
|
||||||
node.assert_attr(failures=3, tests=3)
|
node.assert_attr(failures=3, tests=3)
|
||||||
|
|
||||||
for index, char in enumerate("<&'"):
|
for index, char in enumerate("<&'"):
|
||||||
|
|
||||||
tnode = node.find_nth_by_tag("testcase", index)
|
tnode = node.find_nth_by_tag("testcase", index)
|
||||||
tnode.assert_attr(
|
tnode.assert_attr(
|
||||||
classname="test_failure_escape", name="test_func[%s]" % char
|
classname="test_failure_escape", name="test_func[%s]" % char
|
||||||
|
|
|
@ -92,7 +92,7 @@ class TestSetattrWithImportPath:
|
||||||
mp.delattr("os.path.abspath")
|
mp.delattr("os.path.abspath")
|
||||||
assert not hasattr(os.path, "abspath")
|
assert not hasattr(os.path, "abspath")
|
||||||
mp.undo()
|
mp.undo()
|
||||||
assert os.path.abspath
|
assert os.path.abspath # type:ignore[truthy-function]
|
||||||
|
|
||||||
|
|
||||||
def test_delattr() -> None:
|
def test_delattr() -> None:
|
||||||
|
|
|
@ -880,6 +880,7 @@ def test_makereport_getsource_dynamic_code(
|
||||||
def test_store_except_info_on_error() -> None:
|
def test_store_except_info_on_error() -> None:
|
||||||
"""Test that upon test failure, the exception info is stored on
|
"""Test that upon test failure, the exception info is stored on
|
||||||
sys.last_traceback and friends."""
|
sys.last_traceback and friends."""
|
||||||
|
|
||||||
# Simulate item that might raise a specific exception, depending on `raise_error` class var
|
# Simulate item that might raise a specific exception, depending on `raise_error` class var
|
||||||
class ItemMightRaise:
|
class ItemMightRaise:
|
||||||
nodeid = "item_that_raises"
|
nodeid = "item_that_raises"
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
def test_tbh_chained(testdir):
|
||||||
|
"""Ensure chained exceptions whose frames contain "__tracebackhide__" are not shown (#1904)."""
|
||||||
|
p = testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
def f1():
|
||||||
|
__tracebackhide__ = True
|
||||||
|
try:
|
||||||
|
return f1.meh
|
||||||
|
except AttributeError:
|
||||||
|
pytest.fail("fail")
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fix():
|
||||||
|
f1()
|
||||||
|
|
||||||
|
|
||||||
|
def test(fix):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest(str(p))
|
||||||
|
assert "'function' object has no attribute 'meh'" not in result.stdout.str()
|
||||||
|
assert result.ret == 1
|
Loading…
Reference in New Issue