| Index: build/android/pylib/base/test_dispatcher.py
|
| diff --git a/build/android/pylib/base/shard.py b/build/android/pylib/base/test_dispatcher.py
|
| similarity index 58%
|
| rename from build/android/pylib/base/shard.py
|
| rename to build/android/pylib/base/test_dispatcher.py
|
| index af02eed1508bf318623c282e4d9e860d522ee608..54cd39d6ca048b723b737723ec1242f4423a75ac 100644
|
| --- a/build/android/pylib/base/shard.py
|
| +++ b/build/android/pylib/base/test_dispatcher.py
|
| @@ -2,7 +2,19 @@
|
| # Use of this source code is governed by a BSD-style license that can be
|
| # found in the LICENSE file.
|
|
|
| -"""Implements test sharding logic."""
|
| +"""Dispatches tests, either sharding or replicating them.
|
| +
|
| +To dispatch, performs the following steps:
|
| +* Create a test collection factory, using the given tests
|
| + - If sharding: test collection factory returns the same shared test collection
|
| + to all test runners
|
| + - If replciating: test collection factory returns a unique test collection to
|
| + each test runner, with the same set of tests in each.
|
| +* Get the list of devices to run on
|
| +* Create test runners
|
| +* Run each test runner in its own thread, pulling tests from the test collection
|
| + generated from the test collection factory until there are no tests left.
|
| +"""
|
|
|
| import logging
|
| import threading
|
| @@ -44,8 +56,8 @@ class _Test(object):
|
| """Initializes the _Test object.
|
|
|
| Args:
|
| - test: the test.
|
| - tries: number of tries so far.
|
| + test: The test.
|
| + tries: Number of tries so far.
|
| """
|
| self.test = test
|
| self.tries = tries
|
| @@ -55,7 +67,7 @@ class _TestCollection(object):
|
| """A threadsafe collection of tests.
|
|
|
| Args:
|
| - tests: list of tests to put in the collection.
|
| + tests: List of tests to put in the collection.
|
| """
|
|
|
| def __init__(self, tests=[]):
|
| @@ -117,7 +129,7 @@ class _TestCollection(object):
|
|
|
|
|
| def _RunTestsFromQueue(runner, test_collection, out_results, watcher,
|
| - num_retries):
|
| + num_retries, tag_results_with_device=False):
|
| """Runs tests from the test_collection until empty using the given runner.
|
|
|
| Adds TestRunResults objects to the out_results list and may add tests to the
|
| @@ -129,7 +141,25 @@ def _RunTestsFromQueue(runner, test_collection, out_results, watcher,
|
| out_results: A list to add TestRunResults to.
|
| watcher: A watchdog_timer.WatchdogTimer object, used as a shared timeout.
|
| num_retries: Number of retries for a test.
|
| + tag_results_with_device: If True, appends the name of the device on which
|
| + the test was run to the test name. Used when replicating to identify
|
| + which device ran each copy of the test, and to ensure each copy of the
|
| + test is recorded separately.
|
| """
|
| +
|
| + def TagTestRunResults(test_run_results):
|
| + """Tags all results with the last 4 digits of the device id.
|
| +
|
| + Used when replicating tests to distinguish the same tests run on different
|
| + devices. We use a set to store test results, so the hash (generated from
|
| + name and tag) must be unique to be considered different results.
|
| + """
|
| + new_test_run_results = base_test_result.TestRunResults()
|
| + for test_result in test_run_results.GetAll():
|
| + test_result.SetTag(runner.device[-4:])
|
| + new_test_run_results.AddResult(test_result)
|
| + return new_test_run_results
|
| +
|
| for test in test_collection:
|
| watcher.Reset()
|
| try:
|
| @@ -139,6 +169,8 @@ def _RunTestsFromQueue(runner, test_collection, out_results, watcher,
|
| logging.warning(msg)
|
| raise android_commands.errors.DeviceUnresponsiveError(msg)
|
| result, retry = runner.RunTest(test.test)
|
| + if tag_results_with_device:
|
| + result = TagTestRunResults(result)
|
| test.tries += 1
|
| if retry and test.tries <= num_retries:
|
| # Retry non-passing results, only record passing results.
|
| @@ -167,11 +199,11 @@ def _SetUp(runner_factory, device, out_runners, threadsafe_counter):
|
| added to out_runners.
|
|
|
| Args:
|
| - runner_factory: callable that takes a device and index and returns a
|
| + runner_factory: Callable that takes a device and index and returns a
|
| TestRunner object.
|
| - device: the device serial number to set up.
|
| - out_runners: list to add the successfully set up TestRunner object.
|
| - threadsafe_counter: a _ThreadSafeCounter object used to get shard indices.
|
| + device: The device serial number to set up.
|
| + out_runners: List to add the successfully set up TestRunner object.
|
| + threadsafe_counter: A _ThreadSafeCounter object used to get shard indices.
|
| """
|
| try:
|
| index = threadsafe_counter.GetAndIncrement()
|
| @@ -183,28 +215,34 @@ def _SetUp(runner_factory, device, out_runners, threadsafe_counter):
|
| logging.warning('Failed to create shard for %s: [%s]', device, e)
|
|
|
|
|
| -def _RunAllTests(runners, tests, num_retries, timeout=None):
|
| +def _RunAllTests(runners, test_collection_factory, num_retries, timeout=None,
|
| + tag_results_with_device=False):
|
| """Run all tests using the given TestRunners.
|
|
|
| Args:
|
| - runners: a list of TestRunner objects.
|
| - tests: a list of Tests to run using the given TestRunners.
|
| - num_retries: number of retries for a test.
|
| - timeout: watchdog timeout in seconds, defaults to the default timeout.
|
| + runners: A list of TestRunner objects.
|
| + test_collection_factory: A callable to generate a _TestCollection object for
|
| + each test runner.
|
| + num_retries: Number of retries for a test.
|
| + timeout: Watchdog timeout in seconds.
|
| + tag_results_with_device: If True, appends the name of the device on which
|
| + the test was run to the test name. Used when replicating to identify
|
| + which device ran each copy of the test, and to ensure each copy of the
|
| + test is recorded separately.
|
|
|
| Returns:
|
| A tuple of (TestRunResults object, exit code)
|
| """
|
| - logging.warning('Running %s tests with %s test runners.' %
|
| - (len(tests), len(runners)))
|
| - tests_collection = _TestCollection([_Test(t) for t in tests])
|
| + logging.warning('Running tests with %s test runners.' % (len(runners)))
|
| results = []
|
| exit_code = 0
|
| watcher = watchdog_timer.WatchdogTimer(timeout)
|
| +
|
| workers = reraiser_thread.ReraiserThreadGroup(
|
| [reraiser_thread.ReraiserThread(
|
| _RunTestsFromQueue,
|
| - [r, tests_collection, results, watcher, num_retries],
|
| + [r, test_collection_factory(), results, watcher, num_retries,
|
| + tag_results_with_device],
|
| name=r.device[-4:])
|
| for r in runners])
|
| run_results = base_test_result.TestRunResults()
|
| @@ -231,10 +269,10 @@ def _CreateRunners(runner_factory, devices, timeout=None):
|
| included in the returned list.
|
|
|
| Args:
|
| - runner_factory: callable that takes a device and index and returns a
|
| + runner_factory: Callable that takes a device and index and returns a
|
| TestRunner object.
|
| - devices: list of device serial numbers as strings.
|
| - timeout: watchdog timeout in seconds, defaults to the default timeout.
|
| + devices: List of device serial numbers as strings.
|
| + timeout: Watchdog timeout in seconds, defaults to the default timeout.
|
|
|
| Returns:
|
| A list of TestRunner objects.
|
| @@ -256,8 +294,8 @@ def _TearDownRunners(runners, timeout=None):
|
| """Calls TearDown() for each test runner in parallel.
|
|
|
| Args:
|
| - runners: a list of TestRunner objects.
|
| - timeout: watchdog timeout in seconds, defaults to the default timeout.
|
| + runners: A list of TestRunner objects.
|
| + timeout: Watchdog timeout in seconds, defaults to the default timeout.
|
| """
|
| threads = reraiser_thread.ReraiserThreadGroup(
|
| [reraiser_thread.ReraiserThread(r.TearDown, name=r.device[-4:])
|
| @@ -266,23 +304,59 @@ def _TearDownRunners(runners, timeout=None):
|
| threads.JoinAll(watchdog_timer.WatchdogTimer(timeout))
|
|
|
|
|
| -def ShardAndRunTests(runner_factory, devices, tests, build_type='Debug',
|
| - test_timeout=DEFAULT_TIMEOUT,
|
| - setup_timeout=DEFAULT_TIMEOUT,
|
| - num_retries=2):
|
| +
|
| +def _GetAttachedDevices(wait_for_debugger=False, test_device=None):
|
| + """Get all attached devices.
|
| +
|
| + If we are using a debugger, limit to only one device.
|
| +
|
| + Args:
|
| + wait_for_debugger: True if this run will use a debugger.
|
| + test_device: Name of a specific device to use.
|
| +
|
| + Returns:
|
| + A list of attached devices.
|
| + """
|
| + attached_devices = []
|
| +
|
| + attached_devices = android_commands.GetAttachedDevices()
|
| + if test_device:
|
| + assert test_device in attached_devices, (
|
| + 'Did not find device %s among attached device. Attached devices: %s'
|
| + % (test_device, ', '.join(attached_devices)))
|
| + attached_devices = [test_device]
|
| +
|
| + if len(attached_devices) > 1 and wait_for_debugger:
|
| + logging.warning('Debugger can not be sharded, using first available device')
|
| + attached_devices = attached_devices[:1]
|
| +
|
| + return attached_devices
|
| +
|
| +
|
| +def RunTests(tests, runner_factory, wait_for_debugger, test_device,
|
| + shard=True,
|
| + build_type='Debug',
|
| + test_timeout=DEFAULT_TIMEOUT,
|
| + setup_timeout=DEFAULT_TIMEOUT,
|
| + num_retries=2):
|
| """Run all tests on attached devices, retrying tests that don't pass.
|
|
|
| Args:
|
| - runner_factory: callable that takes a device and index and returns a
|
| - TestRunner object.
|
| - devices: list of attached device serial numbers as strings.
|
| - tests: list of tests to run.
|
| - build_type: either 'Debug' or 'Release'.
|
| - test_timeout: watchdog timeout in seconds for running tests, defaults to the
|
| - default timeout.
|
| - setup_timeout: watchdog timeout in seconds for creating and cleaning up
|
| - test runners, defaults to the default timeout.
|
| - num_retries: number of retries for a test.
|
| + tests: List of tests to run.
|
| + runner_factory: Callable that takes a device and index and returns a
|
| + TestRunner object.
|
| + wait_for_debugger: True if this test is using a debugger.
|
| + test_device: A specific device to run tests on, or None.
|
| + shard: True if we should shard, False if we should replicate tests.
|
| + - Sharding tests will distribute tests across all test runners through a
|
| + shared test collection.
|
| + - Replicating tests will copy all tests to each test runner through a
|
| + unique test collection for each test runner.
|
| + build_type: Either 'Debug' or 'Release'.
|
| + test_timeout: Watchdog timeout in seconds for running tests.
|
| + setup_timeout: Watchdog timeout in seconds for creating and cleaning up
|
| + test runners.
|
| + num_retries: Number of retries for a test.
|
|
|
| Returns:
|
| A tuple of (base_test_result.TestRunResults object, exit code).
|
| @@ -291,10 +365,26 @@ def ShardAndRunTests(runner_factory, devices, tests, build_type='Debug',
|
| logging.error('No tests to run.')
|
| return (base_test_result.TestRunResults(), constants.ERROR_EXIT_CODE)
|
|
|
| + if shard:
|
| + # Generate a shared _TestCollection object for all test runners, so they
|
| + # draw from a common pool of tests.
|
| + shared_test_collection = _TestCollection([_Test(t) for t in tests])
|
| + test_collection_factory = lambda: shared_test_collection
|
| + tag_results_with_device = False
|
| + 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
|
| +
|
| + devices = _GetAttachedDevices(wait_for_debugger, test_device)
|
| +
|
| logging.info('Will run %d tests: %s', len(tests), str(tests))
|
| +
|
| runners = _CreateRunners(runner_factory, devices, setup_timeout)
|
| try:
|
| - return _RunAllTests(runners, tests, num_retries, test_timeout)
|
| + return _RunAllTests(runners, test_collection_factory,
|
| + num_retries, test_timeout, tag_results_with_device)
|
| finally:
|
| try:
|
| _TearDownRunners(runners, setup_timeout)
|
|
|