Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/14943.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
``ConftestImportFailure`` no longer carries a ``cause`` attribute; the original error is available as ``__cause__`` via the regular exception chain.
23 changes: 8 additions & 15 deletions src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,11 @@ class ExitCode(enum.IntEnum):


class ConftestImportFailure(Exception):
def __init__(
self,
path: pathlib.Path,
*,
cause: Exception,
) -> None:
self.path = path
self.cause = cause
"""A conftest.py raised while being imported.

def __str__(self) -> str:
return f"{type(self.cause).__name__}: {self.cause} (from {self.path})"
The path of the failing conftest is the exception argument; the original
error is chained as ``__cause__``.
"""


class PluginImportFailure(Exception):
Expand Down Expand Up @@ -182,9 +176,8 @@ def _print_import_error(header: str, cause: BaseException, file: TextIO) -> None


def print_conftest_import_error(e: ConftestImportFailure, file: TextIO) -> None:
_print_import_error(
f"ImportError while loading conftest '{e.path}'.", e.cause, file
)
assert e.__cause__ is not None, f"{e!r} must be raised `from` the original error"
_print_import_error(f"ImportError while loading conftest '{e}'.", e.__cause__, file)


def print_plugin_import_error(e: PluginImportFailure, file: TextIO) -> None:
Expand Down Expand Up @@ -793,7 +786,7 @@ def _importconftest(
)
except Exception as e:
assert e.__traceback__ is not None
raise ConftestImportFailure(conftestpath, cause=e) from e
raise ConftestImportFailure(conftestpath) from e

self._check_non_top_pytest_plugins(mod, conftestpath)

Expand Down Expand Up @@ -1796,7 +1789,7 @@ def parse(self, args: list[str], addopts: bool = True) -> None:
# we don't want to prevent --help/--version to work
# so just let it pass and print a warning at the end
self.issue_config_time_warning(
PytestConfigWarning(f"could not load initial conftests: {e.path}"),
PytestConfigWarning(f"could not load initial conftests: {e}"),
stacklevel=2,
)
else:
Expand Down
3 changes: 2 additions & 1 deletion src/_pytest/debugging.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,8 @@ def _postmortem_exc_or_tb(
elif isinstance(excinfo.value, ConftestImportFailure):
# A config.ConftestImportFailure is not useful for post_mortem.
# Use the underlying exception instead:
cause = excinfo.value.cause
cause = excinfo.value.__cause__
assert cause is not None
if get_exc:
return cause

Expand Down
3 changes: 2 additions & 1 deletion src/_pytest/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,8 @@ def _repr_failure_py(
from _pytest.fixtures import FixtureLookupError

if isinstance(excinfo.value, ConftestImportFailure):
excinfo = ExceptionInfo.from_exception(excinfo.value.cause)
assert excinfo.value.__cause__ is not None
excinfo = ExceptionInfo.from_exception(excinfo.value.__cause__)
if isinstance(excinfo.value, fail.Exception):
if not excinfo.value.pytrace:
style = "value"
Expand Down
12 changes: 5 additions & 7 deletions testing/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3122,17 +3122,15 @@ def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_false_positives


def test_conftest_import_error_repr(tmp_path: Path) -> None:
"""`ConftestImportFailure` should use a short error message and readable
path to the failed conftest.py file."""
"""`ConftestImportFailure` carries the failed conftest.py path as its
argument and the original error as its cause."""
path = tmp_path.joinpath("foo/conftest.py")
with pytest.raises(
ConftestImportFailure,
match=re.escape(f"RuntimeError: some error (from {path})"),
):
with pytest.raises(ConftestImportFailure, match=re.escape(str(path))) as excinfo:
try:
raise RuntimeError("some error")
except Exception as exc:
raise ConftestImportFailure(path, cause=exc) from exc
raise ConftestImportFailure(path) from exc
assert str(excinfo.value.__cause__) == "some error"


def test_strtobool() -> None:
Expand Down
Loading