blob: 052e968eabababadc8fe0d1d03f3696a192e5b8c [file] [log] [blame]
"""
Test Suites
"""
from __future__ import generators
import sys
import unittest
from nose_helper.case import Test
from nose_helper.config import Config
from nose_helper.util import isclass, resolve_name, try_run
PYTHON_VERSION_MAJOR = sys.version_info[0]
class LazySuite(unittest.TestSuite):
"""A suite that may use a generator as its list of tests
"""
def __init__(self, tests=()):
self._set_tests(tests)
def __iter__(self):
return iter(self._tests)
def __hash__(self):
return object.__hash__(self)
def addTest(self, test):
self._precache.append(test)
def __nonzero__(self):
if self._precache:
return True
if self.test_generator is None:
return False
try:
test = self.test_generator.next()
if test is not None:
self._precache.append(test)
return True
except StopIteration:
pass
return False
def _get_tests(self):
if self.test_generator is not None:
for i in self.test_generator:
yield i
for test in self._precache:
yield test
def _set_tests(self, tests):
self._precache = []
is_suite = isinstance(tests, unittest.TestSuite)
if hasattr(tests, '__call__') and not is_suite:
self.test_generator = tests()
self.test_generator_counter = list(tests())
elif is_suite:
self.addTests([tests])
self.test_generator = None
self.test_generator_counter = None
else:
self.addTests(tests)
self.test_generator = None
self.test_generator_counter = None
def countTestCases(self):
counter = 0
generator = self.test_generator_counter
if generator is not None:
for test in generator:
counter +=1
for test in self._precache:
counter += test.countTestCases()
return counter
_tests = property(_get_tests, _set_tests, None,
"Access the tests in this suite.")
class ContextSuite(LazySuite):
"""A suite with context.
"""
was_setup = False
was_torndown = False
classSetup = ('setup_class', 'setup_all', 'setupClass', 'setupAll',
'setUpClass', 'setUpAll')
classTeardown = ('teardown_class', 'teardown_all', 'teardownClass',
'teardownAll', 'tearDownClass', 'tearDownAll')
moduleSetup = ('setup_module', 'setupModule', 'setUpModule', 'setup',
'setUp')
moduleTeardown = ('teardown_module', 'teardownModule', 'tearDownModule',
'teardown', 'tearDown')
packageSetup = ('setup_package', 'setupPackage', 'setUpPackage')
packageTeardown = ('teardown_package', 'teardownPackage',
'tearDownPackage')
def __init__(self, tests=(), context=None, factory=None,
config=None):
self.context = context
self.factory = factory
if config is None:
config = Config()
self.config = config
self.has_run = False
self.error_context = None
LazySuite.__init__(self, tests)
def __hash__(self):
return object.__hash__(self)
def __call__(self, *arg, **kw):
return self.run(*arg, **kw)
def _exc_info(self):
return sys.exc_info()
def addTests(self, tests, context=None):
if context:
self.context = context
if PYTHON_VERSION_MAJOR < 3 and isinstance(tests, basestring):
raise TypeError("tests must be an iterable of tests, not a string")
else:
if isinstance(tests, str):
raise TypeError("tests must be an iterable of tests, not a string")
for test in tests:
self.addTest(test)
def run(self, result):
"""Run tests in suite inside of suite fixtures.
"""
result, orig = result, result
try:
self.setUp()
except KeyboardInterrupt:
raise
except:
self.error_context = 'setup'
result.addError(self, self._exc_info())
return
try:
for test in self._tests:
if result.shouldStop:
break
test(orig)
finally:
self.has_run = True
try:
self.tearDown()
except KeyboardInterrupt:
raise
except:
self.error_context = 'teardown'
result.addError(self, self._exc_info())
def setUp(self):
if not self:
return
if self.was_setup:
return
context = self.context
if context is None:
return
factory = self.factory
if factory:
ancestors = factory.context.get(self, [])[:]
while ancestors:
ancestor = ancestors.pop()
if ancestor in factory.was_setup:
continue
self.setupContext(ancestor)
if not context in factory.was_setup:
self.setupContext(context)
else:
self.setupContext(context)
self.was_setup = True
def setupContext(self, context):
if self.factory:
if context in self.factory.was_setup:
return
self.factory.was_setup[context] = self
if isclass(context):
names = self.classSetup
else:
names = self.moduleSetup
if hasattr(context, '__path__'):
names = self.packageSetup + names
try_run(context, names)
def tearDown(self):
if not self.was_setup or self.was_torndown:
return
self.was_torndown = True
context = self.context
if context is None:
return
factory = self.factory
if factory:
ancestors = factory.context.get(self, []) + [context]
for ancestor in ancestors:
if not ancestor in factory.was_setup:
continue
if ancestor in factory.was_torndown:
continue
setup = factory.was_setup[ancestor]
if setup is self:
self.teardownContext(ancestor)
else:
self.teardownContext(context)
def teardownContext(self, context):
if self.factory:
if context in self.factory.was_torndown:
return
self.factory.was_torndown[context] = self
if isclass(context):
names = self.classTeardown
else:
names = self.moduleTeardown
if hasattr(context, '__path__'):
names = self.packageTeardown + names
try_run(context, names)
def _get_wrapped_tests(self):
for test in self._get_tests():
if isinstance(test, Test) or isinstance(test, unittest.TestSuite):
yield test
else:
yield Test(test,
config=self.config)
_tests = property(_get_wrapped_tests, LazySuite._set_tests, None,
"Access the tests in this suite. Tests are returned "
"inside of a context wrapper.")
class ContextSuiteFactory(object):
suiteClass = ContextSuite
def __init__(self, config=None):
if config is None:
config = Config()
self.config = config
self.suites = {}
self.context = {}
self.was_setup = {}
self.was_torndown = {}
def __call__(self, tests, **kw):
"""Return 'ContextSuite' for tests.
"""
context = kw.pop('context', getattr(tests, 'context', None))
if context is None:
tests = self.wrapTests(tests)
context = self.findContext(tests)
return self.makeSuite(tests, context, **kw)
def ancestry(self, context):
"""Return the ancestry of the context
"""
if context is None:
return
if hasattr(context, 'im_class'):
context = context.im_class
if hasattr(context, '__module__'):
ancestors = context.__module__.split('.')
elif hasattr(context, '__name__'):
ancestors = context.__name__.split('.')[:-1]
else:
raise TypeError("%s has no ancestors?" % context)
while ancestors:
yield resolve_name('.'.join(ancestors))
ancestors.pop()
def findContext(self, tests):
if hasattr(tests, '__call__') or isinstance(tests, unittest.TestSuite):
return None
context = None
for test in tests:
# Don't look at suites for contexts, only tests
ctx = getattr(test, 'context', None)
if ctx is None:
continue
if context is None:
context = ctx
return context
def makeSuite(self, tests, context, **kw):
suite = self.suiteClass(
tests, context=context, config=self.config, factory=self, **kw)
if context is not None:
self.suites.setdefault(context, []).append(suite)
self.context.setdefault(suite, []).append(context)
for ancestor in self.ancestry(context):
self.suites.setdefault(ancestor, []).append(suite)
self.context[suite].append(ancestor)
return suite
def wrapTests(self, tests):
if hasattr(tests, '__call__') or isinstance(tests, unittest.TestSuite):
return tests
wrapped = []
for test in tests:
if isinstance(test, Test) or isinstance(test, unittest.TestSuite):
wrapped.append(test)
elif isinstance(test, ContextList):
wrapped.append(self.makeSuite(test, context=test.context))
else:
wrapped.append(
Test(test, config=self.config)
)
return wrapped
class ContextList(object):
"""a group of tests in a context.
"""
def __init__(self, tests, context=None):
self.tests = tests
self.context = context
def __iter__(self):
return iter(self.tests)