15 from __future__
import absolute_import
21 from xml.etree
import ElementTree
26 from tests
import _loader
30 collections.namedtuple(
'CaseResult', [
31 'id',
'name',
'kind',
'stdout',
'stderr',
'skip_reason',
'traceback'
33 """A serializable result of a single test case.
36 id (object): Any serializable object used to denote the identity of this
38 name (str or None): A human-readable name of the test case.
39 kind (CaseResult.Kind): The kind of test result.
40 stdout (object or None): Output on stdout, or None if nothing was captured.
41 stderr (object or None): Output on stderr, or None if nothing was captured.
42 skip_reason (object or None): The reason the test was skipped. Must be
43 something if self.kind is CaseResult.Kind.SKIP, else None.
44 traceback (object or None): The traceback of the test. Must be something if
45 self.kind is CaseResult.Kind.{ERROR, FAILURE, EXPECTED_FAILURE}, else
56 EXPECTED_FAILURE =
'expected failure'
57 UNEXPECTED_SUCCESS =
'unexpected success'
67 """Helper keyword constructor for the namedtuple.
69 See this class' attributes for information on the arguments."""
71 assert name
is None or isinstance(name, str)
72 if kind
is CaseResult.Kind.UNTESTED:
74 elif kind
is CaseResult.Kind.RUNNING:
76 elif kind
is CaseResult.Kind.ERROR:
77 assert traceback
is not None
78 elif kind
is CaseResult.Kind.FAILURE:
79 assert traceback
is not None
80 elif kind
is CaseResult.Kind.SUCCESS:
82 elif kind
is CaseResult.Kind.SKIP:
83 assert skip_reason
is not None
84 elif kind
is CaseResult.Kind.EXPECTED_FAILURE:
85 assert traceback
is not None
86 elif kind
is CaseResult.Kind.UNEXPECTED_SUCCESS:
90 return super(cls, CaseResult).
__new__(cls, id, name, kind, stdout,
91 stderr, skip_reason, traceback)
100 """Get a new validated CaseResult with the fields updated.
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
114 skip_reason=skip_reason,
119 """unittest.Result that keeps track of additional information.
121 Uses CaseResult objects to store test-case results, providing additional
122 information beyond that of the standard Python unittest library, such as
126 id_map (callable): A unary callable mapping unittest.TestCase objects to
128 cases (dict): A dictionary mapping from the identifiers returned by id_map
129 to CaseResult objects corresponding to those IDs.
133 """Initialize the object with an identifier mapping.
136 id_map (callable): Corresponds to the attribute `id_map`."""
137 super(AugmentedResult, self).
__init__()
142 """See unittest.TestResult.startTestRun."""
147 """See unittest.TestResult.startTest."""
148 super(AugmentedResult, self).
startTest(test)
149 case_id = self.
id_map(test)
152 kind=CaseResult.Kind.RUNNING)
155 """See unittest.TestResult.addError."""
156 super(AugmentedResult, self).
addError(test, err)
157 case_id = self.
id_map(test)
159 kind=CaseResult.Kind.ERROR, traceback=err)
162 """See unittest.TestResult.addFailure."""
163 super(AugmentedResult, self).
addFailure(test, err)
164 case_id = self.
id_map(test)
166 kind=CaseResult.Kind.FAILURE, traceback=err)
169 """See unittest.TestResult.addSuccess."""
171 case_id = self.
id_map(test)
173 kind=CaseResult.Kind.SUCCESS)
176 """See unittest.TestResult.addSkip."""
177 super(AugmentedResult, self).
addSkip(test, reason)
178 case_id = self.
id_map(test)
180 kind=CaseResult.Kind.SKIP, skip_reason=reason)
183 """See unittest.TestResult.addExpectedFailure."""
185 case_id = self.
id_map(test)
187 kind=CaseResult.Kind.EXPECTED_FAILURE, traceback=err)
190 """See unittest.TestResult.addUnexpectedSuccess."""
192 case_id = self.
id_map(test)
194 kind=CaseResult.Kind.UNEXPECTED_SUCCESS)
197 """Set the output attributes for the CaseResult corresponding to a test.
200 test (unittest.TestCase): The TestCase to set the outputs of.
201 stdout (str): Output from stdout to assign to self.id_map(test).
202 stderr (str): Output from stderr to assign to self.id_map(test).
204 case_id = self.
id_map(test)
206 stdout=stdout.decode(), stderr=stderr.decode())
209 """Convenience method to retrieve filtered case results.
212 filter (callable): A unary predicate to filter over CaseResult objects.
214 return (self.
cases[case_id]
215 for case_id
in self.
cases
216 if filter(self.
cases[case_id]))
220 """Extension to AugmentedResult adding coverage.py support per test.\
223 coverage_context (coverage.Coverage): coverage.py management object.
227 """See AugmentedResult.__init__."""
228 super(CoverageResult, self).
__init__(id_map=id_map)
232 """See unittest.TestResult.startTest.
234 Additionally initializes and begins code coverage tracking."""
235 super(CoverageResult, self).
startTest(test)
240 """See unittest.TestResult.stopTest.
242 Additionally stops and deinitializes code coverage tracking."""
243 super(CoverageResult, self).
stopTest(test)
250 """Namespaced constants for terminal color magic numbers."""
257 UNDERLINE =
'\033[4m'
262 """Extension to CoverageResult adding basic terminal reporting."""
265 """Initialize the result object.
268 out (file-like): Output file to which terminal-colored live results will
270 id_map (callable): See AugmentedResult.__init__.
272 super(TerminalResult, self).
__init__(id_map=id_map)
276 """See unittest.TestResult.startTestRun."""
278 self.
out.
write(_Colors.HEADER +
'Testing gRPC Python...\n' +
282 """See unittest.TestResult.stopTestRun."""
288 """See unittest.TestResult.addError."""
289 super(TerminalResult, self).
addError(test, err)
295 """See unittest.TestResult.addFailure."""
296 super(TerminalResult, self).
addFailure(test, err)
302 """See unittest.TestResult.addSuccess."""
309 """See unittest.TestResult.addSkip."""
310 super(TerminalResult, self).
addSkip(test, reason)
316 """See unittest.TestResult.addExpectedFailure."""
318 self.
out.
write(_Colors.INFO +
'FAILURE_OK {}\n'.
format(test.id()) +
323 """See unittest.TestResult.addUnexpectedSuccess."""
325 self.
out.
write(_Colors.INFO +
'UNEXPECTED_OK {}\n'.
format(test.id()) +
331 """Generate a descriptive string of a Python exception traceback.
334 type (class): The type of the exception.
335 value (Exception): The value of the exception.
336 trace (traceback): Traceback of the exception.
339 str: Formatted exception descriptive string.
341 buffer = moves.cStringIO()
342 traceback.print_exception(type, value, trace, file=buffer)
343 return buffer.getvalue()
347 """A summary string of a result object.
350 result (AugmentedResult): The result object to get the summary of.
353 str: The summary string.
355 assert isinstance(result, AugmentedResult)
357 result.augmented_results(
358 lambda case_result: case_result.kind
is CaseResult.Kind.UNTESTED))
360 result.augmented_results(
361 lambda case_result: case_result.kind
is CaseResult.Kind.RUNNING))
363 result.augmented_results(
364 lambda case_result: case_result.kind
is CaseResult.Kind.FAILURE))
366 result.augmented_results(
367 lambda case_result: case_result.kind
is CaseResult.Kind.ERROR))
369 result.augmented_results(
370 lambda case_result: case_result.kind
is CaseResult.Kind.SUCCESS))
372 result.augmented_results(
373 lambda case_result: case_result.kind
is CaseResult.Kind.SKIP))
374 expected_failures = list(
375 result.augmented_results(
lambda case_result: case_result.kind
is
376 CaseResult.Kind.EXPECTED_FAILURE))
377 unexpected_successes = list(
378 result.augmented_results(
lambda case_result: case_result.kind
is
379 CaseResult.Kind.UNEXPECTED_SUCCESS))
380 running_names = [case.name
for case
in running]
381 finished_count = (
len(failures) +
len(errors) +
len(successes) +
382 len(expected_failures) +
len(unexpected_successes))
383 statistics = (
'{finished} tests finished:\n'
384 '\t{successful} successful\n'
385 '\t{unsuccessful} unsuccessful\n'
386 '\t{skipped} skipped\n'
387 '\t{expected_fail} expected failures\n'
388 '\t{unexpected_successful} unexpected successes\n'
389 'Interrupted Tests:\n'
390 '\t{interrupted}\n'.
format(
391 finished=finished_count,
392 successful=
len(successes),
393 unsuccessful=(
len(failures) +
len(errors)),
395 expected_fail=
len(expected_failures),
396 unexpected_successful=
len(unexpected_successes),
397 interrupted=
str(running_names)))
398 tracebacks =
'\n\n'.join([
399 (_Colors.FAIL +
'{test_name}' + _Colors.END +
'\n' + _Colors.BOLD +
400 'traceback:' + _Colors.END +
'\n' +
'{traceback}\n' + _Colors.BOLD +
401 'stdout:' + _Colors.END +
'\n' +
'{stdout}\n' + _Colors.BOLD +
402 'stderr:' + _Colors.END +
'\n' +
'{stderr}\n').
format(
403 test_name=result.name,
405 stdout=result.stdout,
406 stderr=result.stderr)
407 for result
in itertools.chain(failures, errors)
409 notes =
'Unexpected successes: {}\n'.
format(
410 [result.name
for result
in unexpected_successes])
411 return statistics +
'\nErrors/Failures: \n' + tracebacks +
'\n' + notes
415 """An XML tree object that when written is recognizable by Jenkins.
418 result (AugmentedResult): The result object to get the junit xml output of.
421 ElementTree.ElementTree: The XML tree.
423 assert isinstance(result, AugmentedResult)
424 root = ElementTree.Element(
'testsuites')
425 suite = ElementTree.SubElement(root,
'testsuite', {
426 'name':
'Python gRPC tests',
428 for case
in result.cases.values():
429 if case.kind
is CaseResult.Kind.SUCCESS:
430 ElementTree.SubElement(suite,
'testcase', {
433 elif case.kind
in (CaseResult.Kind.ERROR, CaseResult.Kind.FAILURE):
434 case_xml = ElementTree.SubElement(suite,
'testcase', {
437 error_xml = ElementTree.SubElement(case_xml,
'error', {})
438 error_xml.text =
''.
format(case.stderr, case.traceback)
439 return ElementTree.ElementTree(element=root)