Don't stop at the first package when looking up package-scoped fixtures.

Example:
package1.subpackage1
package1.subpackage2

package1's setup/teardown were executed again when exiting subpackage1 and entering subpackage2.
This commit is contained in:
turturica 2018-04-20 15:04:58 -07:00
parent 7d923c389e
commit c416b1d935
3 changed files with 51 additions and 4 deletions

View File

@ -2,6 +2,7 @@ from __future__ import absolute_import, division, print_function
import functools import functools
import inspect import inspect
import os
import sys import sys
import warnings import warnings
from collections import OrderedDict, deque, defaultdict from collections import OrderedDict, deque, defaultdict
@ -70,6 +71,17 @@ def scopeproperty(name=None, doc=None):
return decoratescope 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): def get_scope_node(node, scope):
cls = scopename2class.get(scope) cls = scopename2class.get(scope)
if cls is None: if cls is None:
@ -558,7 +570,10 @@ class FixtureRequest(FuncargnamesCompatAttr):
if scope == "function": if scope == "function":
# this might also be a non-function Item despite its attribute name # this might also be a non-function Item despite its attribute name
return self._pyfuncitem 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": if node is None and scope == "class":
# fallback to function item itself # fallback to function item itself
node = self._pyfuncitem node = self._pyfuncitem

View File

@ -116,6 +116,7 @@ class Node(object):
Function = _CompatProperty("Function") Function = _CompatProperty("Function")
File = _CompatProperty("File") File = _CompatProperty("File")
Item = _CompatProperty("Item") Item = _CompatProperty("Item")
Package = _CompatProperty("Package")
def _getcustomclass(self, name): def _getcustomclass(self, name):
maybe_compatprop = getattr(type(self), name) maybe_compatprop = getattr(type(self), name)

View File

@ -13,11 +13,11 @@ from itertools import count
import py import py
import six import six
from _pytest.main import FSHookProxy
from _pytest.mark import MarkerError from _pytest.mark import MarkerError
from _pytest.config import hookimpl from _pytest.config import hookimpl
import _pytest import _pytest
from _pytest.main import Session
import pluggy import pluggy
from _pytest import fixtures from _pytest import fixtures
from _pytest import nodes from _pytest import nodes
@ -490,7 +490,7 @@ class Module(nodes.File, PyCollector):
self.addfinalizer(teardown_module) self.addfinalizer(teardown_module)
class Package(Session, Module): class Package(Module):
def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None): def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None):
session = parent.session session = parent.session
@ -503,7 +503,38 @@ class Package(Session, Module):
for path in list(session.config.pluginmanager._duplicatepaths): for path in list(session.config.pluginmanager._duplicatepaths):
if path.dirname == fspath.dirname and path != fspath: if path.dirname == fspath.dirname and path != fspath:
session.config.pluginmanager._duplicatepaths.remove(path) 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): def isinitpath(self, path):
return path in self.session._initialpaths return path in self.session._initialpaths