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