Home | History | Annotate | Download | only in tradefed_py
      1 #
      2 # Copyright (C) 2017 The Android Open Source Project
      3 #
      4 # Licensed under the Apache License, Version 2.0 (the "License");
      5 # you may not use this file except in compliance with the License.
      6 # You may obtain a copy of the License at
      7 #
      8 #      http://www.apache.org/licenses/LICENSE-2.0
      9 #
     10 # Unless required by applicable law or agreed to in writing, software
     11 # distributed under the License is distributed on an "AS IS" BASIS,
     12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 # See the License for the specific language governing permissions and
     14 # limitations under the License.
     15 #
     16 
     17 import sys
     18 import json
     19 import time
     20 import traceback
     21 import unittest
     22 from unittest.util import strclass
     23 from unittest.signals import registerResult
     24 
     25 # Tags that Tradefed can understand in SubprocessResultParser to get results
     26 _CLASSNAME_TAG = 'className'
     27 _METHOD_NAME_TAG = 'testName'
     28 _START_TIME_TAG = 'start_time'
     29 _END_TIME_TAG = 'end_time'
     30 _TRACE_TAG = 'trace'
     31 _TEST_COUNT_TAG = 'testCount'
     32 _REASON_TAG = 'reason'
     33 _TIME_TAG = 'time'
     34 
     35 class TextTestResult(unittest.TextTestResult):
     36     """ Class for callbacks based on test state"""
     37 
     38     def _getClassName(self, test):
     39         return strclass(test.__class__)
     40 
     41     def _getMethodName(self, test):
     42         return test._testMethodName
     43 
     44     def startTestRun(self, count):
     45         """ Callback that marks a test run has started.
     46 
     47         Args:
     48             count: The number of expected tests.
     49         """
     50         resp = {_TEST_COUNT_TAG: count, 'runName': 'python-tradefed'}
     51         self.stream.write('TEST_RUN_STARTED %s\n' % json.dumps(resp))
     52         super(TextTestResult, self).startTestRun()
     53 
     54     def startTest(self, test):
     55         """ Callback that marks a test has started to run.
     56 
     57         Args:
     58             test: The test that started.
     59         """
     60         resp = {_START_TIME_TAG: time.time(), _CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test)}
     61         self.stream.write('TEST_STARTED %s\n' % json.dumps(resp))
     62         super(TextTestResult, self).startTest(test)
     63 
     64     def addSuccess(self, test):
     65         """ Callback that marks a test has finished and passed
     66 
     67         Args:
     68             test: The test that passed.
     69         """
     70         resp = {_END_TIME_TAG: time.time(), _CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test)}
     71         self.stream.write('TEST_ENDED %s\n' % json.dumps(resp))
     72         super(TextTestResult, self).addSuccess(test)
     73 
     74     def addFailure(self, test, err):
     75         """ Callback that marks a test has failed
     76 
     77         Args:
     78             test: The test that failed.
     79             err: the error generated that should be reported.
     80         """
     81         resp = {_CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test), _TRACE_TAG: '\n'.join(traceback.format_exception(*err))}
     82         self.stream.write('TEST_FAILED %s\n' % json.dumps(resp))
     83         resp = {_END_TIME_TAG: time.time(), _CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test)}
     84         self.stream.write('TEST_ENDED %s\n' % json.dumps(resp))
     85         super(TextTestResult, self).addFailure(test, err)
     86 
     87     def addSkip(self, test, reason):
     88         """ Callback that marks a test was being skipped
     89 
     90         Args:
     91             test: The test being skipped.
     92             reason: the message generated that should be reported.
     93         """
     94         resp = {_CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test)}
     95         self.stream.write('TEST_IGNORED %s\n' % json.dumps(resp))
     96         resp = {_END_TIME_TAG: time.time(), _CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test)}
     97         self.stream.write('TEST_ENDED %s\n' % json.dumps(resp))
     98         super(TextTestResult, self).addSkip(test, reason)
     99 
    100     def addExpectedFailure(self, test, err):
    101         """ Callback that marks a test was expected to fail and failed.
    102 
    103         Args:
    104             test: The test responsible for the error.
    105             err: the error generated that should be reported.
    106         """
    107         resp = {_CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test), _TRACE_TAG: '\n'.join(traceback.format_exception(*err))}
    108         self.stream.write('TEST_ASSUMPTION_FAILURE %s\n' % json.dumps(resp))
    109         resp = {_END_TIME_TAG: time.time(), _CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test)}
    110         self.stream.write('TEST_ENDED %s\n' % json.dumps(resp))
    111         super(TextTestResult, self).addExpectedFailure(test, err)
    112 
    113     def addUnexpectedSuccess(self, test):
    114         """ Callback that marks a test was expected to fail but passed.
    115 
    116         Args:
    117             test: The test responsible for the unexpected success.
    118         """
    119         resp = {_CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test), _TRACE_TAG: 'Unexpected success'}
    120         self.stream.write('TEST_ASSUMPTION_FAILURE %s\n' % json.dumps(resp))
    121         resp = {_END_TIME_TAG: time.time(), _CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test)}
    122         self.stream.write('TEST_ENDED %s\n' % json.dumps(resp))
    123         super(TextTestResult, self).addUnexpectedSuccess(test)
    124 
    125     def addError(self, test, err):
    126         """ Callback that marks a run as failed because of an error.
    127 
    128         Args:
    129             test: The test responsible for the error.
    130             err: the error generated that should be reported.
    131         """
    132         resp = {_REASON_TAG: '\n'.join(traceback.format_exception(*err))}
    133         self.stream.write('TEST_RUN_FAILED %s\n' % json.dumps(resp))
    134         super(TextTestResult, self).addError(test, err)
    135 
    136     def stopTestRun(self, elapsedTime):
    137         """ Callback that marks the end of a test run
    138 
    139         Args:
    140             elapsedTime: The elapsed time of the run.
    141         """
    142         resp = {_TIME_TAG: elapsedTime}
    143         self.stream.write('TEST_RUN_ENDED %s\n' % json.dumps(resp))
    144         super(TextTestResult, self).stopTestRun()
    145 
    146 class TfTextTestRunner(unittest.TextTestRunner):
    147     """ Class runner that ensure the callbacks order"""
    148 
    149     def __init__(self, stream=sys.stderr, descriptions=True, verbosity=1,
    150                  failfast=False, buffer=False, resultclass=None, serial=None, extra_options=None):
    151         self.serial = serial
    152         self.extra_options = extra_options
    153         unittest.TextTestRunner.__init__(self, stream, descriptions, verbosity, failfast, buffer, resultclass)
    154 
    155     def _injectDevice(self, testSuites):
    156         """ Method to inject options to the base Python Tradefed class
    157 
    158         Args:
    159             testSuites: the current test holder.
    160         """
    161         if self.serial is not None:
    162             for testSuite in testSuites:
    163                 # each test in the test suite
    164                 for test in testSuite._tests:
    165                     try:
    166                         test.setUpDevice(self.serial, self.stream, self.extra_options)
    167                     except AttributeError:
    168                         self.stream.writeln('Test %s does not implement _TradefedTestClass.' % test)
    169 
    170     def run(self, test):
    171         """ Run the given test case or test suite. Copied from unittest to replace the startTestRun
    172         callback"""
    173         result = self._makeResult()
    174         result.failfast = self.failfast
    175         result.buffer = self.buffer
    176         registerResult(result)
    177         startTime = time.time()
    178         startTestRun = getattr(result, 'startTestRun', None)
    179         if startTestRun is not None:
    180             startTestRun(test.countTestCases())
    181         try:
    182             self._injectDevice(test)
    183             test(result)
    184         finally:
    185             stopTestRun = getattr(result, 'stopTestRun', None)
    186             if stopTestRun is not None:
    187                 stopTestRun(time.time() - startTime)
    188             else:
    189                 result.printErrors()
    190         stopTime = time.time()
    191         timeTaken = stopTime - startTime
    192         if hasattr(result, 'separator2'):
    193             self.stream.writeln(result.separator2)
    194         run = result.testsRun
    195         self.stream.writeln('Ran %d test%s in %.3fs' %
    196                             (run, run != 1 and 's' or '', timeTaken))
    197         self.stream.writeln()
    198 
    199         expectedFails = unexpectedSuccesses = skipped = 0
    200         try:
    201             results = map(len, (result.expectedFailures,
    202                                 result.unexpectedSuccesses,
    203                                 result.skipped))
    204             expectedFails, unexpectedSuccesses, skipped = results
    205         except AttributeError:
    206             pass
    207         infos = []
    208         if not result.wasSuccessful():
    209             self.stream.write('FAILED')
    210             failed, errored = map(len, (result.failures, result.errors))
    211             if failed:
    212                 infos.append('failures=%d' % failed)
    213             if errored:
    214                 infos.append('errors=%d' % errored)
    215         else:
    216             self.stream.write('OK')
    217         if skipped:
    218             infos.append('skipped=%d' % skipped)
    219         if expectedFails:
    220             infos.append('expected failures=%d' % expectedFails)
    221         if unexpectedSuccesses:
    222             infos.append('unexpected successes=%d' % unexpectedSuccesses)
    223         if infos:
    224             self.stream.writeln(' (%s)' % (', '.join(infos),))
    225         else:
    226             self.stream.write('\n')
    227         return result
    228