simple.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. """
  2. This module is pending deprecation as of Django 1.6 and will be removed in
  3. version 1.8.
  4. """
  5. from importlib import import_module
  6. import json
  7. import re
  8. import unittest as real_unittest
  9. import warnings
  10. from django.apps import apps
  11. from django.test import _doctest as doctest
  12. from django.test import runner
  13. from django.test.utils import compare_xml, strip_quotes
  14. # django.utils.unittest is deprecated, but so is django.test.simple,
  15. # and the latter will be removed before the former.
  16. from django.utils import unittest
  17. from django.utils.deprecation import RemovedInDjango18Warning
  18. from django.utils.module_loading import module_has_submodule
  19. __all__ = ('DjangoTestSuiteRunner',)
  20. warnings.warn(
  21. "The django.test.simple module and DjangoTestSuiteRunner are deprecated; "
  22. "use django.test.runner.DiscoverRunner instead.",
  23. RemovedInDjango18Warning)
  24. # The module name for tests outside models.py
  25. TEST_MODULE = 'tests'
  26. normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
  27. normalize_decimals = lambda s: re.sub(r"Decimal\('(\d+(\.\d*)?)'\)",
  28. lambda m: "Decimal(\"%s\")" % m.groups()[0], s)
  29. class OutputChecker(doctest.OutputChecker):
  30. def check_output(self, want, got, optionflags):
  31. """
  32. The entry method for doctest output checking. Defers to a sequence of
  33. child checkers
  34. """
  35. checks = (self.check_output_default,
  36. self.check_output_numeric,
  37. self.check_output_xml,
  38. self.check_output_json)
  39. for check in checks:
  40. if check(want, got, optionflags):
  41. return True
  42. return False
  43. def check_output_default(self, want, got, optionflags):
  44. """
  45. The default comparator provided by doctest - not perfect, but good for
  46. most purposes
  47. """
  48. return doctest.OutputChecker.check_output(self, want, got, optionflags)
  49. def check_output_numeric(self, want, got, optionflags):
  50. """Doctest does an exact string comparison of output, which means that
  51. some numerically equivalent values aren't equal. This check normalizes
  52. * long integers (22L) so that they equal normal integers. (22)
  53. * Decimals so that they are comparable, regardless of the change
  54. made to __repr__ in Python 2.6.
  55. """
  56. return doctest.OutputChecker.check_output(self,
  57. normalize_decimals(normalize_long_ints(want)),
  58. normalize_decimals(normalize_long_ints(got)),
  59. optionflags)
  60. def check_output_xml(self, want, got, optionsflags):
  61. try:
  62. return compare_xml(want, got)
  63. except Exception:
  64. return False
  65. def check_output_json(self, want, got, optionsflags):
  66. """
  67. Tries to compare want and got as if they were JSON-encoded data
  68. """
  69. want, got = strip_quotes(want, got)
  70. try:
  71. want_json = json.loads(want)
  72. got_json = json.loads(got)
  73. except Exception:
  74. return False
  75. return want_json == got_json
  76. class DocTestRunner(doctest.DocTestRunner):
  77. def __init__(self, *args, **kwargs):
  78. doctest.DocTestRunner.__init__(self, *args, **kwargs)
  79. self.optionflags = doctest.ELLIPSIS
  80. doctestOutputChecker = OutputChecker()
  81. def get_tests(app_config):
  82. try:
  83. test_module = import_module('%s.%s' % (app_config.name, TEST_MODULE))
  84. except ImportError:
  85. # Couldn't import tests.py. Was it due to a missing file, or
  86. # due to an import error in a tests.py that actually exists?
  87. if not module_has_submodule(app_config.module, TEST_MODULE):
  88. test_module = None
  89. else:
  90. # The module exists, so there must be an import error in the test
  91. # module itself.
  92. raise
  93. return test_module
  94. def make_doctest(module):
  95. return doctest.DocTestSuite(module,
  96. checker=doctestOutputChecker,
  97. runner=DocTestRunner)
  98. def build_suite(app_config):
  99. """
  100. Create a complete Django test suite for the provided application module.
  101. """
  102. suite = unittest.TestSuite()
  103. # Load unit and doctests in the models.py module. If module has
  104. # a suite() method, use it. Otherwise build the test suite ourselves.
  105. models_module = app_config.models_module
  106. if models_module:
  107. if hasattr(models_module, 'suite'):
  108. suite.addTest(models_module.suite())
  109. else:
  110. suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(
  111. models_module))
  112. try:
  113. suite.addTest(make_doctest(models_module))
  114. except ValueError:
  115. # No doc tests in models.py
  116. pass
  117. # Check to see if a separate 'tests' module exists parallel to the
  118. # models module
  119. tests_module = get_tests(app_config)
  120. if tests_module:
  121. # Load unit and doctests in the tests.py module. If module has
  122. # a suite() method, use it. Otherwise build the test suite ourselves.
  123. if hasattr(tests_module, 'suite'):
  124. suite.addTest(tests_module.suite())
  125. else:
  126. suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(
  127. tests_module))
  128. try:
  129. suite.addTest(make_doctest(tests_module))
  130. except ValueError:
  131. # No doc tests in tests.py
  132. pass
  133. return suite
  134. def build_test(label):
  135. """
  136. Construct a test case with the specified label. Label should be of the
  137. form app_label.TestClass or app_label.TestClass.test_method. Returns an
  138. instantiated test or test suite corresponding to the label provided.
  139. """
  140. parts = label.split('.')
  141. if len(parts) < 2 or len(parts) > 3:
  142. raise ValueError("Test label '%s' should be of the form app.TestCase "
  143. "or app.TestCase.test_method" % label)
  144. app_config = apps.get_app_config(parts[0])
  145. models_module = app_config.models_module
  146. tests_module = get_tests(app_config)
  147. test_modules = []
  148. if models_module:
  149. test_modules.append(models_module)
  150. if tests_module:
  151. test_modules.append(tests_module)
  152. TestClass = None
  153. for module in test_modules:
  154. TestClass = getattr(module, parts[1], None)
  155. if TestClass is not None:
  156. break
  157. try:
  158. if issubclass(TestClass, (unittest.TestCase, real_unittest.TestCase)):
  159. if len(parts) == 2: # label is app.TestClass
  160. try:
  161. return unittest.TestLoader().loadTestsFromTestCase(
  162. TestClass)
  163. except TypeError:
  164. raise ValueError(
  165. "Test label '%s' does not refer to a test class"
  166. % label)
  167. else: # label is app.TestClass.test_method
  168. return TestClass(parts[2])
  169. except TypeError:
  170. # TestClass isn't a TestClass - it must be a method or normal class
  171. pass
  172. #
  173. # If there isn't a TestCase, look for a doctest that matches
  174. #
  175. tests = []
  176. for module in test_modules:
  177. try:
  178. doctests = make_doctest(module)
  179. # Now iterate over the suite, looking for doctests whose name
  180. # matches the pattern that was given
  181. for test in doctests:
  182. if test._dt_test.name in (
  183. '%s.%s' % (module.__name__, '.'.join(parts[1:])),
  184. '%s.__test__.%s' % (
  185. module.__name__, '.'.join(parts[1:]))):
  186. tests.append(test)
  187. except ValueError:
  188. # No doctests found.
  189. pass
  190. # If no tests were found, then we were given a bad test label.
  191. if not tests:
  192. raise ValueError("Test label '%s' does not refer to a test" % label)
  193. # Construct a suite out of the tests that matched.
  194. return unittest.TestSuite(tests)
  195. class DjangoTestSuiteRunner(runner.DiscoverRunner):
  196. def build_suite(self, test_labels, extra_tests=None, **kwargs):
  197. suite = unittest.TestSuite()
  198. if test_labels:
  199. for label in test_labels:
  200. if '.' in label:
  201. suite.addTest(build_test(label))
  202. else:
  203. app_config = apps.get_app_config(label)
  204. suite.addTest(build_suite(app_config))
  205. else:
  206. for app_config in apps.get_app_configs():
  207. suite.addTest(build_suite(app_config))
  208. if extra_tests:
  209. for test in extra_tests:
  210. suite.addTest(test)
  211. return runner.reorder_suite(suite, (unittest.TestCase,))