16
16
class TestResult(unittest.TestResult):
17
17
"""Subclass of unittest.TestResult extending the protocol for flexability.
19
This test result supports an experimental protocol for providing additional
20
data to in test outcomes. All the outcome methods take an optional dict
21
'details'. If supplied any other detail parameters like 'err' or 'reason'
22
should not be provided. The details dict is a mapping from names to
23
MIME content objects (see testtools.content). This permits attaching
24
tracebacks, log files, or even large objects like databases that were
25
part of the test fixture. Until this API is accepted into upstream
26
Python it is considered experimental: it may be replaced at any point
27
by a newer version more in line with upstream Python. Compatibility would
28
be aimed for in this case, but may not be possible.
19
30
:ivar skip_reasons: A dict of skip-reasons -> list of tests. See addSkip.
39
50
# This is the python 2.7 implementation
40
51
self.expectedFailures.append(
41
(test, self._exc_info_to_string(err, test)))
43
def addSkip(self, test, reason):
52
(test, self._err_details_to_string(test, err, details)))
54
def addError(self, test, err=None, details=None):
55
"""Called when an error has occurred. 'err' is a tuple of values as
56
returned by sys.exc_info().
58
:param details: Alternative way to supply details about the outcome.
59
see the class docstring for more information.
61
self.errors.append((test,
62
self._err_details_to_string(test, err, details)))
64
def addFailure(self, test, err=None, details=None):
65
"""Called when an error has occurred. 'err' is a tuple of values as
66
returned by sys.exc_info().
68
:param details: Alternative way to supply details about the outcome.
69
see the class docstring for more information.
71
self.failures.append((test,
72
self._err_details_to_string(test, err, details)))
74
def addSkip(self, test, reason=None, details=None):
44
75
"""Called when a test has been skipped rather than running.
46
77
Like with addSuccess and addError, testStopped should still be called.
52
83
:param test: The test that has been skipped.
53
84
:param reason: The reason for the test being skipped. For instance,
54
85
u"pyGL is not available".
86
:param details: Alternative way to supply details about the outcome.
87
see the class docstring for more information.
91
reason = details.get('reason')
93
reason = 'No reason given'
95
reason = ''.join(reason.iter_bytes())
57
96
skip_list = self.skip_reasons.setdefault(reason, [])
58
97
skip_list.append(test)
60
def addUnexpectedSuccess(self, test):
99
def addSuccess(self, test, details=None):
100
"""Called when a test succeeded."""
102
def addUnexpectedSuccess(self, test, details=None):
61
103
"""Called when a test was expected to fail, but succeed."""
62
104
self.unexpectedSuccesses.append(test)
106
def _err_details_to_string(self, test, err=None, details=None):
107
"""Convert an error in exc_info form or a contents dict to a string."""
109
return self._exc_info_to_string(err, test)
110
return _details_to_str(details)
64
112
def startTestRun(self):
65
113
"""Called before a test run starts.
97
145
def stopTest(self, test):
98
146
self._dispatch('stopTest', test)
100
def addError(self, test, error):
101
self._dispatch('addError', test, error)
103
def addExpectedFailure(self, test, err):
104
self._dispatch('addExpectedFailure', test, err)
106
def addFailure(self, test, failure):
107
self._dispatch('addFailure', test, failure)
109
def addSkip(self, test, reason):
110
self._dispatch('addSkip', test, reason)
112
def addSuccess(self, test):
113
self._dispatch('addSuccess', test)
115
def addUnexpectedSuccess(self, test):
116
self._dispatch('addUnexpectedSuccess', test)
148
def addError(self, test, error=None, details=None):
149
self._dispatch('addError', test, error, details=details)
151
def addExpectedFailure(self, test, err=None, details=None):
152
self._dispatch('addExpectedFailure', test, err, details=details)
154
def addFailure(self, test, err=None, details=None):
155
self._dispatch('addFailure', test, err, details=details)
157
def addSkip(self, test, reason=None, details=None):
158
self._dispatch('addSkip', test, reason, details=details)
160
def addSuccess(self, test, details=None):
161
self._dispatch('addSuccess', test, details=details)
163
def addUnexpectedSuccess(self, test, details=None):
164
self._dispatch('addUnexpectedSuccess', test, details=details)
118
166
def startTestRun(self):
119
167
self._dispatch('startTestRun')
150
198
:param semaphore: A threading.Semaphore with limit 1.
152
200
TestResult.__init__(self)
201
self.result = ExtendedToOriginalDecorator(target)
154
202
self.semaphore = semaphore
156
def addError(self, test, err):
157
self.semaphore.acquire()
159
self.result.startTest(test)
160
self.result.addError(test, err)
161
self.result.stopTest(test)
163
self.semaphore.release()
165
def addExpectedFailure(self, test, err):
166
self.semaphore.acquire()
168
self.result.startTest(test)
169
self.result.addExpectedFailure(test, err)
170
self.result.stopTest(test)
172
self.semaphore.release()
174
def addFailure(self, test, err):
175
self.semaphore.acquire()
177
self.result.startTest(test)
178
self.result.addFailure(test, err)
179
self.result.stopTest(test)
181
self.semaphore.release()
183
def addSkip(self, test, reason):
184
self.semaphore.acquire()
186
self.result.startTest(test)
187
self.result.addSkip(test, reason)
188
self.result.stopTest(test)
190
self.semaphore.release()
192
def addSuccess(self, test):
193
self.semaphore.acquire()
195
self.result.startTest(test)
196
self.result.addSuccess(test)
197
self.result.stopTest(test)
199
self.semaphore.release()
201
def addUnexpectedSuccess(self, test):
202
self.semaphore.acquire()
204
self.result.startTest(test)
205
self.result.addUnexpectedSuccess(test)
204
def addError(self, test, err=None, details=None):
205
self.semaphore.acquire()
207
self.result.startTest(test)
208
self.result.addError(test, err, details=details)
209
self.result.stopTest(test)
211
self.semaphore.release()
213
def addExpectedFailure(self, test, err=None, details=None):
214
self.semaphore.acquire()
216
self.result.startTest(test)
217
self.result.addExpectedFailure(test, err, details=details)
218
self.result.stopTest(test)
220
self.semaphore.release()
222
def addFailure(self, test, err=None, details=None):
223
self.semaphore.acquire()
225
self.result.startTest(test)
226
self.result.addFailure(test, err, details=details)
227
self.result.stopTest(test)
229
self.semaphore.release()
231
def addSkip(self, test, reason=None, details=None):
232
self.semaphore.acquire()
234
self.result.startTest(test)
235
self.result.addSkip(test, reason, details=details)
236
self.result.stopTest(test)
238
self.semaphore.release()
240
def addSuccess(self, test, details=None):
241
self.semaphore.acquire()
243
self.result.startTest(test)
244
self.result.addSuccess(test, details=details)
245
self.result.stopTest(test)
247
self.semaphore.release()
249
def addUnexpectedSuccess(self, test, details=None):
250
self.semaphore.acquire()
252
self.result.startTest(test)
253
self.result.addUnexpectedSuccess(test, details=details)
206
254
self.result.stopTest(test)
208
256
self.semaphore.release()
320
368
def _details_to_exc_info(self, details):
321
369
"""Convert a details dict to an exc_info tuple."""
322
370
return (_StringException,
323
_StringException(self._details_to_str(details)), None)
371
_StringException(_details_to_str(details)), None)
325
def _details_to_str(self, details):
326
"""Convert a details dict to a string."""
328
# sorted is for testing, may want to remove that and use a dict
329
# subclass with defined order for iteritems instead.
330
for key, content in sorted(details.iteritems()):
331
if content.content_type.type != 'text':
332
lines.append('Binary content: %s\n' % key)
334
lines.append('Text attachment: %s\n' % key)
335
lines.append('------------\n')
336
lines.extend(content.iter_bytes())
337
if not lines[-1].endswith('\n'):
339
lines.append('------------\n')
340
return ''.join(lines)
375
return self.decorated.done()
376
except AttributeError:
342
379
def progress(self, offset, whence):
343
380
method = getattr(self.decorated, 'progress', None)
395
432
except AttributeError:
436
def _details_to_str(details):
437
"""Convert a details dict to a string."""
439
# sorted is for testing, may want to remove that and use a dict
440
# subclass with defined order for iteritems instead.
441
for key, content in sorted(details.iteritems()):
442
if content.content_type.type != 'text':
443
chars.append('Binary content: %s\n' % key)
445
chars.append('Text attachment: %s\n' % key)
446
chars.append('------------\n')
447
chars.extend(content.iter_bytes())
448
if not chars[-1].endswith('\n'):
450
chars.append('------------\n')
451
return ''.join(chars)