Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 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 """Run specific test on specific environment.""" | 5 """Run specific test on specific environment.""" |
| 6 | 6 |
| 7 import json | 7 import json |
| 8 import logging | 8 import logging |
| 9 import os | 9 import os |
| 10 import random | |
| 10 import re | 11 import re |
| 11 import shutil | 12 import shutil |
| 12 import string | 13 import string |
| 13 import tempfile | 14 import tempfile |
| 14 import time | 15 import time |
| 15 import zipfile | 16 import zipfile |
| 16 | 17 |
| 18 from devil.utils import parallelizer | |
| 17 from devil.utils import zip_utils | 19 from devil.utils import zip_utils |
| 18 from pylib.base import base_test_result | 20 from pylib.base import base_test_result |
| 19 from pylib.base import test_run | 21 from pylib.base import test_run |
| 20 from pylib.remote.device import appurify_constants | |
| 21 from pylib.remote.device import appurify_sanitized | 22 from pylib.remote.device import appurify_sanitized |
| 22 from pylib.remote.device import remote_device_helper | 23 from pylib.remote.device import remote_device_helper |
| 23 | 24 |
| 24 _DEVICE_OFFLINE_RE = re.compile('error: device not found') | 25 _DEVICE_OFFLINE_RE = re.compile('error: device not found') |
| 25 _LONG_MSG_RE = re.compile('longMsg=(.*)$') | 26 _LONG_MSG_RE = re.compile('longMsg=(.*)$') |
| 26 _SHORT_MSG_RE = re.compile('shortMsg=(.*)$') | 27 _SHORT_MSG_RE = re.compile('shortMsg=(.*)$') |
| 27 | 28 |
| 28 class RemoteDeviceTestRun(test_run.TestRun): | 29 class RemoteDeviceTestRun(test_run.TestRun): |
| 29 """Run tests on a remote device.""" | 30 """Run tests on a remote device.""" |
| 30 | 31 |
| 31 _TEST_RUN_KEY = 'test_run' | 32 WAIT_TIME = 10 |
| 32 _TEST_RUN_ID_KEY = 'test_run_id' | |
| 33 | |
| 34 WAIT_TIME = 5 | |
| 35 COMPLETE = 'complete' | 33 COMPLETE = 'complete' |
| 36 HEARTBEAT_INTERVAL = 300 | 34 HEARTBEAT_INTERVAL = 300 |
| 37 | 35 |
| 36 _TEST_RUN_KEY = 'test_run' | |
| 37 _TEST_RUN_IDS_KEY = 'test_run_ids' | |
| 38 | |
| 38 def __init__(self, env, test_instance): | 39 def __init__(self, env, test_instance): |
| 39 """Constructor. | 40 """Constructor. |
| 40 | 41 |
| 41 Args: | 42 Args: |
| 42 env: Environment the tests will run in. | 43 env: Environment the tests will run in. |
| 43 test_instance: The test that will be run. | 44 test_instance: The test that will be run. |
| 44 """ | 45 """ |
| 45 super(RemoteDeviceTestRun, self).__init__(env, test_instance) | 46 super(RemoteDeviceTestRun, self).__init__(env, test_instance) |
| 47 self._app_id = None | |
| 48 self._appurify_configs = None | |
| 49 self._base_tempfile_dir = '' | |
| 50 self._device_ids = None | |
| 46 self._env = env | 51 self._env = env |
| 47 self._test_instance = test_instance | 52 self._test_instance = test_instance |
| 48 self._app_id = '' | 53 self._test_package = None |
| 49 self._test_id = '' | 54 self._shard_framework_configs = None |
| 50 self._results = '' | 55 self._test_run_ids = [] |
| 51 self._test_run_id = '' | 56 |
| 52 self._results_temp_dir = None | 57 # pylint: disable=no-self-use |
| 58 def _GetAdditionalApks(self): | |
| 59 return NotImplementedError | |
| 60 | |
| 61 def _GetAppPath(self): | |
| 62 raise NotImplementedError | |
| 63 | |
| 64 def _GetTestPath(self): | |
| 65 raise NotImplementedError | |
| 66 | |
| 67 def _GetTestRunnerName(self): | |
| 68 raise NotImplementedError | |
| 69 | |
| 70 # pylint: disable=no-self-use | |
| 71 def _GetTestFramework(self): | |
| 72 raise NotImplementedError | |
| 73 | |
| 74 # pylint: disable=no-self-use | |
| 75 def _GetFrameworkConfigs(self): | |
| 76 return NotImplementedError | |
| 77 | |
| 78 # pylint: disable=unused-argument | |
| 79 def _GetShardEnvironmentVars(self, num_shards): | |
| 80 return NotImplementedError | |
| 53 | 81 |
| 54 #override | 82 #override |
| 55 def SetUp(self): | 83 def SetUp(self): |
| 56 """Set up a test run.""" | 84 """Set up a test run.""" |
| 85 self._base_tempfile_dir = tempfile.mkdtemp() | |
| 57 if self._env.trigger: | 86 if self._env.trigger: |
| 58 self._TriggerSetUp() | 87 self._TriggerSetUp() |
| 59 elif self._env.collect: | 88 elif self._env.collect: |
| 60 assert isinstance(self._env.collect, basestring), ( | 89 assert isinstance(self._env.collect, basestring), ( |
| 61 'File for storing test_run_id must be a string.') | 90 'File for storing test_run_id must be a string.') |
| 62 with open(self._env.collect, 'r') as persisted_data_file: | 91 with open(self._env.collect, 'r') as persisted_data_file: |
| 63 persisted_data = json.loads(persisted_data_file.read()) | 92 persisted_data = json.loads(persisted_data_file.read()) |
| 64 self._env.LoadFrom(persisted_data) | 93 self._LoadFrom(persisted_data) |
| 65 self.LoadFrom(persisted_data) | 94 |
| 95 def _ShouldShard(self): | |
| 96 raise NotImplementedError | |
| 66 | 97 |
| 67 def _TriggerSetUp(self): | 98 def _TriggerSetUp(self): |
| 68 """Set up the triggering of a test run.""" | 99 """Set up the triggering of a test run.""" |
| 69 raise NotImplementedError | 100 self._app_id = self._UploadAppToDevice(self._GetAppPath()) |
| 70 | 101 |
| 71 #override | 102 if self._ShouldShard(): |
| 103 num_shards = min(self._env.num_shards, len(self._env.device_ids)) | |
| 104 # TODO(mikecase): Change to always run with the number of shards passed. | |
| 105 # Will need some shards to wait for new devices to become available. | |
| 106 if num_shards < self._env.num_shards: | |
| 107 logging.warning( | |
| 108 'Requested to shard across %s devices, only %s devices availible.', | |
| 109 self._env.num_shards, num_shards) | |
| 110 else: | |
| 111 num_shards = 1 | |
| 112 logging.critical('Sharding test run across %s device(s).', num_shards) | |
|
jbudorick
2015/10/26 22:23:30
This is info, not critical.
mikecase (-- gone --)
2015/10/27 01:27:43
Done.
| |
| 113 self._device_ids = random.sample(self._env.device_ids, num_shards) | |
| 114 | |
| 115 shard_env_vars = (self._GetShardEnvironmentVars(num_shards) | |
|
jbudorick
2015/10/26 22:23:30
I don't think this is structured properly. Most of
mikecase (-- gone --)
2015/10/27 01:27:43
Yeah, but it even gets messier if you want some of
| |
| 116 or [{} for _ in range(num_shards)]) | |
|
jbudorick
2015/10/26 22:23:30
:/ this is ugly. Maybe [{}] * num_shards
also: wh
mikecase (-- gone --)
2015/10/27 01:27:43
Done.
| |
| 117 framework_configs = self._GetFrameworkConfigs() or {} | |
| 118 | |
| 119 if self._GetTestPath(): | |
| 120 self._test_package, test_package_configs = self._PackageTest( | |
| 121 test_path=self._GetTestPath(), | |
| 122 extra_apks=self._GetAdditionalApks()) | |
| 123 else: | |
| 124 test_package_configs = {} | |
| 125 framework_configs.update(test_package_configs) | |
| 126 | |
| 127 self._appurify_configs = { | |
| 128 'network': self._env.network_config, | |
| 129 'pcap': self._env.pcap_config, | |
| 130 'profiler': self._env.profiler_config, | |
| 131 'videocapture': self._env.videocapture_config, | |
| 132 } | |
| 133 | |
| 134 self._shard_framework_configs = [] | |
| 135 for env_vars in shard_env_vars: | |
| 136 shard_framework_config = dict(framework_configs) | |
| 137 shard_framework_config['environment_vars'] = ','.join( | |
| 138 '%s=%s' % (k, v) for k, v in env_vars.iteritems()) | |
| 139 self._shard_framework_configs.append(shard_framework_config) | |
| 140 | |
| 141 def _Trigger(self, device_id, test_id): | |
| 142 with appurify_sanitized.SanitizeLogging( | |
| 143 verbose_count=self._env.verbose_count, | |
| 144 level=logging.WARNING): | |
| 145 test_start_response = appurify_sanitized.api.tests_run( | |
| 146 access_token=self._env.token, | |
| 147 device_type_id=device_id, | |
| 148 app_id=self._app_id, | |
| 149 test_id=test_id) | |
| 150 remote_device_helper.TestHttpResponse( | |
| 151 response=test_start_response, | |
| 152 error_msg='Unable to run test.') | |
| 153 return test_start_response.json()['response']['test_run_id'] | |
| 154 | |
| 72 def RunTests(self): | 155 def RunTests(self): |
| 73 """Run the test.""" | 156 """Run the test.""" |
| 157 | |
| 158 def trigger(trigger_args): | |
|
jbudorick
2015/10/26 22:23:30
these should be defined in their respective if blo
| |
| 159 device_id = trigger_args[0] | |
| 160 configs = trigger_args[1] | |
| 161 test_id = self._UploadTestToDevice() | |
| 162 self._UploadTestConfigToDevice( | |
| 163 test_id=test_id, | |
| 164 framework_configs=configs, | |
| 165 appurify_configs=self._appurify_configs) | |
| 166 test_run_id = self._Trigger( | |
| 167 device_id=device_id, | |
| 168 test_id=test_id) | |
| 169 self._test_run_ids.append(test_run_id) | |
| 170 | |
| 171 def collect(test_run_id, parsed_results): | |
| 172 download_results_file = tempfile.NamedTemporaryFile( | |
| 173 suffix='.zip', | |
| 174 dir=self._base_tempfile_dir, | |
| 175 delete=False) | |
| 176 test_output = self._Collect(test_run_id=test_run_id) | |
| 177 self._DownloadTestResults(test_output, download_results_file.name) | |
| 178 parsed_results.AddTestRunResults(self._ParseTestResults( | |
| 179 test_output=test_output, | |
| 180 results_zip=download_results_file.name)) | |
| 181 | |
| 74 if self._env.trigger: | 182 if self._env.trigger: |
| 75 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, | 183 parallelizer.SyncParallelizer( |
| 76 logging.WARNING): | 184 zip(self._device_ids, self._shard_framework_configs)).pMap(trigger) |
|
jbudorick
2015/10/26 22:23:30
O_O
I... I don't know how I feel about this.
mikecase (-- gone --)
2015/10/27 01:27:43
Ack
| |
| 77 test_start_res = appurify_sanitized.api.tests_run( | |
| 78 self._env.token, self._env.device_type_id, self._app_id, | |
| 79 self._test_id) | |
| 80 remote_device_helper.TestHttpResponse( | |
| 81 test_start_res, 'Unable to run test.') | |
| 82 self._test_run_id = test_start_res.json()['response']['test_run_id'] | |
| 83 logging.info('Test run id: %s', self._test_run_id) | |
| 84 | |
| 85 if self._env.collect: | 185 if self._env.collect: |
| 86 current_status = '' | 186 parsed_results = base_test_result.TestRunResults() |
| 87 timeout_counter = 0 | 187 parallelizer.SyncParallelizer( |
| 88 heartbeat_counter = 0 | 188 self._test_run_ids).pMap(collect, parsed_results) |
|
jbudorick
2015/10/26 22:23:30
or, unsurprisingly, this.
mikecase (-- gone --)
2015/10/27 01:27:43
Ack
| |
| 89 while self._GetTestStatus(self._test_run_id) != self.COMPLETE: | 189 return parsed_results |
| 90 if self._results['detailed_status'] != current_status: | |
| 91 logging.info('Test status: %s', self._results['detailed_status']) | |
| 92 current_status = self._results['detailed_status'] | |
| 93 timeout_counter = 0 | |
| 94 heartbeat_counter = 0 | |
| 95 if heartbeat_counter > self.HEARTBEAT_INTERVAL: | |
| 96 logging.info('Test status: %s', self._results['detailed_status']) | |
| 97 heartbeat_counter = 0 | |
| 98 | |
| 99 timeout = self._env.timeouts.get( | |
| 100 current_status, self._env.timeouts['unknown']) | |
| 101 if timeout_counter > timeout: | |
| 102 raise remote_device_helper.RemoteDeviceError( | |
| 103 'Timeout while in %s state for %s seconds' | |
| 104 % (current_status, timeout), | |
| 105 is_infra_error=True) | |
| 106 time.sleep(self.WAIT_TIME) | |
| 107 timeout_counter += self.WAIT_TIME | |
| 108 heartbeat_counter += self.WAIT_TIME | |
| 109 self._DownloadTestResults(self._env.results_path) | |
| 110 | |
| 111 if self._results['results']['exception']: | |
| 112 raise remote_device_helper.RemoteDeviceError( | |
| 113 self._results['results']['exception'], is_infra_error=True) | |
| 114 | |
| 115 return self._ParseTestResults() | |
| 116 | 190 |
| 117 #override | 191 #override |
| 118 def TearDown(self): | 192 def TearDown(self): |
| 119 """Tear down the test run.""" | 193 """Tear down the test run.""" |
| 194 if self._base_tempfile_dir: | |
| 195 shutil.rmtree(self._base_tempfile_dir) | |
| 196 | |
| 120 if self._env.collect: | 197 if self._env.collect: |
| 121 self._CollectTearDown() | 198 self._CollectTearDown() |
| 122 elif self._env.trigger: | 199 elif self._env.trigger: |
| 123 assert isinstance(self._env.trigger, basestring), ( | 200 assert isinstance(self._env.trigger, basestring), ( |
| 124 'File for storing test_run_id must be a string.') | 201 'File for storing test_run_id must be a string.') |
| 125 with open(self._env.trigger, 'w') as persisted_data_file: | 202 with open(self._env.trigger, 'w') as persisted_data_file: |
| 126 persisted_data = {} | 203 persisted_data = {} |
| 127 self.DumpTo(persisted_data) | 204 self._DumpTo(persisted_data) |
| 128 self._env.DumpTo(persisted_data) | |
| 129 persisted_data_file.write(json.dumps(persisted_data)) | 205 persisted_data_file.write(json.dumps(persisted_data)) |
| 130 | 206 |
| 207 def _Collect(self, test_run_id): | |
| 208 current_status = '' | |
| 209 timeout_counter = 0 | |
| 210 heartbeat_counter = 0 | |
| 211 results = self._CheckTestResults(test_run_id) | |
| 212 while results['status'] != self.COMPLETE: | |
| 213 if results['detailed_status'] != current_status: | |
| 214 logging.info('Test status: %s', results['detailed_status']) | |
| 215 current_status = results['detailed_status'] | |
| 216 timeout_counter = 0 | |
| 217 heartbeat_counter = 0 | |
| 218 if heartbeat_counter > self.HEARTBEAT_INTERVAL: | |
| 219 logging.info('Test status: %s', results['detailed_status']) | |
| 220 heartbeat_counter = 0 | |
| 221 | |
| 222 timeout = self._env.timeouts.get( | |
| 223 current_status, self._env.timeouts['unknown']) | |
| 224 if timeout_counter > timeout: | |
| 225 raise remote_device_helper.RemoteDeviceError( | |
| 226 message='Timeout while in %s state for %s seconds' | |
| 227 % (current_status, timeout), | |
| 228 is_infra_error=True) | |
| 229 time.sleep(self.WAIT_TIME) | |
| 230 timeout_counter += self.WAIT_TIME | |
| 231 heartbeat_counter += self.WAIT_TIME | |
| 232 results = self._CheckTestResults(test_run_id) | |
| 233 if results['results']['exception']: | |
| 234 raise remote_device_helper.RemoteDeviceError( | |
| 235 message=results['results']['exception'], | |
| 236 is_infra_error=True) | |
| 237 return results | |
| 238 | |
| 131 def _CollectTearDown(self): | 239 def _CollectTearDown(self): |
| 132 if self._GetTestStatus(self._test_run_id) != self.COMPLETE: | 240 def abort_test_run(test_run_id): |
|
rnephew (Reviews Here)
2015/10/26 20:42:18
Is there a reason you can't just pass self._AbortT
mikecase (-- gone --)
2015/10/27 01:27:43
Hmmmmmmmmmm, it seems to work your way. I assumed
| |
| 133 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, | 241 self._AbortTestRun(test_run_id) |
| 134 logging.WARNING): | 242 if self._test_run_ids: |
| 135 test_abort_res = appurify_sanitized.api.tests_abort( | 243 parallelizer.SyncParallelizer(self._test_run_ids).pMap(abort_test_run) |
| 136 self._env.token, self._test_run_id, reason='Test runner exiting.') | |
| 137 remote_device_helper.TestHttpResponse(test_abort_res, | |
| 138 'Unable to abort test.') | |
| 139 if self._results_temp_dir: | |
| 140 shutil.rmtree(self._results_temp_dir) | |
| 141 | 244 |
| 142 def __enter__(self): | 245 def __enter__(self): |
| 143 """Set up the test run when used as a context manager.""" | 246 """Set up the test run when used as a context manager.""" |
| 144 self.SetUp() | 247 self.SetUp() |
| 145 return self | 248 return self |
| 146 | 249 |
| 147 def __exit__(self, exc_type, exc_val, exc_tb): | 250 def __exit__(self, exc_type, exc_val, exc_tb): |
| 148 """Tear down the test run when used as a context manager.""" | 251 """Tear down the test run when used as a context manager.""" |
| 149 self.TearDown() | 252 self.TearDown() |
| 150 | 253 |
| 151 def DumpTo(self, persisted_data): | 254 def _DumpTo(self, persisted_data): |
| 152 test_run_data = { | 255 """Dump test run information to dict.""" |
| 153 self._TEST_RUN_ID_KEY: self._test_run_id, | 256 test_run_data = {self._TEST_RUN_IDS_KEY: self._test_run_ids} |
| 154 } | |
| 155 persisted_data[self._TEST_RUN_KEY] = test_run_data | 257 persisted_data[self._TEST_RUN_KEY] = test_run_data |
| 156 | 258 |
| 157 def LoadFrom(self, persisted_data): | 259 def _LoadFrom(self, persisted_data): |
| 260 """Load test run information.""" | |
| 158 test_run_data = persisted_data[self._TEST_RUN_KEY] | 261 test_run_data = persisted_data[self._TEST_RUN_KEY] |
| 159 self._test_run_id = test_run_data[self._TEST_RUN_ID_KEY] | 262 self._test_run_ids = test_run_data[self._TEST_RUN_IDS_KEY] |
| 160 | 263 |
| 161 def _ParseTestResults(self): | 264 def _ParseTestResults(self, test_output, results_zip): |
| 162 raise NotImplementedError | 265 raise NotImplementedError |
| 163 | 266 |
| 164 def _GetTestByName(self, test_name): | 267 def _DownloadTestResults(self, results, download_path): |
| 165 """Gets test_id for specific test. | |
| 166 | |
| 167 Args: | |
| 168 test_name: Test to find the ID of. | |
| 169 """ | |
| 170 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, | |
| 171 logging.WARNING): | |
| 172 test_list_res = appurify_sanitized.api.tests_list(self._env.token) | |
| 173 remote_device_helper.TestHttpResponse(test_list_res, | |
| 174 'Unable to get tests list.') | |
| 175 for test in test_list_res.json()['response']: | |
| 176 if test['test_type'] == test_name: | |
| 177 return test['test_id'] | |
| 178 raise remote_device_helper.RemoteDeviceError( | |
| 179 'No test found with name %s' % (test_name)) | |
| 180 | |
| 181 def _DownloadTestResults(self, results_path): | |
| 182 """Download the test results from remote device service. | 268 """Download the test results from remote device service. |
| 183 | 269 |
| 184 Downloads results in temporary location, and then copys results | |
| 185 to results_path if results_path is not set to None. | |
| 186 | |
| 187 Args: | |
| 188 results_path: Path to download appurify results zipfile. | |
| 189 | |
| 190 Returns: | 270 Returns: |
| 191 Path to downloaded file. | 271 Path to downloaded file. |
| 192 """ | 272 """ |
| 273 logging.info('Downloading results to %s.', download_path) | |
| 274 with appurify_sanitized.SanitizeLogging( | |
| 275 verbose_count=self._env.verbose_count, | |
| 276 level=logging.WARNING): | |
| 277 appurify_sanitized.utils.wget( | |
| 278 url=results['results']['url'], | |
| 279 path=download_path) | |
| 193 | 280 |
| 194 if self._results_temp_dir is None: | 281 def _CheckTestResults(self, test_run_id): |
| 195 self._results_temp_dir = tempfile.mkdtemp() | 282 """Checks the state of the test. |
| 196 logging.info('Downloading results to %s.', self._results_temp_dir) | |
| 197 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, | |
| 198 logging.WARNING): | |
| 199 appurify_sanitized.utils.wget(self._results['results']['url'], | |
| 200 self._results_temp_dir + '/results') | |
| 201 if results_path: | |
| 202 logging.info('Copying results to %s', results_path) | |
| 203 if not os.path.exists(os.path.dirname(results_path)): | |
| 204 os.makedirs(os.path.dirname(results_path)) | |
| 205 shutil.copy(self._results_temp_dir + '/results', results_path) | |
| 206 return self._results_temp_dir + '/results' | |
| 207 | |
| 208 def _GetTestStatus(self, test_run_id): | |
| 209 """Checks the state of the test, and sets self._results | |
| 210 | 283 |
| 211 Args: | 284 Args: |
| 212 test_run_id: Id of test on on remote service. | 285 test_run_id: Id of test on on remote service. |
| 213 """ | 286 """ |
| 287 with appurify_sanitized.SanitizeLogging( | |
| 288 verbose_count=self._env.verbose_count, | |
| 289 level=logging.WARNING): | |
| 290 test_check_response = appurify_sanitized.api.tests_check_result( | |
| 291 access_token=self._env.token, | |
| 292 test_run_id=test_run_id) | |
| 293 remote_device_helper.TestHttpResponse( | |
| 294 response=test_check_response, | |
| 295 error_msg='Unable to get test status.') | |
| 296 return test_check_response.json()['response'] | |
| 214 | 297 |
| 215 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, | 298 def _PackageTest(self, test_path, extra_apks): |
| 216 logging.WARNING): | 299 """Packages the test and dependencies into a zip. |
| 217 test_check_res = appurify_sanitized.api.tests_check_result( | |
| 218 self._env.token, test_run_id) | |
| 219 remote_device_helper.TestHttpResponse(test_check_res, | |
| 220 'Unable to get test status.') | |
| 221 self._results = test_check_res.json()['response'] | |
| 222 return self._results['status'] | |
| 223 | 300 |
| 224 def _AmInstrumentTestSetup(self, app_path, test_path, runner_package, | 301 Returns: |
| 225 environment_variables, extra_apks=None): | 302 A tuple of the path to the packaged test and a dictionary containing |
| 226 config = {'runner': runner_package} | 303 configs about how the test was packaged. |
| 227 if environment_variables: | 304 """ |
| 228 config['environment_vars'] = ','.join( | 305 package_configs = {} |
| 229 '%s=%s' % (k, v) for k, v in environment_variables.iteritems()) | |
| 230 | |
| 231 self._app_id = self._UploadAppToDevice(app_path) | |
| 232 | |
| 233 data_deps = self._test_instance.GetDataDependencies() | 306 data_deps = self._test_instance.GetDataDependencies() |
| 234 if data_deps: | 307 if data_deps: |
| 235 with tempfile.NamedTemporaryFile(suffix='.zip') as test_with_deps: | 308 test_package = tempfile.NamedTemporaryFile( |
| 236 sdcard_files = [] | 309 suffix='.zip', |
| 237 additional_apks = [] | 310 delete=False, |
| 238 host_test = os.path.basename(test_path) | 311 dir=self._base_tempfile_dir) |
|
jbudorick
2015/10/26 22:23:30
oh. This is how you're deleting the temporary file
mikecase (-- gone --)
2015/10/27 01:27:43
Ack. All my temporary files are put into _base_tem
| |
| 239 with zipfile.ZipFile(test_with_deps.name, 'w') as zip_file: | 312 sdcard_files = [] |
| 240 zip_file.write(test_path, host_test, zipfile.ZIP_DEFLATED) | 313 additional_apks = [] |
| 241 for h, _ in data_deps: | 314 host_test = os.path.basename(test_path) |
| 242 if os.path.isdir(h): | 315 with zipfile.ZipFile(test_package.name, 'w') as zip_file: |
| 243 zip_utils.WriteToZipFile(zip_file, h, '.') | 316 zip_file.write(self._GetTestPath(), host_test, zipfile.ZIP_DEFLATED) |
| 244 sdcard_files.extend(os.listdir(h)) | 317 for h, _ in data_deps: |
| 245 else: | 318 if os.path.isdir(h): |
| 246 zip_utils.WriteToZipFile(zip_file, h, os.path.basename(h)) | 319 zip_utils.WriteToZipFile(zip_file, h, '.') |
| 247 sdcard_files.append(os.path.basename(h)) | 320 sdcard_files.extend(os.listdir(h)) |
| 248 for a in extra_apks or (): | 321 else: |
| 249 zip_utils.WriteToZipFile(zip_file, a, os.path.basename(a)) | 322 zip_utils.WriteToZipFile(zip_file, h, os.path.basename(h)) |
| 250 additional_apks.append(os.path.basename(a)) | 323 sdcard_files.append(os.path.basename(h)) |
| 324 for a in extra_apks or (): | |
| 325 zip_utils.WriteToZipFile(zip_file, a, os.path.basename(a)) | |
| 326 additional_apks.append(os.path.basename(a)) | |
| 251 | 327 |
| 252 config['sdcard_files'] = ','.join(sdcard_files) | 328 package_configs['sdcard_files'] = ','.join(sdcard_files) |
| 253 config['host_test'] = host_test | 329 package_configs['host_test'] = host_test |
| 254 if additional_apks: | 330 if additional_apks: |
| 255 config['additional_apks'] = ','.join(additional_apks) | 331 package_configs['additional_apks'] = ','.join(additional_apks) |
| 256 self._test_id = self._UploadTestToDevice( | 332 return (test_package.name, package_configs) |
| 257 'robotium', test_with_deps.name, app_id=self._app_id) | |
| 258 else: | 333 else: |
| 259 self._test_id = self._UploadTestToDevice('robotium', test_path) | 334 return (self._GetTestPath(), package_configs) |
| 260 | |
| 261 logging.info('Setting config: %s', config) | |
| 262 appurify_configs = {} | |
| 263 if self._env.network_config: | |
| 264 appurify_configs['network'] = self._env.network_config | |
| 265 self._SetTestConfig('robotium', config, **appurify_configs) | |
| 266 | 335 |
| 267 def _UploadAppToDevice(self, app_path): | 336 def _UploadAppToDevice(self, app_path): |
| 268 """Upload app to device.""" | 337 """Upload app to device.""" |
| 269 logging.info('Uploading %s to remote service as %s.', app_path, | 338 logging.info('Uploading %s to remote service as %s.', app_path, |
| 270 self._test_instance.suite) | 339 self._test_instance.suite) |
| 271 with open(app_path, 'rb') as apk_src: | 340 with open(app_path, 'rb') as apk_src: |
| 272 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, | 341 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, |
| 273 logging.WARNING): | 342 logging.WARNING): |
| 274 upload_results = appurify_sanitized.api.apps_upload( | 343 upload_results = appurify_sanitized.api.apps_upload( |
| 275 self._env.token, apk_src, 'raw', name=self._test_instance.suite) | 344 self._env.token, apk_src, 'raw', name=self._test_instance.suite) |
| 276 remote_device_helper.TestHttpResponse( | 345 remote_device_helper.TestHttpResponse( |
| 277 upload_results, 'Unable to upload %s.' % app_path) | 346 upload_results, 'Unable to upload %s.' % app_path) |
| 278 return upload_results.json()['response']['app_id'] | 347 return upload_results.json()['response']['app_id'] |
| 279 | 348 |
| 280 def _UploadTestToDevice(self, test_type, test_path, app_id=None): | 349 def _UploadTestToDevice(self): |
| 281 """Upload test to device | 350 """Upload test to device.""" |
| 282 Args: | 351 logging.info('Shard uploading %s to remote service.', self._test_package) |
| 283 test_type: Type of test that is being uploaded. Ex. uirobot, gtest.. | 352 with open(self._test_package, 'rb') as test_src: |
| 284 """ | 353 with appurify_sanitized.SanitizeLogging( |
| 285 logging.info('Uploading %s to remote service.', test_path) | 354 verbose_count=self._env.verbose_count, |
| 286 with open(test_path, 'rb') as test_src: | 355 level=logging.WARNING): |
| 287 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, | 356 upload_results_response = appurify_sanitized.api.tests_upload( |
| 288 logging.WARNING): | 357 access_token=self._env.token, |
|
jbudorick
2015/10/26 22:23:30
You sure added a lot of kwarg labels. Are they rea
mikecase (-- gone --)
2015/10/27 01:27:43
Added them because it made reading the code far ea
jbudorick
2015/10/27 01:30:32
to be fair, you were also outvoted 2-1 :P
| |
| 289 upload_results = appurify_sanitized.api.tests_upload( | 358 test_source=test_src, |
| 290 self._env.token, test_src, 'raw', test_type, app_id=app_id) | 359 test_source_type='raw', |
| 291 remote_device_helper.TestHttpResponse(upload_results, | 360 test_type=self._GetTestFramework(), |
| 292 'Unable to upload %s.' % test_path) | 361 app_id=self._app_id) |
| 293 return upload_results.json()['response']['test_id'] | 362 remote_device_helper.TestHttpResponse( |
| 363 response=upload_results_response, | |
| 364 error_msg='Unable to upload %s.' % self._test_package) | |
| 365 return upload_results_response.json()['response']['test_id'] | |
| 294 | 366 |
| 295 def _SetTestConfig(self, runner_type, runner_configs, | 367 def _UploadTestConfigToDevice( |
| 296 network=appurify_constants.NETWORK.WIFI_1_BAR, | 368 self, test_id, framework_configs, appurify_configs): |
| 297 pcap=0, profiler=0, videocapture=0): | 369 """Generates and uploads config file for test.""" |
| 298 """Generates and uploads config file for test. | |
| 299 Args: | |
| 300 runner_configs: Configs specific to the runner you are using. | |
| 301 network: Config to specify the network environment the devices running | |
| 302 the tests will be in. | |
| 303 pcap: Option to set the recording the of network traffic from the device. | |
| 304 profiler: Option to set the recording of CPU, memory, and network | |
| 305 transfer usage in the tests. | |
| 306 videocapture: Option to set video capture during the tests. | |
| 307 | |
| 308 """ | |
| 309 logging.info('Generating config file for test.') | 370 logging.info('Generating config file for test.') |
| 371 config_data = ['[appurify]'] | |
| 372 config_data.extend( | |
| 373 '%s=%s' % (k, v) for k, v in appurify_configs.iteritems()) | |
| 374 config_data.append('[%s]' % self._GetTestFramework()) | |
| 375 config_data.extend( | |
| 376 '%s=%s' % (k, v) for k, v in framework_configs.iteritems()) | |
| 310 with tempfile.TemporaryFile() as config: | 377 with tempfile.TemporaryFile() as config: |
| 311 config_data = [ | |
| 312 '[appurify]', | |
| 313 'network=%s' % network, | |
| 314 'pcap=%s' % pcap, | |
| 315 'profiler=%s' % profiler, | |
| 316 'videocapture=%s' % videocapture, | |
| 317 '[%s]' % runner_type | |
| 318 ] | |
| 319 config_data.extend( | |
| 320 '%s=%s' % (k, v) for k, v in runner_configs.iteritems()) | |
| 321 config.write(''.join('%s\n' % l for l in config_data)) | 378 config.write(''.join('%s\n' % l for l in config_data)) |
| 322 config.flush() | 379 config.flush() |
| 323 config.seek(0) | 380 config.seek(0) |
| 324 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, | 381 with appurify_sanitized.SanitizeLogging( |
| 325 logging.WARNING): | 382 verbose_count=self._env.verbose_count, |
| 326 config_response = appurify_sanitized.api.config_upload( | 383 level=logging.WARNING): |
| 327 self._env.token, config, self._test_id) | 384 config_upload_response = appurify_sanitized.api.config_upload( |
| 385 access_token=self._env.token, | |
| 386 config_src=config, | |
| 387 test_id=test_id) | |
| 328 remote_device_helper.TestHttpResponse( | 388 remote_device_helper.TestHttpResponse( |
| 329 config_response, 'Unable to upload test config.') | 389 response=config_upload_response, |
| 390 error_msg='Unable to upload test config.') | |
| 330 | 391 |
| 331 def _LogLogcat(self, level=logging.CRITICAL): | 392 def _DetectPlatformErrors(self, results, test_output, results_zip): |
| 393 if not test_output['results']['pass']: | |
| 394 crash_msg = None | |
| 395 for line in test_output['results']['output'].splitlines(): | |
| 396 m = _LONG_MSG_RE.search(line) | |
| 397 if m: | |
| 398 crash_msg = m.group(1) | |
| 399 break | |
| 400 m = _SHORT_MSG_RE.search(line) | |
| 401 if m: | |
| 402 crash_msg = m.group(1) | |
| 403 if crash_msg: | |
| 404 self._LogLogcat(results_zip) | |
| 405 results.AddResult(base_test_result.BaseTestResult( | |
| 406 crash_msg, base_test_result.ResultType.CRASH)) | |
| 407 elif self._DidDeviceGoOffline(results_zip): | |
| 408 self._LogLogcat(results_zip) | |
| 409 self._LogAdbTraceLog(results_zip) | |
| 410 raise remote_device_helper.RemoteDeviceError( | |
| 411 message='Remote service unable to reach device.', | |
| 412 is_infra_error=True) | |
| 413 else: | |
| 414 # Remote service is reporting a failure, but no failure in results obj. | |
| 415 if results.DidRunPass(): | |
| 416 results.AddResult(base_test_result.BaseTestResult( | |
| 417 'Remote service detected error.', | |
| 418 base_test_result.ResultType.UNKNOWN)) | |
| 419 | |
| 420 def _AbortTestRun(self, test_run_id): | |
| 421 if self._CheckTestResults(test_run_id)['status'] != self.COMPLETE: | |
| 422 with appurify_sanitized.SanitizeLogging( | |
| 423 verbose_count=self._env.verbose_count, | |
| 424 level=logging.WARNING): | |
| 425 test_abort_response = appurify_sanitized.api.tests_abort( | |
| 426 access_token=self._env.token, | |
| 427 test_run_id=test_run_id, | |
| 428 reason='Test runner exiting.') | |
| 429 remote_device_helper.TestHttpResponse( | |
| 430 response=test_abort_response, | |
| 431 error_msg='Unable to abort test.') | |
| 432 | |
| 433 # pylint: disable=no-self-use | |
| 434 def _LogLogcat(self, results_zip, level=logging.CRITICAL): | |
|
jbudorick
2015/10/26 22:23:30
methods that don't use self should usually be move
mikecase (-- gone --)
2015/10/27 01:27:43
Will do. Was kinda unsure since only this class re
| |
| 332 """Prints out logcat downloaded from remote service. | 435 """Prints out logcat downloaded from remote service. |
| 333 Args: | 436 Args: |
| 437 results_zip: path to zipfile containing the results files. | |
| 334 level: logging level to print at. | 438 level: logging level to print at. |
| 335 | 439 |
| 336 Raises: | 440 Raises: |
| 337 KeyError: If appurify_results/logcat.txt file cannot be found in | 441 KeyError: If appurify_results/logcat.txt file cannot be found in |
| 338 downloaded zip. | 442 downloaded zip. |
| 339 """ | 443 """ |
| 340 zip_file = self._DownloadTestResults(None) | 444 with zipfile.ZipFile(results_zip) as z: |
| 341 with zipfile.ZipFile(zip_file) as z: | |
| 342 try: | 445 try: |
| 343 logcat = z.read('appurify_results/logcat.txt') | 446 logcat = z.read('appurify_results/logcat.txt') |
| 344 printable_logcat = ''.join(c for c in logcat if c in string.printable) | 447 printable_logcat = ''.join(c for c in logcat if c in string.printable) |
| 345 for line in printable_logcat.splitlines(): | 448 for line in printable_logcat.splitlines(): |
| 346 logging.log(level, line) | 449 logging.log(level, line) |
| 347 except KeyError: | 450 except KeyError: |
| 348 logging.error('No logcat found.') | 451 logging.error('No logcat found.') |
| 349 | 452 |
| 350 def _LogAdbTraceLog(self): | 453 # pylint: disable=no-self-use |
| 351 zip_file = self._DownloadTestResults(None) | 454 def _LogAdbTraceLog(self, results_zip): |
| 352 with zipfile.ZipFile(zip_file) as z: | 455 with zipfile.ZipFile(results_zip) as z: |
| 353 adb_trace_log = z.read('adb_trace.log') | 456 adb_trace_log = z.read('adb_trace.log') |
| 354 for line in adb_trace_log.splitlines(): | 457 for line in adb_trace_log.splitlines(): |
| 355 logging.critical(line) | 458 logging.critical(line) |
| 356 | 459 |
| 357 def _DidDeviceGoOffline(self): | 460 # pylint: disable=no-self-use |
| 358 zip_file = self._DownloadTestResults(None) | 461 def _DidDeviceGoOffline(self, results_zip): |
| 359 with zipfile.ZipFile(zip_file) as z: | 462 with zipfile.ZipFile(results_zip) as z: |
| 360 adb_trace_log = z.read('adb_trace.log') | 463 adb_trace_log = z.read('adb_trace.log') |
| 361 if any(_DEVICE_OFFLINE_RE.search(l) for l in adb_trace_log.splitlines()): | 464 if any(_DEVICE_OFFLINE_RE.search(l) for l in adb_trace_log.splitlines()): |
| 362 return True | 465 return True |
| 363 return False | 466 return False |
| 364 | |
| 365 def _DetectPlatformErrors(self, results): | |
| 366 if not self._results['results']['pass']: | |
| 367 crash_msg = None | |
| 368 for line in self._results['results']['output'].splitlines(): | |
| 369 m = _LONG_MSG_RE.search(line) | |
| 370 if m: | |
| 371 crash_msg = m.group(1) | |
| 372 break | |
| 373 m = _SHORT_MSG_RE.search(line) | |
| 374 if m: | |
| 375 crash_msg = m.group(1) | |
| 376 if crash_msg: | |
| 377 self._LogLogcat() | |
| 378 results.AddResult(base_test_result.BaseTestResult( | |
| 379 crash_msg, base_test_result.ResultType.CRASH)) | |
| 380 elif self._DidDeviceGoOffline(): | |
| 381 self._LogLogcat() | |
| 382 self._LogAdbTraceLog() | |
| 383 raise remote_device_helper.RemoteDeviceError( | |
| 384 'Remote service unable to reach device.', is_infra_error=True) | |
| 385 else: | |
| 386 # Remote service is reporting a failure, but no failure in results obj. | |
| 387 if results.DidRunPass(): | |
| 388 results.AddResult(base_test_result.BaseTestResult( | |
| 389 'Remote service detected error.', | |
| 390 base_test_result.ResultType.UNKNOWN)) | |
| OLD | NEW |