ENH: Figure out where to go

This commit is contained in:
Eric Larson 2024-07-02 18:25:59 -04:00
parent 3efcff8bd1
commit 9733d57bed
4 changed files with 46 additions and 9 deletions

View File

@ -11,7 +11,10 @@ in case of warnings which need to format their messages.
from __future__ import annotations
import inspect
from pathlib import Path
from warnings import warn
from warnings import warn_explicit
from _pytest.warning_types import PytestDeprecationWarning
from _pytest.warning_types import PytestRemovedIn9Warning
@ -89,3 +92,25 @@ MARKED_FIXTURE = PytestRemovedIn9Warning(
def check_ispytest(ispytest: bool) -> None:
if not ispytest:
warn(PRIVATE, stacklevel=3)
def _warn_auto_stacklevel(message, category=UserWarning):
"""Emit a warning with trace outside the pytest namespace."""
root_dir = Path(__file__).parents[1]
frame = inspect.currentframe()
fname, lineno = "unknown", 0
while frame:
fname = frame.f_code.co_filename
lineno = frame.f_lineno
if fname and root_dir not in Path(fname).parents:
break
frame = frame.f_back
del frame
warn_explicit(
message,
category,
filename=fname,
lineno=lineno,
module="pytest",
registry=globals().get("__warningregistry__", {}),
)

View File

@ -60,6 +60,7 @@ from _pytest.config.argparsing import Parser
from _pytest.deprecated import check_ispytest
from _pytest.deprecated import MARKED_FIXTURE
from _pytest.deprecated import YIELD_FIXTURE
from _pytest.deprecated import _warn_auto_stacklevel
from _pytest.main import Session
from _pytest.mark import Mark
from _pytest.mark import ParameterSet
@ -1192,7 +1193,7 @@ class FixtureFunctionMarker:
)
if hasattr(function, "pytestmark"):
warnings.warn(MARKED_FIXTURE, stacklevel=4)
_warn_auto_stacklevel(MARKED_FIXTURE)
function = wrap_function_to_error_out_if_called_directly(function, self)
@ -1322,7 +1323,7 @@ def yield_fixture(
.. deprecated:: 3.0
Use :py:func:`pytest.fixture` directly instead.
"""
warnings.warn(YIELD_FIXTURE, stacklevel=2)
_warn_auto_stacklevel(YIELD_FIXTURE)
return fixture(
fixture_function,
*args,

View File

@ -18,7 +18,6 @@ from typing import Sequence
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
import warnings
from .._code import getfslineno
from ..compat import ascii_escaped
@ -27,6 +26,7 @@ from ..compat import NotSetType
from _pytest.config import Config
from _pytest.deprecated import check_ispytest
from _pytest.deprecated import MARKED_FIXTURE
from _pytest.deprecated import _warn_auto_stacklevel
from _pytest.outcomes import fail
from _pytest.scope import _ScopeName
from _pytest.warning_types import PytestUnknownMarkWarning
@ -353,7 +353,7 @@ class MarkDecorator:
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)
@ -408,7 +408,7 @@ def normalize_mark_list(
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.
@ -418,7 +418,7 @@ def store_mark(obj, mark: Mark, *, stacklevel: int = 2) -> None:
from ..fixtures import getfixturemarker
if getfixturemarker(obj) is not None:
warnings.warn(MARKED_FIXTURE, stacklevel=stacklevel)
_warn_auto_stacklevel(MARKED_FIXTURE)
# Always reassign name to avoid updating pytestmark in a reference that
# was only borrowed.
@ -543,12 +543,11 @@ class MarkGenerator:
__tracebackhide__ = True
fail(f"Unknown '{name}' mark, did you mean 'parametrize'?")
warnings.warn(
_warn_auto_stacklevel(
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)

View File

@ -59,7 +59,7 @@ def test_hookimpl_via_function_attributes_are_deprecated():
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
@ -188,6 +188,18 @@ def test_fixture_disallow_on_marked_functions():
# 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 foo():
raise NotImplementedError()
assert len(record) == 1
assert record[0].filename == __file__
def test_fixture_disallow_marks_on_fixtures():
"""Test that applying a mark to a fixture warns (#3364)."""