media/webrtc/trunk/build/android/pylib/python_test_sharder.py

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
michael@0 2 # Use of this source code is governed by a BSD-style license that can be
michael@0 3 # found in the LICENSE file.
michael@0 4
michael@0 5 """Takes care of sharding the python-drive tests in multiple devices."""
michael@0 6
michael@0 7 import copy
michael@0 8 import logging
michael@0 9 import multiprocessing
michael@0 10
michael@0 11 from python_test_caller import CallPythonTest
michael@0 12 from run_java_tests import FatalTestException
michael@0 13 import sharded_tests_queue
michael@0 14 from test_result import TestResults
michael@0 15
michael@0 16
michael@0 17 def SetTestsContainer(tests_container):
michael@0 18 """Sets PythonTestSharder as a top-level field.
michael@0 19
michael@0 20 PythonTestSharder uses multiprocessing.Pool, which creates a pool of
michael@0 21 processes. This is used to initialize each worker in the pool, ensuring that
michael@0 22 each worker has access to this shared pool of tests.
michael@0 23
michael@0 24 The multiprocessing module requires that this be a top-level method.
michael@0 25
michael@0 26 Args:
michael@0 27 tests_container: the container for all the tests.
michael@0 28 """
michael@0 29 PythonTestSharder.tests_container = tests_container
michael@0 30
michael@0 31
michael@0 32 def _DefaultRunnable(test_runner):
michael@0 33 """A default runnable for a PythonTestRunner.
michael@0 34
michael@0 35 Args:
michael@0 36 test_runner: A PythonTestRunner which will run tests.
michael@0 37
michael@0 38 Returns:
michael@0 39 The test results.
michael@0 40 """
michael@0 41 return test_runner.RunTests()
michael@0 42
michael@0 43
michael@0 44 class PythonTestRunner(object):
michael@0 45 """Thin wrapper around a list of PythonTestBase instances.
michael@0 46
michael@0 47 This is meant to be a long-lived object which can run multiple Python tests
michael@0 48 within its lifetime. Tests will receive the device_id and shard_index.
michael@0 49
michael@0 50 The shard index affords the ability to create unique port numbers (e.g.
michael@0 51 DEFAULT_PORT + shard_index) if the test so wishes.
michael@0 52 """
michael@0 53
michael@0 54 def __init__(self, options):
michael@0 55 """Constructor.
michael@0 56
michael@0 57 Args:
michael@0 58 options: Options to use for setting up tests.
michael@0 59 """
michael@0 60 self.options = options
michael@0 61
michael@0 62 def RunTests(self):
michael@0 63 """Runs tests from the shared pool of tests, aggregating results.
michael@0 64
michael@0 65 Returns:
michael@0 66 A list of test results for all of the tests which this runner executed.
michael@0 67 """
michael@0 68 tests = PythonTestSharder.tests_container
michael@0 69
michael@0 70 results = []
michael@0 71 for t in tests:
michael@0 72 res = CallPythonTest(t, self.options)
michael@0 73 results.append(res)
michael@0 74
michael@0 75 return TestResults.FromTestResults(results)
michael@0 76
michael@0 77
michael@0 78 class PythonTestSharder(object):
michael@0 79 """Runs Python tests in parallel on multiple devices.
michael@0 80
michael@0 81 This is lifted more or less wholesale from BaseTestRunner.
michael@0 82
michael@0 83 Under the covers, it creates a pool of long-lived PythonTestRunners, which
michael@0 84 execute tests from the pool of tests.
michael@0 85
michael@0 86 Args:
michael@0 87 attached_devices: a list of device IDs attached to the host.
michael@0 88 available_tests: a list of tests to run which subclass PythonTestBase.
michael@0 89 options: Options to use for setting up tests.
michael@0 90
michael@0 91 Returns:
michael@0 92 An aggregated list of test results.
michael@0 93 """
michael@0 94 tests_container = None
michael@0 95
michael@0 96 def __init__(self, attached_devices, available_tests, options):
michael@0 97 self.options = options
michael@0 98 self.attached_devices = attached_devices
michael@0 99 self.retries = options.shard_retries
michael@0 100 self.tests = available_tests
michael@0 101
michael@0 102 def _SetupSharding(self, tests):
michael@0 103 """Creates the shared pool of tests and makes it available to test runners.
michael@0 104
michael@0 105 Args:
michael@0 106 tests: the list of tests which will be consumed by workers.
michael@0 107 """
michael@0 108 SetTestsContainer(sharded_tests_queue.ShardedTestsQueue(
michael@0 109 len(self.attached_devices), tests))
michael@0 110
michael@0 111 def RunShardedTests(self):
michael@0 112 """Runs tests in parallel using a pool of workers.
michael@0 113
michael@0 114 Returns:
michael@0 115 A list of test results aggregated from all test runs.
michael@0 116 """
michael@0 117 logging.warning('*' * 80)
michael@0 118 logging.warning('Sharding in ' + str(len(self.attached_devices)) +
michael@0 119 ' devices.')
michael@0 120 logging.warning('Note that the output is not synchronized.')
michael@0 121 logging.warning('Look for the "Final result" banner in the end.')
michael@0 122 logging.warning('*' * 80)
michael@0 123 all_passed = []
michael@0 124 test_results = TestResults()
michael@0 125 tests_to_run = self.tests
michael@0 126 for retry in xrange(self.retries):
michael@0 127 logging.warning('Try %d of %d', retry + 1, self.retries)
michael@0 128 self._SetupSharding(self.tests)
michael@0 129 test_runners = self._MakeTestRunners(self.attached_devices)
michael@0 130 logging.warning('Starting...')
michael@0 131 pool = multiprocessing.Pool(len(self.attached_devices),
michael@0 132 SetTestsContainer,
michael@0 133 [PythonTestSharder.tests_container])
michael@0 134
michael@0 135 # List of TestResults objects from each test execution.
michael@0 136 try:
michael@0 137 results_lists = pool.map(_DefaultRunnable, test_runners)
michael@0 138 except Exception:
michael@0 139 logging.exception('Unable to run tests. Something with the '
michael@0 140 'PythonTestRunners has gone wrong.')
michael@0 141 raise FatalTestException('PythonTestRunners were unable to run tests.')
michael@0 142
michael@0 143 test_results = TestResults.FromTestResults(results_lists)
michael@0 144 # Accumulate passing results.
michael@0 145 all_passed += test_results.ok
michael@0 146 # If we have failed tests, map them to tests to retry.
michael@0 147 failed_tests = test_results.GetAllBroken()
michael@0 148 tests_to_run = self._GetTestsToRetry(self.tests,
michael@0 149 failed_tests)
michael@0 150
michael@0 151 # Bail out early if we have no more tests. This can happen if all tests
michael@0 152 # pass before we're out of retries, for example.
michael@0 153 if not tests_to_run:
michael@0 154 break
michael@0 155
michael@0 156 final_results = TestResults()
michael@0 157 # all_passed has accumulated all passing test results.
michael@0 158 # test_results will have the results from the most recent run, which could
michael@0 159 # include a variety of failure modes (unknown, crashed, failed, etc).
michael@0 160 final_results = test_results
michael@0 161 final_results.ok = all_passed
michael@0 162
michael@0 163 return final_results
michael@0 164
michael@0 165 def _MakeTestRunners(self, attached_devices):
michael@0 166 """Initialize and return a list of PythonTestRunners.
michael@0 167
michael@0 168 Args:
michael@0 169 attached_devices: list of device IDs attached to host.
michael@0 170
michael@0 171 Returns:
michael@0 172 A list of PythonTestRunners, one for each device.
michael@0 173 """
michael@0 174 test_runners = []
michael@0 175 for index, device in enumerate(attached_devices):
michael@0 176 logging.warning('*' * 80)
michael@0 177 logging.warning('Creating shard %d for %s', index, device)
michael@0 178 logging.warning('*' * 80)
michael@0 179 # Bind the PythonTestRunner to a device & shard index. Give it the
michael@0 180 # runnable which it will use to actually execute the tests.
michael@0 181 test_options = copy.deepcopy(self.options)
michael@0 182 test_options.ensure_value('device_id', device)
michael@0 183 test_options.ensure_value('shard_index', index)
michael@0 184 test_runner = PythonTestRunner(test_options)
michael@0 185 test_runners.append(test_runner)
michael@0 186
michael@0 187 return test_runners
michael@0 188
michael@0 189 def _GetTestsToRetry(self, available_tests, failed_tests):
michael@0 190 """Infers a list of tests to retry from failed tests and available tests.
michael@0 191
michael@0 192 Args:
michael@0 193 available_tests: a list of tests which subclass PythonTestBase.
michael@0 194 failed_tests: a list of SingleTestResults representing failed tests.
michael@0 195
michael@0 196 Returns:
michael@0 197 A list of test objects which correspond to test names found in
michael@0 198 failed_tests, or an empty list if there is no correspondence.
michael@0 199 """
michael@0 200 failed_test_names = map(lambda t: t.test_name, failed_tests)
michael@0 201 tests_to_retry = [t for t in available_tests
michael@0 202 if t.qualified_name in failed_test_names]
michael@0 203 return tests_to_retry

mercurial