Index: build/android/pylib/base/shard.py |
diff --git a/build/android/pylib/base/shard.py b/build/android/pylib/base/shard.py |
deleted file mode 100644 |
index af02eed1508bf318623c282e4d9e860d522ee608..0000000000000000000000000000000000000000 |
--- a/build/android/pylib/base/shard.py |
+++ /dev/null |
@@ -1,302 +0,0 @@ |
-# 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 threading |
- |
-from pylib import android_commands |
-from pylib import constants |
-from pylib.utils import reraiser_thread |
-from pylib.utils import watchdog_timer |
- |
-import base_test_result |
- |
- |
-DEFAULT_TIMEOUT = 7 * 60 # seven minutes |
- |
- |
-class _ThreadSafeCounter(object): |
- """A threadsafe counter.""" |
- |
- def __init__(self): |
- self._lock = threading.Lock() |
- self._value = 0 |
- |
- def GetAndIncrement(self): |
- """Get the current value and increment it atomically. |
- |
- Returns: |
- The value before incrementing. |
- """ |
- with self._lock: |
- pre_increment = self._value |
- self._value += 1 |
- return pre_increment |
- |
- |
-class _Test(object): |
- """Holds a test with additional metadata.""" |
- |
- def __init__(self, test, tries=0): |
- """Initializes the _Test object. |
- |
- Args: |
- test: the test. |
- tries: number of tries so far. |
- """ |
- self.test = test |
- self.tries = tries |
- |
- |
-class _TestCollection(object): |
- """A threadsafe collection of tests. |
- |
- Args: |
- tests: list of tests to put in the collection. |
- """ |
- |
- def __init__(self, tests=[]): |
- self._lock = threading.Lock() |
- self._tests = [] |
- self._tests_in_progress = 0 |
- # Used to signal that an item is avaliable or all items have been handled. |
- self._item_avaliable_or_all_done = threading.Event() |
- for t in tests: |
- self.add(t) |
- |
- def _pop(self): |
- """Pop a test from the collection. |
- |
- Waits until a test is avaliable or all tests have been handled. |
- |
- Returns: |
- A test or None if all tests have been handled. |
- """ |
- while True: |
- # Wait for a test to be avaliable or all tests to have been handled. |
- self._item_avaliable_or_all_done.wait() |
- with self._lock: |
- # Check which of the two conditions triggered the signal. |
- if self._tests_in_progress == 0: |
- return None |
- try: |
- return self._tests.pop(0) |
- except IndexError: |
- # Another thread beat us to the avaliable test, wait again. |
- self._item_avaliable_or_all_done.clear() |
- |
- def add(self, test): |
- """Add an test to the collection. |
- |
- Args: |
- test: A test to add. |
- """ |
- with self._lock: |
- self._tests.append(test) |
- self._item_avaliable_or_all_done.set() |
- self._tests_in_progress += 1 |
- |
- def test_completed(self): |
- """Indicate that a test has been fully handled.""" |
- with self._lock: |
- self._tests_in_progress -= 1 |
- if self._tests_in_progress == 0: |
- # All tests have been handled, signal all waiting threads. |
- self._item_avaliable_or_all_done.set() |
- |
- def __iter__(self): |
- """Iterate through tests in the collection until all have been handled.""" |
- while True: |
- r = self._pop() |
- if r is None: |
- break |
- yield r |
- |
- |
-def _RunTestsFromQueue(runner, test_collection, out_results, watcher, |
- num_retries): |
- """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 |
- out_retry list. |
- |
- Args: |
- runner: A TestRunner object used to run the tests. |
- test_collection: A _TestCollection from which to get _Test objects to run. |
- 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. |
- """ |
- for test in test_collection: |
- watcher.Reset() |
- try: |
- if not android_commands.IsDeviceAttached(runner.device): |
- # Device is unresponsive, stop handling tests on this device. |
- msg = 'Device %s is unresponsive.' % runner.device |
- logging.warning(msg) |
- raise android_commands.errors.DeviceUnresponsiveError(msg) |
- result, retry = runner.RunTest(test.test) |
- test.tries += 1 |
- if retry and test.tries <= num_retries: |
- # Retry non-passing results, only record passing results. |
- pass_results = base_test_result.TestRunResults() |
- pass_results.AddResults(result.GetPass()) |
- out_results.append(pass_results) |
- logging.warning('Will retry test, try #%s.' % test.tries) |
- test_collection.add(_Test(test=retry, tries=test.tries)) |
- else: |
- # All tests passed or retry limit reached. Either way, record results. |
- out_results.append(result) |
- except: |
- # An unhandleable exception, ensure tests get run by another device and |
- # reraise this exception on the main thread. |
- test_collection.add(test) |
- raise |
- finally: |
- # Retries count as separate tasks so always mark the popped test as done. |
- test_collection.test_completed() |
- |
- |
-def _SetUp(runner_factory, device, out_runners, threadsafe_counter): |
- """Creates a test runner for each device and calls SetUp() in parallel. |
- |
- Note: if a device is unresponsive the corresponding TestRunner will not be |
- added to out_runners. |
- |
- Args: |
- 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. |
- """ |
- try: |
- index = threadsafe_counter.GetAndIncrement() |
- logging.warning('Creating shard %s for device %s.', index, device) |
- runner = runner_factory(device, index) |
- runner.SetUp() |
- out_runners.append(runner) |
- except android_commands.errors.DeviceUnresponsiveError as e: |
- logging.warning('Failed to create shard for %s: [%s]', device, e) |
- |
- |
-def _RunAllTests(runners, tests, num_retries, timeout=None): |
- """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. |
- |
- 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]) |
- results = [] |
- exit_code = 0 |
- watcher = watchdog_timer.WatchdogTimer(timeout) |
- workers = reraiser_thread.ReraiserThreadGroup( |
- [reraiser_thread.ReraiserThread( |
- _RunTestsFromQueue, |
- [r, tests_collection, results, watcher, num_retries], |
- name=r.device[-4:]) |
- for r in runners]) |
- run_results = base_test_result.TestRunResults() |
- workers.StartAll() |
- |
- # Catch DeviceUnresponsiveErrors and set a warning exit code |
- try: |
- workers.JoinAll(watcher) |
- except android_commands.errors.DeviceUnresponsiveError as e: |
- logging.error(e) |
- exit_code = constants.WARNING_EXIT_CODE |
- |
- for r in results: |
- run_results.AddTestRunResults(r) |
- if not run_results.DidRunPass(): |
- exit_code = constants.ERROR_EXIT_CODE |
- return (run_results, exit_code) |
- |
- |
-def _CreateRunners(runner_factory, devices, timeout=None): |
- """Creates a test runner for each device and calls SetUp() in parallel. |
- |
- 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 index and returns a |
- TestRunner object. |
- devices: list of device serial numbers as strings. |
- timeout: watchdog timeout in seconds, defaults to the default timeout. |
- |
- Returns: |
- A list of TestRunner objects. |
- """ |
- logging.warning('Creating %s test runners.' % len(devices)) |
- runners = [] |
- counter = _ThreadSafeCounter() |
- threads = reraiser_thread.ReraiserThreadGroup( |
- [reraiser_thread.ReraiserThread(_SetUp, |
- [runner_factory, d, runners, counter], |
- name=d[-4:]) |
- for d in devices]) |
- threads.StartAll() |
- threads.JoinAll(watchdog_timer.WatchdogTimer(timeout)) |
- return runners |
- |
- |
-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. |
- """ |
- threads = reraiser_thread.ReraiserThreadGroup( |
- [reraiser_thread.ReraiserThread(r.TearDown, name=r.device[-4:]) |
- for r in runners]) |
- threads.StartAll() |
- 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): |
- """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. |
- |
- Returns: |
- A tuple of (base_test_result.TestRunResults object, exit code). |
- """ |
- if not tests: |
- logging.error('No tests to run.') |
- return (base_test_result.TestRunResults(), constants.ERROR_EXIT_CODE) |
- |
- 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) |
- finally: |
- try: |
- _TearDownRunners(runners, setup_timeout) |
- except android_commands.errors.DeviceUnresponsiveError as e: |
- logging.warning('Device unresponsive during TearDown: [%s]', e) |