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 | ||||
| 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 @@ class FormattedExcinfo(object): | |||
|         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: | ||||
|  |  | |||
|  | @ -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.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*', | ||||
|         ]) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue