| 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 |
| 17 from devil.utils import zip_utils | 18 from devil.utils import reraiser_thread |
| 18 from pylib.base import base_test_result | 19 from pylib.base import base_test_result |
| 19 from pylib.base import test_run | 20 from pylib.base import test_run |
| 20 from pylib.remote.device import appurify_constants | |
| 21 from pylib.remote.device import appurify_sanitized | 21 from pylib.remote.device import appurify_sanitized |
| 22 from pylib.remote.device import remote_device_helper | 22 from pylib.remote.device import remote_device_helper |
| 23 | 23 |
| 24 _DEVICE_OFFLINE_RE = re.compile('error: device not found') | 24 _DEVICE_OFFLINE_RE = re.compile('error: device not found') |
| 25 _LONG_MSG_RE = re.compile('longMsg=(.*)$') | 25 _LONG_MSG_RE = re.compile('longMsg=(.*)$') |
| 26 _SHORT_MSG_RE = re.compile('shortMsg=(.*)$') | 26 _SHORT_MSG_RE = re.compile('shortMsg=(.*)$') |
| 27 | 27 |
| 28 class RemoteDeviceTestRun(test_run.TestRun): | 28 class RemoteDeviceTestRun(test_run.TestRun): |
| 29 """Run tests on a remote device.""" | 29 """Run tests on a remote device.""" |
| 30 | 30 |
| 31 _TEST_RUN_KEY = 'test_run' | 31 WAIT_TIME = 10 |
| 32 _TEST_RUN_ID_KEY = 'test_run_id' | |
| 33 | |
| 34 WAIT_TIME = 5 | |
| 35 COMPLETE = 'complete' | 32 COMPLETE = 'complete' |
| 36 HEARTBEAT_INTERVAL = 300 | 33 HEARTBEAT_INTERVAL = 300 |
| 37 | 34 |
| 35 _TEST_RUN_KEY = 'test_run' |
| 36 _TEST_RUN_IDS_KEY = 'test_run_ids' |
| 37 |
| 38 def __init__(self, env, test_instance): | 38 def __init__(self, env, test_instance): |
| 39 """Constructor. | 39 """Constructor. |
| 40 | 40 |
| 41 Args: | 41 Args: |
| 42 env: Environment the tests will run in. | 42 env: Environment the tests will run in. |
| 43 test_instance: The test that will be run. | 43 test_instance: The test that will be run. |
| 44 """ | 44 """ |
| 45 super(RemoteDeviceTestRun, self).__init__(env, test_instance) | 45 super(RemoteDeviceTestRun, self).__init__(env, test_instance) |
| 46 self._app_id = None |
| 47 self._appurify_configs = { |
| 48 'network': env.network_config, |
| 49 'pcap': env.pcap_config, |
| 50 'profiler': env.profiler_config, |
| 51 'videocapture': env.videocapture_config, |
| 52 } |
| 53 self._device_ids = None |
| 46 self._env = env | 54 self._env = env |
| 47 self._test_instance = test_instance | 55 self._test_instance = test_instance |
| 48 self._app_id = '' | 56 self._test_ids = [] |
| 49 self._test_id = '' | 57 self._test_run_ids = [] |
| 50 self._results = '' | 58 |
| 51 self._test_run_id = '' | 59 def _GetTestFramework(self): |
| 52 self._results_temp_dir = None | 60 raise NotImplementedError |
| 61 |
| 62 def _SetupTestShards(self, num_shards): |
| 63 raise NotImplementedError |
| 64 |
| 65 def _GetAppPath(self): |
| 66 raise NotImplementedError |
| 53 | 67 |
| 54 #override | 68 #override |
| 55 def SetUp(self): | 69 def SetUp(self): |
| 56 """Set up a test run.""" | 70 """Set up a test run.""" |
| 57 if self._env.trigger: | 71 if self._env.trigger: |
| 58 self._TriggerSetUp() | 72 num_shards = 1 |
| 73 if self._ShouldShard(): |
| 74 num_shards = min(self._env.num_shards, len(self._env.device_ids)) |
| 75 # TODO(mikecase): Change to always run with the number of shards passed. |
| 76 # Will need some shards to wait for new devices to become available. |
| 77 if num_shards < self._env.num_shards: |
| 78 logging.warning( |
| 79 'Requested to shard across %s devices, only %s availible.', |
| 80 self._env.num_shards, num_shards) |
| 81 logging.info('Sharding test run across %s device(s).', num_shards) |
| 82 |
| 83 self._test_ids = self._SetupTestShards(num_shards) |
| 84 |
| 59 elif self._env.collect: | 85 elif self._env.collect: |
| 60 assert isinstance(self._env.collect, basestring), ( | 86 assert isinstance(self._env.collect, basestring), ( |
| 61 'File for storing test_run_id must be a string.') | 87 'File for storing test_run_id must be a string.') |
| 62 with open(self._env.collect, 'r') as persisted_data_file: | 88 with open(self._env.collect, 'r') as persisted_data_file: |
| 63 persisted_data = json.loads(persisted_data_file.read()) | 89 persisted_data = json.loads(persisted_data_file.read()) |
| 64 self._env.LoadFrom(persisted_data) | 90 self._LoadFrom(persisted_data) |
| 65 self.LoadFrom(persisted_data) | |
| 66 | 91 |
| 67 def _TriggerSetUp(self): | 92 def _ShouldShard(self): |
| 68 """Set up the triggering of a test run.""" | |
| 69 raise NotImplementedError | 93 raise NotImplementedError |
| 70 | 94 |
| 71 #override | 95 def _Trigger(self, device_id, test_id): |
| 96 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, |
| 97 logging.WARNING): |
| 98 test_start_response = appurify_sanitized.api.tests_run( |
| 99 access_token=self._env.token, device_type_id=device_id, |
| 100 app_id=self._app_id, test_id=test_id) |
| 101 remote_device_helper.TestHttpResponse(test_start_response, |
| 102 'Unable to run test.') |
| 103 return test_start_response.json()['response']['test_run_id'] |
| 104 |
| 72 def RunTests(self): | 105 def RunTests(self): |
| 73 """Run the test.""" | 106 """Run the test.""" |
| 107 |
| 74 if self._env.trigger: | 108 if self._env.trigger: |
| 75 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, | 109 def trigger(device_id, test_id): |
| 76 logging.WARNING): | 110 test_run_id = self._Trigger(device_id, test_id) |
| 77 test_start_res = appurify_sanitized.api.tests_run( | 111 self._test_run_ids.append(test_run_id) |
| 78 self._env.token, self._env.device_type_id, self._app_id, | 112 |
| 79 self._test_id) | 113 self._app_id = self._UploadAppToDevice(self._GetAppPath()) |
| 80 remote_device_helper.TestHttpResponse( | 114 self._device_ids = random.sample( |
| 81 test_start_res, 'Unable to run test.') | 115 self._env.device_ids, len(self._test_ids)) |
| 82 self._test_run_id = test_start_res.json()['response']['test_run_id'] | 116 |
| 83 logging.info('Test run id: %s', self._test_run_id) | 117 workers = reraiser_thread.ReraiserThreadGroup( |
| 118 [reraiser_thread.ReraiserThread( |
| 119 trigger, args=[device_id, test_id], |
| 120 name='trigger test %s' % test_id) |
| 121 for device_id, test_id in zip(self._device_ids, self._test_ids)]) |
| 122 workers.StartAll() |
| 123 workers.JoinAll() |
| 84 | 124 |
| 85 if self._env.collect: | 125 if self._env.collect: |
| 86 current_status = '' | 126 parsed_results = base_test_result.TestRunResults() |
| 87 timeout_counter = 0 | |
| 88 heartbeat_counter = 0 | |
| 89 while self._GetTestStatus(self._test_run_id) != self.COMPLETE: | |
| 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 | 127 |
| 99 timeout = self._env.timeouts.get( | 128 def collect(test_run_id): |
| 100 current_status, self._env.timeouts['unknown']) | 129 with tempfile.NamedTemporaryFile( |
| 101 if timeout_counter > timeout: | 130 prefix='results-', suffix='.zip') as download_results_file: |
| 102 raise remote_device_helper.RemoteDeviceError( | 131 test_output = self._Collect(test_run_id=test_run_id) |
| 103 'Timeout while in %s state for %s seconds' | 132 self._DownloadTestResults(test_output, download_results_file.name) |
| 104 % (current_status, timeout), | 133 parsed_results.AddTestRunResults(self._ParseTestResults( |
| 105 is_infra_error=True) | 134 test_output, download_results_file.name)) |
| 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 | 135 |
| 111 if self._results['results']['exception']: | 136 workers = reraiser_thread.ReraiserThreadGroup( |
| 112 raise remote_device_helper.RemoteDeviceError( | 137 [reraiser_thread.ReraiserThread( |
| 113 self._results['results']['exception'], is_infra_error=True) | 138 collect, args=[test_run_id], |
| 114 | 139 name='collect test %s' % test_run_id) |
| 115 return self._ParseTestResults() | 140 for test_run_id in self._test_run_ids]) |
| 141 workers.StartAll() |
| 142 workers.JoinAll() |
| 143 return parsed_results |
| 116 | 144 |
| 117 #override | 145 #override |
| 118 def TearDown(self): | 146 def TearDown(self): |
| 119 """Tear down the test run.""" | 147 """Tear down the test run.""" |
| 148 |
| 120 if self._env.collect: | 149 if self._env.collect: |
| 121 self._CollectTearDown() | 150 self._CollectTearDown() |
| 122 elif self._env.trigger: | 151 elif self._env.trigger: |
| 123 assert isinstance(self._env.trigger, basestring), ( | 152 assert isinstance(self._env.trigger, basestring), ( |
| 124 'File for storing test_run_id must be a string.') | 153 'File for storing test_run_id must be a string.') |
| 125 with open(self._env.trigger, 'w') as persisted_data_file: | 154 with open(self._env.trigger, 'w') as persisted_data_file: |
| 126 persisted_data = {} | 155 persisted_data = {} |
| 127 self.DumpTo(persisted_data) | 156 self._DumpTo(persisted_data) |
| 128 self._env.DumpTo(persisted_data) | |
| 129 persisted_data_file.write(json.dumps(persisted_data)) | 157 persisted_data_file.write(json.dumps(persisted_data)) |
| 130 | 158 |
| 159 def _Collect(self, test_run_id): |
| 160 current_status = '' |
| 161 timeout_counter = 0 |
| 162 heartbeat_counter = 0 |
| 163 results = self._CheckTestResults(test_run_id) |
| 164 while results['status'] != self.COMPLETE: |
| 165 if results['detailed_status'] != current_status: |
| 166 logging.info('Test status: %s', results['detailed_status']) |
| 167 current_status = results['detailed_status'] |
| 168 timeout_counter = 0 |
| 169 heartbeat_counter = 0 |
| 170 if heartbeat_counter > self.HEARTBEAT_INTERVAL: |
| 171 logging.info('Test status: %s', results['detailed_status']) |
| 172 heartbeat_counter = 0 |
| 173 |
| 174 timeout = self._env.timeouts.get( |
| 175 current_status, self._env.timeouts['unknown']) |
| 176 if timeout_counter > timeout: |
| 177 raise remote_device_helper.RemoteDeviceError( |
| 178 'Timeout while in %s state for %s seconds' |
| 179 % (current_status, timeout), |
| 180 is_infra_error=True) |
| 181 time.sleep(self.WAIT_TIME) |
| 182 timeout_counter += self.WAIT_TIME |
| 183 heartbeat_counter += self.WAIT_TIME |
| 184 results = self._CheckTestResults(test_run_id) |
| 185 if results['results']['exception']: |
| 186 raise remote_device_helper.RemoteDeviceError( |
| 187 results['results']['exception'], |
| 188 is_infra_error=True) |
| 189 return results |
| 190 |
| 131 def _CollectTearDown(self): | 191 def _CollectTearDown(self): |
| 132 if self._GetTestStatus(self._test_run_id) != self.COMPLETE: | 192 if self._test_run_ids: |
| 133 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, | 193 workers = reraiser_thread.ReraiserThreadGroup( |
| 134 logging.WARNING): | 194 [reraiser_thread.ReraiserThread( |
| 135 test_abort_res = appurify_sanitized.api.tests_abort( | 195 self._AbortTestRun, args=[test_run_id], |
| 136 self._env.token, self._test_run_id, reason='Test runner exiting.') | 196 name='aborting test %s' % test_run_id) |
| 137 remote_device_helper.TestHttpResponse(test_abort_res, | 197 for test_run_id in self._test_run_ids]) |
| 138 'Unable to abort test.') | 198 workers.StartAll() |
| 139 if self._results_temp_dir: | 199 workers.JoinAll() |
| 140 shutil.rmtree(self._results_temp_dir) | |
| 141 | 200 |
| 142 def __enter__(self): | 201 def __enter__(self): |
| 143 """Set up the test run when used as a context manager.""" | 202 """Set up the test run when used as a context manager.""" |
| 144 self.SetUp() | 203 self.SetUp() |
| 145 return self | 204 return self |
| 146 | 205 |
| 147 def __exit__(self, exc_type, exc_val, exc_tb): | 206 def __exit__(self, exc_type, exc_val, exc_tb): |
| 148 """Tear down the test run when used as a context manager.""" | 207 """Tear down the test run when used as a context manager.""" |
| 149 self.TearDown() | 208 self.TearDown() |
| 150 | 209 |
| 151 def DumpTo(self, persisted_data): | 210 def _DumpTo(self, persisted_data): |
| 152 test_run_data = { | 211 """Dump test run information to dict.""" |
| 153 self._TEST_RUN_ID_KEY: self._test_run_id, | 212 test_run_data = {self._TEST_RUN_IDS_KEY: self._test_run_ids} |
| 154 } | |
| 155 persisted_data[self._TEST_RUN_KEY] = test_run_data | 213 persisted_data[self._TEST_RUN_KEY] = test_run_data |
| 156 | 214 |
| 157 def LoadFrom(self, persisted_data): | 215 def _LoadFrom(self, persisted_data): |
| 216 """Load test run information.""" |
| 158 test_run_data = persisted_data[self._TEST_RUN_KEY] | 217 test_run_data = persisted_data[self._TEST_RUN_KEY] |
| 159 self._test_run_id = test_run_data[self._TEST_RUN_ID_KEY] | 218 self._test_run_ids = test_run_data[self._TEST_RUN_IDS_KEY] |
| 160 | 219 |
| 161 def _ParseTestResults(self): | 220 def _ParseTestResults(self, test_output, results_zip): |
| 162 raise NotImplementedError | 221 raise NotImplementedError |
| 163 | 222 |
| 164 def _GetTestByName(self, test_name): | 223 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. | 224 """Download the test results from remote device service. |
| 183 | 225 |
| 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: | 226 Returns: |
| 191 Path to downloaded file. | 227 Path to downloaded file. |
| 192 """ | 228 """ |
| 229 logging.info('Downloading results to %s.', download_path) |
| 230 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, |
| 231 logging.WARNING): |
| 232 appurify_sanitized.utils.wget( |
| 233 url=results['results']['url'], path=download_path) |
| 234 if self._env.results_dir: |
| 235 logging.info('Copying results to %s from %s', |
| 236 self._env.results_dir, download_path) |
| 237 if not os.path.exists(os.path.dirname(self._env.results_dir)): |
| 238 os.makedirs(self._env.results_dir) |
| 239 shutil.copy(download_path, self._env.results_dir) |
| 193 | 240 |
| 194 if self._results_temp_dir is None: | 241 def _CheckTestResults(self, test_run_id): |
| 195 self._results_temp_dir = tempfile.mkdtemp() | 242 """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 | 243 |
| 211 Args: | 244 Args: |
| 212 test_run_id: Id of test on on remote service. | 245 test_run_id: Id of test on on remote service. |
| 213 """ | 246 """ |
| 214 | |
| 215 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, | 247 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, |
| 216 logging.WARNING): | 248 logging.WARNING): |
| 217 test_check_res = appurify_sanitized.api.tests_check_result( | 249 test_check_response = appurify_sanitized.api.tests_check_result( |
| 218 self._env.token, test_run_id) | 250 access_token=self._env.token, test_run_id=test_run_id) |
| 219 remote_device_helper.TestHttpResponse(test_check_res, | 251 remote_device_helper.TestHttpResponse(test_check_response, |
| 220 'Unable to get test status.') | 252 'Unable to get test status.') |
| 221 self._results = test_check_res.json()['response'] | 253 return test_check_response.json()['response'] |
| 222 return self._results['status'] | |
| 223 | |
| 224 def _AmInstrumentTestSetup(self, app_path, test_path, runner_package, | |
| 225 environment_variables, extra_apks=None): | |
| 226 config = {'runner': runner_package} | |
| 227 if environment_variables: | |
| 228 config['environment_vars'] = ','.join( | |
| 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() | |
| 234 if data_deps: | |
| 235 with tempfile.NamedTemporaryFile(suffix='.zip') as test_with_deps: | |
| 236 sdcard_files = [] | |
| 237 additional_apks = [] | |
| 238 host_test = os.path.basename(test_path) | |
| 239 with zipfile.ZipFile(test_with_deps.name, 'w') as zip_file: | |
| 240 zip_file.write(test_path, host_test, zipfile.ZIP_DEFLATED) | |
| 241 for h, _ in data_deps: | |
| 242 if os.path.isdir(h): | |
| 243 zip_utils.WriteToZipFile(zip_file, h, '.') | |
| 244 sdcard_files.extend(os.listdir(h)) | |
| 245 else: | |
| 246 zip_utils.WriteToZipFile(zip_file, h, os.path.basename(h)) | |
| 247 sdcard_files.append(os.path.basename(h)) | |
| 248 for a in extra_apks or (): | |
| 249 zip_utils.WriteToZipFile(zip_file, a, os.path.basename(a)) | |
| 250 additional_apks.append(os.path.basename(a)) | |
| 251 | |
| 252 config['sdcard_files'] = ','.join(sdcard_files) | |
| 253 config['host_test'] = host_test | |
| 254 if additional_apks: | |
| 255 config['additional_apks'] = ','.join(additional_apks) | |
| 256 self._test_id = self._UploadTestToDevice( | |
| 257 'robotium', test_with_deps.name, app_id=self._app_id) | |
| 258 else: | |
| 259 self._test_id = self._UploadTestToDevice('robotium', test_path) | |
| 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 | 254 |
| 267 def _UploadAppToDevice(self, app_path): | 255 def _UploadAppToDevice(self, app_path): |
| 268 """Upload app to device.""" | 256 """Upload app to device.""" |
| 269 logging.info('Uploading %s to remote service as %s.', app_path, | 257 logging.info('Uploading %s to remote service as %s.', app_path, |
| 270 self._test_instance.suite) | 258 self._test_instance.suite) |
| 271 with open(app_path, 'rb') as apk_src: | 259 with open(app_path, 'rb') as apk_src: |
| 272 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, | 260 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, |
| 273 logging.WARNING): | 261 logging.WARNING): |
| 274 upload_results = appurify_sanitized.api.apps_upload( | 262 upload_results = appurify_sanitized.api.apps_upload( |
| 275 self._env.token, apk_src, 'raw', name=self._test_instance.suite) | 263 self._env.token, apk_src, 'raw', name=self._test_instance.suite) |
| 276 remote_device_helper.TestHttpResponse( | 264 remote_device_helper.TestHttpResponse(upload_results, |
| 277 upload_results, 'Unable to upload %s.' % app_path) | 265 'Unable to upload %s.' % app_path) |
| 278 return upload_results.json()['response']['app_id'] | 266 return upload_results.json()['response']['app_id'] |
| 279 | 267 |
| 280 def _UploadTestToDevice(self, test_type, test_path, app_id=None): | 268 def _UploadTestToDevice(self, test_path): |
| 281 """Upload test to device | 269 """Upload test to device.""" |
| 282 Args: | 270 logging.info('Shard uploading %s to remote service.', test_path) |
| 283 test_type: Type of test that is being uploaded. Ex. uirobot, gtest.. | |
| 284 """ | |
| 285 logging.info('Uploading %s to remote service.', test_path) | |
| 286 with open(test_path, 'rb') as test_src: | 271 with open(test_path, 'rb') as test_src: |
| 287 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, | 272 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, |
| 288 logging.WARNING): | 273 logging.WARNING): |
| 289 upload_results = appurify_sanitized.api.tests_upload( | 274 upload_results_response = appurify_sanitized.api.tests_upload( |
| 290 self._env.token, test_src, 'raw', test_type, app_id=app_id) | 275 access_token=self._env.token, test_source=test_src, |
| 291 remote_device_helper.TestHttpResponse(upload_results, | 276 test_source_type='raw', test_type=self._GetTestFramework(), |
| 292 'Unable to upload %s.' % test_path) | 277 app_id=self._app_id) |
| 293 return upload_results.json()['response']['test_id'] | 278 remote_device_helper.TestHttpResponse( |
| 279 upload_results_response, 'Unable to upload %s.' % test_path) |
| 280 return upload_results_response.json()['response']['test_id'] |
| 294 | 281 |
| 295 def _SetTestConfig(self, runner_type, runner_configs, | 282 def _UploadTestConfigToDevice( |
| 296 network=appurify_constants.NETWORK.WIFI_1_BAR, | 283 self, test_id, framework_configs, appurify_configs): |
| 297 pcap=0, profiler=0, videocapture=0): | 284 """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.') | 285 logging.info('Generating config file for test.') |
| 286 config_data = ['[appurify]'] |
| 287 config_data.extend( |
| 288 '%s=%s' % (k, v) for k, v in appurify_configs.iteritems()) |
| 289 config_data.append('[%s]' % self._GetTestFramework()) |
| 290 config_data.extend( |
| 291 '%s=%s' % (k, v) for k, v in framework_configs.iteritems()) |
| 310 with tempfile.TemporaryFile() as config: | 292 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)) | 293 config.write(''.join('%s\n' % l for l in config_data)) |
| 322 config.flush() | 294 config.flush() |
| 323 config.seek(0) | 295 config.seek(0) |
| 324 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, | 296 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, |
| 325 logging.WARNING): | 297 logging.WARNING): |
| 326 config_response = appurify_sanitized.api.config_upload( | 298 config_upload_response = appurify_sanitized.api.config_upload( |
| 327 self._env.token, config, self._test_id) | 299 access_token=self._env.token, config_src=config, test_id=test_id) |
| 328 remote_device_helper.TestHttpResponse( | 300 remote_device_helper.TestHttpResponse(config_upload_response, |
| 329 config_response, 'Unable to upload test config.') | 301 'Unable to upload test config.') |
| 330 | 302 |
| 331 def _LogLogcat(self, level=logging.CRITICAL): | 303 def _AbortTestRun(self, test_run_id): |
| 332 """Prints out logcat downloaded from remote service. | 304 logging.info('Aborting test run %s.', test_run_id) |
| 333 Args: | 305 if self._CheckTestResults(test_run_id)['status'] != self.COMPLETE: |
| 334 level: logging level to print at. | 306 with appurify_sanitized.SanitizeLogging(self._env.verbose_count, |
| 307 logging.WARNING): |
| 308 test_abort_response = appurify_sanitized.api.tests_abort( |
| 309 access_token=self._env.token, test_run_id=test_run_id, |
| 310 reason='Test runner exiting.') |
| 311 remote_device_helper.TestHttpResponse(test_abort_response, |
| 312 'Unable to abort test.') |
| 335 | 313 |
| 336 Raises: | 314 def DetectPlatformErrors(results, test_output, results_zip): |
| 337 KeyError: If appurify_results/logcat.txt file cannot be found in | 315 if not test_output['results']['pass']: |
| 338 downloaded zip. | 316 crash_msg = None |
| 339 """ | 317 for line in test_output['results']['output'].splitlines(): |
| 340 zip_file = self._DownloadTestResults(None) | 318 m = _LONG_MSG_RE.search(line) |
| 341 with zipfile.ZipFile(zip_file) as z: | 319 if m: |
| 342 try: | 320 crash_msg = m.group(1) |
| 343 logcat = z.read('appurify_results/logcat.txt') | 321 break |
| 344 printable_logcat = ''.join(c for c in logcat if c in string.printable) | 322 m = _SHORT_MSG_RE.search(line) |
| 345 for line in printable_logcat.splitlines(): | 323 if m: |
| 346 logging.log(level, line) | 324 crash_msg = m.group(1) |
| 347 except KeyError: | 325 if crash_msg: |
| 348 logging.error('No logcat found.') | 326 _LogLogcat(results_zip) |
| 327 results.AddResult(base_test_result.BaseTestResult( |
| 328 crash_msg, base_test_result.ResultType.CRASH)) |
| 329 elif _DidDeviceGoOffline(results_zip): |
| 330 _LogLogcat(results_zip) |
| 331 _LogAdbTraceLog(results_zip) |
| 332 raise remote_device_helper.RemoteDeviceError( |
| 333 'Remote service unable to reach device.', |
| 334 is_infra_error=True) |
| 335 else: |
| 336 # Remote service is reporting a failure, but no failure in results obj. |
| 337 if results.DidRunPass(): |
| 338 results.AddResult(base_test_result.BaseTestResult( |
| 339 'Remote service detected error.', |
| 340 base_test_result.ResultType.UNKNOWN)) |
| 349 | 341 |
| 350 def _LogAdbTraceLog(self): | 342 def _LogLogcat(results_zip, level=logging.CRITICAL): |
| 351 zip_file = self._DownloadTestResults(None) | 343 """Prints out logcat downloaded from remote service. |
| 352 with zipfile.ZipFile(zip_file) as z: | 344 Args: |
| 353 adb_trace_log = z.read('adb_trace.log') | 345 results_zip: path to zipfile containing the results files. |
| 354 for line in adb_trace_log.splitlines(): | 346 level: logging level to print at. |
| 355 logging.critical(line) | |
| 356 | 347 |
| 357 def _DidDeviceGoOffline(self): | 348 Raises: |
| 358 zip_file = self._DownloadTestResults(None) | 349 KeyError: If appurify_results/logcat.txt file cannot be found in |
| 359 with zipfile.ZipFile(zip_file) as z: | 350 downloaded zip. |
| 360 adb_trace_log = z.read('adb_trace.log') | 351 """ |
| 361 if any(_DEVICE_OFFLINE_RE.search(l) for l in adb_trace_log.splitlines()): | 352 with zipfile.ZipFile(results_zip) as z: |
| 362 return True | 353 try: |
| 363 return False | 354 logcat = z.read('appurify_results/logcat.txt') |
| 355 printable_logcat = ''.join(c for c in logcat if c in string.printable) |
| 356 for line in printable_logcat.splitlines(): |
| 357 logging.log(level, line) |
| 358 except KeyError: |
| 359 logging.error('No logcat found.') |
| 364 | 360 |
| 365 def _DetectPlatformErrors(self, results): | 361 def _LogAdbTraceLog(results_zip): |
| 366 if not self._results['results']['pass']: | 362 with zipfile.ZipFile(results_zip) as z: |
| 367 crash_msg = None | 363 adb_trace_log = z.read('adb_trace.log') |
| 368 for line in self._results['results']['output'].splitlines(): | 364 for line in adb_trace_log.splitlines(): |
| 369 m = _LONG_MSG_RE.search(line) | 365 logging.critical(line) |
| 370 if m: | 366 |
| 371 crash_msg = m.group(1) | 367 def _DidDeviceGoOffline(results_zip): |
| 372 break | 368 with zipfile.ZipFile(results_zip) as z: |
| 373 m = _SHORT_MSG_RE.search(line) | 369 adb_trace_log = z.read('adb_trace.log') |
| 374 if m: | 370 if any(_DEVICE_OFFLINE_RE.search(l) for l in adb_trace_log.splitlines()): |
| 375 crash_msg = m.group(1) | 371 return True |
| 376 if crash_msg: | 372 return False |
| 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 |