OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 """Implements test sharding logic.""" |
| 6 |
| 7 import logging |
| 8 import sys |
| 9 import threading |
| 10 |
| 11 from pylib import android_commands |
| 12 from pylib import forwarder |
| 13 |
| 14 import test_result |
| 15 |
| 16 |
| 17 class _Worker(threading.Thread): |
| 18 """Runs tests from the test_queue using the given runner in a separate thread. |
| 19 |
| 20 Places results in the out_results. |
| 21 """ |
| 22 def __init__(self, runner, test_queue, out_results, out_retry): |
| 23 """Initializes the worker. |
| 24 |
| 25 Args: |
| 26 runner: A TestRunner object used to run the tests. |
| 27 test_queue: A list from which to get tests to run. |
| 28 out_results: A list to add TestResults to. |
| 29 out_retry: A list to add tests to retry. |
| 30 """ |
| 31 super(_Worker, self).__init__() |
| 32 self.daemon = True |
| 33 self._exc_info = None |
| 34 self._runner = runner |
| 35 self._test_queue = test_queue |
| 36 self._out_results = out_results |
| 37 self._out_retry = out_retry |
| 38 |
| 39 #override |
| 40 def run(self): |
| 41 """Run tests from the queue in a seperate thread until it is empty. |
| 42 |
| 43 Adds TestResults objects to the out_results list and may add tests to the |
| 44 out_retry list. |
| 45 """ |
| 46 try: |
| 47 while True: |
| 48 test = self._test_queue.pop() |
| 49 result, retry = self._runner.Run(test) |
| 50 self._out_results.append(result) |
| 51 if retry: |
| 52 self._out_retry.append(retry) |
| 53 except IndexError: |
| 54 pass |
| 55 except: |
| 56 self._exc_info = sys.exc_info() |
| 57 raise |
| 58 |
| 59 def ReraiseIfException(self): |
| 60 """Reraise exception if an exception was raised in the thread.""" |
| 61 if self._exc_info: |
| 62 raise self._exc_info[0], self._exc_info[1], self._exc_info[2] |
| 63 |
| 64 |
| 65 def _RunAllTests(runners, tests): |
| 66 """Run all tests using the given TestRunners. |
| 67 |
| 68 Args: |
| 69 runners: a list of TestRunner objects. |
| 70 tests: a list of Tests to run using the given TestRunners. |
| 71 |
| 72 Returns: |
| 73 Tuple: (list of TestResults, list of tests to retry) |
| 74 """ |
| 75 tests_queue = list(tests) |
| 76 workers = [] |
| 77 results = [] |
| 78 retry = [] |
| 79 for r in runners: |
| 80 worker = _Worker(r, tests_queue, results, retry) |
| 81 worker.start() |
| 82 workers.append(worker) |
| 83 while workers: |
| 84 for w in workers[:]: |
| 85 # Allow the main thread to periodically check for keyboard interrupts. |
| 86 w.join(0.1) |
| 87 if not w.isAlive(): |
| 88 w.ReraiseIfException() |
| 89 workers.remove(w) |
| 90 return (results, retry) |
| 91 |
| 92 |
| 93 def _CreateRunners(runner_factory, devices): |
| 94 """Creates a test runner for each device. |
| 95 |
| 96 Note: if a device is unresponsive the corresponding TestRunner will not be |
| 97 included in the returned list. |
| 98 |
| 99 Args: |
| 100 runner_factory: callable that takes a device and returns a TestRunner. |
| 101 devices: list of device serial numbers as strings. |
| 102 |
| 103 Returns: |
| 104 A list of TestRunner objects. |
| 105 """ |
| 106 test_runners = [] |
| 107 for index, device in enumerate(devices): |
| 108 logging.warning('*' * 80) |
| 109 logging.warning('Creating shard %d for %s', index, device) |
| 110 logging.warning('*' * 80) |
| 111 try: |
| 112 test_runners.append(runner_factory(device)) |
| 113 except android_commands.errors.DeviceUnresponsiveError as e: |
| 114 logging.warning('****Failed to create a shard: [%s]', e) |
| 115 return test_runners |
| 116 |
| 117 |
| 118 def ShardAndRunTests(runner_factory, devices, tests, build_type='Debug', |
| 119 tries=3): |
| 120 """Run all tests on attached devices, retrying tests that don't pass. |
| 121 |
| 122 Args: |
| 123 runner_factory: callable that takes a device and returns a TestRunner. |
| 124 devices: list of attached device serial numbers as strings. |
| 125 tests: list of tests to run. |
| 126 build_type: either 'Debug' or 'Release'. |
| 127 tries: number of tries before accepting failure. |
| 128 |
| 129 Returns: |
| 130 A test_result.TestResults object. |
| 131 """ |
| 132 final_results = test_result.TestResults() |
| 133 results = test_result.TestResults() |
| 134 forwarder.Forwarder.KillHost(build_type) |
| 135 try_count = 0 |
| 136 while tests: |
| 137 devices = set(devices).intersection(android_commands.GetAttachedDevices()) |
| 138 if not devices: |
| 139 # There are no visible devices attached, this is unrecoverable. |
| 140 msg = 'No devices attached and visible to run tests!' |
| 141 logging.critical(msg) |
| 142 raise Exception(msg) |
| 143 if try_count >= tries: |
| 144 # We've retried too many times, return the TestResults up to this point. |
| 145 results.ok = final_results.ok |
| 146 final_results = results |
| 147 break |
| 148 try_count += 1 |
| 149 runners = _CreateRunners(runner_factory, devices) |
| 150 try: |
| 151 results_list, tests = _RunAllTests(runners, tests) |
| 152 results = test_result.TestResults.FromTestResults(results_list) |
| 153 final_results.ok += results.ok |
| 154 except android_commands.errors.DeviceUnresponsiveError as e: |
| 155 logging.warning('****Failed to run test: [%s]', e) |
| 156 forwarder.Forwarder.KillHost(build_type) |
| 157 return final_results |
OLD | NEW |