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