Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6)

Side by Side Diff: third_party/grpc/src/python/grpcio/tests/_result.py

Issue 1932353002: Initial checkin of gRPC to third_party/ Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 # Copyright 2015, Google Inc.
2 # All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 # * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 # * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 # * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 import cStringIO as StringIO
31 import collections
32 import itertools
33 import traceback
34 import unittest
35 from xml.etree import ElementTree
36
37 import coverage
38
39 from tests import _loader
40
41
42 class CaseResult(collections.namedtuple('CaseResult', [
43 'id', 'name', 'kind', 'stdout', 'stderr', 'skip_reason', 'traceback'])):
44 """A serializable result of a single test case.
45
46 Attributes:
47 id (object): Any serializable object used to denote the identity of this
48 test case.
49 name (str or None): A human-readable name of the test case.
50 kind (CaseResult.Kind): The kind of test result.
51 stdout (object or None): Output on stdout, or None if nothing was captured.
52 stderr (object or None): Output on stderr, or None if nothing was captured.
53 skip_reason (object or None): The reason the test was skipped. Must be
54 something if self.kind is CaseResult.Kind.SKIP, else None.
55 traceback (object or None): The traceback of the test. Must be something if
56 self.kind is CaseResult.Kind.{ERROR, FAILURE, EXPECTED_FAILURE}, else
57 None.
58 """
59
60 class Kind:
61 UNTESTED = 'untested'
62 RUNNING = 'running'
63 ERROR = 'error'
64 FAILURE = 'failure'
65 SUCCESS = 'success'
66 SKIP = 'skip'
67 EXPECTED_FAILURE = 'expected failure'
68 UNEXPECTED_SUCCESS = 'unexpected success'
69
70 def __new__(cls, id=None, name=None, kind=None, stdout=None, stderr=None,
71 skip_reason=None, traceback=None):
72 """Helper keyword constructor for the namedtuple.
73
74 See this class' attributes for information on the arguments."""
75 assert id is not None
76 assert name is None or isinstance(name, str)
77 if kind is CaseResult.Kind.UNTESTED:
78 pass
79 elif kind is CaseResult.Kind.RUNNING:
80 pass
81 elif kind is CaseResult.Kind.ERROR:
82 assert traceback is not None
83 elif kind is CaseResult.Kind.FAILURE:
84 assert traceback is not None
85 elif kind is CaseResult.Kind.SUCCESS:
86 pass
87 elif kind is CaseResult.Kind.SKIP:
88 assert skip_reason is not None
89 elif kind is CaseResult.Kind.EXPECTED_FAILURE:
90 assert traceback is not None
91 elif kind is CaseResult.Kind.UNEXPECTED_SUCCESS:
92 pass
93 else:
94 assert False
95 return super(cls, CaseResult).__new__(
96 cls, id, name, kind, stdout, stderr, skip_reason, traceback)
97
98 def updated(self, name=None, kind=None, stdout=None, stderr=None,
99 skip_reason=None, traceback=None):
100 """Get a new validated CaseResult with the fields updated.
101
102 See this class' attributes for information on the arguments."""
103 name = self.name if name is None else name
104 kind = self.kind if kind is None else kind
105 stdout = self.stdout if stdout is None else stdout
106 stderr = self.stderr if stderr is None else stderr
107 skip_reason = self.skip_reason if skip_reason is None else skip_reason
108 traceback = self.traceback if traceback is None else traceback
109 return CaseResult(id=self.id, name=name, kind=kind, stdout=stdout,
110 stderr=stderr, skip_reason=skip_reason,
111 traceback=traceback)
112
113
114 class AugmentedResult(unittest.TestResult):
115 """unittest.Result that keeps track of additional information.
116
117 Uses CaseResult objects to store test-case results, providing additional
118 information beyond that of the standard Python unittest library, such as
119 standard output.
120
121 Attributes:
122 id_map (callable): A unary callable mapping unittest.TestCase objects to
123 unique identifiers.
124 cases (dict): A dictionary mapping from the identifiers returned by id_map
125 to CaseResult objects corresponding to those IDs.
126 """
127
128 def __init__(self, id_map):
129 """Initialize the object with an identifier mapping.
130
131 Arguments:
132 id_map (callable): Corresponds to the attribute `id_map`."""
133 super(AugmentedResult, self).__init__()
134 self.id_map = id_map
135 self.cases = None
136
137 def startTestRun(self):
138 """See unittest.TestResult.startTestRun."""
139 super(AugmentedResult, self).startTestRun()
140 self.cases = dict()
141
142 def stopTestRun(self):
143 """See unittest.TestResult.stopTestRun."""
144 super(AugmentedResult, self).stopTestRun()
145
146 def startTest(self, test):
147 """See unittest.TestResult.startTest."""
148 super(AugmentedResult, self).startTest(test)
149 case_id = self.id_map(test)
150 self.cases[case_id] = CaseResult(
151 id=case_id, name=test.id(), kind=CaseResult.Kind.RUNNING)
152
153 def addError(self, test, error):
154 """See unittest.TestResult.addError."""
155 super(AugmentedResult, self).addError(test, error)
156 case_id = self.id_map(test)
157 self.cases[case_id] = self.cases[case_id].updated(
158 kind=CaseResult.Kind.ERROR, traceback=error)
159
160 def addFailure(self, test, error):
161 """See unittest.TestResult.addFailure."""
162 super(AugmentedResult, self).addFailure(test, error)
163 case_id = self.id_map(test)
164 self.cases[case_id] = self.cases[case_id].updated(
165 kind=CaseResult.Kind.FAILURE, traceback=error)
166
167 def addSuccess(self, test):
168 """See unittest.TestResult.addSuccess."""
169 super(AugmentedResult, self).addSuccess(test)
170 case_id = self.id_map(test)
171 self.cases[case_id] = self.cases[case_id].updated(
172 kind=CaseResult.Kind.SUCCESS)
173
174 def addSkip(self, test, reason):
175 """See unittest.TestResult.addSkip."""
176 super(AugmentedResult, self).addSkip(test, reason)
177 case_id = self.id_map(test)
178 self.cases[case_id] = self.cases[case_id].updated(
179 kind=CaseResult.Kind.SKIP, skip_reason=reason)
180
181 def addExpectedFailure(self, test, error):
182 """See unittest.TestResult.addExpectedFailure."""
183 super(AugmentedResult, self).addExpectedFailure(test, error)
184 case_id = self.id_map(test)
185 self.cases[case_id] = self.cases[case_id].updated(
186 kind=CaseResult.Kind.EXPECTED_FAILURE, traceback=error)
187
188 def addUnexpectedSuccess(self, test):
189 """See unittest.TestResult.addUnexpectedSuccess."""
190 super(AugmentedResult, self).addUnexpectedSuccess(test)
191 case_id = self.id_map(test)
192 self.cases[case_id] = self.cases[case_id].updated(
193 kind=CaseResult.Kind.UNEXPECTED_SUCCESS)
194
195 def set_output(self, test, stdout, stderr):
196 """Set the output attributes for the CaseResult corresponding to a test.
197
198 Args:
199 test (unittest.TestCase): The TestCase to set the outputs of.
200 stdout (str): Output from stdout to assign to self.id_map(test).
201 stderr (str): Output from stderr to assign to self.id_map(test).
202 """
203 case_id = self.id_map(test)
204 self.cases[case_id] = self.cases[case_id].updated(
205 stdout=stdout, stderr=stderr)
206
207 def augmented_results(self, filter):
208 """Convenience method to retrieve filtered case results.
209
210 Args:
211 filter (callable): A unary predicate to filter over CaseResult objects.
212 """
213 return (self.cases[case_id] for case_id in self.cases
214 if filter(self.cases[case_id]))
215
216
217 class CoverageResult(AugmentedResult):
218 """Extension to AugmentedResult adding coverage.py support per test.\
219
220 Attributes:
221 coverage_context (coverage.Coverage): coverage.py management object.
222 """
223
224 def __init__(self, id_map):
225 """See AugmentedResult.__init__."""
226 super(CoverageResult, self).__init__(id_map=id_map)
227 self.coverage_context = None
228
229 def startTest(self, test):
230 """See unittest.TestResult.startTest.
231
232 Additionally initializes and begins code coverage tracking."""
233 super(CoverageResult, self).startTest(test)
234 self.coverage_context = coverage.Coverage(data_suffix=True)
235 self.coverage_context.start()
236
237 def stopTest(self, test):
238 """See unittest.TestResult.stopTest.
239
240 Additionally stops and deinitializes code coverage tracking."""
241 super(CoverageResult, self).stopTest(test)
242 self.coverage_context.stop()
243 self.coverage_context.save()
244 self.coverage_context = None
245
246 def stopTestRun(self):
247 """See unittest.TestResult.stopTestRun."""
248 super(CoverageResult, self).stopTestRun()
249 # TODO(atash): Dig deeper into why the following line fails to properly
250 # combine coverage data from the Cython plugin.
251 #coverage.Coverage().combine()
252
253
254 class _Colors:
255 """Namespaced constants for terminal color magic numbers."""
256 HEADER = '\033[95m'
257 INFO = '\033[94m'
258 OK = '\033[92m'
259 WARN = '\033[93m'
260 FAIL = '\033[91m'
261 BOLD = '\033[1m'
262 UNDERLINE = '\033[4m'
263 END = '\033[0m'
264
265
266 class TerminalResult(CoverageResult):
267 """Extension to CoverageResult adding basic terminal reporting."""
268
269 def __init__(self, out, id_map):
270 """Initialize the result object.
271
272 Args:
273 out (file-like): Output file to which terminal-colored live results will
274 be written.
275 id_map (callable): See AugmentedResult.__init__.
276 """
277 super(TerminalResult, self).__init__(id_map=id_map)
278 self.out = out
279
280 def startTestRun(self):
281 """See unittest.TestResult.startTestRun."""
282 super(TerminalResult, self).startTestRun()
283 self.out.write(
284 _Colors.HEADER +
285 'Testing gRPC Python...\n' +
286 _Colors.END)
287
288 def stopTestRun(self):
289 """See unittest.TestResult.stopTestRun."""
290 super(TerminalResult, self).stopTestRun()
291 self.out.write(summary(self))
292 self.out.flush()
293
294 def addError(self, test, error):
295 """See unittest.TestResult.addError."""
296 super(TerminalResult, self).addError(test, error)
297 self.out.write(
298 _Colors.FAIL +
299 'ERROR {}\n'.format(test.id()) +
300 _Colors.END)
301 self.out.flush()
302
303 def addFailure(self, test, error):
304 """See unittest.TestResult.addFailure."""
305 super(TerminalResult, self).addFailure(test, error)
306 self.out.write(
307 _Colors.FAIL +
308 'FAILURE {}\n'.format(test.id()) +
309 _Colors.END)
310 self.out.flush()
311
312 def addSuccess(self, test):
313 """See unittest.TestResult.addSuccess."""
314 super(TerminalResult, self).addSuccess(test)
315 self.out.write(
316 _Colors.OK +
317 'SUCCESS {}\n'.format(test.id()) +
318 _Colors.END)
319 self.out.flush()
320
321 def addSkip(self, test, reason):
322 """See unittest.TestResult.addSkip."""
323 super(TerminalResult, self).addSkip(test, reason)
324 self.out.write(
325 _Colors.INFO +
326 'SKIP {}\n'.format(test.id()) +
327 _Colors.END)
328 self.out.flush()
329
330 def addExpectedFailure(self, test, error):
331 """See unittest.TestResult.addExpectedFailure."""
332 super(TerminalResult, self).addExpectedFailure(test, error)
333 self.out.write(
334 _Colors.INFO +
335 'FAILURE_OK {}\n'.format(test.id()) +
336 _Colors.END)
337 self.out.flush()
338
339 def addUnexpectedSuccess(self, test):
340 """See unittest.TestResult.addUnexpectedSuccess."""
341 super(TerminalResult, self).addUnexpectedSuccess(test)
342 self.out.write(
343 _Colors.INFO +
344 'UNEXPECTED_OK {}\n'.format(test.id()) +
345 _Colors.END)
346 self.out.flush()
347
348 def _traceback_string(type, value, trace):
349 """Generate a descriptive string of a Python exception traceback.
350
351 Args:
352 type (class): The type of the exception.
353 value (Exception): The value of the exception.
354 trace (traceback): Traceback of the exception.
355
356 Returns:
357 str: Formatted exception descriptive string.
358 """
359 buffer = StringIO.StringIO()
360 traceback.print_exception(type, value, trace, file=buffer)
361 return buffer.getvalue()
362
363 def summary(result):
364 """A summary string of a result object.
365
366 Args:
367 result (AugmentedResult): The result object to get the summary of.
368
369 Returns:
370 str: The summary string.
371 """
372 assert isinstance(result, AugmentedResult)
373 untested = list(result.augmented_results(
374 lambda case_result: case_result.kind is CaseResult.Kind.UNTESTED))
375 running = list(result.augmented_results(
376 lambda case_result: case_result.kind is CaseResult.Kind.RUNNING))
377 failures = list(result.augmented_results(
378 lambda case_result: case_result.kind is CaseResult.Kind.FAILURE))
379 errors = list(result.augmented_results(
380 lambda case_result: case_result.kind is CaseResult.Kind.ERROR))
381 successes = list(result.augmented_results(
382 lambda case_result: case_result.kind is CaseResult.Kind.SUCCESS))
383 skips = list(result.augmented_results(
384 lambda case_result: case_result.kind is CaseResult.Kind.SKIP))
385 expected_failures = list(result.augmented_results(
386 lambda case_result: case_result.kind is CaseResult.Kind.EXPECTED_FAILURE))
387 unexpected_successes = list(result.augmented_results(
388 lambda case_result: case_result.kind is CaseResult.Kind.UNEXPECTED_SUCCESS ))
389 running_names = [case.name for case in running]
390 finished_count = (len(failures) + len(errors) + len(successes) +
391 len(expected_failures) + len(unexpected_successes))
392 statistics = (
393 '{finished} tests finished:\n'
394 '\t{successful} successful\n'
395 '\t{unsuccessful} unsuccessful\n'
396 '\t{skipped} skipped\n'
397 '\t{expected_fail} expected failures\n'
398 '\t{unexpected_successful} unexpected successes\n'
399 'Interrupted Tests:\n'
400 '\t{interrupted}\n'
401 .format(finished=finished_count,
402 successful=len(successes),
403 unsuccessful=(len(failures)+len(errors)),
404 skipped=len(skips),
405 expected_fail=len(expected_failures),
406 unexpected_successful=len(unexpected_successes),
407 interrupted=str(running_names)))
408 tracebacks = '\n\n'.join([
409 (_Colors.FAIL + '{test_name}' + _Colors.END + '\n' +
410 _Colors.BOLD + 'traceback:' + _Colors.END + '\n' +
411 '{traceback}\n' +
412 _Colors.BOLD + 'stdout:' + _Colors.END + '\n' +
413 '{stdout}\n' +
414 _Colors.BOLD + 'stderr:' + _Colors.END + '\n' +
415 '{stderr}\n').format(
416 test_name=result.name,
417 traceback=_traceback_string(*result.traceback),
418 stdout=result.stdout, stderr=result.stderr)
419 for result in itertools.chain(failures, errors)
420 ])
421 notes = 'Unexpected successes: {}\n'.format([
422 result.name for result in unexpected_successes])
423 return statistics + '\nErrors/Failures: \n' + tracebacks + '\n' + notes
424
425
426 def jenkins_junit_xml(result):
427 """An XML tree object that when written is recognizable by Jenkins.
428
429 Args:
430 result (AugmentedResult): The result object to get the junit xml output of.
431
432 Returns:
433 ElementTree.ElementTree: The XML tree.
434 """
435 assert isinstance(result, AugmentedResult)
436 root = ElementTree.Element('testsuites')
437 suite = ElementTree.SubElement(root, 'testsuite', {
438 'name': 'Python gRPC tests',
439 })
440 for case in result.cases.values():
441 if case.kind is CaseResult.Kind.SUCCESS:
442 ElementTree.SubElement(suite, 'testcase', {
443 'name': case.name,
444 })
445 elif case.kind in (CaseResult.Kind.ERROR, CaseResult.Kind.FAILURE):
446 case_xml = ElementTree.SubElement(suite, 'testcase', {
447 'name': case.name,
448 })
449 error_xml = ElementTree.SubElement(case_xml, 'error', {})
450 error_xml.text = ''.format(case.stderr, case.traceback)
451 return ElementTree.ElementTree(element=root)
OLDNEW
« no previous file with comments | « third_party/grpc/src/python/grpcio/tests/_loader.py ('k') | third_party/grpc/src/python/grpcio/tests/_runner.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698