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