merge conftest management into PytestPluginManager

--HG--
branch : plugin_no_pytest
This commit is contained in:
holger krekel 2015-04-22 14:15:42 +02:00
parent 894d7dca22
commit d632a0d5c2
5 changed files with 128 additions and 149 deletions

View File

@ -98,6 +98,12 @@ class PytestPluginManager(PluginManager):
self._warnings = [] self._warnings = []
self._plugin_distinfo = [] self._plugin_distinfo = []
self._globalplugins = [] self._globalplugins = []
# state related to local conftest plugins
self._path2confmods = {}
self._conftestpath2mod = {}
self._confcutdir = None
self.addhooks(hookspec) self.addhooks(hookspec)
self.register(self) self.register(self)
if os.environ.get('PYTEST_DEBUG'): if os.environ.get('PYTEST_DEBUG'):
@ -140,6 +146,89 @@ class PytestPluginManager(PluginManager):
for warning in self._warnings: for warning in self._warnings:
config.warn(code="I1", message=warning) config.warn(code="I1", message=warning)
#
# internal API for local conftest plugin handling
#
def _set_initial_conftests(self, namespace):
""" load initial conftest files given a preparsed "namespace".
As conftest files may add their own command line options
which have arguments ('--my-opt somepath') we might get some
false positives. All builtin and 3rd party plugins will have
been loaded, however, so common options will not confuse our logic
here.
"""
current = py.path.local()
self._confcutdir = current.join(namespace.confcutdir, abs=True) \
if namespace.confcutdir else None
testpaths = namespace.file_or_dir
foundanchor = False
for path in testpaths:
path = str(path)
# remove node-id syntax
i = path.find("::")
if i != -1:
path = path[:i]
anchor = current.join(path, abs=1)
if exists(anchor): # we found some file object
self._try_load_conftest(anchor)
foundanchor = True
if not foundanchor:
self._try_load_conftest(current)
def _try_load_conftest(self, anchor):
self._getconftestmodules(anchor)
# let's also consider test* subdirs
if anchor.check(dir=1):
for x in anchor.listdir("test*"):
if x.check(dir=1):
self._getconftestmodules(x)
def _getconftestmodules(self, path):
try:
return self._path2confmods[path]
except KeyError:
clist = []
for parent in path.parts():
if self._confcutdir and self._confcutdir.relto(parent):
continue
conftestpath = parent.join("conftest.py")
if conftestpath.check(file=1):
mod = self._importconftest(conftestpath)
clist.append(mod)
self._path2confmods[path] = clist
return clist
def _rget_with_confmod(self, name, path):
modules = self._getconftestmodules(path)
for mod in reversed(modules):
try:
return mod, getattr(mod, name)
except AttributeError:
continue
raise KeyError(name)
def _importconftest(self, conftestpath):
try:
return self._conftestpath2mod[conftestpath]
except KeyError:
pkgpath = conftestpath.pypkgpath()
if pkgpath is None:
_ensure_removed_sysmodule(conftestpath.purebasename)
try:
mod = conftestpath.pyimport()
except Exception:
raise ConftestImportFailure(conftestpath, sys.exc_info())
self._conftestpath2mod[conftestpath] = mod
dirpath = conftestpath.dirpath()
if dirpath in self._path2confmods:
for path, mods in self._path2confmods.items():
if path and path.relto(dirpath) or path == dirpath:
assert mod not in mods
mods.append(mod)
self.trace("loaded conftestmodule %r" %(mod))
self.consider_conftest(mod)
return mod
# #
# API for bootstrapping plugin loading # API for bootstrapping plugin loading
# #
@ -572,96 +661,6 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
return action._formatted_action_invocation return action._formatted_action_invocation
class Conftest(object):
""" the single place for accessing values and interacting
towards conftest modules from pytest objects.
"""
def __init__(self, onimport=None):
self._path2confmods = {}
self._onimport = onimport
self._conftestpath2mod = {}
self._confcutdir = None
def setinitial(self, namespace):
""" load initial conftest files given a preparsed "namespace".
As conftest files may add their own command line options
which have arguments ('--my-opt somepath') we might get some
false positives. All builtin and 3rd party plugins will have
been loaded, however, so common options will not confuse our logic
here.
"""
current = py.path.local()
self._confcutdir = current.join(namespace.confcutdir, abs=True) \
if namespace.confcutdir else None
testpaths = namespace.file_or_dir
foundanchor = False
for path in testpaths:
path = str(path)
# remove node-id syntax
i = path.find("::")
if i != -1:
path = path[:i]
anchor = current.join(path, abs=1)
if exists(anchor): # we found some file object
self._try_load_conftest(anchor)
foundanchor = True
if not foundanchor:
self._try_load_conftest(current)
def _try_load_conftest(self, anchor):
self.getconftestmodules(anchor)
# let's also consider test* subdirs
if anchor.check(dir=1):
for x in anchor.listdir("test*"):
if x.check(dir=1):
self.getconftestmodules(x)
def getconftestmodules(self, path):
try:
return self._path2confmods[path]
except KeyError:
clist = []
for parent in path.parts():
if self._confcutdir and self._confcutdir.relto(parent):
continue
conftestpath = parent.join("conftest.py")
if conftestpath.check(file=1):
mod = self.importconftest(conftestpath)
clist.append(mod)
self._path2confmods[path] = clist
return clist
def rget_with_confmod(self, name, path):
modules = self.getconftestmodules(path)
for mod in reversed(modules):
try:
return mod, getattr(mod, name)
except AttributeError:
continue
raise KeyError(name)
def importconftest(self, conftestpath):
try:
return self._conftestpath2mod[conftestpath]
except KeyError:
pkgpath = conftestpath.pypkgpath()
if pkgpath is None:
_ensure_removed_sysmodule(conftestpath.purebasename)
try:
mod = conftestpath.pyimport()
except Exception:
raise ConftestImportFailure(conftestpath, sys.exc_info())
self._conftestpath2mod[conftestpath] = mod
dirpath = conftestpath.dirpath()
if dirpath in self._path2confmods:
for path, mods in self._path2confmods.items():
if path and path.relto(dirpath) or path == dirpath:
assert mod not in mods
mods.append(mod)
if self._onimport:
self._onimport(mod)
return mod
def _ensure_removed_sysmodule(modname): def _ensure_removed_sysmodule(modname):
try: try:
@ -697,7 +696,6 @@ class Config(object):
#: a pluginmanager instance #: a pluginmanager instance
self.pluginmanager = pluginmanager self.pluginmanager = pluginmanager
self.trace = self.pluginmanager.trace.root.get("config") self.trace = self.pluginmanager.trace.root.get("config")
self._conftest = Conftest(onimport=self._onimportconftest)
self.hook = self.pluginmanager.hook self.hook = self.pluginmanager.hook
self._inicache = {} self._inicache = {}
self._opt2dest = {} self._opt2dest = {}
@ -783,10 +781,6 @@ class Config(object):
config.pluginmanager.consider_pluginarg(x) config.pluginmanager.consider_pluginarg(x)
return config return config
def _onimportconftest(self, conftestmodule):
self.trace("loaded conftestmodule %r" %(conftestmodule,))
self.pluginmanager.consider_conftest(conftestmodule)
def _processopt(self, opt): def _processopt(self, opt):
for name in opt._short_opts + opt._long_opts: for name in opt._short_opts + opt._long_opts:
self._opt2dest[name] = opt.dest self._opt2dest[name] = opt.dest
@ -797,10 +791,10 @@ class Config(object):
def _getmatchingplugins(self, fspath): def _getmatchingplugins(self, fspath):
return self.pluginmanager._globalplugins + \ return self.pluginmanager._globalplugins + \
self._conftest.getconftestmodules(fspath) self.pluginmanager._getconftestmodules(fspath)
def pytest_load_initial_conftests(self, early_config): def pytest_load_initial_conftests(self, early_config):
self._conftest.setinitial(early_config.known_args_namespace) self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
pytest_load_initial_conftests.trylast = True pytest_load_initial_conftests.trylast = True
def _initini(self, args): def _initini(self, args):
@ -907,7 +901,7 @@ class Config(object):
def _getconftest_pathlist(self, name, path): def _getconftest_pathlist(self, name, path):
try: try:
mod, relroots = self._conftest.rget_with_confmod(name, path) mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
except KeyError: except KeyError:
return None return None
modpath = py.path.local(mod.__file__).dirpath() modpath = py.path.local(mod.__file__).dirpath()

View File

@ -132,7 +132,7 @@ class DoctestModule(pytest.File):
def collect(self): def collect(self):
import doctest import doctest
if self.fspath.basename == "conftest.py": if self.fspath.basename == "conftest.py":
module = self.config._conftest.importconftest(self.fspath) module = self.config._conftest._importconftest(self.fspath)
else: else:
try: try:
module = self.fspath.pyimport() module = self.fspath.pyimport()

View File

@ -1487,7 +1487,7 @@ class TestAutouseManagement:
reprec = testdir.inline_run("-v","-s") reprec = testdir.inline_run("-v","-s")
reprec.assertoutcome(passed=8) reprec.assertoutcome(passed=8)
config = reprec.getcalls("pytest_unconfigure")[0].config config = reprec.getcalls("pytest_unconfigure")[0].config
l = config._conftest.getconftestmodules(p)[0].l l = config.pluginmanager._getconftestmodules(p)[0].l
assert l == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2 assert l == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2
def test_scope_ordering(self, testdir): def test_scope_ordering(self, testdir):

View File

@ -1,7 +1,6 @@
from textwrap import dedent from textwrap import dedent
import py, pytest import py, pytest
from _pytest.config import Conftest from _pytest.config import PytestPluginManager
@pytest.fixture(scope="module", params=["global", "inpackage"]) @pytest.fixture(scope="module", params=["global", "inpackage"])
@ -16,7 +15,7 @@ def basedir(request):
return tmpdir return tmpdir
def ConftestWithSetinitial(path): def ConftestWithSetinitial(path):
conftest = Conftest() conftest = PytestPluginManager()
conftest_setinitial(conftest, [path]) conftest_setinitial(conftest, [path])
return conftest return conftest
@ -25,51 +24,41 @@ def conftest_setinitial(conftest, args, confcutdir=None):
def __init__(self): def __init__(self):
self.file_or_dir = args self.file_or_dir = args
self.confcutdir = str(confcutdir) self.confcutdir = str(confcutdir)
conftest.setinitial(Namespace()) conftest._set_initial_conftests(Namespace())
class TestConftestValueAccessGlobal: class TestConftestValueAccessGlobal:
def test_basic_init(self, basedir): def test_basic_init(self, basedir):
conftest = Conftest() conftest = PytestPluginManager()
p = basedir.join("adir") p = basedir.join("adir")
assert conftest.rget_with_confmod("a", p)[1] == 1 assert conftest._rget_with_confmod("a", p)[1] == 1
def test_onimport(self, basedir):
l = []
conftest = Conftest(onimport=l.append)
adir = basedir.join("adir")
conftest_setinitial(conftest, [adir], confcutdir=basedir)
assert len(l) == 1
assert conftest.rget_with_confmod("a", adir)[1] == 1
assert conftest.rget_with_confmod("b", adir.join("b"))[1] == 2
assert len(l) == 2
def test_immediate_initialiation_and_incremental_are_the_same(self, basedir): def test_immediate_initialiation_and_incremental_are_the_same(self, basedir):
conftest = Conftest() conftest = PytestPluginManager()
len(conftest._path2confmods) len(conftest._path2confmods)
conftest.getconftestmodules(basedir) conftest._getconftestmodules(basedir)
snap1 = len(conftest._path2confmods) snap1 = len(conftest._path2confmods)
#assert len(conftest._path2confmods) == snap1 + 1 #assert len(conftest._path2confmods) == snap1 + 1
conftest.getconftestmodules(basedir.join('adir')) conftest._getconftestmodules(basedir.join('adir'))
assert len(conftest._path2confmods) == snap1 + 1 assert len(conftest._path2confmods) == snap1 + 1
conftest.getconftestmodules(basedir.join('b')) conftest._getconftestmodules(basedir.join('b'))
assert len(conftest._path2confmods) == snap1 + 2 assert len(conftest._path2confmods) == snap1 + 2
def test_value_access_not_existing(self, basedir): def test_value_access_not_existing(self, basedir):
conftest = ConftestWithSetinitial(basedir) conftest = ConftestWithSetinitial(basedir)
with pytest.raises(KeyError): with pytest.raises(KeyError):
conftest.rget_with_confmod('a', basedir) conftest._rget_with_confmod('a', basedir)
def test_value_access_by_path(self, basedir): def test_value_access_by_path(self, basedir):
conftest = ConftestWithSetinitial(basedir) conftest = ConftestWithSetinitial(basedir)
adir = basedir.join("adir") adir = basedir.join("adir")
assert conftest.rget_with_confmod("a", adir)[1] == 1 assert conftest._rget_with_confmod("a", adir)[1] == 1
assert conftest.rget_with_confmod("a", adir.join("b"))[1] == 1.5 assert conftest._rget_with_confmod("a", adir.join("b"))[1] == 1.5
def test_value_access_with_confmod(self, basedir): def test_value_access_with_confmod(self, basedir):
startdir = basedir.join("adir", "b") startdir = basedir.join("adir", "b")
startdir.ensure("xx", dir=True) startdir.ensure("xx", dir=True)
conftest = ConftestWithSetinitial(startdir) conftest = ConftestWithSetinitial(startdir)
mod, value = conftest.rget_with_confmod("a", startdir) mod, value = conftest._rget_with_confmod("a", startdir)
assert value == 1.5 assert value == 1.5
path = py.path.local(mod.__file__) path = py.path.local(mod.__file__)
assert path.dirpath() == basedir.join("adir", "b") assert path.dirpath() == basedir.join("adir", "b")
@ -85,9 +74,9 @@ def test_conftest_in_nonpkg_with_init(tmpdir):
def test_doubledash_considered(testdir): def test_doubledash_considered(testdir):
conf = testdir.mkdir("--option") conf = testdir.mkdir("--option")
conf.join("conftest.py").ensure() conf.join("conftest.py").ensure()
conftest = Conftest() conftest = PytestPluginManager()
conftest_setinitial(conftest, [conf.basename, conf.basename]) conftest_setinitial(conftest, [conf.basename, conf.basename])
l = conftest.getconftestmodules(conf) l = conftest._getconftestmodules(conf)
assert len(l) == 1 assert len(l) == 1
def test_issue151_load_all_conftests(testdir): def test_issue151_load_all_conftests(testdir):
@ -96,7 +85,7 @@ def test_issue151_load_all_conftests(testdir):
p = testdir.mkdir(name) p = testdir.mkdir(name)
p.ensure("conftest.py") p.ensure("conftest.py")
conftest = Conftest() conftest = PytestPluginManager()
conftest_setinitial(conftest, names) conftest_setinitial(conftest, names)
d = list(conftest._conftestpath2mod.values()) d = list(conftest._conftestpath2mod.values())
assert len(d) == len(names) assert len(d) == len(names)
@ -105,15 +94,15 @@ def test_conftest_global_import(testdir):
testdir.makeconftest("x=3") testdir.makeconftest("x=3")
p = testdir.makepyfile(""" p = testdir.makepyfile("""
import py, pytest import py, pytest
from _pytest.config import Conftest from _pytest.config import PytestPluginManager
conf = Conftest() conf = PytestPluginManager()
mod = conf.importconftest(py.path.local("conftest.py")) mod = conf._importconftest(py.path.local("conftest.py"))
assert mod.x == 3 assert mod.x == 3
import conftest import conftest
assert conftest is mod, (conftest, mod) assert conftest is mod, (conftest, mod)
subconf = py.path.local().ensure("sub", "conftest.py") subconf = py.path.local().ensure("sub", "conftest.py")
subconf.write("y=4") subconf.write("y=4")
mod2 = conf.importconftest(subconf) mod2 = conf._importconftest(subconf)
assert mod != mod2 assert mod != mod2
assert mod2.y == 4 assert mod2.y == 4
import conftest import conftest
@ -125,27 +114,27 @@ def test_conftest_global_import(testdir):
def test_conftestcutdir(testdir): def test_conftestcutdir(testdir):
conf = testdir.makeconftest("") conf = testdir.makeconftest("")
p = testdir.mkdir("x") p = testdir.mkdir("x")
conftest = Conftest() conftest = PytestPluginManager()
conftest_setinitial(conftest, [testdir.tmpdir], confcutdir=p) conftest_setinitial(conftest, [testdir.tmpdir], confcutdir=p)
l = conftest.getconftestmodules(p) l = conftest._getconftestmodules(p)
assert len(l) == 0 assert len(l) == 0
l = conftest.getconftestmodules(conf.dirpath()) l = conftest._getconftestmodules(conf.dirpath())
assert len(l) == 0 assert len(l) == 0
assert conf not in conftest._conftestpath2mod assert conf not in conftest._conftestpath2mod
# but we can still import a conftest directly # but we can still import a conftest directly
conftest.importconftest(conf) conftest._importconftest(conf)
l = conftest.getconftestmodules(conf.dirpath()) l = conftest._getconftestmodules(conf.dirpath())
assert l[0].__file__.startswith(str(conf)) assert l[0].__file__.startswith(str(conf))
# and all sub paths get updated properly # and all sub paths get updated properly
l = conftest.getconftestmodules(p) l = conftest._getconftestmodules(p)
assert len(l) == 1 assert len(l) == 1
assert l[0].__file__.startswith(str(conf)) assert l[0].__file__.startswith(str(conf))
def test_conftestcutdir_inplace_considered(testdir): def test_conftestcutdir_inplace_considered(testdir):
conf = testdir.makeconftest("") conf = testdir.makeconftest("")
conftest = Conftest() conftest = PytestPluginManager()
conftest_setinitial(conftest, [conf.dirpath()], confcutdir=conf.dirpath()) conftest_setinitial(conftest, [conf.dirpath()], confcutdir=conf.dirpath())
l = conftest.getconftestmodules(conf.dirpath()) l = conftest._getconftestmodules(conf.dirpath())
assert len(l) == 1 assert len(l) == 1
assert l[0].__file__.startswith(str(conf)) assert l[0].__file__.startswith(str(conf))
@ -153,7 +142,7 @@ def test_conftestcutdir_inplace_considered(testdir):
def test_setinitial_conftest_subdirs(testdir, name): def test_setinitial_conftest_subdirs(testdir, name):
sub = testdir.mkdir(name) sub = testdir.mkdir(name)
subconftest = sub.ensure("conftest.py") subconftest = sub.ensure("conftest.py")
conftest = Conftest() conftest = PytestPluginManager()
conftest_setinitial(conftest, [sub.dirpath()], confcutdir=testdir.tmpdir) conftest_setinitial(conftest, [sub.dirpath()], confcutdir=testdir.tmpdir)
if name not in ('whatever', '.dotdir'): if name not in ('whatever', '.dotdir'):
assert subconftest in conftest._conftestpath2mod assert subconftest in conftest._conftestpath2mod
@ -199,9 +188,9 @@ def test_conftest_import_order(testdir, monkeypatch):
ct2.write("") ct2.write("")
def impct(p): def impct(p):
return p return p
conftest = Conftest() conftest = PytestPluginManager()
monkeypatch.setattr(conftest, 'importconftest', impct) monkeypatch.setattr(conftest, '_importconftest', impct)
assert conftest.getconftestmodules(sub) == [ct1, ct2] assert conftest._getconftestmodules(sub) == [ct1, ct2]
def test_fixture_dependency(testdir, monkeypatch): def test_fixture_dependency(testdir, monkeypatch):

View File

@ -94,7 +94,7 @@ class TestPytestPluginInteractions:
return xyz + 1 return xyz + 1
""") """)
config = get_plugin_manager().config config = get_plugin_manager().config
config._conftest.importconftest(conf) config.pluginmanager._importconftest(conf)
print(config.pluginmanager.getplugins()) print(config.pluginmanager.getplugins())
res = config.hook.pytest_myhook(xyz=10) res = config.hook.pytest_myhook(xyz=10)
assert res == [11] assert res == [11]
@ -143,7 +143,7 @@ class TestPytestPluginInteractions:
parser.addoption('--test123', action="store_true", parser.addoption('--test123', action="store_true",
default=True) default=True)
""") """)
config._conftest.importconftest(p) config.pluginmanager._importconftest(p)
assert config.option.test123 assert config.option.test123
def test_configure(self, testdir): def test_configure(self, testdir):
@ -849,10 +849,6 @@ class TestPytestPluginManager:
mod = pytestpm.getplugin("pkg.plug") mod = pytestpm.getplugin("pkg.plug")
assert mod.x == 3 assert mod.x == 3
def test_config_sets_conftesthandle_onimport(self, testdir):
config = testdir.parseconfig([])
assert config._conftest._onimport == config._onimportconftest
def test_consider_conftest_deps(self, testdir, pytestpm): def test_consider_conftest_deps(self, testdir, pytestpm):
mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport() mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport()
with pytest.raises(ImportError): with pytest.raises(ImportError):