blob: 0072f2034db560aaa8991e18927dd00f11f1e218 [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()
Michael Foord520ed0a2010-06-05 21:12:23 +0000130 except Exception as e:
Benjamin Peterson847a4112010-03-14 15:04:17 +0000131 currentClass._classSetupFailed = True
Michael Foord520ed0a2010-06-05 21:12:23 +0000132 className = util.strclass(currentClass)
133 errorName = 'setUpClass (%s)' % className
134 self._addClassOrModuleLevelException(result, e, errorName)
Benjamin Peterson847a4112010-03-14 15:04:17 +0000135
136 def _get_previous_module(self, result):
137 previousModule = None
138 previousClass = getattr(result, '_previousTestClass', None)
139 if previousClass is not None:
140 previousModule = previousClass.__module__
141 return previousModule
142
143
144 def _handleModuleFixture(self, test, result):
145 previousModule = self._get_previous_module(result)
146 currentModule = test.__class__.__module__
147 if currentModule == previousModule:
148 return
149
150 self._handleModuleTearDown(result)
151
152
153 result._moduleSetUpFailed = False
154 try:
155 module = sys.modules[currentModule]
156 except KeyError:
157 return
158 setUpModule = getattr(module, 'setUpModule', None)
159 if setUpModule is not None:
160 try:
161 setUpModule()
Michael Foord520ed0a2010-06-05 21:12:23 +0000162 except Exception as e:
Benjamin Peterson847a4112010-03-14 15:04:17 +0000163 result._moduleSetUpFailed = True
Michael Foord520ed0a2010-06-05 21:12:23 +0000164 errorName = 'setUpModule (%s)' % currentModule
165 self._addClassOrModuleLevelException(result, e, errorName)
166
167 def _addClassOrModuleLevelException(self, result, exception, errorName):
168 error = _ErrorHolder(errorName)
169 addSkip = getattr(result, 'addSkip', None)
170 if addSkip is not None and isinstance(exception, case.SkipTest):
171 addSkip(error, str(exception))
172 else:
173 result.addError(error, sys.exc_info())
Benjamin Peterson847a4112010-03-14 15:04:17 +0000174
175 def _handleModuleTearDown(self, result):
176 previousModule = self._get_previous_module(result)
177 if previousModule is None:
178 return
179 if result._moduleSetUpFailed:
180 return
181
182 try:
183 module = sys.modules[previousModule]
184 except KeyError:
185 return
186
187 tearDownModule = getattr(module, 'tearDownModule', None)
188 if tearDownModule is not None:
189 try:
190 tearDownModule()
Michael Foord520ed0a2010-06-05 21:12:23 +0000191 except Exception as e:
192 errorName = 'tearDownModule (%s)' % previousModule
193 self._addClassOrModuleLevelException(result, e, errorName)
Benjamin Peterson847a4112010-03-14 15:04:17 +0000194
195 def _tearDownPreviousClass(self, test, result):
196 previousClass = getattr(result, '_previousTestClass', None)
197 currentClass = test.__class__
198 if currentClass == previousClass:
199 return
200 if getattr(previousClass, '_classSetupFailed', False):
201 return
202 if getattr(result, '_moduleSetUpFailed', False):
203 return
204 if getattr(previousClass, "__unittest_skip__", False):
205 return
206
207 tearDownClass = getattr(previousClass, 'tearDownClass', None)
208 if tearDownClass is not None:
209 try:
210 tearDownClass()
Michael Foord520ed0a2010-06-05 21:12:23 +0000211 except Exception as e:
212 className = util.strclass(previousClass)
213 errorName = 'tearDownClass (%s)' % className
214 self._addClassOrModuleLevelException(result, e, errorName)
Benjamin Peterson847a4112010-03-14 15:04:17 +0000215
Benjamin Peterson847a4112010-03-14 15:04:17 +0000216
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