From 17111f3b242be06c7ae913f106093891b82d7fee Mon Sep 17 00:00:00 2001 From: Tim Peters Date: Wed, 3 Oct 2001 04:08:26 +0000 Subject: SF bug [#467336] doctest failures w/ new-style classes. Taught doctest about static methods, class methods, and property docstrings in new-style classes. As for inspect.py/pydoc.py before it, the new stuff needed didn't really fit into the old architecture (but was less of a strain to force-fit here). New-style class docstrings still aren't found, but that's the subject of a different bug and I want to fix that right instead of hacking around it in doctest. --- Lib/doctest.py | 55 ++++++++++++++++++++--- Lib/test/test_doctest2.py | 112 ++++++++++++++++++++++++++++++++++++++++++++++ Lib/test/test_pyclbr.py | 5 ++- Lib/test/test_support.py | 3 +- 4 files changed, 167 insertions(+), 8 deletions(-) create mode 100644 Lib/test/test_doctest2.py 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 -- cgit v0.12