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