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