| OLD | NEW |
| (Empty) |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 import contextlib | |
| 6 import json | |
| 7 | |
| 8 from recipe_engine import recipe_api | |
| 9 from recipe_engine import util as recipe_util | |
| 10 | |
| 11 class AmpApi(recipe_api.RecipeApi): | |
| 12 | |
| 13 def __init__(self, *args, **kwargs): | |
| 14 super(AmpApi, self).__init__(*args, **kwargs) | |
| 15 self._trigger_file_dir = None | |
| 16 self._base_results_dir = None | |
| 17 | |
| 18 def setup(self): | |
| 19 """Sets up necessary configs.""" | |
| 20 self.m.chromium_android.configure_from_properties('base_config') | |
| 21 | |
| 22 def _get_trigger_dir(self): | |
| 23 if not self._trigger_file_dir: | |
| 24 self._trigger_file_dir = self.m.path.mkdtemp('amp_trigger') | |
| 25 return self._trigger_file_dir | |
| 26 | |
| 27 def _get_trigger_file_for_suite(self, test_run_id): | |
| 28 return self._get_trigger_dir().join('%s.json' % test_run_id) | |
| 29 | |
| 30 def _get_results_dir(self, test_run_id): | |
| 31 if not self._base_results_dir: | |
| 32 self._base_results_dir = self.m.path.mkdtemp('amp_results') | |
| 33 return self._base_results_dir.join(test_run_id) | |
| 34 | |
| 35 def _get_results_zip_path(self, test_run_id): | |
| 36 return self._get_results_dir(test_run_id).join('results.zip') | |
| 37 | |
| 38 def _get_results_unzipped_path(self, test_run_id): | |
| 39 return self._get_results_dir(test_run_id).join('unzipped_results') | |
| 40 | |
| 41 def _get_results_logcat_path(self, test_run_id): | |
| 42 return self._get_results_unzipped_path(test_run_id).join( | |
| 43 'appurify_results', 'logcat.txt') | |
| 44 | |
| 45 def _get_api_key_file(self): | |
| 46 # TODO(phajdan.jr): Remove path['build'] usage, http://crbug.com/437264 . | |
| 47 local_api_key_file = self.m.path['build'].join( | |
| 48 'site_config', '.amp_%s_key' % self.c.pool) | |
| 49 return local_api_key_file | |
| 50 | |
| 51 def _get_api_secret_file(self): | |
| 52 # TODO(phajdan.jr): Remove path['build'] usage, http://crbug.com/437264 . | |
| 53 local_api_secret_file = self.m.path['build'].join( | |
| 54 'site_config', '.amp_%s_secret' % self.c.pool) | |
| 55 return local_api_secret_file | |
| 56 | |
| 57 def _ensure_keys_downloaded(self): | |
| 58 local_api_key_file = self._get_api_key_file() | |
| 59 if not self.m.path.exists(local_api_key_file): | |
| 60 self.m.gsutil.download_url(name='download amp api key', | |
| 61 url=self.c.api_key_file_url, | |
| 62 dest=local_api_key_file) | |
| 63 self.m.path.mock_add_paths(local_api_key_file) | |
| 64 | |
| 65 local_api_secret_file = self._get_api_secret_file() | |
| 66 if not self.m.path.exists(local_api_secret_file): | |
| 67 self.m.gsutil.download_url(name='download amp api secret', | |
| 68 url=self.c.api_secret_file_url, | |
| 69 dest=local_api_secret_file) | |
| 70 self.m.path.mock_add_paths(local_api_secret_file) | |
| 71 | |
| 72 def trigger_test_suite( | |
| 73 self, suite, test_type, test_type_args, amp_args, step_name=None, | |
| 74 verbose=True): | |
| 75 step_name = step_name or suite | |
| 76 args = ([test_type] + test_type_args + amp_args | |
| 77 + ['--trigger', self.m.json.output()]) | |
| 78 if verbose: | |
| 79 args += ['--verbose'] | |
| 80 if self.m.chromium.c.BUILD_CONFIG == 'Release': | |
| 81 args += ['--release'] | |
| 82 | |
| 83 step_test_data = lambda: self.m.json.test_api.output({ | |
| 84 'env': { | |
| 85 'device': { | |
| 86 'brand': 'Foo', | |
| 87 'name': 'Fone', | |
| 88 'os_version': '1.2.3', | |
| 89 }, | |
| 90 }, | |
| 91 'test_run': { | |
| 92 'test_run_id': 'T35TRUN1D', | |
| 93 }, | |
| 94 }) | |
| 95 self._ensure_keys_downloaded() | |
| 96 step_result = self.m.chromium_android.test_runner( | |
| 97 '[trigger] %s' % step_name, | |
| 98 args=args, | |
| 99 step_test_data=step_test_data) | |
| 100 trigger_data = step_result.json.output | |
| 101 try: | |
| 102 device_data = trigger_data['env']['device'] | |
| 103 step_result.presentation.step_text = 'on %s %s %s' % ( | |
| 104 device_data['brand'], | |
| 105 device_data['name'], | |
| 106 device_data['os_version']) | |
| 107 except KeyError: | |
| 108 step_result.presentation.status = self.m.step.WARNING | |
| 109 step_result.presentation.step_text = 'unable to find device info' | |
| 110 | |
| 111 try: | |
| 112 test_run_id = trigger_data['test_run']['test_run_id'] | |
| 113 except KeyError as e: | |
| 114 # Log trigger_data json for debugging. | |
| 115 with contextlib.closing(recipe_util.StringListIO()) as listio: | |
| 116 json.dump(trigger_data, listio, indent=2, sort_keys=True) | |
| 117 step_result = self.m.step.active_result | |
| 118 step_result.presentation.logs['trigger_data'] = listio.lines | |
| 119 raise self.m.step.StepFailure( | |
| 120 'test_run_id not found in trigger_data json') | |
| 121 | |
| 122 self.m.file.write( | |
| 123 '[trigger] save %s' % step_name, | |
| 124 self._get_trigger_file_for_suite(test_run_id), | |
| 125 self.m.json.dumps(trigger_data)) | |
| 126 return test_run_id | |
| 127 | |
| 128 def collect_test_suite( | |
| 129 self, suite, test_type, test_type_args, amp_args, test_run_id, | |
| 130 step_name=None, verbose=True, json_results_file=None, **kwargs): | |
| 131 step_name = step_name or suite | |
| 132 args = ([test_type] + test_type_args + amp_args | |
| 133 + ['--collect', self._get_trigger_file_for_suite(test_run_id)] | |
| 134 + ['--results-path', self._get_results_zip_path(test_run_id)]) | |
| 135 trigger_data = self.m.json.read( | |
| 136 '[collect] load %s' % step_name, | |
| 137 self._get_trigger_file_for_suite(test_run_id), | |
| 138 step_test_data=lambda: self.m.json.test_api.output({ | |
| 139 'env': { | |
| 140 'device': { | |
| 141 'brand': 'Foo', | |
| 142 'name': 'Fone', | |
| 143 'os_version': '1.2.3', | |
| 144 } | |
| 145 } | |
| 146 })).json.output | |
| 147 try: | |
| 148 device_data = trigger_data['env']['device'] | |
| 149 device_info_text = 'on %s %s %s' % ( | |
| 150 device_data['brand'], | |
| 151 device_data['name'], | |
| 152 device_data['os_version']) | |
| 153 except KeyError: | |
| 154 device_data = None | |
| 155 device_info_text = 'unable to find device info' | |
| 156 | |
| 157 if verbose: | |
| 158 args += ['--verbose'] | |
| 159 if json_results_file: | |
| 160 args += ['--json-results-file', json_results_file] | |
| 161 if self.m.chromium.c.BUILD_CONFIG == 'Release': | |
| 162 args += ['--release'] | |
| 163 try: | |
| 164 step_result = self.m.chromium_android.test_runner( | |
| 165 '[collect] %s' % step_name, | |
| 166 args=args, **kwargs) | |
| 167 except self.m.step.StepFailure as f: | |
| 168 step_result = f.result | |
| 169 raise | |
| 170 finally: | |
| 171 step_result.presentation.step_text = device_info_text | |
| 172 if (not device_data and | |
| 173 step_result.presentation.status == self.m.step.SUCCESS): | |
| 174 step_result.presentation.status = self.m.step.WARNING | |
| 175 | |
| 176 return step_result | |
| 177 | |
| 178 def upload_logcat_to_gs(self, bucket, suite, test_run_id): | |
| 179 """Upload the logcat file returned from the appurify results to | |
| 180 Google Storage. | |
| 181 """ | |
| 182 step_result = self.m.json.read( | |
| 183 '[upload logcat] load %s data' % suite, | |
| 184 self._get_trigger_file_for_suite(test_run_id), | |
| 185 step_test_data=lambda: self.m.json.test_api.output({ | |
| 186 'test_run': { | |
| 187 'test_run_id': '12345abcde', | |
| 188 } | |
| 189 })) | |
| 190 trigger_data = step_result.json.output | |
| 191 self.m.zip.unzip( | |
| 192 step_name='[upload logcat] unzip results for %s' % suite, | |
| 193 zip_file=self._get_results_zip_path(test_run_id), | |
| 194 output=self._get_results_unzipped_path(test_run_id)) | |
| 195 self.m.gsutil.upload( | |
| 196 name='[upload logcat] %s' % suite, | |
| 197 source=self._get_results_logcat_path(test_run_id), | |
| 198 bucket=bucket, | |
| 199 dest='logcats/logcat_%s_%s.txt' % (suite, test_run_id), | |
| 200 link_name='logcat') | |
| 201 | |
| 202 def gtest_arguments( | |
| 203 self, suite, isolate_file_path=None): | |
| 204 """Generate command-line arguments for running gtests. | |
| 205 | |
| 206 Args: | |
| 207 suite: The name of the test suite to run. | |
| 208 isolate_file_path: The path to the .isolate file containing data | |
| 209 dependency information for the test suite. | |
| 210 | |
| 211 Returns: | |
| 212 A list of command-line arguments as strings. | |
| 213 """ | |
| 214 gtest_args = ['-s', suite] | |
| 215 if isolate_file_path: | |
| 216 gtest_args += ['--isolate-file-path', isolate_file_path] | |
| 217 return gtest_args | |
| 218 | |
| 219 def instrumentation_test_arguments( | |
| 220 self, apk_under_test, test_apk, isolate_file_path=None, | |
| 221 additional_apks=None, annotation=None, timeout_scale=None): | |
| 222 """Generate command-line arguments for running instrumentation tests. | |
| 223 | |
| 224 Args: | |
| 225 apk_under_test: The path to the APK under test. | |
| 226 test_apk: The path to the test APK. | |
| 227 isolate_file_path: The path to the .isolate file containing data | |
| 228 dependency information for the test suite. | |
| 229 annotation: Comma-separated list of annotations. Will only run | |
| 230 tests with any of the given annotations. | |
| 231 timeout_scale: Scale for the timeouts of individual tests. | |
| 232 | |
| 233 Returns: | |
| 234 A list of command-line arguments as strings. | |
| 235 """ | |
| 236 instrumentation_test_args = [ | |
| 237 '--apk-under-test', apk_under_test, | |
| 238 '--test-apk', test_apk, | |
| 239 ] | |
| 240 if isolate_file_path: | |
| 241 instrumentation_test_args += ['--isolate-file-path', isolate_file_path] | |
| 242 if annotation: | |
| 243 instrumentation_test_args += ['--annotation', annotation] | |
| 244 if additional_apks: | |
| 245 for apk in additional_apks: | |
| 246 instrumentation_test_args += ['--additional-apk', apk] | |
| 247 if timeout_scale: | |
| 248 instrumentation_test_args += ['--timeout-scale', timeout_scale] | |
| 249 return instrumentation_test_args | |
| 250 | |
| 251 def uirobot_arguments(self, app_under_test=None, minutes=5): | |
| 252 """Generate command-line arguments for running uirobot tests. | |
| 253 | |
| 254 Args: | |
| 255 app_under_test: The app to run uirobot tests on. | |
| 256 minutes: The number of minutes for which the uirobot tests should be | |
| 257 run. Defaults to 5. | |
| 258 | |
| 259 Returns: | |
| 260 A list of command-line arguments as strings. | |
| 261 """ | |
| 262 uirobot_args = ['--minutes', minutes] | |
| 263 if app_under_test: | |
| 264 uirobot_args += ['--app-under-test', app_under_test] | |
| 265 return uirobot_args | |
| 266 | |
| 267 def amp_arguments( | |
| 268 self, device_type='Android', device_minimum_os=None, device_name=None, | |
| 269 device_oem=None, device_os=None, device_timeout=None, api_address=None, | |
| 270 api_port=None, api_protocol=None, network_config=None, | |
| 271 test_run_timeout=None): | |
| 272 """Generate command-line arguments for running tests on AMP. | |
| 273 | |
| 274 Args: | |
| 275 device_name: A list of names of devices to use (e.g. 'Galaxy S4'). | |
| 276 Selects a device at random if unspecified. | |
| 277 device_minimum_os: A string containing the minimum OS version to use. | |
| 278 Should not be specified with |device_os|. | |
| 279 device_oem: A list of names of device OEMs to use (e.g. 'Samsung'). | |
| 280 Selects an OEM at random is unspecified. | |
| 281 device_os: A list of OS versions to use (e.g. '4.4.2'). Selects an OS | |
| 282 version at random if unspecified. Should not be specified with | |
| 283 |device_minimum_os|. | |
| 284 device_timeout: An optional number of seconds to wait for a device | |
| 285 meeting the given criteria to be available. | |
| 286 api_address: The IP address of the AMP API endpoint. | |
| 287 api_port: The port of the AMP API endpoint. | |
| 288 api_protocol: The protocol to use to connect to the AMP API endpoint. | |
| 289 network_config: Use to have AMP run tests in a simulated network | |
| 290 environment. See the availible network environment options at | |
| 291 https://appurify.atlassian.net/wiki/display/APD/ | |
| 292 Run+Configurations+-+Test+and+Network | |
| 293 test_run_timeout: The timeout for the test runtime on AMP. | |
| 294 | |
| 295 Returns: | |
| 296 A list of command-line arguments as strings. | |
| 297 """ | |
| 298 assert api_address, 'api_address not specified' | |
| 299 assert api_port, 'api_port not specified' | |
| 300 assert api_protocol, 'api_protocol not specified' | |
| 301 assert not (device_minimum_os and device_os), ( | |
| 302 'cannot specify both device_minimum_os and device_os') | |
| 303 amp_args = [ | |
| 304 '--enable-platform-mode', | |
| 305 '-e', 'remote_device', | |
| 306 '--api-key-file', | |
| 307 self._get_api_key_file(), | |
| 308 '--api-secret-file', | |
| 309 self._get_api_secret_file(), | |
| 310 '--api-address', api_address, | |
| 311 '--api-port', api_port, | |
| 312 '--api-protocol', api_protocol, | |
| 313 '--device-type', device_type, | |
| 314 ] | |
| 315 if device_minimum_os: | |
| 316 amp_args += ['--remote-device-minimum-os', device_minimum_os] | |
| 317 | |
| 318 for d in device_name or []: | |
| 319 amp_args += ['--remote-device', d] | |
| 320 | |
| 321 for d in device_oem or []: | |
| 322 amp_args += ['--device-oem', d] | |
| 323 | |
| 324 for d in device_os or []: | |
| 325 amp_args += ['--remote-device-os', d] | |
| 326 | |
| 327 if device_timeout: | |
| 328 amp_args += ['--remote-device-timeout', device_timeout] | |
| 329 | |
| 330 if network_config: | |
| 331 amp_args += ['--network-config', network_config] | |
| 332 | |
| 333 if test_run_timeout: | |
| 334 amp_args += ['--test-timeout', test_run_timeout] | |
| 335 | |
| 336 return amp_args | |
| 337 | |
| OLD | NEW |