From 7f264e02f4480c49d1440be882416a10951c2165 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Mon, 1 Jul 2013 13:53:06 +0200 Subject: [PATCH] Fixed #20680 -- Deprecated django.utils.unittest. Refs #19204. --- django/utils/unittest/__init__.py | 79 +- django/utils/unittest/__main__.py | 10 - django/utils/unittest/case.py | 1076 ----------------- django/utils/unittest/collector.py | 9 - django/utils/unittest/compatibility.py | 64 - django/utils/unittest/loader.py | 322 ----- django/utils/unittest/main.py | 241 ---- django/utils/unittest/result.py | 183 --- django/utils/unittest/runner.py | 206 ---- django/utils/unittest/signals.py | 57 - django/utils/unittest/suite.py | 287 ----- django/utils/unittest/util.py | 99 -- docs/internals/deprecation.txt | 5 + docs/releases/1.7.txt | 41 + .../django_unittest_classes_hierarchy.graffle | 250 +--- .../django_unittest_classes_hierarchy.pdf | Bin 51979 -> 37750 bytes .../django_unittest_classes_hierarchy.svg | 2 +- docs/topics/testing/overview.txt | 33 +- 18 files changed, 81 insertions(+), 2883 deletions(-) delete mode 100644 django/utils/unittest/__main__.py delete mode 100644 django/utils/unittest/case.py delete mode 100644 django/utils/unittest/collector.py delete mode 100644 django/utils/unittest/compatibility.py delete mode 100644 django/utils/unittest/loader.py delete mode 100644 django/utils/unittest/main.py delete mode 100644 django/utils/unittest/result.py delete mode 100644 django/utils/unittest/runner.py delete mode 100644 django/utils/unittest/signals.py delete mode 100644 django/utils/unittest/suite.py delete mode 100644 django/utils/unittest/util.py create mode 100644 docs/releases/1.7.txt diff --git a/django/utils/unittest/__init__.py b/django/utils/unittest/__init__.py index ac852a375793d..37634e0b37bbf 100644 --- a/django/utils/unittest/__init__.py +++ b/django/utils/unittest/__init__.py @@ -1,80 +1,9 @@ -""" -unittest2 +import warnings -unittest2 is a backport of the new features added to the unittest testing -framework in Python 2.7. It is tested to run on Python 2.4 - 2.6. - -To use unittest2 instead of unittest simply replace ``import unittest`` with -``import unittest2``. - - -Copyright (c) 1999-2003 Steve Purcell -Copyright (c) 2003-2010 Python Software Foundation -This module is free software, and you may redistribute it and/or modify -it under the same terms as Python itself, so long as this copyright message -and disclaimer are retained in their original form. - -IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, -SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF -THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. - -THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, -AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, -SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. -""" - -import sys - -# Django hackery to load the appropriate version of unittest +warnings.warn("django.utils.unittest will be removed in Django 1.9.", + PendingDeprecationWarning) try: - # check the system path first from unittest2 import * except ImportError: - if sys.version_info >= (2,7): - # unittest2 features are native in Python 2.7 - from unittest import * - else: - # otherwise use our bundled version - __all__ = ['TestResult', 'TestCase', 'TestSuite', - 'TextTestRunner', 'TestLoader', 'FunctionTestCase', 'main', - 'defaultTestLoader', 'SkipTest', 'skip', 'skipIf', 'skipUnless', - 'expectedFailure', 'TextTestResult', '__version__', 'collector'] - - __version__ = '0.5.1' - - # Expose obsolete functions for backwards compatibility - __all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases']) - - - from django.utils.unittest.collector import collector - from django.utils.unittest.result import TestResult - from django.utils.unittest.case import \ - TestCase, FunctionTestCase, SkipTest, skip, skipIf,\ - skipUnless, expectedFailure - - from django.utils.unittest.suite import BaseTestSuite, TestSuite - from django.utils.unittest.loader import \ - TestLoader, defaultTestLoader, makeSuite, getTestCaseNames,\ - findTestCases - - from django.utils.unittest.main import TestProgram, main, main_ - from django.utils.unittest.runner import TextTestRunner, TextTestResult - - try: - from django.utils.unittest.signals import\ - installHandler, registerResult, removeResult, removeHandler - except ImportError: - # Compatibility with platforms that don't have the signal module - pass - else: - __all__.extend(['installHandler', 'registerResult', 'removeResult', - 'removeHandler']) - - # deprecated - _TextTestResult = TextTestResult - - __unittest = True + from unittest import * diff --git a/django/utils/unittest/__main__.py b/django/utils/unittest/__main__.py deleted file mode 100644 index 68b893d139d5c..0000000000000 --- a/django/utils/unittest/__main__.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Main entry point""" - -import sys -if sys.argv[0].endswith("__main__.py"): - sys.argv[0] = "unittest2" - -__unittest = True - -from django.utils.unittest.main import main_ -main_() diff --git a/django/utils/unittest/case.py b/django/utils/unittest/case.py deleted file mode 100644 index fffd3c25245ba..0000000000000 --- a/django/utils/unittest/case.py +++ /dev/null @@ -1,1076 +0,0 @@ -"""Test case implementation""" - -import sys -import difflib -import pprint -import re -import unittest -import warnings - -from django.utils.unittest import result -from django.utils.unittest.util import\ - safe_repr, safe_str, strclass,\ - unorderable_list_difference - -from django.utils.unittest.compatibility import wraps - -__unittest = True - - -DIFF_OMITTED = ('\nDiff is %s characters long. ' - 'Set self.maxDiff to None to see it.') - -class SkipTest(Exception): - """ - Raise this exception in a test to skip it. - - Usually you can use TestResult.skip() or one of the skipping decorators - instead of raising this directly. - """ - -class _ExpectedFailure(Exception): - """ - Raise this when a test is expected to fail. - - This is an implementation detail. - """ - - def __init__(self, exc_info): - # can't use super because Python 2.4 exceptions are old style - Exception.__init__(self) - self.exc_info = exc_info - -class _UnexpectedSuccess(Exception): - """ - The test was supposed to fail, but it didn't! - """ - -def _id(obj): - return obj - -def skip(reason): - """ - Unconditionally skip a test. - """ - def decorator(test_item): - if not (isinstance(test_item, type) and issubclass(test_item, TestCase)): - @wraps(test_item) - def skip_wrapper(*args, **kwargs): - raise SkipTest(reason) - test_item = skip_wrapper - - test_item.__unittest_skip__ = True - test_item.__unittest_skip_why__ = reason - return test_item - return decorator - -def skipIf(condition, reason): - """ - Skip a test if the condition is true. - """ - if condition: - return skip(reason) - return _id - -def skipUnless(condition, reason): - """ - Skip a test unless the condition is true. - """ - if not condition: - return skip(reason) - return _id - - -def expectedFailure(func): - @wraps(func) - def wrapper(*args, **kwargs): - try: - func(*args, **kwargs) - except Exception: - raise _ExpectedFailure(sys.exc_info()) - raise _UnexpectedSuccess - return wrapper - - -class _AssertRaisesContext(object): - """A context manager used to implement TestCase.assertRaises* methods.""" - - def __init__(self, expected, test_case, expected_regexp=None): - self.expected = expected - self.failureException = test_case.failureException - self.expected_regexp = expected_regexp - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, tb): - if exc_type is None: - try: - exc_name = self.expected.__name__ - except AttributeError: - exc_name = str(self.expected) - raise self.failureException( - "%s not raised" % (exc_name,)) - if not issubclass(exc_type, self.expected): - # let unexpected exceptions pass through - return False - self.exception = exc_value # store for later retrieval - if self.expected_regexp is None: - return True - - expected_regexp = self.expected_regexp - if isinstance(expected_regexp, basestring): - expected_regexp = re.compile(expected_regexp) - if not expected_regexp.search(str(exc_value)): - raise self.failureException('"%s" does not match "%s"' % - (expected_regexp.pattern, str(exc_value))) - return True - - -class _TypeEqualityDict(object): - - def __init__(self, testcase): - self.testcase = testcase - self._store = {} - - def __setitem__(self, key, value): - self._store[key] = value - - def __getitem__(self, key): - value = self._store[key] - if isinstance(value, basestring): - return getattr(self.testcase, value) - return value - - def get(self, key, default=None): - if key in self._store: - return self[key] - return default - - -class TestCase(unittest.TestCase): - """A class whose instances are single test cases. - - By default, the test code itself should be placed in a method named - 'runTest'. - - If the fixture may be used for many test cases, create as - many test methods as are needed. When instantiating such a TestCase - subclass, specify in the constructor arguments the name of the test method - that the instance is to execute. - - Test authors should subclass TestCase for their own tests. Construction - and deconstruction of the test's environment ('fixture') can be - implemented by overriding the 'setUp' and 'tearDown' methods respectively. - - If it is necessary to override the __init__ method, the base class - __init__ method must always be called. It is important that subclasses - should not change the signature of their __init__ method, since instances - of the classes are instantiated automatically by parts of the framework - in order to be run. - """ - - # This attribute determines which exception will be raised when - # the instance's assertion methods fail; test methods raising this - # exception will be deemed to have 'failed' rather than 'errored' - - failureException = AssertionError - - # This attribute sets the maximum length of a diff in failure messages - # by assert methods using difflib. It is looked up as an instance attribute - # so can be configured by individual tests if required. - - maxDiff = 80*8 - - # This attribute determines whether long messages (including repr of - # objects used in assert methods) will be printed on failure in *addition* - # to any explicit message passed. - - longMessage = True - - # Attribute used by TestSuite for classSetUp - - _classSetupFailed = False - - def __init__(self, methodName='runTest'): - """Create an instance of the class that will use the named test - method when executed. Raises a ValueError if the instance does - not have a method with the specified name. - """ - self._testMethodName = methodName - self._resultForDoCleanups = None - try: - testMethod = getattr(self, methodName) - except AttributeError: - raise ValueError("no such test method in %s: %s" % \ - (self.__class__, methodName)) - self._testMethodDoc = testMethod.__doc__ - self._cleanups = [] - - # Map types to custom assertEqual functions that will compare - # instances of said type in more detail to generate a more useful - # error message. - self._type_equality_funcs = _TypeEqualityDict(self) - self.addTypeEqualityFunc(dict, 'assertDictEqual') - self.addTypeEqualityFunc(list, 'assertListEqual') - self.addTypeEqualityFunc(tuple, 'assertTupleEqual') - self.addTypeEqualityFunc(set, 'assertSetEqual') - self.addTypeEqualityFunc(frozenset, 'assertSetEqual') - self.addTypeEqualityFunc(unicode, 'assertMultiLineEqual') - - def addTypeEqualityFunc(self, typeobj, function): - """Add a type specific assertEqual style function to compare a type. - - This method is for use by TestCase subclasses that need to register - their own type equality functions to provide nicer error messages. - - Args: - typeobj: The data type to call this function on when both values - are of the same type in assertEqual(). - function: The callable taking two arguments and an optional - msg= argument that raises self.failureException with a - useful error message when the two arguments are not equal. - """ - self._type_equality_funcs[typeobj] = function - - def addCleanup(self, function, *args, **kwargs): - """Add a function, with arguments, to be called when the test is - completed. Functions added are called on a LIFO basis and are - called after tearDown on test failure or success. - - Cleanup items are called even if setUp fails (unlike tearDown).""" - self._cleanups.append((function, args, kwargs)) - - @classmethod - def setUpClass(cls): - "Hook method for setting up class fixture before running tests in the class." - - @classmethod - def tearDownClass(cls): - "Hook method for deconstructing the class fixture after running all tests in the class." - - def countTestCases(self): - return 1 - - def defaultTestResult(self): - return result.TestResult() - - def shortDescription(self): - """Returns a one-line description of the test, or None if no - description has been provided. - - The default implementation of this method returns the first line of - the specified test method's docstring. - """ - doc = self._testMethodDoc - return doc and doc.split("\n")[0].strip() or None - - - def id(self): - return "%s.%s" % (strclass(self.__class__), self._testMethodName) - - def __eq__(self, other): - if type(self) is not type(other): - return NotImplemented - - return self._testMethodName == other._testMethodName - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash((type(self), self._testMethodName)) - - def __str__(self): - return "%s (%s)" % (self._testMethodName, strclass(self.__class__)) - - def __repr__(self): - return "<%s testMethod=%s>" % \ - (strclass(self.__class__), self._testMethodName) - - def _addSkip(self, result, reason): - addSkip = getattr(result, 'addSkip', None) - if addSkip is not None: - addSkip(self, reason) - else: - warnings.warn("Use of a TestResult without an addSkip method is deprecated", - DeprecationWarning, 2) - result.addSuccess(self) - - def run(self, result=None): - orig_result = result - if result is None: - result = self.defaultTestResult() - startTestRun = getattr(result, 'startTestRun', None) - if startTestRun is not None: - startTestRun() - - self._resultForDoCleanups = result - result.startTest(self) - - testMethod = getattr(self, self._testMethodName) - - if (getattr(self.__class__, "__unittest_skip__", False) or - getattr(testMethod, "__unittest_skip__", False)): - # If the class or method was skipped. - try: - skip_why = (getattr(self.__class__, '__unittest_skip_why__', '') - or getattr(testMethod, '__unittest_skip_why__', '')) - self._addSkip(result, skip_why) - finally: - result.stopTest(self) - return - try: - success = False - try: - self.setUp() - except SkipTest as e: - self._addSkip(result, str(e)) - except Exception: - result.addError(self, sys.exc_info()) - else: - try: - testMethod() - except self.failureException: - result.addFailure(self, sys.exc_info()) - except _ExpectedFailure as e: - addExpectedFailure = getattr(result, 'addExpectedFailure', None) - if addExpectedFailure is not None: - addExpectedFailure(self, e.exc_info) - else: - warnings.warn("Use of a TestResult without an addExpectedFailure method is deprecated", - DeprecationWarning) - result.addSuccess(self) - except _UnexpectedSuccess: - addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None) - if addUnexpectedSuccess is not None: - addUnexpectedSuccess(self) - else: - warnings.warn("Use of a TestResult without an addUnexpectedSuccess method is deprecated", - DeprecationWarning) - result.addFailure(self, sys.exc_info()) - except SkipTest as e: - self._addSkip(result, str(e)) - except Exception: - result.addError(self, sys.exc_info()) - else: - success = True - - try: - self.tearDown() - except Exception: - result.addError(self, sys.exc_info()) - success = False - - cleanUpSuccess = self.doCleanups() - success = success and cleanUpSuccess - if success: - result.addSuccess(self) - finally: - result.stopTest(self) - if orig_result is None: - stopTestRun = getattr(result, 'stopTestRun', None) - if stopTestRun is not None: - stopTestRun() - - def doCleanups(self): - """Execute all cleanup functions. Normally called for you after - tearDown.""" - result = self._resultForDoCleanups - ok = True - while self._cleanups: - function, args, kwargs = self._cleanups.pop(-1) - try: - function(*args, **kwargs) - except Exception: - ok = False - result.addError(self, sys.exc_info()) - return ok - - def __call__(self, *args, **kwds): - return self.run(*args, **kwds) - - def debug(self): - """Run the test without collecting errors in a TestResult""" - self.setUp() - getattr(self, self._testMethodName)() - self.tearDown() - while self._cleanups: - function, args, kwargs = self._cleanups.pop(-1) - function(*args, **kwargs) - - def skipTest(self, reason): - """Skip this test.""" - raise SkipTest(reason) - - def fail(self, msg=None): - """Fail immediately, with the given message.""" - raise self.failureException(msg) - - def assertFalse(self, expr, msg=None): - "Fail the test if the expression is true." - if expr: - msg = self._formatMessage(msg, "%s is not False" % safe_repr(expr)) - raise self.failureException(msg) - - def assertTrue(self, expr, msg=None): - """Fail the test unless the expression is true.""" - if not expr: - msg = self._formatMessage(msg, "%s is not True" % safe_repr(expr)) - raise self.failureException(msg) - - def _formatMessage(self, msg, standardMsg): - """Honour the longMessage attribute when generating failure messages. - If longMessage is False this means: - * Use only an explicit message if it is provided - * Otherwise use the standard message for the assert - - If longMessage is True: - * Use the standard message - * If an explicit message is provided, plus ' : ' and the explicit message - """ - if not self.longMessage: - return msg or standardMsg - if msg is None: - return standardMsg - try: - return '%s : %s' % (standardMsg, msg) - except UnicodeDecodeError: - return '%s : %s' % (safe_str(standardMsg), safe_str(msg)) - - - def assertRaises(self, excClass, callableObj=None, *args, **kwargs): - """Fail unless an exception of class excClass is thrown - by callableObj when invoked with arguments args and keyword - arguments kwargs. If a different type of exception is - thrown, it will not be caught, and the test case will be - deemed to have suffered an error, exactly as for an - unexpected exception. - - If called with callableObj omitted or None, will return a - context object used like this:: - - with self.assertRaises(SomeException): - do_something() - - The context manager keeps a reference to the exception as - the 'exception' attribute. This allows you to inspect the - exception after the assertion:: - - with self.assertRaises(SomeException) as cm: - do_something() - the_exception = cm.exception - self.assertEqual(the_exception.error_code, 3) - """ - if callableObj is None: - return _AssertRaisesContext(excClass, self) - try: - callableObj(*args, **kwargs) - except excClass: - return - - if hasattr(excClass,'__name__'): - excName = excClass.__name__ - else: - excName = str(excClass) - raise self.failureException("%s not raised" % excName) - - def _getAssertEqualityFunc(self, first, second): - """Get a detailed comparison function for the types of the two args. - - Returns: A callable accepting (first, second, msg=None) that will - raise a failure exception if first != second with a useful human - readable error message for those types. - """ - # - # NOTE(gregory.p.smith): I considered isinstance(first, type(second)) - # and vice versa. I opted for the conservative approach in case - # subclasses are not intended to be compared in detail to their super - # class instances using a type equality func. This means testing - # subtypes won't automagically use the detailed comparison. Callers - # should use their type specific assertSpamEqual method to compare - # subclasses if the detailed comparison is desired and appropriate. - # See the discussion in http://bugs.python.org/issue2578. - # - if type(first) is type(second): - asserter = self._type_equality_funcs.get(type(first)) - if asserter is not None: - return asserter - - return self._baseAssertEqual - - def _baseAssertEqual(self, first, second, msg=None): - """The default assertEqual implementation, not type specific.""" - if not first == second: - standardMsg = '%s != %s' % (safe_repr(first), safe_repr(second)) - msg = self._formatMessage(msg, standardMsg) - raise self.failureException(msg) - - def assertEqual(self, first, second, msg=None): - """Fail if the two objects are unequal as determined by the '==' - operator. - """ - assertion_func = self._getAssertEqualityFunc(first, second) - assertion_func(first, second, msg=msg) - - def assertNotEqual(self, first, second, msg=None): - """Fail if the two objects are equal as determined by the '==' - operator. - """ - if not first != second: - msg = self._formatMessage(msg, '%s == %s' % (safe_repr(first), - safe_repr(second))) - raise self.failureException(msg) - - def assertAlmostEqual(self, first, second, places=None, msg=None, delta=None): - """Fail if the two objects are unequal as determined by their - difference rounded to the given number of decimal places - (default 7) and comparing to zero, or by comparing that the - between the two objects is more than the given delta. - - Note that decimal places (from zero) are usually not the same - as significant digits (measured from the most signficant digit). - - If the two objects compare equal then they will automatically - compare almost equal. - """ - if first == second: - # shortcut - return - if delta is not None and places is not None: - raise TypeError("specify delta or places not both") - - if delta is not None: - if abs(first - second) <= delta: - return - - standardMsg = '%s != %s within %s delta' % (safe_repr(first), - safe_repr(second), - safe_repr(delta)) - else: - if places is None: - places = 7 - - if round(abs(second-first), places) == 0: - return - - standardMsg = '%s != %s within %r places' % (safe_repr(first), - safe_repr(second), - places) - msg = self._formatMessage(msg, standardMsg) - raise self.failureException(msg) - - def assertNotAlmostEqual(self, first, second, places=None, msg=None, delta=None): - """Fail if the two objects are equal as determined by their - difference rounded to the given number of decimal places - (default 7) and comparing to zero, or by comparing that the - between the two objects is less than the given delta. - - Note that decimal places (from zero) are usually not the same - as significant digits (measured from the most signficant digit). - - Objects that are equal automatically fail. - """ - if delta is not None and places is not None: - raise TypeError("specify delta or places not both") - if delta is not None: - if not (first == second) and abs(first - second) > delta: - return - standardMsg = '%s == %s within %s delta' % (safe_repr(first), - safe_repr(second), - safe_repr(delta)) - else: - if places is None: - places = 7 - if not (first == second) and round(abs(second-first), places) != 0: - return - standardMsg = '%s == %s within %r places' % (safe_repr(first), - safe_repr(second), - places) - - msg = self._formatMessage(msg, standardMsg) - raise self.failureException(msg) - - # Synonyms for assertion methods - - # The plurals are undocumented. Keep them that way to discourage use. - # Do not add more. Do not remove. - # Going through a deprecation cycle on these would annoy many people. - assertEquals = assertEqual - assertNotEquals = assertNotEqual - assertAlmostEquals = assertAlmostEqual - assertNotAlmostEquals = assertNotAlmostEqual - assert_ = assertTrue - - # These fail* assertion method names are pending deprecation and will - # be a DeprecationWarning in 3.2; http://bugs.python.org/issue2578 - def _deprecate(original_func): - def deprecated_func(*args, **kwargs): - warnings.warn( - ('Please use %s instead.' % original_func.__name__), - PendingDeprecationWarning, 2) - return original_func(*args, **kwargs) - return deprecated_func - - failUnlessEqual = _deprecate(assertEqual) - failIfEqual = _deprecate(assertNotEqual) - failUnlessAlmostEqual = _deprecate(assertAlmostEqual) - failIfAlmostEqual = _deprecate(assertNotAlmostEqual) - failUnless = _deprecate(assertTrue) - failUnlessRaises = _deprecate(assertRaises) - failIf = _deprecate(assertFalse) - - def assertSequenceEqual(self, seq1, seq2, - msg=None, seq_type=None, max_diff=80*8): - """An equality assertion for ordered sequences (like lists and tuples). - - For the purposes of this function, a valid ordered sequence type is one - which can be indexed, has a length, and has an equality operator. - - Args: - seq1: The first sequence to compare. - seq2: The second sequence to compare. - seq_type: The expected datatype of the sequences, or None if no - datatype should be enforced. - msg: Optional message to use on failure instead of a list of - differences. - max_diff: Maximum size off the diff, larger diffs are not shown - """ - if seq_type is not None: - seq_type_name = seq_type.__name__ - if not isinstance(seq1, seq_type): - raise self.failureException('First sequence is not a %s: %s' - % (seq_type_name, safe_repr(seq1))) - if not isinstance(seq2, seq_type): - raise self.failureException('Second sequence is not a %s: %s' - % (seq_type_name, safe_repr(seq2))) - else: - seq_type_name = "sequence" - - differing = None - try: - len1 = len(seq1) - except (TypeError, NotImplementedError): - differing = 'First %s has no length. Non-sequence?' % ( - seq_type_name) - - if differing is None: - try: - len2 = len(seq2) - except (TypeError, NotImplementedError): - differing = 'Second %s has no length. Non-sequence?' % ( - seq_type_name) - - if differing is None: - if seq1 == seq2: - return - - seq1_repr = repr(seq1) - seq2_repr = repr(seq2) - if len(seq1_repr) > 30: - seq1_repr = seq1_repr[:30] + '...' - if len(seq2_repr) > 30: - seq2_repr = seq2_repr[:30] + '...' - elements = (seq_type_name.capitalize(), seq1_repr, seq2_repr) - differing = '%ss differ: %s != %s\n' % elements - - for i in xrange(min(len1, len2)): - try: - item1 = seq1[i] - except (TypeError, IndexError, NotImplementedError): - differing += ('\nUnable to index element %d of first %s\n' % - (i, seq_type_name)) - break - - try: - item2 = seq2[i] - except (TypeError, IndexError, NotImplementedError): - differing += ('\nUnable to index element %d of second %s\n' % - (i, seq_type_name)) - break - - if item1 != item2: - differing += ('\nFirst differing element %d:\n%s\n%s\n' % - (i, item1, item2)) - break - else: - if (len1 == len2 and seq_type is None and - type(seq1) != type(seq2)): - # The sequences are the same, but have differing types. - return - - if len1 > len2: - differing += ('\nFirst %s contains %d additional ' - 'elements.\n' % (seq_type_name, len1 - len2)) - try: - differing += ('First extra element %d:\n%s\n' % - (len2, seq1[len2])) - except (TypeError, IndexError, NotImplementedError): - differing += ('Unable to index element %d ' - 'of first %s\n' % (len2, seq_type_name)) - elif len1 < len2: - differing += ('\nSecond %s contains %d additional ' - 'elements.\n' % (seq_type_name, len2 - len1)) - try: - differing += ('First extra element %d:\n%s\n' % - (len1, seq2[len1])) - except (TypeError, IndexError, NotImplementedError): - differing += ('Unable to index element %d ' - 'of second %s\n' % (len1, seq_type_name)) - standardMsg = differing - diffMsg = '\n' + '\n'.join( - difflib.ndiff(pprint.pformat(seq1).splitlines(), - pprint.pformat(seq2).splitlines())) - - standardMsg = self._truncateMessage(standardMsg, diffMsg) - msg = self._formatMessage(msg, standardMsg) - self.fail(msg) - - def _truncateMessage(self, message, diff): - max_diff = self.maxDiff - if max_diff is None or len(diff) <= max_diff: - return message + diff - return message + (DIFF_OMITTED % len(diff)) - - def assertListEqual(self, list1, list2, msg=None): - """A list-specific equality assertion. - - Args: - list1: The first list to compare. - list2: The second list to compare. - msg: Optional message to use on failure instead of a list of - differences. - - """ - self.assertSequenceEqual(list1, list2, msg, seq_type=list) - - def assertTupleEqual(self, tuple1, tuple2, msg=None): - """A tuple-specific equality assertion. - - Args: - tuple1: The first tuple to compare. - tuple2: The second tuple to compare. - msg: Optional message to use on failure instead of a list of - differences. - """ - self.assertSequenceEqual(tuple1, tuple2, msg, seq_type=tuple) - - def assertSetEqual(self, set1, set2, msg=None): - """A set-specific equality assertion. - - Args: - set1: The first set to compare. - set2: The second set to compare. - msg: Optional message to use on failure instead of a list of - differences. - - assertSetEqual uses ducktyping to support - different types of sets, and is optimized for sets specifically - (parameters must support a difference method). - """ - try: - difference1 = set1.difference(set2) - except TypeError as e: - self.fail('invalid type when attempting set difference: %s' % e) - except AttributeError as e: - self.fail('first argument does not support set difference: %s' % e) - - try: - difference2 = set2.difference(set1) - except TypeError as e: - self.fail('invalid type when attempting set difference: %s' % e) - except AttributeError as e: - self.fail('second argument does not support set difference: %s' % e) - - if not (difference1 or difference2): - return - - lines = [] - if difference1: - lines.append('Items in the first set but not the second:') - for item in difference1: - lines.append(repr(item)) - if difference2: - lines.append('Items in the second set but not the first:') - for item in difference2: - lines.append(repr(item)) - - standardMsg = '\n'.join(lines) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertIn(self, member, container, msg=None): - """Just like self.assertTrue(a in b), but with a nicer default message.""" - if member not in container: - standardMsg = '%s not found in %s' % (safe_repr(member), - safe_repr(container)) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertNotIn(self, member, container, msg=None): - """Just like self.assertTrue(a not in b), but with a nicer default message.""" - if member in container: - standardMsg = '%s unexpectedly found in %s' % (safe_repr(member), - safe_repr(container)) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertIs(self, expr1, expr2, msg=None): - """Just like self.assertTrue(a is b), but with a nicer default message.""" - if expr1 is not expr2: - standardMsg = '%s is not %s' % (safe_repr(expr1), safe_repr(expr2)) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertIsNot(self, expr1, expr2, msg=None): - """Just like self.assertTrue(a is not b), but with a nicer default message.""" - if expr1 is expr2: - standardMsg = 'unexpectedly identical: %s' % (safe_repr(expr1),) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertDictEqual(self, d1, d2, msg=None): - self.assertTrue(isinstance(d1, dict), 'First argument is not a dictionary') - self.assertTrue(isinstance(d2, dict), 'Second argument is not a dictionary') - - if d1 != d2: - standardMsg = '%s != %s' % (safe_repr(d1, True), safe_repr(d2, True)) - diff = ('\n' + '\n'.join(difflib.ndiff( - pprint.pformat(d1).splitlines(), - pprint.pformat(d2).splitlines()))) - standardMsg = self._truncateMessage(standardMsg, diff) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertDictContainsSubset(self, expected, actual, msg=None): - """Checks whether actual is a superset of expected.""" - missing = [] - mismatched = [] - for key, value in expected.iteritems(): - if key not in actual: - missing.append(key) - elif value != actual[key]: - mismatched.append('%s, expected: %s, actual: %s' % - (safe_repr(key), safe_repr(value), - safe_repr(actual[key]))) - - if not (missing or mismatched): - return - - standardMsg = '' - if missing: - standardMsg = 'Missing: %s' % ','.join(safe_repr(m) for m in - missing) - if mismatched: - if standardMsg: - standardMsg += '; ' - standardMsg += 'Mismatched values: %s' % ','.join(mismatched) - - self.fail(self._formatMessage(msg, standardMsg)) - - def assertItemsEqual(self, expected_seq, actual_seq, msg=None): - """An unordered sequence specific comparison. It asserts that - expected_seq and actual_seq contain the same elements. It is - the equivalent of:: - - self.assertEqual(sorted(expected_seq), sorted(actual_seq)) - - Raises with an error message listing which elements of expected_seq - are missing from actual_seq and vice versa if any. - - Asserts that each element has the same count in both sequences. - Example: - - [0, 1, 1] and [1, 0, 1] compare equal. - - [0, 0, 1] and [0, 1] compare unequal. - """ - try: - expected = sorted(expected_seq) - actual = sorted(actual_seq) - except TypeError: - # Unsortable items (example: set(), complex(), ...) - expected = list(expected_seq) - actual = list(actual_seq) - missing, unexpected = unorderable_list_difference( - expected, actual, ignore_duplicate=False - ) - else: - return self.assertSequenceEqual(expected, actual, msg=msg) - - errors = [] - if missing: - errors.append('Expected, but missing:\n %s' % - safe_repr(missing)) - if unexpected: - errors.append('Unexpected, but present:\n %s' % - safe_repr(unexpected)) - if errors: - standardMsg = '\n'.join(errors) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertMultiLineEqual(self, first, second, msg=None): - """Assert that two multi-line strings are equal.""" - self.assertTrue(isinstance(first, basestring), ( - 'First argument is not a string')) - self.assertTrue(isinstance(second, basestring), ( - 'Second argument is not a string')) - - if first != second: - standardMsg = '%s != %s' % (safe_repr(first, True), safe_repr(second, True)) - diff = '\n' + ''.join(difflib.ndiff(first.splitlines(True), - second.splitlines(True))) - standardMsg = self._truncateMessage(standardMsg, diff) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertLess(self, a, b, msg=None): - """Just like self.assertTrue(a < b), but with a nicer default message.""" - if not a < b: - standardMsg = '%s not less than %s' % (safe_repr(a), safe_repr(b)) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertLessEqual(self, a, b, msg=None): - """Just like self.assertTrue(a <= b), but with a nicer default message.""" - if not a <= b: - standardMsg = '%s not less than or equal to %s' % (safe_repr(a), safe_repr(b)) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertGreater(self, a, b, msg=None): - """Just like self.assertTrue(a > b), but with a nicer default message.""" - if not a > b: - standardMsg = '%s not greater than %s' % (safe_repr(a), safe_repr(b)) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertGreaterEqual(self, a, b, msg=None): - """Just like self.assertTrue(a >= b), but with a nicer default message.""" - if not a >= b: - standardMsg = '%s not greater than or equal to %s' % (safe_repr(a), safe_repr(b)) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertIsNone(self, obj, msg=None): - """Same as self.assertTrue(obj is None), with a nicer default message.""" - if obj is not None: - standardMsg = '%s is not None' % (safe_repr(obj),) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertIsNotNone(self, obj, msg=None): - """Included for symmetry with assertIsNone.""" - if obj is None: - standardMsg = 'unexpectedly None' - self.fail(self._formatMessage(msg, standardMsg)) - - def assertIsInstance(self, obj, cls, msg=None): - """Same as self.assertTrue(isinstance(obj, cls)), with a nicer - default message.""" - if not isinstance(obj, cls): - standardMsg = '%s is not an instance of %r' % (safe_repr(obj), cls) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertNotIsInstance(self, obj, cls, msg=None): - """Included for symmetry with assertIsInstance.""" - if isinstance(obj, cls): - standardMsg = '%s is an instance of %r' % (safe_repr(obj), cls) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertRaisesRegexp(self, expected_exception, expected_regexp, - callable_obj=None, *args, **kwargs): - """Asserts that the message in a raised exception matches a regexp. - - Args: - expected_exception: Exception class expected to be raised. - expected_regexp: Regexp (re pattern object or string) expected - to be found in error message. - callable_obj: Function to be called. - args: Extra args. - kwargs: Extra kwargs. - """ - if callable_obj is None: - return _AssertRaisesContext(expected_exception, self, expected_regexp) - try: - callable_obj(*args, **kwargs) - except expected_exception as exc_value: - if isinstance(expected_regexp, basestring): - expected_regexp = re.compile(expected_regexp) - if not expected_regexp.search(str(exc_value)): - raise self.failureException('"%s" does not match "%s"' % - (expected_regexp.pattern, str(exc_value))) - else: - if hasattr(expected_exception, '__name__'): - excName = expected_exception.__name__ - else: - excName = str(expected_exception) - raise self.failureException("%s not raised" % excName) - - def assertRegexpMatches(self, text, expected_regexp, msg=None): - """Fail the test unless the text matches the regular expression.""" - if isinstance(expected_regexp, basestring): - expected_regexp = re.compile(expected_regexp) - if not expected_regexp.search(text): - msg = msg or "Regexp didn't match" - msg = '%s: %r not found in %r' % (msg, expected_regexp.pattern, text) - raise self.failureException(msg) - - def assertNotRegexpMatches(self, text, unexpected_regexp, msg=None): - """Fail the test if the text matches the regular expression.""" - if isinstance(unexpected_regexp, basestring): - unexpected_regexp = re.compile(unexpected_regexp) - match = unexpected_regexp.search(text) - if match: - msg = msg or "Regexp matched" - msg = '%s: %r matches %r in %r' % (msg, - text[match.start():match.end()], - unexpected_regexp.pattern, - text) - raise self.failureException(msg) - -class FunctionTestCase(TestCase): - """A test case that wraps a test function. - - This is useful for slipping pre-existing test functions into the - unittest framework. Optionally, set-up and tidy-up functions can be - supplied. As with TestCase, the tidy-up ('tearDown') function will - always be called if the set-up ('setUp') function ran successfully. - """ - - def __init__(self, testFunc, setUp=None, tearDown=None, description=None): - super(FunctionTestCase, self).__init__() - self._setUpFunc = setUp - self._tearDownFunc = tearDown - self._testFunc = testFunc - self._description = description - - def setUp(self): - if self._setUpFunc is not None: - self._setUpFunc() - - def tearDown(self): - if self._tearDownFunc is not None: - self._tearDownFunc() - - def runTest(self): - self._testFunc() - - def id(self): - return self._testFunc.__name__ - - def __eq__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - - return self._setUpFunc == other._setUpFunc and \ - self._tearDownFunc == other._tearDownFunc and \ - self._testFunc == other._testFunc and \ - self._description == other._description - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash((type(self), self._setUpFunc, self._tearDownFunc, - self._testFunc, self._description)) - - def __str__(self): - return "%s (%s)" % (strclass(self.__class__), - self._testFunc.__name__) - - def __repr__(self): - return "<%s testFunc=%s>" % (strclass(self.__class__), - self._testFunc) - - def shortDescription(self): - if self._description is not None: - return self._description - doc = self._testFunc.__doc__ - return doc and doc.split("\n")[0].strip() or None diff --git a/django/utils/unittest/collector.py b/django/utils/unittest/collector.py deleted file mode 100644 index 0f76fc34041d8..0000000000000 --- a/django/utils/unittest/collector.py +++ /dev/null @@ -1,9 +0,0 @@ -import os -import sys -from django.utils.unittest.loader import defaultTestLoader - -def collector(): - # import __main__ triggers code re-execution - __main__ = sys.modules['__main__'] - setupDir = os.path.abspath(os.path.dirname(__main__.__file__)) - return defaultTestLoader.discover(setupDir) diff --git a/django/utils/unittest/compatibility.py b/django/utils/unittest/compatibility.py deleted file mode 100644 index a0dc499cbf46f..0000000000000 --- a/django/utils/unittest/compatibility.py +++ /dev/null @@ -1,64 +0,0 @@ -import os -import sys - -try: - from functools import wraps -except ImportError: - # only needed for Python 2.4 - def wraps(_): - def _wraps(func): - return func - return _wraps - -__unittest = True - -def _relpath_nt(path, start=os.path.curdir): - """Return a relative version of a path""" - - if not path: - raise ValueError("no path specified") - start_list = os.path.abspath(start).split(os.path.sep) - path_list = os.path.abspath(path).split(os.path.sep) - if start_list[0].lower() != path_list[0].lower(): - unc_path, rest = os.path.splitunc(path) - unc_start, rest = os.path.splitunc(start) - if bool(unc_path) ^ bool(unc_start): - raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)" - % (path, start)) - else: - raise ValueError("path is on drive %s, start on drive %s" - % (path_list[0], start_list[0])) - # Work out how much of the filepath is shared by start and path. - for i in range(min(len(start_list), len(path_list))): - if start_list[i].lower() != path_list[i].lower(): - break - else: - i += 1 - - rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] - if not rel_list: - return os.path.curdir - return os.path.join(*rel_list) - -# default to posixpath definition -def _relpath_posix(path, start=os.path.curdir): - """Return a relative version of a path""" - - if not path: - raise ValueError("no path specified") - - start_list = os.path.abspath(start).split(os.path.sep) - path_list = os.path.abspath(path).split(os.path.sep) - - # Work out how much of the filepath is shared by start and path. - i = len(os.path.commonprefix([start_list, path_list])) - - rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] - if not rel_list: - return os.path.curdir - return os.path.join(*rel_list) - -if os.path is sys.modules.get('ntpath'): - relpath = _relpath_nt -else: - relpath = _relpath_posix diff --git a/django/utils/unittest/loader.py b/django/utils/unittest/loader.py deleted file mode 100644 index 695bac40efdd6..0000000000000 --- a/django/utils/unittest/loader.py +++ /dev/null @@ -1,322 +0,0 @@ -"""Loading unittests.""" - -import os -import re -import sys -import traceback -import types -import unittest - -from fnmatch import fnmatch - -from django.utils.unittest import case, suite - -try: - from os.path import relpath -except ImportError: - from django.utils.unittest.compatibility import relpath - -__unittest = True - - -def _CmpToKey(mycmp): - 'Convert a cmp= function into a key= function' - class K(object): - def __init__(self, obj): - self.obj = obj - def __lt__(self, other): - return mycmp(self.obj, other.obj) == -1 - return K - - -# what about .pyc or .pyo (etc) -# we would need to avoid loading the same tests multiple times -# from '.py', '.pyc' *and* '.pyo' -VALID_MODULE_NAME = re.compile(r'[_a-z]\w*\.py$', re.IGNORECASE) - - -def _make_failed_import_test(name, suiteClass): - message = 'Failed to import test module: %s' % name - if hasattr(traceback, 'format_exc'): - # Python 2.3 compatibility - # format_exc returns two frames of discover.py as well - message += '\n%s' % traceback.format_exc() - return _make_failed_test('ModuleImportFailure', name, ImportError(message), - suiteClass) - -def _make_failed_load_tests(name, exception, suiteClass): - return _make_failed_test('LoadTestsFailure', name, exception, suiteClass) - -def _make_failed_test(classname, methodname, exception, suiteClass): - def testFailure(self): - raise exception - attrs = {methodname: testFailure} - TestClass = type(classname, (case.TestCase,), attrs) - return suiteClass((TestClass(methodname),)) - - -class TestLoader(unittest.TestLoader): - """ - This class is responsible for loading tests according to various criteria - and returning them wrapped in a TestSuite - """ - testMethodPrefix = 'test' - sortTestMethodsUsing = cmp - suiteClass = suite.TestSuite - _top_level_dir = None - - def loadTestsFromTestCase(self, testCaseClass): - """Return a suite of all tests cases contained in testCaseClass""" - if issubclass(testCaseClass, suite.TestSuite): - raise TypeError("Test cases should not be derived from TestSuite." - " Maybe you meant to derive from TestCase?") - testCaseNames = self.getTestCaseNames(testCaseClass) - if not testCaseNames and hasattr(testCaseClass, 'runTest'): - testCaseNames = ['runTest'] - loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames)) - return loaded_suite - - def loadTestsFromModule(self, module, use_load_tests=True): - """Return a suite of all tests cases contained in the given module""" - tests = [] - for name in dir(module): - obj = getattr(module, name) - if isinstance(obj, type) and issubclass(obj, unittest.TestCase): - tests.append(self.loadTestsFromTestCase(obj)) - - load_tests = getattr(module, 'load_tests', None) - tests = self.suiteClass(tests) - if use_load_tests and load_tests is not None: - try: - return load_tests(self, tests, None) - except Exception as e: - return _make_failed_load_tests(module.__name__, e, - self.suiteClass) - return tests - - def loadTestsFromName(self, name, module=None): - """Return a suite of all tests cases given a string specifier. - - The name may resolve either to a module, a test case class, a - test method within a test case class, or a callable object which - returns a TestCase or TestSuite instance. - - The method optionally resolves the names relative to a given module. - """ - parts = name.split('.') - if module is None: - parts_copy = parts[:] - while parts_copy: - try: - module = __import__('.'.join(parts_copy)) - break - except ImportError: - del parts_copy[-1] - if not parts_copy: - raise - parts = parts[1:] - obj = module - for part in parts: - parent, obj = obj, getattr(obj, part) - - if isinstance(obj, types.ModuleType): - return self.loadTestsFromModule(obj) - elif isinstance(obj, type) and issubclass(obj, unittest.TestCase): - return self.loadTestsFromTestCase(obj) - elif (isinstance(obj, types.UnboundMethodType) and - isinstance(parent, type) and - issubclass(parent, unittest.TestCase)): - return self.suiteClass([parent(obj.__name__)]) - elif isinstance(obj, unittest.TestSuite): - return obj - elif hasattr(obj, '__call__'): - test = obj() - if isinstance(test, unittest.TestSuite): - return test - elif isinstance(test, unittest.TestCase): - return self.suiteClass([test]) - else: - raise TypeError("calling %s returned %s, not a test" % - (obj, test)) - else: - raise TypeError("don't know how to make test from: %s" % obj) - - def loadTestsFromNames(self, names, module=None): - """Return a suite of all tests cases found using the given sequence - of string specifiers. See 'loadTestsFromName()'. - """ - suites = [self.loadTestsFromName(name, module) for name in names] - return self.suiteClass(suites) - - def getTestCaseNames(self, testCaseClass): - """Return a sorted sequence of method names found within testCaseClass - """ - def isTestMethod(attrname, testCaseClass=testCaseClass, - prefix=self.testMethodPrefix): - return attrname.startswith(prefix) and \ - hasattr(getattr(testCaseClass, attrname), '__call__') - testFnNames = filter(isTestMethod, dir(testCaseClass)) - if self.sortTestMethodsUsing: - testFnNames.sort(key=_CmpToKey(self.sortTestMethodsUsing)) - return testFnNames - - def discover(self, start_dir, pattern='test*.py', top_level_dir=None): - """Find and return all test modules from the specified start - directory, recursing into subdirectories to find them. Only test files - that match the pattern will be loaded. (Using shell style pattern - matching.) - - All test modules must be importable from the top level of the project. - If the start directory is not the top level directory then the top - level directory must be specified separately. - - If a test package name (directory with '__init__.py') matches the - pattern then the package will be checked for a 'load_tests' function. If - this exists then it will be called with loader, tests, pattern. - - If load_tests exists then discovery does *not* recurse into the package, - load_tests is responsible for loading all tests in the package. - - The pattern is deliberately not stored as a loader attribute so that - packages can continue discovery themselves. top_level_dir is stored so - load_tests does not need to pass this argument in to loader.discover(). - """ - set_implicit_top = False - if top_level_dir is None and self._top_level_dir is not None: - # make top_level_dir optional if called from load_tests in a package - top_level_dir = self._top_level_dir - elif top_level_dir is None: - set_implicit_top = True - top_level_dir = start_dir - - top_level_dir = os.path.abspath(top_level_dir) - - if not top_level_dir in sys.path: - # all test modules must be importable from the top level directory - # should we *unconditionally* put the start directory in first - # in sys.path to minimise likelihood of conflicts between installed - # modules and development versions? - sys.path.insert(0, top_level_dir) - self._top_level_dir = top_level_dir - - is_not_importable = False - if os.path.isdir(os.path.abspath(start_dir)): - start_dir = os.path.abspath(start_dir) - if start_dir != top_level_dir: - is_not_importable = not os.path.isfile(os.path.join(start_dir, '__init__.py')) - else: - # support for discovery from dotted module names - try: - __import__(start_dir) - except ImportError: - is_not_importable = True - else: - the_module = sys.modules[start_dir] - top_part = start_dir.split('.')[0] - start_dir = os.path.abspath(os.path.dirname((the_module.__file__))) - if set_implicit_top: - self._top_level_dir = os.path.abspath(os.path.dirname(os.path.dirname(sys.modules[top_part].__file__))) - sys.path.remove(top_level_dir) - - if is_not_importable: - raise ImportError('Start directory is not importable: %r' % start_dir) - - tests = list(self._find_tests(start_dir, pattern)) - return self.suiteClass(tests) - - def _get_name_from_path(self, path): - path = os.path.splitext(os.path.normpath(path))[0] - - _relpath = relpath(path, self._top_level_dir) - assert not os.path.isabs(_relpath), "Path must be within the project" - assert not _relpath.startswith('..'), "Path must be within the project" - - name = _relpath.replace(os.path.sep, '.') - return name - - def _get_module_from_name(self, name): - __import__(name) - return sys.modules[name] - - def _match_path(self, path, full_path, pattern): - # override this method to use alternative matching strategy - return fnmatch(path, pattern) - - def _find_tests(self, start_dir, pattern): - """Used by discovery. Yields test suites it loads.""" - paths = os.listdir(start_dir) - - for path in paths: - full_path = os.path.join(start_dir, path) - if os.path.isfile(full_path): - if not VALID_MODULE_NAME.match(path): - # valid Python identifiers only - continue - if not self._match_path(path, full_path, pattern): - continue - # if the test file matches, load it - name = self._get_name_from_path(full_path) - try: - module = self._get_module_from_name(name) - except: - yield _make_failed_import_test(name, self.suiteClass) - else: - mod_file = os.path.abspath(getattr(module, '__file__', full_path)) - realpath = os.path.splitext(mod_file)[0] - fullpath_noext = os.path.splitext(full_path)[0] - if realpath.lower() != fullpath_noext.lower(): - module_dir = os.path.dirname(realpath) - mod_name = os.path.splitext(os.path.basename(full_path))[0] - expected_dir = os.path.dirname(full_path) - msg = ("%r module incorrectly imported from %r. Expected %r. " - "Is this module globally installed?") - raise ImportError(msg % (mod_name, module_dir, expected_dir)) - yield self.loadTestsFromModule(module) - elif os.path.isdir(full_path): - if not os.path.isfile(os.path.join(full_path, '__init__.py')): - continue - - load_tests = None - tests = None - if fnmatch(path, pattern): - # only check load_tests if the package directory itself matches the filter - name = self._get_name_from_path(full_path) - package = self._get_module_from_name(name) - load_tests = getattr(package, 'load_tests', None) - tests = self.loadTestsFromModule(package, use_load_tests=False) - - if load_tests is None: - if tests is not None: - # tests loaded from package file - yield tests - # recurse into the package - for test in self._find_tests(full_path, pattern): - yield test - else: - try: - yield load_tests(self, tests, pattern) - except Exception as e: - yield _make_failed_load_tests(package.__name__, e, - self.suiteClass) - -defaultTestLoader = TestLoader() - - -def _makeLoader(prefix, sortUsing, suiteClass=None): - loader = TestLoader() - loader.sortTestMethodsUsing = sortUsing - loader.testMethodPrefix = prefix - if suiteClass: - loader.suiteClass = suiteClass - return loader - -def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp): - return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass) - -def makeSuite(testCaseClass, prefix='test', sortUsing=cmp, - suiteClass=suite.TestSuite): - return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass) - -def findTestCases(module, prefix='test', sortUsing=cmp, - suiteClass=suite.TestSuite): - return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(module) diff --git a/django/utils/unittest/main.py b/django/utils/unittest/main.py deleted file mode 100644 index 659310babf207..0000000000000 --- a/django/utils/unittest/main.py +++ /dev/null @@ -1,241 +0,0 @@ -"""Unittest main program""" - -import sys -import os -import types - -from django.utils.unittest import loader, runner -try: - from django.utils.unittest.signals import installHandler -except ImportError: - installHandler = None - -__unittest = True - -FAILFAST = " -f, --failfast Stop on first failure\n" -CATCHBREAK = " -c, --catch Catch control-C and display results\n" -BUFFEROUTPUT = " -b, --buffer Buffer stdout and stderr during test runs\n" - -USAGE_AS_MAIN = """\ -Usage: %(progName)s [options] [tests] - -Options: - -h, --help Show this message - -v, --verbose Verbose output - -q, --quiet Minimal output -%(failfast)s%(catchbreak)s%(buffer)s -Examples: - %(progName)s test_module - run tests from test_module - %(progName)s test_module.TestClass - run tests from - test_module.TestClass - %(progName)s test_module.TestClass.test_method - run specified test method - -[tests] can be a list of any number of test modules, classes and test -methods. - -Alternative Usage: %(progName)s discover [options] - -Options: - -v, --verbose Verbose output -%(failfast)s%(catchbreak)s%(buffer)s -s directory Directory to start discovery ('.' default) - -p pattern Pattern to match test files ('test*.py' default) - -t directory Top level directory of project (default to - start directory) - -For test discovery all test modules must be importable from the top -level directory of the project. -""" - -USAGE_FROM_MODULE = """\ -Usage: %(progName)s [options] [test] [...] - -Options: - -h, --help Show this message - -v, --verbose Verbose output - -q, --quiet Minimal output -%(failfast)s%(catchbreak)s%(buffer)s -Examples: - %(progName)s - run default set of tests - %(progName)s MyTestSuite - run suite 'MyTestSuite' - %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething - %(progName)s MyTestCase - run all 'test*' test methods - in MyTestCase -""" - - -class TestProgram(object): - """A command-line program that runs a set of tests; this is primarily - for making test modules conveniently executable. - """ - USAGE = USAGE_FROM_MODULE - - # defaults for testing - failfast = catchbreak = buffer = progName = None - - def __init__(self, module='__main__', defaultTest=None, - argv=None, testRunner=None, - testLoader=loader.defaultTestLoader, exit=True, - verbosity=1, failfast=None, catchbreak=None, buffer=None): - if isinstance(module, basestring): - self.module = __import__(module) - for part in module.split('.')[1:]: - self.module = getattr(self.module, part) - else: - self.module = module - if argv is None: - argv = sys.argv - - self.exit = exit - self.verbosity = verbosity - self.failfast = failfast - self.catchbreak = catchbreak - self.buffer = buffer - self.defaultTest = defaultTest - self.testRunner = testRunner - self.testLoader = testLoader - self.progName = os.path.basename(argv[0]) - self.parseArgs(argv) - self.runTests() - - def usageExit(self, msg=None): - if msg: - print(msg) - usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '', - 'buffer': ''} - if self.failfast != False: - usage['failfast'] = FAILFAST - if self.catchbreak != False and installHandler is not None: - usage['catchbreak'] = CATCHBREAK - if self.buffer != False: - usage['buffer'] = BUFFEROUTPUT - print(self.USAGE % usage) - sys.exit(2) - - def parseArgs(self, argv): - if len(argv) > 1 and argv[1].lower() == 'discover': - self._do_discovery(argv[2:]) - return - - import getopt - long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch', 'buffer'] - try: - options, args = getopt.getopt(argv[1:], 'hHvqfcb', long_opts) - for opt, value in options: - if opt in ('-h','-H','--help'): - self.usageExit() - if opt in ('-q','--quiet'): - self.verbosity = 0 - if opt in ('-v','--verbose'): - self.verbosity = 2 - if opt in ('-f','--failfast'): - if self.failfast is None: - self.failfast = True - # Should this raise an exception if -f is not valid? - if opt in ('-c','--catch'): - if self.catchbreak is None and installHandler is not None: - self.catchbreak = True - # Should this raise an exception if -c is not valid? - if opt in ('-b','--buffer'): - if self.buffer is None: - self.buffer = True - # Should this raise an exception if -b is not valid? - if len(args) == 0 and self.defaultTest is None: - # createTests will load tests from self.module - self.testNames = None - elif len(args) > 0: - self.testNames = args - if __name__ == '__main__': - # to support python -m unittest ... - self.module = None - else: - self.testNames = (self.defaultTest,) - self.createTests() - except getopt.error as msg: - self.usageExit(msg) - - def createTests(self): - if self.testNames is None: - self.test = self.testLoader.loadTestsFromModule(self.module) - else: - self.test = self.testLoader.loadTestsFromNames(self.testNames, - self.module) - - def _do_discovery(self, argv, Loader=loader.TestLoader): - # handle command line args for test discovery - self.progName = '%s discover' % self.progName - import optparse - parser = optparse.OptionParser() - parser.prog = self.progName - parser.add_option('-v', '--verbose', dest='verbose', default=False, - help='Verbose output', action='store_true') - if self.failfast != False: - parser.add_option('-f', '--failfast', dest='failfast', default=False, - help='Stop on first fail or error', - action='store_true') - if self.catchbreak != False and installHandler is not None: - parser.add_option('-c', '--catch', dest='catchbreak', default=False, - help='Catch ctrl-C and display results so far', - action='store_true') - if self.buffer != False: - parser.add_option('-b', '--buffer', dest='buffer', default=False, - help='Buffer stdout and stderr during tests', - action='store_true') - parser.add_option('-s', '--start-directory', dest='start', default='.', - help="Directory to start discovery ('.' default)") - parser.add_option('-p', '--pattern', dest='pattern', default='test*.py', - help="Pattern to match tests ('test*.py' default)") - parser.add_option('-t', '--top-level-directory', dest='top', default=None, - help='Top level directory of project (defaults to start directory)') - - options, args = parser.parse_args(argv) - if len(args) > 3: - self.usageExit() - - for name, value in zip(('start', 'pattern', 'top'), args): - setattr(options, name, value) - - # only set options from the parsing here - # if they weren't set explicitly in the constructor - if self.failfast is None: - self.failfast = options.failfast - if self.catchbreak is None and installHandler is not None: - self.catchbreak = options.catchbreak - if self.buffer is None: - self.buffer = options.buffer - - if options.verbose: - self.verbosity = 2 - - start_dir = options.start - pattern = options.pattern - top_level_dir = options.top - - loader = Loader() - self.test = loader.discover(start_dir, pattern, top_level_dir) - - def runTests(self): - if self.catchbreak: - installHandler() - if self.testRunner is None: - self.testRunner = runner.TextTestRunner - if isinstance(self.testRunner, (type, types.ClassType)): - try: - testRunner = self.testRunner(verbosity=self.verbosity, - failfast=self.failfast, - buffer=self.buffer) - except TypeError: - # didn't accept the verbosity, buffer or failfast arguments - testRunner = self.testRunner() - else: - # it is assumed to be a TestRunner instance - testRunner = self.testRunner - self.result = testRunner.run(self.test) - if self.exit: - sys.exit(not self.result.wasSuccessful()) - -main = TestProgram - -def main_(): - TestProgram.USAGE = USAGE_AS_MAIN - main(module=None) - diff --git a/django/utils/unittest/result.py b/django/utils/unittest/result.py deleted file mode 100644 index 2d2a1ada9523e..0000000000000 --- a/django/utils/unittest/result.py +++ /dev/null @@ -1,183 +0,0 @@ -"""Test result object""" - -import sys -import traceback -import unittest - -from StringIO import StringIO - -from django.utils.unittest import util -from django.utils.unittest.compatibility import wraps - -__unittest = True - -def failfast(method): - @wraps(method) - def inner(self, *args, **kw): - if getattr(self, 'failfast', False): - self.stop() - return method(self, *args, **kw) - return inner - - -STDOUT_LINE = '\nStdout:\n%s' -STDERR_LINE = '\nStderr:\n%s' - -class TestResult(unittest.TestResult): - """Holder for test result information. - - Test results are automatically managed by the TestCase and TestSuite - classes, and do not need to be explicitly manipulated by writers of tests. - - Each instance holds the total number of tests run, and collections of - failures and errors that occurred among those test runs. The collections - contain tuples of (testcase, exceptioninfo), where exceptioninfo is the - formatted traceback of the error that occurred. - """ - _previousTestClass = None - _moduleSetUpFailed = False - - def __init__(self): - self.failfast = False - self.failures = [] - self.errors = [] - self.testsRun = 0 - self.skipped = [] - self.expectedFailures = [] - self.unexpectedSuccesses = [] - self.shouldStop = False - self.buffer = False - self._stdout_buffer = None - self._stderr_buffer = None - self._original_stdout = sys.stdout - self._original_stderr = sys.stderr - self._mirrorOutput = False - - def startTest(self, test): - "Called when the given test is about to be run" - self.testsRun += 1 - self._mirrorOutput = False - if self.buffer: - if self._stderr_buffer is None: - self._stderr_buffer = StringIO() - self._stdout_buffer = StringIO() - sys.stdout = self._stdout_buffer - sys.stderr = self._stderr_buffer - - def startTestRun(self): - """Called once before any tests are executed. - - See startTest for a method called before each test. - """ - - def stopTest(self, test): - """Called when the given test has been run""" - if self.buffer: - if self._mirrorOutput: - output = sys.stdout.getvalue() - error = sys.stderr.getvalue() - if output: - if not output.endswith('\n'): - output += '\n' - self._original_stdout.write(STDOUT_LINE % output) - if error: - if not error.endswith('\n'): - error += '\n' - self._original_stderr.write(STDERR_LINE % error) - - sys.stdout = self._original_stdout - sys.stderr = self._original_stderr - self._stdout_buffer.seek(0) - self._stdout_buffer.truncate() - self._stderr_buffer.seek(0) - self._stderr_buffer.truncate() - self._mirrorOutput = False - - - def stopTestRun(self): - """Called once after all tests are executed. - - See stopTest for a method called after each test. - """ - - @failfast - def addError(self, test, err): - """Called when an error has occurred. 'err' is a tuple of values as - returned by sys.exc_info(). - """ - self.errors.append((test, self._exc_info_to_string(err, test))) - self._mirrorOutput = True - - @failfast - def addFailure(self, test, err): - """Called when an error has occurred. 'err' is a tuple of values as - returned by sys.exc_info().""" - self.failures.append((test, self._exc_info_to_string(err, test))) - self._mirrorOutput = True - - def addSuccess(self, test): - "Called when a test has completed successfully" - pass - - def addSkip(self, test, reason): - """Called when a test is skipped.""" - self.skipped.append((test, reason)) - - def addExpectedFailure(self, test, err): - """Called when an expected failure/error occured.""" - self.expectedFailures.append( - (test, self._exc_info_to_string(err, test))) - - @failfast - def addUnexpectedSuccess(self, test): - """Called when a test was expected to fail, but succeed.""" - self.unexpectedSuccesses.append(test) - - def wasSuccessful(self): - "Tells whether or not this result was a success" - return (len(self.failures) + len(self.errors) == 0) - - def stop(self): - "Indicates that the tests should be aborted" - self.shouldStop = True - - def _exc_info_to_string(self, err, test): - """Converts a sys.exc_info()-style tuple of values into a string.""" - exctype, value, tb = err - # Skip test runner traceback levels - while tb and self._is_relevant_tb_level(tb): - tb = tb.tb_next - if exctype is test.failureException: - # Skip assert*() traceback levels - length = self._count_relevant_tb_levels(tb) - msgLines = traceback.format_exception(exctype, value, tb, length) - else: - msgLines = traceback.format_exception(exctype, value, tb) - - if self.buffer: - output = sys.stdout.getvalue() - error = sys.stderr.getvalue() - if output: - if not output.endswith('\n'): - output += '\n' - msgLines.append(STDOUT_LINE % output) - if error: - if not error.endswith('\n'): - error += '\n' - msgLines.append(STDERR_LINE % error) - return ''.join(msgLines) - - def _is_relevant_tb_level(self, tb): - return '__unittest' in tb.tb_frame.f_globals - - def _count_relevant_tb_levels(self, tb): - length = 0 - while tb and not self._is_relevant_tb_level(tb): - length += 1 - tb = tb.tb_next - return length - - def __repr__(self): - return "<%s run=%i errors=%i failures=%i>" % \ - (util.strclass(self.__class__), self.testsRun, len(self.errors), - len(self.failures)) diff --git a/django/utils/unittest/runner.py b/django/utils/unittest/runner.py deleted file mode 100644 index 242173ee315b4..0000000000000 --- a/django/utils/unittest/runner.py +++ /dev/null @@ -1,206 +0,0 @@ -"""Running tests""" - -import sys -import time -import unittest - -from django.utils.unittest import result - -try: - from django.utils.unittest.signals import registerResult -except ImportError: - def registerResult(_): - pass - -__unittest = True - - -class _WritelnDecorator(object): - """Used to decorate file-like objects with a handy 'writeln' method""" - def __init__(self,stream): - self.stream = stream - - def __getattr__(self, attr): - if attr in ('stream', '__getstate__'): - raise AttributeError(attr) - return getattr(self.stream,attr) - - def writeln(self, arg=None): - if arg: - self.write(arg) - self.write('\n') # text-mode streams translate to \r\n if needed - - -class TextTestResult(result.TestResult): - """A test result class that can print formatted text results to a stream. - - Used by TextTestRunner. - """ - separator1 = '=' * 70 - separator2 = '-' * 70 - - def __init__(self, stream, descriptions, verbosity): - super(TextTestResult, self).__init__() - self.stream = stream - self.showAll = verbosity > 1 - self.dots = verbosity == 1 - self.descriptions = descriptions - - def getDescription(self, test): - doc_first_line = test.shortDescription() - if self.descriptions and doc_first_line: - return '\n'.join((str(test), doc_first_line)) - else: - return str(test) - - def startTest(self, test): - super(TextTestResult, self).startTest(test) - if self.showAll: - self.stream.write(self.getDescription(test)) - self.stream.write(" ... ") - self.stream.flush() - - def addSuccess(self, test): - super(TextTestResult, self).addSuccess(test) - if self.showAll: - self.stream.writeln("ok") - elif self.dots: - self.stream.write('.') - self.stream.flush() - - def addError(self, test, err): - super(TextTestResult, self).addError(test, err) - if self.showAll: - self.stream.writeln("ERROR") - elif self.dots: - self.stream.write('E') - self.stream.flush() - - def addFailure(self, test, err): - super(TextTestResult, self).addFailure(test, err) - if self.showAll: - self.stream.writeln("FAIL") - elif self.dots: - self.stream.write('F') - self.stream.flush() - - def addSkip(self, test, reason): - super(TextTestResult, self).addSkip(test, reason) - if self.showAll: - self.stream.writeln("skipped %r" % (reason,)) - elif self.dots: - self.stream.write("s") - self.stream.flush() - - def addExpectedFailure(self, test, err): - super(TextTestResult, self).addExpectedFailure(test, err) - if self.showAll: - self.stream.writeln("expected failure") - elif self.dots: - self.stream.write("x") - self.stream.flush() - - def addUnexpectedSuccess(self, test): - super(TextTestResult, self).addUnexpectedSuccess(test) - if self.showAll: - self.stream.writeln("unexpected success") - elif self.dots: - self.stream.write("u") - self.stream.flush() - - def printErrors(self): - if self.dots or self.showAll: - self.stream.writeln() - self.printErrorList('ERROR', self.errors) - self.printErrorList('FAIL', self.failures) - - def printErrorList(self, flavour, errors): - for test, err in errors: - self.stream.writeln(self.separator1) - self.stream.writeln("%s: %s" % (flavour, self.getDescription(test))) - self.stream.writeln(self.separator2) - self.stream.writeln("%s" % err) - - def stopTestRun(self): - super(TextTestResult, self).stopTestRun() - self.printErrors() - - -class TextTestRunner(unittest.TextTestRunner): - """A test runner class that displays results in textual form. - - It prints out the names of tests as they are run, errors as they - occur, and a summary of the results at the end of the test run. - """ - resultclass = TextTestResult - - def __init__(self, stream=sys.stderr, descriptions=True, verbosity=1, - failfast=False, buffer=False, resultclass=None): - self.stream = _WritelnDecorator(stream) - self.descriptions = descriptions - self.verbosity = verbosity - self.failfast = failfast - self.buffer = buffer - if resultclass is not None: - self.resultclass = resultclass - - def _makeResult(self): - return self.resultclass(self.stream, self.descriptions, self.verbosity) - - def run(self, test): - "Run the given test case or test suite." - result = self._makeResult() - result.failfast = self.failfast - result.buffer = self.buffer - registerResult(result) - - startTime = time.time() - startTestRun = getattr(result, 'startTestRun', None) - if startTestRun is not None: - startTestRun() - try: - test(result) - finally: - stopTestRun = getattr(result, 'stopTestRun', None) - if stopTestRun is not None: - stopTestRun() - else: - result.printErrors() - stopTime = time.time() - timeTaken = stopTime - startTime - if hasattr(result, 'separator2'): - self.stream.writeln(result.separator2) - run = result.testsRun - self.stream.writeln("Ran %d test%s in %.3fs" % - (run, run != 1 and "s" or "", timeTaken)) - self.stream.writeln() - - expectedFails = unexpectedSuccesses = skipped = 0 - try: - results = map(len, (result.expectedFailures, - result.unexpectedSuccesses, - result.skipped)) - expectedFails, unexpectedSuccesses, skipped = results - except AttributeError: - pass - infos = [] - if not result.wasSuccessful(): - self.stream.write("FAILED") - failed, errored = map(len, (result.failures, result.errors)) - if failed: - infos.append("failures=%d" % failed) - if errored: - infos.append("errors=%d" % errored) - else: - self.stream.write("OK") - if skipped: - infos.append("skipped=%d" % skipped) - if expectedFails: - infos.append("expected failures=%d" % expectedFails) - if unexpectedSuccesses: - infos.append("unexpected successes=%d" % unexpectedSuccesses) - if infos: - self.stream.writeln(" (%s)" % (", ".join(infos),)) - else: - self.stream.write("\n") - return result diff --git a/django/utils/unittest/signals.py b/django/utils/unittest/signals.py deleted file mode 100644 index f1731ea13d577..0000000000000 --- a/django/utils/unittest/signals.py +++ /dev/null @@ -1,57 +0,0 @@ -import signal -import weakref - -from django.utils.unittest.compatibility import wraps - -__unittest = True - - -class _InterruptHandler(object): - def __init__(self, default_handler): - self.called = False - self.default_handler = default_handler - - def __call__(self, signum, frame): - installed_handler = signal.getsignal(signal.SIGINT) - if installed_handler is not self: - # if we aren't the installed handler, then delegate immediately - # to the default handler - self.default_handler(signum, frame) - - if self.called: - self.default_handler(signum, frame) - self.called = True - for result in _results.keys(): - result.stop() - -_results = weakref.WeakKeyDictionary() -def registerResult(result): - _results[result] = 1 - -def removeResult(result): - return bool(_results.pop(result, None)) - -_interrupt_handler = None -def installHandler(): - global _interrupt_handler - if _interrupt_handler is None: - default_handler = signal.getsignal(signal.SIGINT) - _interrupt_handler = _InterruptHandler(default_handler) - signal.signal(signal.SIGINT, _interrupt_handler) - - -def removeHandler(method=None): - if method is not None: - @wraps(method) - def inner(*args, **kwargs): - initial = signal.getsignal(signal.SIGINT) - removeHandler() - try: - return method(*args, **kwargs) - finally: - signal.signal(signal.SIGINT, initial) - return inner - - global _interrupt_handler - if _interrupt_handler is not None: - signal.signal(signal.SIGINT, _interrupt_handler.default_handler) diff --git a/django/utils/unittest/suite.py b/django/utils/unittest/suite.py deleted file mode 100644 index da8ac2e2a8e91..0000000000000 --- a/django/utils/unittest/suite.py +++ /dev/null @@ -1,287 +0,0 @@ -"""TestSuite""" - -import sys -import unittest -from django.utils.unittest import case, util - -__unittest = True - - -class BaseTestSuite(unittest.TestSuite): - """A simple test suite that doesn't provide class or module shared fixtures. - """ - def __init__(self, tests=()): - self._tests = [] - self.addTests(tests) - - def __repr__(self): - return "<%s tests=%s>" % (util.strclass(self.__class__), list(self)) - - def __eq__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - return list(self) == list(other) - - def __ne__(self, other): - return not self == other - - # Can't guarantee hash invariant, so flag as unhashable - __hash__ = None - - def __iter__(self): - return iter(self._tests) - - def countTestCases(self): - cases = 0 - for test in self: - cases += test.countTestCases() - return cases - - def addTest(self, test): - # sanity checks - if not hasattr(test, '__call__'): - raise TypeError("%r is not callable" % (repr(test),)) - if isinstance(test, type) and issubclass(test, - (case.TestCase, TestSuite)): - raise TypeError("TestCases and TestSuites must be instantiated " - "before passing them to addTest()") - self._tests.append(test) - - def addTests(self, tests): - if isinstance(tests, basestring): - raise TypeError("tests must be an iterable of tests, not a string") - for test in tests: - self.addTest(test) - - def run(self, result): - for test in self: - if result.shouldStop: - break - test(result) - return result - - def __call__(self, *args, **kwds): - return self.run(*args, **kwds) - - def debug(self): - """Run the tests without collecting errors in a TestResult""" - for test in self: - test.debug() - - -class TestSuite(BaseTestSuite): - """A test suite is a composite test consisting of a number of TestCases. - - For use, create an instance of TestSuite, then add test case instances. - When all tests have been added, the suite can be passed to a test - runner, such as TextTestRunner. It will run the individual test cases - in the order in which they were added, aggregating the results. When - subclassing, do not forget to call the base class constructor. - """ - - - def run(self, result): - self._wrapped_run(result) - self._tearDownPreviousClass(None, result) - self._handleModuleTearDown(result) - return result - - def debug(self): - """Run the tests without collecting errors in a TestResult""" - debug = _DebugResult() - self._wrapped_run(debug, True) - self._tearDownPreviousClass(None, debug) - self._handleModuleTearDown(debug) - - ################################ - # private methods - def _wrapped_run(self, result, debug=False): - for test in self: - if result.shouldStop: - break - - if _isnotsuite(test): - self._tearDownPreviousClass(test, result) - self._handleModuleFixture(test, result) - self._handleClassSetUp(test, result) - result._previousTestClass = test.__class__ - - if (getattr(test.__class__, '_classSetupFailed', False) or - getattr(result, '_moduleSetUpFailed', False)): - continue - - if hasattr(test, '_wrapped_run'): - test._wrapped_run(result, debug) - elif not debug: - test(result) - else: - test.debug() - - def _handleClassSetUp(self, test, result): - previousClass = getattr(result, '_previousTestClass', None) - currentClass = test.__class__ - if currentClass == previousClass: - return - if result._moduleSetUpFailed: - return - if getattr(currentClass, "__unittest_skip__", False): - return - - try: - currentClass._classSetupFailed = False - except TypeError: - # test may actually be a function - # so its class will be a builtin-type - pass - - setUpClass = getattr(currentClass, 'setUpClass', None) - if setUpClass is not None: - try: - setUpClass() - except Exception as e: - if isinstance(result, _DebugResult): - raise - currentClass._classSetupFailed = True - className = util.strclass(currentClass) - errorName = 'setUpClass (%s)' % className - self._addClassOrModuleLevelException(result, e, errorName) - - def _get_previous_module(self, result): - previousModule = None - previousClass = getattr(result, '_previousTestClass', None) - if previousClass is not None: - previousModule = previousClass.__module__ - return previousModule - - - def _handleModuleFixture(self, test, result): - previousModule = self._get_previous_module(result) - currentModule = test.__class__.__module__ - if currentModule == previousModule: - return - - self._handleModuleTearDown(result) - - - result._moduleSetUpFailed = False - try: - module = sys.modules[currentModule] - except KeyError: - return - setUpModule = getattr(module, 'setUpModule', None) - if setUpModule is not None: - try: - setUpModule() - except Exception as e: - if isinstance(result, _DebugResult): - raise - result._moduleSetUpFailed = True - errorName = 'setUpModule (%s)' % currentModule - self._addClassOrModuleLevelException(result, e, errorName) - - def _addClassOrModuleLevelException(self, result, exception, errorName): - error = _ErrorHolder(errorName) - addSkip = getattr(result, 'addSkip', None) - if addSkip is not None and isinstance(exception, case.SkipTest): - addSkip(error, str(exception)) - else: - result.addError(error, sys.exc_info()) - - def _handleModuleTearDown(self, result): - previousModule = self._get_previous_module(result) - if previousModule is None: - return - if result._moduleSetUpFailed: - return - - try: - module = sys.modules[previousModule] - except KeyError: - return - - tearDownModule = getattr(module, 'tearDownModule', None) - if tearDownModule is not None: - try: - tearDownModule() - except Exception as e: - if isinstance(result, _DebugResult): - raise - errorName = 'tearDownModule (%s)' % previousModule - self._addClassOrModuleLevelException(result, e, errorName) - - def _tearDownPreviousClass(self, test, result): - previousClass = getattr(result, '_previousTestClass', None) - currentClass = test.__class__ - if currentClass == previousClass: - return - if getattr(previousClass, '_classSetupFailed', False): - return - if getattr(result, '_moduleSetUpFailed', False): - return - if getattr(previousClass, "__unittest_skip__", False): - return - - tearDownClass = getattr(previousClass, 'tearDownClass', None) - if tearDownClass is not None: - try: - tearDownClass() - except Exception as e: - if isinstance(result, _DebugResult): - raise - className = util.strclass(previousClass) - errorName = 'tearDownClass (%s)' % className - self._addClassOrModuleLevelException(result, e, errorName) - - -class _ErrorHolder(object): - """ - Placeholder for a TestCase inside a result. As far as a TestResult - is concerned, this looks exactly like a unit test. Used to insert - arbitrary errors into a test suite run. - """ - # Inspired by the ErrorHolder from Twisted: - # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py - - # attribute used by TestResult._exc_info_to_string - failureException = None - - def __init__(self, description): - self.description = description - - def id(self): - return self.description - - def shortDescription(self): - return None - - def __repr__(self): - return "" % (self.description,) - - def __str__(self): - return self.id() - - def run(self, result): - # could call result.addError(...) - but this test-like object - # shouldn't be run anyway - pass - - def __call__(self, result): - return self.run(result) - - def countTestCases(self): - return 0 - -def _isnotsuite(test): - "A crude way to tell apart testcases and suites with duck-typing" - try: - iter(test) - except TypeError: - return True - return False - - -class _DebugResult(object): - "Used by the TestSuite to hold previous class when running in debug." - _previousTestClass = None - _moduleSetUpFailed = False - shouldStop = False diff --git a/django/utils/unittest/util.py b/django/utils/unittest/util.py deleted file mode 100644 index c45d008cc8863..0000000000000 --- a/django/utils/unittest/util.py +++ /dev/null @@ -1,99 +0,0 @@ -"""Various utility functions.""" - -__unittest = True - - -_MAX_LENGTH = 80 -def safe_repr(obj, short=False): - try: - result = repr(obj) - except Exception: - result = object.__repr__(obj) - if not short or len(result) < _MAX_LENGTH: - return result - return result[:_MAX_LENGTH] + ' [truncated]...' - -def safe_str(obj): - try: - return str(obj) - except Exception: - return object.__str__(obj) - -def strclass(cls): - return "%s.%s" % (cls.__module__, cls.__name__) - -def sorted_list_difference(expected, actual): - """Finds elements in only one or the other of two, sorted input lists. - - Returns a two-element tuple of lists. The first list contains those - elements in the "expected" list but not in the "actual" list, and the - second contains those elements in the "actual" list but not in the - "expected" list. Duplicate elements in either input list are ignored. - """ - i = j = 0 - missing = [] - unexpected = [] - while True: - try: - e = expected[i] - a = actual[j] - if e < a: - missing.append(e) - i += 1 - while expected[i] == e: - i += 1 - elif e > a: - unexpected.append(a) - j += 1 - while actual[j] == a: - j += 1 - else: - i += 1 - try: - while expected[i] == e: - i += 1 - finally: - j += 1 - while actual[j] == a: - j += 1 - except IndexError: - missing.extend(expected[i:]) - unexpected.extend(actual[j:]) - break - return missing, unexpected - -def unorderable_list_difference(expected, actual, ignore_duplicate=False): - """Same behavior as sorted_list_difference but - for lists of unorderable items (like dicts). - - As it does a linear search per item (remove) it - has O(n*n) performance. - """ - missing = [] - unexpected = [] - while expected: - item = expected.pop() - try: - actual.remove(item) - except ValueError: - missing.append(item) - if ignore_duplicate: - for lst in expected, actual: - try: - while True: - lst.remove(item) - except ValueError: - pass - if ignore_duplicate: - while actual: - item = actual.pop() - unexpected.append(item) - try: - while True: - actual.remove(item) - except ValueError: - pass - return missing, unexpected - - # anything left in actual is unexpected - return missing, actual diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index ee38df12a1839..7503506ecc1e6 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -407,6 +407,11 @@ these changes. * The ``Model._meta.get_(add|change|delete)_permission`` methods will be removed. +1.9 +--- + +* ``django.utils.unittest`` will be removed. + 2.0 --- diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt new file mode 100644 index 0000000000000..2707b2896e502 --- /dev/null +++ b/docs/releases/1.7.txt @@ -0,0 +1,41 @@ +============================================ +Django 1.7 release notes - UNDER DEVELOPMENT +============================================ + +Welcome to Django 1.7! + +These release notes cover the `new features`_, as well as some `backwards +incompatible changes`_ you'll want to be aware of when upgrading from Django +1.6 or older versions. We've also dropped some features, which are detailed in +:doc:`our deprecation plan `, and we've `begun the +deprecation process for some features`_. + +.. _`new features`: `What's new in Django 1.7`_ +.. _`backwards incompatible changes`: `Backwards incompatible changes in 1.7`_ +.. _`begun the deprecation process for some features`: `Features deprecated in 1.7`_ + +What's new in Django 1.7 +======================== + +Backwards incompatible changes in 1.7 +===================================== + +.. warning:: + + In addition to the changes outlined in this section, be sure to review the + :doc:`deprecation plan ` for any features that + have been removed. If you haven't updated your code within the + deprecation timeline for a given feature, its removal may appear as a + backwards incompatible change. + +Features deprecated in 1.7 +========================== + +``django.utils.unittest`` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +``django.utils.unittest`` provided uniform access to the ``unittest2`` library +on all Python versions. Since ``unittest2`` became the standard library's +:mod:`unittest` module in Python 2.7, and Django 1.7 drops support for older +Python versions, this module isn't useful anymore. It has been deprecated. Use +:mod:`unittest` instead. diff --git a/docs/topics/testing/_images/django_unittest_classes_hierarchy.graffle b/docs/topics/testing/_images/django_unittest_classes_hierarchy.graffle index 7211c0f3bee50..c0edad721d22f 100644 --- a/docs/topics/testing/_images/django_unittest_classes_hierarchy.graffle +++ b/docs/topics/testing/_images/django_unittest_classes_hierarchy.graffle @@ -7,14 +7,14 @@ ApplicationVersion com.omnigroup.OmniGrafflePro - 139.16.0.171715 + 139.15.0.171074 AutoAdjust BackgroundGraphic Bounds - {{0, 0}, {559.28997802734375, 782.8900146484375}} + {{0, 0}, {559, 783}} Class SolidGraphic ID @@ -96,53 +96,6 @@ 2 - - Class - LineGraphic - Head - - ID - 12 - Info - 1 - - ID - 27 - OrthogonalBarAutomatic - - OrthogonalBarPoint - {0, 0} - OrthogonalBarPosition - -1 - Points - - {135, 270} - {369, 225} - - Style - - stroke - - HeadArrow - UMLInheritance - HeadScale - 0.79999995231628418 - Legacy - - LineType - 2 - TailArrow - 0 - - - Tail - - ID - 26 - Position - 0.5 - - Class LineGraphic @@ -162,7 +115,7 @@ Points {135, 315} - {135, 225} + {135, 261} Style @@ -274,144 +227,7 @@ Bounds - {{378, 252}, {81, 27}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 12 - - ID - 22 - Shape - NoteShape - Style - - stroke - - Color - - b - 0 - g - 0.501961 - r - 0 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;\red0\green128\blue0;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\i\fs24 \cf2 Python < 2.7} - VerticalPad - 0 - - TextRelativeArea - {{0, 0}, {1, 1}} - - - Bounds - {{45, 252}, {81, 27}} - Class - ShapedGraphic - FontInfo - - Font - Helvetica - Size - 12 - - ID - 20 - Shape - NoteShape - Style - - stroke - - Color - - b - 0 - g - 0.501961 - r - 0 - - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;\red0\green128\blue0;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\i\fs24 \cf2 Python \uc0\u8805 2.7} - VerticalPad - 0 - - - - Bounds - {{288, 198}, {162, 27}} - Class - ShapedGraphic - ID - 12 - Magnets - - {0, 1} - {0, -1} - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - FillType - 2 - GradientAngle - 90 - GradientColor - - w - 0.666667 - - - stroke - - CornerRadius - 5 - - - Text - - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs24 \cf0 TestCase} - - - - Bounds - {{54, 198}, {162, 27}} + {{54, 234}, {162, 27}} Class ShapedGraphic ID @@ -448,7 +264,7 @@ Text Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc @@ -495,7 +311,7 @@ Text Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc @@ -542,7 +358,7 @@ Text Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc @@ -589,7 +405,7 @@ Text Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc @@ -636,7 +452,7 @@ Text Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc @@ -660,7 +476,7 @@ Align 2 Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr @@ -672,7 +488,7 @@ Bounds - {{18, 153}, {225, 90}} + {{18, 216}, {468, 63}} Class ShapedGraphic ID @@ -686,40 +502,16 @@ Align 2 Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier-Oblique;\f1\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr -\f0\fs24 \cf0 django.utils.unittest\ -= unittest (standard library)} - - TextPlacement - 0 - - - Bounds - {{261, 153}, {225, 90}} - Class - ShapedGraphic - ID - 19 - Shape - Rectangle - Style - - Text - - Align - 2 - Text - {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 -\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr +\f0\i\fs24 \cf0 standard library\ -\f0\fs24 \cf0 django.utils.unittest\ -= unittest2 (bundled copy)} +\f1\i0 \ +\ +unittest} TextPlacement 0 @@ -777,7 +569,7 @@ MasterSheets ModificationDate - 2012-12-16 19:08:28 +0000 + 2013-07-01 11:48:06 +0000 Modifier Aymeric Augustin NotesVisible @@ -808,7 +600,7 @@ NSPaperSize size - {595.28997802734375, 841.8900146484375} + {595, 842} NSPrintReverseOrientation @@ -853,7 +645,7 @@ ExpandedCanvases Frame - {{9, 4}, {694, 874}} + {{613, 284}, {694, 774}} ListView OutlineWidth @@ -867,7 +659,7 @@ SidebarWidth 120 VisibleRegion - {{0, 0}, {559, 735}} + {{0, 0}, {545, 620}} Zoom 1 ZoomValues diff --git a/docs/topics/testing/_images/django_unittest_classes_hierarchy.pdf b/docs/topics/testing/_images/django_unittest_classes_hierarchy.pdf index cedaba22ac7f5e26690d43ddf9aa081c2c28eedb..0835aec9ba8c5a18f8c711bd2334e53071a8464a 100644 GIT binary patch delta 17565 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 delta 27782 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 -2012-12-16 19:08ZCanevas 1Calque 1django.utils.unittest= unittest2 (bundled copy)django.utils.unittest= unittest (standard library)django.testLiveServerTestCaseTestCaseTransactionTestCaseSimpleTestCaseTestCaseTestCasePython ≥ 2.7Python < 2.7 +2013-07-01 11:48ZCanevas 1Calque 1standard libraryunittestdjango.testLiveServerTestCaseTestCaseTransactionTestCaseSimpleTestCaseTestCase diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt index d56b1be20f49c..9cf805e371205 100644 --- a/docs/topics/testing/overview.txt +++ b/docs/topics/testing/overview.txt @@ -21,27 +21,15 @@ module defines tests using a class-based approach. .. admonition:: unittest2 - Python 2.7 introduced some major changes to the ``unittest`` library, - adding some extremely useful features. To ensure that every Django - project can benefit from these new features, Django ships with a - copy of unittest2_, a copy of Python 2.7's ``unittest``, backported for - Python 2.6 compatibility. - - To access this library, Django provides the - ``django.utils.unittest`` module alias. If you are using Python - 2.7, or you have installed ``unittest2`` locally, Django will map the alias - to it. Otherwise, Django will use its own bundled version of ``unittest2``. - - To use this alias, simply use:: - - from django.utils import unittest + .. deprecated:: 1.7 - wherever you would have historically used:: - - import unittest + Python 2.7 introduced some major changes to the ``unittest`` library, + adding some extremely useful features. To ensure that every Django project + could benefit from these new features, Django used to ship with a copy of + Python 2.7's ``unittest`` backported for Python 2.6 compatibility. - If you want to continue to use the legacy ``unittest`` library, you can -- - you just won't get any of the nice new ``unittest2`` features. + Since Django no longer supports Python versions older than 2.7, + ``django.utils.unittest`` is deprecated. Simply use ``unittest``. .. _unittest2: http://pypi.python.org/pypi/unittest2 @@ -849,13 +837,10 @@ Normal Python unit test classes extend a base class of .. figure:: _images/django_unittest_classes_hierarchy.* :alt: Hierarchy of Django unit testing classes (TestCase subclasses) :width: 508 - :height: 391 + :height: 328 Hierarchy of Django unit testing classes -Regardless of the version of Python you're using, if you've installed -``unittest2``, ``django.utils.unittest`` will point to that library. - SimpleTestCase ~~~~~~~~~~~~~~ @@ -905,7 +890,7 @@ features like: then you should use :class:`~django.test.TransactionTestCase` or :class:`~django.test.TestCase` instead. -``SimpleTestCase`` inherits from ``django.utils.unittest.TestCase``. +``SimpleTestCase`` inherits from ``unittest.TestCase``. TransactionTestCase ~~~~~~~~~~~~~~~~~~~