blob: cf6d94a4026b54c8b9b2934aa99c69d5c65416ef [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()
133 except:
134 currentClass._classSetupFailed = True
135 self._addClassSetUpError(result, currentClass)
136
137 def _get_previous_module(self, result):
138 previousModule = None
139 previousClass = getattr(result, '_previousTestClass', None)
140 if previousClass is not None:
141 previousModule = previousClass.__module__
142 return previousModule
143
144
145 def _handleModuleFixture(self, test, result):
146 previousModule = self._get_previous_module(result)
147 currentModule = test.__class__.__module__
148 if currentModule == previousModule:
149 return
150
151 self._handleModuleTearDown(result)
152
153
154 result._moduleSetUpFailed = False
155 try:
156 module = sys.modules[currentModule]
157 except KeyError:
158 return
159 setUpModule = getattr(module, 'setUpModule', None)
160 if setUpModule is not None:
161 try:
162 setUpModule()
163 except:
164 result._moduleSetUpFailed = True
165 error = _ErrorHolder('setUpModule (%s)' % currentModule)
166 result.addError(error, sys.exc_info())
167
168 def _handleModuleTearDown(self, result):
169 previousModule = self._get_previous_module(result)
170 if previousModule is None:
171 return
172 if result._moduleSetUpFailed:
173 return
174
175 try:
176 module = sys.modules[previousModule]
177 except KeyError:
178 return
179
180 tearDownModule = getattr(module, 'tearDownModule', None)
181 if tearDownModule is not None:
182 try:
183 tearDownModule()
184 except:
185 error = _ErrorHolder('tearDownModule (%s)' % previousModule)
186 result.addError(error, sys.exc_info())
187
188 def _tearDownPreviousClass(self, test, result):
189 previousClass = getattr(result, '_previousTestClass', None)
190 currentClass = test.__class__
191 if currentClass == previousClass:
192 return
193 if getattr(previousClass, '_classSetupFailed', False):
194 return
195 if getattr(result, '_moduleSetUpFailed', False):
196 return
197 if getattr(previousClass, "__unittest_skip__", False):
198 return
199
200 tearDownClass = getattr(previousClass, 'tearDownClass', None)
201 if tearDownClass is not None:
202 try:
203 tearDownClass()
204 except:
205 self._addClassTearDownError(result)
206
207 def _addClassTearDownError(self, result):
208 className = util.strclass(result._previousTestClass)
209 error = _ErrorHolder('classTearDown (%s)' % className)
210 result.addError(error, sys.exc_info())
211
212 def _addClassSetUpError(self, result, klass):
213 className = util.strclass(klass)
214 error = _ErrorHolder('classSetUp (%s)' % className)
215 result.addError(error, sys.exc_info())
216
217
218class _ErrorHolder(object):
219 """
220 Placeholder for a TestCase inside a result. As far as a TestResult
221 is concerned, this looks exactly like a unit test. Used to insert
222 arbitrary errors into a test suite run.
223 """
224 # Inspired by the ErrorHolder from Twisted:
225 # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py
226
227 # attribute used by TestResult._exc_info_to_string
228 failureException = None
229
230 def __init__(self, description):
231 self.description = description
232
233 def id(self):
234 return self.description
235
236 def shortDescription(self):
237 return None
238
239 def __repr__(self):
240 return "<ErrorHolder description=%r>" % (self.description,)
241
242 def __str__(self):
243 return self.id()
244
245 def run(self, result):
246 # could call result.addError(...) - but this test-like object
247 # shouldn't be run anyway
248 pass
249
250 def __call__(self, result):
251 return self.run(result)
252
253 def countTestCases(self):
254 return 0
255
256def _isnotsuite(test):
257 "A crude way to tell apart testcases and suites with duck-typing"
258 try:
259 iter(test)
260 except TypeError:
261 return True
262 return False