[Pytest-commit] commit/pytest: hpk42: ref #322 cleanup all teardown calling to only happen when setup succeeded.
commits-noreply at bitbucket.org
commits-noreply at bitbucket.org
Fri Aug 2 09:52:54 CEST 2013
1 new commit in pytest:
https://bitbucket.org/hpk42/pytest/commits/a36faafd111e/
Changeset: a36faafd111e
User: hpk42
Date: 2013-08-02 09:52:40
Summary: ref #322 cleanup all teardown calling to only happen when setup succeeded.
don't use autouse fixtures for now because it would cause a proliferation
and overhead for the execution of every test. Rather introduce a
node.addfinalizer(fin) to attach a finalizer to the respective node
and call it from node.setup() functions if the setup phase succeeded
(i.e. there is no setup function or it finished successfully)
Affected #: 10 files
diff -r 44f1eaaf106f677b20f1c0fb6220fe198d9164ed -r a36faafd111e3ef317b18f4fe86054bdfaaa4301 CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,9 +2,10 @@
-----------------------------------
- fix issue322: tearDownClass is not run if setUpClass failed. Thanks
- Mathieu Agopian for fixing. The patch moves handling setUpClass
- into a new autofixture. (XXX impl-decide if rather adding addfinalizer()
- API to node's would have a similar effect)
+ Mathieu Agopian for the initial fix. Also make all of pytest/nose finalizer
+ mimick the same generic behaviour: if a setupX exists and fails,
+ don't run teardownX. This also introduces a new method "node.addfinalizer()"
+ helper which can only be called during the setup phase of a node.
- fix issue336: autouse fixture in plugins should work again.
diff -r 44f1eaaf106f677b20f1c0fb6220fe198d9164ed -r a36faafd111e3ef317b18f4fe86054bdfaaa4301 _pytest/__init__.py
--- a/_pytest/__init__.py
+++ b/_pytest/__init__.py
@@ -1,2 +1,2 @@
#
-__version__ = '2.4.0.dev8'
+__version__ = '2.4.0.dev10'
diff -r 44f1eaaf106f677b20f1c0fb6220fe198d9164ed -r a36faafd111e3ef317b18f4fe86054bdfaaa4301 _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -41,7 +41,7 @@
help="run pytest in strict mode, warnings become errors.")
group = parser.getgroup("collect", "collection")
- group.addoption('--collectonly', '--collect-only', action="store_true",
+ group.addoption('--collectonly', '--collect-only', action="store_true",
help="only collect tests, don't execute them."),
group.addoption('--pyargs', action="store_true",
help="try to interpret all arguments as python packages.")
@@ -326,6 +326,14 @@
def getplugins(self):
return self.config._getmatchingplugins(self.fspath)
+ def addfinalizer(self, fin):
+ """ register a function to be called when this node is finalized.
+
+ This method can only be called when this node is active
+ in a setup chain, for example during self.setup().
+ """
+ self.session._setupstate.addfinalizer(fin, self)
+
def getparent(self, cls):
current = self
while current and not isinstance(current, cls):
diff -r 44f1eaaf106f677b20f1c0fb6220fe198d9164ed -r a36faafd111e3ef317b18f4fe86054bdfaaa4301 _pytest/python.py
--- a/_pytest/python.py
+++ b/_pytest/python.py
@@ -384,19 +384,19 @@
setup_module(self.obj)
else:
setup_module()
-
- def teardown(self):
- teardown_module = xunitsetup(self.obj, 'tearDownModule')
- if teardown_module is None:
- teardown_module = xunitsetup(self.obj, 'teardown_module')
- if teardown_module is not None:
+ fin = getattr(self.obj, 'tearDownModule', None)
+ if fin is None:
+ fin = getattr(self.obj, 'teardown_module', None)
+ if fin 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(teardown_module)[0]:
- teardown_module(self.obj)
+ if inspect.getargspec(fin)[0]:
+ finalizer = lambda: fin(self.obj)
else:
- teardown_module()
+ finalizer = fin
+ self.addfinalizer(finalizer)
+
class Class(PyCollector):
""" Collector for test methods. """
@@ -415,12 +415,11 @@
setup_class = getattr(setup_class, '__func__', setup_class)
setup_class(self.obj)
- def teardown(self):
- 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)
- teardown_class(self.obj)
+ fin_class = getattr(self.obj, 'teardown_class', None)
+ if fin_class is not None:
+ fin_class = getattr(fin_class, 'im_func', fin_class)
+ fin_class = getattr(fin_class, '__func__', fin_class)
+ self.addfinalizer(lambda: fin_class(self.obj))
class Instance(PyCollector):
def _getobj(self):
@@ -449,23 +448,17 @@
else:
obj = self.parent.obj
if inspect.ismethod(self.obj):
- name = 'setup_method'
+ setup_name = 'setup_method'
+ teardown_name = 'teardown_method'
else:
- name = 'setup_function'
- setup_func_or_method = xunitsetup(obj, name)
+ setup_name = 'setup_function'
+ teardown_name = 'teardown_function'
+ setup_func_or_method = xunitsetup(obj, setup_name)
if setup_func_or_method is not None:
setup_func_or_method(self.obj)
-
- def teardown(self):
- """ perform teardown for this test function. """
- if inspect.ismethod(self.obj):
- name = 'teardown_method'
- else:
- name = 'teardown_function'
- obj = self.parent.obj
- teardown_func_or_meth = xunitsetup(obj, name)
- if teardown_func_or_meth is not None:
- teardown_func_or_meth(self.obj)
+ fin = getattr(obj, teardown_name, None)
+ if fin is not None:
+ self.addfinalizer(lambda: fin(self.obj))
def _prunetraceback(self, excinfo):
if hasattr(self, '_obj') and not self.config.option.fulltrace:
diff -r 44f1eaaf106f677b20f1c0fb6220fe198d9164ed -r a36faafd111e3ef317b18f4fe86054bdfaaa4301 _pytest/unittest.py
--- a/_pytest/unittest.py
+++ b/_pytest/unittest.py
@@ -19,21 +19,6 @@
return False
- at pytest.fixture(scope='class', autouse=True)
-def _xunit_setUpClass(request):
- """Add support for unittest.TestCase setUpClass and tearDownClass."""
- if not is_unittest(request.cls):
- return # only support setUpClass / tearDownClass for unittest.TestCase
- if getattr(request.cls, '__unittest_skip__', False):
- return # skipped
- setup = getattr(request.cls, 'setUpClass', None)
- teardown = getattr(request.cls, 'tearDownClass', None)
- if setup is not None:
- setup()
- if teardown is not None:
- request.addfinalizer(teardown)
-
-
def pytest_pycollect_makeitem(collector, name, obj):
if is_unittest(obj):
return UnitTestCase(name, parent=collector)
@@ -42,6 +27,18 @@
class UnitTestCase(pytest.Class):
nofuncargs = True # marker for fixturemanger.getfixtureinfo()
# to declare that our children do not support funcargs
+ #
+ def setup(self):
+ cls = self.obj
+ if getattr(cls, '__unittest_skip__', False):
+ return # skipped
+ setup = getattr(cls, 'setUpClass', None)
+ if setup is not None:
+ setup()
+ teardown = getattr(cls, 'tearDownClass', None)
+ if teardown is not None:
+ self.addfinalizer(teardown)
+ super(UnitTestCase, self).setup()
def collect(self):
self.session._fixturemanager.parsefactories(self, unittest=True)
diff -r 44f1eaaf106f677b20f1c0fb6220fe198d9164ed -r a36faafd111e3ef317b18f4fe86054bdfaaa4301 doc/en/xunit_setup.txt
--- a/doc/en/xunit_setup.txt
+++ b/doc/en/xunit_setup.txt
@@ -13,8 +13,15 @@
supported you may also use pytest's more powerful :ref:`fixture mechanism
<fixture>` which leverages the concept of dependency injection, allowing
for a more modular and more scalable approach for managing test state,
-especially for larger projects and for functional testing. It is safe
-to mix both fixture mechanisms.
+especially for larger projects and for functional testing. You can
+mix both fixture mechanisms in the same file but unittest-based
+test methods cannot receive fixture arguments.
+
+.. note::
+
+ As of pytest-2.4, teardownX functions are not called if
+ setupX existed and failed/was skipped. This harmonizes
+ behaviour across all major python testing tools.
Module level setup/teardown
--------------------------------------
diff -r 44f1eaaf106f677b20f1c0fb6220fe198d9164ed -r a36faafd111e3ef317b18f4fe86054bdfaaa4301 setup.py
--- a/setup.py
+++ b/setup.py
@@ -11,7 +11,7 @@
name='pytest',
description='py.test: simple powerful testing with Python',
long_description = long_description,
- version='2.4.0.dev8',
+ version='2.4.0.dev10',
url='http://pytest.org',
license='MIT license',
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
diff -r 44f1eaaf106f677b20f1c0fb6220fe198d9164ed -r a36faafd111e3ef317b18f4fe86054bdfaaa4301 testing/python/fixture.py
--- a/testing/python/fixture.py
+++ b/testing/python/fixture.py
@@ -834,8 +834,6 @@
l = reprec.getfailedcollections()
assert len(l) == 1
- @pytest.mark.xfail(reason="unclear if it should be supported at all, "
- "currently broken")
def test_request_can_be_overridden(self, testdir):
testdir.makepyfile("""
import pytest
diff -r 44f1eaaf106f677b20f1c0fb6220fe198d9164ed -r a36faafd111e3ef317b18f4fe86054bdfaaa4301 testing/test_runner_xunit.py
--- a/testing/test_runner_xunit.py
+++ b/testing/test_runner_xunit.py
@@ -32,6 +32,39 @@
rep = reprec.matchreport("test_module")
assert rep.passed
+def test_module_setup_failure_no_teardown(testdir):
+ reprec = testdir.inline_runsource("""
+ l = []
+ def setup_module(module):
+ l.append(1)
+ 0/0
+
+ def test_nothing():
+ pass
+
+ def teardown_module(module):
+ l.append(2)
+ """)
+ reprec.assertoutcome(failed=1)
+ calls = reprec.getcalls("pytest_runtest_setup")
+ assert calls[0].item.module.l == [1]
+
+def test_setup_function_failure_no_teardown(testdir):
+ reprec = testdir.inline_runsource("""
+ modlevel = []
+ def setup_function(function):
+ modlevel.append(1)
+ 0/0
+
+ def teardown_function(module):
+ modlevel.append(2)
+
+ def test_func():
+ pass
+ """)
+ calls = reprec.getcalls("pytest_runtest_setup")
+ assert calls[0].item.module.modlevel == [1]
+
def test_class_setup(testdir):
reprec = testdir.inline_runsource("""
class TestSimpleClassSetup:
@@ -55,6 +88,23 @@
""")
reprec.assertoutcome(passed=1+2+1)
+def test_class_setup_failure_no_teardown(testdir):
+ reprec = testdir.inline_runsource("""
+ class TestSimpleClassSetup:
+ clslevel = []
+ def setup_class(cls):
+ 0/0
+
+ def teardown_class(cls):
+ cls.clslevel.append(1)
+
+ def test_classlevel(self):
+ pass
+
+ def test_cleanup():
+ assert not TestSimpleClassSetup.clslevel
+ """)
+ reprec.assertoutcome(failed=1, passed=1)
def test_method_setup(testdir):
reprec = testdir.inline_runsource("""
@@ -72,6 +122,25 @@
""")
reprec.assertoutcome(passed=2)
+def test_method_setup_failure_no_teardown(testdir):
+ reprec = testdir.inline_runsource("""
+ class TestMethodSetup:
+ clslevel = []
+ def setup_method(self, method):
+ self.clslevel.append(1)
+ 0/0
+
+ def teardown_method(self, method):
+ self.clslevel.append(2)
+
+ def test_method(self):
+ pass
+
+ def test_cleanup():
+ assert TestMethodSetup.clslevel == [1]
+ """)
+ reprec.assertoutcome(failed=1, passed=1)
+
def test_method_generator_setup(testdir):
reprec = testdir.inline_runsource("""
class TestSetupTeardownOnInstance:
@@ -134,23 +203,7 @@
""")
reprec.assertoutcome(passed=2, failed=0)
-def test_failing_setup_calls_teardown(testdir):
- p = testdir.makepyfile("""
- def setup_module(mod):
- raise ValueError(42)
- def test_function():
- assert 0
- def teardown_module(mod):
- raise ValueError(43)
- """)
- result = testdir.runpytest(p)
- result.stdout.fnmatch_lines([
- "*42*",
- "*43*",
- "*2 error*"
- ])
-
-def test_setup_that_skips_calledagain_and_teardown(testdir):
+def test_setup_that_skips_calledagain(testdir):
p = testdir.makepyfile("""
import pytest
def setup_module(mod):
@@ -159,14 +212,9 @@
pass
def test_function2():
pass
- def teardown_module(mod):
- raise ValueError(43)
""")
- result = testdir.runpytest(p)
- result.stdout.fnmatch_lines([
- "*ValueError*43*",
- "*2 skipped*1 error*",
- ])
+ reprec = testdir.inline_run(p)
+ reprec.assertoutcome(skipped=2)
def test_setup_fails_again_on_all_tests(testdir):
p = testdir.makepyfile("""
@@ -177,14 +225,9 @@
pass
def test_function2():
pass
- def teardown_module(mod):
- raise ValueError(43)
""")
- result = testdir.runpytest(p)
- result.stdout.fnmatch_lines([
- "*3 error*"
- ])
- assert "passed" not in result.stdout.str()
+ reprec = testdir.inline_run(p)
+ reprec.assertoutcome(failed=2)
def test_setup_funcarg_setup_when_outer_scope_fails(testdir):
p = testdir.makepyfile("""
@@ -207,6 +250,3 @@
"*2 error*"
])
assert "xyz43" not in result.stdout.str()
-
-
-
diff -r 44f1eaaf106f677b20f1c0fb6220fe198d9164ed -r a36faafd111e3ef317b18f4fe86054bdfaaa4301 testing/test_unittest.py
--- a/testing/test_unittest.py
+++ b/testing/test_unittest.py
@@ -65,7 +65,7 @@
rep = reprec.matchreport("test_both", when="teardown")
assert rep.failed and '42' in str(rep.longrepr)
-def test_unittest_style_setup_teardown(testdir):
+def test_setUpModule(testdir):
testpath = testdir.makepyfile("""
l = []
@@ -86,6 +86,23 @@
"*2 passed*",
])
+def test_setUpModule_failing_no_teardown(testdir):
+ testpath = testdir.makepyfile("""
+ l = []
+
+ def setUpModule():
+ 0/0
+
+ def tearDownModule():
+ l.append(1)
+
+ def test_hello():
+ pass
+ """)
+ reprec = testdir.inline_run(testpath)
+ reprec.assertoutcome(passed=0, failed=1)
+ call = reprec.getcalls("pytest_runtest_setup")[0]
+ assert not call.item.module.l
def test_new_instances(testdir):
testpath = testdir.makepyfile("""
@@ -636,3 +653,4 @@
""")
reprec = testdir.inline_run(testpath)
reprec.assertoutcome(passed=1, failed=1)
+
Repository URL: https://bitbucket.org/hpk42/pytest/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
More information about the pytest-commit
mailing list