diff --git a/CHANGELOG b/CHANGELOG index 44053343b..0e1aa81ce 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,11 @@ pytest.parametrize in Python 3. Thanks Paul Kehrer for the report and Bruno Oliveira for the PR. +- fix #995: fixed internal error when filtering tracebacks where one entry + was generated by an exec() statement. + Thanks Daniel Hahler, Ashley C Straw, Philippe Gauthier and Pavel Savchenko + for contributing and Bruno Oliveira for the PR. + 2.8.1 ----- diff --git a/_pytest/cacheprovider.py b/_pytest/cacheprovider.py index a1f728d9c..afd852de9 100755 --- a/_pytest/cacheprovider.py +++ b/_pytest/cacheprovider.py @@ -192,8 +192,8 @@ def cache(request): cache.get(key, default) cache.set(key, value) - Keys must be strings not containing a "/" separator. Add a unique identifier - (such as plugin/app name) to avoid clashes with other cache users. + Keys must be a ``/`` separated value, where the first part is usually the + name of your plugin or application to avoid clashes with other cache users. Values can be any object handled by the json stdlib module. """ diff --git a/_pytest/python.py b/_pytest/python.py index 4dc3dd7ad..28a81520b 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -49,6 +49,13 @@ def _has_positional_arg(func): def filter_traceback(entry): + # entry.path might sometimes return a str() object when the entry + # points to dynamically generated code + # see https://bitbucket.org/pytest-dev/py/issues/71 + raw_filename = entry.frame.code.raw.co_filename + is_generated = '<' in raw_filename and '>' in raw_filename + if is_generated: + return False return entry.path != cutdir1 and not entry.path.relto(cutdir2) diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index 1e9807cf5..bc738f513 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -332,17 +332,17 @@ after others, i.e. the position in the ``N``-sized list of functions: .. code-block:: python # Plugin 1 - @pytest.hookimpl_spec(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_collection_modifyitems(items): # will execute as early as possible # Plugin 2 - @pytest.hookimpl_spec(trylast=True) + @pytest.hookimpl(trylast=True) def pytest_collection_modifyitems(items): # will execute as late as possible # Plugin 3 - @pytest.hookimpl_spec(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_collection_modifyitems(items): # will execute even before the tryfirst one above! outcome = yield diff --git a/testing/python/collect.py b/testing/python/collect.py index fba8c477c..f660a8ab1 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -733,6 +733,54 @@ class TestTracebackCutting: "E*NameError*", ]) + def test_traceback_filter_error_during_fixture_collection(self, testdir): + """integration test for issue #995. + """ + testdir.makepyfile(""" + import pytest + + def fail_me(func): + ns = {} + exec('def w(): raise ValueError("fail me")', ns) + return ns['w'] + + @pytest.fixture(scope='class') + @fail_me + def fail_fixture(): + pass + + def test_failing_fixture(fail_fixture): + pass + """) + result = testdir.runpytest() + assert result.ret != 0 + out = result.stdout.str() + assert "INTERNALERROR>" not in out + result.stdout.fnmatch_lines([ + "*ValueError: fail me*", + "* 1 error in *", + ]) + + def test_filter_traceback_accepts_non_paths(self): + """test that filter_traceback() works around py.code.Code bug + where sometimes its "path" attribute is not a py.path.local object: + https://bitbucket.org/pytest-dev/py/issues/71 + This fixes #995. + """ + from _pytest.python import filter_traceback + try: + ns = {} + exec('def foo(): raise ValueError', ns) + ns['foo']() + except ValueError: + import sys + _, _, tb = sys.exc_info() + + tb = py.code.Traceback(tb) + assert isinstance(tb[-1].path, str) # symptom of the py.code bug + assert not filter_traceback(tb[-1]) + + class TestReportInfo: def test_itemreport_reportinfo(self, testdir, linecomp): testdir.makeconftest("""