Fix internal error with recursive tracebacks with that frames contain objects that can't be compared
Fix #2459
This commit is contained in:
parent
57e2ced969
commit
2127a2378a
|
@ -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