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,14 +663,15 @@ 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.
|
||||||
"""
|
"""
|
||||||
meth = getattr(obj, name, None)
|
for name in names:
|
||||||
if fixtures.getfixturemarker(meth) is None:
|
meth = getattr(obj, name, None)
|
||||||
return meth
|
if meth is not None and fixtures.getfixturemarker(meth) is None:
|
||||||
|
return meth
|
||||||
|
|
||||||
|
|
||||||
class Class(PyCollector):
|
class Class(PyCollector):
|
||||||
|
@ -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