diff --git a/CHANGELOG b/CHANGELOG index 54ee42296..c997782bc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,9 @@ Changes between 1.X and 1.1.1 - new option: --ignore will prevent specified path from collection. Can be specified multiple times. +- new option: --confcutdir=dir will make py.test only consider conftest + files that are relative to the specified dir. + - install 'py.test' and `py.which` with a ``-$VERSION`` suffix to disambiguate between Python3, python2.X, Jython and PyPy installed versions. diff --git a/py/impl/test/conftesthandle.py b/py/impl/test/conftesthandle.py index 49b7ad3fc..fcfae18bf 100644 --- a/py/impl/test/conftesthandle.py +++ b/py/impl/test/conftesthandle.py @@ -8,10 +8,11 @@ class Conftest(object): Note that triggering Conftest instances to import conftest.py files may result in added cmdline options. """ - def __init__(self, onimport=None): + def __init__(self, onimport=None, confcutdir=None): self._path2confmods = {} self._onimport = onimport self._conftestpath2mod = {} + self._confcutdir = confcutdir def setinitial(self, args): """ try to find a first anchor path for looking up global values @@ -23,6 +24,17 @@ class Conftest(object): bootstrapped ... """ current = py.path.local() + opt = '--confcutdir' + for i in range(len(args)): + opt1 = str(args[i]) + if opt1.startswith(opt): + if opt1 == opt: + if len(args) > i: + p = current.join(args[i+1], abs=True) + elif opt1.startswith(opt + "="): + p = current.join(opt1[len(opt)+1:], abs=1) + self._confcutdir = p + break for arg in args + [current]: anchor = current.join(arg, abs=1) if anchor.check(): # we found some file object @@ -37,16 +49,20 @@ class Conftest(object): clist = self._path2confmods[path] except KeyError: if path is None: - raise ValueError("missing default conftest.") + raise ValueError("missing default confest.") dp = path.dirpath() if dp == path: - clist = self._path2confmods[path] = [] + clist = [] else: + cutdir = self._confcutdir clist = self.getconftestmodules(dp) - conftestpath = path.join("conftest.py") - if conftestpath.check(file=1): - clist.append(self.importconftest(conftestpath)) - self._path2confmods[path] = clist + if cutdir and path != cutdir and not path.relto(cutdir): + pass + else: + conftestpath = path.join("conftest.py") + if conftestpath.check(file=1): + clist.append(self.importconftest(conftestpath)) + self._path2confmods[path] = clist # be defensive: avoid changes from caller side to # affect us by always returning a copy of the actual list return clist[:] @@ -77,8 +93,14 @@ class Conftest(object): mod = conftestpath.pyimport(modname=modname) else: mod = conftestpath.pyimport() - self._postimport(mod) 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._postimport(mod) return mod def _postimport(self, mod): diff --git a/py/plugin/pytest_default.py b/py/plugin/pytest_default.py index 9257ac3fb..e65e7e6c6 100644 --- a/py/plugin/pytest_default.py +++ b/py/plugin/pytest_default.py @@ -73,7 +73,9 @@ def pytest_addoption(parser): "test process debugging and configuration") group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir", help="base temporary directory for this test run.") - + group.addoption('--confcutdir', dest="confcutdir", default=None, + metavar="dir", + help="only load conftest.py's relative to specified dir.") if execnet: add_dist_options(parser) else: diff --git a/testing/plugin/test_pytest_default.py b/testing/plugin/test_pytest_default.py index e95d79d43..e8c4a3724 100644 --- a/testing/plugin/test_pytest_default.py +++ b/testing/plugin/test_pytest_default.py @@ -92,3 +92,14 @@ def test_pytest_report_iteminfo(): res = pytest_report_iteminfo(FakeItem()) assert res == "-reportinfo-" + + +def test_conftest_confcutdir(testdir): + testdir.makeconftest("assert 0") + x = testdir.mkdir("x") + x.join("conftest.py").write(py.code.Source(""" + def pytest_addoption(parser): + parser.addoption("--xyz", action="store_true") + """)) + result = testdir.runpytest("-h", "--confcutdir=%s" % x, x) + assert result.stdout.fnmatch_lines(["*--xyz*"]) diff --git a/testing/pytest/test_conftesthandle.py b/testing/pytest/test_conftesthandle.py index 58415e9c3..dc95350df 100644 --- a/testing/pytest/test_conftesthandle.py +++ b/testing/pytest/test_conftesthandle.py @@ -89,3 +89,46 @@ class TestConftestValueAccessGlobal: path = py.path.local(mod.__file__) assert path.dirpath() == basedir.join("adir", "b") assert path.purebasename == "conftest" + +def test_conftestcutdir(testdir): + conf = testdir.makeconftest("") + p = testdir.mkdir("x") + conftest = Conftest(confcutdir=p) + conftest.setinitial([testdir.tmpdir]) + l = conftest.getconftestmodules(p) + assert len(l) == 0 + l = conftest.getconftestmodules(conf.dirpath()) + assert len(l) == 0 + assert conf not in conftest._conftestpath2mod + # but we can still import a conftest directly + conftest.importconftest(conf) + l = conftest.getconftestmodules(conf.dirpath()) + assert l[0].__file__.startswith(str(conf)) + # and all sub paths get updated properly + l = conftest.getconftestmodules(p) + assert len(l) == 1 + assert l[0].__file__.startswith(str(conf)) + +def test_conftestcutdir_inplace_considered(testdir): + conf = testdir.makeconftest("") + conftest = Conftest(confcutdir=conf.dirpath()) + conftest.setinitial([conf.dirpath()]) + l = conftest.getconftestmodules(conf.dirpath()) + assert len(l) == 1 + assert l[0].__file__.startswith(str(conf)) + +def test_setinitial_confcut(testdir): + conf = testdir.makeconftest("") + sub = testdir.mkdir("sub") + sub.chdir() + for opts in (["--confcutdir=%s" % sub, sub], + [sub, "--confcutdir=%s" % sub], + ["--confcutdir=.", sub], + [sub, "--confcutdir", sub], + [str(sub), "--confcutdir", "."], + ): + conftest = Conftest() + conftest.setinitial(opts) + assert conftest._confcutdir == sub + assert conftest.getconftestmodules(sub) == [] + assert conftest.getconftestmodules(conf.dirpath()) == []