Merge pull request #4935 from Zac-HD/warn-unknown-marks
Emit warning on unknown marks via decorator
This commit is contained in:
		
						commit
						1c9dcf1f39
					
				|  | @ -0,0 +1,2 @@ | ||||||
|  | A warning is now emitted when unknown marks are used as a decorator. | ||||||
|  | This is often due to a typo, which can lead to silently broken tests. | ||||||
|  | @ -31,7 +31,10 @@ which also serve as documentation. | ||||||
| Raising errors on unknown marks | Raising errors on unknown marks | ||||||
| ------------------------------- | ------------------------------- | ||||||
| 
 | 
 | ||||||
| Marks can be registered in ``pytest.ini`` like this: | Unknown marks applied with the ``@pytest.mark.name_of_the_mark`` decorator | ||||||
|  | will always emit a warning, in order to avoid silently doing something | ||||||
|  | surprising due to mis-typed names.  You can disable the warning for custom | ||||||
|  | marks by registering them in ``pytest.ini`` like this: | ||||||
| 
 | 
 | ||||||
| .. code-block:: ini | .. code-block:: ini | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -147,8 +147,7 @@ def pytest_collection_modifyitems(items, config): | ||||||
| 
 | 
 | ||||||
| def pytest_configure(config): | def pytest_configure(config): | ||||||
|     config._old_mark_config = MARK_GEN._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) |     empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ from ..compat import getfslineno | ||||||
| from ..compat import MappingMixin | from ..compat import MappingMixin | ||||||
| from ..compat import NOTSET | from ..compat import NOTSET | ||||||
| from _pytest.outcomes import fail | from _pytest.outcomes import fail | ||||||
|  | from _pytest.warning_types import UnknownMarkWarning | ||||||
| 
 | 
 | ||||||
| EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark" | EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark" | ||||||
| 
 | 
 | ||||||
|  | @ -283,28 +284,38 @@ class MarkGenerator(object): | ||||||
|     on the ``test_function`` object. """ |     on the ``test_function`` object. """ | ||||||
| 
 | 
 | ||||||
|     _config = None |     _config = None | ||||||
|  |     _markers = set() | ||||||
| 
 | 
 | ||||||
|     def __getattr__(self, name): |     def __getattr__(self, name): | ||||||
|         if name[0] == "_": |         if name[0] == "_": | ||||||
|             raise AttributeError("Marker name must NOT start with underscore") |             raise AttributeError("Marker name must NOT start with underscore") | ||||||
|         if self._config is not None: |  | ||||||
|             self._check(name) |  | ||||||
|         return MarkDecorator(Mark(name, (), {})) |  | ||||||
| 
 | 
 | ||||||
|     def _check(self, name): |         if self._config is not None: | ||||||
|         try: |             # We store a set of markers as a performance optimisation - if a mark | ||||||
|             if name in self._markers: |             # name is in the set we definitely know it, but a mark may be known and | ||||||
|                 return |             # not in the set.  We therefore start by updating the set! | ||||||
|         except AttributeError: |             if name not in self._markers: | ||||||
|             pass |                 for line in self._config.getini("markers"): | ||||||
|         self._markers = values = set() |                     # example lines: "skipif(condition): skip the given test if..." | ||||||
|         for line in self._config.getini("markers"): |                     # or "hypothesis: tests which use Hypothesis", so to get the | ||||||
|             marker = line.split(":", 1)[0] |                     # marker name we we split on both `:` and `(`. | ||||||
|             marker = marker.rstrip() |                     marker = line.split(":")[0].split("(")[0].strip() | ||||||
|             x = marker.split("(", 1)[0] |                     self._markers.add(marker) | ||||||
|             values.add(x) | 
 | ||||||
|         if name not in self._markers: |             # If the name is not in the set of known marks after updating, | ||||||
|             fail("{!r} not a registered marker".format(name), pytrace=False) |             # then it really is time to issue a warning or an error. | ||||||
|  |             if name not in self._markers: | ||||||
|  |                 if self._config.option.strict: | ||||||
|  |                     fail("{!r} not a registered marker".format(name), pytrace=False) | ||||||
|  |                 else: | ||||||
|  |                     warnings.warn( | ||||||
|  |                         "Unknown pytest.mark.%s - is this a typo?  You can register " | ||||||
|  |                         "custom marks to avoid this warning - for details, see " | ||||||
|  |                         "https://docs.pytest.org/en/latest/mark.html" % name, | ||||||
|  |                         UnknownMarkWarning, | ||||||
|  |                     ) | ||||||
|  | 
 | ||||||
|  |         return MarkDecorator(Mark(name, (), {})) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| MARK_GEN = MarkGenerator() | MARK_GEN = MarkGenerator() | ||||||
|  |  | ||||||
|  | @ -9,6 +9,15 @@ class PytestWarning(UserWarning): | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class UnknownMarkWarning(PytestWarning): | ||||||
|  |     """ | ||||||
|  |     Bases: :class:`PytestWarning`. | ||||||
|  | 
 | ||||||
|  |     Warning emitted on use of unknown markers. | ||||||
|  |     See https://docs.pytest.org/en/latest/mark.html for details. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class PytestDeprecationWarning(PytestWarning, DeprecationWarning): | class PytestDeprecationWarning(PytestWarning, DeprecationWarning): | ||||||
|     """ |     """ | ||||||
|     Bases: :class:`pytest.PytestWarning`, :class:`DeprecationWarning`. |     Bases: :class:`pytest.PytestWarning`, :class:`DeprecationWarning`. | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								tox.ini
								
								
								
								
							
							
						
						
									
										2
									
								
								tox.ini
								
								
								
								
							|  | @ -165,6 +165,8 @@ filterwarnings = | ||||||
|     ignore::pytest.PytestExperimentalApiWarning |     ignore::pytest.PytestExperimentalApiWarning | ||||||
|     # Do not cause SyntaxError for invalid escape sequences in py37. |     # Do not cause SyntaxError for invalid escape sequences in py37. | ||||||
|     default:invalid escape sequence:DeprecationWarning |     default:invalid escape sequence:DeprecationWarning | ||||||
|  |     # ignore use of unregistered marks, because we use many to test the implementation | ||||||
|  |     ignore::_pytest.warning_types.UnknownMarkWarning | ||||||
| pytester_example_dir = testing/example_scripts | pytester_example_dir = testing/example_scripts | ||||||
| markers = | markers = | ||||||
|     issue |     issue | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue