|  | @@ -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)
 |