From 5941b2e07198c7b4c29cd135e98289471ff9debf Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 9 Jan 2015 19:55:49 +0100 Subject: [PATCH] fix issue 655: crude workarounds around python2/3 exception leaks --- CHANGELOG | 5 ++++- _pytest/python.py | 15 +++++++++++---- testing/python/fixture.py | 23 +++++++++++++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 658d3b86b..f03f80889 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ 2.7.0.dev (compared to 2.6.4) ----------------------------- +- fix issue655: work around different ways that cause python2/3 + to leak sys.exc_info into fixtures/tests causing failures in 3rd party code + - fix issue615: assertion re-writing did not correctly escape % signs when formatting boolean operations, which tripped over mixing booleans with modulo operators. Thanks to Tom Viner for the report, @@ -14,7 +17,7 @@ which want to wrap the execution of certain hooks for their purposes. This supersedes the undocumented ``__multicall__`` protocol which pytest itself and some external plugins use. Note that pytest-2.8 - is scheduled to drop supporting the old ``__multicall__`` + is scheduled to drop supporting the old ``__multicall__`` and only support the hookwrapper protocol. - use hookwrapper mechanism in builtin pytest plugins. diff --git a/_pytest/python.py b/_pytest/python.py index 74ddf48b2..e35163599 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -15,6 +15,8 @@ NOTSET = object() isfunction = inspect.isfunction isclass = inspect.isclass callable = py.builtin.callable +# used to work around a python2 exception info leak +exc_clear = getattr(sys, 'exc_clear', lambda: None) def getfslineno(obj): # xxx let decorators etc specify a sane ordering @@ -1389,10 +1391,12 @@ class FixtureRequest(FuncargnamesCompatAttr): cached_result = (self, [0], None) return PseudoFixtureDef raise - result = self._getfuncargvalue(fixturedef) - self._funcargs[argname] = result - self._fixturedefs[argname] = fixturedef - return fixturedef + # remove indent to prevent the python3 exception + # from leaking into the call + result = self._getfuncargvalue(fixturedef) + self._funcargs[argname] = result + self._fixturedefs[argname] = fixturedef + return fixturedef def _get_fixturestack(self): current = self @@ -1439,6 +1443,9 @@ class FixtureRequest(FuncargnamesCompatAttr): (scope, argname, self.scope, "\n".join(lines)))) __tracebackhide__ = False + # clear sys.exc_info before invoking the fixture (python bug?) + # if its not explicitly cleared it will leak into the call + exc_clear() try: # call the fixture function val = fixturedef.execute(request=subrequest) diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 9739b8eb8..07fd0abcb 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -261,6 +261,29 @@ class TestFillFixtures: ]) assert "INTERNAL" not in result.stdout.str() + def test_fixture_excinfo_leak(self, testdir): + # on python2 sys.excinfo would leak into fixture executions + testdir.makepyfile(""" + import sys + import traceback + import pytest + + @pytest.fixture + def leak(): + if sys.exc_info()[0]: # python3 bug :) + traceback.print_exc() + #fails + assert sys.exc_info() == (None, None, None) + + def test_leak(leak): + if sys.exc_info()[0]: # python3 bug :) + traceback.print_exc() + assert sys.exc_info() == (None, None, None) + """) + result = testdir.runpytest() + assert result.ret == 0 + + class TestRequestBasic: def test_request_attributes(self, testdir): item = testdir.getitem("""