| """ Python test discovery, setup and run of test functions. """ |
| import fnmatch |
| import functools |
| import inspect |
| import re |
| import types |
| import sys |
| |
| import py |
| import pytest |
| from _pytest._code.code import TerminalRepr |
| from _pytest.mark import MarkDecorator, MarkerError |
| |
| try: |
| import enum |
| except ImportError: # pragma: no cover |
| # Only available in Python 3.4+ or as a backport |
| enum = None |
| |
| import _pytest |
| import _pytest._pluggy as pluggy |
| |
| cutdir2 = py.path.local(_pytest.__file__).dirpath() |
| cutdir1 = py.path.local(pluggy.__file__.rstrip("oc")) |
| |
| |
| NoneType = type(None) |
| NOTSET = object() |
| isfunction = inspect.isfunction |
| isclass = inspect.isclass |
| callable = py.builtin.callable |
| # used to work around a python2 exception info leak |
| exc_clear = getattr(sys, 'exc_clear', lambda: None) |
| # The type of re.compile objects is not exposed in Python. |
| REGEX_TYPE = type(re.compile('')) |
| |
| _PY3 = sys.version_info > (3, 0) |
| _PY2 = not _PY3 |
| |
| |
| if hasattr(inspect, 'signature'): |
| def _format_args(func): |
| return str(inspect.signature(func)) |
| else: |
| def _format_args(func): |
| return inspect.formatargspec(*inspect.getargspec(func)) |
| |
| if sys.version_info[:2] == (2, 6): |
| def isclass(object): |
| """ Return true if the object is a class. Overrides inspect.isclass for |
| python 2.6 because it will return True for objects which always return |
| something on __getattr__ calls (see #1035). |
| Backport of https://hg.python.org/cpython/rev/35bf8f7a8edc |
| """ |
| return isinstance(object, (type, types.ClassType)) |
| |
| def _has_positional_arg(func): |
| return func.__code__.co_argcount |
| |
| |
| def filter_traceback(entry): |
| # entry.path might sometimes return a str object when the entry |
| # points to dynamically generated code |
| # see https://bitbucket.org/pytest-dev/py/issues/71 |
| raw_filename = entry.frame.code.raw.co_filename |
| is_generated = '<' in raw_filename and '>' in raw_filename |
| if is_generated: |
| return False |
| # entry.path might point to an inexisting file, in which case it will |
| # alsso return a str object. see #1133 |
| p = py.path.local(entry.path) |
| return p != cutdir1 and not p.relto(cutdir2) |
| |
| |
| def get_real_func(obj): |
| """ gets the real function object of the (possibly) wrapped object by |
| functools.wraps or functools.partial. |
| """ |
| while hasattr(obj, "__wrapped__"): |
| obj = obj.__wrapped__ |
| if isinstance(obj, functools.partial): |
| obj = obj.func |
| return obj |
| |
| def getfslineno(obj): |
| # xxx let decorators etc specify a sane ordering |
| obj = get_real_func(obj) |
| if hasattr(obj, 'place_as'): |
| obj = obj.place_as |
| fslineno = _pytest._code.getfslineno(obj) |
| assert isinstance(fslineno[1], int), obj |
| return fslineno |
| |
| def getimfunc(func): |
| try: |
| return func.__func__ |
| except AttributeError: |
| try: |
| return func.im_func |
| except AttributeError: |
| return func |
| |
| def safe_getattr(object, name, default): |
| """ Like getattr but return default upon any Exception. |
| |
| Attribute access can potentially fail for 'evil' Python objects. |
| See issue214 |
| """ |
| try: |
| return getattr(object, name, default) |
| except Exception: |
| return default |
| |
| |
| class FixtureFunctionMarker: |
| def __init__(self, scope, params, |
| autouse=False, yieldctx=False, ids=None): |
| self.scope = scope |
| self.params = params |
| self.autouse = autouse |
| self.yieldctx = yieldctx |
| self.ids = ids |
| |
| def __call__(self, function): |
| if isclass(function): |
| raise ValueError( |
| "class fixtures not supported (may be in the future)") |
| function._pytestfixturefunction = self |
| return function |
| |
| |
| def fixture(scope="function", params=None, autouse=False, ids=None): |
| """ (return a) decorator to mark a fixture factory function. |
| |
| This decorator can be used (with or or without parameters) to define |
| a fixture function. The name of the fixture function can later be |
| referenced to cause its invocation ahead of running tests: test |
| modules or classes can use the pytest.mark.usefixtures(fixturename) |
| marker. Test functions can directly use fixture names as input |
| arguments in which case the fixture instance returned from the fixture |
| function will be injected. |
| |
| :arg scope: the scope for which this fixture is shared, one of |
| "function" (default), "class", "module", "session". |
| |
| :arg params: an optional list of parameters which will cause multiple |
| invocations of the fixture function and all of the tests |
| using it. |
| |
| :arg autouse: if True, the fixture func is activated for all tests that |
| can see it. If False (the default) then an explicit |
| reference is needed to activate the fixture. |
| |
| :arg ids: list of string ids each corresponding to the params |
| so that they are part of the test id. If no ids are provided |
| they will be generated automatically from the params. |
| |
| """ |
| if callable(scope) and params is None and autouse == False: |
| # direct decoration |
| return FixtureFunctionMarker( |
| "function", params, autouse)(scope) |
| if params is not None and not isinstance(params, (list, tuple)): |
| params = list(params) |
| return FixtureFunctionMarker(scope, params, autouse, ids=ids) |
| |
| def yield_fixture(scope="function", params=None, autouse=False, ids=None): |
| """ (return a) decorator to mark a yield-fixture factory function |
| (EXPERIMENTAL). |
| |
| This takes the same arguments as :py:func:`pytest.fixture` but |
| expects a fixture function to use a ``yield`` instead of a ``return`` |
| statement to provide a fixture. See |
| http://pytest.org/en/latest/yieldfixture.html for more info. |
| """ |
| if callable(scope) and params is None and autouse == False: |
| # direct decoration |
| return FixtureFunctionMarker( |
| "function", params, autouse, yieldctx=True)(scope) |
| else: |
| return FixtureFunctionMarker(scope, params, autouse, |
| yieldctx=True, ids=ids) |
| |
| defaultfuncargprefixmarker = fixture() |
| |
| def pyobj_property(name): |
| def get(self): |
| node = self.getparent(getattr(pytest, name)) |
| if node is not None: |
| return node.obj |
| doc = "python %s object this node was collected from (can be None)." % ( |
| name.lower(),) |
| return property(get, None, None, doc) |
| |
| |
| def pytest_addoption(parser): |
| group = parser.getgroup("general") |
| group.addoption('--fixtures', '--funcargs', |
| action="store_true", dest="showfixtures", default=False, |
| help="show available fixtures, sorted by plugin appearance") |
| parser.addini("usefixtures", type="args", default=[], |
| help="list of default fixtures to be used with this project") |
| parser.addini("python_files", type="args", |
| default=['test_*.py', '*_test.py'], |
| help="glob-style file patterns for Python test module discovery") |
| parser.addini("python_classes", type="args", default=["Test",], |
| help="prefixes or glob names for Python test class discovery") |
| parser.addini("python_functions", type="args", default=["test",], |
| help="prefixes or glob names for Python test function and " |
| "method discovery") |
| |
| group.addoption("--import-mode", default="prepend", |
| choices=["prepend", "append"], dest="importmode", |
| help="prepend/append to sys.path when importing test modules, " |
| "default is to prepend.") |
| |
| |
| def pytest_cmdline_main(config): |
| if config.option.showfixtures: |
| showfixtures(config) |
| return 0 |
| |
| |
| def pytest_generate_tests(metafunc): |
| # those alternative spellings are common - raise a specific error to alert |
| # the user |
| alt_spellings = ['parameterize', 'parametrise', 'parameterise'] |
| for attr in alt_spellings: |
| if hasattr(metafunc.function, attr): |
| msg = "{0} has '{1}', spelling should be 'parametrize'" |
| raise MarkerError(msg.format(metafunc.function.__name__, attr)) |
| try: |
| markers = metafunc.function.parametrize |
| except AttributeError: |
| return |
| for marker in markers: |
| metafunc.parametrize(*marker.args, **marker.kwargs) |
| |
| def pytest_configure(config): |
| config.addinivalue_line("markers", |
| "parametrize(argnames, argvalues): call a test function multiple " |
| "times passing in different arguments in turn. argvalues generally " |
| "needs to be a list of values if argnames specifies only one name " |
| "or a list of tuples of values if argnames specifies multiple names. " |
| "Example: @parametrize('arg1', [1,2]) would lead to two calls of the " |
| "decorated test function, one with arg1=1 and another with arg1=2." |
| "see http://pytest.org/latest/parametrize.html for more info and " |
| "examples." |
| ) |
| config.addinivalue_line("markers", |
| "usefixtures(fixturename1, fixturename2, ...): mark tests as needing " |
| "all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures " |
| ) |
| |
| def pytest_sessionstart(session): |
| session._fixturemanager = FixtureManager(session) |
| |
| @pytest.hookimpl(trylast=True) |
| def pytest_namespace(): |
| raises.Exception = pytest.fail.Exception |
| return { |
| 'fixture': fixture, |
| 'yield_fixture': yield_fixture, |
| 'raises' : raises, |
| 'collect': { |
| 'Module': Module, 'Class': Class, 'Instance': Instance, |
| 'Function': Function, 'Generator': Generator, |
| '_fillfuncargs': fillfixtures} |
| } |
| |
| @fixture(scope="session") |
| def pytestconfig(request): |
| """ the pytest config object with access to command line opts.""" |
| return request.config |
| |
| |
| @pytest.hookimpl(trylast=True) |
| def pytest_pyfunc_call(pyfuncitem): |
| testfunction = pyfuncitem.obj |
| if pyfuncitem._isyieldedfunction(): |
| testfunction(*pyfuncitem._args) |
| else: |
| funcargs = pyfuncitem.funcargs |
| testargs = {} |
| for arg in pyfuncitem._fixtureinfo.argnames: |
| testargs[arg] = funcargs[arg] |
| testfunction(**testargs) |
| return True |
| |
| def pytest_collect_file(path, parent): |
| ext = path.ext |
| if ext == ".py": |
| if not parent.session.isinitpath(path): |
| for pat in parent.config.getini('python_files'): |
| if path.fnmatch(pat): |
| break |
| else: |
| return |
| ihook = parent.session.gethookproxy(path) |
| return ihook.pytest_pycollect_makemodule(path=path, parent=parent) |
| |
| def pytest_pycollect_makemodule(path, parent): |
| return Module(path, parent) |
| |
| @pytest.hookimpl(hookwrapper=True) |
| def pytest_pycollect_makeitem(collector, name, obj): |
| outcome = yield |
| res = outcome.get_result() |
| if res is not None: |
| raise StopIteration |
| # nothing was collected elsewhere, let's do it here |
| if isclass(obj): |
| if collector.istestclass(obj, name): |
| Class = collector._getcustomclass("Class") |
| outcome.force_result(Class(name, parent=collector)) |
| elif collector.istestfunction(obj, name): |
| # mock seems to store unbound methods (issue473), normalize it |
| obj = getattr(obj, "__func__", obj) |
| # We need to try and unwrap the function if it's a functools.partial |
| # or a funtools.wrapped. |
| # We musn't if it's been wrapped with mock.patch (python 2 only) |
| if not (isfunction(obj) or isfunction(get_real_func(obj))): |
| collector.warn(code="C2", message= |
| "cannot collect %r because it is not a function." |
| % name, ) |
| elif getattr(obj, "__test__", True): |
| if is_generator(obj): |
| res = Generator(name, parent=collector) |
| else: |
| res = list(collector._genfunctions(name, obj)) |
| outcome.force_result(res) |
| |
| def is_generator(func): |
| try: |
| return _pytest._code.getrawcode(func).co_flags & 32 # generator function |
| except AttributeError: # builtin functions have no bytecode |
| # assume them to not be generators |
| return False |
| |
| class PyobjContext(object): |
| module = pyobj_property("Module") |
| cls = pyobj_property("Class") |
| instance = pyobj_property("Instance") |
| |
| class PyobjMixin(PyobjContext): |
| def obj(): |
| def fget(self): |
| try: |
| return self._obj |
| except AttributeError: |
| self._obj = obj = self._getobj() |
| return obj |
| def fset(self, value): |
| self._obj = value |
| return property(fget, fset, None, "underlying python object") |
| obj = obj() |
| |
| def _getobj(self): |
| return getattr(self.parent.obj, self.name) |
| |
| def getmodpath(self, stopatmodule=True, includemodule=False): |
| """ return python path relative to the containing module. """ |
| chain = self.listchain() |
| chain.reverse() |
| parts = [] |
| for node in chain: |
| if isinstance(node, Instance): |
| continue |
| name = node.name |
| if isinstance(node, Module): |
| assert name.endswith(".py") |
| name = name[:-3] |
| if stopatmodule: |
| if includemodule: |
| parts.append(name) |
| break |
| parts.append(name) |
| parts.reverse() |
| s = ".".join(parts) |
| return s.replace(".[", "[") |
| |
| def _getfslineno(self): |
| return getfslineno(self.obj) |
| |
| def reportinfo(self): |
| # XXX caching? |
| obj = self.obj |
| compat_co_firstlineno = getattr(obj, 'compat_co_firstlineno', None) |
| if isinstance(compat_co_firstlineno, int): |
| # nose compatibility |
| fspath = sys.modules[obj.__module__].__file__ |
| if fspath.endswith(".pyc"): |
| fspath = fspath[:-1] |
| lineno = compat_co_firstlineno |
| else: |
| fspath, lineno = getfslineno(obj) |
| modpath = self.getmodpath() |
| assert isinstance(lineno, int) |
| return fspath, lineno, modpath |
| |
| class PyCollector(PyobjMixin, pytest.Collector): |
| |
| def funcnamefilter(self, name): |
| return self._matches_prefix_or_glob_option('python_functions', name) |
| |
| def isnosetest(self, obj): |
| """ Look for the __test__ attribute, which is applied by the |
| @nose.tools.istest decorator |
| """ |
| # We explicitly check for "is True" here to not mistakenly treat |
| # classes with a custom __getattr__ returning something truthy (like a |
| # function) as test classes. |
| return safe_getattr(obj, '__test__', False) is True |
| |
| def classnamefilter(self, name): |
| return self._matches_prefix_or_glob_option('python_classes', name) |
| |
| def istestfunction(self, obj, name): |
| return ( |
| (self.funcnamefilter(name) or self.isnosetest(obj)) and |
| safe_getattr(obj, "__call__", False) and getfixturemarker(obj) is None |
| ) |
| |
| def istestclass(self, obj, name): |
| return self.classnamefilter(name) or self.isnosetest(obj) |
| |
| def _matches_prefix_or_glob_option(self, option_name, name): |
| """ |
| checks if the given name matches the prefix or glob-pattern defined |
| in ini configuration. |
| """ |
| for option in self.config.getini(option_name): |
| if name.startswith(option): |
| return True |
| # check that name looks like a glob-string before calling fnmatch |
| # because this is called for every name in each collected module, |
| # and fnmatch is somewhat expensive to call |
| elif ('*' in option or '?' in option or '[' in option) and \ |
| fnmatch.fnmatch(name, option): |
| return True |
| return False |
| |
| def collect(self): |
| if not getattr(self.obj, "__test__", True): |
| return [] |
| |
| # NB. we avoid random getattrs and peek in the __dict__ instead |
| # (XXX originally introduced from a PyPy need, still true?) |
| dicts = [getattr(self.obj, '__dict__', {})] |
| for basecls in inspect.getmro(self.obj.__class__): |
| dicts.append(basecls.__dict__) |
| seen = {} |
| l = [] |
| for dic in dicts: |
| for name, obj in list(dic.items()): |
| if name in seen: |
| continue |
| seen[name] = True |
| res = self.makeitem(name, obj) |
| if res is None: |
| continue |
| if not isinstance(res, list): |
| res = [res] |
| l.extend(res) |
| l.sort(key=lambda item: item.reportinfo()[:2]) |
| return l |
| |
| def makeitem(self, name, obj): |
| #assert self.ihook.fspath == self.fspath, self |
| return self.ihook.pytest_pycollect_makeitem( |
| collector=self, name=name, obj=obj) |
| |
| def _genfunctions(self, name, funcobj): |
| module = self.getparent(Module).obj |
| clscol = self.getparent(Class) |
| cls = clscol and clscol.obj or None |
| transfer_markers(funcobj, cls, module) |
| fm = self.session._fixturemanager |
| fixtureinfo = fm.getfixtureinfo(self, funcobj, cls) |
| metafunc = Metafunc(funcobj, fixtureinfo, self.config, |
| cls=cls, module=module) |
| methods = [] |
| if hasattr(module, "pytest_generate_tests"): |
| methods.append(module.pytest_generate_tests) |
| if hasattr(cls, "pytest_generate_tests"): |
| methods.append(cls().pytest_generate_tests) |
| if methods: |
| self.ihook.pytest_generate_tests.call_extra(methods, |
| dict(metafunc=metafunc)) |
| else: |
| self.ihook.pytest_generate_tests(metafunc=metafunc) |
| |
| Function = self._getcustomclass("Function") |
| if not metafunc._calls: |
| yield Function(name, parent=self, fixtureinfo=fixtureinfo) |
| else: |
| # add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs |
| add_funcarg_pseudo_fixture_def(self, metafunc, fm) |
| |
| for callspec in metafunc._calls: |
| subname = "%s[%s]" %(name, callspec.id) |
| yield Function(name=subname, parent=self, |
| callspec=callspec, callobj=funcobj, |
| fixtureinfo=fixtureinfo, |
| keywords={callspec.id:True}) |
| |
| def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager): |
| # this function will transform all collected calls to a functions |
| # if they use direct funcargs (i.e. direct parametrization) |
| # because we want later test execution to be able to rely on |
| # an existing FixtureDef structure for all arguments. |
| # XXX we can probably avoid this algorithm if we modify CallSpec2 |
| # to directly care for creating the fixturedefs within its methods. |
| if not metafunc._calls[0].funcargs: |
| return # this function call does not have direct parametrization |
| # collect funcargs of all callspecs into a list of values |
| arg2params = {} |
| arg2scope = {} |
| for callspec in metafunc._calls: |
| for argname, argvalue in callspec.funcargs.items(): |
| assert argname not in callspec.params |
| callspec.params[argname] = argvalue |
| arg2params_list = arg2params.setdefault(argname, []) |
| callspec.indices[argname] = len(arg2params_list) |
| arg2params_list.append(argvalue) |
| if argname not in arg2scope: |
| scopenum = callspec._arg2scopenum.get(argname, |
| scopenum_function) |
| arg2scope[argname] = scopes[scopenum] |
| callspec.funcargs.clear() |
| |
| # register artificial FixtureDef's so that later at test execution |
| # time we can rely on a proper FixtureDef to exist for fixture setup. |
| arg2fixturedefs = metafunc._arg2fixturedefs |
| for argname, valuelist in arg2params.items(): |
| # if we have a scope that is higher than function we need |
| # to make sure we only ever create an according fixturedef on |
| # a per-scope basis. We thus store and cache the fixturedef on the |
| # node related to the scope. |
| scope = arg2scope[argname] |
| node = None |
| if scope != "function": |
| node = get_scope_node(collector, scope) |
| if node is None: |
| assert scope == "class" and isinstance(collector, Module) |
| # use module-level collector for class-scope (for now) |
| node = collector |
| if node and argname in node._name2pseudofixturedef: |
| arg2fixturedefs[argname] = [node._name2pseudofixturedef[argname]] |
| else: |
| fixturedef = FixtureDef(fixturemanager, '', argname, |
| get_direct_param_fixture_func, |
| arg2scope[argname], |
| valuelist, False, False) |
| arg2fixturedefs[argname] = [fixturedef] |
| if node is not None: |
| node._name2pseudofixturedef[argname] = fixturedef |
| |
| |
| def get_direct_param_fixture_func(request): |
| return request.param |
| |
| class FuncFixtureInfo: |
| def __init__(self, argnames, names_closure, name2fixturedefs): |
| self.argnames = argnames |
| self.names_closure = names_closure |
| self.name2fixturedefs = name2fixturedefs |
| |
| |
| def _marked(func, mark): |
| """ Returns True if :func: is already marked with :mark:, False otherwise. |
| This can happen if marker is applied to class and the test file is |
| invoked more than once. |
| """ |
| try: |
| func_mark = getattr(func, mark.name) |
| except AttributeError: |
| return False |
| return mark.args == func_mark.args and mark.kwargs == func_mark.kwargs |
| |
| |
| def transfer_markers(funcobj, cls, mod): |
| # XXX this should rather be code in the mark plugin or the mark |
| # plugin should merge with the python plugin. |
| for holder in (cls, mod): |
| try: |
| pytestmark = holder.pytestmark |
| except AttributeError: |
| continue |
| if isinstance(pytestmark, list): |
| for mark in pytestmark: |
| if not _marked(funcobj, mark): |
| mark(funcobj) |
| else: |
| if not _marked(funcobj, pytestmark): |
| pytestmark(funcobj) |
| |
| class Module(pytest.File, PyCollector): |
| """ Collector for test classes and functions. """ |
| def _getobj(self): |
| return self._memoizedcall('_obj', self._importtestmodule) |
| |
| def collect(self): |
| self.session._fixturemanager.parsefactories(self) |
| return super(Module, self).collect() |
| |
| def _importtestmodule(self): |
| # we assume we are only called once per module |
| importmode = self.config.getoption("--import-mode") |
| try: |
| mod = self.fspath.pyimport(ensuresyspath=importmode) |
| except SyntaxError: |
| raise self.CollectError( |
| _pytest._code.ExceptionInfo().getrepr(style="short")) |
| except self.fspath.ImportMismatchError: |
| e = sys.exc_info()[1] |
| raise self.CollectError( |
| "import file mismatch:\n" |
| "imported module %r has this __file__ attribute:\n" |
| " %s\n" |
| "which is not the same as the test file we want to collect:\n" |
| " %s\n" |
| "HINT: remove __pycache__ / .pyc files and/or use a " |
| "unique basename for your test file modules" |
| % e.args |
| ) |
| #print "imported test module", mod |
| self.config.pluginmanager.consider_module(mod) |
| return mod |
| |
| def setup(self): |
| setup_module = xunitsetup(self.obj, "setUpModule") |
| if setup_module is None: |
| setup_module = xunitsetup(self.obj, "setup_module") |
| if setup_module is not None: |
| #XXX: nose compat hack, move to nose plugin |
| # if it takes a positional arg, its probably a pytest style one |
| # so we pass the current module object |
| if _has_positional_arg(setup_module): |
| setup_module(self.obj) |
| else: |
| setup_module() |
| fin = getattr(self.obj, 'tearDownModule', None) |
| if fin is None: |
| fin = getattr(self.obj, 'teardown_module', None) |
| if fin is not None: |
| #XXX: nose compat hack, move to nose plugin |
| # if it takes a positional arg, it's probably a pytest style one |
| # so we pass the current module object |
| if _has_positional_arg(fin): |
| finalizer = lambda: fin(self.obj) |
| else: |
| finalizer = fin |
| self.addfinalizer(finalizer) |
| |
| |
| class Class(PyCollector): |
| """ Collector for test methods. """ |
| def collect(self): |
| if hasinit(self.obj): |
| self.warn("C1", "cannot collect test class %r because it has a " |
| "__init__ constructor" % self.obj.__name__) |
| return [] |
| return [self._getcustomclass("Instance")(name="()", parent=self)] |
| |
| def setup(self): |
| setup_class = xunitsetup(self.obj, 'setup_class') |
| if setup_class is not None: |
| setup_class = getattr(setup_class, 'im_func', setup_class) |
| setup_class = getattr(setup_class, '__func__', setup_class) |
| setup_class(self.obj) |
| |
| fin_class = getattr(self.obj, 'teardown_class', None) |
| if fin_class is not None: |
| fin_class = getattr(fin_class, 'im_func', fin_class) |
| fin_class = getattr(fin_class, '__func__', fin_class) |
| self.addfinalizer(lambda: fin_class(self.obj)) |
| |
| class Instance(PyCollector): |
| def _getobj(self): |
| obj = self.parent.obj() |
| return obj |
| |
| def collect(self): |
| self.session._fixturemanager.parsefactories(self) |
| return super(Instance, self).collect() |
| |
| def newinstance(self): |
| self.obj = self._getobj() |
| return self.obj |
| |
| class FunctionMixin(PyobjMixin): |
| """ mixin for the code common to Function and Generator. |
| """ |
| |
| def setup(self): |
| """ perform setup for this test function. """ |
| if hasattr(self, '_preservedparent'): |
| obj = self._preservedparent |
| elif isinstance(self.parent, Instance): |
| obj = self.parent.newinstance() |
| self.obj = self._getobj() |
| else: |
| obj = self.parent.obj |
| if inspect.ismethod(self.obj): |
| setup_name = 'setup_method' |
| teardown_name = 'teardown_method' |
| else: |
| setup_name = 'setup_function' |
| teardown_name = 'teardown_function' |
| setup_func_or_method = xunitsetup(obj, setup_name) |
| if setup_func_or_method is not None: |
| setup_func_or_method(self.obj) |
| fin = getattr(obj, teardown_name, None) |
| if fin is not None: |
| self.addfinalizer(lambda: fin(self.obj)) |
| |
| def _prunetraceback(self, excinfo): |
| if hasattr(self, '_obj') and not self.config.option.fulltrace: |
| code = _pytest._code.Code(get_real_func(self.obj)) |
| path, firstlineno = code.path, code.firstlineno |
| traceback = excinfo.traceback |
| ntraceback = traceback.cut(path=path, firstlineno=firstlineno) |
| if ntraceback == traceback: |
| ntraceback = ntraceback.cut(path=path) |
| if ntraceback == traceback: |
| #ntraceback = ntraceback.cut(excludepath=cutdir2) |
| ntraceback = ntraceback.filter(filter_traceback) |
| if not ntraceback: |
| ntraceback = traceback |
| |
| excinfo.traceback = ntraceback.filter() |
| # issue364: mark all but first and last frames to |
| # only show a single-line message for each frame |
| if self.config.option.tbstyle == "auto": |
| if len(excinfo.traceback) > 2: |
| for entry in excinfo.traceback[1:-1]: |
| entry.set_repr_style('short') |
| |
| def _repr_failure_py(self, excinfo, style="long"): |
| if excinfo.errisinstance(pytest.fail.Exception): |
| if not excinfo.value.pytrace: |
| return py._builtin._totext(excinfo.value) |
| return super(FunctionMixin, self)._repr_failure_py(excinfo, |
| style=style) |
| |
| def repr_failure(self, excinfo, outerr=None): |
| assert outerr is None, "XXX outerr usage is deprecated" |
| style = self.config.option.tbstyle |
| if style == "auto": |
| style = "long" |
| return self._repr_failure_py(excinfo, style=style) |
| |
| |
| class Generator(FunctionMixin, PyCollector): |
| def collect(self): |
| # test generators are seen as collectors but they also |
| # invoke setup/teardown on popular request |
| # (induced by the common "test_*" naming shared with normal tests) |
| self.session._setupstate.prepare(self) |
| # see FunctionMixin.setup and test_setupstate_is_preserved_134 |
| self._preservedparent = self.parent.obj |
| l = [] |
| seen = {} |
| for i, x in enumerate(self.obj()): |
| name, call, args = self.getcallargs(x) |
| if not callable(call): |
| raise TypeError("%r yielded non callable test %r" %(self.obj, call,)) |
| if name is None: |
| name = "[%d]" % i |
| else: |
| name = "['%s']" % name |
| if name in seen: |
| raise ValueError("%r generated tests with non-unique name %r" %(self, name)) |
| seen[name] = True |
| l.append(self.Function(name, self, args=args, callobj=call)) |
| return l |
| |
| def getcallargs(self, obj): |
| if not isinstance(obj, (tuple, list)): |
| obj = (obj,) |
| # explict naming |
| if isinstance(obj[0], py.builtin._basestring): |
| name = obj[0] |
| obj = obj[1:] |
| else: |
| name = None |
| call, args = obj[0], obj[1:] |
| return name, call, args |
| |
| |
| def hasinit(obj): |
| init = getattr(obj, '__init__', None) |
| if init: |
| if init != object.__init__: |
| return True |
| |
| |
| |
| def fillfixtures(function): |
| """ fill missing funcargs for a test function. """ |
| try: |
| request = function._request |
| except AttributeError: |
| # XXX this special code path is only expected to execute |
| # with the oejskit plugin. It uses classes with funcargs |
| # and we thus have to work a bit to allow this. |
| fm = function.session._fixturemanager |
| fi = fm.getfixtureinfo(function.parent, function.obj, None) |
| function._fixtureinfo = fi |
| request = function._request = FixtureRequest(function) |
| request._fillfixtures() |
| # prune out funcargs for jstests |
| newfuncargs = {} |
| for name in fi.argnames: |
| newfuncargs[name] = function.funcargs[name] |
| function.funcargs = newfuncargs |
| else: |
| request._fillfixtures() |
| |
| |
| _notexists = object() |
| |
| class CallSpec2(object): |
| def __init__(self, metafunc): |
| self.metafunc = metafunc |
| self.funcargs = {} |
| self._idlist = [] |
| self.params = {} |
| self._globalid = _notexists |
| self._globalid_args = set() |
| self._globalparam = _notexists |
| self._arg2scopenum = {} # used for sorting parametrized resources |
| self.keywords = {} |
| self.indices = {} |
| |
| def copy(self, metafunc): |
| cs = CallSpec2(self.metafunc) |
| cs.funcargs.update(self.funcargs) |
| cs.params.update(self.params) |
| cs.keywords.update(self.keywords) |
| cs.indices.update(self.indices) |
| cs._arg2scopenum.update(self._arg2scopenum) |
| cs._idlist = list(self._idlist) |
| cs._globalid = self._globalid |
| cs._globalid_args = self._globalid_args |
| cs._globalparam = self._globalparam |
| return cs |
| |
| def _checkargnotcontained(self, arg): |
| if arg in self.params or arg in self.funcargs: |
| raise ValueError("duplicate %r" %(arg,)) |
| |
| def getparam(self, name): |
| try: |
| return self.params[name] |
| except KeyError: |
| if self._globalparam is _notexists: |
| raise ValueError(name) |
| return self._globalparam |
| |
| @property |
| def id(self): |
| return "-".join(map(str, filter(None, self._idlist))) |
| |
| def setmulti(self, valtypes, argnames, valset, id, keywords, scopenum, |
| param_index): |
| for arg,val in zip(argnames, valset): |
| self._checkargnotcontained(arg) |
| valtype_for_arg = valtypes[arg] |
| getattr(self, valtype_for_arg)[arg] = val |
| self.indices[arg] = param_index |
| self._arg2scopenum[arg] = scopenum |
| if val is _notexists: |
| self._emptyparamspecified = True |
| self._idlist.append(id) |
| self.keywords.update(keywords) |
| |
| def setall(self, funcargs, id, param): |
| for x in funcargs: |
| self._checkargnotcontained(x) |
| self.funcargs.update(funcargs) |
| if id is not _notexists: |
| self._idlist.append(id) |
| if param is not _notexists: |
| assert self._globalparam is _notexists |
| self._globalparam = param |
| for arg in funcargs: |
| self._arg2scopenum[arg] = scopenum_function |
| |
| |
| class FuncargnamesCompatAttr: |
| """ helper class so that Metafunc, Function and FixtureRequest |
| don't need to each define the "funcargnames" compatibility attribute. |
| """ |
| @property |
| def funcargnames(self): |
| """ alias attribute for ``fixturenames`` for pre-2.3 compatibility""" |
| return self.fixturenames |
| |
| class Metafunc(FuncargnamesCompatAttr): |
| """ |
| Metafunc objects are passed to the ``pytest_generate_tests`` hook. |
| They help to inspect a test function and to generate tests according to |
| test configuration or values specified in the class or module where a |
| test function is defined. |
| |
| :ivar fixturenames: set of fixture names required by the test function |
| |
| :ivar function: underlying python test function |
| |
| :ivar cls: class object where the test function is defined in or ``None``. |
| |
| :ivar module: the module object where the test function is defined in. |
| |
| :ivar config: access to the :class:`_pytest.config.Config` object for the |
| test session. |
| |
| :ivar funcargnames: |
| .. deprecated:: 2.3 |
| Use ``fixturenames`` instead. |
| """ |
| def __init__(self, function, fixtureinfo, config, cls=None, module=None): |
| self.config = config |
| self.module = module |
| self.function = function |
| self.fixturenames = fixtureinfo.names_closure |
| self._arg2fixturedefs = fixtureinfo.name2fixturedefs |
| self.cls = cls |
| self._calls = [] |
| self._ids = py.builtin.set() |
| |
| def parametrize(self, argnames, argvalues, indirect=False, ids=None, |
| scope=None): |
| """ Add new invocations to the underlying test function using the list |
| of argvalues for the given argnames. Parametrization is performed |
| during the collection phase. If you need to setup expensive resources |
| see about setting indirect to do it rather at test setup time. |
| |
| :arg argnames: a comma-separated string denoting one or more argument |
| names, or a list/tuple of argument strings. |
| |
| :arg argvalues: The list of argvalues determines how often a |
| test is invoked with different argument values. If only one |
| argname was specified argvalues is a list of values. If N |
| argnames were specified, argvalues must be a list of N-tuples, |
| where each tuple-element specifies a value for its respective |
| argname. |
| |
| :arg indirect: The list of argnames or boolean. A list of arguments' |
| names (subset of argnames). If True the list contains all names from |
| the argnames. Each argvalue corresponding to an argname in this list will |
| be passed as request.param to its respective argname fixture |
| function so that it can perform more expensive setups during the |
| setup phase of a test rather than at collection time. |
| |
| :arg ids: list of string ids, or a callable. |
| If strings, each is corresponding to the argvalues so that they are |
| part of the test id. |
| If callable, it should take one argument (a single argvalue) and return |
| a string or return None. If None, the automatically generated id for that |
| argument will be used. |
| If no ids are provided they will be generated automatically from |
| the argvalues. |
| |
| :arg scope: if specified it denotes the scope of the parameters. |
| The scope is used for grouping tests by parameter instances. |
| It will also override any fixture-function defined scope, allowing |
| to set a dynamic scope using test context or configuration. |
| """ |
| |
| # individual parametrized argument sets can be wrapped in a series |
| # of markers in which case we unwrap the values and apply the mark |
| # at Function init |
| newkeywords = {} |
| unwrapped_argvalues = [] |
| for i, argval in enumerate(argvalues): |
| while isinstance(argval, MarkDecorator): |
| newmark = MarkDecorator(argval.markname, |
| argval.args[:-1], argval.kwargs) |
| newmarks = newkeywords.setdefault(i, {}) |
| newmarks[newmark.markname] = newmark |
| argval = argval.args[-1] |
| unwrapped_argvalues.append(argval) |
| argvalues = unwrapped_argvalues |
| |
| if not isinstance(argnames, (tuple, list)): |
| argnames = [x.strip() for x in argnames.split(",") if x.strip()] |
| if len(argnames) == 1: |
| argvalues = [(val,) for val in argvalues] |
| if not argvalues: |
| argvalues = [(_notexists,) * len(argnames)] |
| |
| if scope is None: |
| scope = "function" |
| scopenum = scopes.index(scope) |
| valtypes = {} |
| for arg in argnames: |
| if arg not in self.fixturenames: |
| raise ValueError("%r uses no fixture %r" %(self.function, arg)) |
| |
| 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 |
| if callable(ids): |
| idfn = ids |
| ids = None |
| if ids and len(ids) != len(argvalues): |
| raise ValueError('%d tests specified with %d ids' %( |
| len(argvalues), len(ids))) |
| if not ids: |
| ids = idmaker(argnames, argvalues, idfn) |
| newcalls = [] |
| for callspec in self._calls or [CallSpec2(self)]: |
| for param_index, valset in enumerate(argvalues): |
| assert len(valset) == len(argnames) |
| newcallspec = callspec.copy(self) |
| newcallspec.setmulti(valtypes, argnames, valset, ids[param_index], |
| newkeywords.get(param_index, {}), scopenum, |
| param_index) |
| newcalls.append(newcallspec) |
| self._calls = newcalls |
| |
| def addcall(self, funcargs=None, id=_notexists, param=_notexists): |
| """ (deprecated, use parametrize) Add a new call to the underlying |
| test function during the collection phase of a test run. Note that |
| request.addcall() is called during the test collection phase prior and |
| independently to actual test execution. You should only use addcall() |
| if you need to specify multiple arguments of a test function. |
| |
| :arg funcargs: argument keyword dictionary used when invoking |
| the test function. |
| |
| :arg id: used for reporting and identification purposes. If you |
| don't supply an `id` an automatic unique id will be generated. |
| |
| :arg param: a parameter which will be exposed to a later fixture function |
| invocation through the ``request.param`` attribute. |
| """ |
| assert funcargs is None or isinstance(funcargs, dict) |
| if funcargs is not None: |
| for name in funcargs: |
| if name not in self.fixturenames: |
| pytest.fail("funcarg %r not used in this function." % name) |
| else: |
| funcargs = {} |
| if id is None: |
| raise ValueError("id=None not allowed") |
| if id is _notexists: |
| id = len(self._calls) |
| id = str(id) |
| if id in self._ids: |
| raise ValueError("duplicate id %r" % id) |
| self._ids.add(id) |
| |
| cs = CallSpec2(self) |
| cs.setall(funcargs, id, param) |
| self._calls.append(cs) |
| |
| |
| if _PY3: |
| import codecs |
| |
| def _escape_bytes(val): |
| """ |
| If val is pure ascii, returns it as a str(), otherwise escapes |
| into a sequence of escaped bytes: |
| b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6' |
| |
| note: |
| the obvious "v.decode('unicode-escape')" will return |
| valid utf-8 unicode if it finds them in the string, but we |
| want to return escaped bytes for any byte, even if they match |
| a utf-8 string. |
| """ |
| if val: |
| # source: http://goo.gl/bGsnwC |
| encoded_bytes, _ = codecs.escape_encode(val) |
| return encoded_bytes.decode('ascii') |
| else: |
| # empty bytes crashes codecs.escape_encode (#1087) |
| return '' |
| else: |
| def _escape_bytes(val): |
| """ |
| In py2 bytes and str are the same type, so return it unchanged if it |
| is a full ascii string, otherwise escape it into its binary form. |
| """ |
| try: |
| return val.decode('ascii') |
| except UnicodeDecodeError: |
| return val.encode('string-escape') |
| |
| |
| def _idval(val, argname, idx, idfn): |
| if idfn: |
| try: |
| s = idfn(val) |
| if s: |
| return s |
| except Exception: |
| pass |
| |
| if isinstance(val, bytes): |
| return _escape_bytes(val) |
| elif isinstance(val, (float, int, str, bool, NoneType)): |
| return str(val) |
| elif isinstance(val, REGEX_TYPE): |
| return _escape_bytes(val.pattern) if isinstance(val.pattern, bytes) else val.pattern |
| elif enum is not None and isinstance(val, enum.Enum): |
| return str(val) |
| elif isclass(val) and hasattr(val, '__name__'): |
| return val.__name__ |
| elif _PY2 and isinstance(val, unicode): |
| # special case for python 2: if a unicode string is |
| # convertible to ascii, return it as an str() object instead |
| try: |
| return str(val) |
| except UnicodeError: |
| # fallthrough |
| pass |
| return str(argname)+str(idx) |
| |
| def _idvalset(idx, valset, argnames, idfn): |
| this_id = [_idval(val, argname, idx, idfn) |
| for val, argname in zip(valset, argnames)] |
| return "-".join(this_id) |
| |
| def idmaker(argnames, argvalues, idfn=None): |
| ids = [_idvalset(valindex, valset, argnames, idfn) |
| for valindex, valset in enumerate(argvalues)] |
| if len(set(ids)) < len(ids): |
| # user may have provided a bad idfn which means the ids are not unique |
| ids = [str(i) + testid for i, testid in enumerate(ids)] |
| return ids |
| |
| def showfixtures(config): |
| from _pytest.main import wrap_session |
| return wrap_session(config, _showfixtures_main) |
| |
| def _showfixtures_main(config, session): |
| import _pytest.config |
| session.perform_collect() |
| curdir = py.path.local() |
| tw = _pytest.config.create_terminal_writer(config) |
| verbose = config.getvalue("verbose") |
| |
| fm = session._fixturemanager |
| |
| available = [] |
| for argname, fixturedefs in fm._arg2fixturedefs.items(): |
| assert fixturedefs is not None |
| if not fixturedefs: |
| continue |
| fixturedef = fixturedefs[-1] |
| loc = getlocation(fixturedef.func, curdir) |
| available.append((len(fixturedef.baseid), |
| fixturedef.func.__module__, |
| curdir.bestrelpath(loc), |
| fixturedef.argname, fixturedef)) |
| |
| available.sort() |
| currentmodule = None |
| for baseid, module, bestrel, argname, fixturedef in available: |
| if currentmodule != module: |
| if not module.startswith("_pytest."): |
| tw.line() |
| tw.sep("-", "fixtures defined from %s" %(module,)) |
| currentmodule = module |
| if verbose <= 0 and argname[0] == "_": |
| continue |
| if verbose > 0: |
| funcargspec = "%s -- %s" %(argname, bestrel,) |
| else: |
| funcargspec = argname |
| tw.line(funcargspec, green=True) |
| loc = getlocation(fixturedef.func, curdir) |
| doc = fixturedef.func.__doc__ or "" |
| if doc: |
| for line in doc.strip().split("\n"): |
| tw.line(" " + line.strip()) |
| else: |
| tw.line(" %s: no docstring available" %(loc,), |
| red=True) |
| |
| def getlocation(function, curdir): |
| import inspect |
| fn = py.path.local(inspect.getfile(function)) |
| lineno = py.builtin._getcode(function).co_firstlineno |
| if fn.relto(curdir): |
| fn = fn.relto(curdir) |
| return "%s:%d" %(fn, lineno+1) |
| |
| # builtin pytest.raises helper |
| |
| def raises(expected_exception, *args, **kwargs): |
| """ assert that a code block/function call raises ``expected_exception`` |
| and raise a failure exception otherwise. |
| |
| This helper produces a ``ExceptionInfo()`` object (see below). |
| |
| If using Python 2.5 or above, you may use this function as a |
| context manager:: |
| |
| >>> with raises(ZeroDivisionError): |
| ... 1/0 |
| |
| .. note:: |
| |
| When using ``pytest.raises`` as a context manager, it's worthwhile to |
| note that normal context manager rules apply and that the exception |
| raised *must* be the final line in the scope of the context manager. |
| Lines of code after that, within the scope of the context manager will |
| not be executed. For example:: |
| |
| >>> with raises(OSError) as exc_info: |
| assert 1 == 1 # this will execute as expected |
| raise OSError(errno.EEXISTS, 'directory exists') |
| assert exc_info.value.errno == errno.EEXISTS # this will not execute |
| |
| Instead, the following approach must be taken (note the difference in |
| scope):: |
| |
| >>> with raises(OSError) as exc_info: |
| assert 1 == 1 # this will execute as expected |
| raise OSError(errno.EEXISTS, 'directory exists') |
| |
| assert exc_info.value.errno == errno.EEXISTS # this will now execute |
| |
| Or you can specify a callable by passing a to-be-called lambda:: |
| |
| >>> raises(ZeroDivisionError, lambda: 1/0) |
| <ExceptionInfo ...> |
| |
| or you can specify an arbitrary callable with arguments:: |
| |
| >>> def f(x): return 1/x |
| ... |
| >>> raises(ZeroDivisionError, f, 0) |
| <ExceptionInfo ...> |
| >>> raises(ZeroDivisionError, f, x=0) |
| <ExceptionInfo ...> |
| |
| A third possibility is to use a string to be executed:: |
| |
| >>> raises(ZeroDivisionError, "f(0)") |
| <ExceptionInfo ...> |
| |
| .. autoclass:: _pytest._code.ExceptionInfo |
| :members: |
| |
| .. note:: |
| Similar to caught exception objects in Python, explicitly clearing |
| local references to returned ``ExceptionInfo`` objects can |
| help the Python interpreter speed up its garbage collection. |
| |
| Clearing those references breaks a reference cycle |
| (``ExceptionInfo`` --> caught exception --> frame stack raising |
| the exception --> current frame stack --> local variables --> |
| ``ExceptionInfo``) which makes Python keep all objects referenced |
| from that cycle (including all local variables in the current |
| frame) alive until the next cyclic garbage collection run. See the |
| official Python ``try`` statement documentation for more detailed |
| information. |
| |
| """ |
| __tracebackhide__ = True |
| if expected_exception is AssertionError: |
| # we want to catch a AssertionError |
| # replace our subclass with the builtin one |
| # see https://github.com/pytest-dev/pytest/issues/176 |
| from _pytest.assertion.util import BuiltinAssertionError \ |
| as expected_exception |
| msg = ("exceptions must be old-style classes or" |
| " derived from BaseException, not %s") |
| if isinstance(expected_exception, tuple): |
| for exc in expected_exception: |
| if not isclass(exc): |
| raise TypeError(msg % type(exc)) |
| elif not isclass(expected_exception): |
| raise TypeError(msg % type(expected_exception)) |
| |
| if not args: |
| return RaisesContext(expected_exception) |
| elif isinstance(args[0], str): |
| code, = args |
| assert isinstance(code, str) |
| frame = sys._getframe(1) |
| loc = frame.f_locals.copy() |
| loc.update(kwargs) |
| #print "raises frame scope: %r" % frame.f_locals |
| try: |
| code = _pytest._code.Source(code).compile() |
| py.builtin.exec_(code, frame.f_globals, loc) |
| # XXX didn'T mean f_globals == f_locals something special? |
| # this is destroyed here ... |
| except expected_exception: |
| return _pytest._code.ExceptionInfo() |
| else: |
| func = args[0] |
| try: |
| func(*args[1:], **kwargs) |
| except expected_exception: |
| return _pytest._code.ExceptionInfo() |
| pytest.fail("DID NOT RAISE {0}".format(expected_exception)) |
| |
| class RaisesContext(object): |
| def __init__(self, expected_exception): |
| self.expected_exception = expected_exception |
| self.excinfo = None |
| |
| def __enter__(self): |
| self.excinfo = object.__new__(_pytest._code.ExceptionInfo) |
| return self.excinfo |
| |
| def __exit__(self, *tp): |
| __tracebackhide__ = True |
| if tp[0] is None: |
| pytest.fail("DID NOT RAISE") |
| if sys.version_info < (2, 7): |
| # py26: on __exit__() exc_value often does not contain the |
| # exception value. |
| # http://bugs.python.org/issue7853 |
| if not isinstance(tp[1], BaseException): |
| exc_type, value, traceback = tp |
| tp = exc_type, exc_type(value), traceback |
| self.excinfo.__init__(tp) |
| return issubclass(self.excinfo.type, self.expected_exception) |
| |
| # |
| # the basic pytest Function item |
| # |
| |
| class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr): |
| """ a Function Item is responsible for setting up and executing a |
| Python test function. |
| """ |
| _genid = None |
| def __init__(self, name, parent, args=None, config=None, |
| callspec=None, callobj=NOTSET, keywords=None, session=None, |
| fixtureinfo=None): |
| super(Function, self).__init__(name, parent, config=config, |
| session=session) |
| self._args = args |
| if callobj is not NOTSET: |
| self.obj = callobj |
| |
| self.keywords.update(self.obj.__dict__) |
| if callspec: |
| self.callspec = callspec |
| self.keywords.update(callspec.keywords) |
| if keywords: |
| self.keywords.update(keywords) |
| |
| if fixtureinfo is None: |
| fixtureinfo = self.session._fixturemanager.getfixtureinfo( |
| self.parent, self.obj, self.cls, |
| funcargs=not self._isyieldedfunction()) |
| self._fixtureinfo = fixtureinfo |
| self.fixturenames = fixtureinfo.names_closure |
| self._initrequest() |
| |
| def _initrequest(self): |
| self.funcargs = {} |
| if self._isyieldedfunction(): |
| assert not hasattr(self, "callspec"), ( |
| "yielded functions (deprecated) cannot have funcargs") |
| else: |
| if hasattr(self, "callspec"): |
| callspec = self.callspec |
| assert not callspec.funcargs |
| self._genid = callspec.id |
| if hasattr(callspec, "param"): |
| self.param = callspec.param |
| self._request = FixtureRequest(self) |
| |
| @property |
| def function(self): |
| "underlying python 'function' object" |
| return getattr(self.obj, 'im_func', self.obj) |
| |
| def _getobj(self): |
| name = self.name |
| i = name.find("[") # parametrization |
| if i != -1: |
| name = name[:i] |
| return getattr(self.parent.obj, name) |
| |
| @property |
| def _pyfuncitem(self): |
| "(compatonly) for code expecting pytest-2.2 style request objects" |
| return self |
| |
| def _isyieldedfunction(self): |
| return getattr(self, "_args", None) is not None |
| |
| def runtest(self): |
| """ execute the underlying test function. """ |
| self.ihook.pytest_pyfunc_call(pyfuncitem=self) |
| |
| def setup(self): |
| # check if parametrization happend with an empty list |
| try: |
| self.callspec._emptyparamspecified |
| except AttributeError: |
| pass |
| else: |
| fs, lineno = self._getfslineno() |
| pytest.skip("got empty parameter set, function %s at %s:%d" %( |
| self.function.__name__, fs, lineno)) |
| super(Function, self).setup() |
| fillfixtures(self) |
| |
| |
| scope2props = dict(session=()) |
| scope2props["module"] = ("fspath", "module") |
| scope2props["class"] = scope2props["module"] + ("cls",) |
| scope2props["instance"] = scope2props["class"] + ("instance", ) |
| scope2props["function"] = scope2props["instance"] + ("function", "keywords") |
| |
| def scopeproperty(name=None, doc=None): |
| def decoratescope(func): |
| scopename = name or func.__name__ |
| def provide(self): |
| if func.__name__ in scope2props[self.scope]: |
| return func(self) |
| raise AttributeError("%s not available in %s-scoped context" % ( |
| scopename, self.scope)) |
| return property(provide, None, None, func.__doc__) |
| return decoratescope |
| |
| |
| class FixtureRequest(FuncargnamesCompatAttr): |
| """ A request for a fixture from a test or fixture function. |
| |
| A request object gives access to the requesting test context |
| and has an optional ``param`` attribute in case |
| the fixture is parametrized indirectly. |
| """ |
| |
| def __init__(self, pyfuncitem): |
| self._pyfuncitem = pyfuncitem |
| #: fixture for which this request is being performed |
| self.fixturename = None |
| #: Scope string, one of "function", "cls", "module", "session" |
| self.scope = "function" |
| self._funcargs = {} |
| self._fixturedefs = {} |
| fixtureinfo = pyfuncitem._fixtureinfo |
| self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy() |
| self._arg2index = {} |
| self.fixturenames = fixtureinfo.names_closure |
| self._fixturemanager = pyfuncitem.session._fixturemanager |
| |
| @property |
| def node(self): |
| """ underlying collection node (depends on current request scope)""" |
| return self._getscopeitem(self.scope) |
| |
| |
| def _getnextfixturedef(self, argname): |
| fixturedefs = self._arg2fixturedefs.get(argname, None) |
| if fixturedefs is None: |
| # we arrive here because of a a dynamic call to |
| # getfuncargvalue(argname) usage which was naturally |
| # not known at parsing/collection time |
| fixturedefs = self._fixturemanager.getfixturedefs( |
| argname, self._pyfuncitem.parent.nodeid) |
| self._arg2fixturedefs[argname] = fixturedefs |
| # fixturedefs list is immutable so we maintain a decreasing index |
| index = self._arg2index.get(argname, 0) - 1 |
| if fixturedefs is None or (-index > len(fixturedefs)): |
| raise FixtureLookupError(argname, self) |
| self._arg2index[argname] = index |
| return fixturedefs[index] |
| |
| @property |
| def config(self): |
| """ the pytest config object associated with this request. """ |
| return self._pyfuncitem.config |
| |
| |
| @scopeproperty() |
| def function(self): |
| """ test function object if the request has a per-function scope. """ |
| return self._pyfuncitem.obj |
| |
| @scopeproperty("class") |
| def cls(self): |
| """ class (can be None) where the test function was collected. """ |
| clscol = self._pyfuncitem.getparent(pytest.Class) |
| if clscol: |
| return clscol.obj |
| |
| @property |
| def instance(self): |
| """ instance (can be None) on which test function was collected. """ |
| # unittest support hack, see _pytest.unittest.TestCaseFunction |
| try: |
| return self._pyfuncitem._testcase |
| except AttributeError: |
| function = getattr(self, "function", None) |
| if function is not None: |
| return py.builtin._getimself(function) |
| |
| @scopeproperty() |
| def module(self): |
| """ python module object where the test function was collected. """ |
| return self._pyfuncitem.getparent(pytest.Module).obj |
| |
| @scopeproperty() |
| def fspath(self): |
| """ the file system path of the test module which collected this test. """ |
| return self._pyfuncitem.fspath |
| |
| @property |
| def keywords(self): |
| """ keywords/markers dictionary for the underlying node. """ |
| return self.node.keywords |
| |
| @property |
| def session(self): |
| """ pytest session object. """ |
| return self._pyfuncitem.session |
| |
| def addfinalizer(self, finalizer): |
| """ add finalizer/teardown function to be called after the |
| last test within the requesting test context finished |
| execution. """ |
| # XXX usually this method is shadowed by fixturedef specific ones |
| self._addfinalizer(finalizer, scope=self.scope) |
| |
| def _addfinalizer(self, finalizer, scope): |
| colitem = self._getscopeitem(scope) |
| self._pyfuncitem.session._setupstate.addfinalizer( |
| finalizer=finalizer, colitem=colitem) |
| |
| def applymarker(self, marker): |
| """ Apply a marker to a single test function invocation. |
| This method is useful if you don't want to have a keyword/marker |
| on all function invocations. |
| |
| :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object |
| created by a call to ``pytest.mark.NAME(...)``. |
| """ |
| try: |
| self.node.keywords[marker.markname] = marker |
| except AttributeError: |
| raise ValueError(marker) |
| |
| def raiseerror(self, msg): |
| """ raise a FixtureLookupError with the given message. """ |
| raise self._fixturemanager.FixtureLookupError(None, self, msg) |
| |
| def _fillfixtures(self): |
| item = self._pyfuncitem |
| fixturenames = getattr(item, "fixturenames", self.fixturenames) |
| for argname in fixturenames: |
| if argname not in item.funcargs: |
| item.funcargs[argname] = self.getfuncargvalue(argname) |
| |
| def cached_setup(self, setup, teardown=None, scope="module", extrakey=None): |
| """ (deprecated) Return a testing resource managed by ``setup`` & |
| ``teardown`` calls. ``scope`` and ``extrakey`` determine when the |
| ``teardown`` function will be called so that subsequent calls to |
| ``setup`` would recreate the resource. With pytest-2.3 you often |
| do not need ``cached_setup()`` as you can directly declare a scope |
| on a fixture function and register a finalizer through |
| ``request.addfinalizer()``. |
| |
| :arg teardown: function receiving a previously setup resource. |
| :arg setup: a no-argument function creating a resource. |
| :arg scope: a string value out of ``function``, ``class``, ``module`` |
| or ``session`` indicating the caching lifecycle of the resource. |
| :arg extrakey: added to internal caching key of (funcargname, scope). |
| """ |
| if not hasattr(self.config, '_setupcache'): |
| self.config._setupcache = {} # XXX weakref? |
| cachekey = (self.fixturename, self._getscopeitem(scope), extrakey) |
| cache = self.config._setupcache |
| try: |
| val = cache[cachekey] |
| except KeyError: |
| self._check_scope(self.fixturename, self.scope, scope) |
| val = setup() |
| cache[cachekey] = val |
| if teardown is not None: |
| def finalizer(): |
| del cache[cachekey] |
| teardown(val) |
| self._addfinalizer(finalizer, scope=scope) |
| return val |
| |
| def getfuncargvalue(self, argname): |
| """ Dynamically retrieve a named fixture function argument. |
| |
| As of pytest-2.3, it is easier and usually better to access other |
| fixture values by stating it as an input argument in the fixture |
| function. If you only can decide about using another fixture at test |
| setup time, you may use this function to retrieve it inside a fixture |
| function body. |
| """ |
| return self._get_active_fixturedef(argname).cached_result[0] |
| |
| def _get_active_fixturedef(self, argname): |
| try: |
| return self._fixturedefs[argname] |
| except KeyError: |
| try: |
| fixturedef = self._getnextfixturedef(argname) |
| except FixtureLookupError: |
| if argname == "request": |
| class PseudoFixtureDef: |
| cached_result = (self, [0], None) |
| scope = "function" |
| return PseudoFixtureDef |
| raise |
| # remove indent to prevent the python3 exception |
| # from leaking into the call |
| result = self._getfuncargvalue(fixturedef) |
| self._funcargs[argname] = result |
| self._fixturedefs[argname] = fixturedef |
| return fixturedef |
| |
| def _get_fixturestack(self): |
| current = self |
| l = [] |
| while 1: |
| fixturedef = getattr(current, "_fixturedef", None) |
| if fixturedef is None: |
| l.reverse() |
| return l |
| l.append(fixturedef) |
| current = current._parent_request |
| |
| def _getfuncargvalue(self, fixturedef): |
| # prepare a subrequest object before calling fixture function |
| # (latter managed by fixturedef) |
| argname = fixturedef.argname |
| funcitem = self._pyfuncitem |
| scope = fixturedef.scope |
| try: |
| param = funcitem.callspec.getparam(argname) |
| except (AttributeError, ValueError): |
| param = NOTSET |
| param_index = 0 |
| else: |
| # indices might not be set if old-style metafunc.addcall() was used |
| param_index = funcitem.callspec.indices.get(argname, 0) |
| # if a parametrize invocation set a scope it will override |
| # the static scope defined with the fixture function |
| paramscopenum = funcitem.callspec._arg2scopenum.get(argname) |
| if paramscopenum is not None: |
| scope = scopes[paramscopenum] |
| |
| subrequest = SubRequest(self, scope, param, param_index, fixturedef) |
| |
| # check if a higher-level scoped fixture accesses a lower level one |
| subrequest._check_scope(argname, self.scope, scope) |
| |
| # clear sys.exc_info before invoking the fixture (python bug?) |
| # if its not explicitly cleared it will leak into the call |
| exc_clear() |
| try: |
| # call the fixture function |
| val = fixturedef.execute(request=subrequest) |
| finally: |
| # if fixture function failed it might have registered finalizers |
| self.session._setupstate.addfinalizer(fixturedef.finish, |
| subrequest.node) |
| return val |
| |
| def _check_scope(self, argname, invoking_scope, requested_scope): |
| if argname == "request": |
| return |
| if scopemismatch(invoking_scope, requested_scope): |
| # try to report something helpful |
| lines = self._factorytraceback() |
| pytest.fail("ScopeMismatch: You tried to access the %r scoped " |
| "fixture %r with a %r scoped request object, " |
| "involved factories\n%s" %( |
| (requested_scope, argname, invoking_scope, "\n".join(lines))), |
| pytrace=False) |
| |
| def _factorytraceback(self): |
| lines = [] |
| for fixturedef in self._get_fixturestack(): |
| factory = fixturedef.func |
| fs, lineno = getfslineno(factory) |
| p = self._pyfuncitem.session.fspath.bestrelpath(fs) |
| args = _format_args(factory) |
| lines.append("%s:%d: def %s%s" %( |
| p, lineno, factory.__name__, args)) |
| return lines |
| |
| def _getscopeitem(self, scope): |
| if scope == "function": |
| # this might also be a non-function Item despite its attribute name |
| return self._pyfuncitem |
| node = get_scope_node(self._pyfuncitem, scope) |
| if node is None and scope == "class": |
| # fallback to function item itself |
| node = self._pyfuncitem |
| assert node |
| return node |
| |
| def __repr__(self): |
| return "<FixtureRequest for %r>" %(self.node) |
| |
| |
| class SubRequest(FixtureRequest): |
| """ a sub request for handling getting a fixture from a |
| test function/fixture. """ |
| def __init__(self, request, scope, param, param_index, fixturedef): |
| self._parent_request = request |
| self.fixturename = fixturedef.argname |
| if param is not NOTSET: |
| self.param = param |
| self.param_index = param_index |
| self.scope = scope |
| self._fixturedef = fixturedef |
| self.addfinalizer = fixturedef.addfinalizer |
| self._pyfuncitem = request._pyfuncitem |
| self._funcargs = request._funcargs |
| self._fixturedefs = request._fixturedefs |
| self._arg2fixturedefs = request._arg2fixturedefs |
| self._arg2index = request._arg2index |
| self.fixturenames = request.fixturenames |
| self._fixturemanager = request._fixturemanager |
| |
| def __repr__(self): |
| return "<SubRequest %r for %r>" % (self.fixturename, self._pyfuncitem) |
| |
| |
| class ScopeMismatchError(Exception): |
| """ A fixture function tries to use a different fixture function which |
| which has a lower scope (e.g. a Session one calls a function one) |
| """ |
| |
| scopes = "session module class function".split() |
| scopenum_function = scopes.index("function") |
| def scopemismatch(currentscope, newscope): |
| return scopes.index(newscope) > scopes.index(currentscope) |
| |
| |
| class FixtureLookupError(LookupError): |
| """ could not return a requested Fixture (missing or invalid). """ |
| def __init__(self, argname, request, msg=None): |
| self.argname = argname |
| self.request = request |
| self.fixturestack = request._get_fixturestack() |
| self.msg = msg |
| |
| def formatrepr(self): |
| tblines = [] |
| addline = tblines.append |
| stack = [self.request._pyfuncitem.obj] |
| stack.extend(map(lambda x: x.func, self.fixturestack)) |
| msg = self.msg |
| if msg is not None: |
| # the last fixture raise an error, let's present |
| # it at the requesting side |
| stack = stack[:-1] |
| for function in stack: |
| fspath, lineno = getfslineno(function) |
| try: |
| lines, _ = inspect.getsourcelines(get_real_func(function)) |
| except (IOError, IndexError): |
| error_msg = "file %s, line %s: source code not available" |
| addline(error_msg % (fspath, lineno+1)) |
| else: |
| addline("file %s, line %s" % (fspath, lineno+1)) |
| for i, line in enumerate(lines): |
| line = line.rstrip() |
| addline(" " + line) |
| if line.lstrip().startswith('def'): |
| break |
| |
| if msg is None: |
| fm = self.request._fixturemanager |
| available = [] |
| for name, fixturedef in fm._arg2fixturedefs.items(): |
| parentid = self.request._pyfuncitem.parent.nodeid |
| faclist = list(fm._matchfactories(fixturedef, parentid)) |
| if faclist: |
| available.append(name) |
| msg = "fixture %r not found" % (self.argname,) |
| msg += "\n available fixtures: %s" %(", ".join(available),) |
| msg += "\n use 'py.test --fixtures [testpath]' for help on them." |
| |
| return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname) |
| |
| class FixtureLookupErrorRepr(TerminalRepr): |
| def __init__(self, filename, firstlineno, tblines, errorstring, argname): |
| self.tblines = tblines |
| self.errorstring = errorstring |
| self.filename = filename |
| self.firstlineno = firstlineno |
| self.argname = argname |
| |
| def toterminal(self, tw): |
| #tw.line("FixtureLookupError: %s" %(self.argname), red=True) |
| for tbline in self.tblines: |
| tw.line(tbline.rstrip()) |
| for line in self.errorstring.split("\n"): |
| tw.line(" " + line.strip(), red=True) |
| tw.line() |
| tw.line("%s:%d" % (self.filename, self.firstlineno+1)) |
| |
| class FixtureManager: |
| """ |
| pytest fixtures definitions and information is stored and managed |
| from this class. |
| |
| During collection fm.parsefactories() is called multiple times to parse |
| fixture function definitions into FixtureDef objects and internal |
| data structures. |
| |
| During collection of test functions, metafunc-mechanics instantiate |
| a FuncFixtureInfo object which is cached per node/func-name. |
| This FuncFixtureInfo object is later retrieved by Function nodes |
| which themselves offer a fixturenames attribute. |
| |
| The FuncFixtureInfo object holds information about fixtures and FixtureDefs |
| relevant for a particular function. An initial list of fixtures is |
| assembled like this: |
| |
| - ini-defined usefixtures |
| - autouse-marked fixtures along the collection chain up from the function |
| - usefixtures markers at module/class/function level |
| - test function funcargs |
| |
| Subsequently the funcfixtureinfo.fixturenames attribute is computed |
| as the closure of the fixtures needed to setup the initial fixtures, |
| i. e. fixtures needed by fixture functions themselves are appended |
| to the fixturenames list. |
| |
| Upon the test-setup phases all fixturenames are instantiated, retrieved |
| by a lookup of their FuncFixtureInfo. |
| """ |
| |
| _argprefix = "pytest_funcarg__" |
| FixtureLookupError = FixtureLookupError |
| FixtureLookupErrorRepr = FixtureLookupErrorRepr |
| |
| def __init__(self, session): |
| self.session = session |
| self.config = session.config |
| self._arg2fixturedefs = {} |
| self._holderobjseen = set() |
| self._arg2finish = {} |
| self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))] |
| session.config.pluginmanager.register(self, "funcmanage") |
| |
| |
| def getfixtureinfo(self, node, func, cls, funcargs=True): |
| if funcargs and not hasattr(node, "nofuncargs"): |
| if cls is not None: |
| startindex = 1 |
| else: |
| startindex = None |
| argnames = getfuncargnames(func, startindex) |
| else: |
| argnames = () |
| usefixtures = getattr(func, "usefixtures", None) |
| initialnames = argnames |
| if usefixtures is not None: |
| initialnames = usefixtures.args + initialnames |
| fm = node.session._fixturemanager |
| names_closure, arg2fixturedefs = fm.getfixtureclosure(initialnames, |
| node) |
| return FuncFixtureInfo(argnames, names_closure, arg2fixturedefs) |
| |
| def pytest_plugin_registered(self, plugin): |
| nodeid = None |
| try: |
| p = py.path.local(plugin.__file__) |
| except AttributeError: |
| pass |
| else: |
| # construct the base nodeid which is later used to check |
| # what fixtures are visible for particular tests (as denoted |
| # by their test id) |
| if p.basename.startswith("conftest.py"): |
| nodeid = p.dirpath().relto(self.config.rootdir) |
| if p.sep != "/": |
| nodeid = nodeid.replace(p.sep, "/") |
| self.parsefactories(plugin, nodeid) |
| |
| def _getautousenames(self, nodeid): |
| """ return a tuple of fixture names to be used. """ |
| autousenames = [] |
| for baseid, basenames in self._nodeid_and_autousenames: |
| if nodeid.startswith(baseid): |
| if baseid: |
| i = len(baseid) |
| nextchar = nodeid[i:i+1] |
| if nextchar and nextchar not in ":/": |
| continue |
| autousenames.extend(basenames) |
| # make sure autousenames are sorted by scope, scopenum 0 is session |
| autousenames.sort( |
| key=lambda x: self._arg2fixturedefs[x][-1].scopenum) |
| return autousenames |
| |
| def getfixtureclosure(self, fixturenames, parentnode): |
| # collect the closure of all fixtures , starting with the given |
| # fixturenames as the initial set. As we have to visit all |
| # factory definitions anyway, we also return a arg2fixturedefs |
| # mapping so that the caller can reuse it and does not have |
| # to re-discover fixturedefs again for each fixturename |
| # (discovering matching fixtures for a given name/node is expensive) |
| |
| parentid = parentnode.nodeid |
| fixturenames_closure = self._getautousenames(parentid) |
| def merge(otherlist): |
| for arg in otherlist: |
| if arg not in fixturenames_closure: |
| fixturenames_closure.append(arg) |
| merge(fixturenames) |
| arg2fixturedefs = {} |
| lastlen = -1 |
| while lastlen != len(fixturenames_closure): |
| lastlen = len(fixturenames_closure) |
| for argname in fixturenames_closure: |
| if argname in arg2fixturedefs: |
| continue |
| fixturedefs = self.getfixturedefs(argname, parentid) |
| if fixturedefs: |
| arg2fixturedefs[argname] = fixturedefs |
| merge(fixturedefs[-1].argnames) |
| return fixturenames_closure, arg2fixturedefs |
| |
| def pytest_generate_tests(self, metafunc): |
| for argname in metafunc.fixturenames: |
| faclist = metafunc._arg2fixturedefs.get(argname) |
| if faclist: |
| fixturedef = faclist[-1] |
| if fixturedef.params is not None: |
| func_params = getattr(getattr(metafunc.function, 'parametrize', None), 'args', [[None]]) |
| # skip directly parametrized arguments |
| argnames = func_params[0] |
| if not isinstance(argnames, (tuple, list)): |
| argnames = [x.strip() for x in argnames.split(",") if x.strip()] |
| if argname not in func_params and argname not in argnames: |
| metafunc.parametrize(argname, fixturedef.params, |
| indirect=True, scope=fixturedef.scope, |
| ids=fixturedef.ids) |
| else: |
| continue # will raise FixtureLookupError at setup time |
| |
| def pytest_collection_modifyitems(self, items): |
| # separate parametrized setups |
| items[:] = reorder_items(items) |
| |
| def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False): |
| if nodeid is not NOTSET: |
| holderobj = node_or_obj |
| else: |
| holderobj = node_or_obj.obj |
| nodeid = node_or_obj.nodeid |
| if holderobj in self._holderobjseen: |
| return |
| self._holderobjseen.add(holderobj) |
| autousenames = [] |
| for name in dir(holderobj): |
| obj = getattr(holderobj, name, None) |
| # fixture functions have a pytest_funcarg__ prefix (pre-2.3 style) |
| # or are "@pytest.fixture" marked |
| marker = getfixturemarker(obj) |
| if marker is None: |
| if not name.startswith(self._argprefix): |
| continue |
| if not callable(obj): |
| continue |
| marker = defaultfuncargprefixmarker |
| name = name[len(self._argprefix):] |
| elif not isinstance(marker, FixtureFunctionMarker): |
| # magic globals with __getattr__ might have got us a wrong |
| # fixture attribute |
| continue |
| else: |
| assert not name.startswith(self._argprefix) |
| fixturedef = FixtureDef(self, nodeid, name, obj, |
| marker.scope, marker.params, |
| yieldctx=marker.yieldctx, |
| unittest=unittest, ids=marker.ids) |
| faclist = self._arg2fixturedefs.setdefault(name, []) |
| if fixturedef.has_location: |
| faclist.append(fixturedef) |
| else: |
| # fixturedefs with no location are at the front |
| # so this inserts the current fixturedef after the |
| # existing fixturedefs from external plugins but |
| # before the fixturedefs provided in conftests. |
| i = len([f for f in faclist if not f.has_location]) |
| faclist.insert(i, fixturedef) |
| if marker.autouse: |
| autousenames.append(name) |
| if autousenames: |
| self._nodeid_and_autousenames.append((nodeid or '', autousenames)) |
| |
| def getfixturedefs(self, argname, nodeid): |
| try: |
| fixturedefs = self._arg2fixturedefs[argname] |
| except KeyError: |
| return None |
| else: |
| return tuple(self._matchfactories(fixturedefs, nodeid)) |
| |
| def _matchfactories(self, fixturedefs, nodeid): |
| for fixturedef in fixturedefs: |
| if nodeid.startswith(fixturedef.baseid): |
| yield fixturedef |
| |
| |
| def fail_fixturefunc(fixturefunc, msg): |
| fs, lineno = getfslineno(fixturefunc) |
| location = "%s:%s" % (fs, lineno+1) |
| source = _pytest._code.Source(fixturefunc) |
| pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, |
| pytrace=False) |
| |
| def call_fixture_func(fixturefunc, request, kwargs, yieldctx): |
| if yieldctx: |
| if not is_generator(fixturefunc): |
| fail_fixturefunc(fixturefunc, |
| msg="yield_fixture requires yield statement in function") |
| iter = fixturefunc(**kwargs) |
| next = getattr(iter, "__next__", None) |
| if next is None: |
| next = getattr(iter, "next") |
| res = next() |
| def teardown(): |
| try: |
| next() |
| except StopIteration: |
| pass |
| else: |
| fail_fixturefunc(fixturefunc, |
| "yield_fixture function has more than one 'yield'") |
| request.addfinalizer(teardown) |
| else: |
| if is_generator(fixturefunc): |
| fail_fixturefunc(fixturefunc, |
| msg="pytest.fixture functions cannot use ``yield``. " |
| "Instead write and return an inner function/generator " |
| "and let the consumer call and iterate over it.") |
| res = fixturefunc(**kwargs) |
| return res |
| |
| class FixtureDef: |
| """ A container for a factory definition. """ |
| def __init__(self, fixturemanager, baseid, argname, func, scope, params, |
| yieldctx, unittest=False, ids=None): |
| self._fixturemanager = fixturemanager |
| self.baseid = baseid or '' |
| self.has_location = baseid is not None |
| self.func = func |
| self.argname = argname |
| self.scope = scope |
| self.scopenum = scopes.index(scope or "function") |
| self.params = params |
| startindex = unittest and 1 or None |
| self.argnames = getfuncargnames(func, startindex=startindex) |
| self.yieldctx = yieldctx |
| self.unittest = unittest |
| self.ids = ids |
| self._finalizer = [] |
| |
| def addfinalizer(self, finalizer): |
| self._finalizer.append(finalizer) |
| |
| def finish(self): |
| try: |
| while self._finalizer: |
| func = self._finalizer.pop() |
| func() |
| finally: |
| # even if finalization fails, we invalidate |
| # the cached fixture value |
| if hasattr(self, "cached_result"): |
| del self.cached_result |
| |
| def execute(self, request): |
| # get required arguments and register our own finish() |
| # with their finalization |
| kwargs = {} |
| for argname in self.argnames: |
| fixturedef = request._get_active_fixturedef(argname) |
| result, arg_cache_key, exc = fixturedef.cached_result |
| request._check_scope(argname, request.scope, fixturedef.scope) |
| kwargs[argname] = result |
| if argname != "request": |
| fixturedef.addfinalizer(self.finish) |
| |
| my_cache_key = request.param_index |
| cached_result = getattr(self, "cached_result", None) |
| if cached_result is not None: |
| result, cache_key, err = cached_result |
| if my_cache_key == cache_key: |
| if err is not None: |
| py.builtin._reraise(*err) |
| else: |
| return result |
| # we have a previous but differently parametrized fixture instance |
| # so we need to tear it down before creating a new one |
| self.finish() |
| assert not hasattr(self, "cached_result") |
| |
| fixturefunc = self.func |
| |
| if self.unittest: |
| if request.instance is not None: |
| # bind the unbound method to the TestCase instance |
| fixturefunc = self.func.__get__(request.instance) |
| else: |
| # the fixture function needs to be bound to the actual |
| # request.instance so that code working with "self" behaves |
| # as expected. |
| if request.instance is not None: |
| fixturefunc = getimfunc(self.func) |
| if fixturefunc != self.func: |
| fixturefunc = fixturefunc.__get__(request.instance) |
| |
| try: |
| result = call_fixture_func(fixturefunc, request, kwargs, |
| self.yieldctx) |
| except Exception: |
| self.cached_result = (None, my_cache_key, sys.exc_info()) |
| raise |
| self.cached_result = (result, my_cache_key, None) |
| return result |
| |
| def __repr__(self): |
| return ("<FixtureDef name=%r scope=%r baseid=%r >" % |
| (self.argname, self.scope, self.baseid)) |
| |
| def num_mock_patch_args(function): |
| """ return number of arguments used up by mock arguments (if any) """ |
| patchings = getattr(function, "patchings", None) |
| if not patchings: |
| return 0 |
| mock = sys.modules.get("mock", sys.modules.get("unittest.mock", None)) |
| if mock is not None: |
| return len([p for p in patchings |
| if not p.attribute_name and p.new is mock.DEFAULT]) |
| return len(patchings) |
| |
| |
| def getfuncargnames(function, startindex=None): |
| # XXX merge with main.py's varnames |
| #assert not isclass(function) |
| realfunction = function |
| while hasattr(realfunction, "__wrapped__"): |
| realfunction = realfunction.__wrapped__ |
| if startindex is None: |
| startindex = inspect.ismethod(function) and 1 or 0 |
| if realfunction != function: |
| startindex += num_mock_patch_args(function) |
| function = realfunction |
| if isinstance(function, functools.partial): |
| argnames = inspect.getargs(_pytest._code.getrawcode(function.func))[0] |
| partial = function |
| argnames = argnames[len(partial.args):] |
| if partial.keywords: |
| for kw in partial.keywords: |
| argnames.remove(kw) |
| else: |
| argnames = inspect.getargs(_pytest._code.getrawcode(function))[0] |
| defaults = getattr(function, 'func_defaults', |
| getattr(function, '__defaults__', None)) or () |
| numdefaults = len(defaults) |
| if numdefaults: |
| return tuple(argnames[startindex:-numdefaults]) |
| return tuple(argnames[startindex:]) |
| |
| # algorithm for sorting on a per-parametrized resource setup basis |
| # it is called for scopenum==0 (session) first and performs sorting |
| # down to the lower scopes such as to minimize number of "high scope" |
| # setups and teardowns |
| |
| def reorder_items(items): |
| argkeys_cache = {} |
| for scopenum in range(0, scopenum_function): |
| argkeys_cache[scopenum] = d = {} |
| for item in items: |
| keys = set(get_parametrized_fixture_keys(item, scopenum)) |
| if keys: |
| d[item] = keys |
| return reorder_items_atscope(items, set(), argkeys_cache, 0) |
| |
| def reorder_items_atscope(items, ignore, argkeys_cache, scopenum): |
| if scopenum >= scopenum_function or len(items) < 3: |
| return items |
| items_done = [] |
| while 1: |
| items_before, items_same, items_other, newignore = \ |
| slice_items(items, ignore, argkeys_cache[scopenum]) |
| items_before = reorder_items_atscope( |
| items_before, ignore, argkeys_cache,scopenum+1) |
| if items_same is None: |
| # nothing to reorder in this scope |
| assert items_other is None |
| return items_done + items_before |
| items_done.extend(items_before) |
| items = items_same + items_other |
| ignore = newignore |
| |
| |
| def slice_items(items, ignore, scoped_argkeys_cache): |
| # we pick the first item which uses a fixture instance in the |
| # requested scope and which we haven't seen yet. We slice the input |
| # items list into a list of items_nomatch, items_same and |
| # items_other |
| if scoped_argkeys_cache: # do we need to do work at all? |
| it = iter(items) |
| # first find a slicing key |
| for i, item in enumerate(it): |
| argkeys = scoped_argkeys_cache.get(item) |
| if argkeys is not None: |
| argkeys = argkeys.difference(ignore) |
| if argkeys: # found a slicing key |
| slicing_argkey = argkeys.pop() |
| items_before = items[:i] |
| items_same = [item] |
| items_other = [] |
| # now slice the remainder of the list |
| for item in it: |
| argkeys = scoped_argkeys_cache.get(item) |
| if argkeys and slicing_argkey in argkeys and \ |
| slicing_argkey not in ignore: |
| items_same.append(item) |
| else: |
| items_other.append(item) |
| newignore = ignore.copy() |
| newignore.add(slicing_argkey) |
| return (items_before, items_same, items_other, newignore) |
| return items, None, None, None |
| |
| def get_parametrized_fixture_keys(item, scopenum): |
| """ return list of keys for all parametrized arguments which match |
| the specified scope. """ |
| assert scopenum < scopenum_function # function |
| try: |
| cs = item.callspec |
| except AttributeError: |
| pass |
| else: |
| # cs.indictes.items() is random order of argnames but |
| # then again different functions (items) can change order of |
| # arguments so it doesn't matter much probably |
| for argname, param_index in cs.indices.items(): |
| if cs._arg2scopenum[argname] != scopenum: |
| continue |
| if scopenum == 0: # session |
| key = (argname, param_index) |
| elif scopenum == 1: # module |
| key = (argname, param_index, item.fspath) |
| elif scopenum == 2: # class |
| key = (argname, param_index, item.fspath, item.cls) |
| yield key |
| |
| |
| def xunitsetup(obj, name): |
| meth = getattr(obj, name, None) |
| if getfixturemarker(meth) is None: |
| return meth |
| |
| def getfixturemarker(obj): |
| """ return fixturemarker or None if it doesn't exist or raised |
| exceptions.""" |
| try: |
| return getattr(obj, "_pytestfixturefunction", None) |
| except KeyboardInterrupt: |
| raise |
| except Exception: |
| # some objects raise errors like request (from flask import request) |
| # we don't expect them to be fixture functions |
| return None |
| |
| scopename2class = { |
| 'class': Class, |
| 'module': Module, |
| 'function': pytest.Item, |
| } |
| def get_scope_node(node, scope): |
| cls = scopename2class.get(scope) |
| if cls is None: |
| if scope == "session": |
| return node.session |
| raise ValueError("unknown scope") |
| return node.getparent(cls) |