diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index a71cf12bf..e4bb91514 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, division, print_function import functools import inspect +import os import sys import warnings from collections import OrderedDict, deque, defaultdict @@ -70,6 +71,17 @@ def scopeproperty(name=None, doc=None): return decoratescope +def get_scope_package(node, fixturedef): + cls = node.Package + current = node + fixture_package_name = os.path.join(fixturedef.baseid, '__init__.py') + while current and type(current) is not cls or \ + fixture_package_name != current.nodeid: + current = current.parent + assert current + return current + + def get_scope_node(node, scope): cls = scopename2class.get(scope) if cls is None: @@ -558,7 +570,10 @@ class FixtureRequest(FuncargnamesCompatAttr): 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 scope == 'package': + node = get_scope_package(self._pyfuncitem, self._fixturedef) + else: + node = get_scope_node(self._pyfuncitem, scope) if node is None and scope == "class": # fallback to function item itself node = self._pyfuncitem diff --git a/_pytest/nodes.py b/_pytest/nodes.py index 799ee078a..dfe63b220 100644 --- a/_pytest/nodes.py +++ b/_pytest/nodes.py @@ -116,6 +116,7 @@ class Node(object): Function = _CompatProperty("Function") File = _CompatProperty("File") Item = _CompatProperty("Item") + Package = _CompatProperty("Package") def _getcustomclass(self, name): maybe_compatprop = getattr(type(self), name) diff --git a/_pytest/python.py b/_pytest/python.py index de659e517..fa5de427e 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -13,11 +13,11 @@ from itertools import count import py import six +from _pytest.main import FSHookProxy from _pytest.mark import MarkerError from _pytest.config import hookimpl import _pytest -from _pytest.main import Session import pluggy from _pytest import fixtures from _pytest import nodes @@ -490,7 +490,7 @@ class Module(nodes.File, PyCollector): self.addfinalizer(teardown_module) -class Package(Session, Module): +class Package(Module): def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None): session = parent.session @@ -503,7 +503,38 @@ class Package(Session, Module): for path in list(session.config.pluginmanager._duplicatepaths): if path.dirname == fspath.dirname and path != fspath: session.config.pluginmanager._duplicatepaths.remove(path) - pass + + def _recurse(self, path): + ihook = self.gethookproxy(path.dirpath()) + if ihook.pytest_ignore_collect(path=path, config=self.config): + return + for pat in self._norecursepatterns: + if path.check(fnmatch=pat): + return False + ihook = self.gethookproxy(path) + ihook.pytest_collect_directory(path=path, parent=self) + return True + + def gethookproxy(self, fspath): + # check if we have the common case of running + # hooks with all conftest.py filesall conftest.py + pm = self.config.pluginmanager + my_conftestmodules = pm._getconftestmodules(fspath) + remove_mods = pm._conftest_plugins.difference(my_conftestmodules) + if remove_mods: + # one or more conftests are not in use at this fspath + proxy = FSHookProxy(fspath, pm, remove_mods) + else: + # all plugis are active for this fspath + proxy = self.config.hook + return proxy + + def _collectfile(self, path): + ihook = self.gethookproxy(path) + if not self.isinitpath(path): + if ihook.pytest_ignore_collect(path=path, config=self.config): + return () + return ihook.pytest_collect_file(path=path, parent=self) def isinitpath(self, path): return path in self.session._initialpaths