summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/doctest.py55
-rw-r--r--Lib/test/test_doctest2.py112
-rw-r--r--Lib/test/test_pyclbr.py5
-rw-r--r--Lib/test/test_support.py3
4 files changed, 167 insertions, 8 deletions
diff --git a/Lib/doctest.py b/Lib/doctest.py
index 184699a..91c51cb 100644
--- a/Lib/doctest.py
+++ b/Lib/doctest.py
@@ -258,7 +258,7 @@ ok
8 tests in doctest
6 tests in doctest.Tester
10 tests in doctest.Tester.merge
- 7 tests in doctest.Tester.rundict
+ 14 tests in doctest.Tester.rundict
3 tests in doctest.Tester.rundoc
3 tests in doctest.Tester.runstring
2 tests in doctest.__test__._TestClass
@@ -267,8 +267,8 @@ ok
1 tests in doctest.__test__._TestClass.square
2 tests in doctest.__test__.string
7 tests in doctest.is_private
-53 tests in 17 items.
-53 passed and 0 failed.
+60 tests in 17 items.
+60 passed and 0 failed.
Test passed.
"""
@@ -295,6 +295,7 @@ from types import StringTypes as _StringTypes
from inspect import isclass as _isclass
from inspect import isfunction as _isfunction
from inspect import ismodule as _ismodule
+from inspect import classify_class_attrs as _classify_class_attrs
# Extract interactive examples from a string. Return a list of triples,
# (source, outcome, lineno). "source" is the source code, and ends
@@ -747,9 +748,51 @@ see its docs for details.
print f, "of", t, "examples failed in", name + ".__doc__"
self.__record_outcome(name, f, t)
if _isclass(object):
- f2, t2 = self.rundict(object.__dict__, name)
- f = f + f2
- t = t + t2
+ # In 2.2, class and static methods complicate life. Build
+ # a dict "that works", by hook or by crook.
+ d = {}
+ for tag, kind, homecls, value in _classify_class_attrs(object):
+
+ if homecls is not object:
+ # Only look at names defined immediately by the class.
+ continue
+
+ elif self.isprivate(name, tag):
+ continue
+
+ elif kind == "method":
+ # value is already a function
+ d[tag] = value
+
+ elif kind == "static method":
+ # value isn't a function, but getattr reveals one
+ d[tag] = getattr(object, tag)
+
+ elif kind == "class method":
+ # Hmm. A classmethod object doesn't seem to reveal
+ # enough. But getattr turns it into a bound method,
+ # and from there .im_func retrieves the underlying
+ # function.
+ d[tag] = getattr(object, tag).im_func
+
+ elif kind == "property":
+ # The methods implementing the property have their
+ # own docstrings -- but the property may have one too.
+ if value.__doc__ is not None:
+ d[tag] = str(value.__doc__)
+
+ elif kind == "data":
+ # Grab nested classes.
+ if _isclass(value):
+ d[tag] = value
+
+ else:
+ raise ValueError("teach doctest about %r" % kind)
+
+ f2, t2 = self.run__test__(d, name)
+ f += f2
+ t += t2
+
return f, t
def rundict(self, d, name, module=None):
diff --git a/Lib/test/test_doctest2.py b/Lib/test/test_doctest2.py
new file mode 100644
index 0000000..0cbe3d4
--- /dev/null
+++ b/Lib/test/test_doctest2.py
@@ -0,0 +1,112 @@
+"""A module to test whether doctest recognizes some 2.2 features,
+like static and class methods.
+
+>>> print 'yup' # 1
+yup
+"""
+
+import test_support
+
+# XXX The class docstring is skipped.
+class C(object):
+ """Class C.
+
+ >>> print C() # 2
+ 42
+ """
+
+ def __init__(self):
+ """C.__init__.
+
+ >>> print C() # 3
+ 42
+ """
+
+ def __str__(self):
+ """
+ >>> print C() # 4
+ 42
+ """
+ return "42"
+
+ # XXX The class docstring is skipped.
+ class D(object):
+ """A nested D class.
+
+ >>> print "In D!" # 5
+ In D!
+ """
+
+ def nested(self):
+ """
+ >>> print 3 # 6
+ 3
+ """
+
+ def getx(self):
+ """
+ >>> c = C() # 7
+ >>> c.x = 12 # 8
+ >>> print c.x # 9
+ -12
+ """
+ return -self._x
+
+ def setx(self, value):
+ """
+ >>> c = C() # 10
+ >>> c.x = 12 # 11
+ >>> print c.x # 12
+ -12
+ """
+ self._x = value
+
+ x = property(getx, setx, doc="""\
+ >>> c = C() # 13
+ >>> c.x = 12 # 14
+ >>> print c.x # 15
+ -12
+ """)
+
+ def statm():
+ """
+ A static method.
+
+ >>> print C.statm() # 16
+ 666
+ >>> print C().statm() # 17
+ 666
+ """
+ return 666
+
+ statm = staticmethod(statm)
+
+ def clsm(cls, val):
+ """
+ A class method.
+
+ >>> print C.clsm(22) # 18
+ 22
+ >>> print C().clsm(22) # 19
+ 22
+ """
+ return 22
+
+ clsm = classmethod(clsm)
+
+def test_main():
+ import test_doctest2
+ # XXX 2 class docstrings are skipped.
+ # EXPECTED = 19
+ EXPECTED = 17
+ f, t = test_support.run_doctest(test_doctest2)
+ if t != EXPECTED:
+ raise test_support.TestFailed("expected %d tests to run, not %d" %
+ (EXPECTED, t))
+
+# Pollute the namespace with a bunch of imported functions and classes,
+# to make sure they don't get tested.
+from doctest import *
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/test/test_pyclbr.py b/Lib/test/test_pyclbr.py
index fdb3ddf..1a241cf 100644
--- a/Lib/test/test_pyclbr.py
+++ b/Lib/test/test_pyclbr.py
@@ -101,7 +101,10 @@ class PyclbrTest(unittest.TestCase):
def test_easy(self):
self.checkModule('pyclbr')
self.checkModule('doctest',
- ignore=['_isclass', '_isfunction', '_ismodule'])
+ ignore=['_isclass',
+ '_isfunction',
+ '_ismodule',
+ '_classify_class_attrs'])
self.checkModule('rfc822')
self.checkModule('xmllib')
self.checkModule('difflib')
diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py
index 04a778b..e1923a6 100644
--- a/Lib/test/test_support.py
+++ b/Lib/test/test_support.py
@@ -176,7 +176,7 @@ def run_unittest(testclass):
# doctest driver.
def run_doctest(module, verbosity=None):
- """Run doctest on the given module.
+ """Run doctest on the given module. Return (#failures, #tests).
If optional argument verbosity is not specified (or is None), pass
test_support's belief about verbosity on to doctest. Else doctest's
@@ -198,5 +198,6 @@ def run_doctest(module, verbosity=None):
f, t = doctest.testmod(module, verbose=verbosity)
if f:
raise TestFailed("%d of %d doctests failed" % (f, t))
+ return f, t
finally:
sys.stdout = save_stdout