diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 118fc8b1..eab0b36e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,13 @@ +2.4.1 +----- + +- Properly handle chained exceptions when capturing them inside + virtual methods (`#215`_). Thanks `@fabioz`_ for the report and sample + code with the fix. + +.. _#215: https://github.com/pytest-dev/pytest-qt/pull/215 + + 2.4.0 ----- diff --git a/pytestqt/exceptions.py b/pytestqt/exceptions.py index 3b0689f4..75dec33c 100644 --- a/pytestqt/exceptions.py +++ b/pytestqt/exceptions.py @@ -68,12 +68,19 @@ def format_captured_exceptions(exceptions): Formats exceptions given as (type, value, traceback) into a string suitable to display as a test failure. """ - message = 'Qt exceptions in virtual methods:\n' - message += '_' * 80 + '\n' + if sys.version_info.major == 2: + from StringIO import StringIO + else: + from io import StringIO + + stream = StringIO() + stream.write('Qt exceptions in virtual methods:\n') + sep = '_' * 80 + '\n' + stream.write(sep) for (exc_type, value, tback) in exceptions: - message += ''.join(traceback.format_exception(exc_type, value, tback)) + '\n' - message += '_' * 80 + '\n' - return message + traceback.print_exception(exc_type, value, tback, file=stream) + stream.write(sep) + return stream.getvalue() def _is_exception_capture_enabled(item): diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 3bb5ca9a..c0ace2c8 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,7 +1,9 @@ -from pytestqt.exceptions import capture_exceptions, format_captured_exceptions -import pytest import sys +import pytest + +from pytestqt.exceptions import capture_exceptions, format_captured_exceptions + @pytest.mark.parametrize('raise_error', [False, True]) def test_catch_exceptions_in_virtual_methods(testdir, raise_error): @@ -18,7 +20,11 @@ class Receiver(qt_api.QtCore.QObject): def event(self, ev): if {raise_error}: - raise ValueError('mistakes were made') + try: + raise RuntimeError('original error') + except RuntimeError: + raise ValueError('mistakes were made') + return qt_api.QtCore.QObject.event(self, ev) @@ -32,11 +38,14 @@ def test_exceptions(qtbot): '''.format(raise_error=raise_error)) result = testdir.runpytest() if raise_error: - result.stdout.fnmatch_lines([ - '*Qt exceptions in virtual methods:*', + expected_lines = ['*Qt exceptions in virtual methods:*'] + if sys.version_info.major == 3: + expected_lines.append('RuntimeError: original error') + expected_lines.extend([ '*ValueError: mistakes were made*', - '*1 failed*', + '*1 failed*' ]) + result.stdout.fnmatch_lines(expected_lines) assert 'pytest.fail' not in '\n'.join(result.outlines) else: result.stdout.fnmatch_lines('*1 passed*') @@ -55,6 +64,24 @@ def test_format_captured_exceptions(): assert 'ValueError: errors were made' in lines +@pytest.mark.skipif(sys.version_info.major == 2, reason='Python 3 only') +def test_format_captured_exceptions_chained(): + try: + try: + raise ValueError('errors were made') + except ValueError: + raise RuntimeError('error handling value error') + except RuntimeError: + exceptions = [sys.exc_info()] + + obtained_text = format_captured_exceptions(exceptions) + lines = obtained_text.splitlines() + + assert 'Qt exceptions in virtual methods:' in lines + assert 'ValueError: errors were made' in lines + assert 'RuntimeError: error handling value error' in lines + + @pytest.mark.parametrize('no_capture_by_marker', [True, False]) def test_no_capture(testdir, no_capture_by_marker): """