From 627e068516082a588b26d370490310eb3327226d Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 13 Aug 2012 13:37:14 +0200 Subject: [PATCH] fix issue172 so that @pytest.setup marked setup_module/function... functions are not called twice. Also fix ordering to that broader scoped setup functions are executed first. --- CHANGELOG | 2 ++ _pytest/python.py | 34 +++++++++++++++++---------- testing/test_python.py | 52 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7ab010f0e..aba6fd8f8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ Changes between 2.2.4 and 2.3.0.dev ----------------------------------- +- fix issue172 duplicate call of pytest.setup-decoratored setup_module + functions - fix junitxml=path construction so that if tests change the current working directory and the path is a relative path it is constructed correctly from the original current working dir. diff --git a/_pytest/python.py b/_pytest/python.py index 3c1fb4508..e128be9ce 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -363,24 +363,26 @@ class Module(pytest.File, PyCollector): return mod def setup(self): - if hasattr(self.obj, 'setup_module'): + setup_module = xunitsetup(self.obj, "setup_module") + if setup_module is not None: #XXX: nose compat hack, move to nose plugin # if it takes a positional arg, its probably a pytest style one # so we pass the current module object - if inspect.getargspec(self.obj.setup_module)[0]: - self.obj.setup_module(self.obj) + if inspect.getargspec(setup_module)[0]: + setup_module(self.obj) else: - self.obj.setup_module() + setup_module() def teardown(self): - if hasattr(self.obj, 'teardown_module'): + teardown_module = xunitsetup(self.obj, 'teardown_module') + if teardown_module is not None: #XXX: nose compat hack, move to nose plugin # if it takes a positional arg, its probably a py.test style one # so we pass the current module object - if inspect.getargspec(self.obj.teardown_module)[0]: - self.obj.teardown_module(self.obj) + if inspect.getargspec(teardown_module)[0]: + teardown_module(self.obj) else: - self.obj.teardown_module() + teardown_module() class Class(PyCollector): """ Collector for test methods. """ @@ -388,14 +390,14 @@ class Class(PyCollector): return [self._getcustomclass("Instance")(name="()", parent=self)] def setup(self): - setup_class = getattr(self.obj, 'setup_class', None) + setup_class = xunitsetup(self.obj, 'setup_class') if setup_class is not None: setup_class = getattr(setup_class, 'im_func', setup_class) setup_class = getattr(setup_class, '__func__', setup_class) setup_class(self.obj) def teardown(self): - teardown_class = getattr(self.obj, 'teardown_class', None) + teardown_class = xunitsetup(self.obj, 'teardown_class') if teardown_class is not None: teardown_class = getattr(teardown_class, 'im_func', teardown_class) teardown_class = getattr(teardown_class, '__func__', teardown_class) @@ -431,7 +433,7 @@ class FunctionMixin(PyobjMixin): name = 'setup_method' else: name = 'setup_function' - setup_func_or_method = getattr(obj, name, None) + setup_func_or_method = xunitsetup(obj, name) if setup_func_or_method is not None: setup_func_or_method(self.obj) @@ -442,7 +444,7 @@ class FunctionMixin(PyobjMixin): else: name = 'teardown_function' obj = self.parent.obj - teardown_func_or_meth = getattr(obj, name, None) + teardown_func_or_meth = xunitsetup(obj, name) if teardown_func_or_meth is not None: teardown_func_or_meth(self.obj) @@ -1338,6 +1340,7 @@ class FuncargManager: if nodeid.startswith(setupcall.baseid): l.append(setupcall) allargnames.update(setupcall.funcargnames) + l.sort(key=lambda x: x.scopenum) return l, allargnames @@ -1479,6 +1482,7 @@ class SetupCall: self.func = func self.funcargnames = getfuncargnames(func) self.scope = scope + self.scopenum = scopes.index(scope) self.active = False self._finalizer = [] @@ -1595,3 +1599,9 @@ def getfuncargparams(item, ignore, scopenum, cache): # argparams.append(key) return argparams + +def xunitsetup(obj, name): + meth = getattr(obj, name, None) + if meth is not None: + if not hasattr(meth, "_pytestsetup"): + return meth diff --git a/testing/test_python.py b/testing/test_python.py index 90cd7d76b..62633b6bb 100644 --- a/testing/test_python.py +++ b/testing/test_python.py @@ -1874,6 +1874,28 @@ class TestSetupManagement: l = config._conftest.getconftestmodules(p)[0].l assert l == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2 + def test_setup_scope_ordering(self, testdir): + testdir.makepyfile(""" + import pytest + l = [] + @pytest.setup(scope="function") + def fappend2(): + l.append(2) + @pytest.setup(scope="class") + def classappend3(): + l.append(3) + @pytest.setup(scope="module") + def mappend(): + l.append(1) + + class TestHallo: + def test_method(self): + assert l == [1,3,2] + """) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) + + class TestFuncargMarker: def test_parametrize(self, testdir): testdir.makepyfile(""" @@ -2404,3 +2426,33 @@ class TestTestContextVarious: """) reprec = testdir.inline_run() reprec.assertoutcome(passed=2) + +def test_setupdecorator_and_xunit(testdir): + testdir.makepyfile(""" + import pytest + l = [] + @pytest.setup(scope='module') + def setup_module(): + l.append("module") + @pytest.setup() + def setup_function(): + l.append("function") + + def test_func(): + pass + + class TestClass: + @pytest.setup(scope="class") + def setup_class(self): + l.append("class") + @pytest.setup() + def setup_method(self): + l.append("method") + def test_method(self): + pass + def test_all(): + assert l == ["module", "function", "class", + "function", "method", "function"] + """) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=3)