| 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 | 
|---|