- Notifications
You must be signed in to change notification settings - Fork 31.8k
/
Copy pathresult.py
256 lines (217 loc) · 8.92 KB
/
result.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
"""Test result object"""
importio
importsys
importtraceback
from . importutil
fromfunctoolsimportwraps
__unittest=True
deffailfast(method):
@wraps(method)
definner(self, *args, **kw):
ifgetattr(self, 'failfast', False):
self.stop()
returnmethod(self, *args, **kw)
returninner
STDOUT_LINE='\nStdout:\n%s'
STDERR_LINE='\nStderr:\n%s'
classTestResult(object):
"""Holder for test result information.
Test results are automatically managed by the TestCase and TestSuite
classes, and do not need to be explicitly manipulated by writers of tests.
Each instance holds the total number of tests run, and collections of
failures and errors that occurred among those test runs. The collections
contain tuples of (testcase, exceptioninfo), where exceptioninfo is the
formatted traceback of the error that occurred.
"""
_previousTestClass=None
_testRunEntered=False
_moduleSetUpFailed=False
def__init__(self, stream=None, descriptions=None, verbosity=None):
self.failfast=False
self.failures= []
self.errors= []
self.testsRun=0
self.skipped= []
self.expectedFailures= []
self.unexpectedSuccesses= []
self.collectedDurations= []
self.shouldStop=False
self.buffer=False
self.tb_locals=False
self._stdout_buffer=None
self._stderr_buffer=None
self._original_stdout=sys.stdout
self._original_stderr=sys.stderr
self._mirrorOutput=False
defprintErrors(self):
"Called by TestRunner after test run"
defstartTest(self, test):
"Called when the given test is about to be run"
self.testsRun+=1
self._mirrorOutput=False
self._setupStdout()
def_setupStdout(self):
ifself.buffer:
ifself._stderr_bufferisNone:
self._stderr_buffer=io.StringIO()
self._stdout_buffer=io.StringIO()
sys.stdout=self._stdout_buffer
sys.stderr=self._stderr_buffer
defstartTestRun(self):
"""Called once before any tests are executed.
See startTest for a method called before each test.
"""
defstopTest(self, test):
"""Called when the given test has been run"""
self._restoreStdout()
self._mirrorOutput=False
def_restoreStdout(self):
ifself.buffer:
ifself._mirrorOutput:
output=sys.stdout.getvalue()
error=sys.stderr.getvalue()
ifoutput:
ifnotoutput.endswith('\n'):
output+='\n'
self._original_stdout.write(STDOUT_LINE%output)
iferror:
ifnoterror.endswith('\n'):
error+='\n'
self._original_stderr.write(STDERR_LINE%error)
sys.stdout=self._original_stdout
sys.stderr=self._original_stderr
self._stdout_buffer.seek(0)
self._stdout_buffer.truncate()
self._stderr_buffer.seek(0)
self._stderr_buffer.truncate()
defstopTestRun(self):
"""Called once after all tests are executed.
See stopTest for a method called after each test.
"""
@failfast
defaddError(self, test, err):
"""Called when an error has occurred. 'err' is a tuple of values as
returned by sys.exc_info().
"""
self.errors.append((test, self._exc_info_to_string(err, test)))
self._mirrorOutput=True
@failfast
defaddFailure(self, test, err):
"""Called when an error has occurred. 'err' is a tuple of values as
returned by sys.exc_info()."""
self.failures.append((test, self._exc_info_to_string(err, test)))
self._mirrorOutput=True
defaddSubTest(self, test, subtest, err):
"""Called at the end of a subtest.
'err' is None if the subtest ended successfully, otherwise it's a
tuple of values as returned by sys.exc_info().
"""
# By default, we don't do anything with successful subtests, but
# more sophisticated test results might want to record them.
iferrisnotNone:
ifgetattr(self, 'failfast', False):
self.stop()
ifissubclass(err[0], test.failureException):
errors=self.failures
else:
errors=self.errors
errors.append((subtest, self._exc_info_to_string(err, test)))
self._mirrorOutput=True
defaddSuccess(self, test):
"Called when a test has completed successfully"
pass
defaddSkip(self, test, reason):
"""Called when a test is skipped."""
self.skipped.append((test, reason))
defaddExpectedFailure(self, test, err):
"""Called when an expected failure/error occurred."""
self.expectedFailures.append(
(test, self._exc_info_to_string(err, test)))
@failfast
defaddUnexpectedSuccess(self, test):
"""Called when a test was expected to fail, but succeed."""
self.unexpectedSuccesses.append(test)
defaddDuration(self, test, elapsed):
"""Called when a test finished to run, regardless of its outcome.
*test* is the test case corresponding to the test method.
*elapsed* is the time represented in seconds, and it includes the
execution of cleanup functions.
"""
# support for a TextTestRunner using an old TestResult class
ifhasattr(self, "collectedDurations"):
# Pass test repr and not the test object itself to avoid resources leak
self.collectedDurations.append((str(test), elapsed))
defwasSuccessful(self):
"""Tells whether or not this result was a success."""
# The hasattr check is for test_result's OldResult test. That
# way this method works on objects that lack the attribute.
# (where would such result instances come from? old stored pickles?)
return ((len(self.failures) ==len(self.errors) ==0) and
(nothasattr(self, 'unexpectedSuccesses') or
len(self.unexpectedSuccesses) ==0))
defstop(self):
"""Indicates that the tests should be aborted."""
self.shouldStop=True
def_exc_info_to_string(self, err, test):
"""Converts a sys.exc_info()-style tuple of values into a string."""
exctype, value, tb=err
tb=self._clean_tracebacks(exctype, value, tb, test)
tb_e=traceback.TracebackException(
exctype, value, tb,
capture_locals=self.tb_locals, compact=True)
msgLines=list(tb_e.format())
ifself.buffer:
output=sys.stdout.getvalue()
error=sys.stderr.getvalue()
ifoutput:
ifnotoutput.endswith('\n'):
output+='\n'
msgLines.append(STDOUT_LINE%output)
iferror:
ifnoterror.endswith('\n'):
error+='\n'
msgLines.append(STDERR_LINE%error)
return''.join(msgLines)
def_clean_tracebacks(self, exctype, value, tb, test):
ret=None
first=True
excs= [(exctype, value, tb)]
seen= {id(value)} # Detect loops in chained exceptions.
whileexcs:
(exctype, value, tb) =excs.pop()
# Skip test runner traceback levels
whiletbandself._is_relevant_tb_level(tb):
tb=tb.tb_next
# Skip assert*() traceback levels
ifexctypeistest.failureException:
self._remove_unittest_tb_frames(tb)
iffirst:
ret=tb
first=False
else:
value.__traceback__=tb
ifvalueisnotNone:
forcin (value.__cause__, value.__context__):
ifcisnotNoneandid(c) notinseen:
excs.append((type(c), c, c.__traceback__))
seen.add(id(c))
returnret
def_is_relevant_tb_level(self, tb):
return'__unittest'intb.tb_frame.f_globals
def_remove_unittest_tb_frames(self, tb):
'''Truncates usercode tb at the first unittest frame.
If the first frame of the traceback is in user code,
the prefix up to the first unittest frame is returned.
If the first frame is already in the unittest module,
the traceback is not modified.
'''
prev=None
whiletbandnotself._is_relevant_tb_level(tb):
prev=tb
tb=tb.tb_next
ifprevisnotNone:
prev.tb_next=None
def__repr__(self):
return ("<%s run=%i errors=%i failures=%i>"%
(util.strclass(self.__class__), self.testsRun, len(self.errors),
len(self.failures)))