blob: cccc7efda462600cdbcdd9ab4f387bec0e6a6ab6 [file] [log] [blame]
Benjamin Petersond7b0eeb2009-07-19 20:18:21 +00001"""TestSuite"""
2
Michael Foord5ffa3252010-03-07 22:04:55 +00003import sys
4
Benjamin Petersond7b0eeb2009-07-19 20:18:21 +00005from . import case
Michael Foorde91ea562009-09-13 19:07:03 +00006from . import util
Benjamin Petersond7b0eeb2009-07-19 20:18:21 +00007
8
Michael Foord5ffa3252010-03-07 22:04:55 +00009class BaseTestSuite(object):
10 """A simple test suite that doesn't provide class or module shared fixtures.
Benjamin Petersond7b0eeb2009-07-19 20:18:21 +000011 """
12 def __init__(self, tests=()):
13 self._tests = []
14 self.addTests(tests)
15
16 def __repr__(self):
Michael Foorde91ea562009-09-13 19:07:03 +000017 return "<%s tests=%s>" % (util.strclass(self.__class__), list(self))
Benjamin Petersond7b0eeb2009-07-19 20:18:21 +000018
19 def __eq__(self, other):
20 if not isinstance(other, self.__class__):
21 return NotImplemented
22 return list(self) == list(other)
23
24 def __ne__(self, other):
25 return not self == other
26
27 # Can't guarantee hash invariant, so flag as unhashable
28 __hash__ = None
29
30 def __iter__(self):
31 return iter(self._tests)
32
33 def countTestCases(self):
34 cases = 0
35 for test in self:
36 cases += test.countTestCases()
37 return cases
38
39 def addTest(self, test):
40 # sanity checks
41 if not hasattr(test, '__call__'):
R. David Murrayb5d74002010-01-28 21:16:33 +000042 raise TypeError("{} is not callable".format(repr(test)))
Benjamin Petersond7b0eeb2009-07-19 20:18:21 +000043 if isinstance(test, type) and issubclass(test,
44 (case.TestCase, TestSuite)):
45 raise TypeError("TestCases and TestSuites must be instantiated "
46 "before passing them to addTest()")
47 self._tests.append(test)
48
49 def addTests(self, tests):
50 if isinstance(tests, basestring):
51 raise TypeError("tests must be an iterable of tests, not a string")
52 for test in tests:
53 self.addTest(test)
54
55 def run(self, result):
56 for test in self:
57 if result.shouldStop:
58 break
59 test(result)
60 return result
61
62 def __call__(self, *args, **kwds):
63 return self.run(*args, **kwds)
64
65 def debug(self):
66 """Run the tests without collecting errors in a TestResult"""
67 for test in self:
68 test.debug()
Michael Foord5ffa3252010-03-07 22:04:55 +000069
70
71class TestSuite(BaseTestSuite):
72 """A test suite is a composite test consisting of a number of TestCases.
73
74 For use, create an instance of TestSuite, then add test case instances.
75 When all tests have been added, the suite can be passed to a test
76 runner, such as TextTestRunner. It will run the individual test cases
77 in the order in which they were added, aggregating the results. When
78 subclassing, do not forget to call the base class constructor.
79 """
80
81
82 def run(self, result):
83 self._wrapped_run(result)
84 self._tearDownPreviousClass(None, result)
85 self._handleModuleTearDown(result)
86 return result
87
88 ################################
89 # private methods
90 def _wrapped_run(self, result):
91 for test in self:
92 if result.shouldStop:
93 break
94
95 if _isnotsuite(test):
96 self._tearDownPreviousClass(test, result)
97 self._handleModuleFixture(test, result)
98 self._handleClassSetUp(test, result)
99 result._previousTestClass = test.__class__
100
101 if (getattr(test.__class__, '_classSetupFailed', False) or
102 getattr(result, '_moduleSetUpFailed', False)):
103 continue
104
105 if hasattr(test, '_wrapped_run'):
106 test._wrapped_run(result)
107 else:
108 test(result)
109
110 def _handleClassSetUp(self, test, result):
111 previousClass = getattr(result, '_previousTestClass', None)
112 currentClass = test.__class__
113 if currentClass == previousClass:
114 return
115 if result._moduleSetUpFailed:
116 return
117 if getattr(currentClass, "__unittest_skip__", False):
118 return
119
120 currentClass._classSetupFailed = False
121
122 setUpClass = getattr(currentClass, 'setUpClass', None)
123 if setUpClass is not None:
124 try:
125 setUpClass()
126 except:
127 currentClass._classSetupFailed = True
128 self._addClassSetUpError(result, currentClass)
129
130 def _get_previous_module(self, result):
131 previousModule = None
132 previousClass = getattr(result, '_previousTestClass', None)
133 if previousClass is not None:
134 previousModule = previousClass.__module__
135 return previousModule
136
137
138 def _handleModuleFixture(self, test, result):
139 previousModule = self._get_previous_module(result)
140 currentModule = test.__class__.__module__
141 if currentModule == previousModule:
142 return
143
144 self._handleModuleTearDown(result)
145
146
147 result._moduleSetUpFailed = False
148 try:
149 module = sys.modules[currentModule]
150 except KeyError:
151 return
152 setUpModule = getattr(module, 'setUpModule', None)
153 if setUpModule is not None:
154 try:
155 setUpModule()
156 except:
157 result._moduleSetUpFailed = True
158 error = _ErrorHolder('setUpModule (%s)' % currentModule)
159 result.addError(error, sys.exc_info())
160
161 def _handleModuleTearDown(self, result):
162 previousModule = self._get_previous_module(result)
163 if previousModule is None:
164 return
165 if result._moduleSetUpFailed:
166 return
167
168 try:
169 module = sys.modules[previousModule]
170 except KeyError:
171 return
172
173 tearDownModule = getattr(module, 'tearDownModule', None)
174 if tearDownModule is not None:
175 try:
176 tearDownModule()
177 except:
178 error = _ErrorHolder('tearDownModule (%s)' % previousModule)
179 result.addError(error, sys.exc_info())
180
181 def _tearDownPreviousClass(self, test, result):
182 previousClass = getattr(result, '_previousTestClass', None)
183 currentClass = test.__class__
184 if currentClass == previousClass:
185 return
186 if getattr(previousClass, '_classSetupFailed', False):
187 return
188 if getattr(result, '_moduleSetUpFailed', False):
189 return
190 if getattr(previousClass, "__unittest_skip__", False):
191 return
192
193 tearDownClass = getattr(previousClass, 'tearDownClass', None)
194 if tearDownClass is not None:
195 try:
196 tearDownClass()
197 except:
198 self._addClassTearDownError(result)
199
200 def _addClassTearDownError(self, result):
201 className = util.strclass(result._previousTestClass)
202 error = _ErrorHolder('classTearDown (%s)' % className)
203 result.addError(error, sys.exc_info())
204
205 def _addClassSetUpError(self, result, klass):
206 className = util.strclass(klass)
207 error = _ErrorHolder('classSetUp (%s)' % className)
208 result.addError(error, sys.exc_info())
209
210
211class _ErrorHolder(object):
212 """
213 Placeholder for a TestCase inside a result. As far as a TestResult
214 is concerned, this looks exactly like a unit test. Used to insert
215 arbitrary errors into a test suite run.
216 """
217 # Inspired by the ErrorHolder from Twisted:
218 # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py
219
220 # attribute used by TestResult._exc_info_to_string
221 failureException = None
222
223 def __init__(self, description):
224 self.description = description
225
226 def id(self):
227 return self.description
228
229 def shortDescription(self):
230 return None
231
232 def __repr__(self):
233 return "<ErrorHolder description=%r>" % (self.description,)
234
235 def __str__(self):
236 return self.id()
237
238 def run(self, result):
239 # could call result.addError(...) - but this test-like object
240 # shouldn't be run anyway
241 pass
242
243 def __call__(self, result):
244 return self.run(result)
245
246 def countTestCases(self):
247 return 0
248
249def _isnotsuite(test):
250 "A crude way to tell apart testcases and suites with duck-typing"
251 try:
252 iter(test)
253 except TypeError:
254 return True
255 return False