~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/__init__.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-12-23 00:59:10 UTC
  • mfrom: (4794.1.21 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20091223005910-zatr8ajlw8ul6d6s
(robertc) Add testtools 0.9.2 as a hard dependency to run the test
        suite, improving debug output on selftest --parallel,
        reducing code size and adding support for assertThat. (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
46
46
import tempfile
47
47
import threading
48
48
import time
 
49
import traceback
49
50
import unittest
50
51
import warnings
51
52
 
 
53
import testtools
 
54
from testtools import content
52
55
 
53
56
from bzrlib import (
54
57
    branchbuilder,
229
232
                '%d non-main threads were left active in the end.\n'
230
233
                % (TestCase._active_threads - 1))
231
234
 
232
 
    def _extractBenchmarkTime(self, testCase):
 
235
    def _extractBenchmarkTime(self, testCase, details=None):
233
236
        """Add a benchmark time for the current test case."""
 
237
        if details and 'benchtime' in details:
 
238
            return float(''.join(details['benchtime'].iter_bytes()))
234
239
        return getattr(testCase, "_benchtime", None)
235
240
 
236
241
    def _elapsedTestTimeString(self):
270
275
        else:
271
276
            bzr_path = sys.executable
272
277
        self.stream.write(
273
 
            'testing: %s\n' % (bzr_path,))
 
278
            'bzr selftest: %s\n' % (bzr_path,))
274
279
        self.stream.write(
275
280
            '   %s\n' % (
276
281
                    bzrlib.__path__[0],))
321
326
            self.stop()
322
327
        self._cleanupLogFile(test)
323
328
 
324
 
    def addSuccess(self, test):
 
329
    def addSuccess(self, test, details=None):
325
330
        """Tell result that test completed successfully.
326
331
 
327
332
        Called from the TestCase run()
328
333
        """
329
334
        if self._bench_history is not None:
330
 
            benchmark_time = self._extractBenchmarkTime(test)
 
335
            benchmark_time = self._extractBenchmarkTime(test, details)
331
336
            if benchmark_time is not None:
332
337
                self._bench_history.write("%s %s\n" % (
333
338
                    self._formatTime(benchmark_time),
363
368
        self.not_applicable_count += 1
364
369
        self.report_not_applicable(test, reason)
365
370
 
366
 
    def printErrorList(self, flavour, errors):
367
 
        for test, err in errors:
368
 
            self.stream.writeln(self.separator1)
369
 
            self.stream.write("%s: " % flavour)
370
 
            self.stream.writeln(self.getDescription(test))
371
 
            if getattr(test, '_get_log', None) is not None:
372
 
                log_contents = test._get_log()
373
 
                if log_contents:
374
 
                    self.stream.write('\n')
375
 
                    self.stream.write(
376
 
                            ('vvvv[log from %s]' % test.id()).ljust(78,'-'))
377
 
                    self.stream.write('\n')
378
 
                    self.stream.write(log_contents)
379
 
                    self.stream.write('\n')
380
 
                    self.stream.write(
381
 
                            ('^^^^[log from %s]' % test.id()).ljust(78,'-'))
382
 
                    self.stream.write('\n')
383
 
            self.stream.writeln(self.separator2)
384
 
            self.stream.writeln("%s" % err)
385
 
 
386
371
    def _post_mortem(self):
387
372
        """Start a PDB post mortem session."""
388
373
        if os.environ.get('BZR_TEST_PDB', None):
499
484
            ))
500
485
 
501
486
    def report_known_failure(self, test, err):
502
 
        ui.ui_factory.note('XFAIL: %s\n%s\n' % (
503
 
            self._test_description(test), err[1]))
 
487
        pass
504
488
 
505
489
    def report_skip(self, test, reason):
506
490
        pass
604
588
            applied left to right - the first element in the list is the 
605
589
            innermost decorator.
606
590
        """
 
591
        # stream may know claim to know to write unicode strings, but in older
 
592
        # pythons this goes sufficiently wrong that it is a bad idea. (
 
593
        # specifically a built in file with encoding 'UTF-8' will still try
 
594
        # to encode using ascii.
 
595
        new_encoding = osutils.get_terminal_encoding()
 
596
        codec = codecs.lookup(new_encoding)
 
597
        if type(codec) is tuple:
 
598
            # Python 2.4
 
599
            encode = codec[0]
 
600
        else:
 
601
            encode = codec.encode
 
602
        stream = osutils.UnicodeOrBytesToBytesWriter(encode, stream)
 
603
        stream.encoding = new_encoding
607
604
        self.stream = unittest._WritelnDecorator(stream)
608
605
        self.descriptions = descriptions
609
606
        self.verbosity = verbosity
629
626
        for decorator in self._result_decorators:
630
627
            result = decorator(result)
631
628
            result.stop_early = self.stop_on_failure
632
 
        try:
633
 
            import testtools
634
 
        except ImportError:
635
 
            pass
636
 
        else:
637
 
            if isinstance(test, testtools.ConcurrentTestSuite):
638
 
                # We need to catch bzr specific behaviors
639
 
                result = BZRTransformingResult(result)
640
629
        result.startTestRun()
641
630
        try:
642
631
            test.run(result)
660
649
                        % (type(suite), suite))
661
650
 
662
651
 
663
 
class TestSkipped(Exception):
664
 
    """Indicates that a test was intentionally skipped, rather than failing."""
 
652
TestSkipped = testtools.testcase.TestSkipped
665
653
 
666
654
 
667
655
class TestNotApplicable(TestSkipped):
673
661
    """
674
662
 
675
663
 
676
 
class KnownFailure(AssertionError):
677
 
    """Indicates that a test failed in a precisely expected manner.
678
 
 
679
 
    Such failures dont block the whole test suite from passing because they are
680
 
    indicators of partially completed code or of future work. We have an
681
 
    explicit error for them so that we can ensure that they are always visible:
682
 
    KnownFailures are always shown in the output of bzr selftest.
683
 
    """
 
664
# traceback._some_str fails to format exceptions that have the default
 
665
# __str__ which does an implicit ascii conversion. However, repr() on those
 
666
# objects works, for all that its not quite what the doctor may have ordered.
 
667
def _clever_some_str(value):
 
668
    try:
 
669
        return str(value)
 
670
    except:
 
671
        try:
 
672
            return repr(value).replace('\\n', '\n')
 
673
        except:
 
674
            return '<unprintable %s object>' % type(value).__name__
 
675
 
 
676
traceback._some_str = _clever_some_str
 
677
 
 
678
 
 
679
# deprecated - use self.knownFailure(), or self.expectFailure.
 
680
KnownFailure = testtools.testcase._ExpectedFailure
684
681
 
685
682
 
686
683
class UnavailableFeature(Exception):
762
759
        return NullProgressView()
763
760
 
764
761
 
765
 
class TestCase(unittest.TestCase):
 
762
class TestCase(testtools.TestCase):
766
763
    """Base class for bzr unit tests.
767
764
 
768
765
    Tests that need access to disk resources should subclass
787
784
    _leaking_threads_tests = 0
788
785
    _first_thread_leaker_id = None
789
786
    _log_file_name = None
790
 
    _log_contents = ''
791
 
    _keep_log_file = False
792
787
    # record lsprof data when performing benchmark calls.
793
788
    _gather_lsprof_in_benchmarks = False
794
 
    attrs_to_keep = ('id', '_testMethodName', '_testMethodDoc',
795
 
                     '_log_contents', '_log_file_name', '_benchtime',
796
 
                     '_TestCase__testMethodName', '_TestCase__testMethodDoc',)
797
789
 
798
790
    def __init__(self, methodName='testMethod'):
799
791
        super(TestCase, self).__init__(methodName)
800
792
        self._cleanups = []
801
 
        self._bzr_test_setUp_run = False
802
 
        self._bzr_test_tearDown_run = False
803
793
        self._directory_isolation = True
 
794
        self.exception_handlers.insert(0,
 
795
            (UnavailableFeature, self._do_unsupported_or_skip))
 
796
        self.exception_handlers.insert(0,
 
797
            (TestNotApplicable, self._do_not_applicable))
804
798
 
805
799
    def setUp(self):
806
 
        unittest.TestCase.setUp(self)
807
 
        self._bzr_test_setUp_run = True
 
800
        super(TestCase, self).setUp()
 
801
        for feature in getattr(self, '_test_needs_features', []):
 
802
            self.requireFeature(feature)
 
803
        self._log_contents = None
 
804
        self.addDetail("log", content.Content(content.ContentType("text",
 
805
            "plain", {"charset": "utf8"}),
 
806
            lambda:[self._get_log(keep_log_file=True)]))
808
807
        self._cleanEnvironment()
809
808
        self._silenceUI()
810
809
        self._startLogFile()
1289
1288
                m += ": " + msg
1290
1289
            self.fail(m)
1291
1290
 
1292
 
    def expectFailure(self, reason, assertion, *args, **kwargs):
1293
 
        """Invoke a test, expecting it to fail for the given reason.
1294
 
 
1295
 
        This is for assertions that ought to succeed, but currently fail.
1296
 
        (The failure is *expected* but not *wanted*.)  Please be very precise
1297
 
        about the failure you're expecting.  If a new bug is introduced,
1298
 
        AssertionError should be raised, not KnownFailure.
1299
 
 
1300
 
        Frequently, expectFailure should be followed by an opposite assertion.
1301
 
        See example below.
1302
 
 
1303
 
        Intended to be used with a callable that raises AssertionError as the
1304
 
        'assertion' parameter.  args and kwargs are passed to the 'assertion'.
1305
 
 
1306
 
        Raises KnownFailure if the test fails.  Raises AssertionError if the
1307
 
        test succeeds.
1308
 
 
1309
 
        example usage::
1310
 
 
1311
 
          self.expectFailure('Math is broken', self.assertNotEqual, 54,
1312
 
                             dynamic_val)
1313
 
          self.assertEqual(42, dynamic_val)
1314
 
 
1315
 
          This means that a dynamic_val of 54 will cause the test to raise
1316
 
          a KnownFailure.  Once math is fixed and the expectFailure is removed,
1317
 
          only a dynamic_val of 42 will allow the test to pass.  Anything other
1318
 
          than 54 or 42 will cause an AssertionError.
1319
 
        """
1320
 
        try:
1321
 
            assertion(*args, **kwargs)
1322
 
        except AssertionError:
1323
 
            raise KnownFailure(reason)
1324
 
        else:
1325
 
            self.fail('Unexpected success.  Should have failed: %s' % reason)
1326
 
 
1327
1291
    def assertFileEqual(self, content, path):
1328
1292
        """Fail if path does not contain 'content'."""
1329
1293
        self.failUnlessExists(path)
1479
1443
 
1480
1444
        Close the file and delete it, unless setKeepLogfile was called.
1481
1445
        """
1482
 
        if self._log_file is None:
1483
 
            return
 
1446
        if bzrlib.trace._trace_file:
 
1447
            # flush the log file, to get all content
 
1448
            bzrlib.trace._trace_file.flush()
1484
1449
        bzrlib.trace.pop_log_file(self._log_memento)
1485
 
        self._log_file.close()
1486
 
        self._log_file = None
1487
 
        if not self._keep_log_file:
1488
 
            os.remove(self._log_file_name)
1489
 
            self._log_file_name = None
1490
 
 
1491
 
    def setKeepLogfile(self):
1492
 
        """Make the logfile not be deleted when _finishLogFile is called."""
1493
 
        self._keep_log_file = True
 
1450
        # Cache the log result and delete the file on disk
 
1451
        self._get_log(False)
1494
1452
 
1495
1453
    def thisFailsStrictLockCheck(self):
1496
1454
        """It is known that this test would fail with -Dstrict_locks.
1587
1545
        else:
1588
1546
            addSkip(self, reason)
1589
1547
 
1590
 
    def _do_known_failure(self, result):
 
1548
    @staticmethod
 
1549
    def _do_known_failure(self, result, e):
1591
1550
        err = sys.exc_info()
1592
1551
        addExpectedFailure = getattr(result, 'addExpectedFailure', None)
1593
1552
        if addExpectedFailure is not None:
1595
1554
        else:
1596
1555
            result.addSuccess(self)
1597
1556
 
 
1557
    @staticmethod
1598
1558
    def _do_not_applicable(self, result, e):
1599
1559
        if not e.args:
1600
1560
            reason = 'No reason given'
1606
1566
        else:
1607
1567
            self._do_skip(result, reason)
1608
1568
 
1609
 
    def _do_unsupported_or_skip(self, result, reason):
 
1569
    @staticmethod
 
1570
    def _do_unsupported_or_skip(self, result, e):
 
1571
        reason = e.args[0]
1610
1572
        addNotSupported = getattr(result, 'addNotSupported', None)
1611
1573
        if addNotSupported is not None:
1612
1574
            result.addNotSupported(self, reason)
1613
1575
        else:
1614
1576
            self._do_skip(result, reason)
1615
1577
 
1616
 
    def run(self, result=None):
1617
 
        if result is None: result = self.defaultTestResult()
1618
 
        result.startTest(self)
1619
 
        try:
1620
 
            self._run(result)
1621
 
            return result
1622
 
        finally:
1623
 
            result.stopTest(self)
1624
 
 
1625
 
    def _run(self, result):
1626
 
        for feature in getattr(self, '_test_needs_features', []):
1627
 
            if not feature.available():
1628
 
                return self._do_unsupported_or_skip(result, feature)
1629
 
        try:
1630
 
            absent_attr = object()
1631
 
            # Python 2.5
1632
 
            method_name = getattr(self, '_testMethodName', absent_attr)
1633
 
            if method_name is absent_attr:
1634
 
                # Python 2.4
1635
 
                method_name = getattr(self, '_TestCase__testMethodName')
1636
 
            testMethod = getattr(self, method_name)
1637
 
            try:
1638
 
                try:
1639
 
                    self.setUp()
1640
 
                    if not self._bzr_test_setUp_run:
1641
 
                        self.fail(
1642
 
                            "test setUp did not invoke "
1643
 
                            "bzrlib.tests.TestCase's setUp")
1644
 
                except KeyboardInterrupt:
1645
 
                    self._runCleanups()
1646
 
                    raise
1647
 
                except KnownFailure:
1648
 
                    self._do_known_failure(result)
1649
 
                    self.tearDown()
1650
 
                    return
1651
 
                except TestNotApplicable, e:
1652
 
                    self._do_not_applicable(result, e)
1653
 
                    self.tearDown()
1654
 
                    return
1655
 
                except TestSkipped, e:
1656
 
                    self._do_skip(result, e.args[0])
1657
 
                    self.tearDown()
1658
 
                    return result
1659
 
                except UnavailableFeature, e:
1660
 
                    self._do_unsupported_or_skip(result, e.args[0])
1661
 
                    self.tearDown()
1662
 
                    return
1663
 
                except:
1664
 
                    result.addError(self, sys.exc_info())
1665
 
                    self._runCleanups()
1666
 
                    return result
1667
 
 
1668
 
                ok = False
1669
 
                try:
1670
 
                    testMethod()
1671
 
                    ok = True
1672
 
                except KnownFailure:
1673
 
                    self._do_known_failure(result)
1674
 
                except self.failureException:
1675
 
                    result.addFailure(self, sys.exc_info())
1676
 
                except TestNotApplicable, e:
1677
 
                    self._do_not_applicable(result, e)
1678
 
                except TestSkipped, e:
1679
 
                    if not e.args:
1680
 
                        reason = "No reason given."
1681
 
                    else:
1682
 
                        reason = e.args[0]
1683
 
                    self._do_skip(result, reason)
1684
 
                except UnavailableFeature, e:
1685
 
                    self._do_unsupported_or_skip(result, e.args[0])
1686
 
                except KeyboardInterrupt:
1687
 
                    self._runCleanups()
1688
 
                    raise
1689
 
                except:
1690
 
                    result.addError(self, sys.exc_info())
1691
 
 
1692
 
                try:
1693
 
                    self.tearDown()
1694
 
                    if not self._bzr_test_tearDown_run:
1695
 
                        self.fail(
1696
 
                            "test tearDown did not invoke "
1697
 
                            "bzrlib.tests.TestCase's tearDown")
1698
 
                except KeyboardInterrupt:
1699
 
                    self._runCleanups()
1700
 
                    raise
1701
 
                except:
1702
 
                    result.addError(self, sys.exc_info())
1703
 
                    self._runCleanups()
1704
 
                    ok = False
1705
 
                if ok: result.addSuccess(self)
1706
 
                return result
1707
 
            except KeyboardInterrupt:
1708
 
                self._runCleanups()
1709
 
                raise
1710
 
        finally:
1711
 
            saved_attrs = {}
1712
 
            for attr_name in self.attrs_to_keep:
1713
 
                if attr_name in self.__dict__:
1714
 
                    saved_attrs[attr_name] = self.__dict__[attr_name]
1715
 
            self.__dict__ = saved_attrs
1716
 
 
1717
 
    def tearDown(self):
1718
 
        self._runCleanups()
1719
 
        self._log_contents = ''
1720
 
        self._bzr_test_tearDown_run = True
1721
 
        unittest.TestCase.tearDown(self)
1722
 
 
1723
1578
    def time(self, callable, *args, **kwargs):
1724
1579
        """Run callable and accrue the time it takes to the benchmark time.
1725
1580
 
1728
1583
        self._benchcalls.
1729
1584
        """
1730
1585
        if self._benchtime is None:
 
1586
            self.addDetail('benchtime', content.Content(content.ContentType(
 
1587
                "text", "plain"), lambda:[str(self._benchtime)]))
1731
1588
            self._benchtime = 0
1732
1589
        start = time.time()
1733
1590
        try:
1742
1599
        finally:
1743
1600
            self._benchtime += time.time() - start
1744
1601
 
1745
 
    def _runCleanups(self):
1746
 
        """Run registered cleanup functions.
1747
 
 
1748
 
        This should only be called from TestCase.tearDown.
1749
 
        """
1750
 
        # TODO: Perhaps this should keep running cleanups even if
1751
 
        # one of them fails?
1752
 
 
1753
 
        # Actually pop the cleanups from the list so tearDown running
1754
 
        # twice is safe (this happens for skipped tests).
1755
 
        while self._cleanups:
1756
 
            cleanup, args, kwargs = self._cleanups.pop()
1757
 
            cleanup(*args, **kwargs)
1758
 
 
1759
1602
    def log(self, *args):
1760
1603
        mutter(*args)
1761
1604
 
1762
1605
    def _get_log(self, keep_log_file=False):
1763
 
        """Get the log from bzrlib.trace calls from this test.
 
1606
        """Internal helper to get the log from bzrlib.trace for this test.
 
1607
 
 
1608
        Please use self.getDetails, or self.get_log to access this in test case
 
1609
        code.
1764
1610
 
1765
1611
        :param keep_log_file: When True, if the log is still a file on disk
1766
1612
            leave it as a file on disk. When False, if the log is still a file
1768
1614
            self._log_contents.
1769
1615
        :return: A string containing the log.
1770
1616
        """
1771
 
        # flush the log file, to get all content
 
1617
        if self._log_contents is not None:
 
1618
            try:
 
1619
                self._log_contents.decode('utf8')
 
1620
            except UnicodeDecodeError:
 
1621
                unicodestr = self._log_contents.decode('utf8', 'replace')
 
1622
                self._log_contents = unicodestr.encode('utf8')
 
1623
            return self._log_contents
1772
1624
        import bzrlib.trace
1773
1625
        if bzrlib.trace._trace_file:
 
1626
            # flush the log file, to get all content
1774
1627
            bzrlib.trace._trace_file.flush()
1775
 
        if self._log_contents:
1776
 
            # XXX: this can hardly contain the content flushed above --vila
1777
 
            # 20080128
1778
 
            return self._log_contents
1779
1628
        if self._log_file_name is not None:
1780
1629
            logfile = open(self._log_file_name)
1781
1630
            try:
1782
1631
                log_contents = logfile.read()
1783
1632
            finally:
1784
1633
                logfile.close()
 
1634
            try:
 
1635
                log_contents.decode('utf8')
 
1636
            except UnicodeDecodeError:
 
1637
                unicodestr = log_contents.decode('utf8', 'replace')
 
1638
                log_contents = unicodestr.encode('utf8')
1785
1639
            if not keep_log_file:
 
1640
                self._log_file.close()
 
1641
                self._log_file = None
 
1642
                # Permit multiple calls to get_log until we clean it up in
 
1643
                # finishLogFile
1786
1644
                self._log_contents = log_contents
1787
1645
                try:
1788
1646
                    os.remove(self._log_file_name)
1792
1650
                                             ' %r\n' % self._log_file_name))
1793
1651
                    else:
1794
1652
                        raise
 
1653
                self._log_file_name = None
1795
1654
            return log_contents
1796
1655
        else:
1797
 
            return "DELETED log file to reduce memory footprint"
 
1656
            return "No log file content and no log file name."
 
1657
 
 
1658
    def get_log(self):
 
1659
        """Get a unicode string containing the log from bzrlib.trace.
 
1660
 
 
1661
        Undecodable characters are replaced.
 
1662
        """
 
1663
        return u"".join(self.getDetails()['log'].iter_text())
1798
1664
 
1799
1665
    def requireFeature(self, feature):
1800
1666
        """This test requires a specific feature is available.
3219
3085
        if self.randomised:
3220
3086
            return iter(self._tests)
3221
3087
        self.randomised = True
3222
 
        self.stream.writeln("Randomizing test order using seed %s\n" %
 
3088
        self.stream.write("Randomizing test order using seed %s\n\n" %
3223
3089
            (self.actual_seed()))
3224
3090
        # Initialise the random number generator.
3225
3091
        random.seed(self.actual_seed())
3282
3148
    concurrency = osutils.local_concurrency()
3283
3149
    result = []
3284
3150
    from subunit import TestProtocolClient, ProtocolTestCase
 
3151
    from subunit.test_results import AutoTimingTestResultDecorator
3285
3152
    class TestInOtherProcess(ProtocolTestCase):
3286
3153
        # Should be in subunit, I think. RBC.
3287
3154
        def __init__(self, stream, pid):
3310
3177
                sys.stdin.close()
3311
3178
                sys.stdin = None
3312
3179
                stream = os.fdopen(c2pwrite, 'wb', 1)
3313
 
                subunit_result = BzrAutoTimingTestResultDecorator(
 
3180
                subunit_result = AutoTimingTestResultDecorator(
3314
3181
                    TestProtocolClient(stream))
3315
3182
                process_suite.run(subunit_result)
3316
3183
            finally:
3408
3275
 
3409
3276
    def addFailure(self, test, err):
3410
3277
        self.result.addFailure(test, err)
3411
 
 
3412
 
 
3413
 
class BZRTransformingResult(ForwardingResult):
3414
 
 
3415
 
    def addError(self, test, err):
3416
 
        feature = self._error_looks_like('UnavailableFeature: ', err)
3417
 
        if feature is not None:
3418
 
            self.result.addNotSupported(test, feature)
3419
 
        else:
3420
 
            self.result.addError(test, err)
3421
 
 
3422
 
    def addFailure(self, test, err):
3423
 
        known = self._error_looks_like('KnownFailure: ', err)
3424
 
        if known is not None:
3425
 
            self.result.addExpectedFailure(test,
3426
 
                [KnownFailure, KnownFailure(known), None])
3427
 
        else:
3428
 
            self.result.addFailure(test, err)
3429
 
 
3430
 
    def _error_looks_like(self, prefix, err):
3431
 
        """Deserialize exception and returns the stringify value."""
3432
 
        import subunit
3433
 
        value = None
3434
 
        typ, exc, _ = err
3435
 
        if isinstance(exc, subunit.RemoteException):
3436
 
            # stringify the exception gives access to the remote traceback
3437
 
            # We search the last line for 'prefix'
3438
 
            lines = str(exc).split('\n')
3439
 
            while lines and not lines[-1]:
3440
 
                lines.pop(-1)
3441
 
            if lines:
3442
 
                if lines[-1].startswith(prefix):
3443
 
                    value = lines[-1][len(prefix):]
3444
 
        return value
3445
 
 
3446
 
 
3447
 
try:
3448
 
    from subunit.test_results import AutoTimingTestResultDecorator
3449
 
    # Expected failure should be seen as a success not a failure Once subunit
3450
 
    # provide native support for that, BZRTransformingResult and this class
3451
 
    # will become useless.
3452
 
    class BzrAutoTimingTestResultDecorator(AutoTimingTestResultDecorator):
3453
 
 
3454
 
        def addExpectedFailure(self, test, err):
3455
 
            self._before_event()
3456
 
            return self._call_maybe("addExpectedFailure", self._degrade_skip,
3457
 
                                    test, err)
3458
 
except ImportError:
3459
 
    # Let's just define a no-op decorator
3460
 
    BzrAutoTimingTestResultDecorator = lambda x:x
 
3278
ForwardingResult = testtools.ExtendedToOriginalDecorator
3461
3279
 
3462
3280
 
3463
3281
class ProfileResult(ForwardingResult):
4482
4300
# Only define SubUnitBzrRunner if subunit is available.
4483
4301
try:
4484
4302
    from subunit import TestProtocolClient
 
4303
    from subunit.test_results import AutoTimingTestResultDecorator
4485
4304
    class SubUnitBzrRunner(TextTestRunner):
4486
4305
        def run(self, test):
4487
 
            result = BzrAutoTimingTestResultDecorator(
 
4306
            result = AutoTimingTestResultDecorator(
4488
4307
                TestProtocolClient(self.stream))
4489
4308
            test.run(result)
4490
4309
            return result