1 # Copyright 2014 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 import fnmatch 6 import functools 7 import imp 8 import logging 9 10 from devil import base_error 11 from devil.android import device_errors 12 from pylib import valgrind_tools 13 from pylib.base import base_test_result 14 from pylib.base import test_run 15 from pylib.base import test_collection 16 17 18 def IncrementalInstall(device, apk_helper, installer_script): 19 """Performs an incremental install. 20 21 Args: 22 device: Device to install on. 23 apk_helper: ApkHelper instance for the _incremental.apk. 24 installer_script: Path to the installer script for the incremental apk. 25 """ 26 try: 27 install_wrapper = imp.load_source('install_wrapper', installer_script) 28 except IOError: 29 raise Exception('Incremental install script not found: %s\n' % 30 installer_script) 31 params = install_wrapper.GetInstallParameters() 32 33 from incremental_install import installer 34 installer.Install(device, apk_helper, split_globs=params['splits'], 35 native_libs=params['native_libs'], 36 dex_files=params['dex_files'], 37 permissions=None) # Auto-grant permissions from manifest. 38 39 40 def handle_shard_failures(f): 41 """A decorator that handles device failures for per-device functions. 42 43 Args: 44 f: the function being decorated. The function must take at least one 45 argument, and that argument must be the device. 46 """ 47 return handle_shard_failures_with(None)(f) 48 49 50 def handle_shard_failures_with(on_failure): 51 """A decorator that handles device failures for per-device functions. 52 53 This calls on_failure in the event of a failure. 54 55 Args: 56 f: the function being decorated. The function must take at least one 57 argument, and that argument must be the device. 58 on_failure: A binary function to call on failure. 59 """ 60 def decorator(f): 61 @functools.wraps(f) 62 def wrapper(dev, *args, **kwargs): 63 try: 64 return f(dev, *args, **kwargs) 65 except device_errors.CommandTimeoutError: 66 logging.exception('Shard timed out: %s(%s)', f.__name__, str(dev)) 67 except device_errors.DeviceUnreachableError: 68 logging.exception('Shard died: %s(%s)', f.__name__, str(dev)) 69 except base_error.BaseError: 70 logging.exception('Shard failed: %s(%s)', f.__name__, 71 str(dev)) 72 if on_failure: 73 on_failure(dev, f.__name__) 74 return None 75 76 return wrapper 77 78 return decorator 79 80 81 class LocalDeviceTestRun(test_run.TestRun): 82 83 def __init__(self, env, test_instance): 84 super(LocalDeviceTestRun, self).__init__(env, test_instance) 85 self._tools = {} 86 87 #override 88 def RunTests(self): 89 tests = self._GetTests() 90 91 @handle_shard_failures 92 def run_tests_on_device(dev, tests, results): 93 for test in tests: 94 result = None 95 try: 96 result = self._RunTest(dev, test) 97 if isinstance(result, base_test_result.BaseTestResult): 98 results.AddResult(result) 99 elif isinstance(result, list): 100 results.AddResults(result) 101 else: 102 raise Exception( 103 'Unexpected result type: %s' % type(result).__name__) 104 except: 105 if isinstance(tests, test_collection.TestCollection): 106 tests.add(test) 107 raise 108 finally: 109 if isinstance(tests, test_collection.TestCollection): 110 tests.test_completed() 111 112 113 logging.info('Finished running tests on this device.') 114 115 tries = 0 116 results = base_test_result.TestRunResults() 117 all_fail_results = {} 118 while tries < self._env.max_tries and tests: 119 logging.info('STARTING TRY #%d/%d', tries + 1, self._env.max_tries) 120 logging.info('Will run %d tests on %d devices: %s', 121 len(tests), len(self._env.devices), 122 ', '.join(str(d) for d in self._env.devices)) 123 for t in tests: 124 logging.debug(' %s', t) 125 126 try_results = base_test_result.TestRunResults() 127 if self._ShouldShard(): 128 tc = test_collection.TestCollection(self._CreateShards(tests)) 129 self._env.parallel_devices.pMap( 130 run_tests_on_device, tc, try_results).pGet(None) 131 else: 132 self._env.parallel_devices.pMap( 133 run_tests_on_device, tests, try_results).pGet(None) 134 135 for result in try_results.GetAll(): 136 if result.GetType() in (base_test_result.ResultType.PASS, 137 base_test_result.ResultType.SKIP): 138 results.AddResult(result) 139 else: 140 all_fail_results[result.GetName()] = result 141 142 results_names = set(r.GetName() for r in results.GetAll()) 143 144 def has_test_result(name): 145 # When specifying a test filter, names can contain trailing wildcards. 146 # See local_device_gtest_run._ExtractTestsFromFilter() 147 if name.endswith('*'): 148 return any(fnmatch.fnmatch(n, name) for n in results_names) 149 return name in results_names 150 151 tests = [t for t in tests if not has_test_result(self._GetTestName(t))] 152 tries += 1 153 logging.info('FINISHED TRY #%d/%d', tries, self._env.max_tries) 154 if tests: 155 logging.info('%d failed tests remain.', len(tests)) 156 else: 157 logging.info('All tests completed.') 158 159 all_unknown_test_names = set(self._GetTestName(t) for t in tests) 160 all_failed_test_names = set(all_fail_results.iterkeys()) 161 162 unknown_tests = all_unknown_test_names.difference(all_failed_test_names) 163 failed_tests = all_failed_test_names.intersection(all_unknown_test_names) 164 165 if unknown_tests: 166 results.AddResults( 167 base_test_result.BaseTestResult( 168 u, base_test_result.ResultType.UNKNOWN) 169 for u in unknown_tests) 170 if failed_tests: 171 results.AddResults(all_fail_results[f] for f in failed_tests) 172 173 return results 174 175 def GetTool(self, device): 176 if not str(device) in self._tools: 177 self._tools[str(device)] = valgrind_tools.CreateTool( 178 self._env.tool, device) 179 return self._tools[str(device)] 180 181 def _CreateShards(self, tests): 182 raise NotImplementedError 183 184 # pylint: disable=no-self-use 185 def _GetTestName(self, test): 186 return test 187 188 def _GetTests(self): 189 raise NotImplementedError 190 191 def _RunTest(self, device, test): 192 raise NotImplementedError 193 194 def _ShouldShard(self): 195 raise NotImplementedError 196