[Jython-checkins] jython: Update doctest.py from CPythonLib at r85505
frank.wierzbicki
jython-checkins at python.org
Wed Mar 14 04:09:31 CET 2012
http://hg.python.org/jython/rev/d5f2af98d603
changeset: 6342:d5f2af98d603
user: Alex Grönholm <alex.gronholm at nextday.fi>
date: Tue Mar 13 13:19:21 2012 -0700
summary:
Update doctest.py from CPythonLib at r85505
files:
Lib/doctest.py | 168 +++++++++++++++++++++++++-----------
1 files changed, 117 insertions(+), 51 deletions(-)
diff --git a/Lib/doctest.py b/Lib/doctest.py
--- a/Lib/doctest.py
+++ b/Lib/doctest.py
@@ -99,6 +99,9 @@
import unittest, difflib, pdb, tempfile
import warnings
from StringIO import StringIO
+from collections import namedtuple
+
+TestResults = namedtuple('TestResults', 'failed attempted')
# There are 4 basic classes:
# - Example: a <source, want> pair, plus an intra-docstring line number.
@@ -213,13 +216,21 @@
# get_data() opens files as 'rb', so one must do the equivalent
# conversion as universal newlines would do.
return file_contents.replace(os.linesep, '\n'), filename
- return open(filename).read(), filename
+ with open(filename) as f:
+ return f.read(), filename
+
+# Use sys.stdout encoding for ouput.
+_encoding = getattr(sys.__stdout__, 'encoding', None) or 'utf-8'
def _indent(s, indent=4):
"""
- Add the given number of space characters to the beginning every
- non-blank line in `s`, and return the result.
+ Add the given number of space characters to the beginning of
+ every non-blank line in `s`, and return the result.
+ If the string `s` is Unicode, it is encoded using the stdout
+ encoding and the `backslashreplace` error handler.
"""
+ if isinstance(s, unicode):
+ s = s.encode(_encoding, 'backslashreplace')
# This regexp matches the start of non-blank lines:
return re.sub('(?m)^(?!$)', indent*' ', s)
@@ -253,6 +264,9 @@
StringIO.truncate(self, size)
if hasattr(self, "softspace"):
del self.softspace
+ if not self.buf:
+ # Reset it to an empty string, to make sure it's not unicode.
+ self.buf = ''
# Worst-case linear-time ellipsis matching.
def _ellipsis_match(want, got):
@@ -322,6 +336,8 @@
self.__out = out
self.__debugger_used = False
pdb.Pdb.__init__(self, stdout=out)
+ # still use input() to get user input
+ self.use_rawinput = 1
def set_trace(self, frame=None):
self.__debugger_used = True
@@ -817,7 +833,15 @@
# given object's docstring.
try:
file = inspect.getsourcefile(obj) or inspect.getfile(obj)
- source_lines = linecache.getlines(file)
+ if module is not None:
+ # Supply the module globals in case the module was
+ # originally loaded via a PEP 302 loader and
+ # file is not a valid filesystem path
+ source_lines = linecache.getlines(file, module.__dict__)
+ else:
+ # No access to a loader, so assume it's a normal
+ # filesystem path
+ source_lines = linecache.getlines(file)
if not source_lines:
source_lines = None
except TypeError:
@@ -833,6 +857,8 @@
globs = globs.copy()
if extraglobs is not None:
globs.update(extraglobs)
+ if '__name__' not in globs:
+ globs['__name__'] = '__main__' # provide a default module name
# Recursively expore `obj`, extracting DocTests.
tests = []
@@ -851,18 +877,12 @@
"""
if module is None:
return True
+ elif inspect.getmodule(object) is not None:
+ return module is inspect.getmodule(object)
elif inspect.isfunction(object):
return module.__dict__ is object.func_globals
elif inspect.isclass(object):
- # XXX: Jython transition 2.5
- # Java classes appear as Python classes to inspect, but they
- # have no __module__ http://jython.org/bugs/1758279
- # org.python.modules uses Java classes to masq
- if not hasattr(object, '__module__'):
- return False
return module.__name__ == object.__module__
- elif inspect.getmodule(object) is not None:
- return module is inspect.getmodule(object)
elif hasattr(object, '__module__'):
return module.__name__ == object.__module__
elif isinstance(object, property):
@@ -967,8 +987,6 @@
filename = getattr(module, '__file__', module.__name__)
if filename[-4:] in (".pyc", ".pyo"):
filename = filename[:-1]
- elif filename.endswith('$py.class'):
- filename = '%s.py' % filename[:-9]
return self._parser.get_doctest(docstring, globs, name,
filename, lineno)
@@ -1036,10 +1054,10 @@
>>> tests.sort(key = lambda test: test.name)
>>> for test in tests:
... print test.name, '->', runner.run(test)
- _TestClass -> (0, 2)
- _TestClass.__init__ -> (0, 2)
- _TestClass.get -> (0, 2)
- _TestClass.square -> (0, 1)
+ _TestClass -> TestResults(failed=0, attempted=2)
+ _TestClass.__init__ -> TestResults(failed=0, attempted=2)
+ _TestClass.get -> TestResults(failed=0, attempted=2)
+ _TestClass.square -> TestResults(failed=0, attempted=1)
The `summarize` method prints a summary of all the test cases that
have been run by the runner, and returns an aggregated `(f, t)`
@@ -1054,7 +1072,7 @@
7 tests in 4 items.
7 passed and 0 failed.
Test passed.
- (0, 7)
+ TestResults(failed=0, attempted=7)
The aggregated number of tried examples and failed examples is
also available via the `tries` and `failures` attributes:
@@ -1270,9 +1288,9 @@
# Another chance if they didn't care about the detail.
elif self.optionflags & IGNORE_EXCEPTION_DETAIL:
- m1 = re.match(r'[^:]*:', example.exc_msg)
- m2 = re.match(r'[^:]*:', exc_msg)
- if m1 and m2 and check(m1.group(0), m2.group(0),
+ m1 = re.match(r'(?:[^:]*\.)?([^:]*:)', example.exc_msg)
+ m2 = re.match(r'(?:[^:]*\.)?([^:]*:)', exc_msg)
+ if m1 and m2 and check(m1.group(1), m2.group(1),
self.optionflags):
outcome = SUCCESS
@@ -1297,7 +1315,7 @@
# Record and return the number of failures and tries.
self.__record_outcome(test, failures, tries)
- return failures, tries
+ return TestResults(failures, tries)
def __record_outcome(self, test, f, t):
"""
@@ -1310,13 +1328,16 @@
self.tries += t
__LINECACHE_FILENAME_RE = re.compile(r'<doctest '
- r'(?P<name>[\w\.]+)'
+ r'(?P<name>.+)'
r'\[(?P<examplenum>\d+)\]>$')
def __patched_linecache_getlines(self, filename, module_globals=None):
m = self.__LINECACHE_FILENAME_RE.match(filename)
if m and m.group('name') == self.test.name:
example = self.test.examples[int(m.group('examplenum'))]
- return example.source.splitlines(True)
+ source = example.source
+ if isinstance(source, unicode):
+ source = source.encode('ascii', 'backslashreplace')
+ return source.splitlines(True)
else:
return self.save_linecache_getlines(filename, module_globals)
@@ -1365,12 +1386,17 @@
self.save_linecache_getlines = linecache.getlines
linecache.getlines = self.__patched_linecache_getlines
+ # Make sure sys.displayhook just prints the value to stdout
+ save_displayhook = sys.displayhook
+ sys.displayhook = sys.__displayhook__
+
try:
return self.__run(test, compileflags, out)
finally:
sys.stdout = save_stdout
pdb.set_trace = save_set_trace
linecache.getlines = self.save_linecache_getlines
+ sys.displayhook = save_displayhook
if clear_globs:
test.globs.clear()
@@ -1429,7 +1455,7 @@
print "***Test Failed***", totalf, "failures."
elif verbose:
print "Test passed."
- return totalf, totalt
+ return TestResults(totalf, totalt)
#/////////////////////////////////////////////////////////////////
# Backward compatibility cruft to maintain doctest.master.
@@ -1438,8 +1464,10 @@
d = self._name2ft
for name, (f, t) in other._name2ft.items():
if name in d:
- print "*** DocTestRunner.merge: '" + name + "' in both" \
- " testers; summing outcomes."
+ # Don't print here by default, since doing
+ # so breaks some of the buildbots
+ #print "*** DocTestRunner.merge: '" + name + "' in both" \
+ # " testers; summing outcomes."
f2, t2 = d[name]
f = f + f2
t = t + t2
@@ -1675,8 +1703,7 @@
If a failure or error occurs, the globals are left intact:
- >>> if '__builtins__' in test.globs:
- ... del test.globs['__builtins__']
+ >>> del test.globs['__builtins__']
>>> test.globs
{'x': 1}
@@ -1690,8 +1717,7 @@
...
UnexpectedException: <DocTest foo from foo.py:0 (2 examples)>
- >>> if '__builtins__' in test.globs:
- ... del test.globs['__builtins__']
+ >>> del test.globs['__builtins__']
>>> test.globs
{'x': 2}
@@ -1702,7 +1728,7 @@
... ''', {}, 'foo', 'foo.py', 0)
>>> runner.run(test)
- (0, 1)
+ TestResults(failed=0, attempted=1)
>>> test.globs
{}
@@ -1748,7 +1774,7 @@
Return (#failures, #tests).
- See doctest.__doc__ for an overview.
+ See help(doctest) for an overview.
Optional keyword arg "name" gives the name of the module; by default
use m.__name__.
@@ -1832,7 +1858,7 @@
else:
master.merge(runner)
- return runner.failures, runner.tries
+ return TestResults(runner.failures, runner.tries)
def testfile(filename, module_relative=True, name=None, package=None,
globs=None, verbose=None, report=True, optionflags=0,
@@ -1934,6 +1960,8 @@
globs = globs.copy()
if extraglobs is not None:
globs.update(extraglobs)
+ if '__name__' not in globs:
+ globs['__name__'] = '__main__'
if raise_on_error:
runner = DebugRunner(verbose=verbose, optionflags=optionflags)
@@ -1955,7 +1983,7 @@
else:
master.merge(runner)
- return runner.failures, runner.tries
+ return TestResults(runner.failures, runner.tries)
def run_docstring_examples(f, globs, verbose=False, name="NoName",
compileflags=None, optionflags=0):
@@ -2014,7 +2042,7 @@
(f,t) = self.testrunner.run(test)
if self.verbose:
print f, "of", t, "examples failed in string", name
- return (f,t)
+ return TestResults(f,t)
def rundoc(self, object, name=None, module=None):
f = t = 0
@@ -2023,19 +2051,19 @@
for test in tests:
(f2, t2) = self.testrunner.run(test)
(f,t) = (f+f2, t+t2)
- return (f,t)
+ return TestResults(f,t)
def rundict(self, d, name, module=None):
- import new
- m = new.module(name)
+ import types
+ m = types.ModuleType(name)
m.__dict__.update(d)
if module is None:
module = False
return self.rundoc(m, name, module)
def run__test__(self, d, name):
- import new
- m = new.module(name)
+ import types
+ m = types.ModuleType(name)
m.__test__ = d
return self.rundoc(m, name)
@@ -2218,7 +2246,7 @@
self.setUp()
runner = DebugRunner(optionflags=self._dt_optionflags,
checker=self._dt_checker, verbose=False)
- runner.run(self._dt_test)
+ runner.run(self._dt_test, clear_globs=False)
self.tearDown()
def id(self):
@@ -2233,6 +2261,19 @@
def shortDescription(self):
return "Doctest: " + self._dt_test.name
+class SkipDocTestCase(DocTestCase):
+ def __init__(self):
+ DocTestCase.__init__(self, None)
+
+ def setUp(self):
+ self.skipTest("DocTestSuite will not work with -O2 and above")
+
+ def test_skip(self):
+ pass
+
+ def shortDescription(self):
+ return "Skipping tests from %s" % module.__name__
+
def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None,
**options):
"""
@@ -2275,15 +2316,20 @@
module = _normalize_module(module)
tests = test_finder.find(module, globs=globs, extraglobs=extraglobs)
- if globs is None:
- globs = module.__dict__
- if not tests:
+
+ if not tests and sys.flags.optimize >=2:
+ # Skip doctests when running with -O2
+ suite = unittest.TestSuite()
+ suite.addTest(SkipDocTestCase())
+ return suite
+ elif not tests:
# Why do we want to do this? Because it reveals a bug that might
# otherwise be hidden.
raise ValueError(module, "has no tests")
tests.sort()
suite = unittest.TestSuite()
+
for test in tests:
if len(test.examples) == 0:
continue
@@ -2291,8 +2337,6 @@
filename = module.__file__
if filename[-4:] in (".pyc", ".pyo"):
filename = filename[:-1]
- elif filename.endswith('$py.class'):
- filename = '%s.py' % filename[:-9]
test.filename = filename
suite.addTest(DocTestCase(test, **options))
@@ -2657,9 +2701,31 @@
""",
}
+
def _test():
- r = unittest.TextTestRunner()
- r.run(DocTestSuite())
+ testfiles = [arg for arg in sys.argv[1:] if arg and arg[0] != '-']
+ if not testfiles:
+ name = os.path.basename(sys.argv[0])
+ if '__loader__' in globals(): # python -m
+ name, _ = os.path.splitext(name)
+ print("usage: {0} [-v] file ...".format(name))
+ return 2
+ for filename in testfiles:
+ if filename.endswith(".py"):
+ # It is a module -- insert its dir into sys.path and try to
+ # import it. If it is part of a package, that possibly
+ # won't work because of package imports.
+ dirname, filename = os.path.split(filename)
+ sys.path.insert(0, dirname)
+ m = __import__(filename[:-3])
+ del sys.path[0]
+ failures, _ = testmod(m)
+ else:
+ failures, _ = testfile(filename, module_relative=False)
+ if failures:
+ return 1
+ return 0
+
if __name__ == "__main__":
- _test()
+ sys.exit(_test())
--
Repository URL: http://hg.python.org/jython
More information about the Jython-checkins
mailing list