1
# Copyright (C) 2005-2011 Canonical Ltd
2
# Author: Robert Collins <robert.collins@canonical.com>
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24
from .. import pyutils
26
# Mark this python module as being part of the implementation
27
# of unittest: this gives us better tracebacks where the last
28
# shown frame is the test code, not our assertXYZ.
32
class LogCollector(logging.Handler):
35
logging.Handler.__init__(self)
38
def emit(self, record):
39
self.records.append(record.getMessage())
42
def makeCollectingLogger():
43
"""I make a logger instance that collects its logs for programmatic analysis
44
-> (logger, collector)"""
45
logger=logging.Logger("collector")
46
handler=LogCollector()
47
handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
48
logger.addHandler(handler)
49
return logger, handler
52
def visitTests(suite, visitor):
53
"""A foreign method for visiting the tests in a test suite."""
54
for test in suite._tests:
55
#Abusing types to avoid monkey patching unittest.TestCase.
56
# Maybe that would be better?
59
except AttributeError:
60
if isinstance(test, unittest.TestCase):
61
visitor.visitCase(test)
62
elif isinstance(test, unittest.TestSuite):
63
visitor.visitSuite(test)
64
visitTests(test, visitor)
66
print("unvisitable non-unittest.TestCase element %r (%r)" % (
67
test, test.__class__))
70
class FailedCollectionCase(unittest.TestCase):
71
"""Pseudo-test to run and report failure if given case was uncollected"""
73
def __init__(self, case):
74
super(FailedCollectionCase, self).__init__("fail_uncollected")
75
# GZ 2011-09-16: Maybe catch errors from id() method as cases may be
76
# in a bit of a funny state by now.
77
self._problem_case_id = case.id()
80
if self._problem_case_id[-1:] == ")":
81
return self._problem_case_id[:-1] + ",uncollected)"
82
return self._problem_case_id + "(uncollected)"
84
def fail_uncollected(self):
85
self.fail("Uncollected test case: " + self._problem_case_id)
88
class TestSuite(unittest.TestSuite):
89
"""I am an extended TestSuite with a visitor interface.
90
This is primarily to allow filtering of tests - and suites or
91
more in the future. An iterator of just tests wouldn't scale..."""
93
def visit(self, visitor):
94
"""visit the composite. Visiting is depth-first.
95
current callbacks are visitSuite and visitCase."""
96
visitor.visitSuite(self)
97
visitTests(self, visitor)
99
def run(self, result):
100
"""Run the tests in the suite, discarding references after running."""
105
count_stored_tests = getattr(result, "_count_stored_tests", int)
106
from breezy.tests import selftest_debug_flags
107
notify = "uncollected_cases" in selftest_debug_flags
109
if result.shouldStop:
110
self._tests = reversed(tests)
112
case = _run_and_collect_case(tests.pop(), result)()
113
new_stored_count = count_stored_tests()
114
if case is not None and isinstance(case, unittest.TestCase):
115
if stored_count == new_stored_count and notify:
116
# Testcase didn't fail, but somehow is still alive
117
FailedCollectionCase(case).run(result)
118
# Adding a new failure so need to reupdate the count
119
new_stored_count = count_stored_tests()
120
# GZ 2011-09-16: Previously zombied the case at this point by
121
# clearing the dict as fallback, skip for now.
122
stored_count = new_stored_count
126
def _run_and_collect_case(case, res):
127
"""Run test case against result and use weakref to drop the refcount"""
129
return weakref.ref(case)
132
class TestLoader(unittest.TestLoader):
133
"""Custom TestLoader to extend the stock python one."""
135
suiteClass = TestSuite
136
# Memoize test names by test class dict
139
def loadTestsFromModuleNames(self, names):
140
"""use a custom means to load tests from modules.
142
There is an undesirable glitch in the python TestLoader where a
143
import error is ignore. We think this can be solved by ensuring the
144
requested name is resolvable, if its not raising the original error.
146
result = self.suiteClass()
148
result.addTests(self.loadTestsFromModuleName(name))
151
def loadTestsFromModuleName(self, name):
152
result = self.suiteClass()
153
module = pyutils.get_named_object(name)
155
result.addTests(self.loadTestsFromModule(module))
158
def getTestCaseNames(self, test_case_class):
159
test_fn_names = self.test_func_names.get(test_case_class, None)
160
if test_fn_names is not None:
161
# We already know them
164
test_fn_names = unittest.TestLoader.getTestCaseNames(self,
166
self.test_func_names[test_case_class] = test_fn_names
170
class FilteredByModuleTestLoader(TestLoader):
171
"""A test loader that import only the needed modules."""
173
def __init__(self, needs_module):
176
:param needs_module: a callable taking a module name as a
177
parameter returing True if the module should be loaded.
179
TestLoader.__init__(self)
180
self.needs_module = needs_module
182
def loadTestsFromModuleName(self, name):
183
if self.needs_module(name):
184
return TestLoader.loadTestsFromModuleName(self, name)
186
return self.suiteClass()
189
class TestVisitor(object):
190
"""A visitor for Tests"""
192
def visitSuite(self, aTestSuite):
195
def visitCase(self, aTestCase):