blob: d571082f3c2831430b7febfa3ce8f5f83da8ebf6 [file] [log] [blame]
#
# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import sys
import json
import time
import traceback
import unittest
from unittest.util import strclass
from unittest.signals import registerResult
# Tags that Tradefed can understand in SubprocessResultParser to get results
_CLASSNAME_TAG = 'className'
_METHOD_NAME_TAG = 'testName'
_START_TIME_TAG = 'start_time'
_END_TIME_TAG = 'end_time'
_TRACE_TAG = 'trace'
_TEST_COUNT_TAG = 'testCount'
_REASON_TAG = 'reason'
_TIME_TAG = 'time'
class TextTestResult(unittest.TextTestResult):
""" Class for callbacks based on test state"""
def _getClassName(self, test):
return strclass(test.__class__)
def _getMethodName(self, test):
return test._testMethodName
def startTestRun(self, count):
""" Callback that marks a test run has started.
Args:
count: The number of expected tests.
"""
resp = {_TEST_COUNT_TAG: count, 'runName': 'python-tradefed'}
self.stream.write('TEST_RUN_STARTED %s\n' % json.dumps(resp))
super(TextTestResult, self).startTestRun()
def startTest(self, test):
""" Callback that marks a test has started to run.
Args:
test: The test that started.
"""
resp = {_START_TIME_TAG: time.time(), _CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test)}
self.stream.write('TEST_STARTED %s\n' % json.dumps(resp))
super(TextTestResult, self).startTest(test)
def addSuccess(self, test):
""" Callback that marks a test has finished and passed
Args:
test: The test that passed.
"""
resp = {_END_TIME_TAG: time.time(), _CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test)}
self.stream.write('TEST_ENDED %s\n' % json.dumps(resp))
super(TextTestResult, self).addSuccess(test)
def addFailure(self, test, err):
""" Callback that marks a test has failed
Args:
test: The test that failed.
err: the error generated that should be reported.
"""
resp = {_CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test), _TRACE_TAG: '\n'.join(traceback.format_exception(*err))}
self.stream.write('TEST_FAILED %s\n' % json.dumps(resp))
resp = {_END_TIME_TAG: time.time(), _CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test)}
self.stream.write('TEST_ENDED %s\n' % json.dumps(resp))
super(TextTestResult, self).addFailure(test, err)
def addSkip(self, test, reason):
""" Callback that marks a test was being skipped
Args:
test: The test being skipped.
reason: the message generated that should be reported.
"""
resp = {_CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test)}
self.stream.write('TEST_IGNORED %s\n' % json.dumps(resp))
resp = {_END_TIME_TAG: time.time(), _CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test)}
self.stream.write('TEST_ENDED %s\n' % json.dumps(resp))
super(TextTestResult, self).addSkip(test, reason)
def addExpectedFailure(self, test, err):
""" Callback that marks a test was expected to fail and failed.
Args:
test: The test responsible for the error.
err: the error generated that should be reported.
"""
resp = {_CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test), _TRACE_TAG: '\n'.join(traceback.format_exception(*err))}
self.stream.write('TEST_ASSUMPTION_FAILURE %s\n' % json.dumps(resp))
resp = {_END_TIME_TAG: time.time(), _CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test)}
self.stream.write('TEST_ENDED %s\n' % json.dumps(resp))
super(TextTestResult, self).addExpectedFailure(test, err)
def addUnexpectedSuccess(self, test):
""" Callback that marks a test was expected to fail but passed.
Args:
test: The test responsible for the unexpected success.
"""
resp = {_CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test), _TRACE_TAG: 'Unexpected success'}
self.stream.write('TEST_ASSUMPTION_FAILURE %s\n' % json.dumps(resp))
resp = {_END_TIME_TAG: time.time(), _CLASSNAME_TAG: self._getClassName(test), _METHOD_NAME_TAG: self._getMethodName(test)}
self.stream.write('TEST_ENDED %s\n' % json.dumps(resp))
super(TextTestResult, self).addUnexpectedSuccess(test)
def addError(self, test, err):
""" Callback that marks a run as failed because of an error.
Args:
test: The test responsible for the error.
err: the error generated that should be reported.
"""
resp = {_REASON_TAG: '\n'.join(traceback.format_exception(*err))}
self.stream.write('TEST_RUN_FAILED %s\n' % json.dumps(resp))
super(TextTestResult, self).addError(test, err)
def stopTestRun(self, elapsedTime):
""" Callback that marks the end of a test run
Args:
elapsedTime: The elapsed time of the run.
"""
resp = {_TIME_TAG: elapsedTime}
self.stream.write('TEST_RUN_ENDED %s\n' % json.dumps(resp))
super(TextTestResult, self).stopTestRun()
class TfTextTestRunner(unittest.TextTestRunner):
""" Class runner that ensure the callbacks order"""
def __init__(self, stream=sys.stderr, descriptions=True, verbosity=1,
failfast=False, buffer=False, resultclass=None, serial=None, extra_options=None):
self.serial = serial
self.extra_options = extra_options
unittest.TextTestRunner.__init__(self, stream, descriptions, verbosity, failfast, buffer, resultclass)
def _injectDevice(self, testSuites):
""" Method to inject options to the base Python Tradefed class
Args:
testSuites: the current test holder.
"""
if self.serial is not None:
for testSuite in testSuites:
# each test in the test suite
for test in testSuite._tests:
try:
test.setUpDevice(self.serial, self.stream, self.extra_options)
except AttributeError:
self.stream.writeln('Test %s does not implement _TradefedTestClass.' % test)
def run(self, test):
""" Run the given test case or test suite. Copied from unittest to replace the startTestRun
callback"""
result = self._makeResult()
result.failfast = self.failfast
result.buffer = self.buffer
registerResult(result)
startTime = time.time()
startTestRun = getattr(result, 'startTestRun', None)
if startTestRun is not None:
startTestRun(test.countTestCases())
try:
self._injectDevice(test)
test(result)
finally:
stopTestRun = getattr(result, 'stopTestRun', None)
if stopTestRun is not None:
stopTestRun(time.time() - startTime)
else:
result.printErrors()
stopTime = time.time()
timeTaken = stopTime - startTime
if hasattr(result, 'separator2'):
self.stream.writeln(result.separator2)
run = result.testsRun
self.stream.writeln('Ran %d test%s in %.3fs' %
(run, run != 1 and 's' or '', timeTaken))
self.stream.writeln()
expectedFails = unexpectedSuccesses = skipped = 0
try:
results = map(len, (result.expectedFailures,
result.unexpectedSuccesses,
result.skipped))
expectedFails, unexpectedSuccesses, skipped = results
except AttributeError:
pass
infos = []
if not result.wasSuccessful():
self.stream.write('FAILED')
failed, errored = map(len, (result.failures, result.errors))
if failed:
infos.append('failures=%d' % failed)
if errored:
infos.append('errors=%d' % errored)
else:
self.stream.write('OK')
if skipped:
infos.append('skipped=%d' % skipped)
if expectedFails:
infos.append('expected failures=%d' % expectedFails)
if unexpectedSuccesses:
infos.append('unexpected successes=%d' % unexpectedSuccesses)
if infos:
self.stream.writeln(' (%s)' % (', '.join(infos),))
else:
self.stream.write('\n')
return result