Merge pull request #3679 from nicoddemus/parametrized-internal-refactor
Refactor parametrize() code for readability
This commit is contained in:
commit
0565a7a4e1
|
@ -111,7 +111,19 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
|
||||||
]
|
]
|
||||||
del argvalues
|
del argvalues
|
||||||
|
|
||||||
if not parameters:
|
if parameters:
|
||||||
|
# check all parameter sets have the correct number of values
|
||||||
|
for param in parameters:
|
||||||
|
if len(param.values) != len(argnames):
|
||||||
|
raise ValueError(
|
||||||
|
'In "parametrize" the number of values ({}) must be '
|
||||||
|
"equal to the number of names ({})".format(
|
||||||
|
param.values, argnames
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# empty parameter set (likely computed at runtime): create a single
|
||||||
|
# parameter set with NOSET values, with the "empty parameter set" mark applied to it
|
||||||
mark = get_empty_parameterset_mark(config, argnames, func)
|
mark = get_empty_parameterset_mark(config, argnames, func)
|
||||||
parameters.append(
|
parameters.append(
|
||||||
ParameterSet(values=(NOTSET,) * len(argnames), marks=[mark], id=None)
|
ParameterSet(values=(NOTSET,) * len(argnames), marks=[mark], id=None)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import os
|
||||||
import collections
|
import collections
|
||||||
import warnings
|
import warnings
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from itertools import count
|
|
||||||
|
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
@ -865,46 +864,54 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||||
"""
|
"""
|
||||||
from _pytest.fixtures import scope2index
|
from _pytest.fixtures import scope2index
|
||||||
from _pytest.mark import ParameterSet
|
from _pytest.mark import ParameterSet
|
||||||
from py.io import saferepr
|
|
||||||
|
|
||||||
argnames, parameters = ParameterSet._for_parametrize(
|
argnames, parameters = ParameterSet._for_parametrize(
|
||||||
argnames, argvalues, self.function, self.config
|
argnames, argvalues, self.function, self.config
|
||||||
)
|
)
|
||||||
del argvalues
|
del argvalues
|
||||||
default_arg_names = set(get_default_arg_names(self.function))
|
|
||||||
|
|
||||||
if scope is None:
|
if scope is None:
|
||||||
scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
|
scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
|
||||||
|
|
||||||
scopenum = scope2index(scope, descr="call to {}".format(self.parametrize))
|
self._validate_if_using_arg_names(argnames, indirect)
|
||||||
valtypes = {}
|
|
||||||
for arg in argnames:
|
arg_values_types = self._resolve_arg_value_types(argnames, indirect)
|
||||||
if arg not in self.fixturenames:
|
|
||||||
if arg in default_arg_names:
|
ids = self._resolve_arg_ids(argnames, ids, parameters)
|
||||||
raise ValueError(
|
|
||||||
"%r already takes an argument %r with a default value"
|
scopenum = scope2index(scope, descr="call to {}".format(self.parametrize))
|
||||||
% (self.function, arg)
|
|
||||||
)
|
# create the new calls: if we are parametrize() multiple times (by applying the decorator
|
||||||
else:
|
# more than once) then we accumulate those calls generating the cartesian product
|
||||||
if isinstance(indirect, (tuple, list)):
|
# of all calls
|
||||||
name = "fixture" if arg in indirect else "argument"
|
newcalls = []
|
||||||
else:
|
for callspec in self._calls or [CallSpec2(self)]:
|
||||||
name = "fixture" if indirect else "argument"
|
for param_index, (param_id, param_set) in enumerate(zip(ids, parameters)):
|
||||||
raise ValueError("%r uses no %s %r" % (self.function, name, arg))
|
newcallspec = callspec.copy()
|
||||||
|
newcallspec.setmulti2(
|
||||||
|
arg_values_types,
|
||||||
|
argnames,
|
||||||
|
param_set.values,
|
||||||
|
param_id,
|
||||||
|
param_set.marks,
|
||||||
|
scopenum,
|
||||||
|
param_index,
|
||||||
|
)
|
||||||
|
newcalls.append(newcallspec)
|
||||||
|
self._calls = newcalls
|
||||||
|
|
||||||
|
def _resolve_arg_ids(self, argnames, ids, parameters):
|
||||||
|
"""Resolves the actual ids for the given argnames, based on the ``ids`` parameter given
|
||||||
|
to ``parametrize``.
|
||||||
|
|
||||||
|
:param List[str] argnames: list of argument names passed to ``parametrize()``.
|
||||||
|
:param ids: the ids parameter of the parametrized call (see docs).
|
||||||
|
:param List[ParameterSet] parameters: the list of parameter values, same size as ``argnames``.
|
||||||
|
:rtype: List[str]
|
||||||
|
:return: the list of ids for each argname given
|
||||||
|
"""
|
||||||
|
from py.io import saferepr
|
||||||
|
|
||||||
if indirect is True:
|
|
||||||
valtypes = dict.fromkeys(argnames, "params")
|
|
||||||
elif indirect is False:
|
|
||||||
valtypes = dict.fromkeys(argnames, "funcargs")
|
|
||||||
elif isinstance(indirect, (tuple, list)):
|
|
||||||
valtypes = dict.fromkeys(argnames, "funcargs")
|
|
||||||
for arg in indirect:
|
|
||||||
if arg not in argnames:
|
|
||||||
raise ValueError(
|
|
||||||
"indirect given to %r: fixture %r doesn't exist"
|
|
||||||
% (self.function, arg)
|
|
||||||
)
|
|
||||||
valtypes[arg] = "params"
|
|
||||||
idfn = None
|
idfn = None
|
||||||
if callable(ids):
|
if callable(ids):
|
||||||
idfn = ids
|
idfn = ids
|
||||||
|
@ -921,29 +928,57 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||||
msg % (saferepr(id_value), type(id_value).__name__)
|
msg % (saferepr(id_value), type(id_value).__name__)
|
||||||
)
|
)
|
||||||
ids = idmaker(argnames, parameters, idfn, ids, self.config)
|
ids = idmaker(argnames, parameters, idfn, ids, self.config)
|
||||||
newcalls = []
|
return ids
|
||||||
for callspec in self._calls or [CallSpec2(self)]:
|
|
||||||
elements = zip(ids, parameters, count())
|
def _resolve_arg_value_types(self, argnames, indirect):
|
||||||
for a_id, param, param_index in elements:
|
"""Resolves if each parametrized argument must be considered a parameter to a fixture or a "funcarg"
|
||||||
if len(param.values) != len(argnames):
|
to the function, based on the ``indirect`` parameter of the parametrized() call.
|
||||||
|
|
||||||
|
:param List[str] argnames: list of argument names passed to ``parametrize()``.
|
||||||
|
:param indirect: same ``indirect`` parameter of ``parametrize()``.
|
||||||
|
:rtype: Dict[str, str]
|
||||||
|
A dict mapping each arg name to either:
|
||||||
|
* "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)):
|
||||||
|
valtypes = dict.fromkeys(argnames, "funcargs")
|
||||||
|
for arg in indirect:
|
||||||
|
if arg not in argnames:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'In "parametrize" the number of values ({}) must be '
|
"indirect given to %r: fixture %r doesn't exist"
|
||||||
"equal to the number of names ({})".format(
|
% (self.function, arg)
|
||||||
param.values, argnames
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
newcallspec = callspec.copy()
|
valtypes[arg] = "params"
|
||||||
newcallspec.setmulti2(
|
return valtypes
|
||||||
valtypes,
|
|
||||||
argnames,
|
def _validate_if_using_arg_names(self, argnames, indirect):
|
||||||
param.values,
|
"""
|
||||||
a_id,
|
Check if all argnames are being used, by default values, or directly/indirectly.
|
||||||
param.marks,
|
|
||||||
scopenum,
|
:param List[str] argnames: list of argument names passed to ``parametrize()``.
|
||||||
param_index,
|
:param indirect: same ``indirect`` parameter of ``parametrize()``.
|
||||||
)
|
:raise ValueError: if validation fails.
|
||||||
newcalls.append(newcallspec)
|
"""
|
||||||
self._calls = newcalls
|
default_arg_names = set(get_default_arg_names(self.function))
|
||||||
|
for arg in argnames:
|
||||||
|
if arg not in self.fixturenames:
|
||||||
|
if arg in default_arg_names:
|
||||||
|
raise ValueError(
|
||||||
|
"%r already takes an argument %r with a default value"
|
||||||
|
% (self.function, arg)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if isinstance(indirect, (tuple, list)):
|
||||||
|
name = "fixture" if arg in indirect else "argument"
|
||||||
|
else:
|
||||||
|
name = "fixture" if indirect else "argument"
|
||||||
|
raise ValueError("%r uses no %s %r" % (self.function, name, arg))
|
||||||
|
|
||||||
def addcall(self, funcargs=None, id=NOTSET, param=NOTSET):
|
def addcall(self, funcargs=None, id=NOTSET, param=NOTSET):
|
||||||
""" Add a new call to the underlying test function during the collection phase of a test run.
|
""" Add a new call to the underlying test function during the collection phase of a test run.
|
||||||
|
|
Loading…
Reference in New Issue