Source code for slash.core.fixtures.parameters

import operator
import re
from contextlib import contextmanager
from sentinels import NOTHING
from slash import ctx

from ...exceptions import ParameterException
from ...exception_handling import mark_exception_frame_correction
from ...utils.python import wraps, get_argument_names
from ..tagging import Tags
from .fixture_base import FixtureBase
from .utils import FixtureInfo, get_scope_by_name

_PARAM_INFO_ATTR_NAME = '__slash_parametrize__'


[docs]def parametrize(parameter_name, values): """Decorator to create multiple test cases out of a single function or module, where the cases vary by the value of ``parameter_name``, as iterated through ``values``. """ def decorator(func): params = getattr(func, _PARAM_INFO_ATTR_NAME, None) if params is None: params = ParameterizationInfo(func) @wraps(func, preserve=['__slash_fixture__']) def new_func(*args, **kwargs): # for better debugging. _current_variation gets set to None on context exit variation = ctx.session.variations.get_current_variation() for name, param in params.iter_parametrization_fixtures(): value = variation.get_param_value(param) if name not in kwargs: kwargs[name] = value return func(*args, **kwargs) setattr(new_func, _PARAM_INFO_ATTR_NAME, params) returned = new_func else: returned = func params.add_options(parameter_name, values) return returned return decorator
[docs]def iterate(**kwargs): def decorator(func): for name, options in kwargs.items(): func = parametrize(name, options)(func) return func return decorator
[docs]def toggle(param_name): """A shortcut for :func:`slash.parametrize(param_name, [True, False]) <slash.parametrize>` .. note:: Also available for import as slash.parameters.toggle """ return parametrize(param_name, (True, False))
@contextmanager def bound_parametrizations_context(variation, fixture_store, fixture_namespace): assert ctx.session.variations.get_current_variation() is None ctx.session.variations.set_current_variation(variation) try: fixture_store.activate_autouse_fixtures_in_namespace(fixture_namespace) yield finally: ctx.session.variations.set_current_variation(None) def iter_parametrization_fixtures(func): if isinstance(func, FixtureBase): func = func.fixture_func param_info = getattr(func, _PARAM_INFO_ATTR_NAME, None) if param_info is None: return [] return param_info.iter_parametrization_fixtures() class ParameterizationInfo(object): def __init__(self, func): super(ParameterizationInfo, self).__init__() self._argument_names = get_argument_names(func) self._argument_name_set = frozenset(self._argument_names) self._params = {} self._extra_params = {} self.path = '{}:{}'.format(func.__module__, func.__name__) def add_options(self, param_name, values): if param_name in self._params: raise ParameterException('{!r} already parametrized for {}'.format( param_name, self.path)) values = list(values) if not isinstance(param_name, (list, tuple)): names = (param_name,) else: names = param_name values = _normalize_values(values, num_params=len(names)) p = Parametrization(values=values, path='{}.{}'.format(self.path, param_name)) for index, name in enumerate(names): if name in self._argument_name_set: params_dict = self._params else: params_dict = self._extra_params if len(names) > 1: params_dict[name] = p.as_transform(operator.itemgetter(index)) else: params_dict[name] = p def iter_parametrization_fixtures(self): for name in self._argument_names: values = self._params.get(name) if values is not None: yield name, values for name, values in self._extra_params.items(): yield name, values def _id(obj): return obj class Parametrization(FixtureBase): def __init__(self, path, values, info=None, transform=_id): super(Parametrization, self).__init__() self.path = path self.values = values if info is None: info = FixtureInfo(path=path) self.info = info self.scope = get_scope_by_name('test') self.transform = transform def get_value_by_index(self, index): return self.transform(self._compute_value(self.values[index])) def get_tags_by_index(self, index): return self.values[index].tags def _compute_value(self, param): if isinstance(param, list): return [p.value for p in param] return param.value def is_parameter(self): return True def is_fixture(self): return False def get_value(self, kwargs, active_fixture): raise NotImplementedError() # pragma: no cover def get_variations(self): raise NotImplementedError() # pragma: no cover def _resolve(self, store): return {} def as_transform(self, transform): returned = Parametrization(values=self.values, info=self.info, transform=transform, path=self.path) return returned class ParametrizationValue(object): def __init__(self, label=NOTHING, value=NOTHING, tags=None): super(ParametrizationValue, self).__init__() self._validate_label(label) self.label = label self.value = value self.tags = Tags() if tags: if not isinstance(tags, list): tags = [tags] for tag in tags: name, value = tag if isinstance(tag, tuple) else (tag, NOTHING) self.tags[name] = value def _validate_label(self, label): if isinstance(label, str) and not re.match(r'^[a-zA-Z_][0-9a-zA-Z_]{0,29}$', label): raise RuntimeError('Invalid label: {!r}'.format(label)) def __rfloordiv__(self, other): assert self.value is NOTHING, 'Parameter already has a value' self.value = other return self def _normalize_values(values, num_params=1): returned = [] for index, value in enumerate(values): value = _normalize_single_value(value, default_label=index) if num_params > 1: if not isinstance(value.value, (tuple, list)): raise RuntimeError('Invalid parametrization value (expected sequence): {!r}'.format(value.value)) if len(value.value) != num_params: raise RuntimeError('Invalid parametrization value (invalid length, expected {}): {!r}'.format(num_params, value.value)) returned.append(value) return returned def _normalize_single_value(value, default_label): if not isinstance(value, ParametrizationValue): value = ParametrizationValue(label=default_label, value=value) if value.value is NOTHING: raise mark_exception_frame_correction( RuntimeError('Parameter {} has no value defined!'.format(value.label)), +4) return value