From 11f100813e4aa40d5c89f904aea6e2ac38c4607e Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 29 Sep 2015 22:29:43 -0300 Subject: [PATCH 1/5] Fix internal error when filtering tracebacks where one entry was generated by an exec() statement Fix #995 --- CHANGELOG | 4 ++++ _pytest/python.py | 5 +++- testing/python/collect.py | 48 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b0141f257..c05a55008 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,10 @@ 2.8.2.dev --------- +- 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/python.py b/_pytest/python.py index 0ad18b3ef..8a1d04c6c 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -49,7 +49,10 @@ def _has_positional_arg(func): def filter_traceback(entry): - return entry.path != cutdir1 and not entry.path.relto(cutdir2) + # ensure entry.path is always a py.path.local object + # see https://bitbucket.org/pytest-dev/py/issues/71 + path = py.path.local(entry.path) + return path != cutdir1 and not path.relto(cutdir2) def get_real_func(obj): diff --git a/testing/python/collect.py b/testing/python/collect.py index fba8c477c..e9178fb39 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 filter_traceback(tb[-1]) + + class TestReportInfo: def test_itemreport_reportinfo(self, testdir, linecomp): testdir.makeconftest(""" From a808e092040c9575312b372b3779cff2e9fee4da Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 30 Sep 2015 00:27:06 -0300 Subject: [PATCH 2/5] fix docstring for cache fixture regarding ``/`` on keys --- _pytest/cacheprovider.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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. """ From f4f23e8e097d383758d128a2d70816660779353a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Wed, 30 Sep 2015 16:12:33 +0300 Subject: [PATCH 3/5] Correct hook examples in docs. --- doc/en/writing_plugins.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index 1e9807cf5..865b079be 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.mark.tryfirst def pytest_collection_modifyitems(items): # will execute as early as possible # Plugin 2 - @pytest.hookimpl_spec(trylast=True) + @pytest.mark.trylast 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 From c8f5a40fd98b28c5f1f3296a7e853ffe40644a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Wed, 30 Sep 2015 16:58:12 +0300 Subject: [PATCH 4/5] Edit examples again to use hookimpl. --- doc/en/writing_plugins.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index 865b079be..bc738f513 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -332,12 +332,12 @@ after others, i.e. the position in the ``N``-sized list of functions: .. code-block:: python # Plugin 1 - @pytest.mark.tryfirst + @pytest.hookimpl(tryfirst=True) def pytest_collection_modifyitems(items): # will execute as early as possible # Plugin 2 - @pytest.mark.trylast + @pytest.hookimpl(trylast=True) def pytest_collection_modifyitems(items): # will execute as late as possible From d1e00f6e194741ba6e9ff1e662fe9145069211bd Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 30 Sep 2015 17:15:53 -0300 Subject: [PATCH 5/5] Detect dynamic code explicitly in filter_traceback --- _pytest/python.py | 10 +++++++--- testing/python/collect.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/_pytest/python.py b/_pytest/python.py index 8a1d04c6c..20e790530 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -49,10 +49,14 @@ def _has_positional_arg(func): def filter_traceback(entry): - # ensure entry.path is always a py.path.local object + # 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 - path = py.path.local(entry.path) - return path != cutdir1 and not path.relto(cutdir2) + 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) def get_real_func(obj): diff --git a/testing/python/collect.py b/testing/python/collect.py index e9178fb39..f660a8ab1 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -778,7 +778,7 @@ class TestTracebackCutting: tb = py.code.Traceback(tb) assert isinstance(tb[-1].path, str) # symptom of the py.code bug - assert filter_traceback(tb[-1]) + assert not filter_traceback(tb[-1]) class TestReportInfo: