summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulien Desprez <jdesprez@google.com>2021-11-01 11:20:46 -0700
committerJulien Desprez <jdesprez@google.com>2021-11-01 11:21:10 -0700
commit3d434fe202b50dba0ce57f804469d7f8ca7d8f1f (patch)
tree01ef71a70b80b9cd81205bc95f710ad5233f5017
parentd8305cd73c33110a034922a48df80273a1b007d1 (diff)
downloadplatform_testing-3d434fe202b50dba0ce57f804469d7f8ca7d8f1f.tar.gz
Move tf python lib to platform_testing
Test: presubmit Bug: 202742315 Change-Id: Ic45f2fdb4ab9429d0c857df02cca5528f57ac7c9 Merged-In: Ieee715dc88cd17592d2d0f80db5edd2c772313b7
-rw-r--r--libraries/tradefed-python-lib/Android.bp30
-rw-r--r--libraries/tradefed-python-lib/OWNERS5
-rw-r--r--libraries/tradefed-python-lib/__init__.py0
-rw-r--r--libraries/tradefed-python-lib/helloWorld/Android.bp36
-rw-r--r--libraries/tradefed-python-lib/helloWorld/AndroidTest.xml21
-rw-r--r--libraries/tradefed-python-lib/helloWorld/test_hello_world.py52
-rw-r--r--libraries/tradefed-python-lib/tradefed_py/__init__.py0
-rw-r--r--libraries/tradefed-python-lib/tradefed_py/adb_handler.py57
-rw-r--r--libraries/tradefed-python-lib/tradefed_py/android_device.py61
-rw-r--r--libraries/tradefed-python-lib/tradefed_py/base_test.py53
-rw-r--r--libraries/tradefed-python-lib/tradefed_py/tf_main.py97
-rw-r--r--libraries/tradefed-python-lib/tradefed_py/tf_runner.py227
12 files changed, 639 insertions, 0 deletions
diff --git a/libraries/tradefed-python-lib/Android.bp b/libraries/tradefed-python-lib/Android.bp
new file mode 100644
index 000000000..13a3ff415
--- /dev/null
+++ b/libraries/tradefed-python-lib/Android.bp
@@ -0,0 +1,30 @@
+// Copyright 2017 Google Inc. All rights reserved.
+//
+// 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.
+
+// Base library that should be extended/included to run as part of Tradefed
+python_library_host {
+ name: "tradefed_python_lib",
+ srcs: [
+ "tradefed_py/*.py",
+ ],
+ version: {
+ py2: {
+ enabled: true,
+ },
+ py3: {
+ enabled: false,
+ },
+ }
+}
+
diff --git a/libraries/tradefed-python-lib/OWNERS b/libraries/tradefed-python-lib/OWNERS
new file mode 100644
index 000000000..23f5f25de
--- /dev/null
+++ b/libraries/tradefed-python-lib/OWNERS
@@ -0,0 +1,5 @@
+# Root Owners of the Tradefed python repo for code reviews
+dshi@google.com
+frankfeng@google.com
+guangzhu@google.com
+jdesprez@google.com
diff --git a/libraries/tradefed-python-lib/__init__.py b/libraries/tradefed-python-lib/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/libraries/tradefed-python-lib/__init__.py
diff --git a/libraries/tradefed-python-lib/helloWorld/Android.bp b/libraries/tradefed-python-lib/helloWorld/Android.bp
new file mode 100644
index 000000000..529331ce6
--- /dev/null
+++ b/libraries/tradefed-python-lib/helloWorld/Android.bp
@@ -0,0 +1,36 @@
+// Copyright 2017 Google Inc. All rights reserved.
+//
+// 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.
+
+// Example hello world test that can run in Tradefed
+python_binary_host {
+ name: "tradefed_hello_world",
+ main: "test_hello_world.py",
+ srcs: [
+ "test_hello_world.py",
+ ],
+ libs: [
+ "tradefed_python_lib",
+ ],
+ test_suites: ["null-suite"],
+ version: {
+ py2: {
+ enabled: true,
+ embedded_launcher: true,
+ },
+ py3: {
+ enabled: false,
+ },
+ }
+}
+
diff --git a/libraries/tradefed-python-lib/helloWorld/AndroidTest.xml b/libraries/tradefed-python-lib/helloWorld/AndroidTest.xml
new file mode 100644
index 000000000..bce1f0ab1
--- /dev/null
+++ b/libraries/tradefed-python-lib/helloWorld/AndroidTest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for python tradefed hello world">
+ <option name="test-suite-tag" value="python-tradefed" />
+ <test class="com.android.tradefed.testtype.python.PythonBinaryHostTest" >
+ <option name="par-file-name" value="tradefed_hello_world" />
+ </test>
+</configuration>
diff --git a/libraries/tradefed-python-lib/helloWorld/test_hello_world.py b/libraries/tradefed-python-lib/helloWorld/test_hello_world.py
new file mode 100644
index 000000000..4b5ad8368
--- /dev/null
+++ b/libraries/tradefed-python-lib/helloWorld/test_hello_world.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+#
+# 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 unittest
+from tradefed_py import base_test
+from tradefed_py import tf_main
+
+class HelloWorldTest(base_test._TradefedTestClass):
+ """An example showing a possible implementation of python test"""
+
+ def test_hello(self):
+ self.assertEqual('hello'.upper(), 'HELLO')
+
+ def test_world_failed(self):
+ self.assertEqual('world'.upper(), 'WORLD2')
+
+ @unittest.skip('demonstrating skipping')
+ def test_skipped(self):
+ self.fail('should have been skipped')
+
+ @unittest.expectedFailure
+ def test_expectation(self):
+ self.fail('failed')
+
+ @unittest.expectedFailure
+ def test_failedExpectation(self):
+ pass
+
+ def test_device(self):
+ """If a serial was provided this test will check that we can query the
+ device. It will throw if the serial is invalid.
+ """
+ if self.serial is not None:
+ res = self.android_device.executeShellCommand('id')
+ self.assertTrue('uid' in res)
+
+if __name__ == '__main__':
+ tf_main.main()
diff --git a/libraries/tradefed-python-lib/tradefed_py/__init__.py b/libraries/tradefed-python-lib/tradefed_py/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/libraries/tradefed-python-lib/tradefed_py/__init__.py
diff --git a/libraries/tradefed-python-lib/tradefed_py/adb_handler.py b/libraries/tradefed-python-lib/tradefed_py/adb_handler.py
new file mode 100644
index 000000000..0464dba39
--- /dev/null
+++ b/libraries/tradefed-python-lib/tradefed_py/adb_handler.py
@@ -0,0 +1,57 @@
+#
+# 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 subprocess
+
+class AdbHandler(object):
+ """Adb wrapper to execute shell and adb command to the device."""
+
+ def __init__(self, serial=None):
+ self.serial = serial
+ self.adb_cmd = 'adb -s {}'.format(serial)
+
+ def exec_adb_command(self, cmd):
+ """Method to execute an adb command against the device."""
+ cmd = "{} {}".format(self.adb_cmd, cmd)
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
+ (out, err) = proc.communicate()
+ ret = proc.returncode
+ if ret == 0:
+ return out
+ raise AdbError(cmd=cmd, stdout=out, stderr=err, ret_code=ret)
+
+ def exec_shell_command(self, cmd):
+ """Method to execute a shell command against the device."""
+ cmd = '{} shell {}'.format(self.adb_cmd, cmd)
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
+ (out, err) = proc.communicate()
+ ret = proc.returncode
+ if ret == 0:
+ return out
+ raise AdbError(cmd=cmd, stdout=out, stderr=err, ret_code=ret)
+
+class AdbError(Exception):
+ """Raised when there is an error in adb operations."""
+
+ def __init__(self, cmd, stdout, stderr, ret_code):
+ self.cmd = cmd
+ self.stdout = stdout
+ self.stderr = stderr
+ self.ret_code = ret_code
+
+ def __str__(self):
+ return ('Error executing adb cmd "%s". ret: %d, stdout: %s, stderr: %s'
+ ) % (self.cmd, self.ret_code, self.stdout, self.stderr)
diff --git a/libraries/tradefed-python-lib/tradefed_py/android_device.py b/libraries/tradefed-python-lib/tradefed_py/android_device.py
new file mode 100644
index 000000000..568054fc6
--- /dev/null
+++ b/libraries/tradefed-python-lib/tradefed_py/android_device.py
@@ -0,0 +1,61 @@
+#
+# 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 adb_handler
+
+class AndroidTestDevice(object):
+ """Class representing an android device.
+
+ Each instance represents a different device connected to adb.
+ """
+
+ def __init__(self, serial=None, stream=None):
+ # TODO: Implement and flesh out the device interface
+ self.serial = serial
+ self._logging = stream
+ self.adb = adb_handler.AdbHandler(serial)
+
+ def executeShellCommand(self, cmd):
+ """Convenience method to call the adb wrapper to execute a shell command.
+
+ Args:
+ cmd: The command to be executed in 'adb shell'
+
+ Returns:
+ The stdout of the command if succeed. Or raise AdbError if failed.
+ """
+ return self.adb.exec_shell_command(cmd)
+
+ def getProp(self, name):
+ if not name:
+ raise DeviceCommandError('getProp', 'Name of property cannot be None')
+ out = self.executeShellCommand('getprop %s' % name)
+ return out.strip()
+
+ def _printHostLog(self, message):
+ self._logging.write('%s \n' % message)
+
+class DeviceCommandError(Exception):
+ """ Exception raised when an error is encountered while running a command.
+ """
+
+ def __init__(self, cmd, message):
+ self.cmd = cmd
+ self.message = message
+
+ def __str__(self):
+ return ('Error executing device cmd "%s". message: "%s"'
+ ) % (self.cmd, self.message)
diff --git a/libraries/tradefed-python-lib/tradefed_py/base_test.py b/libraries/tradefed-python-lib/tradefed_py/base_test.py
new file mode 100644
index 000000000..4fd916eb7
--- /dev/null
+++ b/libraries/tradefed-python-lib/tradefed_py/base_test.py
@@ -0,0 +1,53 @@
+#
+# 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 android_device
+import json
+import unittest
+
+_DATA_NAME = 'dataName'
+_DATA_TYPE = 'dataType'
+_DATA_FILE = 'dataFile'
+
+class _TradefedTestClass(unittest.TestCase):
+ """ A base test class to extends to receive a device object for testing in python
+
+ All tests should extends this class to be properly supported by Tradefed.
+ """
+
+ def setUpDevice(self, serial, stream, options):
+ """ Setter method that will allow the test to receive the device object
+
+ Args:
+ serial: The serial of the device allocated for the test.
+ stream: The output stream.
+ options: Additional options given to the tests that can be used.
+ """
+ self.serial = serial
+ self.stream = stream
+ self.extra_options = options
+ self.android_device = android_device.AndroidTestDevice(serial, stream)
+
+ def logFileToTradefed(self, name, filePath, fileType):
+ """ Callback to log a file that will be picked up by Tradefed.
+
+ Args:
+ name: The name under which log the particular data.
+ filePath: Absolute file path of the file to be logged.
+ fileType: The type of the file. (TEXT, PNG, etc.)
+ """
+ resp = {_DATA_NAME: name, _DATA_TYPE: fileType, _DATA_FILE: filePath}
+ self.stream.write('TEST_LOG %s\n' % json.dumps(resp))
diff --git a/libraries/tradefed-python-lib/tradefed_py/tf_main.py b/libraries/tradefed-python-lib/tradefed_py/tf_main.py
new file mode 100644
index 000000000..43a6aa59e
--- /dev/null
+++ b/libraries/tradefed-python-lib/tradefed_py/tf_main.py
@@ -0,0 +1,97 @@
+#
+# 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 getopt
+import os
+import sys
+import tf_runner
+from unittest import loader
+import unittest
+
+class TradefedProgram(unittest.TestProgram):
+ """ Main Runner Class that should be used to run the tests. This runner ensure that the
+ reporting is compatible with Tradefed.
+ """
+
+ def __init__(self, module='__main__', defaultTest=None,
+ argv=None, testRunner=None,
+ testLoader=loader.defaultTestLoader, exit=True,
+ verbosity=1, failfast=None, catchbreak=None, buffer=None, serial=None):
+ self.serial = None
+ self.extra_options = []
+ super(TradefedProgram, self).__init__()
+
+ def parseArgs(self, argv):
+ if len(argv) > 1 and argv[1].lower() == 'discover':
+ self._do_discovery(argv[2:])
+ return
+
+ long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch', 'buffer',
+ 'serial=', 'extra_options=']
+ try:
+ options, args = getopt.getopt(argv[1:], 'hHvqfcbs:e:', long_opts)
+ for opt, value in options:
+ if opt in ('-h','-H','--help'):
+ self.usageExit()
+ if opt in ('-q','--quiet'):
+ self.verbosity = 0
+ if opt in ('-v','--verbose'):
+ self.verbosity = 2
+ if opt in ('-f','--failfast'):
+ if self.failfast is None:
+ self.failfast = True
+ # Should this raise an exception if -f is not valid?
+ if opt in ('-c','--catch'):
+ if self.catchbreak is None and installHandler is not None:
+ self.catchbreak = True
+ # Should this raise an exception if -c is not valid?
+ if opt in ('-b','--buffer'):
+ if self.buffer is None:
+ self.buffer = True
+ # Should this raise an exception if -b is not valid?
+ if opt in ('-s', '--serial'):
+ if self.serial is None:
+ self.serial = value
+ if opt in ('-e', '--extra_options'):
+ self.extra_options.append(value)
+ if len(args) == 0 and self.defaultTest is None:
+ # createTests will load tests from self.module
+ self.testNames = None
+ elif len(args) > 0:
+ self.testNames = args
+ if __name__ == '__main__':
+ # to support python -m unittest ...
+ self.module = None
+ else:
+ self.testNames = (self.defaultTest,)
+ self.createTests()
+ except getopt.error, msg:
+ self.usageExit(msg)
+
+ def runTests(self):
+ if self.testRunner is None:
+ self.testRunner = tf_runner.TfTextTestRunner(verbosity=self.verbosity,
+ failfast=self.failfast,
+ buffer=self.buffer,
+ resultclass=tf_runner.TextTestResult,
+ serial=self.serial,
+ extra_options=self.extra_options)
+ super(TradefedProgram, self).runTests()
+
+main = TradefedProgram
+
+def main_run():
+ TradefedProgram(module=None)
diff --git a/libraries/tradefed-python-lib/tradefed_py/tf_runner.py b/libraries/tradefed-python-lib/tradefed_py/tf_runner.py
new file mode 100644
index 000000000..d571082f3
--- /dev/null
+++ b/libraries/tradefed-python-lib/tradefed_py/tf_runner.py
@@ -0,0 +1,227 @@
+#
+# 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