~landscape/zope3/ztk-1.1.3

« back to all changes in this revision

Viewing changes to src/twisted/trial/reporter.py

  • Committer: Andreas Hasenack
  • Date: 2009-07-20 17:49:16 UTC
  • Revision ID: andreas@canonical.com-20090720174916-g2tn6qmietz2hn0u
Revert twisted removal, it breaks several dozen tests [trivial]

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.trial.test.test_reporter -*-
 
2
#
 
3
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
 
4
# See LICENSE for details.
 
5
#
 
6
# Maintainer: Jonathan Lange <jml@twistedmatrix.com>
 
7
 
 
8
"""Defines classes that handle the results of tests.
 
9
 
 
10
API Stability: Unstable
 
11
"""
 
12
 
 
13
import sys, os
 
14
import time
 
15
import warnings
 
16
 
 
17
from twisted.python import reflect, failure, log
 
18
from twisted.python.util import untilConcludes
 
19
from twisted.trial import itrial
 
20
import zope.interface as zi
 
21
 
 
22
pyunit = __import__('unittest')
 
23
 
 
24
 
 
25
class BrokenTestCaseWarning(Warning):
 
26
    """emitted as a warning when an exception occurs in one of
 
27
    setUp, tearDown, setUpClass, or tearDownClass"""
 
28
 
 
29
 
 
30
class SafeStream(object):
 
31
    """
 
32
    Wraps a stream object so that all C{write} calls are wrapped in
 
33
    L{untilConcludes}.
 
34
    """
 
35
 
 
36
    def __init__(self, original):
 
37
        self.original = original
 
38
 
 
39
    def __getattr__(self, name):
 
40
        return getattr(self.original, name)
 
41
 
 
42
    def write(self, *a, **kw):
 
43
        return untilConcludes(self.original.write, *a, **kw)
 
44
 
 
45
 
 
46
class TestResult(pyunit.TestResult, object):
 
47
    """Accumulates the results of several L{twisted.trial.unittest.TestCase}s.
 
48
    """
 
49
 
 
50
    def __init__(self):
 
51
        super(TestResult, self).__init__()
 
52
        self.skips = []
 
53
        self.expectedFailures = []
 
54
        self.unexpectedSuccesses = []
 
55
        self.successes = []
 
56
        self._timings = []
 
57
 
 
58
    def __repr__(self):
 
59
        return ('<%s run=%d errors=%d failures=%d todos=%d dones=%d skips=%d>'
 
60
                % (reflect.qual(self.__class__), self.testsRun,
 
61
                   len(self.errors), len(self.failures),
 
62
                   len(self.expectedFailures), len(self.skips),
 
63
                   len(self.unexpectedSuccesses)))
 
64
 
 
65
    def _getTime(self):
 
66
        return time.time()
 
67
 
 
68
    def startTest(self, test):
 
69
        """This must be called before the given test is commenced.
 
70
 
 
71
        @type test: L{pyunit.TestCase}
 
72
        """
 
73
        super(TestResult, self).startTest(test)
 
74
        self._testStarted = self._getTime()
 
75
 
 
76
    def stopTest(self, test):
 
77
        """This must be called after the given test is completed.
 
78
 
 
79
        @type test: L{pyunit.TestCase}
 
80
        """
 
81
        super(TestResult, self).stopTest(test)
 
82
        self._lastTime = self._getTime() - self._testStarted
 
83
 
 
84
    def addFailure(self, test, fail):
 
85
        """Report a failed assertion for the given test.
 
86
 
 
87
        @type test: L{pyunit.TestCase}
 
88
        @type fail: L{failure.Failure} or L{tuple}
 
89
        """
 
90
        if isinstance(fail, tuple):
 
91
            fail = failure.Failure(fail[1], fail[0], fail[2])
 
92
        self.failures.append((test, fail))
 
93
 
 
94
    def addError(self, test, error):
 
95
        """Report an error that occurred while running the given test.
 
96
 
 
97
        @type test: L{pyunit.TestCase}
 
98
        @type fail: L{failure.Failure} or L{tuple}
 
99
        """
 
100
        if isinstance(error, tuple):
 
101
            error = failure.Failure(error[1], error[0], error[2])
 
102
        self.errors.append((test, error))
 
103
 
 
104
    def addSkip(self, test, reason):
 
105
        """
 
106
        Report that the given test was skipped.
 
107
 
 
108
        In Trial, tests can be 'skipped'. Tests are skipped mostly because there
 
109
        is some platform or configuration issue that prevents them from being
 
110
        run correctly.
 
111
 
 
112
        @type test: L{pyunit.TestCase}
 
113
        @type reason: L{str}
 
114
        """
 
115
        self.skips.append((test, reason))
 
116
 
 
117
    def addUnexpectedSuccess(self, test, todo):
 
118
        """Report that the given test succeeded against expectations.
 
119
 
 
120
        In Trial, tests can be marked 'todo'. That is, they are expected to fail.
 
121
        When a test that is expected to fail instead succeeds, it should call
 
122
        this method to report the unexpected success.
 
123
 
 
124
        @type test: L{pyunit.TestCase}
 
125
        @type todo: L{unittest.Todo}
 
126
        """
 
127
        # XXX - 'todo' should just be a string
 
128
        self.unexpectedSuccesses.append((test, todo))
 
129
 
 
130
    def addExpectedFailure(self, test, error, todo):
 
131
        """Report that the given test succeeded against expectations.
 
132
 
 
133
        In Trial, tests can be marked 'todo'. That is, they are expected to fail.
 
134
 
 
135
        @type test: L{pyunit.TestCase}
 
136
        @type error: L{failure.Failure}
 
137
        @type todo: L{unittest.Todo}
 
138
        """
 
139
        # XXX - 'todo' should just be a string
 
140
        self.expectedFailures.append((test, error, todo))
 
141
 
 
142
    def addSuccess(self, test):
 
143
        """Report that the given test succeeded.
 
144
 
 
145
        @type test: L{pyunit.TestCase}
 
146
        """
 
147
        self.successes.append((test,))
 
148
 
 
149
    def upDownError(self, method, error, warn, printStatus):
 
150
        pass
 
151
 
 
152
    def cleanupErrors(self, errs):
 
153
        """Report an error that occurred during the cleanup between tests.
 
154
        """
 
155
        # XXX - deprecate this method, we don't need it any more
 
156
 
 
157
    def startSuite(self, name):
 
158
        # XXX - these should be removed, but not in this branch
 
159
        pass
 
160
 
 
161
    def endSuite(self, name):
 
162
        # XXX - these should be removed, but not in this branch
 
163
        pass
 
164
 
 
165
 
 
166
class Reporter(TestResult):
 
167
    zi.implements(itrial.IReporter)
 
168
 
 
169
    separator = '-' * 79
 
170
    doubleSeparator = '=' * 79
 
171
 
 
172
    def __init__(self, stream=sys.stdout, tbformat='default', realtime=False):
 
173
        super(Reporter, self).__init__()
 
174
        self.stream = SafeStream(stream)
 
175
        self.tbformat = tbformat
 
176
        self.realtime = realtime
 
177
 
 
178
    def startTest(self, test):
 
179
        super(Reporter, self).startTest(test)
 
180
 
 
181
    def addFailure(self, test, fail):
 
182
        super(Reporter, self).addFailure(test, fail)
 
183
        if self.realtime:
 
184
            fail = self.failures[-1][1] # guarantee it's a Failure
 
185
            self.write(self._formatFailureTraceback(fail))
 
186
 
 
187
    def addError(self, test, error):
 
188
        super(Reporter, self).addError(test, error)
 
189
        if self.realtime:
 
190
            error = self.errors[-1][1] # guarantee it's a Failure
 
191
            self.write(self._formatFailureTraceback(error))
 
192
 
 
193
    def write(self, format, *args):
 
194
        s = str(format)
 
195
        assert isinstance(s, type(''))
 
196
        if args:
 
197
            self.stream.write(s % args)
 
198
        else:
 
199
            self.stream.write(s)
 
200
        untilConcludes(self.stream.flush)
 
201
 
 
202
    def writeln(self, format, *args):
 
203
        self.write(format, *args)
 
204
        self.write('\n')
 
205
 
 
206
    def upDownError(self, method, error, warn, printStatus):
 
207
        super(Reporter, self).upDownError(method, error, warn, printStatus)
 
208
        if warn:
 
209
            tbStr = self._formatFailureTraceback(error)
 
210
            log.msg(tbStr)
 
211
            msg = ("caught exception in %s, your TestCase is broken\n\n%s"
 
212
                   % (method, tbStr))
 
213
            warnings.warn(msg, BrokenTestCaseWarning, stacklevel=2)
 
214
 
 
215
    def cleanupErrors(self, errs):
 
216
        super(Reporter, self).cleanupErrors(errs)
 
217
        warnings.warn("%s\n%s" % ("REACTOR UNCLEAN! traceback(s) follow: ",
 
218
                                  self._formatFailureTraceback(errs)),
 
219
                      BrokenTestCaseWarning)
 
220
 
 
221
    def _trimFrames(self, frames):
 
222
        # when a method fails synchronously, the stack looks like this:
 
223
        #  [0]: defer.maybeDeferred()
 
224
        #  [1]: utils.runWithWarningsSuppressed()
 
225
        #  [2:-2]: code in the test method which failed
 
226
        #  [-1]: unittest.fail
 
227
 
 
228
        # when a method fails inside a Deferred (i.e., when the test method
 
229
        # returns a Deferred, and that Deferred's errback fires), the stack
 
230
        # captured inside the resulting Failure looks like this:
 
231
        #  [0]: defer.Deferred._runCallbacks
 
232
        #  [1:-2]: code in the testmethod which failed
 
233
        #  [-1]: unittest.fail
 
234
 
 
235
        # as a result, we want to trim either [maybeDeferred,runWWS] or
 
236
        # [Deferred._runCallbacks] from the front, and trim the
 
237
        # [unittest.fail] from the end.
 
238
 
 
239
        newFrames = list(frames)
 
240
 
 
241
        if len(frames) < 2:
 
242
            return newFrames
 
243
 
 
244
        first = newFrames[0]
 
245
        second = newFrames[1]
 
246
        if (first[0] == "maybeDeferred"
 
247
            and os.path.splitext(os.path.basename(first[1]))[0] == 'defer'
 
248
            and second[0] == "runWithWarningsSuppressed"
 
249
            and os.path.splitext(os.path.basename(second[1]))[0] == 'utils'):
 
250
            newFrames = newFrames[2:]
 
251
        elif (first[0] == "_runCallbacks"
 
252
              and os.path.splitext(os.path.basename(first[1]))[0] == 'defer'):
 
253
            newFrames = newFrames[1:]
 
254
 
 
255
        last = newFrames[-1]
 
256
        if (last[0].startswith('fail')
 
257
            and os.path.splitext(os.path.basename(last[1]))[0] == 'unittest'):
 
258
            newFrames = newFrames[:-1]
 
259
 
 
260
        return newFrames
 
261
 
 
262
    def _formatFailureTraceback(self, fail):
 
263
        if isinstance(fail, str):
 
264
            return fail.rstrip() + '\n'
 
265
        fail.frames, frames = self._trimFrames(fail.frames), fail.frames
 
266
        result = fail.getTraceback(detail=self.tbformat, elideFrameworkCode=True)
 
267
        fail.frames = frames
 
268
        return result
 
269
 
 
270
    def _printResults(self, flavour, errors, formatter):
 
271
        for content in errors:
 
272
            self.writeln(self.doubleSeparator)
 
273
            self.writeln('%s: %s' % (flavour, content[0].id()))
 
274
            self.writeln('')
 
275
            self.write(formatter(*(content[1:])))
 
276
 
 
277
    def _printExpectedFailure(self, error, todo):
 
278
        return 'Reason: %r\n%s' % (todo.reason,
 
279
                                   self._formatFailureTraceback(error))
 
280
 
 
281
    def _printUnexpectedSuccess(self, todo):
 
282
        ret = 'Reason: %r\n' % (todo.reason,)
 
283
        if todo.errors:
 
284
            ret += 'Expected errors: %s\n' % (', '.join(todo.errors),)
 
285
        return ret
 
286
 
 
287
    def printErrors(self):
 
288
        """Print all of the non-success results in full to the stream.
 
289
        """
 
290
        self.write('\n')
 
291
        self._printResults('[SKIPPED]', self.skips, lambda x : '%s\n' % x)
 
292
        self._printResults('[TODO]', self.expectedFailures,
 
293
                           self._printExpectedFailure)
 
294
        self._printResults('[FAIL]', self.failures,
 
295
                           self._formatFailureTraceback)
 
296
        self._printResults('[ERROR]', self.errors,
 
297
                           self._formatFailureTraceback)
 
298
        self._printResults('[SUCCESS!?!]', self.unexpectedSuccesses,
 
299
                           self._printUnexpectedSuccess)
 
300
 
 
301
    def printSummary(self):
 
302
        """Print a line summarising the test results to the stream.
 
303
        """
 
304
        summaries = []
 
305
        for stat in ("skips", "expectedFailures", "failures", "errors",
 
306
                     "unexpectedSuccesses", "successes"):
 
307
            num = len(getattr(self, stat))
 
308
            if num:
 
309
                summaries.append('%s=%d' % (stat, num))
 
310
        summary = (summaries and ' ('+', '.join(summaries)+')') or ''
 
311
        if not self.wasSuccessful():
 
312
            status = "FAILED"
 
313
        else:
 
314
            status = "PASSED"
 
315
        self.write("%s%s\n", status, summary)
 
316
 
 
317
 
 
318
class MinimalReporter(Reporter):
 
319
    """A minimalist reporter that prints only a summary of the test result,
 
320
    in the form of (timeTaken, #tests, #tests, #errors, #failures, #skips).
 
321
    """
 
322
 
 
323
    _runStarted = None
 
324
 
 
325
    def startTest(self, test):
 
326
        super(MinimalReporter, self).startTest(test)
 
327
        if self._runStarted is None:
 
328
            self._runStarted = self._getTime()
 
329
 
 
330
    def printErrors(self):
 
331
        pass
 
332
 
 
333
    def printSummary(self):
 
334
        numTests = self.testsRun
 
335
        t = (self._runStarted - self._getTime(), numTests, numTests,
 
336
             len(self.errors), len(self.failures), len(self.skips))
 
337
        self.writeln(' '.join(map(str, t)))
 
338
 
 
339
 
 
340
class TextReporter(Reporter):
 
341
    """
 
342
    Simple reporter that prints a single character for each test as it runs,
 
343
    along with the standard Trial summary text.
 
344
    """
 
345
 
 
346
    def addSuccess(self, test):
 
347
        super(TextReporter, self).addSuccess(test)
 
348
        self.write('.')
 
349
 
 
350
    def addError(self, *args):
 
351
        super(TextReporter, self).addError(*args)
 
352
        self.write('E')
 
353
 
 
354
    def addFailure(self, *args):
 
355
        super(TextReporter, self).addFailure(*args)
 
356
        self.write('F')
 
357
 
 
358
    def addSkip(self, *args):
 
359
        super(TextReporter, self).addSkip(*args)
 
360
        self.write('S')
 
361
 
 
362
    def addExpectedFailure(self, *args):
 
363
        super(TextReporter, self).addExpectedFailure(*args)
 
364
        self.write('T')
 
365
 
 
366
    def addUnexpectedSuccess(self, *args):
 
367
        super(TextReporter, self).addUnexpectedSuccess(*args)
 
368
        self.write('!')
 
369
 
 
370
 
 
371
class VerboseTextReporter(Reporter):
 
372
    """
 
373
    A verbose reporter that prints the name of each test as it is running.
 
374
 
 
375
    Each line is printed with the name of the test, followed by the result of
 
376
    that test.
 
377
    """
 
378
 
 
379
    # This is actually the bwverbose option
 
380
 
 
381
    def startTest(self, tm):
 
382
        self.write('%s ... ', tm.id())
 
383
        super(VerboseTextReporter, self).startTest(tm)
 
384
 
 
385
    def addSuccess(self, test):
 
386
        super(VerboseTextReporter, self).addSuccess(test)
 
387
        self.write('[OK]')
 
388
 
 
389
    def addError(self, *args):
 
390
        super(VerboseTextReporter, self).addError(*args)
 
391
        self.write('[ERROR]')
 
392
 
 
393
    def addFailure(self, *args):
 
394
        super(VerboseTextReporter, self).addFailure(*args)
 
395
        self.write('[FAILURE]')
 
396
 
 
397
    def addSkip(self, *args):
 
398
        super(VerboseTextReporter, self).addSkip(*args)
 
399
        self.write('[SKIPPED]')
 
400
 
 
401
    def addExpectedFailure(self, *args):
 
402
        super(VerboseTextReporter, self).addExpectedFailure(*args)
 
403
        self.write('[TODO]')
 
404
 
 
405
    def addUnexpectedSuccess(self, *args):
 
406
        super(VerboseTextReporter, self).addUnexpectedSuccess(*args)
 
407
        self.write('[SUCCESS!?!]')
 
408
 
 
409
    def stopTest(self, test):
 
410
        super(VerboseTextReporter, self).stopTest(test)
 
411
        self.write('\n')
 
412
 
 
413
 
 
414
class TimingTextReporter(VerboseTextReporter):
 
415
    """Prints out each test as it is running, followed by the time taken for each
 
416
    test to run.
 
417
    """
 
418
 
 
419
    def stopTest(self, method):
 
420
        super(TimingTextReporter, self).stopTest(method)
 
421
        self.write("(%.03f secs)\n" % self._lastTime)
 
422
 
 
423
 
 
424
class _AnsiColorizer(object):
 
425
    """
 
426
    A colorizer is an object that loosely wraps around a stream, allowing
 
427
    callers to write text to the stream in a particular color.
 
428
 
 
429
    Colorizer classes must implement C{supported()} and C{write(text, color)}.
 
430
    """
 
431
    _colors = dict(black=30, red=31, green=32, yellow=33,
 
432
                   blue=34, magenta=35, cyan=36, white=37)
 
433
 
 
434
    def __init__(self, stream):
 
435
        self.stream = stream
 
436
 
 
437
    def supported(self):
 
438
        """
 
439
        A class method that returns True if the current platform supports
 
440
        coloring terminal output using this method. Returns False otherwise.
 
441
        """
 
442
        # assuming stderr
 
443
        # isatty() returns False when SSHd into Win32 machine
 
444
        if 'CYGWIN' in os.environ:
 
445
            return True
 
446
        if not sys.stderr.isatty():
 
447
            return False # auto color only on TTYs
 
448
        try:
 
449
            import curses
 
450
            curses.setupterm()
 
451
            return curses.tigetnum("colors") > 2
 
452
        except:
 
453
            # guess false in case of error
 
454
            return False
 
455
    supported = classmethod(supported)
 
456
 
 
457
    def write(self, text, color):
 
458
        """
 
459
        Write the given text to the stream in the given color.
 
460
 
 
461
        @param text: Text to be written to the stream.
 
462
 
 
463
        @param color: A string label for a color. e.g. 'red', 'white'.
 
464
        """
 
465
        color = self._colors[color]
 
466
        self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text))
 
467
 
 
468
 
 
469
class _Win32Colorizer(object):
 
470
    """
 
471
    See _AnsiColorizer docstring.
 
472
    """
 
473
    def __init__(self, stream):
 
474
        from win32console import GetStdHandle, STD_OUTPUT_HANDLE, \
 
475
             FOREGROUND_RED, FOREGROUND_BLUE, FOREGROUND_GREEN, \
 
476
             FOREGROUND_INTENSITY
 
477
        red, green, blue, bold = (FOREGROUND_RED, FOREGROUND_GREEN,
 
478
                                  FOREGROUND_BLUE, FOREGROUND_INTENSITY)
 
479
        self.stream = stream
 
480
        self.screenBuffer = GetStdHandle(STD_OUTPUT_HANDLE)
 
481
        self._colors = {
 
482
            'normal': red | green | blue,
 
483
            'red': red | bold,
 
484
            'green': green | bold,
 
485
            'blue': blue | bold,
 
486
            'yellow': red | green | bold,
 
487
            'magenta': red | blue | bold,
 
488
            'cyan': green | blue | bold,
 
489
            'white': red | green | blue | bold
 
490
            }
 
491
 
 
492
    def supported(self):
 
493
        try:
 
494
            import win32console
 
495
            screenBuffer = win32console.GetStdHandle(
 
496
                win32console.STD_OUTPUT_HANDLE)
 
497
        except ImportError:
 
498
            return False
 
499
        import pywintypes
 
500
        try:
 
501
            screenBuffer.SetConsoleTextAttribute(
 
502
                win32console.FOREGROUND_RED |
 
503
                win32console.FOREGROUND_GREEN |
 
504
                win32console.FOREGROUND_BLUE)
 
505
        except pywintypes.error:
 
506
            return False
 
507
        else:
 
508
            return True
 
509
    supported = classmethod(supported)
 
510
 
 
511
    def write(self, text, color):
 
512
        color = self._colors[color]
 
513
        self.screenBuffer.SetConsoleTextAttribute(color)
 
514
        self.stream.write(text)
 
515
        self.screenBuffer.SetConsoleTextAttribute(self._colors['normal'])
 
516
 
 
517
 
 
518
class _NullColorizer(object):
 
519
    """
 
520
    See _AnsiColorizer docstring.
 
521
    """
 
522
    def __init__(self, stream):
 
523
        self.stream = stream
 
524
 
 
525
    def supported(self):
 
526
        return True
 
527
    supported = classmethod(supported)
 
528
 
 
529
    def write(self, text, color):
 
530
        self.stream.write(text)
 
531
 
 
532
 
 
533
class TreeReporter(Reporter):
 
534
    """Print out the tests in the form a tree.
 
535
 
 
536
    Tests are indented according to which class and module they belong.
 
537
    Results are printed in ANSI color.
 
538
    """
 
539
 
 
540
    currentLine = ''
 
541
    indent = '  '
 
542
    columns = 79
 
543
 
 
544
    FAILURE = 'red'
 
545
    ERROR = 'red'
 
546
    TODO = 'blue'
 
547
    SKIP = 'blue'
 
548
    TODONE = 'red'
 
549
    SUCCESS = 'green'
 
550
 
 
551
    def __init__(self, stream=sys.stdout, tbformat='default', realtime=False):
 
552
        super(TreeReporter, self).__init__(stream, tbformat, realtime)
 
553
        self._lastTest = []
 
554
        for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]:
 
555
            if colorizer.supported():
 
556
                self._colorizer = colorizer(stream)
 
557
                break
 
558
 
 
559
    def getDescription(self, test):
 
560
        """
 
561
        Return the name of the method which 'test' represents.  This is
 
562
        what gets displayed in the leaves of the tree.
 
563
 
 
564
        e.g. getDescription(TestCase('test_foo')) ==> test_foo
 
565
        """
 
566
        return test.id().split('.')[-1]
 
567
 
 
568
    def addSuccess(self, test):
 
569
        super(TreeReporter, self).addSuccess(test)
 
570
        self.endLine('[OK]', self.SUCCESS)
 
571
 
 
572
    def addError(self, *args):
 
573
        super(TreeReporter, self).addError(*args)
 
574
        self.endLine('[ERROR]', self.ERROR)
 
575
 
 
576
    def addFailure(self, *args):
 
577
        super(TreeReporter, self).addFailure(*args)
 
578
        self.endLine('[FAIL]', self.FAILURE)
 
579
 
 
580
    def addSkip(self, *args):
 
581
        super(TreeReporter, self).addSkip(*args)
 
582
        self.endLine('[SKIPPED]', self.SKIP)
 
583
 
 
584
    def addExpectedFailure(self, *args):
 
585
        super(TreeReporter, self).addExpectedFailure(*args)
 
586
        self.endLine('[TODO]', self.TODO)
 
587
 
 
588
    def addUnexpectedSuccess(self, *args):
 
589
        super(TreeReporter, self).addUnexpectedSuccess(*args)
 
590
        self.endLine('[SUCCESS!?!]', self.TODONE)
 
591
 
 
592
    def write(self, format, *args):
 
593
        if args:
 
594
            format = format % args
 
595
        self.currentLine = format
 
596
        super(TreeReporter, self).write(self.currentLine)
 
597
 
 
598
    def _testPrelude(self, test):
 
599
        segments = [test.__class__.__module__, test.__class__.__name__]
 
600
        indentLevel = 0
 
601
        for seg in segments:
 
602
            if indentLevel < len(self._lastTest):
 
603
                if seg != self._lastTest[indentLevel]:
 
604
                    self.write('%s%s\n' % (self.indent * indentLevel, seg))
 
605
            else:
 
606
                self.write('%s%s\n' % (self.indent * indentLevel, seg))
 
607
            indentLevel += 1
 
608
        self._lastTest = segments
 
609
 
 
610
    def cleanupErrors(self, errs):
 
611
        self._colorizer.write('    cleanup errors', self.ERROR)
 
612
        self.endLine('[ERROR]', self.ERROR)
 
613
        super(TreeReporter, self).cleanupErrors(errs)
 
614
 
 
615
    def upDownError(self, method, error, warn, printStatus):
 
616
        self.write(self.color("  %s" % method, self.ERROR))
 
617
        if printStatus:
 
618
            self.endLine('[ERROR]', self.ERROR)
 
619
        super(TreeReporter, self).upDownError(method, error, warn, printStatus)
 
620
 
 
621
    def startTest(self, method):
 
622
        self._testPrelude(method)
 
623
        self.write('%s%s ... ' % (self.indent * (len(self._lastTest)),
 
624
                                  self.getDescription(method)))
 
625
        super(TreeReporter, self).startTest(method)
 
626
 
 
627
    def endLine(self, message, color):
 
628
        spaces = ' ' * (self.columns - len(self.currentLine) - len(message))
 
629
        super(TreeReporter, self).write(spaces)
 
630
        self._colorizer.write(message, color)
 
631
        super(TreeReporter, self).write("\n")