Commit 67646dc2 authored by Aymeric Augustin's avatar Aymeric Augustin
Browse files

[py3] Ported django.test.doctest.

Based on Vinay Sajip's branch.
parent a8b3ddec
Loading
Loading
Loading
Loading
+103 −16
Original line number Diff line number Diff line
@@ -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 = '<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
            # 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]