This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: doctest chokes on recursive members
Type: Stage:
Components: Library (Lib) Versions: Python 2.3
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: tim.peters Nosy List: gintautasm, mgedmin, terry.reedy, tim.peters
Priority: normal Keywords:

Created on 2003-12-23 11:51 by gintautasm, last changed 2022-04-11 14:56 by admin. This issue is now closed.

Messages (6)
msg19471 - (view) Author: Gintautas Miliauskas (gintautasm) Date: 2003-12-23 11:51
doctests recurse infinitely when they find a recursive
member in a class. A simple example:

--b0rk.py--
class Crash:
    pass

Crash.c = Crash
-----

--test.py--
import doctest
import b0rk

doctest.testmod(b0rk)
-----

--output of test.py--
Traceback (most recent call last):
  File "test.py", line 5, in ?
    doctest.testmod(b0rk)
  File "/usr/lib/python2.3/doctest.py", line 1148, in
testmod
    f, t = tester.rundict(m.__dict__, name, m)
  File "/usr/lib/python2.3/doctest.py", line 908, in
rundict
    f2, t2 = self.__runone(value, name + "." + thisname)
  File "/usr/lib/python2.3/doctest.py", line 1069, in
__runone
    return self.rundoc(target, name)
  File "/usr/lib/python2.3/doctest.py", line 828, in rundoc
    f2, t2 = self.run__test__(d, name)
<.............................>
  File "/usr/lib/python2.3/doctest.py", line 935, in
run__test__
    f, t = self.rundoc(v, thisname)
  File "/usr/lib/python2.3/doctest.py", line 790, in rundoc
    for tag, kind, homecls, value in
_classify_class_attrs(object):
  File "/usr/lib/python2.3/inspect.py", line 201, in
classify_class_attrs
    mro = getmro(cls)
RuntimeError: maximum recursion depth exceeded
-----
msg19472 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2003-12-28 22:05
Logged In: YES 
user_id=593130

Traceback says crash happens in inspect.getmro and not in 
doctest itself.  In 2.2.1, I get

>>> class C: pass
...
>>> C.c=C
>>> import inspect as i
>>> i.getmro(C)
(<class __main__.C at 0x00867510>,)

However, mro resolution was changed for 2.3.  See if above 
changes in 2.3.  Or if calling on class inside imported module 
makes a difference.
msg19473 - (view) Author: Gintautas Miliauskas (gintautasm) Date: 2003-12-29 10:46
Logged In: YES 
user_id=936754

I guess I snipped a little too much traceback...

Here is a part of traceback (reproduced with python 2.2.)
that illustrates the idea:

<...>
  File "/usr/lib/python2.2/doctest.py", line 905, in run__test__
    f, t = self.rundoc(v, thisname)
  File "/usr/lib/python2.2/doctest.py", line 798, in rundoc
    f2, t2 = self.run__test__(d, name)
  File "/usr/lib/python2.2/doctest.py", line 905, in run__test__
    f, t = self.rundoc(v, thisname)
  File "/usr/lib/python2.2/doctest.py", line 798, in rundoc
    f2, t2 = self.run__test__(d, name)
  File "/usr/lib/python2.2/doctest.py", line 905, in run__test__
    f, t = self.rundoc(v, thisname)
  File "/usr/lib/python2.2/doctest.py", line 798, in rundoc
    f2, t2 = self.run__test__(d, name)
<...>

It's definitely not a fault of getmro(). Besides, I've
tested and the crash occurs on Python 2.1, 2.2 and 2.3.
msg19474 - (view) Author: Marius Gedminas (mgedmin) * Date: 2003-12-29 10:52
Logged In: YES 
user_id=44660

The fact that the traceback ends in getmro is just an accident.
There is an infinite recursion between run__test__ and rundoc
when a class attribute references the class itself.

The problem was originally noticed by playing with Zope 3.
Apparently Zope 3 interfaces add a class attribute that
references the class itself (perhaps indirectly) and causes this
problem to appear whenever a class declares that it implements
some interface, e.g.

  from zope.interface import implements

  class SomeClass(object):
      """docstring

      >>> x = SomeClass()
      ...
      """

      implements(ISomeInterface)

Since Zope 3 component architecture requires that virtually
all classes declare what interfaces they implement, it makes
class doctests unusable with Zope 3.

A possible solution is to keep a set of "seen" objects and check
near the beginning of Tester.rundoc whether an object was
already processed.
msg19475 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2004-08-08 03:25
Logged In: YES 
user_id=31435

This is fixed in Python 2.4's doctest.py, which does keep a 
set of already-seen objects.  But, since this was done as 
part of a massive refactoring of doctest.py, I have no plan to 
backport any part of it to the 2.3 line.
msg19476 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2004-08-08 03:29
Logged In: YES 
user_id=31435

BTW, since you mentioned Zope3, Zope3 will use its own 
copy of 2.4's doctest.py until 2.4 becomes the minimum 
version.  So there's no need to backport anything for Zope3's 
benefit.
History
Date User Action Args
2022-04-11 14:56:01adminsetgithub: 39728
2003-12-23 11:51:35gintautasmcreate

Follow Lee on X/Twitter - Father, Husband, Serial builder creating AI, crypto, games & web tools. We are friends :) AI Will Come To Life!

Check out: eBank.nz (Art Generator) | Netwrck.com (AI Tools) | Text-Generator.io (AI API) | BitBank.nz (Crypto AI) | ReadingTime (Kids Reading) | RewordGame | BigMultiplayerChess | WebFiddle | How.nz | Helix AI Assistant