/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/__init__.py

(spiv) Fix BZR_TEST_PDB. (#504070) (Martin [gz])

Show diffs side-by-side

added added

removed removed

Lines of Context:
29
29
 
30
30
import atexit
31
31
import codecs
32
 
from copy import copy
 
32
import copy
33
33
from cStringIO import StringIO
34
34
import difflib
35
35
import doctest
36
36
import errno
 
37
import itertools
37
38
import logging
38
 
import math
39
39
import os
40
 
from pprint import pformat
 
40
import platform
 
41
import pprint
41
42
import random
42
43
import re
43
44
import shlex
44
45
import stat
45
 
from subprocess import Popen, PIPE, STDOUT
 
46
import subprocess
46
47
import sys
47
48
import tempfile
48
49
import threading
70
71
    lock as _mod_lock,
71
72
    memorytree,
72
73
    osutils,
73
 
    progress,
74
74
    ui,
75
75
    urlutils,
76
76
    registry,
 
77
    transport as _mod_transport,
77
78
    workingtree,
78
79
    )
79
80
import bzrlib.branch
103
104
    )
104
105
import bzrlib.trace
105
106
from bzrlib.transport import (
106
 
    get_transport,
107
107
    memory,
108
108
    pathfilter,
109
109
    )
110
 
import bzrlib.transport
111
110
from bzrlib.trace import mutter, note
112
111
from bzrlib.tests import (
113
112
    test_server,
114
113
    TestUtil,
 
114
    treeshape,
115
115
    )
116
 
from bzrlib.tests.http_server import HttpServer
117
 
from bzrlib.tests.TestUtil import (
118
 
                          TestSuite,
119
 
                          TestLoader,
120
 
                          )
121
 
from bzrlib.tests.treeshape import build_tree_contents
122
116
from bzrlib.ui import NullProgressView
123
117
from bzrlib.ui.text import TextUIFactory
124
118
import bzrlib.version_info_formats.format_custom
140
134
SUBUNIT_SEEK_SET = 0
141
135
SUBUNIT_SEEK_CUR = 1
142
136
 
 
137
# These are intentionally brought into this namespace. That way plugins, etc
 
138
# can just "from bzrlib.tests import TestCase, TestLoader, etc"
 
139
TestSuite = TestUtil.TestSuite
 
140
TestLoader = TestUtil.TestLoader
143
141
 
144
 
class ExtendedTestResult(unittest._TextTestResult):
 
142
class ExtendedTestResult(testtools.TextTestResult):
145
143
    """Accepts, reports and accumulates the results of running tests.
146
144
 
147
145
    Compared to the unittest version this class adds support for
168
166
        :param bench_history: Optionally, a writable file object to accumulate
169
167
            benchmark results.
170
168
        """
171
 
        unittest._TextTestResult.__init__(self, stream, descriptions, verbosity)
 
169
        testtools.TextTestResult.__init__(self, stream)
172
170
        if bench_history is not None:
173
171
            from bzrlib.version import _get_bzr_source_tree
174
172
            src_tree = _get_bzr_source_tree()
195
193
        self.count = 0
196
194
        self._overall_start_time = time.time()
197
195
        self._strict = strict
 
196
        self._first_thread_leaker_id = None
 
197
        self._tests_leaking_threads_count = 0
 
198
        self._traceback_from_test = None
198
199
 
199
200
    def stopTestRun(self):
200
201
        run = self.testsRun
201
202
        actionTaken = "Ran"
202
203
        stopTime = time.time()
203
204
        timeTaken = stopTime - self.startTime
204
 
        self.printErrors()
205
 
        self.stream.writeln(self.separator2)
206
 
        self.stream.writeln("%s %d test%s in %.3fs" % (actionTaken,
 
205
        # GZ 2010-07-19: Seems testtools has no printErrors method, and though
 
206
        #                the parent class method is similar have to duplicate
 
207
        self._show_list('ERROR', self.errors)
 
208
        self._show_list('FAIL', self.failures)
 
209
        self.stream.write(self.sep2)
 
210
        self.stream.write("%s %d test%s in %.3fs\n\n" % (actionTaken,
207
211
                            run, run != 1 and "s" or "", timeTaken))
208
 
        self.stream.writeln()
209
212
        if not self.wasSuccessful():
210
213
            self.stream.write("FAILED (")
211
214
            failed, errored = map(len, (self.failures, self.errors))
218
221
                if failed or errored: self.stream.write(", ")
219
222
                self.stream.write("known_failure_count=%d" %
220
223
                    self.known_failure_count)
221
 
            self.stream.writeln(")")
 
224
            self.stream.write(")\n")
222
225
        else:
223
226
            if self.known_failure_count:
224
 
                self.stream.writeln("OK (known_failures=%d)" %
 
227
                self.stream.write("OK (known_failures=%d)\n" %
225
228
                    self.known_failure_count)
226
229
            else:
227
 
                self.stream.writeln("OK")
 
230
                self.stream.write("OK\n")
228
231
        if self.skip_count > 0:
229
232
            skipped = self.skip_count
230
 
            self.stream.writeln('%d test%s skipped' %
 
233
            self.stream.write('%d test%s skipped\n' %
231
234
                                (skipped, skipped != 1 and "s" or ""))
232
235
        if self.unsupported:
233
236
            for feature, count in sorted(self.unsupported.items()):
234
 
                self.stream.writeln("Missing feature '%s' skipped %d tests." %
 
237
                self.stream.write("Missing feature '%s' skipped %d tests.\n" %
235
238
                    (feature, count))
236
239
        if self._strict:
237
240
            ok = self.wasStrictlySuccessful()
238
241
        else:
239
242
            ok = self.wasSuccessful()
240
 
        if TestCase._first_thread_leaker_id:
 
243
        if self._first_thread_leaker_id:
241
244
            self.stream.write(
242
245
                '%s is leaking threads among %d leaking tests.\n' % (
243
 
                TestCase._first_thread_leaker_id,
244
 
                TestCase._leaking_threads_tests))
 
246
                self._first_thread_leaker_id,
 
247
                self._tests_leaking_threads_count))
245
248
            # We don't report the main thread as an active one.
246
249
            self.stream.write(
247
250
                '%d non-main threads were left active in the end.\n'
248
 
                % (TestCase._active_threads - 1))
 
251
                % (len(self._active_threads) - 1))
249
252
 
250
253
    def getDescription(self, test):
251
254
        return test.id()
258
261
 
259
262
    def _elapsedTestTimeString(self):
260
263
        """Return a time string for the overall time the current test has taken."""
261
 
        return self._formatTime(time.time() - self._start_time)
 
264
        return self._formatTime(self._delta_to_float(
 
265
            self._now() - self._start_datetime))
262
266
 
263
267
    def _testTimeString(self, testCase):
264
268
        benchmark_time = self._extractBenchmarkTime(testCase)
275
279
 
276
280
    def _shortened_test_description(self, test):
277
281
        what = test.id()
278
 
        what = re.sub(r'^bzrlib\.(tests|benchmarks)\.', '', what)
 
282
        what = re.sub(r'^bzrlib\.tests\.', '', what)
279
283
        return what
280
284
 
 
285
    # GZ 2010-10-04: Cloned tests may end up harmlessly calling this method
 
286
    #                multiple times in a row, because the handler is added for
 
287
    #                each test but the container list is shared between cases.
 
288
    #                See lp:498869 lp:625574 and lp:637725 for background.
 
289
    def _record_traceback_from_test(self, exc_info):
 
290
        """Store the traceback from passed exc_info tuple till"""
 
291
        self._traceback_from_test = exc_info[2]
 
292
 
281
293
    def startTest(self, test):
282
 
        unittest.TestResult.startTest(self, test)
 
294
        super(ExtendedTestResult, self).startTest(test)
283
295
        if self.count == 0:
284
296
            self.startTests()
 
297
        self.count += 1
285
298
        self.report_test_start(test)
286
299
        test.number = self.count
287
300
        self._recordTestStartTime()
 
301
        # Make testtools cases give us the real traceback on failure
 
302
        addOnException = getattr(test, "addOnException", None)
 
303
        if addOnException is not None:
 
304
            addOnException(self._record_traceback_from_test)
 
305
        # Only check for thread leaks if the test case supports cleanups
 
306
        addCleanup = getattr(test, "addCleanup", None)
 
307
        if addCleanup is not None:
 
308
            addCleanup(self._check_leaked_threads, test)
288
309
 
289
310
    def startTests(self):
290
 
        import platform
291
 
        if getattr(sys, 'frozen', None) is None:
292
 
            bzr_path = osutils.realpath(sys.argv[0])
293
 
        else:
294
 
            bzr_path = sys.executable
295
 
        self.stream.write(
296
 
            'bzr selftest: %s\n' % (bzr_path,))
297
 
        self.stream.write(
298
 
            '   %s\n' % (
299
 
                    bzrlib.__path__[0],))
300
 
        self.stream.write(
301
 
            '   bzr-%s python-%s %s\n' % (
302
 
                    bzrlib.version_string,
303
 
                    bzrlib._format_version_tuple(sys.version_info),
304
 
                    platform.platform(aliased=1),
305
 
                    ))
306
 
        self.stream.write('\n')
 
311
        self.report_tests_starting()
 
312
        self._active_threads = threading.enumerate()
 
313
 
 
314
    def stopTest(self, test):
 
315
        self._traceback_from_test = None
 
316
 
 
317
    def _check_leaked_threads(self, test):
 
318
        """See if any threads have leaked since last call
 
319
 
 
320
        A sample of live threads is stored in the _active_threads attribute,
 
321
        when this method runs it compares the current live threads and any not
 
322
        in the previous sample are treated as having leaked.
 
323
        """
 
324
        now_active_threads = set(threading.enumerate())
 
325
        threads_leaked = now_active_threads.difference(self._active_threads)
 
326
        if threads_leaked:
 
327
            self._report_thread_leak(test, threads_leaked, now_active_threads)
 
328
            self._tests_leaking_threads_count += 1
 
329
            if self._first_thread_leaker_id is None:
 
330
                self._first_thread_leaker_id = test.id()
 
331
            self._active_threads = now_active_threads
307
332
 
308
333
    def _recordTestStartTime(self):
309
334
        """Record that a test has started."""
310
 
        self._start_time = time.time()
311
 
 
312
 
    def _cleanupLogFile(self, test):
313
 
        # We can only do this if we have one of our TestCases, not if
314
 
        # we have a doctest.
315
 
        setKeepLogfile = getattr(test, 'setKeepLogfile', None)
316
 
        if setKeepLogfile is not None:
317
 
            setKeepLogfile()
 
335
        self._start_datetime = self._now()
318
336
 
319
337
    def addError(self, test, err):
320
338
        """Tell result that test finished with an error.
322
340
        Called from the TestCase run() method when the test
323
341
        fails with an unexpected error.
324
342
        """
325
 
        self._post_mortem()
326
 
        unittest.TestResult.addError(self, test, err)
 
343
        self._post_mortem(self._traceback_from_test)
 
344
        super(ExtendedTestResult, self).addError(test, err)
327
345
        self.error_count += 1
328
346
        self.report_error(test, err)
329
347
        if self.stop_early:
330
348
            self.stop()
331
 
        self._cleanupLogFile(test)
332
349
 
333
350
    def addFailure(self, test, err):
334
351
        """Tell result that test failed.
336
353
        Called from the TestCase run() method when the test
337
354
        fails because e.g. an assert() method failed.
338
355
        """
339
 
        self._post_mortem()
340
 
        unittest.TestResult.addFailure(self, test, err)
 
356
        self._post_mortem(self._traceback_from_test)
 
357
        super(ExtendedTestResult, self).addFailure(test, err)
341
358
        self.failure_count += 1
342
359
        self.report_failure(test, err)
343
360
        if self.stop_early:
344
361
            self.stop()
345
 
        self._cleanupLogFile(test)
346
362
 
347
363
    def addSuccess(self, test, details=None):
348
364
        """Tell result that test completed successfully.
356
372
                    self._formatTime(benchmark_time),
357
373
                    test.id()))
358
374
        self.report_success(test)
359
 
        self._cleanupLogFile(test)
360
 
        unittest.TestResult.addSuccess(self, test)
 
375
        super(ExtendedTestResult, self).addSuccess(test)
361
376
        test._log_contents = ''
362
377
 
363
378
    def addExpectedFailure(self, test, err):
386
401
        self.not_applicable_count += 1
387
402
        self.report_not_applicable(test, reason)
388
403
 
389
 
    def _post_mortem(self):
 
404
    def _post_mortem(self, tb=None):
390
405
        """Start a PDB post mortem session."""
391
406
        if os.environ.get('BZR_TEST_PDB', None):
392
 
            import pdb;pdb.post_mortem()
 
407
            import pdb
 
408
            pdb.post_mortem(tb)
393
409
 
394
410
    def progress(self, offset, whence):
395
411
        """The test is adjusting the count of tests to run."""
400
416
        else:
401
417
            raise errors.BzrError("Unknown whence %r" % whence)
402
418
 
403
 
    def report_cleaning_up(self):
404
 
        pass
 
419
    def report_tests_starting(self):
 
420
        """Display information before the test run begins"""
 
421
        if getattr(sys, 'frozen', None) is None:
 
422
            bzr_path = osutils.realpath(sys.argv[0])
 
423
        else:
 
424
            bzr_path = sys.executable
 
425
        self.stream.write(
 
426
            'bzr selftest: %s\n' % (bzr_path,))
 
427
        self.stream.write(
 
428
            '   %s\n' % (
 
429
                    bzrlib.__path__[0],))
 
430
        self.stream.write(
 
431
            '   bzr-%s python-%s %s\n' % (
 
432
                    bzrlib.version_string,
 
433
                    bzrlib._format_version_tuple(sys.version_info),
 
434
                    platform.platform(aliased=1),
 
435
                    ))
 
436
        self.stream.write('\n')
 
437
 
 
438
    def report_test_start(self, test):
 
439
        """Display information on the test just about to be run"""
 
440
 
 
441
    def _report_thread_leak(self, test, leaked_threads, active_threads):
 
442
        """Display information on a test that leaked one or more threads"""
 
443
        # GZ 2010-09-09: A leak summary reported separately from the general
 
444
        #                thread debugging would be nice. Tests under subunit
 
445
        #                need something not using stream, perhaps adding a
 
446
        #                testtools details object would be fitting.
 
447
        if 'threads' in selftest_debug_flags:
 
448
            self.stream.write('%s is leaking, active is now %d\n' %
 
449
                (test.id(), len(active_threads)))
405
450
 
406
451
    def startTestRun(self):
407
452
        self.startTime = time.time()
444
489
        self.pb.finished()
445
490
        super(TextTestResult, self).stopTestRun()
446
491
 
447
 
    def startTestRun(self):
448
 
        super(TextTestResult, self).startTestRun()
 
492
    def report_tests_starting(self):
 
493
        super(TextTestResult, self).report_tests_starting()
449
494
        self.pb.update('[test 0/%d] Starting' % (self.num_tests))
450
495
 
451
 
    def printErrors(self):
452
 
        # clear the pb to make room for the error listing
453
 
        self.pb.clear()
454
 
        super(TextTestResult, self).printErrors()
455
 
 
456
496
    def _progress_prefix_text(self):
457
497
        # the longer this text, the less space we have to show the test
458
498
        # name...
480
520
        return a
481
521
 
482
522
    def report_test_start(self, test):
483
 
        self.count += 1
484
523
        self.pb.update(
485
524
                self._progress_prefix_text()
486
525
                + ' '
513
552
    def report_unsupported(self, test, feature):
514
553
        """test cannot be run because feature is missing."""
515
554
 
516
 
    def report_cleaning_up(self):
517
 
        self.pb.update('Cleaning up')
518
 
 
519
555
 
520
556
class VerboseTestResult(ExtendedTestResult):
521
557
    """Produce long output, with one line per test run plus times"""
528
564
            result = a_string
529
565
        return result.ljust(final_width)
530
566
 
531
 
    def startTestRun(self):
532
 
        super(VerboseTestResult, self).startTestRun()
 
567
    def report_tests_starting(self):
533
568
        self.stream.write('running %d tests...\n' % self.num_tests)
 
569
        super(VerboseTestResult, self).report_tests_starting()
534
570
 
535
571
    def report_test_start(self, test):
536
 
        self.count += 1
537
572
        name = self._shortened_test_description(test)
538
573
        width = osutils.terminal_width()
539
574
        if width is not None:
551
586
        return '%s%s' % (indent, err[1])
552
587
 
553
588
    def report_error(self, test, err):
554
 
        self.stream.writeln('ERROR %s\n%s'
 
589
        self.stream.write('ERROR %s\n%s\n'
555
590
                % (self._testTimeString(test),
556
591
                   self._error_summary(err)))
557
592
 
558
593
    def report_failure(self, test, err):
559
 
        self.stream.writeln(' FAIL %s\n%s'
 
594
        self.stream.write(' FAIL %s\n%s\n'
560
595
                % (self._testTimeString(test),
561
596
                   self._error_summary(err)))
562
597
 
563
598
    def report_known_failure(self, test, err):
564
 
        self.stream.writeln('XFAIL %s\n%s'
 
599
        self.stream.write('XFAIL %s\n%s\n'
565
600
                % (self._testTimeString(test),
566
601
                   self._error_summary(err)))
567
602
 
568
603
    def report_success(self, test):
569
 
        self.stream.writeln('   OK %s' % self._testTimeString(test))
 
604
        self.stream.write('   OK %s\n' % self._testTimeString(test))
570
605
        for bench_called, stats in getattr(test, '_benchcalls', []):
571
 
            self.stream.writeln('LSProf output for %s(%s, %s)' % bench_called)
 
606
            self.stream.write('LSProf output for %s(%s, %s)\n' % bench_called)
572
607
            stats.pprint(file=self.stream)
573
608
        # flush the stream so that we get smooth output. This verbose mode is
574
609
        # used to show the output in PQM.
575
610
        self.stream.flush()
576
611
 
577
612
    def report_skip(self, test, reason):
578
 
        self.stream.writeln(' SKIP %s\n%s'
 
613
        self.stream.write(' SKIP %s\n%s\n'
579
614
                % (self._testTimeString(test), reason))
580
615
 
581
616
    def report_not_applicable(self, test, reason):
582
 
        self.stream.writeln('  N/A %s\n    %s'
 
617
        self.stream.write('  N/A %s\n    %s\n'
583
618
                % (self._testTimeString(test), reason))
584
619
 
585
620
    def report_unsupported(self, test, feature):
586
621
        """test cannot be run because feature is missing."""
587
 
        self.stream.writeln("NODEP %s\n    The feature '%s' is not available."
 
622
        self.stream.write("NODEP %s\n    The feature '%s' is not available.\n"
588
623
                %(self._testTimeString(test), feature))
589
624
 
590
625
 
619
654
            encode = codec.encode
620
655
        stream = osutils.UnicodeOrBytesToBytesWriter(encode, stream)
621
656
        stream.encoding = new_encoding
622
 
        self.stream = unittest._WritelnDecorator(stream)
 
657
        self.stream = stream
623
658
        self.descriptions = descriptions
624
659
        self.verbosity = verbosity
625
660
        self._bench_history = bench_history
749
784
    # XXX: Should probably unify more with CannedInputUIFactory or a
750
785
    # particular configuration of TextUIFactory, or otherwise have a clearer
751
786
    # idea of how they're supposed to be different.
752
 
    # See https://bugs.edge.launchpad.net/bzr/+bug/408213
 
787
    # See https://bugs.launchpad.net/bzr/+bug/408213
753
788
 
754
789
    def __init__(self, stdout=None, stderr=None, stdin=None):
755
790
        if stdin is not None:
789
824
    routine, and to build and check bzr trees.
790
825
 
791
826
    In addition to the usual method of overriding tearDown(), this class also
792
 
    allows subclasses to register functions into the _cleanups list, which is
 
827
    allows subclasses to register cleanup functions via addCleanup, which are
793
828
    run in order as the object is torn down.  It's less likely this will be
794
829
    accidentally overlooked.
795
830
    """
796
831
 
797
 
    _active_threads = None
798
 
    _leaking_threads_tests = 0
799
 
    _first_thread_leaker_id = None
800
 
    _log_file_name = None
 
832
    _log_file = None
801
833
    # record lsprof data when performing benchmark calls.
802
834
    _gather_lsprof_in_benchmarks = False
803
835
 
804
836
    def __init__(self, methodName='testMethod'):
805
837
        super(TestCase, self).__init__(methodName)
806
 
        self._cleanups = []
807
838
        self._directory_isolation = True
808
839
        self.exception_handlers.insert(0,
809
840
            (UnavailableFeature, self._do_unsupported_or_skip))
827
858
        self._track_transports()
828
859
        self._track_locks()
829
860
        self._clear_debug_flags()
830
 
        TestCase._active_threads = threading.activeCount()
831
 
        self.addCleanup(self._check_leaked_threads)
832
861
 
833
862
    def debug(self):
834
863
        # debug a frame up.
835
864
        import pdb
836
865
        pdb.Pdb().set_trace(sys._getframe().f_back)
837
866
 
838
 
    def _check_leaked_threads(self):
839
 
        active = threading.activeCount()
840
 
        leaked_threads = active - TestCase._active_threads
841
 
        TestCase._active_threads = active
842
 
        # If some tests make the number of threads *decrease*, we'll consider
843
 
        # that they are just observing old threads dieing, not agressively kill
844
 
        # random threads. So we don't report these tests as leaking. The risk
845
 
        # is that we have false positives that way (the test see 2 threads
846
 
        # going away but leak one) but it seems less likely than the actual
847
 
        # false positives (the test see threads going away and does not leak).
848
 
        if leaked_threads > 0:
849
 
            TestCase._leaking_threads_tests += 1
850
 
            if TestCase._first_thread_leaker_id is None:
851
 
                TestCase._first_thread_leaker_id = self.id()
 
867
    def discardDetail(self, name):
 
868
        """Extend the addDetail, getDetails api so we can remove a detail.
 
869
 
 
870
        eg. bzr always adds the 'log' detail at startup, but we don't want to
 
871
        include it for skipped, xfail, etc tests.
 
872
 
 
873
        It is safe to call this for a detail that doesn't exist, in case this
 
874
        gets called multiple times.
 
875
        """
 
876
        # We cheat. details is stored in __details which means we shouldn't
 
877
        # touch it. but getDetails() returns the dict directly, so we can
 
878
        # mutate it.
 
879
        details = self.getDetails()
 
880
        if name in details:
 
881
            del details[name]
852
882
 
853
883
    def _clear_debug_flags(self):
854
884
        """Prevent externally set debug flags affecting tests.
943
973
 
944
974
    def permit_dir(self, name):
945
975
        """Permit a directory to be used by this test. See permit_url."""
946
 
        name_transport = get_transport(name)
 
976
        name_transport = _mod_transport.get_transport(name)
947
977
        self.permit_url(name)
948
978
        self.permit_url(name_transport.base)
949
979
 
972
1002
            try:
973
1003
                workingtree.WorkingTree.open(path)
974
1004
            except (errors.NotBranchError, errors.NoWorkingTree):
975
 
                return
 
1005
                raise TestSkipped('Needs a working tree of bzr sources')
976
1006
        finally:
977
1007
            self.enable_directory_isolation()
978
1008
 
1028
1058
        self.addCleanup(transport_server.stop_server)
1029
1059
        # Obtain a real transport because if the server supplies a password, it
1030
1060
        # will be hidden from the base on the client side.
1031
 
        t = get_transport(transport_server.get_url())
 
1061
        t = _mod_transport.get_transport(transport_server.get_url())
1032
1062
        # Some transport servers effectively chroot the backing transport;
1033
1063
        # others like SFTPServer don't - users of the transport can walk up the
1034
1064
        # transport to read the entire backing transport. This wouldn't matter
1095
1125
            message += '\n'
1096
1126
        raise AssertionError("%snot equal:\na = %s\nb = %s\n"
1097
1127
            % (message,
1098
 
               pformat(a), pformat(b)))
 
1128
               pprint.pformat(a), pprint.pformat(b)))
1099
1129
 
1100
1130
    assertEquals = assertEqual
1101
1131
 
1455
1485
 
1456
1486
        The file is removed as the test is torn down.
1457
1487
        """
1458
 
        fileno, name = tempfile.mkstemp(suffix='.log', prefix='testbzr')
1459
 
        self._log_file = os.fdopen(fileno, 'w+')
 
1488
        self._log_file = StringIO()
1460
1489
        self._log_memento = bzrlib.trace.push_log_file(self._log_file)
1461
 
        self._log_file_name = name
1462
1490
        self.addCleanup(self._finishLogFile)
1463
1491
 
1464
1492
    def _finishLogFile(self):
1486
1514
        """
1487
1515
        debug.debug_flags.discard('strict_locks')
1488
1516
 
1489
 
    def addCleanup(self, callable, *args, **kwargs):
1490
 
        """Arrange to run a callable when this case is torn down.
1491
 
 
1492
 
        Callables are run in the reverse of the order they are registered,
1493
 
        ie last-in first-out.
1494
 
        """
1495
 
        self._cleanups.append((callable, args, kwargs))
1496
 
 
1497
1517
    def overrideAttr(self, obj, attr_name, new=_unitialized_attr):
1498
1518
        """Overrides an object attribute restoring it after the test.
1499
1519
 
1524
1544
            'EDITOR': None,
1525
1545
            'BZR_EMAIL': None,
1526
1546
            'BZREMAIL': None, # may still be present in the environment
1527
 
            'EMAIL': None,
 
1547
            'EMAIL': 'jrandom@example.com', # set EMAIL as bzr does not guess
1528
1548
            'BZR_PROGRESS_BAR': None,
1529
1549
            'BZR_LOG': None,
1530
1550
            'BZR_PLUGIN_PATH': None,
1583
1603
        """This test has failed for some known reason."""
1584
1604
        raise KnownFailure(reason)
1585
1605
 
 
1606
    def _suppress_log(self):
 
1607
        """Remove the log info from details."""
 
1608
        self.discardDetail('log')
 
1609
 
1586
1610
    def _do_skip(self, result, reason):
 
1611
        self._suppress_log()
1587
1612
        addSkip = getattr(result, 'addSkip', None)
1588
1613
        if not callable(addSkip):
1589
1614
            result.addSuccess(result)
1592
1617
 
1593
1618
    @staticmethod
1594
1619
    def _do_known_failure(self, result, e):
 
1620
        self._suppress_log()
1595
1621
        err = sys.exc_info()
1596
1622
        addExpectedFailure = getattr(result, 'addExpectedFailure', None)
1597
1623
        if addExpectedFailure is not None:
1605
1631
            reason = 'No reason given'
1606
1632
        else:
1607
1633
            reason = e.args[0]
 
1634
        self._suppress_log ()
1608
1635
        addNotApplicable = getattr(result, 'addNotApplicable', None)
1609
1636
        if addNotApplicable is not None:
1610
1637
            result.addNotApplicable(self, reason)
1612
1639
            self._do_skip(result, reason)
1613
1640
 
1614
1641
    @staticmethod
 
1642
    def _report_skip(self, result, err):
 
1643
        """Override the default _report_skip.
 
1644
 
 
1645
        We want to strip the 'log' detail. If we waint until _do_skip, it has
 
1646
        already been formatted into the 'reason' string, and we can't pull it
 
1647
        out again.
 
1648
        """
 
1649
        self._suppress_log()
 
1650
        super(TestCase, self)._report_skip(self, result, err)
 
1651
 
 
1652
    @staticmethod
 
1653
    def _report_expected_failure(self, result, err):
 
1654
        """Strip the log.
 
1655
 
 
1656
        See _report_skip for motivation.
 
1657
        """
 
1658
        self._suppress_log()
 
1659
        super(TestCase, self)._report_expected_failure(self, result, err)
 
1660
 
 
1661
    @staticmethod
1615
1662
    def _do_unsupported_or_skip(self, result, e):
1616
1663
        reason = e.args[0]
 
1664
        self._suppress_log()
1617
1665
        addNotSupported = getattr(result, 'addNotSupported', None)
1618
1666
        if addNotSupported is not None:
1619
1667
            result.addNotSupported(self, reason)
1666
1714
                unicodestr = self._log_contents.decode('utf8', 'replace')
1667
1715
                self._log_contents = unicodestr.encode('utf8')
1668
1716
            return self._log_contents
1669
 
        import bzrlib.trace
1670
 
        if bzrlib.trace._trace_file:
1671
 
            # flush the log file, to get all content
1672
 
            bzrlib.trace._trace_file.flush()
1673
 
        if self._log_file_name is not None:
1674
 
            logfile = open(self._log_file_name)
1675
 
            try:
1676
 
                log_contents = logfile.read()
1677
 
            finally:
1678
 
                logfile.close()
 
1717
        if self._log_file is not None:
 
1718
            log_contents = self._log_file.getvalue()
1679
1719
            try:
1680
1720
                log_contents.decode('utf8')
1681
1721
            except UnicodeDecodeError:
1682
1722
                unicodestr = log_contents.decode('utf8', 'replace')
1683
1723
                log_contents = unicodestr.encode('utf8')
1684
1724
            if not keep_log_file:
1685
 
                close_attempts = 0
1686
 
                max_close_attempts = 100
1687
 
                first_close_error = None
1688
 
                while close_attempts < max_close_attempts:
1689
 
                    close_attempts += 1
1690
 
                    try:
1691
 
                        self._log_file.close()
1692
 
                    except IOError, ioe:
1693
 
                        if ioe.errno is None:
1694
 
                            # No errno implies 'close() called during
1695
 
                            # concurrent operation on the same file object', so
1696
 
                            # retry.  Probably a thread is trying to write to
1697
 
                            # the log file.
1698
 
                            if first_close_error is None:
1699
 
                                first_close_error = ioe
1700
 
                            continue
1701
 
                        raise
1702
 
                    else:
1703
 
                        break
1704
 
                if close_attempts > 1:
1705
 
                    sys.stderr.write(
1706
 
                        'Unable to close log file on first attempt, '
1707
 
                        'will retry: %s\n' % (first_close_error,))
1708
 
                    if close_attempts == max_close_attempts:
1709
 
                        sys.stderr.write(
1710
 
                            'Unable to close log file after %d attempts.\n'
1711
 
                            % (max_close_attempts,))
1712
1725
                self._log_file = None
1713
1726
                # Permit multiple calls to get_log until we clean it up in
1714
1727
                # finishLogFile
1715
1728
                self._log_contents = log_contents
1716
 
                try:
1717
 
                    os.remove(self._log_file_name)
1718
 
                except OSError, e:
1719
 
                    if sys.platform == 'win32' and e.errno == errno.EACCES:
1720
 
                        sys.stderr.write(('Unable to delete log file '
1721
 
                                             ' %r\n' % self._log_file_name))
1722
 
                    else:
1723
 
                        raise
1724
 
                self._log_file_name = None
1725
1729
            return log_contents
1726
1730
        else:
1727
 
            return "No log file content and no log file name."
 
1731
            return "No log file content."
1728
1732
 
1729
1733
    def get_log(self):
1730
1734
        """Get a unicode string containing the log from bzrlib.trace.
1945
1949
            variables. A value of None will unset the env variable.
1946
1950
            The values must be strings. The change will only occur in the
1947
1951
            child, so you don't need to fix the environment after running.
1948
 
        :param skip_if_plan_to_signal: raise TestSkipped when true and os.kill
1949
 
            is not available.
 
1952
        :param skip_if_plan_to_signal: raise TestSkipped when true and system
 
1953
            doesn't support signalling subprocesses.
1950
1954
        :param allow_plugins: If False (default) pass --no-plugins to bzr.
1951
1955
 
1952
1956
        :returns: Popen object for the started process.
1953
1957
        """
1954
1958
        if skip_if_plan_to_signal:
1955
 
            if not getattr(os, 'kill', None):
1956
 
                raise TestSkipped("os.kill not available.")
 
1959
            if os.name != "posix":
 
1960
                raise TestSkipped("Sending signals not supported")
1957
1961
 
1958
1962
        if env_changes is None:
1959
1963
            env_changes = {}
1986
1990
            if not allow_plugins:
1987
1991
                command.append('--no-plugins')
1988
1992
            command.extend(process_args)
1989
 
            process = self._popen(command, stdin=PIPE, stdout=PIPE, stderr=PIPE)
 
1993
            process = self._popen(command, stdin=subprocess.PIPE,
 
1994
                                  stdout=subprocess.PIPE,
 
1995
                                  stderr=subprocess.PIPE)
1990
1996
        finally:
1991
1997
            restore_environment()
1992
1998
            if cwd is not None:
2000
2006
        Allows tests to override this method to intercept the calls made to
2001
2007
        Popen for introspection.
2002
2008
        """
2003
 
        return Popen(*args, **kwargs)
 
2009
        return subprocess.Popen(*args, **kwargs)
2004
2010
 
2005
2011
    def get_source_path(self):
2006
2012
        """Return the path of the directory containing bzrlib."""
2008
2014
 
2009
2015
    def get_bzr_path(self):
2010
2016
        """Return the path of the 'bzr' executable for this test suite."""
2011
 
        bzr_path = self.get_source_path()+'/bzr'
 
2017
        bzr_path = os.path.join(self.get_source_path(), "bzr")
2012
2018
        if not os.path.isfile(bzr_path):
2013
2019
            # We are probably installed. Assume sys.argv is the right file
2014
2020
            bzr_path = sys.argv[0]
2186
2192
 
2187
2193
        :param relpath: a path relative to the base url.
2188
2194
        """
2189
 
        t = get_transport(self.get_url(relpath))
 
2195
        t = _mod_transport.get_transport(self.get_url(relpath))
2190
2196
        self.assertFalse(t.is_readonly())
2191
2197
        return t
2192
2198
 
2198
2204
 
2199
2205
        :param relpath: a path relative to the base url.
2200
2206
        """
2201
 
        t = get_transport(self.get_readonly_url(relpath))
 
2207
        t = _mod_transport.get_transport(self.get_readonly_url(relpath))
2202
2208
        self.assertTrue(t.is_readonly())
2203
2209
        return t
2204
2210
 
2334
2340
        propagating. This method ensures than a test did not leaked.
2335
2341
        """
2336
2342
        root = TestCaseWithMemoryTransport.TEST_ROOT
2337
 
        self.permit_url(get_transport(root).base)
 
2343
        self.permit_url(_mod_transport.get_transport(root).base)
2338
2344
        wt = workingtree.WorkingTree.open(root)
2339
2345
        last_rev = wt.last_revision()
2340
2346
        if last_rev != 'null:':
2385
2391
            # might be a relative or absolute path
2386
2392
            maybe_a_url = self.get_url(relpath)
2387
2393
            segments = maybe_a_url.rsplit('/', 1)
2388
 
            t = get_transport(maybe_a_url)
 
2394
            t = _mod_transport.get_transport(maybe_a_url)
2389
2395
            if len(segments) > 1 and segments[-1] not in ('', '.'):
2390
2396
                t.ensure_base()
2391
2397
            if format is None:
2408
2414
        made_control = self.make_bzrdir(relpath, format=format)
2409
2415
        return made_control.create_repository(shared=shared)
2410
2416
 
2411
 
    def make_smart_server(self, path):
 
2417
    def make_smart_server(self, path, backing_server=None):
 
2418
        if backing_server is None:
 
2419
            backing_server = self.get_server()
2412
2420
        smart_server = test_server.SmartTCPServer_for_testing()
2413
 
        self.start_server(smart_server, self.get_server())
2414
 
        remote_transport = get_transport(smart_server.get_url()).clone(path)
 
2421
        self.start_server(smart_server, backing_server)
 
2422
        remote_transport = _mod_transport.get_transport(smart_server.get_url()
 
2423
                                                   ).clone(path)
2415
2424
        return remote_transport
2416
2425
 
2417
2426
    def make_branch_and_memory_tree(self, relpath, format=None):
2432
2441
 
2433
2442
    def setUp(self):
2434
2443
        super(TestCaseWithMemoryTransport, self).setUp()
 
2444
        # Ensure that ConnectedTransport doesn't leak sockets
 
2445
        def get_transport_with_cleanup(*args, **kwargs):
 
2446
            t = orig_get_transport(*args, **kwargs)
 
2447
            if isinstance(t, _mod_transport.ConnectedTransport):
 
2448
                self.addCleanup(t.disconnect)
 
2449
            return t
 
2450
 
 
2451
        orig_get_transport = self.overrideAttr(_mod_transport, 'get_transport',
 
2452
                                               get_transport_with_cleanup)
2435
2453
        self._make_test_root()
2436
2454
        self.addCleanup(os.chdir, os.getcwdu())
2437
2455
        self.makeAndChdirToTestDir()
2482
2500
 
2483
2501
    def check_file_contents(self, filename, expect):
2484
2502
        self.log("check contents of file %s" % filename)
2485
 
        contents = file(filename, 'r').read()
 
2503
        f = file(filename)
 
2504
        try:
 
2505
            contents = f.read()
 
2506
        finally:
 
2507
            f.close()
2486
2508
        if contents != expect:
2487
2509
            self.log("expected: %r" % expect)
2488
2510
            self.log("actually: %r" % contents)
2562
2584
                "a list or a tuple. Got %r instead" % (shape,))
2563
2585
        # It's OK to just create them using forward slashes on windows.
2564
2586
        if transport is None or transport.is_readonly():
2565
 
            transport = get_transport(".")
 
2587
            transport = _mod_transport.get_transport(".")
2566
2588
        for name in shape:
2567
2589
            self.assertIsInstance(name, basestring)
2568
2590
            if name[-1] == '/':
2578
2600
                content = "contents of %s%s" % (name.encode('utf-8'), end)
2579
2601
                transport.put_bytes_non_atomic(urlutils.escape(name), content)
2580
2602
 
2581
 
    def build_tree_contents(self, shape):
2582
 
        build_tree_contents(shape)
 
2603
    build_tree_contents = staticmethod(treeshape.build_tree_contents)
2583
2604
 
2584
2605
    def assertInWorkingTree(self, path, root_path='.', tree=None):
2585
2606
        """Assert whether path or paths are in the WorkingTree"""
2726
2747
    """
2727
2748
 
2728
2749
    def setUp(self):
 
2750
        from bzrlib.tests import http_server
2729
2751
        super(ChrootedTestCase, self).setUp()
2730
2752
        if not self.vfs_transport_factory == memory.MemoryServer:
2731
 
            self.transport_readonly_server = HttpServer
 
2753
            self.transport_readonly_server = http_server.HttpServer
2732
2754
 
2733
2755
 
2734
2756
def condition_id_re(pattern):
2737
2759
    :param pattern: A regular expression string.
2738
2760
    :return: A callable that returns True if the re matches.
2739
2761
    """
2740
 
    filter_re = osutils.re_compile_checked(pattern, 0,
2741
 
        'test filter')
 
2762
    filter_re = re.compile(pattern, 0)
2742
2763
    def condition(test):
2743
2764
        test_id = test.id()
2744
2765
        return filter_re.search(test_id)
2996
3017
 
2997
3018
 
2998
3019
def fork_decorator(suite):
 
3020
    if getattr(os, "fork", None) is None:
 
3021
        raise errors.BzrCommandError("platform does not support fork,"
 
3022
            " try --parallel=subprocess instead.")
2999
3023
    concurrency = osutils.local_concurrency()
3000
3024
    if concurrency == 1:
3001
3025
        return suite
3056
3080
    return suite
3057
3081
 
3058
3082
 
3059
 
class TestDecorator(TestSuite):
 
3083
class TestDecorator(TestUtil.TestSuite):
3060
3084
    """A decorator for TestCase/TestSuite objects.
3061
3085
    
3062
3086
    Usually, subclasses should override __iter__(used when flattening test
3065
3089
    """
3066
3090
 
3067
3091
    def __init__(self, suite):
3068
 
        TestSuite.__init__(self)
 
3092
        TestUtil.TestSuite.__init__(self)
3069
3093
        self.addTest(suite)
3070
3094
 
3071
3095
    def countTestCases(self):
3190
3214
 
3191
3215
def partition_tests(suite, count):
3192
3216
    """Partition suite into count lists of tests."""
3193
 
    result = []
3194
 
    tests = list(iter_suite_tests(suite))
3195
 
    tests_per_process = int(math.ceil(float(len(tests)) / count))
3196
 
    for block in range(count):
3197
 
        low_test = block * tests_per_process
3198
 
        high_test = low_test + tests_per_process
3199
 
        process_tests = tests[low_test:high_test]
3200
 
        result.append(process_tests)
3201
 
    return result
 
3217
    # This just assigns tests in a round-robin fashion.  On one hand this
 
3218
    # splits up blocks of related tests that might run faster if they shared
 
3219
    # resources, but on the other it avoids assigning blocks of slow tests to
 
3220
    # just one partition.  So the slowest partition shouldn't be much slower
 
3221
    # than the fastest.
 
3222
    partitions = [list() for i in range(count)]
 
3223
    tests = iter_suite_tests(suite)
 
3224
    for partition, test in itertools.izip(itertools.cycle(partitions), tests):
 
3225
        partition.append(test)
 
3226
    return partitions
3202
3227
 
3203
3228
 
3204
3229
def workaround_zealous_crypto_random():
3238
3263
 
3239
3264
    test_blocks = partition_tests(suite, concurrency)
3240
3265
    for process_tests in test_blocks:
3241
 
        process_suite = TestSuite()
 
3266
        process_suite = TestUtil.TestSuite()
3242
3267
        process_suite.addTests(process_tests)
3243
3268
        c2pread, c2pwrite = os.pipe()
3244
3269
        pid = os.fork()
3310
3335
                '--subunit']
3311
3336
            if '--no-plugins' in sys.argv:
3312
3337
                argv.append('--no-plugins')
3313
 
            # stderr=STDOUT would be ideal, but until we prevent noise on
3314
 
            # stderr it can interrupt the subunit protocol.
3315
 
            process = Popen(argv, stdin=PIPE, stdout=PIPE, stderr=PIPE,
3316
 
                bufsize=1)
 
3338
            # stderr=subprocess.STDOUT would be ideal, but until we prevent
 
3339
            # noise on stderr it can interrupt the subunit protocol.
 
3340
            process = subprocess.Popen(argv, stdin=subprocess.PIPE,
 
3341
                                      stdout=subprocess.PIPE,
 
3342
                                      stderr=subprocess.PIPE,
 
3343
                                      bufsize=1)
3317
3344
            test = TestInSubprocess(process, test_list_file_name)
3318
3345
            result.append(test)
3319
3346
        except:
3368
3395
 
3369
3396
    def startTest(self, test):
3370
3397
        self.profiler = bzrlib.lsprof.BzrProfiler()
 
3398
        # Prevent deadlocks in tests that use lsprof: those tests will
 
3399
        # unavoidably fail.
 
3400
        bzrlib.lsprof.BzrProfiler.profiler_block = 0
3371
3401
        self.profiler.start()
3372
3402
        ForwardingResult.startTest(self, test)
3373
3403
 
3394
3424
#                           rather than failing tests. And no longer raise
3395
3425
#                           LockContention when fctnl locks are not being used
3396
3426
#                           with proper exclusion rules.
 
3427
#   -Ethreads               Will display thread ident at creation/join time to
 
3428
#                           help track thread leaks
3397
3429
selftest_debug_flags = set()
3398
3430
 
3399
3431
 
3632
3664
        'bzrlib.doc',
3633
3665
        'bzrlib.tests.blackbox',
3634
3666
        'bzrlib.tests.commands',
 
3667
        'bzrlib.tests.doc_generate',
3635
3668
        'bzrlib.tests.per_branch',
3636
3669
        'bzrlib.tests.per_bzrdir',
3637
 
        'bzrlib.tests.per_bzrdir_colo',
 
3670
        'bzrlib.tests.per_controldir',
 
3671
        'bzrlib.tests.per_controldir_colo',
3638
3672
        'bzrlib.tests.per_foreign_vcs',
3639
3673
        'bzrlib.tests.per_interrepository',
3640
3674
        'bzrlib.tests.per_intertree',
3653
3687
        'bzrlib.tests.per_workingtree',
3654
3688
        'bzrlib.tests.test__annotator',
3655
3689
        'bzrlib.tests.test__bencode',
 
3690
        'bzrlib.tests.test__btree_serializer',
3656
3691
        'bzrlib.tests.test__chk_map',
3657
3692
        'bzrlib.tests.test__dirstate_helpers',
3658
3693
        'bzrlib.tests.test__groupcompress',
3701
3736
        'bzrlib.tests.test_export',
3702
3737
        'bzrlib.tests.test_extract',
3703
3738
        'bzrlib.tests.test_fetch',
 
3739
        'bzrlib.tests.test_fixtures',
3704
3740
        'bzrlib.tests.test_fifo_cache',
3705
3741
        'bzrlib.tests.test_filters',
3706
3742
        'bzrlib.tests.test_ftp_transport',
3727
3763
        'bzrlib.tests.test_knit',
3728
3764
        'bzrlib.tests.test_lazy_import',
3729
3765
        'bzrlib.tests.test_lazy_regex',
 
3766
        'bzrlib.tests.test_library_state',
3730
3767
        'bzrlib.tests.test_lock',
3731
3768
        'bzrlib.tests.test_lockable_files',
3732
3769
        'bzrlib.tests.test_lockdir',
3789
3826
        'bzrlib.tests.test_switch',
3790
3827
        'bzrlib.tests.test_symbol_versioning',
3791
3828
        'bzrlib.tests.test_tag',
 
3829
        'bzrlib.tests.test_test_server',
3792
3830
        'bzrlib.tests.test_testament',
3793
3831
        'bzrlib.tests.test_textfile',
3794
3832
        'bzrlib.tests.test_textmerge',
3800
3838
        'bzrlib.tests.test_transport_log',
3801
3839
        'bzrlib.tests.test_tree',
3802
3840
        'bzrlib.tests.test_treebuilder',
 
3841
        'bzrlib.tests.test_treeshape',
3803
3842
        'bzrlib.tests.test_tsort',
3804
3843
        'bzrlib.tests.test_tuned_gzip',
3805
3844
        'bzrlib.tests.test_ui',
3809
3848
        'bzrlib.tests.test_urlutils',
3810
3849
        'bzrlib.tests.test_version',
3811
3850
        'bzrlib.tests.test_version_info',
 
3851
        'bzrlib.tests.test_versionedfile',
3812
3852
        'bzrlib.tests.test_weave',
3813
3853
        'bzrlib.tests.test_whitebox',
3814
3854
        'bzrlib.tests.test_win32utils',
3836
3876
        'bzrlib.option',
3837
3877
        'bzrlib.symbol_versioning',
3838
3878
        'bzrlib.tests',
 
3879
        'bzrlib.tests.fixtures',
3839
3880
        'bzrlib.timestamp',
3840
3881
        'bzrlib.version_info_formats.format_custom',
3841
3882
        ]
3982
4023
    ...     bzrlib.tests.test_sampler.DemoTest('test_nothing'),
3983
4024
    ...     [('one', dict(param=1)),
3984
4025
    ...      ('two', dict(param=2))],
3985
 
    ...     TestSuite())
 
4026
    ...     TestUtil.TestSuite())
3986
4027
    >>> tests = list(iter_suite_tests(r))
3987
4028
    >>> len(tests)
3988
4029
    2
4035
4076
    :param new_id: The id to assign to it.
4036
4077
    :return: The new test.
4037
4078
    """
4038
 
    new_test = copy(test)
 
4079
    new_test = copy.copy(test)
4039
4080
    new_test.id = lambda: new_id
 
4081
    # XXX: Workaround <https://bugs.launchpad.net/testtools/+bug/637725>, which
 
4082
    # causes cloned tests to share the 'details' dict.  This makes it hard to
 
4083
    # read the test output for parameterized tests, because tracebacks will be
 
4084
    # associated with irrelevant tests.
 
4085
    try:
 
4086
        details = new_test._TestCase__details
 
4087
    except AttributeError:
 
4088
        # must be a different version of testtools than expected.  Do nothing.
 
4089
        pass
 
4090
    else:
 
4091
        # Reset the '__details' dict.
 
4092
        new_test._TestCase__details = {}
4040
4093
    return new_test
4041
4094
 
4042
4095
 
4102
4155
        if test_id != None:
4103
4156
            ui.ui_factory.clear_term()
4104
4157
            sys.stderr.write('\nWhile running: %s\n' % (test_id,))
 
4158
        # Ugly, but the last thing we want here is fail, so bear with it.
 
4159
        printable_e = str(e).decode(osutils.get_user_encoding(), 'replace'
 
4160
                                    ).encode('ascii', 'replace')
4105
4161
        sys.stderr.write('Unable to remove testing dir %s\n%s'
4106
 
                         % (os.path.basename(dirname), e))
 
4162
                         % (os.path.basename(dirname), printable_e))
4107
4163
 
4108
4164
 
4109
4165
class Feature(object):
4339
4395
UnicodeFilename = _UnicodeFilename()
4340
4396
 
4341
4397
 
 
4398
class _ByteStringNamedFilesystem(Feature):
 
4399
    """Is the filesystem based on bytes?"""
 
4400
 
 
4401
    def _probe(self):
 
4402
        if os.name == "posix":
 
4403
            return True
 
4404
        return False
 
4405
 
 
4406
ByteStringNamedFilesystem = _ByteStringNamedFilesystem()
 
4407
 
 
4408
 
4342
4409
class _UTF8Filesystem(Feature):
4343
4410
    """Is the filesystem UTF-8?"""
4344
4411
 
4454
4521
try:
4455
4522
    from subunit import TestProtocolClient
4456
4523
    from subunit.test_results import AutoTimingTestResultDecorator
 
4524
    class SubUnitBzrProtocolClient(TestProtocolClient):
 
4525
 
 
4526
        def addSuccess(self, test, details=None):
 
4527
            # The subunit client always includes the details in the subunit
 
4528
            # stream, but we don't want to include it in ours.
 
4529
            if details is not None and 'log' in details:
 
4530
                del details['log']
 
4531
            return super(SubUnitBzrProtocolClient, self).addSuccess(
 
4532
                test, details)
 
4533
 
4457
4534
    class SubUnitBzrRunner(TextTestRunner):
4458
4535
        def run(self, test):
4459
4536
            result = AutoTimingTestResultDecorator(
4460
 
                TestProtocolClient(self.stream))
 
4537
                SubUnitBzrProtocolClient(self.stream))
4461
4538
            test.run(result)
4462
4539
            return result
4463
4540
except ImportError: