mirror of
https://github.com/django/django.git
synced 2025-07-05 10:19:20 +00:00
[soc2010/test-refactor] Unittest2 integration properly done. unittest2 package (at rev 221) is bundled as django.utils.unittest and the imports are patched to reflect django usage. Tests should import django.utils.unittest rather than the stdlib package. "It's a beautiful thing, the destruction of doctests."
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2010/test-refactor@13410 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
154808166a
commit
86580d042e
@ -1,12 +1,12 @@
|
|||||||
import sys
|
import sys
|
||||||
import signal
|
import signal
|
||||||
import unittest
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import get_app, get_apps
|
from django.db.models import get_app, get_apps
|
||||||
from django.test import _doctest as doctest
|
from django.test import _doctest as doctest
|
||||||
from django.test.utils import setup_test_environment, teardown_test_environment
|
from django.test.utils import setup_test_environment, teardown_test_environment
|
||||||
from django.test.testcases import OutputChecker, DocTestRunner, TestCase
|
from django.test.testcases import OutputChecker, DocTestRunner, TestCase
|
||||||
|
from django.utils import unittest
|
||||||
|
|
||||||
# The module name for tests outside models.py
|
# The module name for tests outside models.py
|
||||||
TEST_MODULE = 'tests'
|
TEST_MODULE = 'tests'
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import re
|
import re
|
||||||
import unittest2 as unittest
|
|
||||||
from urlparse import urlsplit, urlunsplit
|
from urlparse import urlsplit, urlunsplit
|
||||||
from xml.dom.minidom import parseString, Node
|
from xml.dom.minidom import parseString, Node
|
||||||
|
|
||||||
|
#from django.utils import unittest
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
@ -11,7 +12,7 @@ from django.db import transaction, connections, DEFAULT_DB_ALIAS
|
|||||||
from django.http import QueryDict
|
from django.http import QueryDict
|
||||||
from django.test import _doctest as doctest
|
from django.test import _doctest as doctest
|
||||||
from django.test.client import Client
|
from django.test.client import Client
|
||||||
from django.utils import simplejson
|
from django.utils import simplejson, unittest
|
||||||
from django.utils.encoding import smart_str
|
from django.utils.encoding import smart_str
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
14
django/utils/unittest/__init__.py
Normal file
14
django/utils/unittest/__init__.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
#Django hackery to load the appropriate version of unittest
|
||||||
|
|
||||||
|
if sys.version_info >= (2,7):
|
||||||
|
#unittest2 features are native in Python 2.7
|
||||||
|
from unittest import *
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
#check the system path first
|
||||||
|
from unittest2 import *
|
||||||
|
except ImportError:
|
||||||
|
#otherwise fall back to our bundled version
|
||||||
|
from unittest2_package_init import *
|
10
django/utils/unittest/__main__.py
Normal file
10
django/utils/unittest/__main__.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
"""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_()
|
1095
django/utils/unittest/case.py
Normal file
1095
django/utils/unittest/case.py
Normal file
File diff suppressed because it is too large
Load Diff
9
django/utils/unittest/collector.py
Normal file
9
django/utils/unittest/collector.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
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)
|
64
django/utils/unittest/compatibility.py
Normal file
64
django/utils/unittest/compatibility.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
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
|
323
django/utils/unittest/loader.py
Normal file
323
django/utils/unittest/loader.py
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
"""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, 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, case.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))
|
||||||
|
testFnNames.sort(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, 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)
|
241
django/utils/unittest/main.py
Normal file
241
django/utils/unittest/main.py
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
"""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, 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)
|
||||||
|
|
184
django/utils/unittest/result.py
Normal file
184
django/utils/unittest/result.py
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
"""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):
|
||||||
|
def inner(self, *args, **kw):
|
||||||
|
if getattr(self, 'failfast', False):
|
||||||
|
self.stop()
|
||||||
|
return method(self, *args, **kw)
|
||||||
|
inner = wraps(method)(inner)
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
addError = failfast(addError)
|
||||||
|
|
||||||
|
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
|
||||||
|
addFailure = failfast(addFailure)
|
||||||
|
|
||||||
|
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)))
|
||||||
|
|
||||||
|
def addUnexpectedSuccess(self, test):
|
||||||
|
"""Called when a test was expected to fail, but succeed."""
|
||||||
|
self.unexpectedSuccesses.append(test)
|
||||||
|
addUnexpectedSuccess = failfast(addUnexpectedSuccess)
|
||||||
|
|
||||||
|
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))
|
206
django/utils/unittest/runner.py
Normal file
206
django/utils/unittest/runner.py
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
"""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
|
57
django/utils/unittest/signals.py
Normal file
57
django/utils/unittest/signals.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
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:
|
||||||
|
def inner(*args, **kwargs):
|
||||||
|
initial = signal.getsignal(signal.SIGINT)
|
||||||
|
removeHandler()
|
||||||
|
try:
|
||||||
|
return method(*args, **kwargs)
|
||||||
|
finally:
|
||||||
|
signal.signal(signal.SIGINT, initial)
|
||||||
|
inner = wraps(method)(inner)
|
||||||
|
return inner
|
||||||
|
|
||||||
|
global _interrupt_handler
|
||||||
|
if _interrupt_handler is not None:
|
||||||
|
signal.signal(signal.SIGINT, _interrupt_handler.default_handler)
|
287
django/utils/unittest/suite.py
Normal file
287
django/utils/unittest/suite.py
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
"""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, 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, 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, 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, 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 "<ErrorHolder description=%r>" % (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
|
67
django/utils/unittest/unittest2_package_init.py
Normal file
67
django/utils/unittest/unittest2_package_init.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
"""
|
||||||
|
unittest2
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__all__ = ['TestResult', 'TestCase', 'TestSuite',
|
||||||
|
'TextTestRunner', 'TestLoader', 'FunctionTestCase', 'main',
|
||||||
|
'defaultTestLoader', 'SkipTest', 'skip', 'skipIf', 'skipUnless',
|
||||||
|
'expectedFailure', 'TextTestResult', '__version__', 'collector']
|
||||||
|
|
||||||
|
__version__ = '0.5.0 alpha'
|
||||||
|
|
||||||
|
# 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
|
99
django/utils/unittest/util.py
Normal file
99
django/utils/unittest/util.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
"""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
|
Loading…
x
Reference in New Issue
Block a user