################################################################################
if __name__.startswith('pygame.tests.'):
from pygame.tests.test_utils import unittest,import_submodule
else:
from test.test_utils import unittest,import_submodule
import re
import time
import sys
try:
import StringIO
except ImportError:
import io as StringIO
import random
from inspect import getdoc
# This is needed for correct tracebacks
__unittest = 1
################################################################################
# Redirect stdout / stderr for the tests
def redirect_output():
yield sys.stderr, sys.stdout
sys.stderr, sys.stdout = StringIO.StringIO(), StringIO.StringIO()
yield sys.stderr, sys.stdout
def restore_output(err, out):
sys.stderr, sys.stdout = err, out
def StringIOContents(io):
io.seek(0)
return io.read()
################################################################################
# TestCase patching
#
def TestCase_run(self, result=None):
if result is None: result = self.defaultTestResult()
result.startTest(self)
testMethod = getattr(self, self._testMethodName)
try:
########################################################################
# Pre run:
#TODO: only redirect output if not tagged interactive
result.tests[self.dot_syntax_name()] = {
'times' : [],
}
tests = result.tests[self.dot_syntax_name()]
(realerr, realout), (stderr, stdout) = redirect_output()
test_tags = list(get_tags(self.__class__, testMethod))
if 0 or 'interactive' in test_tags: # DEBUG
restore_output(realerr, realout)
########################################################################
for i in range(self.times_run):
t = time.time()
try:
self.setUp()
except KeyboardInterrupt:
raise
except:
result.addError(self, self._exc_info())
return
ok = False
try:
testMethod()
ok = True
except self.failureException:
result.addFailure(self, self._exc_info())
except KeyboardInterrupt:
raise
except:
result.addError(self, self._exc_info())
try:
self.tearDown()
except KeyboardInterrupt:
raise
except:
result.addError(self, self._exc_info())
ok = False
tests["times"] += [time.time() -t]
if not ok: break
# if ok:
# if i == 0:
# result.addSuccess(self)
# else: break
if ok:
result.addSuccess(self)
########################################################################
# Post run
restore_output(realerr, realout)
tests["stdout"] = StringIOContents(stdout)
tests["stderr"] = StringIOContents(stderr)
tests["tags"] = test_tags
########################################################################
finally:
result.stopTest(self)
################################################################################
# TestResult
#
def TestResult___init__(self):
self.failures = []
self.errors = []
self.tests = {}
self.testsRun = 0
self.shouldStop = 0
# TODO: all this is available in the traceback object err
FILE_LINENUMBER_RE = re.compile(r'File "([^"]+)", line ([0-9]+)')
def errorHandling(key):
def handling(self, test, err):
traceback = self._exc_info_to_string(err, test)
error_file, line_number = FILE_LINENUMBER_RE.search(traceback).groups()
error = (
test.dot_syntax_name(),
traceback,
error_file,
line_number, #TODO: add locals etc
)
getattr(self, key).append(error)
# Append it to individual test dict for easy access
self.tests[test.dot_syntax_name()][key[:-1]] = error
return handling
################################################################################
def printErrorList(self, flavour, errors):
for test, err in [(e[0], e[1]) for e in errors]:
self.stream.writeln(self.separator1)
self.stream.writeln("%s: %s" % (flavour, test))
self.stream.writeln(self.separator2)
self.stream.writeln("%s" % err)
# DUMP REDIRECTED STDERR / STDOUT ON ERROR / FAILURE
if self.show_redirected_on_errors:
stderr, stdout = map(self.tests[test].get, ('stderr','stdout'))
if stderr: self.stream.writeln("STDERR:\n%s" % stderr)
if stdout: self.stream.writeln("STDOUT:\n%s" % stdout)
################################################################################
# Exclude by tags
#
TAGS_RE = re.compile(r"\|[tT]ags:(-?[ a-zA-Z,0-9_\n]+)\|", re.M)
class TestTags:
def __init__(self):
self.memoized = {}
self.parent_modules = {}
def get_parent_module(self, class_):
if class_ not in self.parent_modules:
self.parent_modules[class_] = import_submodule(class_.__module__)
return self.parent_modules[class_]
def __call__(self, parent_class, meth):
key = (parent_class, meth.__name__)
if key not in self.memoized:
parent_module = self.get_parent_module(parent_class)
module_tags = getattr(parent_module, '__tags__', [])
class_tags = getattr(parent_class, '__tags__', [])
tags = TAGS_RE.search(getdoc(meth) or '')
if tags: test_tags = [t.strip() for t in tags.group(1).split(',')]
else: test_tags = []
combined = set()
for tags in (module_tags, class_tags, test_tags):
if not tags: continue
add = set([t for t in tags if not t.startswith('-')])
remove = set([t[1:] for t in tags if t not in add])
if add: combined.update(add)
if remove: combined.difference_update(remove)
self.memoized[key] = combined
return self.memoized[key]
get_tags = TestTags()
################################################################################
# unittest.TestLoader
#
def CmpToKey(mycmp):
'Convert a cmp= function into a key= function'
class K(object):
def __init__(self, obj, *args):
self.obj = obj
def __lt__(self, other):
return mycmp(self.obj, other.obj) == -1
return K
def getTestCaseNames(self, testCaseClass):
def test_wanted(attrname, testCaseClass=testCaseClass,
prefix=self.testMethodPrefix):
if not attrname.startswith(prefix): return False
else:
actual_attr = getattr(testCaseClass, attrname)
return (
hasattr(actual_attr, '__call__') and
not [t for t in get_tags(testCaseClass, actual_attr)
if t in self.exclude]
)
# TODO:
# Replace test_not_implemented mechanism with technique that names the tests
# todo_test_xxxxxx, then when wanting to fail them, loads any members that
# startswith(test_prefix)
# REGEX FOR TEST_NOT_IMPLEMENTED
# SEARCH:
# def (test_[^ ]+)((?:\s+#.*\n?)+\s+)self\.assert_\(test_not_implemented\(\)\)
# REPLACE:
# def todo_\1\2self.fail()
testFnNames = [c for c in dir(testCaseClass) if test_wanted(c)]
for baseclass in testCaseClass.__bases__:
for testFnName in self.getTestCaseNames(baseclass):
if testFnName not in testFnNames: # handle overridden methods
testFnNames.append(testFnName)
if self.randomize_tests:
random.shuffle(testFnNames)
elif self.sortTestMethodsUsing:
testFnNames.sort(key=CmpToKey(self.sortTestMethodsUsing))
return testFnNames
################################################################################
def patch(**kwds):
"""Customize the unittest module according to the run-time options
Recognized keyword arguments:
incomplete, randomize, seed, exclude, timings and show_output
"""
option_incomplete = kwds.get('incomplete', False)
option_randomize = kwds.get('randomize', False)
try:
option_seed = kwds['seed'] is not None
except KeyError:
option_seed = False
option_exclude = kwds.get('exclude', ('interactive',))
option_timings = kwds.get('timings', 1)
option_show_output = kwds.get('show_output', False)
# Incomplete todo_xxx tests
if option_incomplete:
unittest.TestLoader.testMethodPrefix = (
unittest.TestLoader.testMethodPrefix, 'todo_'
)
# Randomizing
unittest.TestLoader.randomize_tests = option_randomize or option_seed
unittest.TestLoader.getTestCaseNames = getTestCaseNames
unittest.TestLoader.exclude = option_exclude
# Timing
unittest.TestCase.times_run = option_timings
unittest.TestCase.run = TestCase_run
unittest.TestCase.dot_syntax_name = lambda self: (
"%s.%s"% (self.__class__.__name__, self._testMethodName) )
# Error logging
unittest.TestResult.show_redirected_on_errors = option_show_output
unittest.TestResult.__init__ = TestResult___init__
unittest.TestResult.addError = errorHandling('errors')
unittest.TestResult.addFailure = errorHandling('failures')
unittest._TextTestResult.printErrorList = printErrorList
################################################################################
|