From 67646dc28d6fc7b4032f1f5a18a5347e81755638 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 3 Aug 2012 23:00:22 +0200 Subject: [PATCH] [py3] Ported django.test.doctest. Based on Vinay Sajip's branch. --- django/test/_doctest.py | 119 ++++++++++++++++++++++++++++++++++------ 1 file changed, 103 insertions(+), 16 deletions(-) diff --git a/django/test/_doctest.py b/django/test/_doctest.py index 316c785f33..82d4a6d08e 100644 --- a/django/test/_doctest.py +++ b/django/test/_doctest.py @@ -105,7 +105,7 @@ import unittest, difflib, pdb, tempfile import warnings from django.utils import six -from django.utils.six import StringIO +from django.utils.six.moves import StringIO, xrange if sys.platform.startswith('java'): # On Jython, isclass() reports some modules as classes. Patch it. @@ -501,11 +501,31 @@ class DocTest: # This lets us sort tests by name: + def _cmpkey(self): + return (self.name, self.filename, self.lineno, id(self)) def __cmp__(self, other): if not isinstance(other, DocTest): return -1 - return cmp((self.name, self.filename, self.lineno, id(self)), - (other.name, other.filename, other.lineno, id(other))) + return cmp(self._cmpkey(), other._cmpkey()) + + 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 @@ -1229,6 +1249,57 @@ class DocTestRunner: # __patched_linecache_getlines). filename = '' % (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 # any exception that gets raised. (But don't intercept # keyboard interrupts.) @@ -1243,9 +1314,14 @@ class DocTestRunner: except: exception = sys.exc_info() self.debugger.set_continue() # ==== Example Finished ==== + finally: + # restore the original displayhook + sys.displayhook = original_displayhook got = self._fakeout.getvalue() # the actual output self._fakeout.truncate(0) + # Python 3.1 requires seek after truncate + self._fakeout.seek(0) outcome = FAILURE # guilty until proved innocent or insane # If the example executed without raising any exceptions, @@ -1256,10 +1332,21 @@ class DocTestRunner: # The example raised an exception: check if it was expected. else: - exc_info = sys.exc_info() - exc_msg = traceback.format_exception_only(*exc_info[:2])[-1] + exc_msg = traceback.format_exception_only(*exception[: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: - got += _exception_traceback(exc_info) + got += _exception_traceback(exception) # If `example.exc_msg` is None, then we weren't expecting # an exception. @@ -1289,7 +1376,7 @@ class DocTestRunner: elif outcome is BOOM: if not quiet: self.report_unexpected_exception(out, test, example, - exc_info) + exception) failures += 1 else: assert False, ("unknown outcome", outcome) @@ -1629,8 +1716,8 @@ class DebugRunner(DocTestRunner): ... {}, 'foo', 'foo.py', 0) >>> try: ... runner.run(test) - ... except UnexpectedException as failure: - ... pass + ... except UnexpectedException as e: + ... failure = e >>> failure.test is test True @@ -1657,8 +1744,8 @@ class DebugRunner(DocTestRunner): >>> try: ... runner.run(test) - ... except DocTestFailure as failure: - ... pass + ... except DocTestFailure as e: + ... failure = e DocTestFailure objects provide access to the test: @@ -2167,8 +2254,8 @@ class DocTestCase(unittest.TestCase): >>> case = DocTestCase(test) >>> try: ... case.debug() - ... except UnexpectedException as failure: - ... pass + ... except UnexpectedException as e: + ... failure = e The UnexpectedException contains the test, the example, and the original exception: @@ -2196,8 +2283,8 @@ class DocTestCase(unittest.TestCase): >>> try: ... case.debug() - ... except DocTestFailure as failure: - ... pass + ... except DocTestFailure as e: + ... failure = e DocTestFailure objects provide access to the test: @@ -2646,7 +2733,7 @@ __test__ = {"_TestClass": _TestClass, "whitespace normalization": r""" If the whitespace normalization flag is used, then 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, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]