Skip to content

Commit 1c9dcf1

Browse files
authored
Merge pull request #4935 from Zac-HD/warn-unknown-marks
Emit warning on unknown marks via decorator
2 parents 407d74b + cab4069 commit 1c9dcf1

File tree

6 files changed

+45
-19
lines changed

6 files changed

+45
-19
lines changed

changelog/4826.feature.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
A warning is now emitted when unknown marks are used as a decorator.
2+
This is often due to a typo, which can lead to silently broken tests.

doc/en/mark.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ which also serve as documentation.
3131
Raising errors on unknown marks
3232
-------------------------------
3333

34-
Marks can be registered in ``pytest.ini`` like this:
34+
Unknown marks applied with the ``@pytest.mark.name_of_the_mark`` decorator
35+
will always emit a warning, in order to avoid silently doing something
36+
surprising due to mis-typed names. You can disable the warning for custom
37+
marks by registering them in ``pytest.ini`` like this:
3538

3639
.. code-block:: ini
3740

src/_pytest/mark/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,7 @@ def pytest_collection_modifyitems(items, config):
147147

148148
def pytest_configure(config):
149149
config._old_mark_config = MARK_GEN._config
150-
if config.option.strict:
151-
MARK_GEN._config = config
150+
MARK_GEN._config = config
152151

153152
empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION)
154153

src/_pytest/mark/structures.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from ..compat import MappingMixin
1212
from ..compat import NOTSET
1313
from _pytest.outcomes import fail
14+
from _pytest.warning_types import UnknownMarkWarning
1415

1516
EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark"
1617

@@ -283,28 +284,38 @@ def test_function():
283284
on the ``test_function`` object. """
284285

285286
_config = None
287+
_markers = set()
286288

287289
def __getattr__(self, name):
288290
if name[0] == "_":
289291
raise AttributeError("Marker name must NOT start with underscore")
292+
290293
if self._config is not None:
291-
self._check(name)
292-
return MarkDecorator(Mark(name, (), {}))
294+
# We store a set of markers as a performance optimisation - if a mark
295+
# name is in the set we definitely know it, but a mark may be known and
296+
# not in the set. We therefore start by updating the set!
297+
if name not in self._markers:
298+
for line in self._config.getini("markers"):
299+
# example lines: "skipif(condition): skip the given test if..."
300+
# or "hypothesis: tests which use Hypothesis", so to get the
301+
# marker name we we split on both `:` and `(`.
302+
marker = line.split(":")[0].split("(")[0].strip()
303+
self._markers.add(marker)
304+
305+
# If the name is not in the set of known marks after updating,
306+
# then it really is time to issue a warning or an error.
307+
if name not in self._markers:
308+
if self._config.option.strict:
309+
fail("{!r} not a registered marker".format(name), pytrace=False)
310+
else:
311+
warnings.warn(
312+
"Unknown pytest.mark.%s - is this a typo? You can register "
313+
"custom marks to avoid this warning - for details, see "
314+
"https://docs.pytest.org/en/latest/mark.html" % name,
315+
UnknownMarkWarning,
316+
)
293317

294-
def _check(self, name):
295-
try:
296-
if name in self._markers:
297-
return
298-
except AttributeError:
299-
pass
300-
self._markers = values = set()
301-
for line in self._config.getini("markers"):
302-
marker = line.split(":", 1)[0]
303-
marker = marker.rstrip()
304-
x = marker.split("(", 1)[0]
305-
values.add(x)
306-
if name not in self._markers:
307-
fail("{!r} not a registered marker".format(name), pytrace=False)
318+
return MarkDecorator(Mark(name, (), {}))
308319

309320

310321
MARK_GEN = MarkGenerator()

src/_pytest/warning_types.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ class PytestWarning(UserWarning):
99
"""
1010

1111

12+
class UnknownMarkWarning(PytestWarning):
13+
"""
14+
Bases: :class:`PytestWarning`.
15+
16+
Warning emitted on use of unknown markers.
17+
See https://docs.pytest.org/en/latest/mark.html for details.
18+
"""
19+
20+
1221
class PytestDeprecationWarning(PytestWarning, DeprecationWarning):
1322
"""
1423
Bases: :class:`pytest.PytestWarning`, :class:`DeprecationWarning`.

tox.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ filterwarnings =
165165
ignore::pytest.PytestExperimentalApiWarning
166166
# Do not cause SyntaxError for invalid escape sequences in py37.
167167
default:invalid escape sequence:DeprecationWarning
168+
# ignore use of unregistered marks, because we use many to test the implementation
169+
ignore::_pytest.warning_types.UnknownMarkWarning
168170
pytester_example_dir = testing/example_scripts
169171
markers =
170172
issue

0 commit comments

Comments
 (0)