Merge pull request #3044 from RonnyPfannschmidt/parameterset-empty-enable-xfail
empty parameterset - enable opt to xfail
This commit is contained in:
		
						commit
						b3247c1d03
					
				|  | @ -13,6 +13,8 @@ from _pytest.config import UsageError | ||||||
| from .deprecated import MARK_PARAMETERSET_UNPACKING | from .deprecated import MARK_PARAMETERSET_UNPACKING | ||||||
| from .compat import NOTSET, getfslineno | from .compat import NOTSET, getfslineno | ||||||
| 
 | 
 | ||||||
|  | EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark" | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def alias(name, warning=None): | def alias(name, warning=None): | ||||||
|     getter = attrgetter(name) |     getter = attrgetter(name) | ||||||
|  | @ -73,7 +75,7 @@ class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')): | ||||||
|         return cls(argval, marks=newmarks, id=None) |         return cls(argval, marks=newmarks, id=None) | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def _for_parameterize(cls, argnames, argvalues, function): |     def _for_parameterize(cls, argnames, argvalues, function, config): | ||||||
|         if not isinstance(argnames, (tuple, list)): |         if not isinstance(argnames, (tuple, list)): | ||||||
|             argnames = [x.strip() for x in argnames.split(",") if x.strip()] |             argnames = [x.strip() for x in argnames.split(",") if x.strip()] | ||||||
|             force_tuple = len(argnames) == 1 |             force_tuple = len(argnames) == 1 | ||||||
|  | @ -85,10 +87,7 @@ class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')): | ||||||
|         del argvalues |         del argvalues | ||||||
| 
 | 
 | ||||||
|         if not parameters: |         if not parameters: | ||||||
|             fs, lineno = getfslineno(function) |             mark = get_empty_parameterset_mark(config, argnames, function) | ||||||
|             reason = "got empty parameter set %r, function %s at %s:%d" % ( |  | ||||||
|                 argnames, function.__name__, fs, lineno) |  | ||||||
|             mark = MARK_GEN.skip(reason=reason) |  | ||||||
|             parameters.append(ParameterSet( |             parameters.append(ParameterSet( | ||||||
|                 values=(NOTSET,) * len(argnames), |                 values=(NOTSET,) * len(argnames), | ||||||
|                 marks=[mark], |                 marks=[mark], | ||||||
|  | @ -97,6 +96,20 @@ class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')): | ||||||
|         return argnames, parameters |         return argnames, parameters | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def get_empty_parameterset_mark(config, argnames, function): | ||||||
|  |     requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION) | ||||||
|  |     if requested_mark in ('', None, 'skip'): | ||||||
|  |         mark = MARK_GEN.skip | ||||||
|  |     elif requested_mark == 'xfail': | ||||||
|  |         mark = MARK_GEN.xfail(run=False) | ||||||
|  |     else: | ||||||
|  |         raise LookupError(requested_mark) | ||||||
|  |     fs, lineno = getfslineno(function) | ||||||
|  |     reason = "got empty parameter set %r, function %s at %s:%d" % ( | ||||||
|  |         argnames, function.__name__, fs, lineno) | ||||||
|  |     return mark(reason=reason) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class MarkerError(Exception): | class MarkerError(Exception): | ||||||
| 
 | 
 | ||||||
|     """Error in use of a pytest marker/attribute.""" |     """Error in use of a pytest marker/attribute.""" | ||||||
|  | @ -136,6 +149,9 @@ def pytest_addoption(parser): | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     parser.addini("markers", "markers for test functions", 'linelist') |     parser.addini("markers", "markers for test functions", 'linelist') | ||||||
|  |     parser.addini( | ||||||
|  |         EMPTY_PARAMETERSET_OPTION, | ||||||
|  |         "default marker for empty parametersets") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def pytest_cmdline_main(config): | def pytest_cmdline_main(config): | ||||||
|  | @ -279,6 +295,13 @@ def pytest_configure(config): | ||||||
|     if config.option.strict: |     if config.option.strict: | ||||||
|         MARK_GEN._config = config |         MARK_GEN._config = config | ||||||
| 
 | 
 | ||||||
|  |     empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION) | ||||||
|  | 
 | ||||||
|  |     if empty_parameterset not in ('skip', 'xfail', None, ''): | ||||||
|  |         raise UsageError( | ||||||
|  |             "{!s} must be one of skip and xfail," | ||||||
|  |             " but it is {!r}".format(EMPTY_PARAMETERSET_OPTION, empty_parameterset)) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def pytest_unconfigure(config): | def pytest_unconfigure(config): | ||||||
|     MARK_GEN._config = getattr(config, '_old_mark_config', None) |     MARK_GEN._config = getattr(config, '_old_mark_config', None) | ||||||
|  |  | ||||||
|  | @ -786,7 +786,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): | ||||||
|         from _pytest.mark import ParameterSet |         from _pytest.mark import ParameterSet | ||||||
|         from py.io import saferepr |         from py.io import saferepr | ||||||
|         argnames, parameters = ParameterSet._for_parameterize( |         argnames, parameters = ParameterSet._for_parameterize( | ||||||
|             argnames, argvalues, self.function) |             argnames, argvalues, self.function, self.config) | ||||||
|         del argvalues |         del argvalues | ||||||
| 
 | 
 | ||||||
|         if scope is None: |         if scope is None: | ||||||
|  |  | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | Introduce ``empty_parameter_set_mark`` ini option to select which mark to apply when ``@pytest.mark.parametrize`` is given an empty set of parameters. Valid options are ``skip`` (default) and ``xfail``. Note that it is planned to change the default to ``xfail`` in future releases as this is considered less error prone. | ||||||
|  | @ -346,3 +346,28 @@ passed multiple times. The expected format is ``name=value``. For example:: | ||||||
|         # content of pytest.ini |         # content of pytest.ini | ||||||
|         [pytest] |         [pytest] | ||||||
|         console_output_style = classic |         console_output_style = classic | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .. confval:: empty_parameter_set_mark | ||||||
|  | 
 | ||||||
|  |     .. versionadded:: 3.4 | ||||||
|  | 
 | ||||||
|  |     Allows to pick the action for empty parametersets in parameterization | ||||||
|  | 
 | ||||||
|  |     * ``skip`` skips tests with a empty parameterset (default) | ||||||
|  |     * ``xfail`` marks tests with a empty parameterset as xfail(run=False) | ||||||
|  | 
 | ||||||
|  |     .. code-block:: ini | ||||||
|  | 
 | ||||||
|  |       # content of pytest.ini | ||||||
|  |       [pytest] | ||||||
|  |       empty_parameter_set_mark = xfail | ||||||
|  | 
 | ||||||
|  |     .. note:: | ||||||
|  | 
 | ||||||
|  |       The default value of this option is planned to change to ``xfail`` in future releases | ||||||
|  |       as this is considered less error prone, see `#3155`_ for more details. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .. _`#3155`: https://github.com/pytest-dev/pytest/issues/3155 | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ PY3 = sys.version_info >= (3, 0) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestMetafunc(object): | class TestMetafunc(object): | ||||||
|     def Metafunc(self, func): |     def Metafunc(self, func, config=None): | ||||||
|         # the unit tests of this class check if things work correctly |         # the unit tests of this class check if things work correctly | ||||||
|         # on the funcarg level, so we don't need a full blown |         # on the funcarg level, so we don't need a full blown | ||||||
|         # initiliazation |         # initiliazation | ||||||
|  | @ -26,7 +26,7 @@ class TestMetafunc(object): | ||||||
| 
 | 
 | ||||||
|         names = fixtures.getfuncargnames(func) |         names = fixtures.getfuncargnames(func) | ||||||
|         fixtureinfo = FixtureInfo(names) |         fixtureinfo = FixtureInfo(names) | ||||||
|         return python.Metafunc(func, fixtureinfo, None) |         return python.Metafunc(func, fixtureinfo, config) | ||||||
| 
 | 
 | ||||||
|     def test_no_funcargs(self, testdir): |     def test_no_funcargs(self, testdir): | ||||||
|         def function(): |         def function(): | ||||||
|  | @ -156,7 +156,19 @@ class TestMetafunc(object): | ||||||
|     def test_parametrize_empty_list(self): |     def test_parametrize_empty_list(self): | ||||||
|         def func(y): |         def func(y): | ||||||
|             pass |             pass | ||||||
|         metafunc = self.Metafunc(func) | 
 | ||||||
|  |         class MockConfig(object): | ||||||
|  |             def getini(self, name): | ||||||
|  |                 return '' | ||||||
|  | 
 | ||||||
|  |             @property | ||||||
|  |             def hook(self): | ||||||
|  |                 return self | ||||||
|  | 
 | ||||||
|  |             def pytest_make_parametrize_id(self, **kw): | ||||||
|  |                 pass | ||||||
|  | 
 | ||||||
|  |         metafunc = self.Metafunc(func, MockConfig()) | ||||||
|         metafunc.parametrize("y", []) |         metafunc.parametrize("y", []) | ||||||
|         assert 'skip' == metafunc._calls[0].marks[0].name |         assert 'skip' == metafunc._calls[0].marks[0].name | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,10 @@ import os | ||||||
| import sys | import sys | ||||||
| 
 | 
 | ||||||
| import pytest | import pytest | ||||||
| from _pytest.mark import MarkGenerator as Mark, ParameterSet, transfer_markers | from _pytest.mark import ( | ||||||
|  |     MarkGenerator as Mark, ParameterSet, transfer_markers, | ||||||
|  |     EMPTY_PARAMETERSET_OPTION, | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestMark(object): | class TestMark(object): | ||||||
|  | @ -891,3 +894,27 @@ class TestMarkDecorator(object): | ||||||
|     ]) |     ]) | ||||||
|     def test__eq__(self, lhs, rhs, expected): |     def test__eq__(self, lhs, rhs, expected): | ||||||
|         assert (lhs == rhs) == expected |         assert (lhs == rhs) == expected | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @pytest.mark.parametrize('mark', [None, '', 'skip', 'xfail']) | ||||||
|  | def test_parameterset_for_parametrize_marks(testdir, mark): | ||||||
|  |     if mark is not None: | ||||||
|  |         testdir.makeini( | ||||||
|  |             "[pytest]\n{}={}".format(EMPTY_PARAMETERSET_OPTION, mark)) | ||||||
|  | 
 | ||||||
|  |     config = testdir.parseconfig() | ||||||
|  |     from _pytest.mark import pytest_configure, get_empty_parameterset_mark | ||||||
|  |     pytest_configure(config) | ||||||
|  |     result_mark = get_empty_parameterset_mark(config, ['a'], all) | ||||||
|  |     if mark in (None, ''): | ||||||
|  |         # normalize to the requested name | ||||||
|  |         mark = 'skip' | ||||||
|  |     assert result_mark.name == mark | ||||||
|  |     assert result_mark.kwargs['reason'].startswith("got empty parameter set ") | ||||||
|  |     if mark == 'xfail': | ||||||
|  |         assert result_mark.kwargs.get('run') is False | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_parameterset_for_parametrize_bad_markname(testdir): | ||||||
|  |     with pytest.raises(pytest.UsageError): | ||||||
|  |         test_parameterset_for_parametrize_marks(testdir, 'bad') | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue