diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7c2e5565653..34fee332861 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,38 @@ .. towncrier release notes start +Pytest 3.1.2 (2017-06-08) +========================= + +Bug Fixes +--------- + +- Required options added via ``pytest_addoption`` will no longer prevent using + --help without passing them. (#1999) + +- Respect ``python_files`` in assertion rewriting. (#2121) + +- Fix recursion error detection when frames in the traceback contain objects + that can't be compared (like ``numpy`` arrays). (#2459) + +- ``UnicodeWarning`` is issued from the internal pytest warnings plugin only + when the message contains non-ascii unicode (Python 2 only). (#2463) + +- Added a workaround for Python 3.6 WindowsConsoleIO breaking due to Pytests's + FDCapture. Other code using console handles might still be affected by the + very same issue and might require further workarounds/fixes, i.e. colorama. + (#2467) + + +Improved Documentation +---------------------- + +- Fix internal API links to ``pluggy`` objects. (#2331) + +- Make it clear that ``pytest.xfail`` stops test execution at the calling point + and improve overall flow of the ``skipping`` docs. (#810) + + Pytest 3.1.1 (2017-05-30) ========================= diff --git a/_pytest/_code/code.py b/_pytest/_code/code.py index f872dba0be3..9b3408dc476 100644 --- a/_pytest/_code/code.py +++ b/_pytest/_code/code.py @@ -3,7 +3,7 @@ from inspect import CO_VARARGS, CO_VARKEYWORDS import re from weakref import ref -from _pytest.compat import _PY2, _PY3, PY35 +from _pytest.compat import _PY2, _PY3, PY35, safe_str import py builtin_repr = repr @@ -602,21 +602,48 @@ def repr_traceback(self, excinfo): traceback = excinfo.traceback if self.tbfilter: traceback = traceback.filter() - recursionindex = None + if is_recursion_error(excinfo): - recursionindex = traceback.recursionindex() + traceback, extraline = self._truncate_recursive_traceback(traceback) + else: + extraline = None + last = traceback[-1] entries = [] - extraline = None for index, entry in enumerate(traceback): einfo = (last == entry) and excinfo or None reprentry = self.repr_traceback_entry(entry, einfo) entries.append(reprentry) - if index == recursionindex: - extraline = "!!! Recursion detected (same locals & position)" - break return ReprTraceback(entries, extraline, style=self.style) + def _truncate_recursive_traceback(self, traceback): + """ + Truncate the given recursive traceback trying to find the starting point + of the recursion. + + The detection is done by going through each traceback entry and finding the + point in which the locals of the frame are equal to the locals of a previous frame (see ``recursionindex()``. + + Handle the situation where the recursion process might raise an exception (for example + comparing numpy arrays using equality raises a TypeError), in which case we do our best to + warn the user of the error and show a limited traceback. + """ + try: + recursionindex = traceback.recursionindex() + except Exception as e: + max_frames = 10 + extraline = ( + '!!! Recursion error detected, but an error occurred locating the origin of recursion.\n' + ' The following exception happened when comparing locals in the stack frame:\n' + ' {exc_type}: {exc_msg}\n' + ' Displaying first and last {max_frames} stack frames out of {total}.' + ).format(exc_type=type(e).__name__, exc_msg=safe_str(e), max_frames=max_frames, total=len(traceback)) + traceback = traceback[:max_frames] + traceback[-max_frames:] + else: + extraline = "!!! Recursion detected (same locals & position)" + traceback = traceback[:recursionindex + 1] + + return traceback, extraline def repr_excinfo(self, excinfo): if _PY2: diff --git a/_pytest/warnings.py b/_pytest/warnings.py index b227dd68a9c..4fe28bd315b 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -66,8 +66,9 @@ def catch_warnings_for_item(item): unicode_warning = False if compat._PY2 and any(isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args): - warn_msg.args = [compat.safe_str(m) for m in warn_msg.args] - unicode_warning = True + new_args = [compat.safe_str(m) for m in warn_msg.args] + unicode_warning = warn_msg.args != new_args + warn_msg.args = new_args msg = warnings.formatwarning( warn_msg, warning.category, @@ -76,8 +77,8 @@ def catch_warnings_for_item(item): if unicode_warning: warnings.warn( - "This warning %s is broken as it's message is not a str instance" - "(after all this is a stdlib problem workaround)" % msg, + "Warning is using unicode non convertible to ascii, " + "converting to a safe representation:\n %s" % msg, UnicodeWarning) diff --git a/changelog/1999.bugfix b/changelog/1999.bugfix deleted file mode 100644 index 5321b6c2197..00000000000 --- a/changelog/1999.bugfix +++ /dev/null @@ -1,2 +0,0 @@ -Required options added via ``pytest_addoption`` will no longer prevent -using --help without passing them. diff --git a/changelog/2121.bugfix b/changelog/2121.bugfix deleted file mode 100644 index 15fd7706c58..00000000000 --- a/changelog/2121.bugfix +++ /dev/null @@ -1 +0,0 @@ -Respect ``python_files`` in assertion rewriting. diff --git a/changelog/2331.doc b/changelog/2331.doc deleted file mode 100644 index 239f67318e5..00000000000 --- a/changelog/2331.doc +++ /dev/null @@ -1 +0,0 @@ -Fix internal API links to ``pluggy`` objects. diff --git a/changelog/2467.bugfix b/changelog/2467.bugfix deleted file mode 100644 index e67110b64ea..00000000000 --- a/changelog/2467.bugfix +++ /dev/null @@ -1,3 +0,0 @@ -Added a workaround for Python 3.6 WindowsConsoleIO breaking due to Pytests's -FDCapture. Other code using console handles might still be affected by the -very same issue and might require further workarounds/fixes, i.e. colorama. diff --git a/changelog/810.doc b/changelog/810.doc deleted file mode 100644 index dc1d3e52c20..00000000000 --- a/changelog/810.doc +++ /dev/null @@ -1 +0,0 @@ -Make it clear that ``pytest.xfail`` stops test execution at the calling point and improve overall flow of the ``skipping`` docs. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 282e9d9f457..dbb4e24d60c 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-3.1.2 release-3.1.1 release-3.1.0 release-3.0.7 diff --git a/doc/en/announce/release-3.1.2.rst b/doc/en/announce/release-3.1.2.rst new file mode 100644 index 00000000000..60168a857ba --- /dev/null +++ b/doc/en/announce/release-3.1.2.rst @@ -0,0 +1,23 @@ +pytest-3.1.2 +======================================= + +pytest 3.1.2 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Andreas Pelme +* ApaDoctor +* Bruno Oliveira +* Florian Bruhin +* Ronny Pfannschmidt +* Segev Finer + + +Happy testing, +The pytest Development Team diff --git a/doc/en/skipping.rst b/doc/en/skipping.rst index 3903844baa0..72b7b043323 100644 --- a/doc/en/skipping.rst +++ b/doc/en/skipping.rst @@ -322,8 +322,6 @@ Running it with the report-on-xfail option gives this output:: ======= 7 xfailed in 0.12 seconds ======== - - .. _`skip/xfail with parametrize`: Skip/xfail with parametrize diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index b7dafdb46b4..3128beff835 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -1140,3 +1140,36 @@ def test(tmpdir): result = testdir.runpytest() result.stdout.fnmatch_lines(['* 1 failed in *']) assert 'INTERNALERROR' not in result.stdout.str() + result.stderr.str() + + +def test_exception_repr_extraction_error_on_recursion(): + """ + Ensure we can properly detect a recursion error even + if some locals raise error on comparision (#2459). + """ + class numpy_like(object): + + def __eq__(self, other): + if type(other) is numpy_like: + raise ValueError('The truth value of an array ' + 'with more than one element is ambiguous.') + + def a(x): + return b(numpy_like()) + + def b(x): + return a(numpy_like()) + + try: + a(numpy_like()) + except: + from _pytest._code.code import ExceptionInfo + from _pytest.pytester import LineMatcher + exc_info = ExceptionInfo() + + matcher = LineMatcher(str(exc_info.getrepr()).splitlines()) + matcher.fnmatch_lines([ + '!!! Recursion error detected, but an error occurred locating the origin of recursion.', + '*The following exception happened*', + '*ValueError: The truth value of an array*', + ]) diff --git a/testing/test_warnings.py b/testing/test_warnings.py index ca4345abc9b..69bda1172fa 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -161,7 +161,7 @@ def test_func(fix): '*test_py2_unicode.py:8: UserWarning: \u6d4b\u8bd5', '*warnings.warn(u"\u6d4b\u8bd5")', - '*warnings.py:*: UnicodeWarning: This warning*\u6d4b\u8bd5', + '*warnings.py:*: UnicodeWarning: Warning is using unicode non*', '* 1 passed, 2 warnings*', ])