Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: Fix warning stacklevel #12564

Closed
wants to merge 5 commits into from
Closed
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/12564.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix the ``stacklevel`` used when warning about marks used on fixtures.
26 changes: 26 additions & 0 deletions src/_pytest/deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@

from __future__ import annotations

import inspect
from pathlib import Path
from typing import Any
from warnings import warn
from warnings import warn_explicit

from _pytest.warning_types import PytestDeprecationWarning
from _pytest.warning_types import PytestRemovedIn9Warning
Expand Down Expand Up @@ -89,3 +93,25 @@
def check_ispytest(ispytest: bool) -> None:
if not ispytest:
warn(PRIVATE, stacklevel=3)


def _warn_auto_stacklevel(message: Warning | str, category: Any = UserWarning) -> None:
"""Emit a warning with trace outside the pytest namespace."""
root_dir = Path(__file__).parent
frame = inspect.currentframe()
fname, lineno = "unknown", 0

Check warning on line 102 in src/_pytest/deprecated.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/deprecated.py#L100-L102

Added lines #L100 - L102 were not covered by tests
while frame:
fname = frame.f_code.co_filename
lineno = frame.f_lineno

Check warning on line 105 in src/_pytest/deprecated.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/deprecated.py#L104-L105

Added lines #L104 - L105 were not covered by tests
if fname and root_dir not in Path(fname).parents:
break
frame = frame.f_back
del frame
warn_explicit(

Check warning on line 110 in src/_pytest/deprecated.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/deprecated.py#L107-L110

Added lines #L107 - L110 were not covered by tests
message,
category,
filename=fname,
lineno=lineno,
module="pytest",
registry=globals().get("__warningregistry__", {}),
)
6 changes: 3 additions & 3 deletions src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
import warnings

import _pytest
from _pytest import nodes
Expand All @@ -57,6 +56,7 @@
from _pytest.config import Config
from _pytest.config import ExitCode
from _pytest.config.argparsing import Parser
from _pytest.deprecated import _warn_auto_stacklevel
from _pytest.deprecated import check_ispytest
from _pytest.deprecated import MARKED_FIXTURE
from _pytest.deprecated import YIELD_FIXTURE
Expand Down Expand Up @@ -1192,7 +1192,7 @@
)

if hasattr(function, "pytestmark"):
warnings.warn(MARKED_FIXTURE, stacklevel=2)
_warn_auto_stacklevel(MARKED_FIXTURE)

Check warning on line 1195 in src/_pytest/fixtures.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/fixtures.py#L1195

Added line #L1195 was not covered by tests

function = wrap_function_to_error_out_if_called_directly(function, self)

Expand Down Expand Up @@ -1322,7 +1322,7 @@
.. deprecated:: 3.0
Use :py:func:`pytest.fixture` directly instead.
"""
warnings.warn(YIELD_FIXTURE, stacklevel=2)
_warn_auto_stacklevel(YIELD_FIXTURE)

Check warning on line 1325 in src/_pytest/fixtures.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/fixtures.py#L1325

Added line #L1325 was not covered by tests
return fixture(
fixture_function,
*args,
Expand Down
11 changes: 5 additions & 6 deletions src/_pytest/mark/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
import warnings

from .._code import getfslineno
from ..compat import ascii_escaped
from ..compat import NOTSET
from ..compat import NotSetType
from _pytest.config import Config
from _pytest.deprecated import _warn_auto_stacklevel
from _pytest.deprecated import check_ispytest
from _pytest.deprecated import MARKED_FIXTURE
from _pytest.outcomes import fail
Expand Down Expand Up @@ -353,7 +353,7 @@
func = args[0]
is_class = inspect.isclass(func)
if len(args) == 1 and (istestfunc(func) or is_class):
store_mark(func, self.mark, stacklevel=3)
store_mark(func, self.mark)
return func
return self.with_args(*args, **kwargs)

Expand Down Expand Up @@ -408,7 +408,7 @@
yield mark_obj


def store_mark(obj, mark: Mark, *, stacklevel: int = 2) -> None:
def store_mark(obj, mark: Mark) -> None:
"""Store a Mark on an object.

This is used to implement the Mark declarations/decorators correctly.
Expand All @@ -418,7 +418,7 @@
from ..fixtures import getfixturemarker

if getfixturemarker(obj) is not None:
warnings.warn(MARKED_FIXTURE, stacklevel=stacklevel)
_warn_auto_stacklevel(MARKED_FIXTURE)

Check warning on line 421 in src/_pytest/mark/structures.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/mark/structures.py#L421

Added line #L421 was not covered by tests

# Always reassign name to avoid updating pytestmark in a reference that
# was only borrowed.
Expand Down Expand Up @@ -543,12 +543,11 @@
__tracebackhide__ = True
fail(f"Unknown '{name}' mark, did you mean 'parametrize'?")

warnings.warn(
_warn_auto_stacklevel(

Check warning on line 546 in src/_pytest/mark/structures.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/mark/structures.py#L546

Added line #L546 was not covered by tests
f"Unknown pytest.mark.{name} - is this a typo? You can register "
"custom marks to avoid this warning - for details, see "
"https://docs.pytest.org/en/stable/how-to/mark.html",
PytestUnknownMarkWarning,
2,
)

return MarkDecorator(Mark(name, (), {}, _ispytest=True), _ispytest=True)
Expand Down
18 changes: 17 additions & 1 deletion testing/deprecated_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def pytest_runtest_call(self):

with pytest.warns(
PytestDeprecationWarning,
match=r"Please use the pytest.hookimpl\(tryfirst=True\)",
match=r"Please use the pytest\.hookimpl\(tryfirst=True\)",
) as recorder:
pm.register(DeprecatedMarkImplPlugin())
(record,) = recorder
Expand Down Expand Up @@ -185,6 +185,22 @@ def foo():
# from applying @fixture twice
# ValueError("fixture is being applied more than once to the same function")
assert len(record) == 1
# should point to this file
assert record[0].filename == __file__

# Same with a different order
with pytest.warns(
pytest.PytestRemovedIn9Warning,
match=r"Marks applied to fixtures have no effect",
) as record:

@pytest.mark.parametrize("example", ["hello"])
@pytest.fixture
def bar():
raise NotImplementedError()

assert len(record) == 1
assert record[0].filename == __file__


def test_fixture_disallow_marks_on_fixtures():
Expand Down
Loading