| Index: build/android/pylib/base/shard.py
|
| diff --git a/build/android/pylib/base/shard.py b/build/android/pylib/base/shard.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..b4e1281ef979537043fb42c15609ba6a158162c8
|
| --- /dev/null
|
| +++ b/build/android/pylib/base/shard.py
|
| @@ -0,0 +1,157 @@
|
| +# Copyright (c) 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.
|
| +
|
| +"""Implements test sharding logic."""
|
| +
|
| +import logging
|
| +import sys
|
| +import threading
|
| +
|
| +from pylib import android_commands
|
| +from pylib import forwarder
|
| +
|
| +import test_result
|
| +
|
| +
|
| +class _Worker(threading.Thread):
|
| + """Runs tests from the test_queue using the given runner in a separate thread.
|
| +
|
| + Places results in the out_results.
|
| + """
|
| + def __init__(self, runner, test_queue, out_results, out_retry):
|
| + """Initializes the worker.
|
| +
|
| + Args:
|
| + runner: A TestRunner object used to run the tests.
|
| + test_queue: A list from which to get tests to run.
|
| + out_results: A list to add TestResults to.
|
| + out_retry: A list to add tests to retry.
|
| + """
|
| + super(_Worker, self).__init__()
|
| + self.daemon = True
|
| + self._exc_info = None
|
| + self._runner = runner
|
| + self._test_queue = test_queue
|
| + self._out_results = out_results
|
| + self._out_retry = out_retry
|
| +
|
| + #override
|
| + def run(self):
|
| + """Run tests from the queue in a seperate thread until it is empty.
|
| +
|
| + Adds TestResults objects to the out_results list and may add tests to the
|
| + out_retry list.
|
| + """
|
| + try:
|
| + while True:
|
| + test = self._test_queue.pop()
|
| + result, retry = self._runner.Run(test)
|
| + self._out_results.append(result)
|
| + if retry:
|
| + self._out_retry.append(retry)
|
| + except IndexError:
|
| + pass
|
| + except:
|
| + self._exc_info = sys.exc_info()
|
| + raise
|
| +
|
| + def ReraiseIfException(self):
|
| + """Reraise exception if an exception was raised in the thread."""
|
| + if self._exc_info:
|
| + raise self._exc_info[0], self._exc_info[1], self._exc_info[2]
|
| +
|
| +
|
| +def _RunAllTests(runners, tests):
|
| + """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.
|
| +
|
| + Returns:
|
| + Tuple: (list of TestResults, list of tests to retry)
|
| + """
|
| + tests_queue = list(tests)
|
| + workers = []
|
| + results = []
|
| + retry = []
|
| + for r in runners:
|
| + worker = _Worker(r, tests_queue, results, retry)
|
| + worker.start()
|
| + workers.append(worker)
|
| + while workers:
|
| + for w in workers[:]:
|
| + # Allow the main thread to periodically check for keyboard interrupts.
|
| + w.join(0.1)
|
| + if not w.isAlive():
|
| + w.ReraiseIfException()
|
| + workers.remove(w)
|
| + return (results, retry)
|
| +
|
| +
|
| +def _CreateRunners(runner_factory, devices):
|
| + """Creates a test runner for each device.
|
| +
|
| + Note: if a device is unresponsive the corresponding TestRunner will not be
|
| + included in the returned list.
|
| +
|
| + Args:
|
| + runner_factory: callable that takes a device and returns a TestRunner.
|
| + devices: list of device serial numbers as strings.
|
| +
|
| + Returns:
|
| + A list of TestRunner objects.
|
| + """
|
| + test_runners = []
|
| + for index, device in enumerate(devices):
|
| + logging.warning('*' * 80)
|
| + logging.warning('Creating shard %d for %s', index, device)
|
| + logging.warning('*' * 80)
|
| + try:
|
| + test_runners.append(runner_factory(device))
|
| + except android_commands.errors.DeviceUnresponsiveError as e:
|
| + logging.warning('****Failed to create a shard: [%s]', e)
|
| + return test_runners
|
| +
|
| +
|
| +def ShardAndRunTests(runner_factory, devices, tests, build_type='Debug',
|
| + tries=3):
|
| + """Run all tests on attached devices, retrying tests that don't pass.
|
| +
|
| + Args:
|
| + runner_factory: callable that takes a device and returns a TestRunner.
|
| + devices: list of attached device serial numbers as strings.
|
| + tests: list of tests to run.
|
| + build_type: either 'Debug' or 'Release'.
|
| + tries: number of tries before accepting failure.
|
| +
|
| + Returns:
|
| + A test_result.TestResults object.
|
| + """
|
| + final_results = test_result.TestResults()
|
| + results = test_result.TestResults()
|
| + forwarder.Forwarder.KillHost(build_type)
|
| + try_count = 0
|
| + while tests:
|
| + devices = set(devices).intersection(android_commands.GetAttachedDevices())
|
| + if not devices:
|
| + # There are no visible devices attached, this is unrecoverable.
|
| + msg = 'No devices attached and visible to run tests!'
|
| + logging.critical(msg)
|
| + raise Exception(msg)
|
| + if try_count >= tries:
|
| + # We've retried too many times, return the TestResults up to this point.
|
| + results.ok = final_results.ok
|
| + final_results = results
|
| + break
|
| + try_count += 1
|
| + runners = _CreateRunners(runner_factory, devices)
|
| + try:
|
| + results_list, tests = _RunAllTests(runners, tests)
|
| + results = test_result.TestResults.FromTestResults(results_list)
|
| + final_results.ok += results.ok
|
| + except android_commands.errors.DeviceUnresponsiveError as e:
|
| + logging.warning('****Failed to run test: [%s]', e)
|
| + forwarder.Forwarder.KillHost(build_type)
|
| + return final_results
|
|
|