Commit cd79f337 authored by Carl Meyer's avatar Carl Meyer
Browse files

Fixed #20503 - Moved doctest utilities in with the rest of the deprecated test code.

The ``DocTestRunner`` and ``OutputChecker`` were formerly in
``django.test.testcases``, now they are in ``django.test.simple``. This avoids
triggering the ``django.test._doctest`` deprecation message with any import
from ``django.test``. Since these utility classes are undocumented internal
API, they can be moved without a separate deprecation process.

Also removed the deprecation warnings specific to these classes, as they are
now covered by the module-level warning in ``django.test.simple``.

Thanks Anssi for the report.

Refs #17365.
parent 0027f139
Loading
Loading
Loading
Loading
+68 −2
Original line number Diff line number Diff line
@@ -3,14 +3,15 @@ This module is pending deprecation as of Django 1.6 and will be removed in
version 1.8.

"""

import json
import re
import unittest as real_unittest
import warnings

from django.db.models import get_app, get_apps
from django.test import _doctest as doctest
from django.test import runner
from django.test.testcases import OutputChecker, DocTestRunner
from django.test.utils import compare_xml, strip_quotes
from django.utils import unittest
from django.utils.importlib import import_module
from django.utils.module_loading import module_has_submodule
@@ -25,6 +26,71 @@ warnings.warn(
# The module name for tests outside models.py
TEST_MODULE = 'tests'


normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
normalize_decimals = lambda s: re.sub(r"Decimal\('(\d+(\.\d*)?)'\)",
                                lambda m: "Decimal(\"%s\")" % m.groups()[0], s)


class OutputChecker(doctest.OutputChecker):
    def check_output(self, want, got, optionflags):
        """
        The entry method for doctest output checking. Defers to a sequence of
        child checkers
        """
        checks = (self.check_output_default,
                  self.check_output_numeric,
                  self.check_output_xml,
                  self.check_output_json)
        for check in checks:
            if check(want, got, optionflags):
                return True
        return False

    def check_output_default(self, want, got, optionflags):
        """
        The default comparator provided by doctest - not perfect, but good for
        most purposes
        """
        return doctest.OutputChecker.check_output(self, want, got, optionflags)

    def check_output_numeric(self, want, got, optionflags):
        """Doctest does an exact string comparison of output, which means that
        some numerically equivalent values aren't equal. This check normalizes
         * long integers (22L) so that they equal normal integers. (22)
         * Decimals so that they are comparable, regardless of the change
           made to __repr__ in Python 2.6.
        """
        return doctest.OutputChecker.check_output(self,
            normalize_decimals(normalize_long_ints(want)),
            normalize_decimals(normalize_long_ints(got)),
            optionflags)

    def check_output_xml(self, want, got, optionsflags):
        try:
            return compare_xml(want, got)
        except Exception:
            return False

    def check_output_json(self, want, got, optionsflags):
        """
        Tries to compare want and got as if they were JSON-encoded data
        """
        want, got = strip_quotes(want, got)
        try:
            want_json = json.loads(want)
            got_json = json.loads(got)
        except Exception:
            return False
        return want_json == got_json


class DocTestRunner(doctest.DocTestRunner):
    def __init__(self, *args, **kwargs):
        doctest.DocTestRunner.__init__(self, *args, **kwargs)
        self.optionflags = doctest.ELLIPSIS


doctestOutputChecker = OutputChecker()


+2 −77
Original line number Diff line number Diff line
@@ -30,12 +30,11 @@ from django.core.urlresolvers import clear_url_caches, set_urlconf
from django.db import connection, connections, DEFAULT_DB_ALIAS, transaction
from django.forms.fields import CharField
from django.http import QueryDict
from django.test import _doctest as doctest
from django.test.client import Client
from django.test.html import HTMLParseError, parse_html
from django.test.signals import template_rendered
from django.test.utils import (CaptureQueriesContext, ContextList,
    override_settings, compare_xml, strip_quotes)
    override_settings, compare_xml)
from django.utils import six, unittest as ut2
from django.utils.encoding import force_text
from django.utils.unittest import skipIf # Imported here for backward compatibility
@@ -43,15 +42,10 @@ from django.utils.unittest.util import safe_repr
from django.views.static import serve


__all__ = ('DocTestRunner', 'OutputChecker', 'TestCase', 'TransactionTestCase',
__all__ = ('TestCase', 'TransactionTestCase',
           'SimpleTestCase', 'skipIfDBFeature', 'skipUnlessDBFeature')


normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
normalize_decimals = lambda s: re.sub(r"Decimal\('(\d+(\.\d*)?)'\)",
                                lambda m: "Decimal(\"%s\")" % m.groups()[0], s)


def to_list(value):
    """
    Puts value into a list if it's not already one.
@@ -96,75 +90,6 @@ def assert_and_parse_html(self, html, user_msg, msg):
    return dom


class OutputChecker(doctest.OutputChecker):
    def __init__(self):
        warnings.warn(
            "The django.test.testcases.OutputChecker class is deprecated; "
            "use the doctest module from the Python standard library instead.",
            PendingDeprecationWarning)

    def check_output(self, want, got, optionflags):
        """
        The entry method for doctest output checking. Defers to a sequence of
        child checkers
        """
        checks = (self.check_output_default,
                  self.check_output_numeric,
                  self.check_output_xml,
                  self.check_output_json)
        for check in checks:
            if check(want, got, optionflags):
                return True
        return False

    def check_output_default(self, want, got, optionflags):
        """
        The default comparator provided by doctest - not perfect, but good for
        most purposes
        """
        return doctest.OutputChecker.check_output(self, want, got, optionflags)

    def check_output_numeric(self, want, got, optionflags):
        """Doctest does an exact string comparison of output, which means that
        some numerically equivalent values aren't equal. This check normalizes
         * long integers (22L) so that they equal normal integers. (22)
         * Decimals so that they are comparable, regardless of the change
           made to __repr__ in Python 2.6.
        """
        return doctest.OutputChecker.check_output(self,
            normalize_decimals(normalize_long_ints(want)),
            normalize_decimals(normalize_long_ints(got)),
            optionflags)

    def check_output_xml(self, want, got, optionsflags):
        try:
            return compare_xml(want, got)
        except Exception:
            return False

    def check_output_json(self, want, got, optionsflags):
        """
        Tries to compare want and got as if they were JSON-encoded data
        """
        want, got = strip_quotes(want, got)
        try:
            want_json = json.loads(want)
            got_json = json.loads(got)
        except Exception:
            return False
        return want_json == got_json


class DocTestRunner(doctest.DocTestRunner):
    def __init__(self, *args, **kwargs):
        warnings.warn(
            "The django.test.testcases.DocTestRunner class is deprecated; "
            "use the doctest module from the Python standard library instead.",
            PendingDeprecationWarning)
        doctest.DocTestRunner.__init__(self, *args, **kwargs)
        self.optionflags = doctest.ELLIPSIS


class _AssertNumQueriesContext(CaptureQueriesContext):
    def __init__(self, test_case, num, connection):
        self.test_case = test_case
+2 −4
Original line number Diff line number Diff line
@@ -386,10 +386,8 @@ these changes.
  ``django.test.simple.DjangoTestSuiteRunner`` will be removed. Instead use
  ``django.test.runner.DiscoverRunner``.

* The module ``django.test._doctest`` and the classes
  ``django.test.testcases.DocTestRunner`` and
  ``django.test.testcases.OutputChecker`` will be removed. Instead use the
  doctest module from the Python standard library.
* The module ``django.test._doctest`` will be removed. Instead use the doctest
  module from the Python standard library.

* The ``CACHE_MIDDLEWARE_ANONYMOUS_ONLY`` setting will be removed.

+6 −12
Original line number Diff line number Diff line
@@ -345,21 +345,15 @@ support some types of tests that were supported by the previous runner:
  your test suite, follow the `recommendations in the Python documentation`_.

Django bundles a modified version of the :mod:`doctest` module from the Python
standard library (in ``django.test._doctest``) in order to allow passing in a
custom ``DocTestRunner`` when instantiating a ``DocTestSuite``, and includes
some additional doctest utilities (``django.test.testcases.DocTestRunner``
turns on the ``ELLIPSIS`` option by default, and
``django.test.testcases.OutputChecker`` provides better matching of XML, JSON,
and numeric data types).

These utilities are deprecated and will be removed in Django 1.8; doctest
suites should be updated to work with the standard library's doctest module (or
converted to unittest-compatible tests).
standard library (in ``django.test._doctest``) and includes some additional
doctest utilities. These utilities are deprecated and will be removed in Django
1.8; doctest suites should be updated to work with the standard library's
doctest module (or converted to unittest-compatible tests).

If you wish to delay updates to your test suite, you can set your
:setting:`TEST_RUNNER` setting to ``django.test.simple.DjangoTestSuiteRunner``
to fully restore the old test behavior. ``DjangoTestSuiteRunner`` is
deprecated but will not be removed from Django until version 1.8.
to fully restore the old test behavior. ``DjangoTestSuiteRunner`` is deprecated
but will not be removed from Django until version 1.8.

.. _recommendations in the Python documentation: http://docs.python.org/2/library/doctest.html#unittest-api