[py3] Ported django.test.doctest.

Based on Vinay Sajip's branch.
This commit is contained in:
Aymeric Augustin 2012-08-03 23:00:22 +02:00
parent a8b3ddec5f
commit 67646dc28d
1 changed files with 103 additions and 16 deletions

View File

@ -105,7 +105,7 @@ import unittest, difflib, pdb, tempfile
import warnings import warnings
from django.utils import six from django.utils import six
from django.utils.six import StringIO from django.utils.six.moves import StringIO, xrange
if sys.platform.startswith('java'): if sys.platform.startswith('java'):
# On Jython, isclass() reports some modules as classes. Patch it. # On Jython, isclass() reports some modules as classes. Patch it.
@ -501,11 +501,31 @@ class DocTest:
# This lets us sort tests by name: # This lets us sort tests by name:
def _cmpkey(self):
return (self.name, self.filename, self.lineno, id(self))
def __cmp__(self, other): def __cmp__(self, other):
if not isinstance(other, DocTest): if not isinstance(other, DocTest):
return -1 return -1
return cmp((self.name, self.filename, self.lineno, id(self)), return cmp(self._cmpkey(), other._cmpkey())
(other.name, other.filename, other.lineno, id(other)))
def __lt__(self, other):
return self._cmpkey() < other._cmpkey()
def __le__(self, other):
return self._cmpkey() <= other._cmpkey()
def __gt__(self, other):
return self._cmpkey() > other._cmpkey()
def __ge__(self, other):
return self._cmpkey() >= other._cmpkey()
def __eq__(self, other):
return self._cmpkey() == other._cmpkey()
def __ne__(self, other):
return self._cmpkey() != other._cmpkey()
###################################################################### ######################################################################
## 3. DocTestParser ## 3. DocTestParser
@ -1229,6 +1249,57 @@ class DocTestRunner:
# __patched_linecache_getlines). # __patched_linecache_getlines).
filename = '<doctest %s[%d]>' % (test.name, examplenum) filename = '<doctest %s[%d]>' % (test.name, examplenum)
# Doctest and Py3 issue:
# If the current example that we wish to run is going to fail
# because it expects a leading u"", then use an alternate displayhook
original_displayhook = sys.displayhook
if six.PY3:
# only set alternate displayhook if Python 3.x or after
lines = []
def py3_displayhook(value):
if value is None:
# None should not be considered at all
return original_displayhook(value)
# Collect the repr output in one variable
s = repr(value)
# Strip b"" and u"" prefixes from the repr and expected output
# TODO: better way of stripping the prefixes?
expected = example.want
expected = expected.strip() # be wary of newlines
s = s.replace("u", "")
s = s.replace("b", "")
expected = expected.replace("u", "")
expected = expected.replace("b", "")
# single quote vs. double quote should not matter
# default all quote marks to double quote
s = s.replace("'", '"')
expected = expected.replace("'", '"')
# In case of multi-line expected result
lines.append(s)
# let them match
if s == expected: # be wary of false positives here
# they should be the same, print expected value
sys.stdout.write("%s\n" % example.want.strip())
# multi-line expected output, doctest uses loop
elif len(expected.split("\n")) == len(lines):
if "\n".join(lines) == expected:
sys.stdout.write("%s\n" % example.want.strip())
else:
sys.stdout.write("%s\n" % repr(value))
elif len(expected.split("\n")) != len(lines):
# we are not done looping yet, do not print anything!
pass
else:
sys.stdout.write("%s\n" % repr(value))
sys.displayhook = py3_displayhook
# Run the example in the given context (globs), and record # Run the example in the given context (globs), and record
# any exception that gets raised. (But don't intercept # any exception that gets raised. (But don't intercept
# keyboard interrupts.) # keyboard interrupts.)
@ -1243,9 +1314,14 @@ class DocTestRunner:
except: except:
exception = sys.exc_info() exception = sys.exc_info()
self.debugger.set_continue() # ==== Example Finished ==== self.debugger.set_continue() # ==== Example Finished ====
finally:
# restore the original displayhook
sys.displayhook = original_displayhook
got = self._fakeout.getvalue() # the actual output got = self._fakeout.getvalue() # the actual output
self._fakeout.truncate(0) self._fakeout.truncate(0)
# Python 3.1 requires seek after truncate
self._fakeout.seek(0)
outcome = FAILURE # guilty until proved innocent or insane outcome = FAILURE # guilty until proved innocent or insane
# If the example executed without raising any exceptions, # If the example executed without raising any exceptions,
@ -1256,10 +1332,21 @@ class DocTestRunner:
# The example raised an exception: check if it was expected. # The example raised an exception: check if it was expected.
else: else:
exc_info = sys.exc_info() exc_msg = traceback.format_exception_only(*exception[:2])[-1]
exc_msg = traceback.format_exception_only(*exc_info[:2])[-1] if six.PY3:
# module name will be in group(1) and the expected
# exception message will be in group(2)
m = re.match(r'(.*)\.(\w+:.+\s)', exc_msg)
# make sure there's a match
if m != None:
f_name = m.group(1)
# check to see if m.group(1) contains the module name
if f_name == exception[0].__module__:
# strip the module name from exc_msg
exc_msg = m.group(2)
if not quiet: if not quiet:
got += _exception_traceback(exc_info) got += _exception_traceback(exception)
# If `example.exc_msg` is None, then we weren't expecting # If `example.exc_msg` is None, then we weren't expecting
# an exception. # an exception.
@ -1289,7 +1376,7 @@ class DocTestRunner:
elif outcome is BOOM: elif outcome is BOOM:
if not quiet: if not quiet:
self.report_unexpected_exception(out, test, example, self.report_unexpected_exception(out, test, example,
exc_info) exception)
failures += 1 failures += 1
else: else:
assert False, ("unknown outcome", outcome) assert False, ("unknown outcome", outcome)
@ -1629,8 +1716,8 @@ class DebugRunner(DocTestRunner):
... {}, 'foo', 'foo.py', 0) ... {}, 'foo', 'foo.py', 0)
>>> try: >>> try:
... runner.run(test) ... runner.run(test)
... except UnexpectedException as failure: ... except UnexpectedException as e:
... pass ... failure = e
>>> failure.test is test >>> failure.test is test
True True
@ -1657,8 +1744,8 @@ class DebugRunner(DocTestRunner):
>>> try: >>> try:
... runner.run(test) ... runner.run(test)
... except DocTestFailure as failure: ... except DocTestFailure as e:
... pass ... failure = e
DocTestFailure objects provide access to the test: DocTestFailure objects provide access to the test:
@ -2167,8 +2254,8 @@ class DocTestCase(unittest.TestCase):
>>> case = DocTestCase(test) >>> case = DocTestCase(test)
>>> try: >>> try:
... case.debug() ... case.debug()
... except UnexpectedException as failure: ... except UnexpectedException as e:
... pass ... failure = e
The UnexpectedException contains the test, the example, and The UnexpectedException contains the test, the example, and
the original exception: the original exception:
@ -2196,8 +2283,8 @@ class DocTestCase(unittest.TestCase):
>>> try: >>> try:
... case.debug() ... case.debug()
... except DocTestFailure as failure: ... except DocTestFailure as e:
... pass ... failure = e
DocTestFailure objects provide access to the test: DocTestFailure objects provide access to the test:
@ -2646,7 +2733,7 @@ __test__ = {"_TestClass": _TestClass,
"whitespace normalization": r""" "whitespace normalization": r"""
If the whitespace normalization flag is used, then If the whitespace normalization flag is used, then
differences in whitespace are ignored. differences in whitespace are ignored.
>>> print(range(30)) #doctest: +NORMALIZE_WHITESPACE >>> print(list(xrange(30))) #doctest: +NORMALIZE_WHITESPACE
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29] 27, 28, 29]