Various cleanups in src/_pytest/python.py (#5599)
Various cleanups in src/_pytest/python.py
This commit is contained in:
		
						commit
						499fda2349
					
				|  | @ -1,11 +1,12 @@ | ||||||
| """ Python test discovery, setup and run of test functions. """ | """ Python test discovery, setup and run of test functions. """ | ||||||
| import collections |  | ||||||
| import enum | import enum | ||||||
| import fnmatch | import fnmatch | ||||||
| import inspect | import inspect | ||||||
| import os | import os | ||||||
| import sys | import sys | ||||||
| import warnings | import warnings | ||||||
|  | from collections import Counter | ||||||
|  | from collections.abc import Sequence | ||||||
| from functools import partial | from functools import partial | ||||||
| from textwrap import dedent | from textwrap import dedent | ||||||
| 
 | 
 | ||||||
|  | @ -240,9 +241,6 @@ class PyobjContext: | ||||||
| class PyobjMixin(PyobjContext): | class PyobjMixin(PyobjContext): | ||||||
|     _ALLOW_MARKERS = True |     _ALLOW_MARKERS = True | ||||||
| 
 | 
 | ||||||
|     def __init__(self, *k, **kw): |  | ||||||
|         super().__init__(*k, **kw) |  | ||||||
| 
 |  | ||||||
|     @property |     @property | ||||||
|     def obj(self): |     def obj(self): | ||||||
|         """Underlying Python object.""" |         """Underlying Python object.""" | ||||||
|  | @ -394,12 +392,8 @@ class PyCollector(PyobjMixin, nodes.Collector): | ||||||
|             methods.append(module.pytest_generate_tests) |             methods.append(module.pytest_generate_tests) | ||||||
|         if hasattr(cls, "pytest_generate_tests"): |         if hasattr(cls, "pytest_generate_tests"): | ||||||
|             methods.append(cls().pytest_generate_tests) |             methods.append(cls().pytest_generate_tests) | ||||||
|         if methods: | 
 | ||||||
|             self.ihook.pytest_generate_tests.call_extra( |         self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc)) | ||||||
|                 methods, dict(metafunc=metafunc) |  | ||||||
|             ) |  | ||||||
|         else: |  | ||||||
|             self.ihook.pytest_generate_tests(metafunc=metafunc) |  | ||||||
| 
 | 
 | ||||||
|         if not metafunc._calls: |         if not metafunc._calls: | ||||||
|             yield Function(name, parent=self, fixtureinfo=fixtureinfo) |             yield Function(name, parent=self, fixtureinfo=fixtureinfo) | ||||||
|  | @ -444,13 +438,12 @@ class Module(nodes.File, PyCollector): | ||||||
|         Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with |         Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with | ||||||
|         other fixtures (#517). |         other fixtures (#517). | ||||||
|         """ |         """ | ||||||
|         setup_module = _get_non_fixture_func(self.obj, "setUpModule") |         setup_module = _get_first_non_fixture_func( | ||||||
|         if setup_module is None: |             self.obj, ("setUpModule", "setup_module") | ||||||
|             setup_module = _get_non_fixture_func(self.obj, "setup_module") |         ) | ||||||
| 
 |         teardown_module = _get_first_non_fixture_func( | ||||||
|         teardown_module = _get_non_fixture_func(self.obj, "tearDownModule") |             self.obj, ("tearDownModule", "teardown_module") | ||||||
|         if teardown_module is None: |         ) | ||||||
|             teardown_module = _get_non_fixture_func(self.obj, "teardown_module") |  | ||||||
| 
 | 
 | ||||||
|         if setup_module is None and teardown_module is None: |         if setup_module is None and teardown_module is None: | ||||||
|             return |             return | ||||||
|  | @ -472,8 +465,10 @@ class Module(nodes.File, PyCollector): | ||||||
|         Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with |         Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with | ||||||
|         other fixtures (#517). |         other fixtures (#517). | ||||||
|         """ |         """ | ||||||
|         setup_function = _get_non_fixture_func(self.obj, "setup_function") |         setup_function = _get_first_non_fixture_func(self.obj, ("setup_function",)) | ||||||
|         teardown_function = _get_non_fixture_func(self.obj, "teardown_function") |         teardown_function = _get_first_non_fixture_func( | ||||||
|  |             self.obj, ("teardown_function",) | ||||||
|  |         ) | ||||||
|         if setup_function is None and teardown_function is None: |         if setup_function is None and teardown_function is None: | ||||||
|             return |             return | ||||||
| 
 | 
 | ||||||
|  | @ -557,15 +552,15 @@ class Package(Module): | ||||||
|     def setup(self): |     def setup(self): | ||||||
|         # not using fixtures to call setup_module here because autouse fixtures |         # not using fixtures to call setup_module here because autouse fixtures | ||||||
|         # from packages are not called automatically (#4085) |         # from packages are not called automatically (#4085) | ||||||
|         setup_module = _get_non_fixture_func(self.obj, "setUpModule") |         setup_module = _get_first_non_fixture_func( | ||||||
|         if setup_module is None: |             self.obj, ("setUpModule", "setup_module") | ||||||
|             setup_module = _get_non_fixture_func(self.obj, "setup_module") |         ) | ||||||
|         if setup_module is not None: |         if setup_module is not None: | ||||||
|             _call_with_optional_argument(setup_module, self.obj) |             _call_with_optional_argument(setup_module, self.obj) | ||||||
| 
 | 
 | ||||||
|         teardown_module = _get_non_fixture_func(self.obj, "tearDownModule") |         teardown_module = _get_first_non_fixture_func( | ||||||
|         if teardown_module is None: |             self.obj, ("tearDownModule", "teardown_module") | ||||||
|             teardown_module = _get_non_fixture_func(self.obj, "teardown_module") |         ) | ||||||
|         if teardown_module is not None: |         if teardown_module is not None: | ||||||
|             func = partial(_call_with_optional_argument, teardown_module, self.obj) |             func = partial(_call_with_optional_argument, teardown_module, self.obj) | ||||||
|             self.addfinalizer(func) |             self.addfinalizer(func) | ||||||
|  | @ -656,27 +651,6 @@ class Package(Module): | ||||||
|                 pkg_prefixes.add(path) |                 pkg_prefixes.add(path) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _get_xunit_setup_teardown(holder, attr_name, param_obj=None): |  | ||||||
|     """ |  | ||||||
|     Return a callable to perform xunit-style setup or teardown if |  | ||||||
|     the function exists in the ``holder`` object. |  | ||||||
|     The ``param_obj`` parameter is the parameter which will be passed to the function |  | ||||||
|     when the callable is called without arguments, defaults to the ``holder`` object. |  | ||||||
|     Return ``None`` if a suitable callable is not found. |  | ||||||
|     """ |  | ||||||
|     # TODO: only needed because of Package! |  | ||||||
|     param_obj = param_obj if param_obj is not None else holder |  | ||||||
|     result = _get_non_fixture_func(holder, attr_name) |  | ||||||
|     if result is not None: |  | ||||||
|         arg_count = result.__code__.co_argcount |  | ||||||
|         if inspect.ismethod(result): |  | ||||||
|             arg_count -= 1 |  | ||||||
|         if arg_count: |  | ||||||
|             return lambda: result(param_obj) |  | ||||||
|         else: |  | ||||||
|             return result |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def _call_with_optional_argument(func, arg): | def _call_with_optional_argument(func, arg): | ||||||
|     """Call the given function with the given argument if func accepts one argument, otherwise |     """Call the given function with the given argument if func accepts one argument, otherwise | ||||||
|     calls func without arguments""" |     calls func without arguments""" | ||||||
|  | @ -689,13 +663,14 @@ def _call_with_optional_argument(func, arg): | ||||||
|         func() |         func() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _get_non_fixture_func(obj, name): | def _get_first_non_fixture_func(obj, names): | ||||||
|     """Return the attribute from the given object to be used as a setup/teardown |     """Return the attribute from the given object to be used as a setup/teardown | ||||||
|     xunit-style function, but only if not marked as a fixture to |     xunit-style function, but only if not marked as a fixture to | ||||||
|     avoid calling it twice. |     avoid calling it twice. | ||||||
|     """ |     """ | ||||||
|  |     for name in names: | ||||||
|         meth = getattr(obj, name, None) |         meth = getattr(obj, name, None) | ||||||
|     if fixtures.getfixturemarker(meth) is None: |         if meth is not None and fixtures.getfixturemarker(meth) is None: | ||||||
|             return meth |             return meth | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -736,7 +711,7 @@ class Class(PyCollector): | ||||||
|         Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with |         Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with | ||||||
|         other fixtures (#517). |         other fixtures (#517). | ||||||
|         """ |         """ | ||||||
|         setup_class = _get_non_fixture_func(self.obj, "setup_class") |         setup_class = _get_first_non_fixture_func(self.obj, ("setup_class",)) | ||||||
|         teardown_class = getattr(self.obj, "teardown_class", None) |         teardown_class = getattr(self.obj, "teardown_class", None) | ||||||
|         if setup_class is None and teardown_class is None: |         if setup_class is None and teardown_class is None: | ||||||
|             return |             return | ||||||
|  | @ -760,7 +735,7 @@ class Class(PyCollector): | ||||||
|         Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with |         Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with | ||||||
|         other fixtures (#517). |         other fixtures (#517). | ||||||
|         """ |         """ | ||||||
|         setup_method = _get_non_fixture_func(self.obj, "setup_method") |         setup_method = _get_first_non_fixture_func(self.obj, ("setup_method",)) | ||||||
|         teardown_method = getattr(self.obj, "teardown_method", None) |         teardown_method = getattr(self.obj, "teardown_method", None) | ||||||
|         if setup_method is None and teardown_method is None: |         if setup_method is None and teardown_method is None: | ||||||
|             return |             return | ||||||
|  | @ -1070,12 +1045,9 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): | ||||||
|             * "params" if the argname should be the parameter of a fixture of the same name. |             * "params" if the argname should be the parameter of a fixture of the same name. | ||||||
|             * "funcargs" if the argname should be a parameter to the parametrized test function. |             * "funcargs" if the argname should be a parameter to the parametrized test function. | ||||||
|         """ |         """ | ||||||
|         valtypes = {} |         if isinstance(indirect, bool): | ||||||
|         if indirect is True: |             valtypes = dict.fromkeys(argnames, "params" if indirect else "funcargs") | ||||||
|             valtypes = dict.fromkeys(argnames, "params") |         elif isinstance(indirect, Sequence): | ||||||
|         elif indirect is False: |  | ||||||
|             valtypes = dict.fromkeys(argnames, "funcargs") |  | ||||||
|         elif isinstance(indirect, (tuple, list)): |  | ||||||
|             valtypes = dict.fromkeys(argnames, "funcargs") |             valtypes = dict.fromkeys(argnames, "funcargs") | ||||||
|             for arg in indirect: |             for arg in indirect: | ||||||
|                 if arg not in argnames: |                 if arg not in argnames: | ||||||
|  | @ -1086,6 +1058,13 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): | ||||||
|                         pytrace=False, |                         pytrace=False, | ||||||
|                     ) |                     ) | ||||||
|                 valtypes[arg] = "params" |                 valtypes[arg] = "params" | ||||||
|  |         else: | ||||||
|  |             fail( | ||||||
|  |                 "In {func}: expected Sequence or boolean for indirect, got {type}".format( | ||||||
|  |                     type=type(indirect).__name__, func=self.function.__name__ | ||||||
|  |                 ), | ||||||
|  |                 pytrace=False, | ||||||
|  |             ) | ||||||
|         return valtypes |         return valtypes | ||||||
| 
 | 
 | ||||||
|     def _validate_if_using_arg_names(self, argnames, indirect): |     def _validate_if_using_arg_names(self, argnames, indirect): | ||||||
|  | @ -1213,7 +1192,7 @@ def idmaker(argnames, parametersets, idfn=None, ids=None, config=None, item=None | ||||||
|     if len(set(ids)) != len(ids): |     if len(set(ids)) != len(ids): | ||||||
|         # The ids are not unique |         # The ids are not unique | ||||||
|         duplicates = [testid for testid in ids if ids.count(testid) > 1] |         duplicates = [testid for testid in ids if ids.count(testid) > 1] | ||||||
|         counters = collections.defaultdict(lambda: 0) |         counters = Counter() | ||||||
|         for index, testid in enumerate(ids): |         for index, testid in enumerate(ids): | ||||||
|             if testid in duplicates: |             if testid in duplicates: | ||||||
|                 ids[index] = testid + str(counters[testid]) |                 ids[index] = testid + str(counters[testid]) | ||||||
|  | @ -1402,14 +1381,11 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr): | ||||||
|         # https://github.com/pytest-dev/pytest/issues/4569 |         # https://github.com/pytest-dev/pytest/issues/4569 | ||||||
| 
 | 
 | ||||||
|         self.keywords.update( |         self.keywords.update( | ||||||
|             dict.fromkeys( |             { | ||||||
|                 [ |                 mark.name: True | ||||||
|                     mark.name |  | ||||||
|                 for mark in self.iter_markers() |                 for mark in self.iter_markers() | ||||||
|                 if mark.name not in self.keywords |                 if mark.name not in self.keywords | ||||||
|                 ], |             } | ||||||
|                 True, |  | ||||||
|             ) |  | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         if fixtureinfo is None: |         if fixtureinfo is None: | ||||||
|  |  | ||||||
|  | @ -599,6 +599,17 @@ class TestMetafunc: | ||||||
|         assert metafunc._calls[0].funcargs == dict(x="a", y="b") |         assert metafunc._calls[0].funcargs == dict(x="a", y="b") | ||||||
|         assert metafunc._calls[0].params == {} |         assert metafunc._calls[0].params == {} | ||||||
| 
 | 
 | ||||||
|  |     def test_parametrize_indirect_wrong_type(self): | ||||||
|  |         def func(x, y): | ||||||
|  |             pass | ||||||
|  | 
 | ||||||
|  |         metafunc = self.Metafunc(func) | ||||||
|  |         with pytest.raises( | ||||||
|  |             pytest.fail.Exception, | ||||||
|  |             match="In func: expected Sequence or boolean for indirect, got dict", | ||||||
|  |         ): | ||||||
|  |             metafunc.parametrize("x, y", [("a", "b")], indirect={}) | ||||||
|  | 
 | ||||||
|     def test_parametrize_indirect_list_functional(self, testdir): |     def test_parametrize_indirect_list_functional(self, testdir): | ||||||
|         """ |         """ | ||||||
|         #714 |         #714 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue