From 3bca62e9e44e4b5384890974ad514a60b633a556 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 2 Apr 2014 11:29:23 +0200 Subject: [PATCH] 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. --- CHANGELOG | 5 +++ _pytest/capture.py | 2 +- _pytest/config.py | 45 ++++++++++----------------- testing/test_config.py | 2 +- testing/test_conftest.py | 67 +++++++++++++++++++++++----------------- 5 files changed, 63 insertions(+), 58 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5c24274a4..190dd4b51 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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. diff --git a/_pytest/capture.py b/_pytest/capture.py index be4f5bbbe..8b369cd41 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -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 diff --git a/_pytest/config.py b/_pytest/config.py index eee4d085a..066cd6624 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -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 diff --git a/testing/test_config.py b/testing/test_config.py index 4d34876a4..11a57fa0c 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -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): diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 9b5fa94e9..09c21c027 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -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")