Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(964)

Side by Side Diff: build/android/pylib/host_driven/python_test_sharder.py

Issue 18444004: Makes host driven tests use the common sharder. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fixes calls to Dispatch and the RunTests functions Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Takes care of sharding the python-drive tests in multiple devices.""" 5 """Takes care of sharding the python-drive tests in multiple devices."""
6 6
7 # TODO(gkanwar): Rename to python_test_runner.py
8
7 import copy 9 import copy
8 import logging 10 import logging
9 import multiprocessing 11 import multiprocessing
10 12
11 from pylib.base import base_test_result 13 from pylib.base import base_test_result
14 from pylib.base import base_test_runner
12 from pylib.base import sharded_tests_queue 15 from pylib.base import sharded_tests_queue
13 from pylib.forwarder import Forwarder 16 from pylib.instrumentation import test_result
14 17
15 from python_test_caller import CallPythonTest 18 import python_test_base
16 19
17 20
18 def SetTestsContainer(tests_container): 21 class PythonExceptionTestResult(test_result.InstrumentationTestResult):
19 """Sets PythonTestSharder as a top-level field. 22 """Helper class for creating a test result from python exception."""
20 23
21 PythonTestSharder uses multiprocessing.Pool, which creates a pool of 24 def __init__(self, test_name, start_date_ms, exc_info):
22 processes. This is used to initialize each worker in the pool, ensuring that 25 """Constructs an PythonExceptionTestResult object.
23 each worker has access to this shared pool of tests.
24 26
25 The multiprocessing module requires that this be a top-level method. 27 Args:
28 test_name: name of the test which raised an exception.
29 start_date_ms: the starting time for the test.
30 exc_info: exception info, ostensibly from sys.exc_info().
31 """
32 exc_type, exc_value, exc_traceback = exc_info
33 trace_info = ''.join(traceback.format_exception(exc_type, exc_value,
34 exc_traceback))
35 log_msg = 'Exception:\n' + trace_info
36 duration_ms = (int(time.time()) * 1000) - start_date_ms
26 37
27 Args: 38 super(PythonExceptionTestResult, self).__init__(
28 tests_container: the container for all the tests. 39 'PythonWrapper#' + test_name,
29 """ 40 base_test_result.ResultType.FAIL,
30 PythonTestSharder.tests_container = tests_container 41 start_date_ms,
42 duration_ms,
43 log=str(exc_type) + ' ' + log_msg)
31 44
32 45
33 def _DefaultRunnable(test_runner): 46 class PythonTestRunner(base_test_runner.BaseTestRunner):
34 """A default runnable for a PythonTestRunner.
35
36 Args:
37 test_runner: A PythonTestRunner which will run tests.
38
39 Returns:
40 The test results.
41 """
42 return test_runner.RunTests()
43
44
45 class PythonTestRunner(object):
46 """Thin wrapper around a list of PythonTestBase instances. 47 """Thin wrapper around a list of PythonTestBase instances.
47 48
48 This is meant to be a long-lived object which can run multiple Python tests 49 This is meant to be a long-lived object which can run multiple Python tests
49 within its lifetime. Tests will receive the device_id and shard_index. 50 within its lifetime. Tests will receive the device_id and shard_index.
50 51
51 The shard index affords the ability to create unique port numbers (e.g. 52 The shard index affords the ability to create unique port numbers (e.g.
52 DEFAULT_PORT + shard_index) if the test so wishes. 53 DEFAULT_PORT + shard_index) if the test so wishes.
53 """ 54 """
54 55
55 def __init__(self, options): 56 #override
56 """Constructor. 57 def __init__(self, options, device, shard_index):
58 """Create a new PythonTestRunner
59
60 This is a thin wrapper around the instrumentation TestRunner, since this
61 test runner essentially does the same things as the instrumentation test
62 runner with slight changes.
57 63
58 Args: 64 Args:
59 options: Options to use for setting up tests. 65 options: An optparse.Options object requiring the following attributes
66 (list pulled from pylib/instrumentation/test_runner.py):
67 - build_type: 'Release' or 'Debug'.
68 - install_apk: Re-installs the apk if opted.
69 - save_perf_json: Whether or not to save the JSON file from UI perf
70 tests.
71 - screenshot_failures: Take a screenshot for a test failure
72 - tool: Name of the Valgrind tool.
73 - wait_for_debugger: blocks until the debugger is connected.
74 - disable_assertions: Whether to disable java assertions on the device.
75 - push_deps: If True, push all dependencies to the device.
76 device: Attached android device.
77 shard_index: Shard index.
78 test_package: A TestPackage object, or None if no test package needs to be
79 installed.
60 """ 80 """
81 super(PythonTestRunner, self).__init__(
82 device, options.tool, options.build_type, options.push_deps)
61 self.options = options 83 self.options = options
84 self.options.shard_index = shard_index
85 self.options.device_id = device
62 86
63 def RunTests(self): 87 #override
64 """Runs tests from the shared pool of tests, aggregating results. 88 def RunTest(self, test):
89 """Sets up and runs a test case.
90
91 Args:
92 test: an object which is ostensibly a subclass of PythonTestBase.
65 93
66 Returns: 94 Returns:
67 A list of test results for all of the tests which this runner executed. 95 A TestRunResults object which contains any results produced by the test
96 or, in the case of a Python exception, the Python exception info, and
97 which tests to retry or None.
68 """ 98 """
69 tests = PythonTestSharder.tests_container
70 99
71 results = base_test_result.TestRunResults() 100 assert(isinstance(test, python_test_base.PythonTestBase))
72 for t in tests: 101 test.SetUp(self.options)
73 results.AddTestRunResults(CallPythonTest(t, self.options)) 102 results = test.Run()
74 return results 103 test.TearDown()
75 104
76 105 if not results.DidRunPass():
77 class PythonTestSharder(object): 106 return results, test
78 """Runs Python tests in parallel on multiple devices. 107 else:
79 108 return results, None
80 This is lifted more or less wholesale from BaseTestRunner.
81
82 Under the covers, it creates a pool of long-lived PythonTestRunners, which
83 execute tests from the pool of tests.
84
85 Args:
86 attached_devices: a list of device IDs attached to the host.
87 available_tests: a list of tests to run which subclass PythonTestBase.
88 options: Options to use for setting up tests.
89
90 Returns:
91 An aggregated list of test results.
92 """
93 tests_container = None
94
95 def __init__(self, attached_devices, available_tests, options):
96 self.options = options
97 self.attached_devices = attached_devices
98 self.retries = options.shard_retries
99 self.tests = available_tests
100
101 def _SetupSharding(self, tests):
102 """Creates the shared pool of tests and makes it available to test runners.
103
104 Args:
105 tests: the list of tests which will be consumed by workers.
106 """
107 SetTestsContainer(sharded_tests_queue.ShardedTestsQueue(
108 len(self.attached_devices), tests))
109
110 def RunShardedTests(self):
111 """Runs tests in parallel using a pool of workers.
112
113 Returns:
114 A list of test results aggregated from all test runs.
115 """
116 logging.warning('*' * 80)
117 logging.warning('Sharding in ' + str(len(self.attached_devices)) +
118 ' devices.')
119 logging.warning('Note that the output is not synchronized.')
120 logging.warning('Look for the "Final result" banner in the end.')
121 logging.warning('*' * 80)
122 final_results = base_test_result.TestRunResults()
123 tests_to_run = self.tests
124
125 Forwarder.KillHost()
126
127 for retry in xrange(self.retries):
128 logging.warning('Try %d of %d', retry + 1, self.retries)
129 self._SetupSharding(self.tests)
130 test_runners = self._MakeTestRunners(self.attached_devices)
131 logging.warning('Starting...')
132 pool = multiprocessing.Pool(len(self.attached_devices),
133 SetTestsContainer,
134 [PythonTestSharder.tests_container])
135
136 # List of TestRunResults objects from each test execution.
137 try:
138 results_lists = pool.map(_DefaultRunnable, test_runners)
139 except Exception:
140 logging.exception('Unable to run tests. Something with the '
141 'PythonTestRunners has gone wrong.')
142 raise Exception('PythonTestRunners were unable to run tests.')
143
144 test_results = base_test_result.TestRunResults()
145 for t in results_lists:
146 test_results.AddTestRunResults(t)
147 # Accumulate passing results.
148 final_results.AddResults(test_results.GetPass())
149 # If we have failed tests, map them to tests to retry.
150 failed_tests = [t.GetName() for t in test_results.GetNotPass()]
151 tests_to_run = self._GetTestsToRetry(self.tests, failed_tests)
152
153 # Bail out early if we have no more tests. This can happen if all tests
154 # pass before we're out of retries, for example.
155 if not tests_to_run:
156 break
157
158 # all_passed has accumulated all passing test results.
159 # test_results will have the results from the most recent run, which could
160 # include a variety of failure modes (unknown, crashed, failed, etc).
161 test_results.AddResults(final_results.GetPass())
162 final_results = test_results
163
164 return final_results
165
166 def _MakeTestRunners(self, attached_devices):
167 """Initialize and return a list of PythonTestRunners.
168
169 Args:
170 attached_devices: list of device IDs attached to host.
171
172 Returns:
173 A list of PythonTestRunners, one for each device.
174 """
175 test_runners = []
176 for index, device in enumerate(attached_devices):
177 logging.warning('*' * 80)
178 logging.warning('Creating shard %d for %s', index, device)
179 logging.warning('*' * 80)
180 # Bind the PythonTestRunner to a device & shard index. Give it the
181 # runnable which it will use to actually execute the tests.
182 test_options = copy.deepcopy(self.options)
183 test_options.ensure_value('device_id', device)
184 test_options.ensure_value('shard_index', index)
185 test_runner = PythonTestRunner(test_options)
186 test_runners.append(test_runner)
187
188 return test_runners
189
190 def _GetTestsToRetry(self, available_tests, failed_test_names):
191 """Infers a list of tests to retry from failed tests and available tests.
192
193 Args:
194 available_tests: a list of tests which subclass PythonTestBase.
195 failed_test_names: a list of failed test names.
196
197 Returns:
198 A list of test objects which correspond to test names found in
199 failed_test_names, or an empty list if there is no correspondence.
200 """
201 tests_to_retry = [t for t in available_tests
202 if t.qualified_name in failed_test_names]
203 return tests_to_retry
OLDNEW
« no previous file with comments | « build/android/pylib/host_driven/python_test_caller.py ('k') | build/android/pylib/host_driven/run_python_tests.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698