| Index: third_party/grpc/src/python/grpcio/tests/_result.py
|
| diff --git a/third_party/grpc/src/python/grpcio/tests/_result.py b/third_party/grpc/src/python/grpcio/tests/_result.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0670be921f83eb362809430d9f0069eb0c7fafb3
|
| --- /dev/null
|
| +++ b/third_party/grpc/src/python/grpcio/tests/_result.py
|
| @@ -0,0 +1,451 @@
|
| +# Copyright 2015, Google Inc.
|
| +# All rights reserved.
|
| +#
|
| +# Redistribution and use in source and binary forms, with or without
|
| +# modification, are permitted provided that the following conditions are
|
| +# met:
|
| +#
|
| +# * Redistributions of source code must retain the above copyright
|
| +# notice, this list of conditions and the following disclaimer.
|
| +# * Redistributions in binary form must reproduce the above
|
| +# copyright notice, this list of conditions and the following disclaimer
|
| +# in the documentation and/or other materials provided with the
|
| +# distribution.
|
| +# * Neither the name of Google Inc. nor the names of its
|
| +# contributors may be used to endorse or promote products derived from
|
| +# this software without specific prior written permission.
|
| +#
|
| +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
| +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
| +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
| +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
| +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
| +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| +
|
| +import cStringIO as StringIO
|
| +import collections
|
| +import itertools
|
| +import traceback
|
| +import unittest
|
| +from xml.etree import ElementTree
|
| +
|
| +import coverage
|
| +
|
| +from tests import _loader
|
| +
|
| +
|
| +class CaseResult(collections.namedtuple('CaseResult', [
|
| + 'id', 'name', 'kind', 'stdout', 'stderr', 'skip_reason', 'traceback'])):
|
| + """A serializable result of a single test case.
|
| +
|
| + Attributes:
|
| + id (object): Any serializable object used to denote the identity of this
|
| + test case.
|
| + name (str or None): A human-readable name of the test case.
|
| + kind (CaseResult.Kind): The kind of test result.
|
| + stdout (object or None): Output on stdout, or None if nothing was captured.
|
| + stderr (object or None): Output on stderr, or None if nothing was captured.
|
| + skip_reason (object or None): The reason the test was skipped. Must be
|
| + something if self.kind is CaseResult.Kind.SKIP, else None.
|
| + traceback (object or None): The traceback of the test. Must be something if
|
| + self.kind is CaseResult.Kind.{ERROR, FAILURE, EXPECTED_FAILURE}, else
|
| + None.
|
| + """
|
| +
|
| + class Kind:
|
| + UNTESTED = 'untested'
|
| + RUNNING = 'running'
|
| + ERROR = 'error'
|
| + FAILURE = 'failure'
|
| + SUCCESS = 'success'
|
| + SKIP = 'skip'
|
| + EXPECTED_FAILURE = 'expected failure'
|
| + UNEXPECTED_SUCCESS = 'unexpected success'
|
| +
|
| + def __new__(cls, id=None, name=None, kind=None, stdout=None, stderr=None,
|
| + skip_reason=None, traceback=None):
|
| + """Helper keyword constructor for the namedtuple.
|
| +
|
| + See this class' attributes for information on the arguments."""
|
| + assert id is not None
|
| + assert name is None or isinstance(name, str)
|
| + if kind is CaseResult.Kind.UNTESTED:
|
| + pass
|
| + elif kind is CaseResult.Kind.RUNNING:
|
| + pass
|
| + elif kind is CaseResult.Kind.ERROR:
|
| + assert traceback is not None
|
| + elif kind is CaseResult.Kind.FAILURE:
|
| + assert traceback is not None
|
| + elif kind is CaseResult.Kind.SUCCESS:
|
| + pass
|
| + elif kind is CaseResult.Kind.SKIP:
|
| + assert skip_reason is not None
|
| + elif kind is CaseResult.Kind.EXPECTED_FAILURE:
|
| + assert traceback is not None
|
| + elif kind is CaseResult.Kind.UNEXPECTED_SUCCESS:
|
| + pass
|
| + else:
|
| + assert False
|
| + return super(cls, CaseResult).__new__(
|
| + cls, id, name, kind, stdout, stderr, skip_reason, traceback)
|
| +
|
| + def updated(self, name=None, kind=None, stdout=None, stderr=None,
|
| + skip_reason=None, traceback=None):
|
| + """Get a new validated CaseResult with the fields updated.
|
| +
|
| + See this class' attributes for information on the arguments."""
|
| + name = self.name if name is None else name
|
| + kind = self.kind if kind is None else kind
|
| + stdout = self.stdout if stdout is None else stdout
|
| + stderr = self.stderr if stderr is None else stderr
|
| + skip_reason = self.skip_reason if skip_reason is None else skip_reason
|
| + traceback = self.traceback if traceback is None else traceback
|
| + return CaseResult(id=self.id, name=name, kind=kind, stdout=stdout,
|
| + stderr=stderr, skip_reason=skip_reason,
|
| + traceback=traceback)
|
| +
|
| +
|
| +class AugmentedResult(unittest.TestResult):
|
| + """unittest.Result that keeps track of additional information.
|
| +
|
| + Uses CaseResult objects to store test-case results, providing additional
|
| + information beyond that of the standard Python unittest library, such as
|
| + standard output.
|
| +
|
| + Attributes:
|
| + id_map (callable): A unary callable mapping unittest.TestCase objects to
|
| + unique identifiers.
|
| + cases (dict): A dictionary mapping from the identifiers returned by id_map
|
| + to CaseResult objects corresponding to those IDs.
|
| + """
|
| +
|
| + def __init__(self, id_map):
|
| + """Initialize the object with an identifier mapping.
|
| +
|
| + Arguments:
|
| + id_map (callable): Corresponds to the attribute `id_map`."""
|
| + super(AugmentedResult, self).__init__()
|
| + self.id_map = id_map
|
| + self.cases = None
|
| +
|
| + def startTestRun(self):
|
| + """See unittest.TestResult.startTestRun."""
|
| + super(AugmentedResult, self).startTestRun()
|
| + self.cases = dict()
|
| +
|
| + def stopTestRun(self):
|
| + """See unittest.TestResult.stopTestRun."""
|
| + super(AugmentedResult, self).stopTestRun()
|
| +
|
| + def startTest(self, test):
|
| + """See unittest.TestResult.startTest."""
|
| + super(AugmentedResult, self).startTest(test)
|
| + case_id = self.id_map(test)
|
| + self.cases[case_id] = CaseResult(
|
| + id=case_id, name=test.id(), kind=CaseResult.Kind.RUNNING)
|
| +
|
| + def addError(self, test, error):
|
| + """See unittest.TestResult.addError."""
|
| + super(AugmentedResult, self).addError(test, error)
|
| + case_id = self.id_map(test)
|
| + self.cases[case_id] = self.cases[case_id].updated(
|
| + kind=CaseResult.Kind.ERROR, traceback=error)
|
| +
|
| + def addFailure(self, test, error):
|
| + """See unittest.TestResult.addFailure."""
|
| + super(AugmentedResult, self).addFailure(test, error)
|
| + case_id = self.id_map(test)
|
| + self.cases[case_id] = self.cases[case_id].updated(
|
| + kind=CaseResult.Kind.FAILURE, traceback=error)
|
| +
|
| + def addSuccess(self, test):
|
| + """See unittest.TestResult.addSuccess."""
|
| + super(AugmentedResult, self).addSuccess(test)
|
| + case_id = self.id_map(test)
|
| + self.cases[case_id] = self.cases[case_id].updated(
|
| + kind=CaseResult.Kind.SUCCESS)
|
| +
|
| + def addSkip(self, test, reason):
|
| + """See unittest.TestResult.addSkip."""
|
| + super(AugmentedResult, self).addSkip(test, reason)
|
| + case_id = self.id_map(test)
|
| + self.cases[case_id] = self.cases[case_id].updated(
|
| + kind=CaseResult.Kind.SKIP, skip_reason=reason)
|
| +
|
| + def addExpectedFailure(self, test, error):
|
| + """See unittest.TestResult.addExpectedFailure."""
|
| + super(AugmentedResult, self).addExpectedFailure(test, error)
|
| + case_id = self.id_map(test)
|
| + self.cases[case_id] = self.cases[case_id].updated(
|
| + kind=CaseResult.Kind.EXPECTED_FAILURE, traceback=error)
|
| +
|
| + def addUnexpectedSuccess(self, test):
|
| + """See unittest.TestResult.addUnexpectedSuccess."""
|
| + super(AugmentedResult, self).addUnexpectedSuccess(test)
|
| + case_id = self.id_map(test)
|
| + self.cases[case_id] = self.cases[case_id].updated(
|
| + kind=CaseResult.Kind.UNEXPECTED_SUCCESS)
|
| +
|
| + def set_output(self, test, stdout, stderr):
|
| + """Set the output attributes for the CaseResult corresponding to a test.
|
| +
|
| + Args:
|
| + test (unittest.TestCase): The TestCase to set the outputs of.
|
| + stdout (str): Output from stdout to assign to self.id_map(test).
|
| + stderr (str): Output from stderr to assign to self.id_map(test).
|
| + """
|
| + case_id = self.id_map(test)
|
| + self.cases[case_id] = self.cases[case_id].updated(
|
| + stdout=stdout, stderr=stderr)
|
| +
|
| + def augmented_results(self, filter):
|
| + """Convenience method to retrieve filtered case results.
|
| +
|
| + Args:
|
| + filter (callable): A unary predicate to filter over CaseResult objects.
|
| + """
|
| + return (self.cases[case_id] for case_id in self.cases
|
| + if filter(self.cases[case_id]))
|
| +
|
| +
|
| +class CoverageResult(AugmentedResult):
|
| + """Extension to AugmentedResult adding coverage.py support per test.\
|
| +
|
| + Attributes:
|
| + coverage_context (coverage.Coverage): coverage.py management object.
|
| + """
|
| +
|
| + def __init__(self, id_map):
|
| + """See AugmentedResult.__init__."""
|
| + super(CoverageResult, self).__init__(id_map=id_map)
|
| + self.coverage_context = None
|
| +
|
| + def startTest(self, test):
|
| + """See unittest.TestResult.startTest.
|
| +
|
| + Additionally initializes and begins code coverage tracking."""
|
| + super(CoverageResult, self).startTest(test)
|
| + self.coverage_context = coverage.Coverage(data_suffix=True)
|
| + self.coverage_context.start()
|
| +
|
| + def stopTest(self, test):
|
| + """See unittest.TestResult.stopTest.
|
| +
|
| + Additionally stops and deinitializes code coverage tracking."""
|
| + super(CoverageResult, self).stopTest(test)
|
| + self.coverage_context.stop()
|
| + self.coverage_context.save()
|
| + self.coverage_context = None
|
| +
|
| + def stopTestRun(self):
|
| + """See unittest.TestResult.stopTestRun."""
|
| + super(CoverageResult, self).stopTestRun()
|
| + # TODO(atash): Dig deeper into why the following line fails to properly
|
| + # combine coverage data from the Cython plugin.
|
| + #coverage.Coverage().combine()
|
| +
|
| +
|
| +class _Colors:
|
| + """Namespaced constants for terminal color magic numbers."""
|
| + HEADER = '\033[95m'
|
| + INFO = '\033[94m'
|
| + OK = '\033[92m'
|
| + WARN = '\033[93m'
|
| + FAIL = '\033[91m'
|
| + BOLD = '\033[1m'
|
| + UNDERLINE = '\033[4m'
|
| + END = '\033[0m'
|
| +
|
| +
|
| +class TerminalResult(CoverageResult):
|
| + """Extension to CoverageResult adding basic terminal reporting."""
|
| +
|
| + def __init__(self, out, id_map):
|
| + """Initialize the result object.
|
| +
|
| + Args:
|
| + out (file-like): Output file to which terminal-colored live results will
|
| + be written.
|
| + id_map (callable): See AugmentedResult.__init__.
|
| + """
|
| + super(TerminalResult, self).__init__(id_map=id_map)
|
| + self.out = out
|
| +
|
| + def startTestRun(self):
|
| + """See unittest.TestResult.startTestRun."""
|
| + super(TerminalResult, self).startTestRun()
|
| + self.out.write(
|
| + _Colors.HEADER +
|
| + 'Testing gRPC Python...\n' +
|
| + _Colors.END)
|
| +
|
| + def stopTestRun(self):
|
| + """See unittest.TestResult.stopTestRun."""
|
| + super(TerminalResult, self).stopTestRun()
|
| + self.out.write(summary(self))
|
| + self.out.flush()
|
| +
|
| + def addError(self, test, error):
|
| + """See unittest.TestResult.addError."""
|
| + super(TerminalResult, self).addError(test, error)
|
| + self.out.write(
|
| + _Colors.FAIL +
|
| + 'ERROR {}\n'.format(test.id()) +
|
| + _Colors.END)
|
| + self.out.flush()
|
| +
|
| + def addFailure(self, test, error):
|
| + """See unittest.TestResult.addFailure."""
|
| + super(TerminalResult, self).addFailure(test, error)
|
| + self.out.write(
|
| + _Colors.FAIL +
|
| + 'FAILURE {}\n'.format(test.id()) +
|
| + _Colors.END)
|
| + self.out.flush()
|
| +
|
| + def addSuccess(self, test):
|
| + """See unittest.TestResult.addSuccess."""
|
| + super(TerminalResult, self).addSuccess(test)
|
| + self.out.write(
|
| + _Colors.OK +
|
| + 'SUCCESS {}\n'.format(test.id()) +
|
| + _Colors.END)
|
| + self.out.flush()
|
| +
|
| + def addSkip(self, test, reason):
|
| + """See unittest.TestResult.addSkip."""
|
| + super(TerminalResult, self).addSkip(test, reason)
|
| + self.out.write(
|
| + _Colors.INFO +
|
| + 'SKIP {}\n'.format(test.id()) +
|
| + _Colors.END)
|
| + self.out.flush()
|
| +
|
| + def addExpectedFailure(self, test, error):
|
| + """See unittest.TestResult.addExpectedFailure."""
|
| + super(TerminalResult, self).addExpectedFailure(test, error)
|
| + self.out.write(
|
| + _Colors.INFO +
|
| + 'FAILURE_OK {}\n'.format(test.id()) +
|
| + _Colors.END)
|
| + self.out.flush()
|
| +
|
| + def addUnexpectedSuccess(self, test):
|
| + """See unittest.TestResult.addUnexpectedSuccess."""
|
| + super(TerminalResult, self).addUnexpectedSuccess(test)
|
| + self.out.write(
|
| + _Colors.INFO +
|
| + 'UNEXPECTED_OK {}\n'.format(test.id()) +
|
| + _Colors.END)
|
| + self.out.flush()
|
| +
|
| +def _traceback_string(type, value, trace):
|
| + """Generate a descriptive string of a Python exception traceback.
|
| +
|
| + Args:
|
| + type (class): The type of the exception.
|
| + value (Exception): The value of the exception.
|
| + trace (traceback): Traceback of the exception.
|
| +
|
| + Returns:
|
| + str: Formatted exception descriptive string.
|
| + """
|
| + buffer = StringIO.StringIO()
|
| + traceback.print_exception(type, value, trace, file=buffer)
|
| + return buffer.getvalue()
|
| +
|
| +def summary(result):
|
| + """A summary string of a result object.
|
| +
|
| + Args:
|
| + result (AugmentedResult): The result object to get the summary of.
|
| +
|
| + Returns:
|
| + str: The summary string.
|
| + """
|
| + assert isinstance(result, AugmentedResult)
|
| + untested = list(result.augmented_results(
|
| + lambda case_result: case_result.kind is CaseResult.Kind.UNTESTED))
|
| + running = list(result.augmented_results(
|
| + lambda case_result: case_result.kind is CaseResult.Kind.RUNNING))
|
| + failures = list(result.augmented_results(
|
| + lambda case_result: case_result.kind is CaseResult.Kind.FAILURE))
|
| + errors = list(result.augmented_results(
|
| + lambda case_result: case_result.kind is CaseResult.Kind.ERROR))
|
| + successes = list(result.augmented_results(
|
| + lambda case_result: case_result.kind is CaseResult.Kind.SUCCESS))
|
| + skips = list(result.augmented_results(
|
| + lambda case_result: case_result.kind is CaseResult.Kind.SKIP))
|
| + expected_failures = list(result.augmented_results(
|
| + lambda case_result: case_result.kind is CaseResult.Kind.EXPECTED_FAILURE))
|
| + unexpected_successes = list(result.augmented_results(
|
| + lambda case_result: case_result.kind is CaseResult.Kind.UNEXPECTED_SUCCESS))
|
| + running_names = [case.name for case in running]
|
| + finished_count = (len(failures) + len(errors) + len(successes) +
|
| + len(expected_failures) + len(unexpected_successes))
|
| + statistics = (
|
| + '{finished} tests finished:\n'
|
| + '\t{successful} successful\n'
|
| + '\t{unsuccessful} unsuccessful\n'
|
| + '\t{skipped} skipped\n'
|
| + '\t{expected_fail} expected failures\n'
|
| + '\t{unexpected_successful} unexpected successes\n'
|
| + 'Interrupted Tests:\n'
|
| + '\t{interrupted}\n'
|
| + .format(finished=finished_count,
|
| + successful=len(successes),
|
| + unsuccessful=(len(failures)+len(errors)),
|
| + skipped=len(skips),
|
| + expected_fail=len(expected_failures),
|
| + unexpected_successful=len(unexpected_successes),
|
| + interrupted=str(running_names)))
|
| + tracebacks = '\n\n'.join([
|
| + (_Colors.FAIL + '{test_name}' + _Colors.END + '\n' +
|
| + _Colors.BOLD + 'traceback:' + _Colors.END + '\n' +
|
| + '{traceback}\n' +
|
| + _Colors.BOLD + 'stdout:' + _Colors.END + '\n' +
|
| + '{stdout}\n' +
|
| + _Colors.BOLD + 'stderr:' + _Colors.END + '\n' +
|
| + '{stderr}\n').format(
|
| + test_name=result.name,
|
| + traceback=_traceback_string(*result.traceback),
|
| + stdout=result.stdout, stderr=result.stderr)
|
| + for result in itertools.chain(failures, errors)
|
| + ])
|
| + notes = 'Unexpected successes: {}\n'.format([
|
| + result.name for result in unexpected_successes])
|
| + return statistics + '\nErrors/Failures: \n' + tracebacks + '\n' + notes
|
| +
|
| +
|
| +def jenkins_junit_xml(result):
|
| + """An XML tree object that when written is recognizable by Jenkins.
|
| +
|
| + Args:
|
| + result (AugmentedResult): The result object to get the junit xml output of.
|
| +
|
| + Returns:
|
| + ElementTree.ElementTree: The XML tree.
|
| + """
|
| + assert isinstance(result, AugmentedResult)
|
| + root = ElementTree.Element('testsuites')
|
| + suite = ElementTree.SubElement(root, 'testsuite', {
|
| + 'name': 'Python gRPC tests',
|
| + })
|
| + for case in result.cases.values():
|
| + if case.kind is CaseResult.Kind.SUCCESS:
|
| + ElementTree.SubElement(suite, 'testcase', {
|
| + 'name': case.name,
|
| + })
|
| + elif case.kind in (CaseResult.Kind.ERROR, CaseResult.Kind.FAILURE):
|
| + case_xml = ElementTree.SubElement(suite, 'testcase', {
|
| + 'name': case.name,
|
| + })
|
| + error_xml = ElementTree.SubElement(case_xml, 'error', {})
|
| + error_xml.text = ''.format(case.stderr, case.traceback)
|
| + return ElementTree.ElementTree(element=root)
|
|
|