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 - change skips into warnings for test classes with an __init__ and
callables in test modules which look like a test but are not functions. 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 - fix issue #479: properly handle nose/unittest(2) SkipTest exceptions
during collection/loading of test modules. Thanks to Marc Schlaich during collection/loading of test modules. Thanks to Marc Schlaich
for the complete PR. for the complete PR.

View File

@ -31,7 +31,7 @@ def pytest_addoption(parser):
@pytest.mark.tryfirst @pytest.mark.tryfirst
def pytest_load_initial_conftests(early_config, parser, args, __multicall__): 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 pluginmanager = early_config.pluginmanager
if ns.capture == "no": if ns.capture == "no":
return return

View File

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

View File

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

View File

@ -20,19 +20,26 @@ def pytest_funcarg__basedir(request):
def ConftestWithSetinitial(path): def ConftestWithSetinitial(path):
conftest = Conftest() conftest = Conftest()
conftest.setinitial([path]) conftest_setinitial(conftest, [path])
return conftest 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: class TestConftestValueAccessGlobal:
def test_basic_init(self, basedir): def test_basic_init(self, basedir):
conftest = Conftest() conftest = Conftest()
conftest.setinitial([basedir.join("adir")]) conftest_setinitial(conftest, [basedir.join("adir")])
assert conftest.rget("a") == 1 assert conftest.rget("a") == 1
def test_onimport(self, basedir): def test_onimport(self, basedir):
l = [] l = []
conftest = Conftest(onimport=l.append) conftest = Conftest(onimport=l.append)
conftest.setinitial([basedir.join("adir"), conftest_setinitial(conftest, [basedir.join("adir"),
'--confcutdir=%s' % basedir]) '--confcutdir=%s' % basedir])
assert len(l) == 1 assert len(l) == 1
assert conftest.rget("a") == 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") tmpdir.ensure("adir-1.0/__init__.py")
ConftestWithSetinitial(tmpdir.join("adir-1.0", "b")) ConftestWithSetinitial(tmpdir.join("adir-1.0", "b"))
def test_doubledash_not_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 = Conftest()
conftest.setinitial([conf.basename, conf.basename]) conftest_setinitial(conftest, [conf.basename, conf.basename])
l = conftest.getconftestmodules(None) l = conftest.getconftestmodules(None)
assert len(l) == 0 assert len(l) == 1
def test_issue151_load_all_conftests(testdir): def test_issue151_load_all_conftests(testdir):
names = "code proj src".split() names = "code proj src".split()
@ -114,7 +121,7 @@ def test_issue151_load_all_conftests(testdir):
p.ensure("conftest.py") p.ensure("conftest.py")
conftest = Conftest() conftest = Conftest()
conftest.setinitial(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)
@ -142,8 +149,8 @@ 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(confcutdir=p) conftest = Conftest()
conftest.setinitial([testdir.tmpdir]) 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())
@ -160,34 +167,18 @@ def test_conftestcutdir(testdir):
def test_conftestcutdir_inplace_considered(testdir): def test_conftestcutdir_inplace_considered(testdir):
conf = testdir.makeconftest("") conf = testdir.makeconftest("")
conftest = Conftest(confcutdir=conf.dirpath()) conftest = Conftest()
conftest.setinitial([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))
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()) @pytest.mark.parametrize("name", 'test tests whatever .dotdir'.split())
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 = Conftest()
conftest.setinitial([sub.dirpath(), '--confcutdir=%s' % 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
assert len(conftest._conftestpath2mod) == 1 assert len(conftest._conftestpath2mod) == 1
@ -205,6 +196,26 @@ def test_conftest_confcutdir(testdir):
result = testdir.runpytest("-h", "--confcutdir=%s" % x, x) result = testdir.runpytest("-h", "--confcutdir=%s" % x, x)
result.stdout.fnmatch_lines(["*--xyz*"]) 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): def test_conftest_import_order(testdir, monkeypatch):
ct1 = testdir.makeconftest("") ct1 = testdir.makeconftest("")
sub = testdir.mkdir("sub") sub = testdir.mkdir("sub")