Source code for slash.core.fixtures.utils

import functools
import itertools
import inspect

from sentinels import NOTHING
import six

from ...ctx import context
from ...utils.python import get_arguments_dict, wraps, get_underlying_func
from ...utils.function_marker import function_marker

_id_gen = itertools.count(1000)


def _ensure_fixture_info(func, **kwargs):
    if not hasattr(func, '__slash_fixture__'):
        func.__slash_fixture__ = FixtureInfo(func, **kwargs)
    return func

[docs]def fixture(func=None, name=None, scope=None, autouse=False): if func is None: return functools.partial(fixture, name=name, scope=scope, autouse=autouse) if inspect.isgeneratorfunction(get_underlying_func(func)): return yield_fixture(func=func, name=name, scope=scope, autouse=autouse) return _ensure_fixture_info(func=func, name=name, scope=scope, autouse=autouse)
def use_fixtures(fixture_names): if not isinstance(fixture_names, list): raise RuntimeError("use_fixtures expects a list of fixture names as an argument (got {!r})".format(fixture_names)) def decorator(func): extra_fixtures = getattr(func, '__extrafixtures__', None) if extra_fixtures is None: func.__extrafixtures__ = fixture_names else: func.__extrafixtures__.extend(fixture_names) return func return decorator nofixtures = function_marker('__slash_nofixtures__') nofixtures.__doc__ = 'Marks the decorated function as opting out of automatic fixture deduction. ' + \ 'Slash will not attempt to parse needed fixtures from its argument list' class FixtureInfo(object): def __init__(self, func=None, name=None, scope=None, autouse=False, path=None): super(FixtureInfo, self).__init__() self.path = path self.id = next(_id_gen) if name is None: if func is None: name = '__unnamed_{}'.format(self.id) else: name = func.__name__ if scope is None: scope = 'test' self.name = name self.func = func self.autouse = autouse self.scope = _SCOPES[scope] if self.func is not None: self.required_args = get_arguments_dict(self.func) else: self.required_args = {} if 'this' in self.required_args: self.required_args.pop('this') self.needs_this = True else: self.needs_this = False @property def scope_name(self): return _SCOPES_BY_ID[self.scope] def get_scope_by_name(scope_name): return _SCOPES[scope_name] def get_scope_name_by_scope(scope_id): return _SCOPES_BY_ID[scope_id] _SCOPES = dict( zip(('test', 'module', 'session'), itertools.count())) _SCOPES_BY_ID = dict((id, name) for (name, id) in _SCOPES.items())
[docs]def generator_fixture(func=None, **kw): """A utility for generating parametrization values from a generator: >>> @slash.generator_fixture ... def some_parameter(): ... yield first_value ... yield second_value .. note:: A generator parameter is a shortcut for a simple parametrized fixture, so the entire iteration is exhausted during test load time """ from .parameters import parametrize if func is None: return functools.partial(generator_fixture, **kw) @parametrize('param', list(func())) def new_func(param): return param kw.setdefault('name', func.__name__) return _ensure_fixture_info(func=new_func, **kw)
[docs]def yield_fixture(func=None, **kw): """Builds a fixture out of a generator. The pre-yield part of the generator is used as the setup, where the yielded value becomes the fixture value. The post-yield part is added as a cleanup: >>> @slash.yield_fixture ... def some_fixture(arg1, arg2): ... m = Microwave() ... m.turn_on(wait=True) ... yield m ... m.turn_off() """ if func is None: return functools.partial(yield_fixture, **kw) func = _ensure_fixture_info(func=func, **kw) @wraps(func) def new_func(**kwargs): f = func(**kwargs) value = next(f) @context.fixture.add_cleanup def cleanup(): # pylint: disable=unused-variable try: next(f) except StopIteration: pass else: raise RuntimeError('Yielded fixture did not stop at cleanup') return value return new_func
class _use_type(type): def __getattr__(cls, attr): if attr.startswith('_'): raise AttributeError(attr) return cls(attr)
[docs]class use(six.with_metaclass(_use_type)): # pylint: disable=no-init """ Allows tests to use fixtures under different names: >>> def test_something(m: use('microwave')): ... ... For cosmetic purposes, you can also use ``use`` with attribute access: >>> def test_something(m: use.microwave): ... ... """ def __init__(self, real_fixture_name): self.real_fixture_name = real_fixture_name def __repr__(self): return '<slash.use({!r})>'.format(self.real_fixture_name)
def get_real_fixture_name_from_argument(argument): if argument.annotation is not NOTHING and isinstance(argument.annotation, use): return argument.annotation.real_fixture_name return argument.name __all__ = ['fixture', 'nofixtures', 'generator_fixture', 'yield_fixture', 'use', 'use_fixtures']