Skip to content

Commit 499fda2

Browse files
authored
Various cleanups in src/_pytest/python.py (#5599)
Various cleanups in src/_pytest/python.py
2 parents 0f8b462 + 374c432 commit 499fda2

File tree

2 files changed

+54
-67
lines changed

2 files changed

+54
-67
lines changed

src/_pytest/python.py

Lines changed: 43 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
""" Python test discovery, setup and run of test functions. """
2-
import collections
32
import enum
43
import fnmatch
54
import inspect
65
import os
76
import sys
87
import warnings
8+
from collections import Counter
9+
from collections.abc import Sequence
910
from functools import partial
1011
from textwrap import dedent
1112

@@ -240,9 +241,6 @@ class PyobjContext:
240241
class PyobjMixin(PyobjContext):
241242
_ALLOW_MARKERS = True
242243

243-
def __init__(self, *k, **kw):
244-
super().__init__(*k, **kw)
245-
246244
@property
247245
def obj(self):
248246
"""Underlying Python object."""
@@ -394,12 +392,8 @@ def _genfunctions(self, name, funcobj):
394392
methods.append(module.pytest_generate_tests)
395393
if hasattr(cls, "pytest_generate_tests"):
396394
methods.append(cls().pytest_generate_tests)
397-
if methods:
398-
self.ihook.pytest_generate_tests.call_extra(
399-
methods, dict(metafunc=metafunc)
400-
)
401-
else:
402-
self.ihook.pytest_generate_tests(metafunc=metafunc)
395+
396+
self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc))
403397

404398
if not metafunc._calls:
405399
yield Function(name, parent=self, fixtureinfo=fixtureinfo)
@@ -444,13 +438,12 @@ def _inject_setup_module_fixture(self):
444438
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
445439
other fixtures (#517).
446440
"""
447-
setup_module = _get_non_fixture_func(self.obj, "setUpModule")
448-
if setup_module is None:
449-
setup_module = _get_non_fixture_func(self.obj, "setup_module")
450-
451-
teardown_module = _get_non_fixture_func(self.obj, "tearDownModule")
452-
if teardown_module is None:
453-
teardown_module = _get_non_fixture_func(self.obj, "teardown_module")
441+
setup_module = _get_first_non_fixture_func(
442+
self.obj, ("setUpModule", "setup_module")
443+
)
444+
teardown_module = _get_first_non_fixture_func(
445+
self.obj, ("tearDownModule", "teardown_module")
446+
)
454447

455448
if setup_module is None and teardown_module is None:
456449
return
@@ -472,8 +465,10 @@ def _inject_setup_function_fixture(self):
472465
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
473466
other fixtures (#517).
474467
"""
475-
setup_function = _get_non_fixture_func(self.obj, "setup_function")
476-
teardown_function = _get_non_fixture_func(self.obj, "teardown_function")
468+
setup_function = _get_first_non_fixture_func(self.obj, ("setup_function",))
469+
teardown_function = _get_first_non_fixture_func(
470+
self.obj, ("teardown_function",)
471+
)
477472
if setup_function is None and teardown_function is None:
478473
return
479474

@@ -557,15 +552,15 @@ def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None):
557552
def setup(self):
558553
# not using fixtures to call setup_module here because autouse fixtures
559554
# from packages are not called automatically (#4085)
560-
setup_module = _get_non_fixture_func(self.obj, "setUpModule")
561-
if setup_module is None:
562-
setup_module = _get_non_fixture_func(self.obj, "setup_module")
555+
setup_module = _get_first_non_fixture_func(
556+
self.obj, ("setUpModule", "setup_module")
557+
)
563558
if setup_module is not None:
564559
_call_with_optional_argument(setup_module, self.obj)
565560

566-
teardown_module = _get_non_fixture_func(self.obj, "tearDownModule")
567-
if teardown_module is None:
568-
teardown_module = _get_non_fixture_func(self.obj, "teardown_module")
561+
teardown_module = _get_first_non_fixture_func(
562+
self.obj, ("tearDownModule", "teardown_module")
563+
)
569564
if teardown_module is not None:
570565
func = partial(_call_with_optional_argument, teardown_module, self.obj)
571566
self.addfinalizer(func)
@@ -656,27 +651,6 @@ def collect(self):
656651
pkg_prefixes.add(path)
657652

658653

659-
def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
660-
"""
661-
Return a callable to perform xunit-style setup or teardown if
662-
the function exists in the ``holder`` object.
663-
The ``param_obj`` parameter is the parameter which will be passed to the function
664-
when the callable is called without arguments, defaults to the ``holder`` object.
665-
Return ``None`` if a suitable callable is not found.
666-
"""
667-
# TODO: only needed because of Package!
668-
param_obj = param_obj if param_obj is not None else holder
669-
result = _get_non_fixture_func(holder, attr_name)
670-
if result is not None:
671-
arg_count = result.__code__.co_argcount
672-
if inspect.ismethod(result):
673-
arg_count -= 1
674-
if arg_count:
675-
return lambda: result(param_obj)
676-
else:
677-
return result
678-
679-
680654
def _call_with_optional_argument(func, arg):
681655
"""Call the given function with the given argument if func accepts one argument, otherwise
682656
calls func without arguments"""
@@ -689,14 +663,15 @@ def _call_with_optional_argument(func, arg):
689663
func()
690664

691665

692-
def _get_non_fixture_func(obj, name):
666+
def _get_first_non_fixture_func(obj, names):
693667
"""Return the attribute from the given object to be used as a setup/teardown
694668
xunit-style function, but only if not marked as a fixture to
695669
avoid calling it twice.
696670
"""
697-
meth = getattr(obj, name, None)
698-
if fixtures.getfixturemarker(meth) is None:
699-
return meth
671+
for name in names:
672+
meth = getattr(obj, name, None)
673+
if meth is not None and fixtures.getfixturemarker(meth) is None:
674+
return meth
700675

701676

702677
class Class(PyCollector):
@@ -736,7 +711,7 @@ def _inject_setup_class_fixture(self):
736711
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
737712
other fixtures (#517).
738713
"""
739-
setup_class = _get_non_fixture_func(self.obj, "setup_class")
714+
setup_class = _get_first_non_fixture_func(self.obj, ("setup_class",))
740715
teardown_class = getattr(self.obj, "teardown_class", None)
741716
if setup_class is None and teardown_class is None:
742717
return
@@ -760,7 +735,7 @@ def _inject_setup_method_fixture(self):
760735
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
761736
other fixtures (#517).
762737
"""
763-
setup_method = _get_non_fixture_func(self.obj, "setup_method")
738+
setup_method = _get_first_non_fixture_func(self.obj, ("setup_method",))
764739
teardown_method = getattr(self.obj, "teardown_method", None)
765740
if setup_method is None and teardown_method is None:
766741
return
@@ -1070,12 +1045,9 @@ def _resolve_arg_value_types(self, argnames, indirect):
10701045
* "params" if the argname should be the parameter of a fixture of the same name.
10711046
* "funcargs" if the argname should be a parameter to the parametrized test function.
10721047
"""
1073-
valtypes = {}
1074-
if indirect is True:
1075-
valtypes = dict.fromkeys(argnames, "params")
1076-
elif indirect is False:
1077-
valtypes = dict.fromkeys(argnames, "funcargs")
1078-
elif isinstance(indirect, (tuple, list)):
1048+
if isinstance(indirect, bool):
1049+
valtypes = dict.fromkeys(argnames, "params" if indirect else "funcargs")
1050+
elif isinstance(indirect, Sequence):
10791051
valtypes = dict.fromkeys(argnames, "funcargs")
10801052
for arg in indirect:
10811053
if arg not in argnames:
@@ -1086,6 +1058,13 @@ def _resolve_arg_value_types(self, argnames, indirect):
10861058
pytrace=False,
10871059
)
10881060
valtypes[arg] = "params"
1061+
else:
1062+
fail(
1063+
"In {func}: expected Sequence or boolean for indirect, got {type}".format(
1064+
type=type(indirect).__name__, func=self.function.__name__
1065+
),
1066+
pytrace=False,
1067+
)
10891068
return valtypes
10901069

10911070
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
12131192
if len(set(ids)) != len(ids):
12141193
# The ids are not unique
12151194
duplicates = [testid for testid in ids if ids.count(testid) > 1]
1216-
counters = collections.defaultdict(lambda: 0)
1195+
counters = Counter()
12171196
for index, testid in enumerate(ids):
12181197
if testid in duplicates:
12191198
ids[index] = testid + str(counters[testid])
@@ -1402,14 +1381,11 @@ def __init__(
14021381
# https://github.com/pytest-dev/pytest/issues/4569
14031382

14041383
self.keywords.update(
1405-
dict.fromkeys(
1406-
[
1407-
mark.name
1408-
for mark in self.iter_markers()
1409-
if mark.name not in self.keywords
1410-
],
1411-
True,
1412-
)
1384+
{
1385+
mark.name: True
1386+
for mark in self.iter_markers()
1387+
if mark.name not in self.keywords
1388+
}
14131389
)
14141390

14151391
if fixtureinfo is None:

testing/python/metafunc.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,17 @@ def func(x, y):
599599
assert metafunc._calls[0].funcargs == dict(x="a", y="b")
600600
assert metafunc._calls[0].params == {}
601601

602+
def test_parametrize_indirect_wrong_type(self):
603+
def func(x, y):
604+
pass
605+
606+
metafunc = self.Metafunc(func)
607+
with pytest.raises(
608+
pytest.fail.Exception,
609+
match="In func: expected Sequence or boolean for indirect, got dict",
610+
):
611+
metafunc.parametrize("x, y", [("a", "b")], indirect={})
612+
602613
def test_parametrize_indirect_list_functional(self, testdir):
603614
"""
604615
#714

0 commit comments

Comments
 (0)