1 new commit in pytest:
https://bitbucket.org/hpk42/pytest/changeset/e776739a7800/ changeset: e776739a7800 user: hpk42 date: 2011-11-18 17:01:29 summary: fix issue90 - perform teardown after its actual test function/item. This is implemented by modifying the runtestprotocol to remember "pending" teardowns and call them before the setup of the next item. affected #: 10 files diff -r b22c3b301862b6cbd2593cc4933b87a190ee3fff -r e776739a7800d45ba9e55c91ad33827a16b90420 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ Changes between 2.1.3 and XXX 2.2.0 ---------------------------------------- +- fix issue90: introduce eager tearing down of test items so that + teardown function are called earlier. - add an all-powerful metafunc.parametrize function which allows to parametrize test function arguments in multiple steps and therefore from indepdenent plugins and palces. diff -r b22c3b301862b6cbd2593cc4933b87a190ee3fff -r e776739a7800d45ba9e55c91ad33827a16b90420 _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.2.0.dev8' +__version__ = '2.2.0.dev9' diff -r b22c3b301862b6cbd2593cc4933b87a190ee3fff -r e776739a7800d45ba9e55c91ad33827a16b90420 _pytest/capture.py --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -163,17 +163,6 @@ def pytest_runtest_teardown(self, item): self.resumecapture_item(item) - def pytest__teardown_final(self, __multicall__, session): - method = self._getmethod(session.config, None) - self.resumecapture(method) - try: - rep = __multicall__.execute() - finally: - outerr = self.suspendcapture() - if rep: - addouterr(rep, outerr) - return rep - def pytest_keyboard_interrupt(self, excinfo): if hasattr(self, '_capturing'): self.suspendcapture() diff -r b22c3b301862b6cbd2593cc4933b87a190ee3fff -r e776739a7800d45ba9e55c91ad33827a16b90420 _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -82,11 +82,11 @@ session.exitstatus = EXIT_INTERNALERROR if excinfo.errisinstance(SystemExit): sys.stderr.write("mainloop: caught Spurious SystemExit!\n") + if initstate >= 2: + config.hook.pytest_sessionfinish(session=session, + exitstatus=session.exitstatus or (session._testsfailed and 1)) if not session.exitstatus and session._testsfailed: session.exitstatus = EXIT_TESTSFAILED - if initstate >= 2: - config.hook.pytest_sessionfinish(session=session, - exitstatus=session.exitstatus) if initstate >= 1: config.pluginmanager.do_unconfigure(config) return session.exitstatus @@ -106,7 +106,7 @@ def pytest_runtestloop(session): if session.config.option.collectonly: return True - for item in session.session.items: + for item in session.items: item.config.hook.pytest_runtest_protocol(item=item) if session.shouldstop: raise session.Interrupted(session.shouldstop) diff -r b22c3b301862b6cbd2593cc4933b87a190ee3fff -r e776739a7800d45ba9e55c91ad33827a16b90420 _pytest/pytester.py --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -355,9 +355,11 @@ if not plugins: plugins = [] plugins.append(Collect()) - self.pytestmain(list(args), plugins=[Collect()]) + ret = self.pytestmain(list(args), plugins=[Collect()]) + reprec = rec[0] + reprec.ret = ret assert len(rec) == 1 - return items, rec[0] + return items, reprec def parseconfig(self, *args): args = [str(x) for x in args] diff -r b22c3b301862b6cbd2593cc4933b87a190ee3fff -r e776739a7800d45ba9e55c91ad33827a16b90420 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -387,6 +387,7 @@ tw.line() tw.line("%s:%d" % (self.filename, self.firstlineno+1)) + class Generator(FunctionMixin, PyCollectorMixin, pytest.Collector): def collect(self): # test generators are seen as collectors but they also diff -r b22c3b301862b6cbd2593cc4933b87a190ee3fff -r e776739a7800d45ba9e55c91ad33827a16b90420 _pytest/runner.py --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -60,19 +60,33 @@ def __init__(self, location): self.location = location +def perform_pending_teardown(config, nextitem): + try: + olditem, log = config._pendingteardown + except AttributeError: + pass + else: + del config._pendingteardown + olditem.nextitem = nextitem + call_and_report(olditem, "teardown", log) + def pytest_runtest_protocol(item): + perform_pending_teardown(item.config, item) item.ihook.pytest_runtest_logstart( nodeid=item.nodeid, location=item.location, ) - runtestprotocol(item) + runtestprotocol(item, teardowndelayed=True) return True -def runtestprotocol(item, log=True): +def runtestprotocol(item, log=True, teardowndelayed=False): rep = call_and_report(item, "setup", log) reports = [rep] if rep.passed: reports.append(call_and_report(item, "call", log)) - reports.append(call_and_report(item, "teardown", log)) + if teardowndelayed: + item.config._pendingteardown = item, log + else: + reports.append(call_and_report(item, "teardown", log)) return reports def pytest_runtest_setup(item): @@ -85,12 +99,13 @@ item.session._setupstate.teardown_exact(item) def pytest__teardown_final(session): - call = CallInfo(session._setupstate.teardown_all, when="teardown") - if call.excinfo: - ntraceback = call.excinfo.traceback .cut(excludepath=py._pydir) - call.excinfo.traceback = ntraceback.filter() - longrepr = call.excinfo.getrepr(funcargs=True) - return TeardownErrorReport(longrepr) + perform_pending_teardown(session.config, None) + #call = CallInfo(session._setupstate.teardown_all, when="teardown") + #if call.excinfo: + # ntraceback = call.excinfo.traceback .cut(excludepath=py._pydir) + # call.excinfo.traceback = ntraceback.filter() + # longrepr = call.excinfo.getrepr(funcargs=True) + # return TeardownErrorReport(longrepr) def pytest_report_teststatus(report): if report.when in ("setup", "teardown"): @@ -325,19 +340,28 @@ assert not self._finalizers def teardown_exact(self, item): - if self.stack and item == self.stack[-1]: + try: + colitem = item.nextitem + except AttributeError: + # in distributed testing there might be no known nexitem + # and in this case we use the parent node to at least call + # teardown of the current item + colitem = item.parent + needed_collectors = colitem and colitem.listchain() or [] + self._teardown_towards(needed_collectors) + + def _teardown_towards(self, needed_collectors): + while self.stack: + if self.stack == needed_collectors[:len(self.stack)]: + break self._pop_and_teardown() - else: - self._callfinalizers(item) def prepare(self, colitem): """ setup objects along the collector chain to the test-method and teardown previously setup objects.""" needed_collectors = colitem.listchain() - while self.stack: - if self.stack == needed_collectors[:len(self.stack)]: - break - self._pop_and_teardown() + self._teardown_towards(needed_collectors) + # check if the last collection node has raised an error for col in self.stack: if hasattr(col, '_prepare_exc'): diff -r b22c3b301862b6cbd2593cc4933b87a190ee3fff -r e776739a7800d45ba9e55c91ad33827a16b90420 doc/announce/release-2.2.0.txt --- a/doc/announce/release-2.2.0.txt +++ b/doc/announce/release-2.2.0.txt @@ -1,8 +1,8 @@ -py.test 2.2.0: improved test markers and duration profiling +py.test 2.2.0: test marking++, parametrization++ and duration profiling =========================================================================== -pytest-2.2.0 is a quite [1] backward compatible release of the popular -py.test testing tool. There are a couple of new features: +pytest-2.2.0 is a test-suite compatible release of the popular +py.test testing tool. There are a couple of new features and improvements: * "--duration=N" option showing the N slowest test execution or setup/teardown calls. @@ -16,8 +16,13 @@ a new "markers" ini-variable for registering test markers. The new "--strict" option will bail out with an error if you are using unregistered markers. +* teardown functions are now more eagerly called so that they appear + more directly connected to the last test item that needed a particular + fixture/setup. + Usage of improved parametrize is documented in examples at http://pytest.org/latest/example/parametrize.html + Usages of the improved marking mechanism is illustrated by a couple of initial examples, see http://pytest.org/latest/example/markers.html @@ -40,9 +45,11 @@ holger krekel -[1] notes on incompatibility +notes on incompatibility ------------------------------ +While test suites should work unchanged you might need to upgrade plugins: + * You need a new version of the pytest-xdist plugin (1.7) for distributing test runs. diff -r b22c3b301862b6cbd2593cc4933b87a190ee3fff -r e776739a7800d45ba9e55c91ad33827a16b90420 setup.py --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ name='pytest', description='py.test: simple powerful testing with Python', long_description = long_description, - version='2.2.0.dev8', + version='2.2.0.dev9', url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], diff -r b22c3b301862b6cbd2593cc4933b87a190ee3fff -r e776739a7800d45ba9e55c91ad33827a16b90420 testing/test_runner.py --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -160,6 +160,45 @@ #assert rep.failed.where.path.basename == "test_func.py" #assert rep.failed.failurerepr == "hello" + def test_teardown_final_returncode(self, testdir): + rec = testdir.inline_runsource(""" + def test_func(): + pass + def teardown_function(func): + raise ValueError(42) + """) + assert rec.ret == 1 + + def test_exact_teardown_issue90(self, testdir): + rec = testdir.inline_runsource(""" + import pytest + + class TestClass: + def test_method(self): + pass + def teardown_class(cls): + raise Exception() + + def test_func(): + pass + def teardown_function(func): + raise ValueError(42) + """) + reps = rec.getreports("pytest_runtest_logreport") + print (reps) + for i in range(2): + assert reps[i].nodeid.endswith("test_method") + assert reps[i].passed + assert reps[2].when == "teardown" + assert reps[2].failed + assert len(reps) == 6 + for i in range(3,5): + assert reps[i].nodeid.endswith("test_func") + assert reps[i].passed + assert reps[5].when == "teardown" + assert reps[5].nodeid.endswith("test_func") + assert reps[5].failed + def test_failure_in_setup_function_ignores_custom_repr(self, testdir): testdir.makepyfile(conftest=""" import pytest 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. _______________________________________________ py-svn mailing list py-svn@codespeak.net http://codespeak.net/mailman/listinfo/py-svn