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