summaryrefslogtreecommitdiffstats
path: root/build
diff options
context:
space:
mode:
authorgkanwar@chromium.org <gkanwar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-06 19:10:13 +0000
committergkanwar@chromium.org <gkanwar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-06 19:10:13 +0000
commit37ee0c790f69885f796e8a849f9d36e657f06056 (patch)
tree6ad31e9ed709b5f2e0b47b6a74834492880ae8cd /build
parentfe7a38e4b5215d542e334d8f6b332719d9194e41 (diff)
downloadchromium_src-37ee0c790f69885f796e8a849f9d36e657f06056.zip
chromium_src-37ee0c790f69885f796e8a849f9d36e657f06056.tar.gz
chromium_src-37ee0c790f69885f796e8a849f9d36e657f06056.tar.bz2
Converts host driven tests to common test_dispatcher
Also renames several files in pylib/host_driven to match the general file naming scheme. This change will break existing host-driven tests downstream which are run though scripts other than test_runner. NOTRY=True BUG=176323 Review URL: https://chromiumcodereview.appspot.com/19537004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@215944 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'build')
-rwxr-xr-xbuild/android/buildbot/bb_device_steps.py2
-rw-r--r--build/android/pylib/base/sharded_tests_queue.py35
-rw-r--r--build/android/pylib/base/test_dispatcher.py4
-rw-r--r--build/android/pylib/host_driven/java_unittest_utils.py27
-rw-r--r--build/android/pylib/host_driven/python_test_base.py152
-rw-r--r--build/android/pylib/host_driven/python_test_caller.py115
-rw-r--r--build/android/pylib/host_driven/python_test_sharder.py203
-rw-r--r--build/android/pylib/host_driven/run_python_tests.py234
-rw-r--r--build/android/pylib/host_driven/setup.py203
-rw-r--r--build/android/pylib/host_driven/test_case.py151
-rw-r--r--build/android/pylib/host_driven/test_info_collection.py4
-rw-r--r--build/android/pylib/host_driven/test_runner.py135
-rw-r--r--build/android/pylib/host_driven/tests_annotations.py6
-rw-r--r--build/android/pylib/instrumentation/test_jar.py6
-rw-r--r--build/android/pylib/utils/run_tests_helper.py3
-rwxr-xr-xbuild/android/run_monkey_test.py132
-rwxr-xr-xbuild/android/test_runner.py87
17 files changed, 611 insertions, 888 deletions
diff --git a/build/android/buildbot/bb_device_steps.py b/build/android/buildbot/bb_device_steps.py
index 2f4b0c7..8bc537f 100755
--- a/build/android/buildbot/bb_device_steps.py
+++ b/build/android/buildbot/bb_device_steps.py
@@ -33,7 +33,7 @@ LOGCAT_DIR = os.path.join(CHROME_SRC, 'out', 'logcat')
# apk_package: package for the apk to be installed.
# test_apk: apk to run tests on.
# test_data: data folder in format destination:source.
-# host_driven_root: The python test root directory.
+# host_driven_root: The host-driven test root directory.
# annotation: Annotation of the tests to include.
# exclude_annotation: The annotation of the tests to exclude.
I_TEST = collections.namedtuple('InstrumentationTest', [
diff --git a/build/android/pylib/base/sharded_tests_queue.py b/build/android/pylib/base/sharded_tests_queue.py
deleted file mode 100644
index c2b4f2d..0000000
--- a/build/android/pylib/base/sharded_tests_queue.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-"""A module that contains a queue for running sharded tests."""
-
-import multiprocessing
-
-
-class ShardedTestsQueue(object):
- """A queue for managing pending tests across different runners.
-
- This class should only be used when sharding.
-
- Attributes:
- num_devices: an integer; the number of attached Android devices.
- tests: a list of tests to be run.
- tests_queue: if sharding, a JoinableQueue object that holds tests from
- |tests|. Otherwise, a list holding tests.
- results_queue: a Queue object to hold TestRunResults objects.
- """
- _STOP_SENTINEL = 'STOP' # sentinel value for iter()
-
- def __init__(self, num_devices, tests):
- self.num_devices = num_devices
- self.tests_queue = multiprocessing.Queue()
- for test in tests:
- self.tests_queue.put(test)
- for _ in xrange(self.num_devices):
- self.tests_queue.put(ShardedTestsQueue._STOP_SENTINEL)
-
- def __iter__(self):
- """Returns an iterator with the test cases."""
- return iter(self.tests_queue.get, ShardedTestsQueue._STOP_SENTINEL)
diff --git a/build/android/pylib/base/test_dispatcher.py b/build/android/pylib/base/test_dispatcher.py
index 45096aa..a8363c2 100644
--- a/build/android/pylib/base/test_dispatcher.py
+++ b/build/android/pylib/base/test_dispatcher.py
@@ -371,15 +371,17 @@ def RunTests(tests, runner_factory, wait_for_debugger, test_device,
shared_test_collection = _TestCollection([_Test(t) for t in tests])
test_collection_factory = lambda: shared_test_collection
tag_results_with_device = False
+ log_string = 'sharded across devices'
else:
# Generate a unique _TestCollection object for each test runner, but use
# the same set of tests.
test_collection_factory = lambda: _TestCollection([_Test(t) for t in tests])
tag_results_with_device = True
+ log_string = 'replicated on each device'
devices = _GetAttachedDevices(wait_for_debugger, test_device)
- logging.info('Will run %d tests: %s', len(tests), str(tests))
+ logging.info('Will run %d tests (%s): %s', len(tests), log_string, str(tests))
runners = _CreateRunners(runner_factory, devices, setup_timeout)
try:
return _RunAllTests(runners, test_collection_factory,
diff --git a/build/android/pylib/host_driven/java_unittest_utils.py b/build/android/pylib/host_driven/java_unittest_utils.py
deleted file mode 100644
index de90638..0000000
--- a/build/android/pylib/host_driven/java_unittest_utils.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""This file is imported by python tests ran by run_python_tests.py."""
-
-import os
-
-from pylib import android_commands
-from pylib.instrumentation import test_runner
-
-
-def _GetPackageName(fname):
- """Extracts the package name from the test file path."""
- base_root = os.path.join('com', 'google', 'android')
- dirname = os.path.dirname(fname)
- package = dirname[dirname.rfind(base_root):]
- return package.replace(os.sep, '.')
-
-
-def RunJavaTest(fname, suite, test, ports_to_forward):
- device = android_commands.GetAttachedDevices()[0]
- package_name = _GetPackageName(fname)
- test = package_name + '.' + suite + '#' + test
- java_test_runner = test_runner.TestRunner(False, device, [test], False,
- False, False, 0, ports_to_forward)
- return java_test_runner.Run()
diff --git a/build/android/pylib/host_driven/python_test_base.py b/build/android/pylib/host_driven/python_test_base.py
deleted file mode 100644
index 42a6326..0000000
--- a/build/android/pylib/host_driven/python_test_base.py
+++ /dev/null
@@ -1,152 +0,0 @@
-# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Base class for Android Python-driven tests.
-
-This test case is intended to serve as the base class for any Python-driven
-tests. It is similar to the Python unitttest module in that the user's tests
-inherit from this case and add their tests in that case.
-
-When a PythonTestBase object is instantiated, its purpose is to run only one of
-its tests. The test runner gives it the name of the test the instance will
-run. The test runner calls SetUp with the Android device ID which the test will
-run against. The runner runs the test method itself, collecting the result,
-and calls TearDown.
-
-Tests can basically do whatever they want in the test methods, such as call
-Java tests using _RunJavaTests. Those methods have the advantage of massaging
-the Java test results into Python test results.
-"""
-
-import logging
-import os
-import time
-
-from pylib import android_commands
-from pylib.base import base_test_result
-from pylib.instrumentation import test_options
-from pylib.instrumentation import test_package
-from pylib.instrumentation import test_result
-from pylib.instrumentation import test_runner
-
-
-# aka the parent of com.google.android
-BASE_ROOT = 'src' + os.sep
-
-
-class PythonTestBase(object):
- """Base class for Python-driven tests."""
-
- def __init__(self, test_name):
- # test_name must match one of the test methods defined on a subclass which
- # inherits from this class.
- # It's stored so we can do the attr lookup on demand, allowing this class
- # to be pickled, a requirement for the multiprocessing module.
- self.test_name = test_name
- class_name = self.__class__.__name__
- self.qualified_name = class_name + '.' + self.test_name
-
- def SetUp(self, options):
- self.options = options
- self.shard_index = self.options.shard_index
- self.device_id = self.options.device_id
- self.adb = android_commands.AndroidCommands(self.device_id)
- self.ports_to_forward = []
-
- def TearDown(self):
- pass
-
- def GetOutDir(self):
- return os.path.join(os.environ['CHROME_SRC'], 'out',
- self.options.build_type)
-
- def Run(self):
- logging.warning('Running Python-driven test: %s', self.test_name)
- return getattr(self, self.test_name)()
-
- def _RunJavaTest(self, fname, suite, test):
- """Runs a single Java test with a Java TestRunner.
-
- Args:
- fname: filename for the test (e.g. foo/bar/baz/tests/FooTest.py)
- suite: name of the Java test suite (e.g. FooTest)
- test: name of the test method to run (e.g. testFooBar)
-
- Returns:
- TestRunResults object with a single test result.
- """
- test = self._ComposeFullTestName(fname, suite, test)
- test_pkg = test_package.TestPackage(
- self.options.test_apk_path, self.options.test_apk_jar_path)
- instrumentation_options = test_options.InstrumentationOptions(
- self.options.build_type,
- self.options.tool,
- self.options.cleanup_test_files,
- self.options.push_deps,
- self.options.annotations,
- self.options.exclude_annotations,
- self.options.test_filter,
- self.options.test_data,
- self.options.save_perf_json,
- self.options.screenshot_failures,
- self.options.disable_assertions,
- self.options.wait_for_debugger,
- self.options.test_apk,
- self.options.test_apk_path,
- self.options.test_apk_jar_path)
- java_test_runner = test_runner.TestRunner(instrumentation_options,
- self.device_id,
- self.shard_index, test_pkg,
- self.ports_to_forward)
- try:
- java_test_runner.SetUp()
- return java_test_runner.RunTest(test)[0]
- finally:
- java_test_runner.TearDown()
-
- def _RunJavaTests(self, fname, tests):
- """Calls a list of tests and stops at the first test failure.
-
- This method iterates until either it encounters a non-passing test or it
- exhausts the list of tests. Then it returns the appropriate Python result.
-
- Args:
- fname: filename for the Python test
- tests: a list of Java test names which will be run
-
- Returns:
- A TestRunResults object containing a result for this Python test.
- """
- test_type = base_test_result.ResultType.PASS
- log = ''
-
- start_ms = int(time.time()) * 1000
- for test in tests:
- # We're only running one test at a time, so this TestRunResults object
- # will hold only one result.
- suite, test_name = test.split('.')
- java_results = self._RunJavaTest(fname, suite, test_name)
- assert len(java_results.GetAll()) == 1
- if not java_results.DidRunPass():
- result = java_results.GetNotPass().pop()
- log = result.GetLog()
- test_type = result.GetType()
- break
- duration_ms = int(time.time()) * 1000 - start_ms
-
- python_results = base_test_result.TestRunResults()
- python_results.AddResult(
- test_result.InstrumentationTestResult(
- self.qualified_name, test_type, start_ms, duration_ms, log=log))
- return python_results
-
- def _ComposeFullTestName(self, fname, suite, test):
- package_name = self._GetPackageName(fname)
- return package_name + '.' + suite + '#' + test
-
- def _GetPackageName(self, fname):
- """Extracts the package name from the test file path."""
- dirname = os.path.dirname(fname)
- package = dirname[dirname.rfind(BASE_ROOT) + len(BASE_ROOT):]
- return package.replace(os.sep, '.')
diff --git a/build/android/pylib/host_driven/python_test_caller.py b/build/android/pylib/host_driven/python_test_caller.py
deleted file mode 100644
index fc53d77..0000000
--- a/build/android/pylib/host_driven/python_test_caller.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Helper module for calling python-based tests."""
-
-
-import logging
-import sys
-import time
-import traceback
-
-from pylib.base import base_test_result
-from pylib.instrumentation import test_result
-
-
-class PythonExceptionTestResult(test_result.InstrumentationTestResult):
- """Helper class for creating a test result from python exception."""
-
- def __init__(self, test_name, start_date_ms, exc_info):
- """Constructs an PythonExceptionTestResult object.
-
- Args:
- test_name: name of the test which raised an exception.
- start_date_ms: the starting time for the test.
- exc_info: exception info, ostensibly from sys.exc_info().
- """
- exc_type, exc_value, exc_traceback = exc_info
- trace_info = ''.join(traceback.format_exception(exc_type, exc_value,
- exc_traceback))
- log_msg = 'Exception:\n' + trace_info
- duration_ms = (int(time.time()) * 1000) - start_date_ms
-
- super(PythonExceptionTestResult, self).__init__(
- 'PythonWrapper#' + test_name,
- base_test_result.ResultType.FAIL,
- start_date_ms,
- duration_ms,
- log=str(exc_type) + ' ' + log_msg)
-
-
-def CallPythonTest(test, options):
- """Invokes a test function and translates Python exceptions into test results.
-
- This method invokes SetUp()/TearDown() on the test. It is intended to be
- resilient to exceptions in SetUp(), the test itself, and TearDown(). Any
- Python exception means the test is marked as failed, and the test result will
- contain information about the exception.
-
- If SetUp() raises an exception, the test is not run.
-
- If TearDown() raises an exception, the test is treated as a failure. However,
- if the test itself raised an exception beforehand, that stack trace will take
- precedence whether or not TearDown() also raised an exception.
-
- shard_index is not applicable in single-device scenarios, when test execution
- is serial rather than parallel. Tests can use this to bring up servers with
- unique port numbers, for example. See also python_test_sharder.
-
- Args:
- test: an object which is ostensibly a subclass of PythonTestBase.
- options: Options to use for setting up tests.
-
- Returns:
- A TestRunResults object which contains any results produced by the test or,
- in the case of a Python exception, the Python exception info.
- """
-
- start_date_ms = int(time.time()) * 1000
- failed = False
-
- try:
- test.SetUp(options)
- except Exception:
- failed = True
- logging.exception(
- 'Caught exception while trying to run SetUp() for test: ' +
- test.qualified_name)
- # Tests whose SetUp() method has failed are likely to fail, or at least
- # yield invalid results.
- exc_info = sys.exc_info()
- results = base_test_result.TestRunResults()
- results.AddResult(PythonExceptionTestResult(
- test.qualified_name, start_date_ms, exc_info))
- return results
-
- try:
- results = test.Run()
- except Exception:
- # Setting this lets TearDown() avoid stomping on our stack trace from Run()
- # should TearDown() also raise an exception.
- failed = True
- logging.exception('Caught exception while trying to run test: ' +
- test.qualified_name)
- exc_info = sys.exc_info()
- results = base_test_result.TestRunResults()
- results.AddResult(PythonExceptionTestResult(
- test.qualified_name, start_date_ms, exc_info))
-
- try:
- test.TearDown()
- except Exception:
- logging.exception(
- 'Caught exception while trying run TearDown() for test: ' +
- test.qualified_name)
- if not failed:
- # Don't stomp the error during the test if TearDown blows up. This is a
- # trade-off: if the test fails, this will mask any problem with TearDown
- # until the test is fixed.
- exc_info = sys.exc_info()
- results = base_test_result.TestRunResults()
- results.AddResult(PythonExceptionTestResult(
- test.qualified_name, start_date_ms, exc_info))
-
- return results
diff --git a/build/android/pylib/host_driven/python_test_sharder.py b/build/android/pylib/host_driven/python_test_sharder.py
deleted file mode 100644
index 17539d1f..0000000
--- a/build/android/pylib/host_driven/python_test_sharder.py
+++ /dev/null
@@ -1,203 +0,0 @@
-# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Takes care of sharding the python-drive tests in multiple devices."""
-
-import copy
-import logging
-import multiprocessing
-
-from pylib.base import base_test_result
-from pylib.base import sharded_tests_queue
-from pylib.forwarder import Forwarder
-
-from python_test_caller import CallPythonTest
-
-
-def SetTestsContainer(tests_container):
- """Sets PythonTestSharder as a top-level field.
-
- PythonTestSharder uses multiprocessing.Pool, which creates a pool of
- processes. This is used to initialize each worker in the pool, ensuring that
- each worker has access to this shared pool of tests.
-
- The multiprocessing module requires that this be a top-level method.
-
- Args:
- tests_container: the container for all the tests.
- """
- PythonTestSharder.tests_container = tests_container
-
-
-def _DefaultRunnable(test_runner):
- """A default runnable for a PythonTestRunner.
-
- Args:
- test_runner: A PythonTestRunner which will run tests.
-
- Returns:
- The test results.
- """
- return test_runner.RunTests()
-
-
-class PythonTestRunner(object):
- """Thin wrapper around a list of PythonTestBase instances.
-
- This is meant to be a long-lived object which can run multiple Python tests
- within its lifetime. Tests will receive the device_id and shard_index.
-
- The shard index affords the ability to create unique port numbers (e.g.
- DEFAULT_PORT + shard_index) if the test so wishes.
- """
-
- def __init__(self, options):
- """Constructor.
-
- Args:
- options: Options to use for setting up tests.
- """
- self.options = options
-
- def RunTests(self):
- """Runs tests from the shared pool of tests, aggregating results.
-
- Returns:
- A list of test results for all of the tests which this runner executed.
- """
- tests = PythonTestSharder.tests_container
-
- results = base_test_result.TestRunResults()
- for t in tests:
- results.AddTestRunResults(CallPythonTest(t, self.options))
- return results
-
-
-class PythonTestSharder(object):
- """Runs Python tests in parallel on multiple devices.
-
- This is lifted more or less wholesale from BaseTestRunner.
-
- Under the covers, it creates a pool of long-lived PythonTestRunners, which
- execute tests from the pool of tests.
-
- Args:
- attached_devices: a list of device IDs attached to the host.
- available_tests: a list of tests to run which subclass PythonTestBase.
- options: Options to use for setting up tests.
-
- Returns:
- An aggregated list of test results.
- """
- tests_container = None
-
- def __init__(self, attached_devices, available_tests, options):
- self.options = options
- self.attached_devices = attached_devices
- self.retries = options.num_retries
- self.tests = available_tests
-
- def _SetupSharding(self, tests):
- """Creates the shared pool of tests and makes it available to test runners.
-
- Args:
- tests: the list of tests which will be consumed by workers.
- """
- SetTestsContainer(sharded_tests_queue.ShardedTestsQueue(
- len(self.attached_devices), tests))
-
- def RunShardedTests(self):
- """Runs tests in parallel using a pool of workers.
-
- Returns:
- A list of test results aggregated from all test runs.
- """
- logging.warning('*' * 80)
- logging.warning('Sharding in ' + str(len(self.attached_devices)) +
- ' devices.')
- logging.warning('Note that the output is not synchronized.')
- logging.warning('Look for the "Final result" banner in the end.')
- logging.warning('*' * 80)
- final_results = base_test_result.TestRunResults()
- tests_to_run = self.tests
-
- Forwarder.UseMultiprocessing()
-
- for retry in xrange(self.retries):
- logging.warning('Try %d of %d', retry + 1, self.retries)
- self._SetupSharding(self.tests)
- test_runners = self._MakeTestRunners(self.attached_devices)
- logging.warning('Starting...')
- pool = multiprocessing.Pool(len(self.attached_devices),
- SetTestsContainer,
- [PythonTestSharder.tests_container])
-
- # List of TestRunResults objects from each test execution.
- try:
- results_lists = pool.map(_DefaultRunnable, test_runners)
- except Exception:
- logging.exception('Unable to run tests. Something with the '
- 'PythonTestRunners has gone wrong.')
- raise Exception('PythonTestRunners were unable to run tests.')
-
- test_results = base_test_result.TestRunResults()
- for t in results_lists:
- test_results.AddTestRunResults(t)
- # Accumulate passing results.
- final_results.AddResults(test_results.GetPass())
- # If we have failed tests, map them to tests to retry.
- failed_tests = [t.GetName() for t in test_results.GetNotPass()]
- tests_to_run = self._GetTestsToRetry(self.tests, failed_tests)
-
- # Bail out early if we have no more tests. This can happen if all tests
- # pass before we're out of retries, for example.
- if not tests_to_run:
- break
-
- # all_passed has accumulated all passing test results.
- # test_results will have the results from the most recent run, which could
- # include a variety of failure modes (unknown, crashed, failed, etc).
- test_results.AddResults(final_results.GetPass())
- final_results = test_results
-
- return final_results
-
- def _MakeTestRunners(self, attached_devices):
- """Initialize and return a list of PythonTestRunners.
-
- Args:
- attached_devices: list of device IDs attached to host.
-
- Returns:
- A list of PythonTestRunners, one for each device.
- """
- test_runners = []
- for index, device in enumerate(attached_devices):
- logging.warning('*' * 80)
- logging.warning('Creating shard %d for %s', index, device)
- logging.warning('*' * 80)
- # Bind the PythonTestRunner to a device & shard index. Give it the
- # runnable which it will use to actually execute the tests.
- test_options = copy.deepcopy(self.options)
- test_options.ensure_value('device_id', device)
- test_options.ensure_value('shard_index', index)
- test_runner = PythonTestRunner(test_options)
- test_runners.append(test_runner)
-
- return test_runners
-
- def _GetTestsToRetry(self, available_tests, failed_test_names):
- """Infers a list of tests to retry from failed tests and available tests.
-
- Args:
- available_tests: a list of tests which subclass PythonTestBase.
- failed_test_names: a list of failed test names.
-
- Returns:
- A list of test objects which correspond to test names found in
- failed_test_names, or an empty list if there is no correspondence.
- """
- tests_to_retry = [t for t in available_tests
- if t.qualified_name in failed_test_names]
- return tests_to_retry
diff --git a/build/android/pylib/host_driven/run_python_tests.py b/build/android/pylib/host_driven/run_python_tests.py
deleted file mode 100644
index 09f2aa1..0000000
--- a/build/android/pylib/host_driven/run_python_tests.py
+++ /dev/null
@@ -1,234 +0,0 @@
-# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Runs the Python tests (relies on using the Java test runner)."""
-
-import logging
-import os
-import sys
-import types
-
-from pylib import android_commands
-from pylib.base import base_test_result
-from pylib.instrumentation import test_options
-from pylib.instrumentation import test_package
-from pylib.instrumentation import test_runner
-from pylib.utils import report_results
-
-import python_test_base
-from python_test_sharder import PythonTestSharder
-from test_info_collection import TestInfoCollection
-
-
-def _GetPythonFiles(root, files):
- """Returns all files from |files| that end in 'Test.py'.
-
- Args:
- root: A directory name with python files.
- files: A list of file names.
-
- Returns:
- A list with all Python driven test file paths.
- """
- return [os.path.join(root, f) for f in files if f.endswith('Test.py')]
-
-
-def _InferImportNameFromFile(python_file):
- """Given a file, infer the import name for that file.
-
- Example: /usr/foo/bar/baz.py -> baz.
-
- Args:
- python_file: path to the Python file, ostensibly to import later.
-
- Returns:
- The module name for the given file.
- """
- return os.path.splitext(os.path.basename(python_file))[0]
-
-
-def DispatchPythonTests(options):
- """Dispatches the Python tests. If there are multiple devices, use sharding.
-
- Args:
- options: command line options.
-
- Returns:
- A tuple of (base_test_result.TestRunResults object, exit code)
-
- Raises:
- Exception: If there are no attached devices.
- """
-
- attached_devices = android_commands.GetAttachedDevices()
- if not attached_devices:
- raise Exception('You have no devices attached or visible!')
- if options.test_device:
- attached_devices = [options.test_device]
-
- test_collection = TestInfoCollection()
- all_tests = _GetAllTests(options.python_test_root, options.official_build)
- test_collection.AddTests(all_tests)
- test_names = [t.qualified_name for t in all_tests]
- logging.debug('All available tests: ' + str(test_names))
-
- available_tests = test_collection.GetAvailableTests(
- options.annotations, options.exclude_annotations, options.test_filter)
-
- if not available_tests:
- logging.warning('No Python tests to run with current args.')
- return (base_test_result.TestRunResults(), 0)
-
- test_names = [t.qualified_name for t in available_tests]
- logging.debug('Final list of tests to run: ' + str(test_names))
-
- # Copy files to each device before running any tests.
- for device_id in attached_devices:
- logging.debug('Pushing files to device %s', device_id)
- test_pkg = test_package.TestPackage(options.test_apk_path,
- options.test_apk_jar_path)
- instrumentation_options = test_options.InstrumentationOptions(
- options.build_type,
- options.tool,
- options.cleanup_test_files,
- options.push_deps,
- options.annotations,
- options.exclude_annotations,
- options.test_filter,
- options.test_data,
- options.save_perf_json,
- options.screenshot_failures,
- options.disable_assertions,
- options.wait_for_debugger,
- options.test_apk,
- options.test_apk_path,
- options.test_apk_jar_path)
- test_files_copier = test_runner.TestRunner(instrumentation_options,
- device_id, 0, test_pkg, [])
- test_files_copier.InstallTestPackage()
- if options.push_deps:
- logging.info('Pushing data deps to device.')
- test_files_copier.PushDataDeps()
- else:
- logging.warning('Skipping pushing data deps to device.')
-
- # Actually run the tests.
- if len(attached_devices) > 1 and options.wait_for_debugger:
- logging.warning('Debugger can not be sharded, '
- 'using first available device')
- attached_devices = attached_devices[:1]
- logging.debug('Running Python tests')
- sharder = PythonTestSharder(attached_devices, available_tests, options)
- test_results = sharder.RunShardedTests()
-
- if not test_results.DidRunPass():
- return (test_results, 1)
-
- return (test_results, 0)
-
-
-def _GetTestModules(python_test_root, is_official_build):
- """Retrieve a sorted list of pythonDrivenTests.
-
- Walks the location of pythonDrivenTests, imports them, and provides the list
- of imported modules to the caller.
-
- Args:
- python_test_root: the path to walk, looking for pythonDrivenTests
- is_official_build: whether to run only those tests marked 'official'
-
- Returns:
- A list of Python modules which may have zero or more tests.
- """
- # By default run all python tests under pythonDrivenTests.
- python_test_file_list = []
- for root, _, files in os.walk(python_test_root):
- if (root.endswith('host_driven_tests') or
- root.endswith('pythonDrivenTests') or
- (is_official_build and root.endswith('pythonDrivenTests/official'))):
- python_test_file_list += _GetPythonFiles(root, files)
- python_test_file_list.sort()
-
- test_module_list = [_GetModuleFromFile(test_file)
- for test_file in python_test_file_list]
- return test_module_list
-
-
-def _GetModuleFromFile(python_file):
- """Gets the module associated with a file by importing it.
-
- Args:
- python_file: file to import
-
- Returns:
- The module object.
- """
- sys.path.append(os.path.dirname(python_file))
- import_name = _InferImportNameFromFile(python_file)
- return __import__(import_name)
-
-
-def _GetTestsFromClass(test_class):
- """Create a list of test objects for each test method on this class.
-
- Test methods are methods on the class which begin with 'test'.
-
- Args:
- test_class: class object which contains zero or more test methods.
-
- Returns:
- A list of test objects, each of which is bound to one test.
- """
- test_names = [m for m in dir(test_class)
- if _IsTestMethod(m, test_class)]
- return map(test_class, test_names)
-
-
-def _GetTestClassesFromModule(test_module):
- tests = []
- for name in dir(test_module):
- attr = getattr(test_module, name)
- if _IsTestClass(attr):
- tests.extend(_GetTestsFromClass(attr))
- return tests
-
-
-def _IsTestClass(test_class):
- return (type(test_class) is types.TypeType and
- issubclass(test_class, python_test_base.PythonTestBase) and
- test_class is not python_test_base.PythonTestBase)
-
-
-def _IsTestMethod(attrname, test_case_class):
- """Checks whether this is a valid test method.
-
- Args:
- attrname: the method name.
- test_case_class: the test case class.
-
- Returns:
- True if test_case_class.'attrname' is callable and it starts with 'test';
- False otherwise.
- """
- attr = getattr(test_case_class, attrname)
- return callable(attr) and attrname.startswith('test')
-
-
-def _GetAllTests(test_root, is_official_build):
- """Retrieve a list of Python test modules and their respective methods.
-
- Args:
- test_root: path which contains Python-driven test files
- is_official_build: whether this is an official build
-
- Returns:
- List of test case objects for all available test methods.
- """
- if not test_root:
- return []
- all_tests = []
- test_module_list = _GetTestModules(test_root, is_official_build)
- for module in test_module_list:
- all_tests.extend(_GetTestClassesFromModule(module))
- return all_tests
diff --git a/build/android/pylib/host_driven/setup.py b/build/android/pylib/host_driven/setup.py
new file mode 100644
index 0000000..ae7860a
--- /dev/null
+++ b/build/android/pylib/host_driven/setup.py
@@ -0,0 +1,203 @@
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Setup for instrumentation host-driven tests."""
+
+import logging
+import os
+import sys
+import types
+
+import test_case
+import test_info_collection
+import test_runner
+
+
+def _GetPythonFiles(root, files):
+ """Returns all files from |files| that end in 'Test.py'.
+
+ Args:
+ root: A directory name with python files.
+ files: A list of file names.
+
+ Returns:
+ A list with all python files that match the testing naming scheme.
+ """
+ return [os.path.join(root, f) for f in files if f.endswith('Test.py')]
+
+
+def _InferImportNameFromFile(python_file):
+ """Given a file, infer the import name for that file.
+
+ Example: /usr/foo/bar/baz.py -> baz.
+
+ Args:
+ python_file: Path to the Python file, ostensibly to import later.
+
+ Returns:
+ The module name for the given file.
+ """
+ return os.path.splitext(os.path.basename(python_file))[0]
+
+
+def _GetTestModules(host_driven_test_root, is_official_build):
+ """Retrieve a list of python modules that match the testing naming scheme.
+
+ Walks the location of host-driven tests, imports them, and provides the list
+ of imported modules to the caller.
+
+ Args:
+ host_driven_test_root: The path to walk, looking for the
+ pythonDrivenTests or host_driven_tests directory
+ is_official_build: Whether to run only those tests marked 'official'
+
+ Returns:
+ A list of python modules under |host_driven_test_root| which match the
+ testing naming scheme. Each module should define one or more classes that
+ derive from HostDrivenTestCase.
+ """
+ # By default run all host-driven tests under pythonDrivenTests or
+ # host_driven_tests.
+ host_driven_test_file_list = []
+ for root, _, files in os.walk(host_driven_test_root):
+ if (root.endswith('host_driven_tests') or
+ root.endswith('pythonDrivenTests') or
+ (is_official_build and (root.endswith('pythonDrivenTests/official') or
+ root.endswith('host_driven_tests/official')))):
+ host_driven_test_file_list += _GetPythonFiles(root, files)
+ host_driven_test_file_list.sort()
+
+ test_module_list = [_GetModuleFromFile(test_file)
+ for test_file in host_driven_test_file_list]
+ return test_module_list
+
+
+def _GetModuleFromFile(python_file):
+ """Gets the python module associated with a file by importing it.
+
+ Args:
+ python_file: File to import.
+
+ Returns:
+ The module object.
+ """
+ sys.path.append(os.path.dirname(python_file))
+ import_name = _InferImportNameFromFile(python_file)
+ return __import__(import_name)
+
+
+def _GetTestsFromClass(test_case_class, **kwargs):
+ """Returns one test object for each test method in |test_case_class|.
+
+ Test methods are methods on the class which begin with 'test'.
+
+ Args:
+ test_case_class: Class derived from HostDrivenTestCase which contains zero
+ or more test methods.
+ kwargs: Keyword args to pass into the constructor of test cases.
+
+ Returns:
+ A list of test case objects, each initialized for a particular test method.
+ """
+ test_names = [m for m in dir(test_case_class)
+ if _IsTestMethod(m, test_case_class)]
+ return [test_case_class(name, **kwargs) for name in test_names]
+
+
+def _GetTestsFromModule(test_module, **kwargs):
+ """Gets a list of test objects from |test_module|.
+
+ Args:
+ test_module: Module from which to get the set of test methods.
+ kwargs: Keyword args to pass into the constructor of test cases.
+
+ Returns:
+ A list of test case objects each initialized for a particular test method
+ defined in |test_module|.
+ """
+
+ tests = []
+ for name in dir(test_module):
+ attr = getattr(test_module, name)
+ if _IsTestCaseClass(attr):
+ tests.extend(_GetTestsFromClass(attr, **kwargs))
+ return tests
+
+
+def _IsTestCaseClass(test_class):
+ return (type(test_class) is types.TypeType and
+ issubclass(test_class, test_case.HostDrivenTestCase) and
+ test_class is not test_case.HostDrivenTestCase)
+
+
+def _IsTestMethod(attrname, test_case_class):
+ """Checks whether this is a valid test method.
+
+ Args:
+ attrname: The method name.
+ test_case_class: The test case class.
+
+ Returns:
+ True if test_case_class.'attrname' is callable and it starts with 'test';
+ False otherwise.
+ """
+ attr = getattr(test_case_class, attrname)
+ return callable(attr) and attrname.startswith('test')
+
+
+def _GetAllTests(test_root, is_official_build, **kwargs):
+ """Retrieve a list of host-driven tests defined under |test_root|.
+
+ Args:
+ test_root: Path which contains host-driven test files.
+ is_official_build: Whether this is an official build.
+ kwargs: Keyword args to pass into the constructor of test cases.
+
+ Returns:
+ List of test case objects, one for each available test method.
+ """
+ if not test_root:
+ return []
+ all_tests = []
+ test_module_list = _GetTestModules(test_root, is_official_build)
+ for module in test_module_list:
+ all_tests.extend(_GetTestsFromModule(module, **kwargs))
+ return all_tests
+
+
+def InstrumentationSetup(host_driven_test_root, official_build,
+ instrumentation_options):
+ """Creates a list of host-driven instrumentation tests and a runner factory.
+
+ Args:
+ host_driven_test_root: Directory where the host-driven tests are.
+ official_build: True if this is an official build.
+ instrumentation_options: An InstrumentationOptions object.
+
+ Returns:
+ A tuple of (TestRunnerFactory, tests).
+ """
+
+ test_collection = test_info_collection.TestInfoCollection()
+ all_tests = _GetAllTests(
+ host_driven_test_root, official_build,
+ instrumentation_options=instrumentation_options)
+ test_collection.AddTests(all_tests)
+
+ available_tests = test_collection.GetAvailableTests(
+ instrumentation_options.annotations,
+ instrumentation_options.exclude_annotations,
+ instrumentation_options.test_filter)
+ logging.debug('All available tests: ' + str(
+ [t.tagged_name for t in available_tests]))
+
+ def TestRunnerFactory(device, shard_index):
+ return test_runner.HostDrivenTestRunner(
+ device, shard_index,
+ instrumentation_options.tool,
+ instrumentation_options.build_type,
+ instrumentation_options.push_deps,
+ instrumentation_options.cleanup_test_files)
+
+ return (TestRunnerFactory, available_tests)
diff --git a/build/android/pylib/host_driven/test_case.py b/build/android/pylib/host_driven/test_case.py
new file mode 100644
index 0000000..2bf5f0a
--- /dev/null
+++ b/build/android/pylib/host_driven/test_case.py
@@ -0,0 +1,151 @@
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Base class for host-driven test cases.
+
+This test case is intended to serve as the base class for any host-driven
+test cases. It is similar to the Python unitttest module in that test cases
+inherit from this class and add methods which will be run as tests.
+
+When a HostDrivenTestCase object is instantiated, its purpose is to run only one
+test method in the derived class. The test runner gives it the name of the test
+method the instance will run. The test runner calls SetUp with the device ID
+which the test method will run against. The test runner runs the test method
+itself, collecting the result, and calls TearDown.
+
+Tests can perform arbitrary Python commands and asserts in test methods. Tests
+that run instrumentation tests can make use of the _RunJavaTests helper function
+to trigger Java tests and convert results into a single host-driven test result.
+"""
+
+import logging
+import os
+import time
+
+from pylib import android_commands
+from pylib.base import base_test_result
+from pylib.instrumentation import test_package
+from pylib.instrumentation import test_result
+from pylib.instrumentation import test_runner
+
+# aka the parent of com.google.android
+BASE_ROOT = 'src' + os.sep
+
+
+class HostDrivenTestCase(object):
+ """Base class for host-driven test cases."""
+
+ _HOST_DRIVEN_TAG = 'HostDriven'
+
+ def __init__(self, test_name, instrumentation_options=None):
+ """Create a test case initialized to run |test_name|.
+
+ Args:
+ test_name: The name of the method to run as the test.
+ instrumentation_options: An InstrumentationOptions object.
+ """
+ self.test_name = test_name
+ class_name = self.__class__.__name__
+ self.qualified_name = '%s.%s' % (class_name, self.test_name)
+ # Use tagged_name when creating results, so that we can identify host-driven
+ # tests in the overall results.
+ self.tagged_name = '%s_%s' % (self._HOST_DRIVEN_TAG, self.qualified_name)
+
+ self.instrumentation_options = instrumentation_options
+ self.ports_to_forward = []
+
+ def SetUp(self, device, shard_index, build_type, push_deps,
+ cleanup_test_files):
+ self.device_id = device
+ self.shard_index = shard_index
+ self.build_type = build_type
+ self.adb = android_commands.AndroidCommands(self.device_id)
+ self.push_deps = push_deps
+ self.cleanup_test_files = cleanup_test_files
+
+ def TearDown(self):
+ pass
+
+ def GetOutDir(self):
+ return os.path.join(os.environ['CHROME_SRC'], 'out',
+ self.build_type)
+
+ def Run(self):
+ logging.info('Running host-driven test: %s', self.tagged_name)
+ # Get the test method on the derived class and execute it
+ return getattr(self, self.test_name)()
+
+ def __RunJavaTest(self, package_name, test_case, test_method):
+ """Runs a single Java test method with a Java TestRunner.
+
+ Args:
+ package_name: Package name in which the java tests live
+ (e.g. foo.bar.baz.tests)
+ test_case: Name of the Java test case (e.g. FooTest)
+ test_method: Name of the test method to run (e.g. testFooBar)
+
+ Returns:
+ TestRunResults object with a single test result.
+ """
+ test = '%s.%s#%s' % (package_name, test_case, test_method)
+ test_pkg = test_package.TestPackage(
+ self.instrumentation_options.test_apk_path,
+ self.instrumentation_options.test_apk_jar_path)
+ java_test_runner = test_runner.TestRunner(self.instrumentation_options,
+ self.device_id,
+ self.shard_index, test_pkg,
+ self.ports_to_forward)
+ try:
+ java_test_runner.SetUp()
+ return java_test_runner.RunTest(test)[0]
+ finally:
+ java_test_runner.TearDown()
+
+ def _RunJavaTests(self, package_name, tests):
+ """Calls a list of tests and stops at the first test failure.
+
+ This method iterates until either it encounters a non-passing test or it
+ exhausts the list of tests. Then it returns the appropriate overall result.
+
+ Test cases may make use of this method internally to assist in running
+ instrumentation tests. This function relies on instrumentation_options
+ being defined.
+
+ Args:
+ package_name: Package name in which the java tests live
+ (e.g. foo.bar.baz.tests)
+ tests: A list of Java test names which will be run
+
+ Returns:
+ A TestRunResults object containing an overall result for this set of Java
+ tests. If any Java tests do not pass, this is a fail overall.
+ """
+ test_type = base_test_result.ResultType.PASS
+ log = ''
+
+ start_ms = int(time.time()) * 1000
+ for test in tests:
+ # We're only running one test at a time, so this TestRunResults object
+ # will hold only one result.
+ suite, test_name = test.split('.')
+ java_result = self.__RunJavaTest(package_name, suite, test_name)
+ assert len(java_result.GetAll()) == 1
+ if not java_result.DidRunPass():
+ result = java_result.GetNotPass().pop()
+ log = result.GetLog()
+ test_type = result.GetType()
+ break
+ duration_ms = int(time.time()) * 1000 - start_ms
+
+ overall_result = base_test_result.TestRunResults()
+ overall_result.AddResult(
+ test_result.InstrumentationTestResult(
+ self.tagged_name, test_type, start_ms, duration_ms, log=log))
+ return overall_result
+
+ def __str__(self):
+ return self.tagged_name
+
+ def __repr__(self):
+ return self.tagged_name
diff --git a/build/android/pylib/host_driven/test_info_collection.py b/build/android/pylib/host_driven/test_info_collection.py
index cc0ab13..a857d3c 100644
--- a/build/android/pylib/host_driven/test_info_collection.py
+++ b/build/android/pylib/host_driven/test_info_collection.py
@@ -2,7 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-"""Module containing information about the python-driven tests."""
+"""Module containing information about the host-driven tests."""
import logging
import os
@@ -70,7 +70,7 @@ class TestInfoCollection(object):
Args:
annotations: List of annotations. Each test in the returned list is
annotated with atleast one of these annotations.
- exlcude_annotations: List of annotations. The tests in the returned
+ exclude_annotations: List of annotations. The tests in the returned
list are not annotated with any of these annotations.
name_filter: name filter which tests must match, if any
diff --git a/build/android/pylib/host_driven/test_runner.py b/build/android/pylib/host_driven/test_runner.py
new file mode 100644
index 0000000..9a9acdd
--- /dev/null
+++ b/build/android/pylib/host_driven/test_runner.py
@@ -0,0 +1,135 @@
+# Copyright 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Runs host-driven tests on a particular device."""
+
+import logging
+import sys
+import time
+import traceback
+
+from pylib.base import base_test_result
+from pylib.base import base_test_runner
+from pylib.instrumentation import test_result
+
+import test_case
+
+
+class HostDrivenExceptionTestResult(test_result.InstrumentationTestResult):
+ """Test result corresponding to a python exception in a host-driven test."""
+
+ def __init__(self, test_name, start_date_ms, exc_info):
+ """Constructs a HostDrivenExceptionTestResult object.
+
+ Args:
+ test_name: name of the test which raised an exception.
+ start_date_ms: the starting time for the test.
+ exc_info: exception info, ostensibly from sys.exc_info().
+ """
+ exc_type, exc_value, exc_traceback = exc_info
+ trace_info = ''.join(traceback.format_exception(exc_type, exc_value,
+ exc_traceback))
+ log_msg = 'Exception:\n' + trace_info
+ duration_ms = (int(time.time()) * 1000) - start_date_ms
+
+ super(HostDrivenExceptionTestResult, self).__init__(
+ test_name,
+ base_test_result.ResultType.FAIL,
+ start_date_ms,
+ duration_ms,
+ log=str(exc_type) + ' ' + log_msg)
+
+
+class HostDrivenTestRunner(base_test_runner.BaseTestRunner):
+ """Orchestrates running a set of host-driven tests.
+
+ Any Python exceptions in the tests are caught and translated into a failed
+ result, rather than being re-raised on the main thread.
+ """
+
+ #override
+ def __init__(self, device, shard_index, tool, build_type, push_deps,
+ cleanup_test_files):
+ """Creates a new HostDrivenTestRunner.
+
+ Args:
+ device: Attached android device.
+ shard_index: Shard index.
+ tool: Name of the Valgrind tool.
+ build_type: 'Release' or 'Debug'.
+ push_deps: If True, push all dependencies to the device.
+ cleanup_test_files: Whether or not to cleanup test files on device.
+ """
+
+ super(HostDrivenTestRunner, self).__init__(device, tool, build_type,
+ push_deps, cleanup_test_files)
+
+ # The shard index affords the ability to create unique port numbers (e.g.
+ # DEFAULT_PORT + shard_index) if the test so wishes.
+ self.shard_index = shard_index
+
+ #override
+ def RunTest(self, test):
+ """Sets up and runs a test case.
+
+ Args:
+ test: An object which is ostensibly a subclass of HostDrivenTestCase.
+
+ Returns:
+ A TestRunResults object which contains the result produced by the test
+ and, in the case of a failure, the test that should be retried.
+ """
+
+ assert isinstance(test, test_case.HostDrivenTestCase)
+
+ start_date_ms = int(time.time()) * 1000
+ exception_raised = False
+
+ try:
+ test.SetUp(self.device, self.shard_index, self.build_type,
+ self._push_deps, self._cleanup_test_files)
+ except Exception:
+ logging.exception(
+ 'Caught exception while trying to run SetUp() for test: ' +
+ test.tagged_name)
+ # Tests whose SetUp() method has failed are likely to fail, or at least
+ # yield invalid results.
+ exc_info = sys.exc_info()
+ results = base_test_result.TestRunResults()
+ results.AddResult(HostDrivenExceptionTestResult(
+ test.tagged_name, start_date_ms, exc_info))
+ return results, test
+
+ try:
+ results = test.Run()
+ except Exception:
+ # Setting this lets TearDown() avoid stomping on our stack trace from
+ # Run() should TearDown() also raise an exception.
+ exception_raised = True
+ logging.exception('Caught exception while trying to run test: ' +
+ test.tagged_name)
+ exc_info = sys.exc_info()
+ results = base_test_result.TestRunResults()
+ results.AddResult(HostDrivenExceptionTestResult(
+ test.tagged_name, start_date_ms, exc_info))
+
+ try:
+ test.TearDown()
+ except Exception:
+ logging.exception(
+ 'Caught exception while trying run TearDown() for test: ' +
+ test.tagged_name)
+ if not exception_raised:
+ # Don't stomp the error during the test if TearDown blows up. This is a
+ # trade-off: if the test fails, this will mask any problem with TearDown
+ # until the test is fixed.
+ exc_info = sys.exc_info()
+ results = base_test_result.TestRunResults()
+ results.AddResult(HostDrivenExceptionTestResult(
+ test.tagged_name, start_date_ms, exc_info))
+
+ if not results.DidRunPass():
+ return results, test
+ else:
+ return results, None
diff --git a/build/android/pylib/host_driven/tests_annotations.py b/build/android/pylib/host_driven/tests_annotations.py
index 8744256..2654e32 100644
--- a/build/android/pylib/host_driven/tests_annotations.py
+++ b/build/android/pylib/host_driven/tests_annotations.py
@@ -2,7 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-"""Annotations for python-driven tests."""
+"""Annotations for host-driven tests."""
import os
@@ -13,7 +13,7 @@ class AnnotatedFunctions(object):
@staticmethod
def _AddFunction(annotation, function):
- """Adds an annotated to function to our container.
+ """Adds an annotated function to our container.
Args:
annotation: the annotation string.
@@ -56,7 +56,7 @@ class AnnotatedFunctions(object):
if qualified_function_name in tests]
-# The following functions are annotations used for the python driven tests.
+# The following functions are annotations used for the host-driven tests.
def Smoke(function):
return AnnotatedFunctions._AddFunction('Smoke', function)
diff --git a/build/android/pylib/instrumentation/test_jar.py b/build/android/pylib/instrumentation/test_jar.py
index e582a1d..fc0865d 100644
--- a/build/android/pylib/instrumentation/test_jar.py
+++ b/build/android/pylib/instrumentation/test_jar.py
@@ -169,7 +169,7 @@ class TestJar(object):
for test_method in self.GetTestMethods():
annotations_ = frozenset(self.GetTestAnnotations(test_method))
if (annotations_.isdisjoint(self._ANNOTATIONS) and
- not self.IsPythonDrivenTest(test_method)):
+ not self.IsHostDrivenTest(test_method)):
tests_missing_annotations.append(test_method)
return sorted(tests_missing_annotations)
@@ -202,7 +202,7 @@ class TestJar(object):
available_tests = list(set(available_tests) - set(excluded_tests))
else:
available_tests = [m for m in self.GetTestMethods()
- if not self.IsPythonDrivenTest(m)]
+ if not self.IsHostDrivenTest(m)]
tests = []
if test_filter:
@@ -216,5 +216,5 @@ class TestJar(object):
return tests
@staticmethod
- def IsPythonDrivenTest(test):
+ def IsHostDrivenTest(test):
return 'pythonDrivenTests' in test
diff --git a/build/android/pylib/utils/run_tests_helper.py b/build/android/pylib/utils/run_tests_helper.py
index 60823fc..fba0871c 100644
--- a/build/android/pylib/utils/run_tests_helper.py
+++ b/build/android/pylib/utils/run_tests_helper.py
@@ -2,10 +2,9 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-"""Helper functions common to native, java and python test runners."""
+"""Helper functions common to native, java and host-driven test runners."""
import logging
-import os
import sys
import time
diff --git a/build/android/run_monkey_test.py b/build/android/run_monkey_test.py
index b2b5e4a..4957ac6 100755
--- a/build/android/run_monkey_test.py
+++ b/build/android/run_monkey_test.py
@@ -9,32 +9,58 @@ import optparse
import random
import sys
-from pylib import android_commands
from pylib.base import base_test_result
-from pylib.host_driven import python_test_base
-from pylib.host_driven import python_test_sharder
+from pylib.base import test_dispatcher
+from pylib.host_driven import test_case
+from pylib.host_driven import test_runner
from pylib.utils import report_results
from pylib.utils import test_options_parser
-class MonkeyTest(python_test_base.PythonTestBase):
+class MonkeyTest(test_case.HostDrivenTestCase):
+ def __init__(self, test_name, package_name, activity_name, category, seed,
+ throttle, event_count, verbosity, extra_args):
+ """Create a MonkeyTest object.
+
+ Args:
+ test_name: Name of the method to run for this test object.
+ package_name: Allowed package.
+ activity_name: Name of the activity to start.
+ category: A list of allowed categories.
+ seed: Seed value for pseduo-random generator. Same seed value
+ generates the same sequence of events. Seed is randomized by default.
+ throttle: Delay between events (ms).
+ event_count: Number of events to generate.
+ verbosity: Verbosity level [0-3].
+ extra_args: A string of other args to pass to the command verbatim.
+ """
+ super(MonkeyTest, self).__init__(test_name)
+ self.package_name = package_name
+ self.activity_name = activity_name
+ self.category = category
+ self.seed = seed or random.randint(1, 100)
+ self.throttle = throttle
+ self.event_count = event_count
+ self.verbosity = verbosity
+ self.extra_args = extra_args
+
def testMonkey(self):
# Launch and wait for Chrome to launch.
- self.adb.StartActivity(self.options.package_name,
- self.options.activity_name,
+ self.adb.StartActivity(self.package_name,
+ self.activity_name,
wait_for_completion=True,
action='android.intent.action.MAIN',
force_stop=True)
# Chrome crashes are not always caught by Monkey test runner.
# Verify Chrome has the same PID before and after the test.
- before_pids = self.adb.ExtractPid(self.options.package_name)
+ before_pids = self.adb.ExtractPid(self.package_name)
# Run the test.
output = ''
if before_pids:
output = '\n'.join(self._LaunchMonkeyTest())
- after_pids = self.adb.ExtractPid(self.options.package_name)
+ after_pids = self.adb.ExtractPid(self.package_name)
crashed = (not before_pids or not after_pids
or after_pids[0] != before_pids[0])
@@ -42,82 +68,63 @@ class MonkeyTest(python_test_base.PythonTestBase):
results = base_test_result.TestRunResults()
if 'Monkey finished' in output and not crashed:
result = base_test_result.BaseTestResult(
- self.qualified_name, base_test_result.ResultType.PASS, log=output)
+ self.tagged_name, base_test_result.ResultType.PASS, log=output)
else:
result = base_test_result.BaseTestResult(
- self.qualified_name, base_test_result.ResultType.FAIL, log=output)
+ self.tagged_name, base_test_result.ResultType.FAIL, log=output)
results.AddResult(result)
return results
def _LaunchMonkeyTest(self):
"""Runs monkey test for a given package.
- Looks at the following parameters in the options object provided
- in class initializer:
- package_name: Allowed package.
- category: A list of allowed categories.
- throttle: Delay between events (ms).
- seed: Seed value for pseduo-random generator. Same seed value
- generates the same sequence of events. Seed is randomized by
- default.
- event_count: Number of events to generate.
- verbosity: Verbosity level [0-3].
- extra_args: A string of other args to pass to the command verbatim.
+ Returns:
+ Output from the monkey command on the device.
"""
- category = self.options.category or []
- seed = self.options.seed or random.randint(1, 100)
- throttle = self.options.throttle or 100
- event_count = self.options.event_count or 10000
- verbosity = self.options.verbosity or 1
- extra_args = self.options.extra_args or ''
-
- timeout_ms = event_count * throttle * 1.5
+ timeout_ms = self.event_count * self.throttle * 1.5
cmd = ['monkey',
- '-p %s' % self.options.package_name,
- ' '.join(['-c %s' % c for c in category]),
- '--throttle %d' % throttle,
- '-s %d' % seed,
- '-v ' * verbosity,
+ '-p %s' % self.package_name,
+ ' '.join(['-c %s' % c for c in self.category]),
+ '--throttle %d' % self.throttle,
+ '-s %d' % self.seed,
+ '-v ' * self.verbosity,
'--monitor-native-crashes',
'--kill-process-after-error',
- extra_args,
- '%d' % event_count]
+ self.extra_args,
+ '%d' % self.event_count]
return self.adb.RunShellCommand(' '.join(cmd), timeout_time=timeout_ms)
-def DispatchPythonTests(options):
- """Dispatches the Monkey tests, sharding it if there multiple devices."""
+def RunMonkeyTests(options):
+ """Runs the Monkey tests, replicating it if there multiple devices."""
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
- attached_devices = android_commands.GetAttachedDevices()
- if not attached_devices:
- raise Exception('You have no devices attached or visible!')
# Actually run the tests.
logging.debug('Running monkey tests.')
- # TODO(frankf): This is a stop-gap solution. Come up with a
- # general way for running tests on every devices.
- available_tests = []
- for k in range(len(attached_devices)):
- new_method = 'testMonkey%d' % k
- setattr(MonkeyTest, new_method, MonkeyTest.testMonkey)
- available_tests.append(MonkeyTest(new_method))
- options.ensure_value('shard_retries', 1)
- sharder = python_test_sharder.PythonTestSharder(
- attached_devices, available_tests, options)
- results = sharder.RunShardedTests()
+ available_tests = [
+ MonkeyTest('testMonkey', options.package_name, options.activity_name,
+ category=options.category, seed=options.seed,
+ throttle=options.throttle, event_count=options.event_count,
+ verbosity=options.verbosity, extra_args=options.extra_args)]
+
+ def TestRunnerFactory(device, shard_index):
+ return test_runner.HostDrivenTestRunner(
+ device, shard_index, '', options.build_type, False, False)
+
+ results, exit_code = test_dispatcher.RunTests(
+ available_tests, TestRunnerFactory, False, None, shard=False,
+ build_type=options.build_type, num_retries=0)
+
report_results.LogFull(
results=results,
test_type='Monkey',
test_package='Monkey',
build_type=options.build_type)
- # TODO(gkanwar): After the host-driven tests have been refactored, they sould
- # use the comment exit code system (part of pylib/base/shard.py)
- if not results.DidRunPass():
- return 1
- return 0
+
+ return exit_code
def main():
@@ -128,12 +135,12 @@ def main():
parser.add_option('--activity-name',
default='com.google.android.apps.chrome.Main',
help='Name of the activity to start [default: %default].')
- parser.add_option('--category',
- help='A list of allowed categories [default: ""].')
+ parser.add_option('--category', default='',
+ help='A list of allowed categories [default: %default].')
parser.add_option('--throttle', default=100, type='int',
help='Delay between events (ms) [default: %default]. ')
parser.add_option('--seed', type='int',
- help=('Seed value for pseduo-random generator. Same seed '
+ help=('Seed value for pseudo-random generator. Same seed '
'value generates the same sequence of events. Seed '
'is randomized by default.'))
parser.add_option('--event-count', default=10000, type='int',
@@ -156,10 +163,7 @@ def main():
if options.category:
options.category = options.category.split(',')
- # TODO(gkanwar): This should go away when the host-driven tests are refactored
- options.num_retries = 1
-
- DispatchPythonTests(options)
+ RunMonkeyTests(options)
if __name__ == '__main__':
diff --git a/build/android/test_runner.py b/build/android/test_runner.py
index 6e2a1e4..9632ed7 100755
--- a/build/android/test_runner.py
+++ b/build/android/test_runner.py
@@ -23,7 +23,7 @@ from pylib.base import test_dispatcher
from pylib.gtest import gtest_config
from pylib.gtest import setup as gtest_setup
from pylib.gtest import test_options as gtest_test_options
-from pylib.host_driven import run_python_tests as python_dispatch
+from pylib.host_driven import setup as host_driven_setup
from pylib.instrumentation import setup as instrumentation_setup
from pylib.instrumentation import test_options as instrumentation_test_options
from pylib.uiautomator import setup as uiautomator_setup
@@ -159,19 +159,13 @@ def AddJavaTestOptions(option_parser):
'-E', '--exclude-annotation', dest='exclude_annotation_str',
help=('Comma-separated list of annotations. Exclude tests with these '
'annotations.'))
- option_parser.add_option('-j', '--java_only', action='store_true',
- default=False, help='Run only the Java tests.')
- option_parser.add_option('-p', '--python_only', action='store_true',
- default=False,
- help='Run only the host-driven tests.')
option_parser.add_option('--screenshot', dest='screenshot_failures',
action='store_true',
help='Capture screenshots of test failures')
option_parser.add_option('--save-perf-json', action='store_true',
help='Saves the JSON file for each UI Perf test.')
- option_parser.add_option('--official-build', help='Run official build tests.')
- option_parser.add_option('--python_test_root',
- help='Root of the host-driven tests.')
+ option_parser.add_option('--official-build', action='store_true',
+ help='Run official build tests.')
option_parser.add_option('--keep_test_server_ports',
action='store_true',
help=('Indicates the test server ports must be '
@@ -195,19 +189,6 @@ def AddJavaTestOptions(option_parser):
def ProcessJavaTestOptions(options, error_func):
"""Processes options/arguments and populates |options| with defaults."""
- if options.java_only and options.python_only:
- error_func('Options java_only (-j) and python_only (-p) '
- 'are mutually exclusive.')
- options.run_java_tests = True
- options.run_python_tests = True
- if options.java_only:
- options.run_python_tests = False
- elif options.python_only:
- options.run_java_tests = False
-
- if not options.python_test_root:
- options.run_python_tests = False
-
if options.annotation_str:
options.annotations = options.annotation_str.split(',')
elif options.test_filter:
@@ -237,6 +218,13 @@ def AddInstrumentationTestOptions(option_parser):
AddJavaTestOptions(option_parser)
AddCommonOptions(option_parser)
+ option_parser.add_option('-j', '--java_only', action='store_true',
+ default=False, help='Run only the Java tests.')
+ option_parser.add_option('-p', '--python_only', action='store_true',
+ default=False,
+ help='Run only the host-driven tests.')
+ option_parser.add_option('--python_test_root',
+ help='Root of the host-driven tests.')
option_parser.add_option('-w', '--wait_debugger', dest='wait_for_debugger',
action='store_true',
help='Wait for debugger.')
@@ -264,6 +252,19 @@ def ProcessInstrumentationOptions(options, error_func):
ProcessJavaTestOptions(options, error_func)
+ if options.java_only and options.python_only:
+ error_func('Options java_only (-j) and python_only (-p) '
+ 'are mutually exclusive.')
+ options.run_java_tests = True
+ options.run_python_tests = True
+ if options.java_only:
+ options.run_python_tests = False
+ elif options.python_only:
+ options.run_java_tests = False
+
+ if not options.python_test_root:
+ options.run_python_tests = False
+
if not options.test_apk:
error_func('--test-apk must be specified.')
@@ -431,8 +432,17 @@ def _RunInstrumentationTests(options, error_func):
results.AddTestRunResults(test_results)
if options.run_python_tests:
- test_results, test_exit_code = (
- python_dispatch.DispatchPythonTests(options))
+ runner_factory, tests = host_driven_setup.InstrumentationSetup(
+ options.python_test_root, options.official_build,
+ instrumentation_options)
+
+ test_results, test_exit_code = test_dispatcher.RunTests(
+ tests, runner_factory, False,
+ options.test_device,
+ shard=True,
+ build_type=options.build_type,
+ test_timeout=None,
+ num_retries=options.num_retries)
results.AddTestRunResults(test_results)
@@ -458,27 +468,14 @@ def _RunUIAutomatorTests(options, error_func):
results = base_test_result.TestRunResults()
exit_code = 0
- if options.run_java_tests:
- runner_factory, tests = uiautomator_setup.Setup(uiautomator_options)
+ runner_factory, tests = uiautomator_setup.Setup(uiautomator_options)
- test_results, exit_code = test_dispatcher.RunTests(
- tests, runner_factory, False, options.test_device,
- shard=True,
- build_type=options.build_type,
- test_timeout=None,
- num_retries=options.num_retries)
-
- results.AddTestRunResults(test_results)
-
- if options.run_python_tests:
- test_results, test_exit_code = (
- python_dispatch.DispatchPythonTests(options))
-
- results.AddTestRunResults(test_results)
-
- # Only allow exit code escalation
- if test_exit_code and exit_code != constants.ERROR_EXIT_CODE:
- exit_code = test_exit_code
+ results, exit_code = test_dispatcher.RunTests(
+ tests, runner_factory, False, options.test_device,
+ shard=True,
+ build_type=options.build_type,
+ test_timeout=None,
+ num_retries=options.num_retries)
report_results.LogFull(
results=results,
@@ -525,8 +522,6 @@ def RunTestsCommand(command, options, args, option_parser):
else:
raise Exception('Unknown test type.')
- return exit_code
-
def HelpCommand(command, options, args, option_parser):
"""Display help for a certain command, or overall help.