diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index bc4c467f918..2dd3f4ed5df 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -146,8 +146,7 @@ def pytest_collection_modifyitems(items, config): def pytest_configure(config): config._old_mark_config = MARK_GEN._config - if config.option.strict: - MARK_GEN._config = config + MARK_GEN._config = config empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION) diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index ce9a25421c2..2cdf3d1c133 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -11,6 +11,7 @@ from ..compat import MappingMixin from ..compat import NOTSET from _pytest.outcomes import fail +from _pytest.warning_types import PytestWarning EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark" @@ -283,28 +284,37 @@ def test_function(): on the ``test_function`` object. """ _config = None + _markers = set() def __getattr__(self, name): if name[0] == "_": raise AttributeError("Marker name must NOT start with underscore") + if self._config is not None: - self._check(name) + self._update_markers(name) + if name not in self._markers: + warnings.warn( + "Unknown mark %r. You can register custom marks to avoid this " + "warning, without risking typos that silently break your tests. " + "See https://docs.pytest.org/en/latest/mark.html for more detail.", + PytestWarning, + ) + if self._config.option.strict: + fail("{!r} not a registered marker".format(name), pytrace=False) + return MarkDecorator(Mark(name, (), {})) - def _check(self, name): - try: - if name in self._markers: - return - except AttributeError: - pass - self._markers = values = set() - for line in self._config.getini("markers"): - marker = line.split(":", 1)[0] - marker = marker.rstrip() - x = marker.split("(", 1)[0] - values.add(x) + def _update_markers(self, name): + # We store a set of registered markers as a performance optimisation, + # but more could be added to `self._config` by other plugins at runtime. + # If we see an unknown marker, we therefore update the set and try again! if name not in self._markers: - fail("{!r} not a registered marker".format(name), pytrace=False) + for line in self._config.getini("markers"): + # example lines: "skipif(condition): skip the given test if..." + # or "hypothesis: tests which use Hypothesis", so to get the + # marker name we we split on both `:` and `(`. + marker = line.split(":")[0].split("(")[0].strip() + self._markers.add(marker) MARK_GEN = MarkGenerator()