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. """ | ||||
| import collections | ||||
| import enum | ||||
| import fnmatch | ||||
| import inspect | ||||
| import os | ||||
| import sys | ||||
| import warnings | ||||
| from collections import Counter | ||||
| from collections.abc import Sequence | ||||
| from functools import partial | ||||
| from textwrap import dedent | ||||
| 
 | ||||
|  | @ -240,9 +241,6 @@ class PyobjContext: | |||
| class PyobjMixin(PyobjContext): | ||||
|     _ALLOW_MARKERS = True | ||||
| 
 | ||||
|     def __init__(self, *k, **kw): | ||||
|         super().__init__(*k, **kw) | ||||
| 
 | ||||
|     @property | ||||
|     def obj(self): | ||||
|         """Underlying Python object.""" | ||||
|  | @ -394,12 +392,8 @@ class PyCollector(PyobjMixin, nodes.Collector): | |||
|             methods.append(module.pytest_generate_tests) | ||||
|         if hasattr(cls, "pytest_generate_tests"): | ||||
|             methods.append(cls().pytest_generate_tests) | ||||
|         if methods: | ||||
|             self.ihook.pytest_generate_tests.call_extra( | ||||
|                 methods, dict(metafunc=metafunc) | ||||
|             ) | ||||
|         else: | ||||
|             self.ihook.pytest_generate_tests(metafunc=metafunc) | ||||
| 
 | ||||
|         self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc)) | ||||
| 
 | ||||
|         if not metafunc._calls: | ||||
|             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 | ||||
|         other fixtures (#517). | ||||
|         """ | ||||
|         setup_module = _get_non_fixture_func(self.obj, "setUpModule") | ||||
|         if setup_module is None: | ||||
|             setup_module = _get_non_fixture_func(self.obj, "setup_module") | ||||
| 
 | ||||
|         teardown_module = _get_non_fixture_func(self.obj, "tearDownModule") | ||||
|         if teardown_module is None: | ||||
|             teardown_module = _get_non_fixture_func(self.obj, "teardown_module") | ||||
|         setup_module = _get_first_non_fixture_func( | ||||
|             self.obj, ("setUpModule", "setup_module") | ||||
|         ) | ||||
|         teardown_module = _get_first_non_fixture_func( | ||||
|             self.obj, ("tearDownModule", "teardown_module") | ||||
|         ) | ||||
| 
 | ||||
|         if setup_module is None and teardown_module is None: | ||||
|             return | ||||
|  | @ -472,8 +465,10 @@ class Module(nodes.File, PyCollector): | |||
|         Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with | ||||
|         other fixtures (#517). | ||||
|         """ | ||||
|         setup_function = _get_non_fixture_func(self.obj, "setup_function") | ||||
|         teardown_function = _get_non_fixture_func(self.obj, "teardown_function") | ||||
|         setup_function = _get_first_non_fixture_func(self.obj, ("setup_function",)) | ||||
|         teardown_function = _get_first_non_fixture_func( | ||||
|             self.obj, ("teardown_function",) | ||||
|         ) | ||||
|         if setup_function is None and teardown_function is None: | ||||
|             return | ||||
| 
 | ||||
|  | @ -557,15 +552,15 @@ class Package(Module): | |||
|     def setup(self): | ||||
|         # not using fixtures to call setup_module here because autouse fixtures | ||||
|         # from packages are not called automatically (#4085) | ||||
|         setup_module = _get_non_fixture_func(self.obj, "setUpModule") | ||||
|         if setup_module is None: | ||||
|             setup_module = _get_non_fixture_func(self.obj, "setup_module") | ||||
|         setup_module = _get_first_non_fixture_func( | ||||
|             self.obj, ("setUpModule", "setup_module") | ||||
|         ) | ||||
|         if setup_module is not None: | ||||
|             _call_with_optional_argument(setup_module, self.obj) | ||||
| 
 | ||||
|         teardown_module = _get_non_fixture_func(self.obj, "tearDownModule") | ||||
|         if teardown_module is None: | ||||
|             teardown_module = _get_non_fixture_func(self.obj, "teardown_module") | ||||
|         teardown_module = _get_first_non_fixture_func( | ||||
|             self.obj, ("tearDownModule", "teardown_module") | ||||
|         ) | ||||
|         if teardown_module is not None: | ||||
|             func = partial(_call_with_optional_argument, teardown_module, self.obj) | ||||
|             self.addfinalizer(func) | ||||
|  | @ -656,27 +651,6 @@ class Package(Module): | |||
|                 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): | ||||
|     """Call the given function with the given argument if func accepts one argument, otherwise | ||||
|     calls func without arguments""" | ||||
|  | @ -689,13 +663,14 @@ def _call_with_optional_argument(func, arg): | |||
|         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 | ||||
|     xunit-style function, but only if not marked as a fixture to | ||||
|     avoid calling it twice. | ||||
|     """ | ||||
|     for name in names: | ||||
|         meth = getattr(obj, name, None) | ||||
|     if fixtures.getfixturemarker(meth) is None: | ||||
|         if meth is not None and fixtures.getfixturemarker(meth) is None: | ||||
|             return meth | ||||
| 
 | ||||
| 
 | ||||
|  | @ -736,7 +711,7 @@ class Class(PyCollector): | |||
|         Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with | ||||
|         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) | ||||
|         if setup_class is None and teardown_class is None: | ||||
|             return | ||||
|  | @ -760,7 +735,7 @@ class Class(PyCollector): | |||
|         Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with | ||||
|         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) | ||||
|         if setup_method is None and teardown_method is None: | ||||
|             return | ||||
|  | @ -1070,12 +1045,9 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): | |||
|             * "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. | ||||
|         """ | ||||
|         valtypes = {} | ||||
|         if indirect is True: | ||||
|             valtypes = dict.fromkeys(argnames, "params") | ||||
|         elif indirect is False: | ||||
|             valtypes = dict.fromkeys(argnames, "funcargs") | ||||
|         elif isinstance(indirect, (tuple, list)): | ||||
|         if isinstance(indirect, bool): | ||||
|             valtypes = dict.fromkeys(argnames, "params" if indirect else "funcargs") | ||||
|         elif isinstance(indirect, Sequence): | ||||
|             valtypes = dict.fromkeys(argnames, "funcargs") | ||||
|             for arg in indirect: | ||||
|                 if arg not in argnames: | ||||
|  | @ -1086,6 +1058,13 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): | |||
|                         pytrace=False, | ||||
|                     ) | ||||
|                 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 | ||||
| 
 | ||||
|     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): | ||||
|         # The ids are not unique | ||||
|         duplicates = [testid for testid in ids if ids.count(testid) > 1] | ||||
|         counters = collections.defaultdict(lambda: 0) | ||||
|         counters = Counter() | ||||
|         for index, testid in enumerate(ids): | ||||
|             if testid in duplicates: | ||||
|                 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 | ||||
| 
 | ||||
|         self.keywords.update( | ||||
|             dict.fromkeys( | ||||
|                 [ | ||||
|                     mark.name | ||||
|             { | ||||
|                 mark.name: True | ||||
|                 for mark in self.iter_markers() | ||||
|                 if mark.name not in self.keywords | ||||
|                 ], | ||||
|                 True, | ||||
|             ) | ||||
|             } | ||||
|         ) | ||||
| 
 | ||||
|         if fixtureinfo is None: | ||||
|  |  | |||
|  | @ -599,6 +599,17 @@ class TestMetafunc: | |||
|         assert metafunc._calls[0].funcargs == dict(x="a", y="b") | ||||
|         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): | ||||
|         """ | ||||
|         #714 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue