-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Internal error due to infinite recursion #13156
Comments
This looks very similar to #3163, #4648 and #8957 (none of which have a reproducer either, unfortunately). Given that the infinite recursion happens inside Python's own Without a reproducer, I don't think there is much that can be done from pytest's side. Could you perhaps show the stacktrace with |
Here is the code where the loop occurs: https://github.com/python/cpython/blob/v3.6.15/Lib/traceback.py#L489-L498 So for that to happen, there needs to be an exception that:
For the "very long chain of exceptions" scenario, I found python/cpython#87014 which seems similar enough to your stacktrace, and that has been fixed in Python 3.10: python/cpython@6dfd173 So unless there is a reproducer that shows the contrary, this is a CPython bug that's been fixed already. |
Thank you for the quick reply. Here's the stack trace produced with
|
Thank you for this post, especially the links to cpython. from unittest.mock import patch
from traceback import StackSummary, TracebackException, _some_str, walk_tb
from contextlib import contextmanager
"""
Adopted https://github.com/python/cpython/commit/6dfd1734f5b230bb8fbd2a9df806c1333b6652a8
to prevent MaxRecursionError in tests.
To be removed completely once we use Python 3.10+.
"""
@contextmanager
def patch_traceback():
def _load_lines(self):
"""Private API. force all lines in the stack to be loaded."""
for frame in self.stack:
frame.line
def format(self, *, chain=True):
"""Format the exception.
If chain is not *True*, *__cause__* and *__context__* will not be formatted.
The return value is a generator of strings, each ending in a newline and
some containing internal newlines. `print_exception` is a wrapper around
this method which just prints the lines to a file.
The message indicating which exception occurred is always the last
string in the output.
"""
output = []
exc = self
while exc:
if chain:
if exc.__cause__ is not None:
chained_msg = _cause_message
chained_exc = exc.__cause__
elif (exc.__context__ is not None and
not exc.__suppress_context__):
chained_msg = _context_message
chained_exc = exc.__context__
else:
chained_msg = None
chained_exc = None
output.append((chained_msg, exc))
exc = chained_exc
else:
output.append((None, exc))
exc = None
for msg, exc in reversed(output):
if msg is not None:
yield msg
if exc.stack:
yield 'Traceback (most recent call last):\n'
yield from exc.stack.format()
yield from exc.format_exception_only()
def __init__(
self,
exc_type,
exc_value,
exc_traceback,
*,
limit=None,
lookup_lines=True,
capture_locals=False,
_seen=None
):
is_recursive_call = _seen is not None
# NB: we need to accept exc_traceback, exc_value, exc_traceback to
# permit backwards compat with the existing API, otherwise we
# need stub thunk objects just to glue it together.
# Handle loops in __cause__ or __context__.
if _seen is None:
_seen = set()
_seen.add(id(exc_value))
# TODO: locals.
self.stack = StackSummary.extract(
walk_tb(exc_traceback),
limit=limit,
lookup_lines=lookup_lines,
capture_locals=capture_locals,
)
self.exc_type = exc_type
# Capture now to permit freeing resources: only complication is in the
# unofficial API _format_final_exc_line
self._str = _some_str(exc_value)
if exc_type and issubclass(exc_type, SyntaxError):
# Handle SyntaxError's specially
self.filename = exc_value.filename
self.lineno = str(exc_value.lineno)
self.text = exc_value.text
self.offset = exc_value.offset
self.msg = exc_value.msg
if lookup_lines:
self._load_lines()
self.__suppress_context__ = (
exc_value.__suppress_context__ if exc_value else False
)
# Convert __cause__ and __context__ to `TracebackExceptions`s, use a
# queue to avoid recursion (only the top-level call gets _seen == None)
if not is_recursive_call:
queue = [(self, exc_value)]
while queue:
te, e = queue.pop()
if (
e
and e.__cause__ is not None
and id(e.__cause__) not in _seen
):
cause = TracebackException(
type(e.__cause__),
e.__cause__,
e.__cause__.__traceback__,
limit=limit,
lookup_lines=lookup_lines,
capture_locals=capture_locals,
_seen=_seen,
)
else:
cause = None
if (
e
and e.__context__ is not None
and id(e.__context__) not in _seen
):
context = TracebackException(
type(e.__context__),
e.__context__,
e.__context__.__traceback__,
limit=limit,
lookup_lines=lookup_lines,
capture_locals=capture_locals,
_seen=_seen,
)
else:
context = None
te.__cause__ = cause
te.__context__ = context
if cause:
queue.append((te.__cause__, e.__cause__))
if context:
queue.append((te.__context__, e.__context__))
with patch('traceback.TracebackException.__init__', __init__), patch(
'traceback.TracebackException._load_lines', _load_lines
), patch(
'traceback.TracebackException.format', format
):
yield
@pytest.fixture(autouse=True, scope='session')
def patch_traceback_fixture():
with patch_traceback():
yield |
Closing this then, as it indeed looks like you're just seeing a very long traceback and this CPython bug. |
Error description
We are experiencing an internal error that enters an infinite loop – from what I can see, during reporting another error.
pip list
Pytest and OS versions
Detailed description
Unfortunately, the details how to replicate this issue are a bit elusive to me still and as such I can't provide a minimal example at this time. Hopefully, the stack trace contains enough information. Additionally, we heavily use selenium in the affected tests, and the app is built on Django. What I tried:
--tb=no
,--tb=native
,-p no:warnings
does not help.pytest-custom-exit-code
package does not help.I run the tests with the following invocation:
I understand that this is about an obsolete Python version and as such the issue will be likely closed.
The text was updated successfully, but these errors were encountered: