Merge pull request #2476 from nicoddemus/fix-2459-numpy-comparison
Fix internal error when a recursion error occurs and frames contain objects that can't be compared
This commit is contained in:
		
						commit
						917b9a8352
					
				|  | @ -3,7 +3,7 @@ import sys | ||||||
| from inspect import CO_VARARGS, CO_VARKEYWORDS | from inspect import CO_VARARGS, CO_VARKEYWORDS | ||||||
| import re | import re | ||||||
| from weakref import ref | from weakref import ref | ||||||
| from _pytest.compat import _PY2, _PY3, PY35 | from _pytest.compat import _PY2, _PY3, PY35, safe_str | ||||||
| 
 | 
 | ||||||
| import py | import py | ||||||
| builtin_repr = repr | builtin_repr = repr | ||||||
|  | @ -602,21 +602,48 @@ class FormattedExcinfo(object): | ||||||
|         traceback = excinfo.traceback |         traceback = excinfo.traceback | ||||||
|         if self.tbfilter: |         if self.tbfilter: | ||||||
|             traceback = traceback.filter() |             traceback = traceback.filter() | ||||||
|         recursionindex = None | 
 | ||||||
|         if is_recursion_error(excinfo): |         if is_recursion_error(excinfo): | ||||||
|             recursionindex = traceback.recursionindex() |             traceback, extraline = self._truncate_recursive_traceback(traceback) | ||||||
|  |         else: | ||||||
|  |             extraline = None | ||||||
|  | 
 | ||||||
|         last = traceback[-1] |         last = traceback[-1] | ||||||
|         entries = [] |         entries = [] | ||||||
|         extraline = None |  | ||||||
|         for index, entry in enumerate(traceback): |         for index, entry in enumerate(traceback): | ||||||
|             einfo = (last == entry) and excinfo or None |             einfo = (last == entry) and excinfo or None | ||||||
|             reprentry = self.repr_traceback_entry(entry, einfo) |             reprentry = self.repr_traceback_entry(entry, einfo) | ||||||
|             entries.append(reprentry) |             entries.append(reprentry) | ||||||
|             if index == recursionindex: |  | ||||||
|                 extraline = "!!! Recursion detected (same locals & position)" |  | ||||||
|                 break |  | ||||||
|         return ReprTraceback(entries, extraline, style=self.style) |         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): |     def repr_excinfo(self, excinfo): | ||||||
|         if _PY2: |         if _PY2: | ||||||
|  |  | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | Fix recursion error detection when frames in the traceback contain objects that can't be compared (like ``numpy`` arrays). | ||||||
|  | @ -1140,3 +1140,36 @@ def test_cwd_deleted(testdir): | ||||||
|     result = testdir.runpytest() |     result = testdir.runpytest() | ||||||
|     result.stdout.fnmatch_lines(['* 1 failed in *']) |     result.stdout.fnmatch_lines(['* 1 failed in *']) | ||||||
|     assert 'INTERNALERROR' not in result.stdout.str() + result.stderr.str() |     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*', | ||||||
|  |         ]) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue