1"""Test result object"""
2
3import sys
4import traceback
5import unittest
6
7from StringIO import StringIO
8
9from unittest2 import util
10from unittest2.compatibility import wraps
11
12__unittest = True
13
14def failfast(method):
15    @wraps(method)
16    def inner(self, *args, **kw):
17        if getattr(self, 'failfast', False):
18            self.stop()
19        return method(self, *args, **kw)
20    return inner
21
22
23STDOUT_LINE = '\nStdout:\n%s'
24STDERR_LINE = '\nStderr:\n%s'
25
26class TestResult(unittest.TestResult):
27    """Holder for test result information.
28
29    Test results are automatically managed by the TestCase and TestSuite
30    classes, and do not need to be explicitly manipulated by writers of tests.
31
32    Each instance holds the total number of tests run, and collections of
33    failures and errors that occurred among those test runs. The collections
34    contain tuples of (testcase, exceptioninfo), where exceptioninfo is the
35    formatted traceback of the error that occurred.
36    """
37    _previousTestClass = None
38    _moduleSetUpFailed = False
39
40    def __init__(self):
41        self.failfast = False
42        self.failures = []
43        self.errors = []
44        self.testsRun = 0
45        self.skipped = []
46        self.expectedFailures = []
47        self.unexpectedSuccesses = []
48        self.shouldStop = False
49        self.buffer = False
50        self._stdout_buffer = None
51        self._stderr_buffer = None
52        self._original_stdout = sys.stdout
53        self._original_stderr = sys.stderr
54        self._mirrorOutput = False
55
56    def startTest(self, test):
57        "Called when the given test is about to be run"
58        self.testsRun += 1
59        self._mirrorOutput = False
60        if self.buffer:
61            if self._stderr_buffer is None:
62                self._stderr_buffer = StringIO()
63                self._stdout_buffer = StringIO()
64            sys.stdout = self._stdout_buffer
65            sys.stderr = self._stderr_buffer
66
67    def startTestRun(self):
68        """Called once before any tests are executed.
69
70        See startTest for a method called before each test.
71        """
72
73    def stopTest(self, test):
74        """Called when the given test has been run"""
75        if self.buffer:
76            if self._mirrorOutput:
77                output = sys.stdout.getvalue()
78                error = sys.stderr.getvalue()
79                if output:
80                    if not output.endswith('\n'):
81                        output += '\n'
82                    self._original_stdout.write(STDOUT_LINE % output)
83                if error:
84                    if not error.endswith('\n'):
85                        error += '\n'
86                    self._original_stderr.write(STDERR_LINE % error)
87
88            sys.stdout = self._original_stdout
89            sys.stderr = self._original_stderr
90            self._stdout_buffer.seek(0)
91            self._stdout_buffer.truncate()
92            self._stderr_buffer.seek(0)
93            self._stderr_buffer.truncate()
94        self._mirrorOutput = False
95
96
97    def stopTestRun(self):
98        """Called once after all tests are executed.
99
100        See stopTest for a method called after each test.
101        """
102
103    @failfast
104    def addError(self, test, err):
105        """Called when an error has occurred. 'err' is a tuple of values as
106        returned by sys.exc_info().
107        """
108        self.errors.append((test, self._exc_info_to_string(err, test)))
109        self._mirrorOutput = True
110
111    @failfast
112    def addFailure(self, test, err):
113        """Called when an error has occurred. 'err' is a tuple of values as
114        returned by sys.exc_info()."""
115        self.failures.append((test, self._exc_info_to_string(err, test)))
116        self._mirrorOutput = True
117
118    def addSuccess(self, test):
119        "Called when a test has completed successfully"
120        pass
121
122    def addSkip(self, test, reason):
123        """Called when a test is skipped."""
124        self.skipped.append((test, reason))
125
126    def addExpectedFailure(self, test, err, bugnumber):
127        """Called when an expected failure/error occured."""
128        self.expectedFailures.append(
129            (test, self._exc_info_to_string(err, test)))
130
131    @failfast
132    def addUnexpectedSuccess(self, test, bugnumber):
133        """Called when a test was expected to fail, but succeed."""
134        self.unexpectedSuccesses.append(test)
135
136    def wasSuccessful(self):
137        "Tells whether or not this result was a success"
138        return (len(self.failures) + len(self.errors) == 0)
139
140    def stop(self):
141        "Indicates that the tests should be aborted"
142        self.shouldStop = True
143
144    def _exc_info_to_string(self, err, test):
145        """Converts a sys.exc_info()-style tuple of values into a string."""
146        exctype, value, tb = err
147        # Skip test runner traceback levels
148        while tb and self._is_relevant_tb_level(tb):
149            tb = tb.tb_next
150        if exctype is test.failureException:
151            # Skip assert*() traceback levels
152            length = self._count_relevant_tb_levels(tb)
153            msgLines = traceback.format_exception(exctype, value, tb, length)
154        else:
155            msgLines = traceback.format_exception(exctype, value, tb)
156
157        if self.buffer:
158            output = sys.stdout.getvalue()
159            error = sys.stderr.getvalue()
160            if output:
161                if not output.endswith('\n'):
162                    output += '\n'
163                msgLines.append(STDOUT_LINE % output)
164            if error:
165                if not error.endswith('\n'):
166                    error += '\n'
167                msgLines.append(STDERR_LINE % error)
168        return ''.join(msgLines)
169
170    def _is_relevant_tb_level(self, tb):
171        return '__unittest' in tb.tb_frame.f_globals
172
173    def _count_relevant_tb_levels(self, tb):
174        length = 0
175        while tb and not self._is_relevant_tb_level(tb):
176            length += 1
177            tb = tb.tb_next
178        return length
179
180    def __repr__(self):
181        return "<%s run=%i errors=%i failures=%i>" % \
182               (util.strclass(self.__class__), self.testsRun, len(self.errors),
183                len(self.failures))
184