~lifeless/testtools/0.9.1

« back to all changes in this revision

Viewing changes to testtools/testresult.py

  • Committer: Robert Collins
  • Date: 2009-11-02 08:10:18 UTC
  • mfrom: (24.1.1 details)
  • Revision ID: robertc@robertcollins.net-20091102081018-gzwbx9l4vc44j7nv
Merge support for the extended outcome API.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
class TestResult(unittest.TestResult):
17
17
    """Subclass of unittest.TestResult extending the protocol for flexability.
18
18
 
 
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.
 
29
 
19
30
    :ivar skip_reasons: A dict of skip-reasons -> list of tests. See addSkip.
20
31
    """
21
32
 
27
38
        self.unexpectedSuccesses = []
28
39
        # -- End:   As per python 2.7 --
29
40
 
30
 
    def addExpectedFailure(self, test, err):
 
41
    def addExpectedFailure(self, test, err=None, details=None):
31
42
        """Called when a test has failed in an expected manner.
32
43
 
33
44
        Like with addSuccess and addError, testStopped should still be called.
38
49
        """
39
50
        # This is the python 2.7 implementation
40
51
        self.expectedFailures.append(
41
 
            (test, self._exc_info_to_string(err, test)))
42
 
 
43
 
    def addSkip(self, test, reason):
 
52
            (test, self._err_details_to_string(test, err, details)))
 
53
 
 
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().
 
57
 
 
58
        :param details: Alternative way to supply details about the outcome.
 
59
            see the class docstring for more information.
 
60
        """
 
61
        self.errors.append((test,
 
62
            self._err_details_to_string(test, err, details)))
 
63
 
 
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().
 
67
 
 
68
        :param details: Alternative way to supply details about the outcome.
 
69
            see the class docstring for more information.
 
70
        """
 
71
        self.failures.append((test,
 
72
            self._err_details_to_string(test, err, details)))
 
73
 
 
74
    def addSkip(self, test, reason=None, details=None):
44
75
        """Called when a test has been skipped rather than running.
45
76
 
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.
55
88
        :return: None
56
89
        """
 
90
        if reason is None:
 
91
            reason = details.get('reason')
 
92
            if reason is None:
 
93
                reason = 'No reason given'
 
94
            else:
 
95
                reason = ''.join(reason.iter_bytes())
57
96
        skip_list = self.skip_reasons.setdefault(reason, [])
58
97
        skip_list.append(test)
59
98
 
60
 
    def addUnexpectedSuccess(self, test):
 
99
    def addSuccess(self, test, details=None):
 
100
        """Called when a test succeeded."""
 
101
 
 
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)
63
105
 
 
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."""
 
108
        if err is not None:
 
109
            return self._exc_info_to_string(err, test)
 
110
        return _details_to_str(details)
 
111
 
64
112
    def startTestRun(self):
65
113
        """Called before a test run starts.
66
114
 
85
133
 
86
134
    def __init__(self, *results):
87
135
        TestResult.__init__(self)
88
 
        self._results = list(results)
 
136
        self._results = map(ExtendedToOriginalDecorator, results)
89
137
 
90
138
    def _dispatch(self, message, *args, **kwargs):
91
139
        for result in self._results:
97
145
    def stopTest(self, test):
98
146
        self._dispatch('stopTest', test)
99
147
 
100
 
    def addError(self, test, error):
101
 
        self._dispatch('addError', test, error)
102
 
 
103
 
    def addExpectedFailure(self, test, err):
104
 
        self._dispatch('addExpectedFailure', test, err)
105
 
 
106
 
    def addFailure(self, test, failure):
107
 
        self._dispatch('addFailure', test, failure)
108
 
 
109
 
    def addSkip(self, test, reason):
110
 
        self._dispatch('addSkip', test, reason)
111
 
 
112
 
    def addSuccess(self, test):
113
 
        self._dispatch('addSuccess', test)
114
 
 
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)
 
150
 
 
151
    def addExpectedFailure(self, test, err=None, details=None):
 
152
        self._dispatch('addExpectedFailure', test, err, details=details)
 
153
 
 
154
    def addFailure(self, test, err=None, details=None):
 
155
        self._dispatch('addFailure', test, err, details=details)
 
156
 
 
157
    def addSkip(self, test, reason=None, details=None):
 
158
        self._dispatch('addSkip', test, reason, details=details)
 
159
 
 
160
    def addSuccess(self, test, details=None):
 
161
        self._dispatch('addSuccess', test, details=details)
 
162
 
 
163
    def addUnexpectedSuccess(self, test, details=None):
 
164
        self._dispatch('addUnexpectedSuccess', test, details=details)
117
165
 
118
166
    def startTestRun(self):
119
167
        self._dispatch('startTestRun')
150
198
        :param semaphore: A threading.Semaphore with limit 1.
151
199
        """
152
200
        TestResult.__init__(self)
153
 
        self.result = target
 
201
        self.result = ExtendedToOriginalDecorator(target)
154
202
        self.semaphore = semaphore
155
203
 
156
 
    def addError(self, test, err):
157
 
        self.semaphore.acquire()
158
 
        try:
159
 
            self.result.startTest(test)
160
 
            self.result.addError(test, err)
161
 
            self.result.stopTest(test)
162
 
        finally:
163
 
            self.semaphore.release()
164
 
 
165
 
    def addExpectedFailure(self, test, err):
166
 
        self.semaphore.acquire()
167
 
        try:
168
 
            self.result.startTest(test)
169
 
            self.result.addExpectedFailure(test, err)
170
 
            self.result.stopTest(test)
171
 
        finally:
172
 
            self.semaphore.release()
173
 
 
174
 
    def addFailure(self, test, err):
175
 
        self.semaphore.acquire()
176
 
        try:
177
 
            self.result.startTest(test)
178
 
            self.result.addFailure(test, err)
179
 
            self.result.stopTest(test)
180
 
        finally:
181
 
            self.semaphore.release()
182
 
 
183
 
    def addSkip(self, test, reason):
184
 
        self.semaphore.acquire()
185
 
        try:
186
 
            self.result.startTest(test)
187
 
            self.result.addSkip(test, reason)
188
 
            self.result.stopTest(test)
189
 
        finally:
190
 
            self.semaphore.release()
191
 
 
192
 
    def addSuccess(self, test):
193
 
        self.semaphore.acquire()
194
 
        try:
195
 
            self.result.startTest(test)
196
 
            self.result.addSuccess(test)
197
 
            self.result.stopTest(test)
198
 
        finally:
199
 
            self.semaphore.release()
200
 
 
201
 
    def addUnexpectedSuccess(self, test):
202
 
        self.semaphore.acquire()
203
 
        try:
204
 
            self.result.startTest(test)
205
 
            self.result.addUnexpectedSuccess(test)
 
204
    def addError(self, test, err=None, details=None):
 
205
        self.semaphore.acquire()
 
206
        try:
 
207
            self.result.startTest(test)
 
208
            self.result.addError(test, err, details=details)
 
209
            self.result.stopTest(test)
 
210
        finally:
 
211
            self.semaphore.release()
 
212
 
 
213
    def addExpectedFailure(self, test, err=None, details=None):
 
214
        self.semaphore.acquire()
 
215
        try:
 
216
            self.result.startTest(test)
 
217
            self.result.addExpectedFailure(test, err, details=details)
 
218
            self.result.stopTest(test)
 
219
        finally:
 
220
            self.semaphore.release()
 
221
 
 
222
    def addFailure(self, test, err=None, details=None):
 
223
        self.semaphore.acquire()
 
224
        try:
 
225
            self.result.startTest(test)
 
226
            self.result.addFailure(test, err, details=details)
 
227
            self.result.stopTest(test)
 
228
        finally:
 
229
            self.semaphore.release()
 
230
 
 
231
    def addSkip(self, test, reason=None, details=None):
 
232
        self.semaphore.acquire()
 
233
        try:
 
234
            self.result.startTest(test)
 
235
            self.result.addSkip(test, reason, details=details)
 
236
            self.result.stopTest(test)
 
237
        finally:
 
238
            self.semaphore.release()
 
239
 
 
240
    def addSuccess(self, test, details=None):
 
241
        self.semaphore.acquire()
 
242
        try:
 
243
            self.result.startTest(test)
 
244
            self.result.addSuccess(test, details=details)
 
245
            self.result.stopTest(test)
 
246
        finally:
 
247
            self.semaphore.release()
 
248
 
 
249
    def addUnexpectedSuccess(self, test, details=None):
 
250
        self.semaphore.acquire()
 
251
        try:
 
252
            self.result.startTest(test)
 
253
            self.result.addUnexpectedSuccess(test, details=details)
206
254
            self.result.stopTest(test)
207
255
        finally:
208
256
            self.semaphore.release()
285
333
                return addSkip(test, details=details)
286
334
            except TypeError, e:
287
335
                # have to convert
288
 
                reason = self._details_to_str(details)
 
336
                reason = _details_to_str(details)
289
337
        return addSkip(test, reason)
290
338
 
291
339
    def addUnexpectedSuccess(self, test, details=None):
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)
324
372
 
325
 
    def _details_to_str(self, details):
326
 
        """Convert a details dict to a string."""
327
 
        lines = []
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)
333
 
                continue
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'):
338
 
                lines.append('\n')
339
 
            lines.append('------------\n')
340
 
        return ''.join(lines)
 
373
    def done(self):
 
374
        try:
 
375
            return self.decorated.done()
 
376
        except AttributeError:
 
377
            return
341
378
 
342
379
    def progress(self, offset, whence):
343
380
        method = getattr(self.decorated, 'progress', None)
395
432
        except AttributeError:
396
433
            return False
397
434
 
 
435
 
 
436
def _details_to_str(details):
 
437
    """Convert a details dict to a string."""
 
438
    chars = []
 
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)
 
444
            continue
 
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'):
 
449
            chars.append('\n')
 
450
        chars.append('------------\n')
 
451
    return ''.join(chars)
 
452