from testutils import assert_raises class Foo(object): pass Foo.__repr__ = Foo.__str__ foo = Foo() # Since the default __str__ implementation calls __repr__ and __repr__ is # actually __str__, str(foo) should raise a RecursionError. assert_raises(RecursionError, str, foo) # A __call__ that is the object being called dispatches through the call slot # again, and none of that pushes a Python frame. class Caller: pass caller = Caller() Caller.__call__ = caller assert_raises(RecursionError, caller) # The same shape through the descriptor protocol: resolving the attribute # fetches __get__, which is the descriptor itself. class Descr: pass descr = Descr() Descr.__get__ = descr Descr.x = descr try: descr.x except (RecursionError, TypeError): # RecursionError here, TypeError from the call of a non-callable elsewhere pass else: raise AssertionError("descr.x should not resolve") def recurse(): recurse() try: recurse() except RecursionError as e: msg = str(e) assert msg, f"empty RecursionError message: {e!r}" assert not msg.endswith(" "), f"trailing space in RecursionError: {e!r}" import sys prev_limit = sys.getrecursionlimit() try: sys.setrecursionlimit(50) try: recurse() except RecursionError as e: assert str(e) == "maximum recursion depth exceeded", ( f"unexpected message: {e!r}" ) assert e.args == ("maximum recursion depth exceeded",), ( f"unexpected args: {e.args!r}" ) finally: sys.setrecursionlimit(prev_limit)