From cc2337af3a8eaebaddf5b2fe49300cb82dc582e0 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 16 Oct 2012 16:13:12 +0200 Subject: [PATCH] refine parsefactories interface, fix two_classes test originally reported by Alex Okrushko, also add a few more tests to make sure autouse-fixtures are properly distinguished --- _pytest/__init__.py | 2 +- _pytest/python.py | 66 +++++++++++++++++++++++------------------- _pytest/unittest.py | 3 +- setup.py | 2 +- testing/test_python.py | 49 +++++++++++++++++++++++++++---- 5 files changed, 83 insertions(+), 39 deletions(-) diff --git a/_pytest/__init__.py b/_pytest/__init__.py index e738f5c64..038a30e37 100644 --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.3.0.dev23' +__version__ = '2.3.0.dev24' diff --git a/_pytest/python.py b/_pytest/python.py index 54f183d34..1b1fde898 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -83,7 +83,7 @@ def pytest_addoption(parser): group.addoption('--fixtures', '--fixtures', action="store_true", dest="showfixtures", default=False, help="show available fixtures, sorted by plugin appearance") - parser.addini("usefixtures", type="args", default=(), + parser.addini("usefixtures", type="args", default=[], help="list of default fixtures to be used with this project") parser.addini("python_files", type="args", default=('test_*.py', '*_test.py'), @@ -379,7 +379,7 @@ class Module(pytest.File, PyCollector): return self._memoizedcall('_obj', self._importtestmodule) def collect(self): - self.session._fixturemanager._parsefactories(self.obj, self.nodeid) + self.session._fixturemanager.parsefactories(self) return super(Module, self).collect() def _importtestmodule(self): @@ -452,7 +452,7 @@ class Instance(PyCollector): return obj def collect(self): - self.session._fixturemanager._parsefactories(self.obj, self.nodeid) + self.session._fixturemanager.parsefactories(self) return super(Instance, self).collect() def newinstance(self): @@ -781,7 +781,7 @@ def _showfixtures_main(config, session): fm = session._fixturemanager available = [] - for argname in fm.arg2fixturedefs: + for argname in fm._arg2fixturedefs: fixturedefs = fm.getfixturedefs(argname, nodeid) assert fixturedefs is not None if not fixturedefs: @@ -1335,7 +1335,7 @@ class FixtureLookupError(LookupError): fm = self.request._fixturemanager nodeid = self.request._parentid available = [] - for name, fixturedef in fm.arg2fixturedefs.items(): + for name, fixturedef in fm._arg2fixturedefs.items(): faclist = list(fm._matchfactories(fixturedef, self.request._parentid)) if faclist: available.append(name) @@ -1370,11 +1370,11 @@ class FixtureManager: def __init__(self, session): self.session = session self.config = session.config - self.arg2fixturedefs = {} + self._arg2fixturedefs = {} self._seenplugins = set() self._holderobjseen = set() self._arg2finish = {} - self._autofixtures = [] + self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))] session.config.pluginmanager.register(self, "funcmanage") ### XXX this hook should be called for historic events like pytest_configure @@ -1391,7 +1391,7 @@ class FixtureManager: else: if p.basename.startswith("conftest.py"): nodeid = p.dirpath().relto(self.session.fspath) - self._parsefactories(plugin, nodeid) + self.parsefactories(plugin, nodeid) self._seenplugins.add(plugin) @pytest.mark.tryfirst @@ -1400,19 +1400,21 @@ class FixtureManager: for plugin in plugins: self.pytest_plugin_registered(plugin) - def getdefaultfixtures(self): - """ return a tuple of default fixture names (XXX for the given file path). """ - try: - return self._defaultfixtures - except AttributeError: - defaultfixtures = tuple(self.config.getini("usefixtures")) - # make sure the self._autofixtures list is sorted - # by scope, scopenum 0 is session - self._autofixtures.sort( - key=lambda x: self.arg2fixturedefs[x][-1].scopenum) - defaultfixtures = defaultfixtures + tuple(self._autofixtures) - self._defaultfixtures = defaultfixtures - return defaultfixtures + def _getautousenames(self, nodeid): + """ return a tuple of fixture names to be used. """ + autousenames = [] + for baseid, basenames in self._nodeid_and_autousenames: + if nodeid.startswith(baseid): + if baseid: + i = len(baseid) + 1 + nextchar = nodeid[i:i+1] + if nextchar and nextchar not in ":/": + continue + autousenames.extend(basenames) + # make sure autousenames are sorted by scope, scopenum 0 is session + autousenames.sort( + key=lambda x: self._arg2fixturedefs[x][-1].scopenum) + return autousenames def getfixtureclosure(self, fixturenames, parentnode): # collect the closure of all fixtures , starting with the given @@ -1423,7 +1425,7 @@ class FixtureManager: # (discovering matching fixtures for a given name/node is expensive) parentid = parentnode.nodeid - fixturenames_closure = list(self.getdefaultfixtures()) + fixturenames_closure = self._getautousenames(parentid) def merge(otherlist): for arg in otherlist: if arg not in fixturenames_closure: @@ -1484,10 +1486,16 @@ class FixtureManager: for fin in l: fin() - def _parsefactories(self, holderobj, nodeid, unittest=False): + def parsefactories(self, node_or_obj, nodeid=None, unittest=False): + if nodeid is not None: + holderobj = node_or_obj + else: + holderobj = node_or_obj.obj + nodeid = node_or_obj.nodeid if holderobj in self._holderobjseen: return self._holderobjseen.add(holderobj) + autousenames = [] for name in dir(holderobj): obj = getattr(holderobj, name) if not callable(obj): @@ -1509,18 +1517,16 @@ class FixtureManager: fixturedef = FixtureDef(self, nodeid, name, obj, marker.scope, marker.params, unittest=unittest) - faclist = self.arg2fixturedefs.setdefault(name, []) + faclist = self._arg2fixturedefs.setdefault(name, []) faclist.append(fixturedef) if marker.autouse: - self._autofixtures.append(name) - try: - del self._defaultfixtures - except AttributeError: - pass + autousenames.append(name) + if autousenames: + self._nodeid_and_autousenames.append((nodeid, autousenames)) def getfixturedefs(self, argname, nodeid): try: - fixturedefs = self.arg2fixturedefs[argname] + fixturedefs = self._arg2fixturedefs[argname] except KeyError: return None else: diff --git a/_pytest/unittest.py b/_pytest/unittest.py index 05c823a6c..cad8ae3bf 100644 --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -22,8 +22,7 @@ def pytest_pycollect_makeitem(collector, name, obj): class UnitTestCase(pytest.Class): def collect(self): - self.session._fixturemanager._parsefactories(self.obj, self.nodeid, - unittest=True) + self.session._fixturemanager.parsefactories(self, unittest=True) loader = py.std.unittest.TestLoader() module = self.getparent(pytest.Module).obj cls = self.obj diff --git a/setup.py b/setup.py index f5a0b8b39..27dd1ab33 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ def main(): name='pytest', description='py.test: simple powerful testing with Python', long_description = long_description, - version='2.3.0.dev23', + version='2.3.0.dev24', url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], diff --git a/testing/test_python.py b/testing/test_python.py index 82970bc91..934fe12c9 100644 --- a/testing/test_python.py +++ b/testing/test_python.py @@ -2026,14 +2026,14 @@ class TestSetupDiscovery: def test_parsefactories_conftest(self, testdir): testdir.makepyfile(""" def test_check_setup(item, fm): - assert len(fm._autofixtures) == 2 - assert "perfunction2" in fm._autofixtures - assert "perfunction" in fm._autofixtures + autousenames = fm._getautousenames(item.nodeid) + assert len(autousenames) == 2 + assert "perfunction2" in autousenames + assert "perfunction" in autousenames """) reprec = testdir.inline_run("-s") reprec.assertoutcome(passed=1) - @pytest.mark.xfail def test_two_classes_separated_autouse(self, testdir): testdir.makepyfile(""" import pytest @@ -2113,6 +2113,45 @@ class TestSetupDiscovery: reprec = testdir.inline_run("-s") reprec.assertoutcome(failed=0, passed=0) + def test_autouse_in_conftests(self, testdir): + a = testdir.mkdir("a") + b = testdir.mkdir("a1") + conftest = testdir.makeconftest(""" + import pytest + @pytest.fixture(autouse=True) + def hello(): + xxx + """) + conftest.move(a.join(conftest.basename)) + a.join("test_something.py").write("def test_func(): pass") + b.join("test_otherthing.py").write("def test_func(): pass") + result = testdir.runpytest() + result.stdout.fnmatch_lines(""" + *1 passed*1 error* + """) + + def test_autouse_in_module_and_two_classes(self, testdir): + testdir.makepyfile(""" + import pytest + l = [] + @pytest.fixture(autouse=True) + def append1(): + l.append("module") + def test_x(): + assert l == ["module"] + + class TestA: + @pytest.fixture(autouse=True) + def append2(self): + l.append("A") + def test_hello(self): + assert l == ["module", "module", "A"], l + class TestA2: + def test_world(self): + assert l == ["module", "module", "A", "module"], l + """) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=3) class TestSetupManagement: def test_funcarg_and_setup(self, testdir): @@ -2220,7 +2259,7 @@ class TestSetupManagement: def test_2(self): pass """) - reprec = testdir.inline_run("-v",) + reprec = testdir.inline_run("-v","-s") reprec.assertoutcome(passed=8) config = reprec.getcalls("pytest_unconfigure")[0].config l = config._conftest.getconftestmodules(p)[0].l