1"""Test result object"""
2
3import os
4import sys
5import traceback
6
7from StringIO import StringIO
8
9from . import util
10from functools 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
22STDOUT_LINE = '\nStdout:\n%s'
23STDERR_LINE = '\nStderr:\n%s'
24
25
26class TestResult(object):
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    _testRunEntered = False
39    _moduleSetUpFailed = False
40    def __init__(self, stream=None, descriptions=None, verbosity=None):
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 printErrors(self):
57        "Called by TestRunner after test run"
58
59    def startTest(self, test):
60        "Called when the given test is about to be run"
61        self.testsRun += 1
62        self._mirrorOutput = False
63        self._setupStdout()
64
65    def _setupStdout(self):
66        if self.buffer:
67            if self._stderr_buffer is None:
68                self._stderr_buffer = StringIO()
69                self._stdout_buffer = StringIO()
70            sys.stdout = self._stdout_buffer
71            sys.stderr = self._stderr_buffer
72
73    def startTestRun(self):
74        """Called once before any tests are executed.
75
76        See startTest for a method called before each test.
77        """
78
79    def stopTest(self, test):
80        """Called when the given test has been run"""
81        self._restoreStdout()
82        self._mirrorOutput = False
83
84    def _restoreStdout(self):
85        if self.buffer:
86            if self._mirrorOutput:
87                output = sys.stdout.getvalue()
88                error = sys.stderr.getvalue()
89                if output:
90                    if not output.endswith('\n'):
91                        output += '\n'
92                    self._original_stdout.write(STDOUT_LINE % output)
93                if error:
94                    if not error.endswith('\n'):
95                        error += '\n'
96                    self._original_stderr.write(STDERR_LINE % error)
97
98            sys.stdout = self._original_stdout
99            sys.stderr = self._original_stderr
100            self._stdout_buffer.seek(0)
101            self._stdout_buffer.truncate()
102            self._stderr_buffer.seek(0)
103            self._stderr_buffer.truncate()
104
105    def stopTestRun(self):
106        """Called once after all tests are executed.
107
108        See stopTest for a method called after each test.
109        """
110
111    @failfast
112    def addError(self, test, err):
113        """Called when an error has occurred. 'err' is a tuple of values as
114        returned by sys.exc_info().
115        """
116        self.errors.append((test, self._exc_info_to_string(err, test)))
117        self._mirrorOutput = True
118
119    @failfast
120    def addFailure(self, test, err):
121        """Called when an error has occurred. 'err' is a tuple of values as
122        returned by sys.exc_info()."""
123        self.failures.append((test, self._exc_info_to_string(err, test)))
124        self._mirrorOutput = True
125
126    def addSuccess(self, test):
127        "Called when a test has completed successfully"
128        pass
129
130    def addSkip(self, test, reason):
131        """Called when a test is skipped."""
132        self.skipped.append((test, reason))
133
134    def addExpectedFailure(self, test, err):
135        """Called when an expected failure/error occured."""
136        self.expectedFailures.append(
137            (test, self._exc_info_to_string(err, test)))
138
139    @failfast
140    def addUnexpectedSuccess(self, test):
141        """Called when a test was expected to fail, but succeed."""
142        self.unexpectedSuccesses.append(test)
143
144    def wasSuccessful(self):
145        "Tells whether or not this result was a success"
146        return len(self.failures) == len(self.errors) == 0
147
148    def stop(self):
149        "Indicates that the tests should be aborted"
150        self.shouldStop = True
151
152    def _exc_info_to_string(self, err, test):
153        """Converts a sys.exc_info()-style tuple of values into a string."""
154        exctype, value, tb = err
155        # Skip test runner traceback levels
156        while tb and self._is_relevant_tb_level(tb):
157            tb = tb.tb_next
158
159        if exctype is test.failureException:
160            # Skip assert*() traceback levels
161            length = self._count_relevant_tb_levels(tb)
162            msgLines = traceback.format_exception(exctype, value, tb, length)
163        else:
164            msgLines = traceback.format_exception(exctype, value, tb)
165
166        if self.buffer:
167            output = sys.stdout.getvalue()
168            error = sys.stderr.getvalue()
169            if output:
170                if not output.endswith('\n'):
171                    output += '\n'
172                msgLines.append(STDOUT_LINE % output)
173            if error:
174                if not error.endswith('\n'):
175                    error += '\n'
176                msgLines.append(STDERR_LINE % error)
177        return ''.join(msgLines)
178
179
180    def _is_relevant_tb_level(self, tb):
181        return '__unittest' in tb.tb_frame.f_globals
182
183    def _count_relevant_tb_levels(self, tb):
184        length = 0
185        while tb and not self._is_relevant_tb_level(tb):
186            length += 1
187            tb = tb.tb_next
188        return length
189
190    def __repr__(self):
191        return ("<%s run=%i errors=%i failures=%i>" %
192               (util.strclass(self.__class__), self.testsRun, len(self.errors),
193                len(self.failures)))
194