~percona-dev/percona-server/5.1.59-innodb_log_archiving

« back to all changes in this revision

Viewing changes to python-for-subunit2junitxml/testtools/testresult/real.py

  • Committer: Stewart Smith
  • Date: 2011-10-06 06:45:16 UTC
  • Revision ID: stewart@flamingspork.com-20111006064516-rrjg17x7wwn9vr6w
add subunit support to mtr

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (c) 2008 testtools developers. See LICENSE for details.
 
2
 
 
3
"""Test results and related things."""
 
4
 
 
5
__metaclass__ = type
 
6
__all__ = [
 
7
    'ExtendedToOriginalDecorator',
 
8
    'MultiTestResult',
 
9
    'TestResult',
 
10
    'ThreadsafeForwardingResult',
 
11
    ]
 
12
 
 
13
import datetime
 
14
import sys
 
15
import unittest
 
16
 
 
17
from testtools.compat import all, _format_exc_info, str_is_unicode, _u
 
18
 
 
19
# From http://docs.python.org/library/datetime.html
 
20
_ZERO = datetime.timedelta(0)
 
21
 
 
22
# A UTC class.
 
23
 
 
24
class UTC(datetime.tzinfo):
 
25
    """UTC"""
 
26
 
 
27
    def utcoffset(self, dt):
 
28
        return _ZERO
 
29
 
 
30
    def tzname(self, dt):
 
31
        return "UTC"
 
32
 
 
33
    def dst(self, dt):
 
34
        return _ZERO
 
35
 
 
36
utc = UTC()
 
37
 
 
38
 
 
39
class TestResult(unittest.TestResult):
 
40
    """Subclass of unittest.TestResult extending the protocol for flexability.
 
41
 
 
42
    This test result supports an experimental protocol for providing additional
 
43
    data to in test outcomes. All the outcome methods take an optional dict
 
44
    'details'. If supplied any other detail parameters like 'err' or 'reason'
 
45
    should not be provided. The details dict is a mapping from names to
 
46
    MIME content objects (see testtools.content). This permits attaching
 
47
    tracebacks, log files, or even large objects like databases that were
 
48
    part of the test fixture. Until this API is accepted into upstream
 
49
    Python it is considered experimental: it may be replaced at any point
 
50
    by a newer version more in line with upstream Python. Compatibility would
 
51
    be aimed for in this case, but may not be possible.
 
52
 
 
53
    :ivar skip_reasons: A dict of skip-reasons -> list of tests. See addSkip.
 
54
    """
 
55
 
 
56
    def __init__(self):
 
57
        # startTestRun resets all attributes, and older clients don't know to
 
58
        # call startTestRun, so it is called once here.
 
59
        # Because subclasses may reasonably not expect this, we call the 
 
60
        # specific version we want to run.
 
61
        TestResult.startTestRun(self)
 
62
 
 
63
    def addExpectedFailure(self, test, err=None, details=None):
 
64
        """Called when a test has failed in an expected manner.
 
65
 
 
66
        Like with addSuccess and addError, testStopped should still be called.
 
67
 
 
68
        :param test: The test that has been skipped.
 
69
        :param err: The exc_info of the error that was raised.
 
70
        :return: None
 
71
        """
 
72
        # This is the python 2.7 implementation
 
73
        self.expectedFailures.append(
 
74
            (test, self._err_details_to_string(test, err, details)))
 
75
 
 
76
    def addError(self, test, err=None, details=None):
 
77
        """Called when an error has occurred. 'err' is a tuple of values as
 
78
        returned by sys.exc_info().
 
79
 
 
80
        :param details: Alternative way to supply details about the outcome.
 
81
            see the class docstring for more information.
 
82
        """
 
83
        self.errors.append((test,
 
84
            self._err_details_to_string(test, err, details)))
 
85
 
 
86
    def addFailure(self, test, err=None, details=None):
 
87
        """Called when an error has occurred. 'err' is a tuple of values as
 
88
        returned by sys.exc_info().
 
89
 
 
90
        :param details: Alternative way to supply details about the outcome.
 
91
            see the class docstring for more information.
 
92
        """
 
93
        self.failures.append((test,
 
94
            self._err_details_to_string(test, err, details)))
 
95
 
 
96
    def addSkip(self, test, reason=None, details=None):
 
97
        """Called when a test has been skipped rather than running.
 
98
 
 
99
        Like with addSuccess and addError, testStopped should still be called.
 
100
 
 
101
        This must be called by the TestCase. 'addError' and 'addFailure' will
 
102
        not call addSkip, since they have no assumptions about the kind of
 
103
        errors that a test can raise.
 
104
 
 
105
        :param test: The test that has been skipped.
 
106
        :param reason: The reason for the test being skipped. For instance,
 
107
            u"pyGL is not available".
 
108
        :param details: Alternative way to supply details about the outcome.
 
109
            see the class docstring for more information.
 
110
        :return: None
 
111
        """
 
112
        if reason is None:
 
113
            reason = details.get('reason')
 
114
            if reason is None:
 
115
                reason = 'No reason given'
 
116
            else:
 
117
                reason = ''.join(reason.iter_text())
 
118
        skip_list = self.skip_reasons.setdefault(reason, [])
 
119
        skip_list.append(test)
 
120
 
 
121
    def addSuccess(self, test, details=None):
 
122
        """Called when a test succeeded."""
 
123
 
 
124
    def addUnexpectedSuccess(self, test, details=None):
 
125
        """Called when a test was expected to fail, but succeed."""
 
126
        self.unexpectedSuccesses.append(test)
 
127
 
 
128
    def wasSuccessful(self):
 
129
        """Has this result been successful so far?
 
130
 
 
131
        If there have been any errors, failures or unexpected successes,
 
132
        return False.  Otherwise, return True.
 
133
 
 
134
        Note: This differs from standard unittest in that we consider
 
135
        unexpected successes to be equivalent to failures, rather than
 
136
        successes.
 
137
        """
 
138
        return not (self.errors or self.failures or self.unexpectedSuccesses)
 
139
 
 
140
    if str_is_unicode:
 
141
        # Python 3 and IronPython strings are unicode, use parent class method
 
142
        _exc_info_to_unicode = unittest.TestResult._exc_info_to_string
 
143
    else:
 
144
        # For Python 2, need to decode components of traceback according to
 
145
        # their source, so can't use traceback.format_exception
 
146
        # Here follows a little deep magic to copy the existing method and
 
147
        # replace the formatter with one that returns unicode instead
 
148
        from types import FunctionType as __F, ModuleType as __M
 
149
        __f = unittest.TestResult._exc_info_to_string.im_func
 
150
        __g = dict(__f.func_globals)
 
151
        __m = __M("__fake_traceback")
 
152
        __m.format_exception = _format_exc_info
 
153
        __g["traceback"] = __m
 
154
        _exc_info_to_unicode = __F(__f.func_code, __g, "_exc_info_to_unicode")
 
155
        del __F, __M, __f, __g, __m
 
156
 
 
157
    def _err_details_to_string(self, test, err=None, details=None):
 
158
        """Convert an error in exc_info form or a contents dict to a string."""
 
159
        if err is not None:
 
160
            return self._exc_info_to_unicode(err, test)
 
161
        return _details_to_str(details)
 
162
 
 
163
    def _now(self):
 
164
        """Return the current 'test time'.
 
165
 
 
166
        If the time() method has not been called, this is equivalent to
 
167
        datetime.now(), otherwise its the last supplied datestamp given to the
 
168
        time() method.
 
169
        """
 
170
        if self.__now is None:
 
171
            return datetime.datetime.now(utc)
 
172
        else:
 
173
            return self.__now
 
174
 
 
175
    def startTestRun(self):
 
176
        """Called before a test run starts.
 
177
 
 
178
        New in Python 2.7. The testtools version resets the result to a
 
179
        pristine condition ready for use in another test run.  Note that this
 
180
        is different from Python 2.7's startTestRun, which does nothing.
 
181
        """
 
182
        super(TestResult, self).__init__()
 
183
        self.skip_reasons = {}
 
184
        self.__now = None
 
185
        # -- Start: As per python 2.7 --
 
186
        self.expectedFailures = []
 
187
        self.unexpectedSuccesses = []
 
188
        # -- End:   As per python 2.7 --
 
189
 
 
190
    def stopTestRun(self):
 
191
        """Called after a test run completes
 
192
 
 
193
        New in python 2.7
 
194
        """
 
195
 
 
196
    def time(self, a_datetime):
 
197
        """Provide a timestamp to represent the current time.
 
198
 
 
199
        This is useful when test activity is time delayed, or happening
 
200
        concurrently and getting the system time between API calls will not
 
201
        accurately represent the duration of tests (or the whole run).
 
202
 
 
203
        Calling time() sets the datetime used by the TestResult object.
 
204
        Time is permitted to go backwards when using this call.
 
205
 
 
206
        :param a_datetime: A datetime.datetime object with TZ information or
 
207
            None to reset the TestResult to gathering time from the system.
 
208
        """
 
209
        self.__now = a_datetime
 
210
 
 
211
    def done(self):
 
212
        """Called when the test runner is done.
 
213
 
 
214
        deprecated in favour of stopTestRun.
 
215
        """
 
216
 
 
217
 
 
218
class MultiTestResult(TestResult):
 
219
    """A test result that dispatches to many test results."""
 
220
 
 
221
    def __init__(self, *results):
 
222
        TestResult.__init__(self)
 
223
        self._results = list(map(ExtendedToOriginalDecorator, results))
 
224
 
 
225
    def _dispatch(self, message, *args, **kwargs):
 
226
        return tuple(
 
227
            getattr(result, message)(*args, **kwargs)
 
228
            for result in self._results)
 
229
 
 
230
    def startTest(self, test):
 
231
        return self._dispatch('startTest', test)
 
232
 
 
233
    def stopTest(self, test):
 
234
        return self._dispatch('stopTest', test)
 
235
 
 
236
    def addError(self, test, error=None, details=None):
 
237
        return self._dispatch('addError', test, error, details=details)
 
238
 
 
239
    def addExpectedFailure(self, test, err=None, details=None):
 
240
        return self._dispatch(
 
241
            'addExpectedFailure', test, err, details=details)
 
242
 
 
243
    def addFailure(self, test, err=None, details=None):
 
244
        return self._dispatch('addFailure', test, err, details=details)
 
245
 
 
246
    def addSkip(self, test, reason=None, details=None):
 
247
        return self._dispatch('addSkip', test, reason, details=details)
 
248
 
 
249
    def addSuccess(self, test, details=None):
 
250
        return self._dispatch('addSuccess', test, details=details)
 
251
 
 
252
    def addUnexpectedSuccess(self, test, details=None):
 
253
        return self._dispatch('addUnexpectedSuccess', test, details=details)
 
254
 
 
255
    def startTestRun(self):
 
256
        return self._dispatch('startTestRun')
 
257
 
 
258
    def stopTestRun(self):
 
259
        return self._dispatch('stopTestRun')
 
260
 
 
261
    def time(self, a_datetime):
 
262
        return self._dispatch('time', a_datetime)
 
263
 
 
264
    def done(self):
 
265
        return self._dispatch('done')
 
266
 
 
267
    def wasSuccessful(self):
 
268
        """Was this result successful?
 
269
 
 
270
        Only returns True if every constituent result was successful.
 
271
        """
 
272
        return all(self._dispatch('wasSuccessful'))
 
273
 
 
274
 
 
275
class TextTestResult(TestResult):
 
276
    """A TestResult which outputs activity to a text stream."""
 
277
 
 
278
    def __init__(self, stream):
 
279
        """Construct a TextTestResult writing to stream."""
 
280
        super(TextTestResult, self).__init__()
 
281
        self.stream = stream
 
282
        self.sep1 = '=' * 70 + '\n'
 
283
        self.sep2 = '-' * 70 + '\n'
 
284
 
 
285
    def _delta_to_float(self, a_timedelta):
 
286
        return (a_timedelta.days * 86400.0 + a_timedelta.seconds +
 
287
            a_timedelta.microseconds / 1000000.0)
 
288
 
 
289
    def _show_list(self, label, error_list):
 
290
        for test, output in error_list:
 
291
            self.stream.write(self.sep1)
 
292
            self.stream.write("%s: %s\n" % (label, test.id()))
 
293
            self.stream.write(self.sep2)
 
294
            self.stream.write(output)
 
295
 
 
296
    def startTestRun(self):
 
297
        super(TextTestResult, self).startTestRun()
 
298
        self.__start = self._now()
 
299
        self.stream.write("Tests running...\n")
 
300
 
 
301
    def stopTestRun(self):
 
302
        if self.testsRun != 1:
 
303
            plural = 's'
 
304
        else:
 
305
            plural = ''
 
306
        stop = self._now()
 
307
        self._show_list('ERROR', self.errors)
 
308
        self._show_list('FAIL', self.failures)
 
309
        for test in self.unexpectedSuccesses:
 
310
            self.stream.write(
 
311
                "%sUNEXPECTED SUCCESS: %s\n%s" % (
 
312
                    self.sep1, test.id(), self.sep2))
 
313
        self.stream.write("Ran %d test%s in %.3fs\n\n" %
 
314
            (self.testsRun, plural,
 
315
             self._delta_to_float(stop - self.__start)))
 
316
        if self.wasSuccessful():
 
317
            self.stream.write("OK\n")
 
318
        else:
 
319
            self.stream.write("FAILED (")
 
320
            details = []
 
321
            details.append("failures=%d" % (
 
322
                sum(map(len, (
 
323
                    self.failures, self.errors, self.unexpectedSuccesses)))))
 
324
            self.stream.write(", ".join(details))
 
325
            self.stream.write(")\n")
 
326
        super(TextTestResult, self).stopTestRun()
 
327
 
 
328
 
 
329
class ThreadsafeForwardingResult(TestResult):
 
330
    """A TestResult which ensures the target does not receive mixed up calls.
 
331
 
 
332
    This is used when receiving test results from multiple sources, and batches
 
333
    up all the activity for a single test into a thread-safe batch where all
 
334
    other ThreadsafeForwardingResult objects sharing the same semaphore will be
 
335
    locked out.
 
336
 
 
337
    Typical use of ThreadsafeForwardingResult involves creating one
 
338
    ThreadsafeForwardingResult per thread in a ConcurrentTestSuite. These
 
339
    forward to the TestResult that the ConcurrentTestSuite run method was
 
340
    called with.
 
341
 
 
342
    target.done() is called once for each ThreadsafeForwardingResult that
 
343
    forwards to the same target. If the target's done() takes special action,
 
344
    care should be taken to accommodate this.
 
345
    """
 
346
 
 
347
    def __init__(self, target, semaphore):
 
348
        """Create a ThreadsafeForwardingResult forwarding to target.
 
349
 
 
350
        :param target: A TestResult.
 
351
        :param semaphore: A threading.Semaphore with limit 1.
 
352
        """
 
353
        TestResult.__init__(self)
 
354
        self.result = ExtendedToOriginalDecorator(target)
 
355
        self.semaphore = semaphore
 
356
 
 
357
    def _add_result_with_semaphore(self, method, test, *args, **kwargs):
 
358
        self.semaphore.acquire()
 
359
        try:
 
360
            self.result.time(self._test_start)
 
361
            self.result.startTest(test)
 
362
            self.result.time(self._now())
 
363
            try:
 
364
                method(test, *args, **kwargs)
 
365
            finally:
 
366
                self.result.stopTest(test)
 
367
        finally:
 
368
            self.semaphore.release()
 
369
 
 
370
    def addError(self, test, err=None, details=None):
 
371
        self._add_result_with_semaphore(self.result.addError,
 
372
            test, err, details=details)
 
373
 
 
374
    def addExpectedFailure(self, test, err=None, details=None):
 
375
        self._add_result_with_semaphore(self.result.addExpectedFailure,
 
376
            test, err, details=details)
 
377
 
 
378
    def addFailure(self, test, err=None, details=None):
 
379
        self._add_result_with_semaphore(self.result.addFailure,
 
380
            test, err, details=details)
 
381
 
 
382
    def addSkip(self, test, reason=None, details=None):
 
383
        self._add_result_with_semaphore(self.result.addSkip,
 
384
            test, reason, details=details)
 
385
 
 
386
    def addSuccess(self, test, details=None):
 
387
        self._add_result_with_semaphore(self.result.addSuccess,
 
388
            test, details=details)
 
389
 
 
390
    def addUnexpectedSuccess(self, test, details=None):
 
391
        self._add_result_with_semaphore(self.result.addUnexpectedSuccess,
 
392
            test, details=details)
 
393
 
 
394
    def startTestRun(self):
 
395
        self.semaphore.acquire()
 
396
        try:
 
397
            self.result.startTestRun()
 
398
        finally:
 
399
            self.semaphore.release()
 
400
 
 
401
    def stopTestRun(self):
 
402
        self.semaphore.acquire()
 
403
        try:
 
404
            self.result.stopTestRun()
 
405
        finally:
 
406
            self.semaphore.release()
 
407
 
 
408
    def done(self):
 
409
        self.semaphore.acquire()
 
410
        try:
 
411
            self.result.done()
 
412
        finally:
 
413
            self.semaphore.release()
 
414
 
 
415
    def startTest(self, test):
 
416
        self._test_start = self._now()
 
417
        super(ThreadsafeForwardingResult, self).startTest(test)
 
418
 
 
419
    def wasSuccessful(self):
 
420
        return self.result.wasSuccessful()
 
421
 
 
422
 
 
423
class ExtendedToOriginalDecorator(object):
 
424
    """Permit new TestResult API code to degrade gracefully with old results.
 
425
 
 
426
    This decorates an existing TestResult and converts missing outcomes
 
427
    such as addSkip to older outcomes such as addSuccess. It also supports
 
428
    the extended details protocol. In all cases the most recent protocol
 
429
    is attempted first, and fallbacks only occur when the decorated result
 
430
    does not support the newer style of calling.
 
431
    """
 
432
 
 
433
    def __init__(self, decorated):
 
434
        self.decorated = decorated
 
435
 
 
436
    def __getattr__(self, name):
 
437
        return getattr(self.decorated, name)
 
438
 
 
439
    def addError(self, test, err=None, details=None):
 
440
        self._check_args(err, details)
 
441
        if details is not None:
 
442
            try:
 
443
                return self.decorated.addError(test, details=details)
 
444
            except TypeError:
 
445
                # have to convert
 
446
                err = self._details_to_exc_info(details)
 
447
        return self.decorated.addError(test, err)
 
448
 
 
449
    def addExpectedFailure(self, test, err=None, details=None):
 
450
        self._check_args(err, details)
 
451
        addExpectedFailure = getattr(
 
452
            self.decorated, 'addExpectedFailure', None)
 
453
        if addExpectedFailure is None:
 
454
            return self.addSuccess(test)
 
455
        if details is not None:
 
456
            try:
 
457
                return addExpectedFailure(test, details=details)
 
458
            except TypeError:
 
459
                # have to convert
 
460
                err = self._details_to_exc_info(details)
 
461
        return addExpectedFailure(test, err)
 
462
 
 
463
    def addFailure(self, test, err=None, details=None):
 
464
        self._check_args(err, details)
 
465
        if details is not None:
 
466
            try:
 
467
                return self.decorated.addFailure(test, details=details)
 
468
            except TypeError:
 
469
                # have to convert
 
470
                err = self._details_to_exc_info(details)
 
471
        return self.decorated.addFailure(test, err)
 
472
 
 
473
    def addSkip(self, test, reason=None, details=None):
 
474
        self._check_args(reason, details)
 
475
        addSkip = getattr(self.decorated, 'addSkip', None)
 
476
        if addSkip is None:
 
477
            return self.decorated.addSuccess(test)
 
478
        if details is not None:
 
479
            try:
 
480
                return addSkip(test, details=details)
 
481
            except TypeError:
 
482
                # extract the reason if it's available
 
483
                try:
 
484
                    reason = ''.join(details['reason'].iter_text())
 
485
                except KeyError:
 
486
                    reason = _details_to_str(details)
 
487
        return addSkip(test, reason)
 
488
 
 
489
    def addUnexpectedSuccess(self, test, details=None):
 
490
        outcome = getattr(self.decorated, 'addUnexpectedSuccess', None)
 
491
        if outcome is None:
 
492
            try:
 
493
                test.fail("")
 
494
            except test.failureException:
 
495
                return self.addFailure(test, sys.exc_info())
 
496
        if details is not None:
 
497
            try:
 
498
                return outcome(test, details=details)
 
499
            except TypeError:
 
500
                pass
 
501
        return outcome(test)
 
502
 
 
503
    def addSuccess(self, test, details=None):
 
504
        if details is not None:
 
505
            try:
 
506
                return self.decorated.addSuccess(test, details=details)
 
507
            except TypeError:
 
508
                pass
 
509
        return self.decorated.addSuccess(test)
 
510
 
 
511
    def _check_args(self, err, details):
 
512
        param_count = 0
 
513
        if err is not None:
 
514
            param_count += 1
 
515
        if details is not None:
 
516
            param_count += 1
 
517
        if param_count != 1:
 
518
            raise ValueError("Must pass only one of err '%s' and details '%s"
 
519
                % (err, details))
 
520
 
 
521
    def _details_to_exc_info(self, details):
 
522
        """Convert a details dict to an exc_info tuple."""
 
523
        return (_StringException,
 
524
            _StringException(_details_to_str(details)), None)
 
525
 
 
526
    def done(self):
 
527
        try:
 
528
            return self.decorated.done()
 
529
        except AttributeError:
 
530
            return
 
531
 
 
532
    def progress(self, offset, whence):
 
533
        method = getattr(self.decorated, 'progress', None)
 
534
        if method is None:
 
535
            return
 
536
        return method(offset, whence)
 
537
 
 
538
    @property
 
539
    def shouldStop(self):
 
540
        return self.decorated.shouldStop
 
541
 
 
542
    def startTest(self, test):
 
543
        return self.decorated.startTest(test)
 
544
 
 
545
    def startTestRun(self):
 
546
        try:
 
547
            return self.decorated.startTestRun()
 
548
        except AttributeError:
 
549
            return
 
550
 
 
551
    def stop(self):
 
552
        return self.decorated.stop()
 
553
 
 
554
    def stopTest(self, test):
 
555
        return self.decorated.stopTest(test)
 
556
 
 
557
    def stopTestRun(self):
 
558
        try:
 
559
            return self.decorated.stopTestRun()
 
560
        except AttributeError:
 
561
            return
 
562
 
 
563
    def tags(self, new_tags, gone_tags):
 
564
        method = getattr(self.decorated, 'tags', None)
 
565
        if method is None:
 
566
            return
 
567
        return method(new_tags, gone_tags)
 
568
 
 
569
    def time(self, a_datetime):
 
570
        method = getattr(self.decorated, 'time', None)
 
571
        if method is None:
 
572
            return
 
573
        return method(a_datetime)
 
574
 
 
575
    def wasSuccessful(self):
 
576
        return self.decorated.wasSuccessful()
 
577
 
 
578
 
 
579
class _StringException(Exception):
 
580
    """An exception made from an arbitrary string."""
 
581
 
 
582
    if not str_is_unicode:
 
583
        def __init__(self, string):
 
584
            if type(string) is not unicode:
 
585
                raise TypeError("_StringException expects unicode, got %r" %
 
586
                    (string,))
 
587
            Exception.__init__(self, string)
 
588
 
 
589
        def __str__(self):
 
590
            return self.args[0].encode("utf-8")
 
591
 
 
592
        def __unicode__(self):
 
593
            return self.args[0]
 
594
    # For 3.0 and above the default __str__ is fine, so we don't define one.
 
595
 
 
596
    def __hash__(self):
 
597
        return id(self)
 
598
 
 
599
    def __eq__(self, other):
 
600
        try:
 
601
            return self.args == other.args
 
602
        except AttributeError:
 
603
            return False
 
604
 
 
605
 
 
606
def _details_to_str(details):
 
607
    """Convert a details dict to a string."""
 
608
    chars = []
 
609
    # sorted is for testing, may want to remove that and use a dict
 
610
    # subclass with defined order for items instead.
 
611
    for key, content in sorted(details.items()):
 
612
        if content.content_type.type != 'text':
 
613
            chars.append('Binary content: %s\n' % key)
 
614
            continue
 
615
        chars.append('Text attachment: %s\n' % key)
 
616
        chars.append('------------\n')
 
617
        chars.extend(content.iter_text())
 
618
        if not chars[-1].endswith('\n'):
 
619
            chars.append('\n')
 
620
        chars.append('------------\n')
 
621
    return _u('').join(chars)