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.
This commit is contained in:
Carl Meyer 2013-05-27 14:41:39 -06:00
parent 0027f13904
commit cd79f33723
4 changed files with 78 additions and 95 deletions

View File

@ -3,14 +3,15 @@ This module is pending deprecation as of Django 1.6 and will be removed in
version 1.8. version 1.8.
""" """
import json
import re
import unittest as real_unittest import unittest as real_unittest
import warnings import warnings
from django.db.models import get_app, get_apps from django.db.models import get_app, get_apps
from django.test import _doctest as doctest from django.test import _doctest as doctest
from django.test import runner 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 import unittest
from django.utils.importlib import import_module from django.utils.importlib import import_module
from django.utils.module_loading import module_has_submodule from django.utils.module_loading import module_has_submodule
@ -25,6 +26,71 @@ warnings.warn(
# The module name for tests outside models.py # The module name for tests outside models.py
TEST_MODULE = 'tests' 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() doctestOutputChecker = OutputChecker()

View File

@ -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.db import connection, connections, DEFAULT_DB_ALIAS, transaction
from django.forms.fields import CharField from django.forms.fields import CharField
from django.http import QueryDict from django.http import QueryDict
from django.test import _doctest as doctest
from django.test.client import Client from django.test.client import Client
from django.test.html import HTMLParseError, parse_html from django.test.html import HTMLParseError, parse_html
from django.test.signals import template_rendered from django.test.signals import template_rendered
from django.test.utils import (CaptureQueriesContext, ContextList, 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 import six, unittest as ut2
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.unittest import skipIf # Imported here for backward compatibility 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 from django.views.static import serve
__all__ = ('DocTestRunner', 'OutputChecker', 'TestCase', 'TransactionTestCase', __all__ = ('TestCase', 'TransactionTestCase',
'SimpleTestCase', 'skipIfDBFeature', 'skipUnlessDBFeature') '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): def to_list(value):
""" """
Puts value into a list if it's not already one. 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 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): class _AssertNumQueriesContext(CaptureQueriesContext):
def __init__(self, test_case, num, connection): def __init__(self, test_case, num, connection):
self.test_case = test_case self.test_case = test_case

View File

@ -386,10 +386,8 @@ these changes.
``django.test.simple.DjangoTestSuiteRunner`` will be removed. Instead use ``django.test.simple.DjangoTestSuiteRunner`` will be removed. Instead use
``django.test.runner.DiscoverRunner``. ``django.test.runner.DiscoverRunner``.
* The module ``django.test._doctest`` and the classes * The module ``django.test._doctest`` will be removed. Instead use the doctest
``django.test.testcases.DocTestRunner`` and module from the Python standard library.
``django.test.testcases.OutputChecker`` will be removed. Instead use the
doctest module from the Python standard library.
* The ``CACHE_MIDDLEWARE_ANONYMOUS_ONLY`` setting will be removed. * The ``CACHE_MIDDLEWARE_ANONYMOUS_ONLY`` setting will be removed.

View File

@ -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`_. your test suite, follow the `recommendations in the Python documentation`_.
Django bundles a modified version of the :mod:`doctest` module from the Python 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 standard library (in ``django.test._doctest``) and includes some additional
custom ``DocTestRunner`` when instantiating a ``DocTestSuite``, and includes doctest utilities. These utilities are deprecated and will be removed in Django
some additional doctest utilities (``django.test.testcases.DocTestRunner`` 1.8; doctest suites should be updated to work with the standard library's
turns on the ``ELLIPSIS`` option by default, and doctest module (or converted to unittest-compatible tests).
``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).
If you wish to delay updates to your test suite, you can set your If you wish to delay updates to your test suite, you can set your
:setting:`TEST_RUNNER` setting to ``django.test.simple.DjangoTestSuiteRunner`` :setting:`TEST_RUNNER` setting to ``django.test.simple.DjangoTestSuiteRunner``
to fully restore the old test behavior. ``DjangoTestSuiteRunner`` is to fully restore the old test behavior. ``DjangoTestSuiteRunner`` is deprecated
deprecated but will not be removed from Django until version 1.8. 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 .. _recommendations in the Python documentation: http://docs.python.org/2/library/doctest.html#unittest-api