~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/__init__.py

  • Committer: John Arbash Meinel
  • Date: 2010-02-17 17:11:16 UTC
  • mfrom: (4797.2.17 2.1)
  • mto: (4797.2.18 2.1)
  • mto: This revision was merged to the branch mainline in revision 5055.
  • Revision ID: john@arbash-meinel.com-20100217171116-h7t9223ystbnx5h8
merge bzr.2.1 in preparation for NEWS entry.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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
# nb: check this before importing anything else from within it
 
55
_testtools_version = getattr(testtools, '__version__', ())
 
56
if _testtools_version < (0, 9, 2):
 
57
    raise ImportError("need at least testtools 0.9.2: %s is %r"
 
58
        % (testtools.__file__, _testtools_version))
 
59
from testtools import content
52
60
 
53
61
from bzrlib import (
54
62
    branchbuilder,
55
63
    bzrdir,
 
64
    chk_map,
56
65
    config,
57
66
    debug,
58
67
    errors,
87
96
from bzrlib.symbol_versioning import (
88
97
    DEPRECATED_PARAMETER,
89
98
    deprecated_function,
 
99
    deprecated_in,
90
100
    deprecated_method,
91
101
    deprecated_passed,
92
102
    )
227
237
                '%d non-main threads were left active in the end.\n'
228
238
                % (TestCase._active_threads - 1))
229
239
 
230
 
    def _extractBenchmarkTime(self, testCase):
 
240
    def getDescription(self, test):
 
241
        return test.id()
 
242
 
 
243
    def _extractBenchmarkTime(self, testCase, details=None):
231
244
        """Add a benchmark time for the current test case."""
 
245
        if details and 'benchtime' in details:
 
246
            return float(''.join(details['benchtime'].iter_bytes()))
232
247
        return getattr(testCase, "_benchtime", None)
233
248
 
234
249
    def _elapsedTestTimeString(self):
268
283
        else:
269
284
            bzr_path = sys.executable
270
285
        self.stream.write(
271
 
            'testing: %s\n' % (bzr_path,))
 
286
            'bzr selftest: %s\n' % (bzr_path,))
272
287
        self.stream.write(
273
288
            '   %s\n' % (
274
289
                    bzrlib.__path__[0],))
297
312
        Called from the TestCase run() method when the test
298
313
        fails with an unexpected error.
299
314
        """
300
 
        self._testConcluded(test)
301
 
        if isinstance(err[1], TestNotApplicable):
302
 
            return self._addNotApplicable(test, err)
303
 
        elif isinstance(err[1], UnavailableFeature):
304
 
            return self.addNotSupported(test, err[1].args[0])
305
 
        else:
306
 
            self._post_mortem()
307
 
            unittest.TestResult.addError(self, test, err)
308
 
            self.error_count += 1
309
 
            self.report_error(test, err)
310
 
            if self.stop_early:
311
 
                self.stop()
312
 
            self._cleanupLogFile(test)
 
315
        self._post_mortem()
 
316
        unittest.TestResult.addError(self, test, err)
 
317
        self.error_count += 1
 
318
        self.report_error(test, err)
 
319
        if self.stop_early:
 
320
            self.stop()
 
321
        self._cleanupLogFile(test)
313
322
 
314
323
    def addFailure(self, test, err):
315
324
        """Tell result that test failed.
317
326
        Called from the TestCase run() method when the test
318
327
        fails because e.g. an assert() method failed.
319
328
        """
320
 
        self._testConcluded(test)
321
 
        if isinstance(err[1], KnownFailure):
322
 
            return self._addKnownFailure(test, err)
323
 
        else:
324
 
            self._post_mortem()
325
 
            unittest.TestResult.addFailure(self, test, err)
326
 
            self.failure_count += 1
327
 
            self.report_failure(test, err)
328
 
            if self.stop_early:
329
 
                self.stop()
330
 
            self._cleanupLogFile(test)
 
329
        self._post_mortem()
 
330
        unittest.TestResult.addFailure(self, test, err)
 
331
        self.failure_count += 1
 
332
        self.report_failure(test, err)
 
333
        if self.stop_early:
 
334
            self.stop()
 
335
        self._cleanupLogFile(test)
331
336
 
332
 
    def addSuccess(self, test):
 
337
    def addSuccess(self, test, details=None):
333
338
        """Tell result that test completed successfully.
334
339
 
335
340
        Called from the TestCase run()
336
341
        """
337
 
        self._testConcluded(test)
338
342
        if self._bench_history is not None:
339
 
            benchmark_time = self._extractBenchmarkTime(test)
 
343
            benchmark_time = self._extractBenchmarkTime(test, details)
340
344
            if benchmark_time is not None:
341
345
                self._bench_history.write("%s %s\n" % (
342
346
                    self._formatTime(benchmark_time),
346
350
        unittest.TestResult.addSuccess(self, test)
347
351
        test._log_contents = ''
348
352
 
349
 
    def _testConcluded(self, test):
350
 
        """Common code when a test has finished.
351
 
 
352
 
        Called regardless of whether it succeded, failed, etc.
353
 
        """
354
 
        pass
355
 
 
356
 
    def _addKnownFailure(self, test, err):
 
353
    def addExpectedFailure(self, test, err):
357
354
        self.known_failure_count += 1
358
355
        self.report_known_failure(test, err)
359
356
 
361
358
        """The test will not be run because of a missing feature.
362
359
        """
363
360
        # this can be called in two different ways: it may be that the
364
 
        # test started running, and then raised (through addError)
 
361
        # test started running, and then raised (through requireFeature)
365
362
        # UnavailableFeature.  Alternatively this method can be called
366
 
        # while probing for features before running the tests; in that
367
 
        # case we will see startTest and stopTest, but the test will never
368
 
        # actually run.
 
363
        # while probing for features before running the test code proper; in
 
364
        # that case we will see startTest and stopTest, but the test will
 
365
        # never actually run.
369
366
        self.unsupported.setdefault(str(feature), 0)
370
367
        self.unsupported[str(feature)] += 1
371
368
        self.report_unsupported(test, feature)
375
372
        self.skip_count += 1
376
373
        self.report_skip(test, reason)
377
374
 
378
 
    def _addNotApplicable(self, test, skip_excinfo):
379
 
        if isinstance(skip_excinfo[1], TestNotApplicable):
380
 
            self.not_applicable_count += 1
381
 
            self.report_not_applicable(test, skip_excinfo)
382
 
        try:
383
 
            test.tearDown()
384
 
        except KeyboardInterrupt:
385
 
            raise
386
 
        except:
387
 
            self.addError(test, test.exc_info())
388
 
        else:
389
 
            # seems best to treat this as success from point-of-view of unittest
390
 
            # -- it actually does nothing so it barely matters :)
391
 
            unittest.TestResult.addSuccess(self, test)
392
 
            test._log_contents = ''
393
 
 
394
 
    def printErrorList(self, flavour, errors):
395
 
        for test, err in errors:
396
 
            self.stream.writeln(self.separator1)
397
 
            self.stream.write("%s: " % flavour)
398
 
            self.stream.writeln(self.getDescription(test))
399
 
            if getattr(test, '_get_log', None) is not None:
400
 
                log_contents = test._get_log()
401
 
                if log_contents:
402
 
                    self.stream.write('\n')
403
 
                    self.stream.write(
404
 
                            ('vvvv[log from %s]' % test.id()).ljust(78,'-'))
405
 
                    self.stream.write('\n')
406
 
                    self.stream.write(log_contents)
407
 
                    self.stream.write('\n')
408
 
                    self.stream.write(
409
 
                            ('^^^^[log from %s]' % test.id()).ljust(78,'-'))
410
 
                    self.stream.write('\n')
411
 
            self.stream.writeln(self.separator2)
412
 
            self.stream.writeln("%s" % err)
 
375
    def addNotApplicable(self, test, reason):
 
376
        self.not_applicable_count += 1
 
377
        self.report_not_applicable(test, reason)
413
378
 
414
379
    def _post_mortem(self):
415
380
        """Start a PDB post mortem session."""
496
461
            a += '%dm%ds' % (runtime / 60, runtime % 60)
497
462
        else:
498
463
            a += '%ds' % runtime
499
 
        if self.error_count:
500
 
            a += ', %d err' % self.error_count
501
 
        if self.failure_count:
502
 
            a += ', %d fail' % self.failure_count
 
464
        total_fail_count = self.error_count + self.failure_count
 
465
        if total_fail_count:
 
466
            a += ', %d failed' % total_fail_count
503
467
        # if self.unsupported:
504
468
        #     a += ', %d missing' % len(self.unsupported)
505
469
        a += ']'
528
492
            ))
529
493
 
530
494
    def report_known_failure(self, test, err):
531
 
        ui.ui_factory.note('XFAIL: %s\n%s\n' % (
532
 
            self._test_description(test), err[1]))
 
495
        pass
533
496
 
534
497
    def report_skip(self, test, reason):
535
498
        pass
536
499
 
537
 
    def report_not_applicable(self, test, skip_excinfo):
 
500
    def report_not_applicable(self, test, reason):
538
501
        pass
539
502
 
540
503
    def report_unsupported(self, test, feature):
562
525
    def report_test_start(self, test):
563
526
        self.count += 1
564
527
        name = self._shortened_test_description(test)
565
 
        # width needs space for 6 char status, plus 1 for slash, plus an
566
 
        # 11-char time string, plus a trailing blank
567
 
        # when NUMBERED_DIRS: plus 5 chars on test number, plus 1 char on space
568
 
        self.stream.write(self._ellipsize_to_right(name,
569
 
                          osutils.terminal_width()-18))
 
528
        width = osutils.terminal_width()
 
529
        if width is not None:
 
530
            # width needs space for 6 char status, plus 1 for slash, plus an
 
531
            # 11-char time string, plus a trailing blank
 
532
            # when NUMBERED_DIRS: plus 5 chars on test number, plus 1 char on
 
533
            # space
 
534
            self.stream.write(self._ellipsize_to_right(name, width-18))
 
535
        else:
 
536
            self.stream.write(name)
570
537
        self.stream.flush()
571
538
 
572
539
    def _error_summary(self, err):
601
568
        self.stream.writeln(' SKIP %s\n%s'
602
569
                % (self._testTimeString(test), reason))
603
570
 
604
 
    def report_not_applicable(self, test, skip_excinfo):
605
 
        self.stream.writeln('  N/A %s\n%s'
606
 
                % (self._testTimeString(test),
607
 
                   self._error_summary(skip_excinfo)))
 
571
    def report_not_applicable(self, test, reason):
 
572
        self.stream.writeln('  N/A %s\n    %s'
 
573
                % (self._testTimeString(test), reason))
608
574
 
609
575
    def report_unsupported(self, test, feature):
610
576
        """test cannot be run because feature is missing."""
630
596
            applied left to right - the first element in the list is the 
631
597
            innermost decorator.
632
598
        """
 
599
        # stream may know claim to know to write unicode strings, but in older
 
600
        # pythons this goes sufficiently wrong that it is a bad idea. (
 
601
        # specifically a built in file with encoding 'UTF-8' will still try
 
602
        # to encode using ascii.
 
603
        new_encoding = osutils.get_terminal_encoding()
 
604
        codec = codecs.lookup(new_encoding)
 
605
        if type(codec) is tuple:
 
606
            # Python 2.4
 
607
            encode = codec[0]
 
608
        else:
 
609
            encode = codec.encode
 
610
        stream = osutils.UnicodeOrBytesToBytesWriter(encode, stream)
 
611
        stream.encoding = new_encoding
633
612
        self.stream = unittest._WritelnDecorator(stream)
634
613
        self.descriptions = descriptions
635
614
        self.verbosity = verbosity
655
634
        for decorator in self._result_decorators:
656
635
            result = decorator(result)
657
636
            result.stop_early = self.stop_on_failure
658
 
        try:
659
 
            import testtools
660
 
        except ImportError:
661
 
            pass
662
 
        else:
663
 
            if isinstance(test, testtools.ConcurrentTestSuite):
664
 
                # We need to catch bzr specific behaviors
665
 
                result = BZRTransformingResult(result)
666
637
        result.startTestRun()
667
638
        try:
668
639
            test.run(result)
686
657
                        % (type(suite), suite))
687
658
 
688
659
 
689
 
class TestSkipped(Exception):
690
 
    """Indicates that a test was intentionally skipped, rather than failing."""
 
660
TestSkipped = testtools.testcase.TestSkipped
691
661
 
692
662
 
693
663
class TestNotApplicable(TestSkipped):
699
669
    """
700
670
 
701
671
 
702
 
class KnownFailure(AssertionError):
703
 
    """Indicates that a test failed in a precisely expected manner.
704
 
 
705
 
    Such failures dont block the whole test suite from passing because they are
706
 
    indicators of partially completed code or of future work. We have an
707
 
    explicit error for them so that we can ensure that they are always visible:
708
 
    KnownFailures are always shown in the output of bzr selftest.
709
 
    """
 
672
# traceback._some_str fails to format exceptions that have the default
 
673
# __str__ which does an implicit ascii conversion. However, repr() on those
 
674
# objects works, for all that its not quite what the doctor may have ordered.
 
675
def _clever_some_str(value):
 
676
    try:
 
677
        return str(value)
 
678
    except:
 
679
        try:
 
680
            return repr(value).replace('\\n', '\n')
 
681
        except:
 
682
            return '<unprintable %s object>' % type(value).__name__
 
683
 
 
684
traceback._some_str = _clever_some_str
 
685
 
 
686
 
 
687
# deprecated - use self.knownFailure(), or self.expectFailure.
 
688
KnownFailure = testtools.testcase._ExpectedFailure
710
689
 
711
690
 
712
691
class UnavailableFeature(Exception):
713
692
    """A feature required for this test was not available.
714
693
 
 
694
    This can be considered a specialised form of SkippedTest.
 
695
 
715
696
    The feature should be used to construct the exception.
716
697
    """
717
698
 
786
767
        return NullProgressView()
787
768
 
788
769
 
789
 
class TestCase(unittest.TestCase):
 
770
class TestCase(testtools.TestCase):
790
771
    """Base class for bzr unit tests.
791
772
 
792
773
    Tests that need access to disk resources should subclass
811
792
    _leaking_threads_tests = 0
812
793
    _first_thread_leaker_id = None
813
794
    _log_file_name = None
814
 
    _log_contents = ''
815
 
    _keep_log_file = False
816
795
    # record lsprof data when performing benchmark calls.
817
796
    _gather_lsprof_in_benchmarks = False
818
 
    attrs_to_keep = ('id', '_testMethodName', '_testMethodDoc',
819
 
                     '_log_contents', '_log_file_name', '_benchtime',
820
 
                     '_TestCase__testMethodName', '_TestCase__testMethodDoc',)
821
797
 
822
798
    def __init__(self, methodName='testMethod'):
823
799
        super(TestCase, self).__init__(methodName)
824
800
        self._cleanups = []
825
 
        self._bzr_test_setUp_run = False
826
 
        self._bzr_test_tearDown_run = False
827
801
        self._directory_isolation = True
 
802
        self.exception_handlers.insert(0,
 
803
            (UnavailableFeature, self._do_unsupported_or_skip))
 
804
        self.exception_handlers.insert(0,
 
805
            (TestNotApplicable, self._do_not_applicable))
828
806
 
829
807
    def setUp(self):
830
 
        unittest.TestCase.setUp(self)
831
 
        self._bzr_test_setUp_run = True
 
808
        super(TestCase, self).setUp()
 
809
        for feature in getattr(self, '_test_needs_features', []):
 
810
            self.requireFeature(feature)
 
811
        self._log_contents = None
 
812
        self.addDetail("log", content.Content(content.ContentType("text",
 
813
            "plain", {"charset": "utf8"}),
 
814
            lambda:[self._get_log(keep_log_file=True)]))
832
815
        self._cleanEnvironment()
833
816
        self._silenceUI()
834
817
        self._startLogFile()
938
921
            self._lock_check_thorough = False
939
922
        else:
940
923
            self._lock_check_thorough = True
941
 
            
 
924
 
942
925
        self.addCleanup(self._check_locks)
943
926
        _mod_lock.Lock.hooks.install_named_hook('lock_acquired',
944
927
                                                self._lock_acquired, None)
1037
1020
        server's urls to be used.
1038
1021
        """
1039
1022
        if backing_server is None:
1040
 
            transport_server.setUp()
 
1023
            transport_server.start_server()
1041
1024
        else:
1042
 
            transport_server.setUp(backing_server)
1043
 
        self.addCleanup(transport_server.tearDown)
 
1025
            transport_server.start_server(backing_server)
 
1026
        self.addCleanup(transport_server.stop_server)
1044
1027
        # Obtain a real transport because if the server supplies a password, it
1045
1028
        # will be hidden from the base on the client side.
1046
1029
        t = get_transport(transport_server.get_url())
1143
1126
        :raises AssertionError: If the expected and actual stat values differ
1144
1127
            other than by atime.
1145
1128
        """
1146
 
        self.assertEqual(expected.st_size, actual.st_size)
1147
 
        self.assertEqual(expected.st_mtime, actual.st_mtime)
1148
 
        self.assertEqual(expected.st_ctime, actual.st_ctime)
1149
 
        self.assertEqual(expected.st_dev, actual.st_dev)
1150
 
        self.assertEqual(expected.st_ino, actual.st_ino)
1151
 
        self.assertEqual(expected.st_mode, actual.st_mode)
 
1129
        self.assertEqual(expected.st_size, actual.st_size,
 
1130
                         'st_size did not match')
 
1131
        self.assertEqual(expected.st_mtime, actual.st_mtime,
 
1132
                         'st_mtime did not match')
 
1133
        self.assertEqual(expected.st_ctime, actual.st_ctime,
 
1134
                         'st_ctime did not match')
 
1135
        if sys.platform != 'win32':
 
1136
            # On Win32 both 'dev' and 'ino' cannot be trusted. In python2.4 it
 
1137
            # is 'dev' that varies, in python 2.5 (6?) it is st_ino that is
 
1138
            # odd. Regardless we shouldn't actually try to assert anything
 
1139
            # about their values
 
1140
            self.assertEqual(expected.st_dev, actual.st_dev,
 
1141
                             'st_dev did not match')
 
1142
            self.assertEqual(expected.st_ino, actual.st_ino,
 
1143
                             'st_ino did not match')
 
1144
        self.assertEqual(expected.st_mode, actual.st_mode,
 
1145
                         'st_mode did not match')
1152
1146
 
1153
1147
    def assertLength(self, length, obj_with_len):
1154
1148
        """Assert that obj_with_len is of length length."""
1302
1296
                m += ": " + msg
1303
1297
            self.fail(m)
1304
1298
 
1305
 
    def expectFailure(self, reason, assertion, *args, **kwargs):
1306
 
        """Invoke a test, expecting it to fail for the given reason.
1307
 
 
1308
 
        This is for assertions that ought to succeed, but currently fail.
1309
 
        (The failure is *expected* but not *wanted*.)  Please be very precise
1310
 
        about the failure you're expecting.  If a new bug is introduced,
1311
 
        AssertionError should be raised, not KnownFailure.
1312
 
 
1313
 
        Frequently, expectFailure should be followed by an opposite assertion.
1314
 
        See example below.
1315
 
 
1316
 
        Intended to be used with a callable that raises AssertionError as the
1317
 
        'assertion' parameter.  args and kwargs are passed to the 'assertion'.
1318
 
 
1319
 
        Raises KnownFailure if the test fails.  Raises AssertionError if the
1320
 
        test succeeds.
1321
 
 
1322
 
        example usage::
1323
 
 
1324
 
          self.expectFailure('Math is broken', self.assertNotEqual, 54,
1325
 
                             dynamic_val)
1326
 
          self.assertEqual(42, dynamic_val)
1327
 
 
1328
 
          This means that a dynamic_val of 54 will cause the test to raise
1329
 
          a KnownFailure.  Once math is fixed and the expectFailure is removed,
1330
 
          only a dynamic_val of 42 will allow the test to pass.  Anything other
1331
 
          than 54 or 42 will cause an AssertionError.
1332
 
        """
1333
 
        try:
1334
 
            assertion(*args, **kwargs)
1335
 
        except AssertionError:
1336
 
            raise KnownFailure(reason)
1337
 
        else:
1338
 
            self.fail('Unexpected success.  Should have failed: %s' % reason)
1339
 
 
1340
1299
    def assertFileEqual(self, content, path):
1341
1300
        """Fail if path does not contain 'content'."""
1342
1301
        self.failUnlessExists(path)
1492
1451
 
1493
1452
        Close the file and delete it, unless setKeepLogfile was called.
1494
1453
        """
1495
 
        if self._log_file is None:
1496
 
            return
 
1454
        if bzrlib.trace._trace_file:
 
1455
            # flush the log file, to get all content
 
1456
            bzrlib.trace._trace_file.flush()
1497
1457
        bzrlib.trace.pop_log_file(self._log_memento)
1498
 
        self._log_file.close()
1499
 
        self._log_file = None
1500
 
        if not self._keep_log_file:
1501
 
            os.remove(self._log_file_name)
1502
 
            self._log_file_name = None
1503
 
 
1504
 
    def setKeepLogfile(self):
1505
 
        """Make the logfile not be deleted when _finishLogFile is called."""
1506
 
        self._keep_log_file = True
 
1458
        # Cache the log result and delete the file on disk
 
1459
        self._get_log(False)
1507
1460
 
1508
1461
    def thisFailsStrictLockCheck(self):
1509
1462
        """It is known that this test would fail with -Dstrict_locks.
1541
1494
            'BZR_PROGRESS_BAR': None,
1542
1495
            'BZR_LOG': None,
1543
1496
            'BZR_PLUGIN_PATH': None,
 
1497
            'BZR_CONCURRENCY': None,
1544
1498
            # Make sure that any text ui tests are consistent regardless of
1545
1499
            # the environment the test case is run in; you may want tests that
1546
1500
            # test other combinations.  'dumb' is a reasonable guess for tests
1548
1502
            'TERM': 'dumb',
1549
1503
            'LINES': '25',
1550
1504
            'COLUMNS': '80',
 
1505
            'BZR_COLUMNS': '80',
1551
1506
            # SSH Agent
1552
1507
            'SSH_AUTH_SOCK': None,
1553
1508
            # Proxies
1594
1549
    def _do_skip(self, result, reason):
1595
1550
        addSkip = getattr(result, 'addSkip', None)
1596
1551
        if not callable(addSkip):
1597
 
            result.addError(self, sys.exc_info())
 
1552
            result.addSuccess(result)
1598
1553
        else:
1599
1554
            addSkip(self, reason)
1600
1555
 
1601
 
    def run(self, result=None):
1602
 
        if result is None: result = self.defaultTestResult()
1603
 
        for feature in getattr(self, '_test_needs_features', []):
1604
 
            if not feature.available():
1605
 
                result.startTest(self)
1606
 
                if getattr(result, 'addNotSupported', None):
1607
 
                    result.addNotSupported(self, feature)
1608
 
                else:
1609
 
                    result.addSuccess(self)
1610
 
                result.stopTest(self)
1611
 
                return result
1612
 
        try:
1613
 
            try:
1614
 
                result.startTest(self)
1615
 
                absent_attr = object()
1616
 
                # Python 2.5
1617
 
                method_name = getattr(self, '_testMethodName', absent_attr)
1618
 
                if method_name is absent_attr:
1619
 
                    # Python 2.4
1620
 
                    method_name = getattr(self, '_TestCase__testMethodName')
1621
 
                testMethod = getattr(self, method_name)
1622
 
                try:
1623
 
                    try:
1624
 
                        self.setUp()
1625
 
                        if not self._bzr_test_setUp_run:
1626
 
                            self.fail(
1627
 
                                "test setUp did not invoke "
1628
 
                                "bzrlib.tests.TestCase's setUp")
1629
 
                    except KeyboardInterrupt:
1630
 
                        self._runCleanups()
1631
 
                        raise
1632
 
                    except TestSkipped, e:
1633
 
                        self._do_skip(result, e.args[0])
1634
 
                        self.tearDown()
1635
 
                        return result
1636
 
                    except:
1637
 
                        result.addError(self, sys.exc_info())
1638
 
                        self._runCleanups()
1639
 
                        return result
1640
 
 
1641
 
                    ok = False
1642
 
                    try:
1643
 
                        testMethod()
1644
 
                        ok = True
1645
 
                    except self.failureException:
1646
 
                        result.addFailure(self, sys.exc_info())
1647
 
                    except TestSkipped, e:
1648
 
                        if not e.args:
1649
 
                            reason = "No reason given."
1650
 
                        else:
1651
 
                            reason = e.args[0]
1652
 
                        self._do_skip(result, reason)
1653
 
                    except KeyboardInterrupt:
1654
 
                        self._runCleanups()
1655
 
                        raise
1656
 
                    except:
1657
 
                        result.addError(self, sys.exc_info())
1658
 
 
1659
 
                    try:
1660
 
                        self.tearDown()
1661
 
                        if not self._bzr_test_tearDown_run:
1662
 
                            self.fail(
1663
 
                                "test tearDown did not invoke "
1664
 
                                "bzrlib.tests.TestCase's tearDown")
1665
 
                    except KeyboardInterrupt:
1666
 
                        self._runCleanups()
1667
 
                        raise
1668
 
                    except:
1669
 
                        result.addError(self, sys.exc_info())
1670
 
                        self._runCleanups()
1671
 
                        ok = False
1672
 
                    if ok: result.addSuccess(self)
1673
 
                finally:
1674
 
                    result.stopTest(self)
1675
 
                return result
1676
 
            except TestNotApplicable:
1677
 
                # Not moved from the result [yet].
1678
 
                self._runCleanups()
1679
 
                raise
1680
 
            except KeyboardInterrupt:
1681
 
                self._runCleanups()
1682
 
                raise
1683
 
        finally:
1684
 
            saved_attrs = {}
1685
 
            for attr_name in self.attrs_to_keep:
1686
 
                if attr_name in self.__dict__:
1687
 
                    saved_attrs[attr_name] = self.__dict__[attr_name]
1688
 
            self.__dict__ = saved_attrs
1689
 
 
1690
 
    def tearDown(self):
1691
 
        self._runCleanups()
1692
 
        self._log_contents = ''
1693
 
        self._bzr_test_tearDown_run = True
1694
 
        unittest.TestCase.tearDown(self)
 
1556
    @staticmethod
 
1557
    def _do_known_failure(self, result, e):
 
1558
        err = sys.exc_info()
 
1559
        addExpectedFailure = getattr(result, 'addExpectedFailure', None)
 
1560
        if addExpectedFailure is not None:
 
1561
            addExpectedFailure(self, err)
 
1562
        else:
 
1563
            result.addSuccess(self)
 
1564
 
 
1565
    @staticmethod
 
1566
    def _do_not_applicable(self, result, e):
 
1567
        if not e.args:
 
1568
            reason = 'No reason given'
 
1569
        else:
 
1570
            reason = e.args[0]
 
1571
        addNotApplicable = getattr(result, 'addNotApplicable', None)
 
1572
        if addNotApplicable is not None:
 
1573
            result.addNotApplicable(self, reason)
 
1574
        else:
 
1575
            self._do_skip(result, reason)
 
1576
 
 
1577
    @staticmethod
 
1578
    def _do_unsupported_or_skip(self, result, e):
 
1579
        reason = e.args[0]
 
1580
        addNotSupported = getattr(result, 'addNotSupported', None)
 
1581
        if addNotSupported is not None:
 
1582
            result.addNotSupported(self, reason)
 
1583
        else:
 
1584
            self._do_skip(result, reason)
1695
1585
 
1696
1586
    def time(self, callable, *args, **kwargs):
1697
1587
        """Run callable and accrue the time it takes to the benchmark time.
1701
1591
        self._benchcalls.
1702
1592
        """
1703
1593
        if self._benchtime is None:
 
1594
            self.addDetail('benchtime', content.Content(content.ContentType(
 
1595
                "text", "plain"), lambda:[str(self._benchtime)]))
1704
1596
            self._benchtime = 0
1705
1597
        start = time.time()
1706
1598
        try:
1715
1607
        finally:
1716
1608
            self._benchtime += time.time() - start
1717
1609
 
1718
 
    def _runCleanups(self):
1719
 
        """Run registered cleanup functions.
1720
 
 
1721
 
        This should only be called from TestCase.tearDown.
1722
 
        """
1723
 
        # TODO: Perhaps this should keep running cleanups even if
1724
 
        # one of them fails?
1725
 
 
1726
 
        # Actually pop the cleanups from the list so tearDown running
1727
 
        # twice is safe (this happens for skipped tests).
1728
 
        while self._cleanups:
1729
 
            cleanup, args, kwargs = self._cleanups.pop()
1730
 
            cleanup(*args, **kwargs)
1731
 
 
1732
1610
    def log(self, *args):
1733
1611
        mutter(*args)
1734
1612
 
1735
1613
    def _get_log(self, keep_log_file=False):
1736
 
        """Get the log from bzrlib.trace calls from this test.
 
1614
        """Internal helper to get the log from bzrlib.trace for this test.
 
1615
 
 
1616
        Please use self.getDetails, or self.get_log to access this in test case
 
1617
        code.
1737
1618
 
1738
1619
        :param keep_log_file: When True, if the log is still a file on disk
1739
1620
            leave it as a file on disk. When False, if the log is still a file
1741
1622
            self._log_contents.
1742
1623
        :return: A string containing the log.
1743
1624
        """
1744
 
        # flush the log file, to get all content
 
1625
        if self._log_contents is not None:
 
1626
            try:
 
1627
                self._log_contents.decode('utf8')
 
1628
            except UnicodeDecodeError:
 
1629
                unicodestr = self._log_contents.decode('utf8', 'replace')
 
1630
                self._log_contents = unicodestr.encode('utf8')
 
1631
            return self._log_contents
1745
1632
        import bzrlib.trace
1746
1633
        if bzrlib.trace._trace_file:
 
1634
            # flush the log file, to get all content
1747
1635
            bzrlib.trace._trace_file.flush()
1748
 
        if self._log_contents:
1749
 
            # XXX: this can hardly contain the content flushed above --vila
1750
 
            # 20080128
1751
 
            return self._log_contents
1752
1636
        if self._log_file_name is not None:
1753
1637
            logfile = open(self._log_file_name)
1754
1638
            try:
1755
1639
                log_contents = logfile.read()
1756
1640
            finally:
1757
1641
                logfile.close()
 
1642
            try:
 
1643
                log_contents.decode('utf8')
 
1644
            except UnicodeDecodeError:
 
1645
                unicodestr = log_contents.decode('utf8', 'replace')
 
1646
                log_contents = unicodestr.encode('utf8')
1758
1647
            if not keep_log_file:
 
1648
                self._log_file.close()
 
1649
                self._log_file = None
 
1650
                # Permit multiple calls to get_log until we clean it up in
 
1651
                # finishLogFile
1759
1652
                self._log_contents = log_contents
1760
1653
                try:
1761
1654
                    os.remove(self._log_file_name)
1765
1658
                                             ' %r\n' % self._log_file_name))
1766
1659
                    else:
1767
1660
                        raise
 
1661
                self._log_file_name = None
1768
1662
            return log_contents
1769
1663
        else:
1770
 
            return "DELETED log file to reduce memory footprint"
 
1664
            return "No log file content and no log file name."
 
1665
 
 
1666
    def get_log(self):
 
1667
        """Get a unicode string containing the log from bzrlib.trace.
 
1668
 
 
1669
        Undecodable characters are replaced.
 
1670
        """
 
1671
        return u"".join(self.getDetails()['log'].iter_text())
1771
1672
 
1772
1673
    def requireFeature(self, feature):
1773
1674
        """This test requires a specific feature is available.
1790
1691
 
1791
1692
    def _run_bzr_core(self, args, retcode, encoding, stdin,
1792
1693
            working_dir):
 
1694
        # Clear chk_map page cache, because the contents are likely to mask
 
1695
        # locking errors.
 
1696
        chk_map.clear_cache()
1793
1697
        if encoding is None:
1794
1698
            encoding = osutils.get_user_encoding()
1795
1699
        stdout = StringIOWrapper()
1812
1716
            os.chdir(working_dir)
1813
1717
 
1814
1718
        try:
1815
 
            result = self.apply_redirected(ui.ui_factory.stdin,
1816
 
                stdout, stderr,
1817
 
                bzrlib.commands.run_bzr_catch_user_errors,
1818
 
                args)
 
1719
            try:
 
1720
                result = self.apply_redirected(ui.ui_factory.stdin,
 
1721
                    stdout, stderr,
 
1722
                    bzrlib.commands.run_bzr_catch_user_errors,
 
1723
                    args)
 
1724
            except KeyboardInterrupt:
 
1725
                # Reraise KeyboardInterrupt with contents of redirected stdout
 
1726
                # and stderr as arguments, for tests which are interested in
 
1727
                # stdout and stderr and are expecting the exception.
 
1728
                out = stdout.getvalue()
 
1729
                err = stderr.getvalue()
 
1730
                if out:
 
1731
                    self.log('output:\n%r', out)
 
1732
                if err:
 
1733
                    self.log('errors:\n%r', err)
 
1734
                raise KeyboardInterrupt(out, err)
1819
1735
        finally:
1820
1736
            logger.removeHandler(handler)
1821
1737
            ui.ui_factory = old_ui_factory
2369
2285
            # recreate a new one or all the followng tests will fail.
2370
2286
            # If you need to inspect its content uncomment the following line
2371
2287
            # import pdb; pdb.set_trace()
2372
 
            _rmtree_temp_dir(root + '/.bzr')
 
2288
            _rmtree_temp_dir(root + '/.bzr', test_id=self.id())
2373
2289
            self._create_safety_net()
2374
2290
            raise AssertionError('%s/.bzr should not be modified' % root)
2375
2291
 
2451
2367
        return branchbuilder.BranchBuilder(branch=branch)
2452
2368
 
2453
2369
    def overrideEnvironmentForTesting(self):
2454
 
        os.environ['HOME'] = self.test_home_dir
2455
 
        os.environ['BZR_HOME'] = self.test_home_dir
 
2370
        test_home_dir = self.test_home_dir
 
2371
        if isinstance(test_home_dir, unicode):
 
2372
            test_home_dir = test_home_dir.encode(sys.getfilesystemencoding())
 
2373
        os.environ['HOME'] = test_home_dir
 
2374
        os.environ['BZR_HOME'] = test_home_dir
2456
2375
 
2457
2376
    def setUp(self):
2458
2377
        super(TestCaseWithMemoryTransport, self).setUp()
2564
2483
 
2565
2484
    def deleteTestDir(self):
2566
2485
        os.chdir(TestCaseWithMemoryTransport.TEST_ROOT)
2567
 
        _rmtree_temp_dir(self.test_base_dir)
 
2486
        _rmtree_temp_dir(self.test_base_dir, test_id=self.id())
2568
2487
 
2569
2488
    def build_tree(self, shape, line_endings='binary', transport=None):
2570
2489
        """Build a test tree according to a pattern.
3174
3093
        if self.randomised:
3175
3094
            return iter(self._tests)
3176
3095
        self.randomised = True
3177
 
        self.stream.writeln("Randomizing test order using seed %s\n" %
 
3096
        self.stream.write("Randomizing test order using seed %s\n\n" %
3178
3097
            (self.actual_seed()))
3179
3098
        # Initialise the random number generator.
3180
3099
        random.seed(self.actual_seed())
3237
3156
    concurrency = osutils.local_concurrency()
3238
3157
    result = []
3239
3158
    from subunit import TestProtocolClient, ProtocolTestCase
3240
 
    try:
3241
 
        from subunit.test_results import AutoTimingTestResultDecorator
3242
 
    except ImportError:
3243
 
        AutoTimingTestResultDecorator = lambda x:x
 
3159
    from subunit.test_results import AutoTimingTestResultDecorator
3244
3160
    class TestInOtherProcess(ProtocolTestCase):
3245
3161
        # Should be in subunit, I think. RBC.
3246
3162
        def __init__(self, stream, pid):
3312
3228
        if not os.path.isfile(bzr_path):
3313
3229
            # We are probably installed. Assume sys.argv is the right file
3314
3230
            bzr_path = sys.argv[0]
 
3231
        bzr_path = [bzr_path]
 
3232
        if sys.platform == "win32":
 
3233
            # if we're on windows, we can't execute the bzr script directly
 
3234
            bzr_path = [sys.executable] + bzr_path
3315
3235
        fd, test_list_file_name = tempfile.mkstemp()
3316
3236
        test_list_file = os.fdopen(fd, 'wb', 1)
3317
3237
        for test in process_tests:
3318
3238
            test_list_file.write(test.id() + '\n')
3319
3239
        test_list_file.close()
3320
3240
        try:
3321
 
            argv = [bzr_path, 'selftest', '--load-list', test_list_file_name,
 
3241
            argv = bzr_path + ['selftest', '--load-list', test_list_file_name,
3322
3242
                '--subunit']
3323
3243
            if '--no-plugins' in sys.argv:
3324
3244
                argv.append('--no-plugins')
3363
3283
 
3364
3284
    def addFailure(self, test, err):
3365
3285
        self.result.addFailure(test, err)
3366
 
 
3367
 
 
3368
 
class BZRTransformingResult(ForwardingResult):
3369
 
 
3370
 
    def addError(self, test, err):
3371
 
        feature = self._error_looks_like('UnavailableFeature: ', err)
3372
 
        if feature is not None:
3373
 
            self.result.addNotSupported(test, feature)
3374
 
        else:
3375
 
            self.result.addError(test, err)
3376
 
 
3377
 
    def addFailure(self, test, err):
3378
 
        known = self._error_looks_like('KnownFailure: ', err)
3379
 
        if known is not None:
3380
 
            self.result._addKnownFailure(test, [KnownFailure,
3381
 
                                                KnownFailure(known), None])
3382
 
        else:
3383
 
            self.result.addFailure(test, err)
3384
 
 
3385
 
    def _error_looks_like(self, prefix, err):
3386
 
        """Deserialize exception and returns the stringify value."""
3387
 
        import subunit
3388
 
        value = None
3389
 
        typ, exc, _ = err
3390
 
        if isinstance(exc, subunit.RemoteException):
3391
 
            # stringify the exception gives access to the remote traceback
3392
 
            # We search the last line for 'prefix'
3393
 
            lines = str(exc).split('\n')
3394
 
            while lines and not lines[-1]:
3395
 
                lines.pop(-1)
3396
 
            if lines:
3397
 
                if lines[-1].startswith(prefix):
3398
 
                    value = lines[-1][len(prefix):]
3399
 
        return value
 
3286
ForwardingResult = testtools.ExtendedToOriginalDecorator
3400
3287
 
3401
3288
 
3402
3289
class ProfileResult(ForwardingResult):
3685
3572
        'bzrlib.tests.per_inventory',
3686
3573
        'bzrlib.tests.per_interbranch',
3687
3574
        'bzrlib.tests.per_lock',
 
3575
        'bzrlib.tests.per_merger',
3688
3576
        'bzrlib.tests.per_transport',
3689
3577
        'bzrlib.tests.per_tree',
3690
3578
        'bzrlib.tests.per_pack_repository',
3695
3583
        'bzrlib.tests.per_versionedfile',
3696
3584
        'bzrlib.tests.per_workingtree',
3697
3585
        'bzrlib.tests.test__annotator',
 
3586
        'bzrlib.tests.test__bencode',
3698
3587
        'bzrlib.tests.test__chk_map',
3699
3588
        'bzrlib.tests.test__dirstate_helpers',
3700
3589
        'bzrlib.tests.test__groupcompress',
3708
3597
        'bzrlib.tests.test_api',
3709
3598
        'bzrlib.tests.test_atomicfile',
3710
3599
        'bzrlib.tests.test_bad_files',
3711
 
        'bzrlib.tests.test_bencode',
3712
3600
        'bzrlib.tests.test_bisect_multi',
3713
3601
        'bzrlib.tests.test_branch',
3714
3602
        'bzrlib.tests.test_branchbuilder',
3722
3610
        'bzrlib.tests.test_chk_serializer',
3723
3611
        'bzrlib.tests.test_chunk_writer',
3724
3612
        'bzrlib.tests.test_clean_tree',
 
3613
        'bzrlib.tests.test_cleanup',
3725
3614
        'bzrlib.tests.test_commands',
3726
3615
        'bzrlib.tests.test_commit',
3727
3616
        'bzrlib.tests.test_commit_merge',
3863
3752
    return [
3864
3753
        'bzrlib',
3865
3754
        'bzrlib.branchbuilder',
 
3755
        'bzrlib.decorators',
3866
3756
        'bzrlib.export',
3867
3757
        'bzrlib.inventory',
3868
3758
        'bzrlib.iterablefile',
4075
3965
    return new_test
4076
3966
 
4077
3967
 
4078
 
def _rmtree_temp_dir(dirname):
 
3968
def permute_tests_for_extension(standard_tests, loader, py_module_name,
 
3969
                                ext_module_name):
 
3970
    """Helper for permutating tests against an extension module.
 
3971
 
 
3972
    This is meant to be used inside a modules 'load_tests()' function. It will
 
3973
    create 2 scenarios, and cause all tests in the 'standard_tests' to be run
 
3974
    against both implementations. Setting 'test.module' to the appropriate
 
3975
    module. See bzrlib.tests.test__chk_map.load_tests as an example.
 
3976
 
 
3977
    :param standard_tests: A test suite to permute
 
3978
    :param loader: A TestLoader
 
3979
    :param py_module_name: The python path to a python module that can always
 
3980
        be loaded, and will be considered the 'python' implementation. (eg
 
3981
        'bzrlib._chk_map_py')
 
3982
    :param ext_module_name: The python path to an extension module. If the
 
3983
        module cannot be loaded, a single test will be added, which notes that
 
3984
        the module is not available. If it can be loaded, all standard_tests
 
3985
        will be run against that module.
 
3986
    :return: (suite, feature) suite is a test-suite that has all the permuted
 
3987
        tests. feature is the Feature object that can be used to determine if
 
3988
        the module is available.
 
3989
    """
 
3990
 
 
3991
    py_module = __import__(py_module_name, {}, {}, ['NO_SUCH_ATTRIB'])
 
3992
    scenarios = [
 
3993
        ('python', {'module': py_module}),
 
3994
    ]
 
3995
    suite = loader.suiteClass()
 
3996
    feature = ModuleAvailableFeature(ext_module_name)
 
3997
    if feature.available():
 
3998
        scenarios.append(('C', {'module': feature.module}))
 
3999
    else:
 
4000
        # the compiled module isn't available, so we add a failing test
 
4001
        class FailWithoutFeature(TestCase):
 
4002
            def test_fail(self):
 
4003
                self.requireFeature(feature)
 
4004
        suite.addTest(loader.loadTestsFromTestCase(FailWithoutFeature))
 
4005
    result = multiply_tests(standard_tests, scenarios, suite)
 
4006
    return result, feature
 
4007
 
 
4008
 
 
4009
def _rmtree_temp_dir(dirname, test_id=None):
4079
4010
    # If LANG=C we probably have created some bogus paths
4080
4011
    # which rmtree(unicode) will fail to delete
4081
4012
    # so make sure we are using rmtree(str) to delete everything
4093
4024
        # We don't want to fail here because some useful display will be lost
4094
4025
        # otherwise. Polluting the tmp dir is bad, but not giving all the
4095
4026
        # possible info to the test runner is even worse.
 
4027
        if test_id != None:
 
4028
            ui.ui_factory.clear_term()
 
4029
            sys.stderr.write('\nWhile running: %s\n' % (test_id,))
4096
4030
        sys.stderr.write('Unable to remove testing dir %s\n%s'
4097
4031
                         % (os.path.basename(dirname), e))
4098
4032
 
4182
4116
UnicodeFilenameFeature = _UnicodeFilenameFeature()
4183
4117
 
4184
4118
 
 
4119
class _CompatabilityThunkFeature(Feature):
 
4120
    """This feature is just a thunk to another feature.
 
4121
 
 
4122
    It issues a deprecation warning if it is accessed, to let you know that you
 
4123
    should really use a different feature.
 
4124
    """
 
4125
 
 
4126
    def __init__(self, module, name, this_name, dep_version):
 
4127
        super(_CompatabilityThunkFeature, self).__init__()
 
4128
        self._module = module
 
4129
        self._name = name
 
4130
        self._this_name = this_name
 
4131
        self._dep_version = dep_version
 
4132
        self._feature = None
 
4133
 
 
4134
    def _ensure(self):
 
4135
        if self._feature is None:
 
4136
            msg = (self._dep_version % self._this_name) + (
 
4137
                   ' Use %s.%s instead.' % (self._module, self._name))
 
4138
            symbol_versioning.warn(msg, DeprecationWarning)
 
4139
            mod = __import__(self._module, {}, {}, [self._name])
 
4140
            self._feature = getattr(mod, self._name)
 
4141
 
 
4142
    def _probe(self):
 
4143
        self._ensure()
 
4144
        return self._feature._probe()
 
4145
 
 
4146
 
 
4147
class ModuleAvailableFeature(Feature):
 
4148
    """This is a feature than describes a module we want to be available.
 
4149
 
 
4150
    Declare the name of the module in __init__(), and then after probing, the
 
4151
    module will be available as 'self.module'.
 
4152
 
 
4153
    :ivar module: The module if it is available, else None.
 
4154
    """
 
4155
 
 
4156
    def __init__(self, module_name):
 
4157
        super(ModuleAvailableFeature, self).__init__()
 
4158
        self.module_name = module_name
 
4159
 
 
4160
    def _probe(self):
 
4161
        try:
 
4162
            self._module = __import__(self.module_name, {}, {}, [''])
 
4163
            return True
 
4164
        except ImportError:
 
4165
            return False
 
4166
 
 
4167
    @property
 
4168
    def module(self):
 
4169
        if self.available(): # Make sure the probe has been done
 
4170
            return self._module
 
4171
        return None
 
4172
    
 
4173
    def feature_name(self):
 
4174
        return self.module_name
 
4175
 
 
4176
 
 
4177
# This is kept here for compatibility, it is recommended to use
 
4178
# 'bzrlib.tests.feature.paramiko' instead
 
4179
ParamikoFeature = _CompatabilityThunkFeature('bzrlib.tests.features',
 
4180
    'paramiko', 'bzrlib.tests.ParamikoFeature', deprecated_in((2,1,0)))
 
4181
 
 
4182
 
4185
4183
def probe_unicode_in_user_encoding():
4186
4184
    """Try to encode several unicode strings to use in unicode-aware tests.
4187
4185
    Return first successfull match.
4236
4234
HTTPSServerFeature = _HTTPSServerFeature()
4237
4235
 
4238
4236
 
4239
 
class _ParamikoFeature(Feature):
4240
 
    """Is paramiko available?"""
4241
 
 
4242
 
    def _probe(self):
4243
 
        try:
4244
 
            from bzrlib.transport.sftp import SFTPAbsoluteServer
4245
 
            return True
4246
 
        except errors.ParamikoNotPresent:
4247
 
            return False
4248
 
 
4249
 
    def feature_name(self):
4250
 
        return "Paramiko"
4251
 
 
4252
 
 
4253
 
ParamikoFeature = _ParamikoFeature()
4254
 
 
4255
 
 
4256
4237
class _UnicodeFilename(Feature):
4257
4238
    """Does the filesystem support Unicode filenames?"""
4258
4239
 
4295
4276
            # Windows doesn't have os.kill, and we catch the SIGBREAK signal.
4296
4277
            # We trigger SIGBREAK via a Console api so we need ctypes to
4297
4278
            # access the function
4298
 
            if not have_ctypes:
 
4279
            try:
 
4280
                import ctypes
 
4281
            except OSError:
4299
4282
                return False
4300
4283
        return True
4301
4284
 
4361
4344
CaseInsensitiveFilesystemFeature = _CaseInsensitiveFilesystemFeature()
4362
4345
 
4363
4346
 
4364
 
class _SubUnitFeature(Feature):
4365
 
    """Check if subunit is available."""
 
4347
class _CaseSensitiveFilesystemFeature(Feature):
4366
4348
 
4367
4349
    def _probe(self):
4368
 
        try:
4369
 
            import subunit
 
4350
        if CaseInsCasePresFilenameFeature.available():
 
4351
            return False
 
4352
        elif CaseInsensitiveFilesystemFeature.available():
 
4353
            return False
 
4354
        else:
4370
4355
            return True
4371
 
        except ImportError:
4372
 
            return False
4373
4356
 
4374
4357
    def feature_name(self):
4375
 
        return 'subunit'
4376
 
 
4377
 
SubUnitFeature = _SubUnitFeature()
 
4358
        return 'case-sensitive filesystem'
 
4359
 
 
4360
# new coding style is for feature instances to be lowercase
 
4361
case_sensitive_filesystem_feature = _CaseSensitiveFilesystemFeature()
 
4362
 
 
4363
 
 
4364
# Kept for compatibility, use bzrlib.tests.features.subunit instead
 
4365
SubUnitFeature = _CompatabilityThunkFeature('bzrlib.tests.features', 'subunit',
 
4366
    'bzrlib.tests.SubUnitFeature', deprecated_in((2,1,0)))
4378
4367
# Only define SubUnitBzrRunner if subunit is available.
4379
4368
try:
4380
4369
    from subunit import TestProtocolClient
4381
 
    try:
4382
 
        from subunit.test_results import AutoTimingTestResultDecorator
4383
 
    except ImportError:
4384
 
        AutoTimingTestResultDecorator = lambda x:x
 
4370
    from subunit.test_results import AutoTimingTestResultDecorator
4385
4371
    class SubUnitBzrRunner(TextTestRunner):
4386
4372
        def run(self, test):
4387
4373
            result = AutoTimingTestResultDecorator(