fix issue436: improved finding of initial conftest files from command

line arguments by using the result of parse_known_args rather than
the previous flaky heuristics.  Thanks Marc Abramowitz for tests
and initial fixing approaches in this area.
This commit is contained in:
holger krekel 2014-04-02 11:29:23 +02:00
parent 72b4534a0c
commit 3bca62e9e4
5 changed files with 63 additions and 58 deletions

View File

@ -11,6 +11,11 @@ NEXT (2.6)
- change skips into warnings for test classes with an __init__ and
callables in test modules which look like a test but are not functions.
- fix issue436: improved finding of initial conftest files from command
line arguments by using the result of parse_known_args rather than
the previous flaky heuristics. Thanks Marc Abramowitz for tests
and initial fixing approaches in this area.
- fix issue #479: properly handle nose/unittest(2) SkipTest exceptions
during collection/loading of test modules. Thanks to Marc Schlaich
for the complete PR.

View File

@ -31,7 +31,7 @@ def pytest_addoption(parser):
@pytest.mark.tryfirst
def pytest_load_initial_conftests(early_config, parser, args, __multicall__):
ns = parser.parse_known_args(args)
ns = early_config.known_args_namespace
pluginmanager = early_config.pluginmanager
if ns.capture == "no":
return

View File

@ -449,38 +449,27 @@ class Conftest(object):
""" the single place for accessing values and interacting
towards conftest modules from pytest objects.
"""
def __init__(self, onimport=None, confcutdir=None):
def __init__(self, onimport=None):
self._path2confmods = {}
self._onimport = onimport
self._conftestpath2mod = {}
self._confcutdir = confcutdir
self._confcutdir = None
def setinitial(self, args):
""" try to find a first anchor path for looking up global values
from conftests. This function is usually called _before_
argument parsing. conftest files may add command line options
and we thus have no completely safe way of determining
which parts of the arguments are actually related to options
and which are file system paths. We just try here to get
bootstrapped ...
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()
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
self._confcutdir = current.join(namespace.confcutdir, abs=True) \
if namespace.confcutdir else None
testpaths = namespace.file_or_dir
foundanchor = False
for arg in args:
if hasattr(arg, 'startswith') and arg.startswith("--"):
continue
anchor = current.join(arg, abs=1)
for path in testpaths:
anchor = current.join(path, abs=1)
if exists(anchor): # we found some file object
self._try_load_conftest(anchor)
foundanchor = True
@ -676,8 +665,8 @@ class Config(object):
plugins += self._conftest.getconftestmodules(fspath)
return plugins
def pytest_load_initial_conftests(self, parser, args):
self._conftest.setinitial(args)
def pytest_load_initial_conftests(self, early_config):
self._conftest.setinitial(early_config.known_args_namespace)
pytest_load_initial_conftests.trylast = True
def _initini(self, args):
@ -693,6 +682,7 @@ class Config(object):
self.pluginmanager.consider_preparse(args)
self.pluginmanager.consider_setuptools_entrypoints()
self.pluginmanager.consider_env()
self.known_args_namespace = self._parser.parse_known_args(args)
self.hook.pytest_load_initial_conftests(early_config=self,
args=args, parser=self._parser)
@ -710,7 +700,6 @@ class Config(object):
def parse(self, args):
# parse given cmdline arguments into this config object.
# Note that this can only be called once per testing process.
assert not hasattr(self, 'args'), (
"can only parse cmdline args at most once per Config object")
self._origargs = args

View File

@ -148,7 +148,7 @@ class TestConfigAPI:
assert config.getvalue('x') == 1
config.option.x = 2
assert config.getvalue('x') == 2
config = testdir.parseconfig([str(o)])
config = testdir.parseconfig(str(o))
assert config.getvalue('x') == 1
def test_getconftest_pathlist(self, testdir, tmpdir):

View File

@ -20,19 +20,26 @@ def pytest_funcarg__basedir(request):
def ConftestWithSetinitial(path):
conftest = Conftest()
conftest.setinitial([path])
conftest_setinitial(conftest, [path])
return conftest
def conftest_setinitial(conftest, args, confcutdir=None):
class Namespace:
def __init__(self):
self.file_or_dir = args
self.confcutdir = str(confcutdir)
conftest.setinitial(Namespace())
class TestConftestValueAccessGlobal:
def test_basic_init(self, basedir):
conftest = Conftest()
conftest.setinitial([basedir.join("adir")])
conftest_setinitial(conftest, [basedir.join("adir")])
assert conftest.rget("a") == 1
def test_onimport(self, basedir):
l = []
conftest = Conftest(onimport=l.append)
conftest.setinitial([basedir.join("adir"),
conftest_setinitial(conftest, [basedir.join("adir"),
'--confcutdir=%s' % basedir])
assert len(l) == 1
assert conftest.rget("a") == 1
@ -99,13 +106,13 @@ def test_conftest_in_nonpkg_with_init(tmpdir):
tmpdir.ensure("adir-1.0/__init__.py")
ConftestWithSetinitial(tmpdir.join("adir-1.0", "b"))
def test_doubledash_not_considered(testdir):
def test_doubledash_considered(testdir):
conf = testdir.mkdir("--option")
conf.join("conftest.py").ensure()
conftest = Conftest()
conftest.setinitial([conf.basename, conf.basename])
conftest_setinitial(conftest, [conf.basename, conf.basename])
l = conftest.getconftestmodules(None)
assert len(l) == 0
assert len(l) == 1
def test_issue151_load_all_conftests(testdir):
names = "code proj src".split()
@ -114,7 +121,7 @@ def test_issue151_load_all_conftests(testdir):
p.ensure("conftest.py")
conftest = Conftest()
conftest.setinitial(names)
conftest_setinitial(conftest, names)
d = list(conftest._conftestpath2mod.values())
assert len(d) == len(names)
@ -142,8 +149,8 @@ def test_conftest_global_import(testdir):
def test_conftestcutdir(testdir):
conf = testdir.makeconftest("")
p = testdir.mkdir("x")
conftest = Conftest(confcutdir=p)
conftest.setinitial([testdir.tmpdir])
conftest = Conftest()
conftest_setinitial(conftest, [testdir.tmpdir], confcutdir=p)
l = conftest.getconftestmodules(p)
assert len(l) == 0
l = conftest.getconftestmodules(conf.dirpath())
@ -160,34 +167,18 @@ def test_conftestcutdir(testdir):
def test_conftestcutdir_inplace_considered(testdir):
conf = testdir.makeconftest("")
conftest = Conftest(confcutdir=conf.dirpath())
conftest.setinitial([conf.dirpath()])
conftest = Conftest()
conftest_setinitial(conftest, [conf.dirpath()], confcutdir=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()) == []
@pytest.mark.parametrize("name", 'test tests whatever .dotdir'.split())
def test_setinitial_conftest_subdirs(testdir, name):
sub = testdir.mkdir(name)
subconftest = sub.ensure("conftest.py")
conftest = Conftest()
conftest.setinitial([sub.dirpath(), '--confcutdir=%s' % testdir.tmpdir])
conftest_setinitial(conftest, [sub.dirpath()], confcutdir=testdir.tmpdir)
if name not in ('whatever', '.dotdir'):
assert subconftest in conftest._conftestpath2mod
assert len(conftest._conftestpath2mod) == 1
@ -205,6 +196,26 @@ def test_conftest_confcutdir(testdir):
result = testdir.runpytest("-h", "--confcutdir=%s" % x, x)
result.stdout.fnmatch_lines(["*--xyz*"])
def test_conftest_existing_resultlog(testdir):
x = testdir.mkdir("tests")
x.join("conftest.py").write(py.code.Source("""
def pytest_addoption(parser):
parser.addoption("--xyz", action="store_true")
"""))
testdir.makefile(ext=".log", result="") # Writes result.log
result = testdir.runpytest("-h", "--resultlog", "result.log")
result.stdout.fnmatch_lines(["*--xyz*"])
def test_conftest_existing_junitxml(testdir):
x = testdir.mkdir("tests")
x.join("conftest.py").write(py.code.Source("""
def pytest_addoption(parser):
parser.addoption("--xyz", action="store_true")
"""))
testdir.makefile(ext=".xml", junit="") # Writes junit.xml
result = testdir.runpytest("-h", "--junitxml", "junit.xml")
result.stdout.fnmatch_lines(["*--xyz*"])
def test_conftest_import_order(testdir, monkeypatch):
ct1 = testdir.makeconftest("")
sub = testdir.mkdir("sub")