Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 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 import logging | 5 import logging |
| 6 import os | 6 import os |
| 7 import posixpath | 7 import posixpath |
| 8 import re | 8 import re |
| 9 import time | 9 import time |
| 10 | 10 |
| 11 from devil.android import device_errors | 11 from devil.android import device_errors |
| 12 from devil.android import device_temp_file | |
| 12 from devil.android import flag_changer | 13 from devil.android import flag_changer |
| 13 from devil.android.sdk import shared_prefs | 14 from devil.android.sdk import shared_prefs |
| 14 from devil.utils import reraiser_thread | 15 from devil.utils import reraiser_thread |
| 15 from pylib import valgrind_tools | 16 from pylib import valgrind_tools |
| 16 from pylib.android import logdog_logcat_monitor | 17 from pylib.android import logdog_logcat_monitor |
| 17 from pylib.base import base_test_result | 18 from pylib.base import base_test_result |
| 18 from pylib.instrumentation import instrumentation_test_instance | 19 from pylib.instrumentation import instrumentation_test_instance |
| 19 from pylib.local.device import local_device_environment | 20 from pylib.local.device import local_device_environment |
| 20 from pylib.local.device import local_device_test_run | 21 from pylib.local.device import local_device_test_run |
| 21 from pylib.utils import google_storage_helper | 22 from pylib.utils import google_storage_helper |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 125 def set_debug_app(): | 126 def set_debug_app(): |
| 126 # Set debug app in order to enable reading command line flags on user | 127 # Set debug app in order to enable reading command line flags on user |
| 127 # builds | 128 # builds |
| 128 if self._test_instance.flags: | 129 if self._test_instance.flags: |
| 129 if not self._test_instance.package_info: | 130 if not self._test_instance.package_info: |
| 130 logging.error("Couldn't set debug app: no package info") | 131 logging.error("Couldn't set debug app: no package info") |
| 131 elif not self._test_instance.package_info.package: | 132 elif not self._test_instance.package_info.package: |
| 132 logging.error("Couldn't set debug app: no package defined") | 133 logging.error("Couldn't set debug app: no package defined") |
| 133 else: | 134 else: |
| 134 dev.RunShellCommand(['am', 'set-debug-app', '--persistent', | 135 dev.RunShellCommand(['am', 'set-debug-app', '--persistent', |
| 135 self._test_instance.package_info.package], | 136 self._test_instance.package_info.package], |
| 136 check_return=True) | 137 check_return=True) |
| 137 @trace_event.traced | 138 @trace_event.traced |
| 138 def edit_shared_prefs(): | 139 def edit_shared_prefs(): |
| 139 for pref in self._test_instance.edit_shared_prefs: | 140 for pref in self._test_instance.edit_shared_prefs: |
| 140 prefs = shared_prefs.SharedPrefs(dev, pref['package'], | 141 prefs = shared_prefs.SharedPrefs(dev, pref['package'], |
| 141 pref['filename']) | 142 pref['filename']) |
| 142 prefs.Load() | 143 prefs.Load() |
| 143 for key in pref.get('remove', []): | 144 for key in pref.get('remove', []): |
| 144 try: | 145 try: |
| 145 prefs.Remove(key) | 146 prefs.Remove(key) |
| (...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 239 return tests | 240 return tests |
| 240 | 241 |
| 241 #override | 242 #override |
| 242 def _GetUniqueTestName(self, test): | 243 def _GetUniqueTestName(self, test): |
| 243 return instrumentation_test_instance.GetUniqueTestName(test) | 244 return instrumentation_test_instance.GetUniqueTestName(test) |
| 244 | 245 |
| 245 #override | 246 #override |
| 246 def _RunTest(self, device, test): | 247 def _RunTest(self, device, test): |
| 247 extras = {} | 248 extras = {} |
| 248 | 249 |
| 249 flags = None | 250 flags_to_add = [] |
| 251 flags_to_remove = [] | |
| 250 test_timeout_scale = None | 252 test_timeout_scale = None |
| 251 if self._test_instance.coverage_directory: | 253 if self._test_instance.coverage_directory: |
| 252 coverage_basename = '%s.ec' % ('%s_group' % test[0]['method'] | 254 coverage_basename = '%s.ec' % ('%s_group' % test[0]['method'] |
| 253 if isinstance(test, list) else test['method']) | 255 if isinstance(test, list) else test['method']) |
| 254 extras['coverage'] = 'true' | 256 extras['coverage'] = 'true' |
| 255 coverage_directory = os.path.join( | 257 coverage_directory = os.path.join( |
| 256 device.GetExternalStoragePath(), 'chrome', 'test', 'coverage') | 258 device.GetExternalStoragePath(), 'chrome', 'test', 'coverage') |
| 257 coverage_device_file = os.path.join( | 259 coverage_device_file = os.path.join( |
| 258 coverage_directory, coverage_basename) | 260 coverage_directory, coverage_basename) |
| 259 extras['coverageFile'] = coverage_device_file | 261 extras['coverageFile'] = coverage_device_file |
| 262 # Save screenshot if screenshot dir is specified (save locally) or if | |
| 263 # a GS bucket is passed (save in cloud). | |
| 264 screenshot_device_file = None | |
|
jbudorick
2017/05/08 22:32:06
optional nit: could do something here similar to w
| |
| 265 if (self._test_instance.screenshot_dir or | |
| 266 self._test_instance.gs_results_bucket): | |
| 267 screenshot_device_file = device_temp_file.DeviceTempFile( | |
| 268 device.adb, suffix='.png', dir=device.GetExternalStoragePath()) | |
| 269 flags_to_add.append('--screenshot-file=%s' % screenshot_device_file.name) | |
| 260 | 270 |
| 261 if isinstance(test, list): | 271 if isinstance(test, list): |
| 262 if not self._test_instance.driver_apk: | 272 if not self._test_instance.driver_apk: |
| 263 raise Exception('driver_apk does not exist. ' | 273 raise Exception('driver_apk does not exist. ' |
| 264 'Please build it and try again.') | 274 'Please build it and try again.') |
| 265 if any(t.get('is_junit4') for t in test): | 275 if any(t.get('is_junit4') for t in test): |
| 266 raise Exception('driver apk does not support JUnit4 tests') | 276 raise Exception('driver apk does not support JUnit4 tests') |
| 267 | 277 |
| 268 def name_and_timeout(t): | 278 def name_and_timeout(t): |
| 269 n = instrumentation_test_instance.GetTestName(t) | 279 n = instrumentation_test_instance.GetTestName(t) |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 286 test_display_name = self._GetUniqueTestName(test) | 296 test_display_name = self._GetUniqueTestName(test) |
| 287 if test['is_junit4']: | 297 if test['is_junit4']: |
| 288 target = '%s/%s' % ( | 298 target = '%s/%s' % ( |
| 289 self._test_instance.test_package, | 299 self._test_instance.test_package, |
| 290 self._test_instance.test_runner_junit4) | 300 self._test_instance.test_runner_junit4) |
| 291 else: | 301 else: |
| 292 target = '%s/%s' % ( | 302 target = '%s/%s' % ( |
| 293 self._test_instance.test_package, self._test_instance.test_runner) | 303 self._test_instance.test_package, self._test_instance.test_runner) |
| 294 extras['class'] = test_name | 304 extras['class'] = test_name |
| 295 if 'flags' in test: | 305 if 'flags' in test: |
| 296 flags = test['flags'] | 306 flags_to_add.extend(test['flags'].add) |
| 307 flags_to_remove.extend(test['flags'].remove) | |
| 297 timeout = self._GetTimeoutFromAnnotations( | 308 timeout = self._GetTimeoutFromAnnotations( |
| 298 test['annotations'], test_display_name) | 309 test['annotations'], test_display_name) |
| 299 | 310 |
| 300 test_timeout_scale = self._GetTimeoutScaleFromAnnotations( | 311 test_timeout_scale = self._GetTimeoutScaleFromAnnotations( |
| 301 test['annotations']) | 312 test['annotations']) |
| 302 if test_timeout_scale and test_timeout_scale != 1: | 313 if test_timeout_scale and test_timeout_scale != 1: |
| 303 valgrind_tools.SetChromeTimeoutScale( | 314 valgrind_tools.SetChromeTimeoutScale( |
| 304 device, test_timeout_scale * self._test_instance.timeout_scale) | 315 device, test_timeout_scale * self._test_instance.timeout_scale) |
| 305 | 316 |
| 306 logging.info('preparing to run %s: %s', test_display_name, test) | 317 logging.info('preparing to run %s: %s', test_display_name, test) |
| 307 | 318 |
| 308 if flags: | 319 if flags_to_add or flags_to_remove: |
| 309 self._CreateFlagChangerIfNeeded(device) | 320 self._CreateFlagChangerIfNeeded(device) |
| 310 self._flag_changers[str(device)].PushFlags( | 321 self._flag_changers[str(device)].PushFlags( |
| 311 add=flags.add, remove=flags.remove) | 322 add=flags_to_add, remove=flags_to_remove) |
| 312 | 323 |
| 313 try: | 324 try: |
| 314 device.RunShellCommand( | 325 device.RunShellCommand( |
| 315 ['log', '-p', 'i', '-t', _TAG, 'START %s' % test_name], | 326 ['log', '-p', 'i', '-t', _TAG, 'START %s' % test_name], |
| 316 check_return=True) | 327 check_return=True) |
| 317 time_ms = lambda: int(time.time() * 1e3) | 328 time_ms = lambda: int(time.time() * 1e3) |
| 318 start_ms = time_ms() | 329 start_ms = time_ms() |
| 319 | 330 |
| 320 stream_name = 'logcat_%s_%s_%s' % ( | 331 stream_name = 'logcat_%s_%s_%s' % ( |
| 321 test_name.replace('#', '.'), | 332 test_name.replace('#', '.'), |
| 322 time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()), | 333 time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()), |
| 323 device.serial) | 334 device.serial) |
| 324 logmon = logdog_logcat_monitor.LogdogLogcatMonitor( | 335 logmon = logdog_logcat_monitor.LogdogLogcatMonitor( |
| 325 device.adb, stream_name, filter_specs=LOGCAT_FILTERS) | 336 device.adb, stream_name, filter_specs=LOGCAT_FILTERS) |
| 326 | 337 |
| 327 with contextlib_ext.Optional( | 338 with contextlib_ext.Optional( |
| 328 logmon, self._test_instance.should_save_logcat): | 339 logmon, self._test_instance.should_save_logcat): |
| 329 with contextlib_ext.Optional( | 340 with contextlib_ext.Optional( |
| 330 trace_event.trace(test_name), | 341 trace_event.trace(test_name), |
| 331 self._env.trace_output): | 342 self._env.trace_output): |
| 332 output = device.StartInstrumentation( | 343 output = device.StartInstrumentation( |
| 333 target, raw=True, extras=extras, timeout=timeout, retries=0) | 344 target, raw=True, extras=extras, timeout=timeout, retries=0) |
| 334 logcat_url = logmon.GetLogcatURL() | 345 logcat_url = logmon.GetLogcatURL() |
| 335 finally: | 346 finally: |
| 336 device.RunShellCommand( | 347 device.RunShellCommand( |
| 337 ['log', '-p', 'i', '-t', _TAG, 'END %s' % test_name], | 348 ['log', '-p', 'i', '-t', _TAG, 'END %s' % test_name], |
| 338 check_return=True) | 349 check_return=True) |
| 339 duration_ms = time_ms() - start_ms | 350 duration_ms = time_ms() - start_ms |
| 340 if flags: | 351 if flags_to_add or flags_to_remove: |
| 341 self._flag_changers[str(device)].Restore() | 352 self._flag_changers[str(device)].Restore() |
| 342 if test_timeout_scale: | 353 if test_timeout_scale: |
| 343 valgrind_tools.SetChromeTimeoutScale( | 354 valgrind_tools.SetChromeTimeoutScale( |
| 344 device, self._test_instance.timeout_scale) | 355 device, self._test_instance.timeout_scale) |
| 345 | 356 |
| 346 # TODO(jbudorick): Make instrumentation tests output a JSON so this | 357 # TODO(jbudorick): Make instrumentation tests output a JSON so this |
| 347 # doesn't have to parse the output. | 358 # doesn't have to parse the output. |
| 348 result_code, result_bundle, statuses = ( | 359 result_code, result_bundle, statuses = ( |
| 349 self._test_instance.ParseAmInstrumentRawOutput(output)) | 360 self._test_instance.ParseAmInstrumentRawOutput(output)) |
| 350 results = self._test_instance.GenerateTestResults( | 361 results = self._test_instance.GenerateTestResults( |
| 351 result_code, result_bundle, statuses, start_ms, duration_ms) | 362 result_code, result_bundle, statuses, start_ms, duration_ms) |
| 352 for result in results: | 363 for result in results: |
| 353 if logcat_url: | 364 if logcat_url: |
| 354 result.SetLink('logcat', logcat_url) | 365 result.SetLink('logcat', logcat_url) |
| 355 | 366 |
| 356 # Update the result name if the test used flags. | 367 # Update the result name if the test used flags. |
| 357 if flags: | 368 if flags_to_add or flags_to_remove: |
| 358 for r in results: | 369 for r in results: |
| 359 if r.GetName() == test_name: | 370 if r.GetName() == test_name: |
| 360 r.SetName(test_display_name) | 371 r.SetName(test_display_name) |
| 361 | 372 |
| 362 # Add UNKNOWN results for any missing tests. | 373 # Add UNKNOWN results for any missing tests. |
| 363 iterable_test = test if isinstance(test, list) else [test] | 374 iterable_test = test if isinstance(test, list) else [test] |
| 364 test_names = set(self._GetUniqueTestName(t) for t in iterable_test) | 375 test_names = set(self._GetUniqueTestName(t) for t in iterable_test) |
| 365 results_names = set(r.GetName() for r in results) | 376 results_names = set(r.GetName() for r in results) |
| 366 results.extend( | 377 results.extend( |
| 367 base_test_result.BaseTestResult(u, base_test_result.ResultType.UNKNOWN) | 378 base_test_result.BaseTestResult(u, base_test_result.ResultType.UNKNOWN) |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 379 # - clearing the application state while persisting permissions | 390 # - clearing the application state while persisting permissions |
| 380 if any(r.GetType() not in (base_test_result.ResultType.PASS, | 391 if any(r.GetType() not in (base_test_result.ResultType.PASS, |
| 381 base_test_result.ResultType.SKIP) | 392 base_test_result.ResultType.SKIP) |
| 382 for r in results): | 393 for r in results): |
| 383 with contextlib_ext.Optional( | 394 with contextlib_ext.Optional( |
| 384 tempfile_ext.NamedTemporaryDirectory(), | 395 tempfile_ext.NamedTemporaryDirectory(), |
| 385 self._test_instance.screenshot_dir is None and | 396 self._test_instance.screenshot_dir is None and |
| 386 self._test_instance.gs_results_bucket) as screenshot_host_dir: | 397 self._test_instance.gs_results_bucket) as screenshot_host_dir: |
| 387 screenshot_host_dir = ( | 398 screenshot_host_dir = ( |
| 388 self._test_instance.screenshot_dir or screenshot_host_dir) | 399 self._test_instance.screenshot_dir or screenshot_host_dir) |
| 389 if screenshot_host_dir: | 400 self._SaveScreenshot(device, screenshot_host_dir, |
| 390 file_name = '%s-%s.png' % ( | 401 screenshot_device_file, test_display_name, |
| 391 test_display_name, | 402 results) |
| 392 time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime())) | |
| 393 screenshot_file = device.TakeScreenshot( | |
| 394 os.path.join(screenshot_host_dir, file_name)) | |
| 395 logging.info( | |
| 396 'Saved screenshot for %s to %s.', | |
| 397 test_display_name, screenshot_file) | |
| 398 if self._test_instance.gs_results_bucket: | |
| 399 link = google_storage_helper.upload( | |
| 400 google_storage_helper.unique_name('screenshot', device=device), | |
| 401 screenshot_file, | |
| 402 bucket=self._test_instance.gs_results_bucket + '/screenshots') | |
| 403 for result in results: | |
| 404 result.SetLink('post_test_screenshot', link) | |
| 405 | 403 |
| 406 logging.info('detected failure in %s. raw output:', test_display_name) | 404 logging.info('detected failure in %s. raw output:', test_display_name) |
| 407 for l in output: | 405 for l in output: |
| 408 logging.info(' %s', l) | 406 logging.info(' %s', l) |
| 409 if (not self._env.skip_clear_data | 407 if (not self._env.skip_clear_data |
| 410 and self._test_instance.package_info): | 408 and self._test_instance.package_info): |
| 411 permissions = ( | 409 permissions = ( |
| 412 self._test_instance.apk_under_test.GetPermissions() | 410 self._test_instance.apk_under_test.GetPermissions() |
| 413 if self._test_instance.apk_under_test | 411 if self._test_instance.apk_under_test |
| 414 else None) | 412 else None) |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 435 include_stack_symbols=False, | 433 include_stack_symbols=False, |
| 436 wipe_tombstones=True) | 434 wipe_tombstones=True) |
| 437 stream_name = 'tombstones_%s_%s' % ( | 435 stream_name = 'tombstones_%s_%s' % ( |
| 438 time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()), | 436 time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()), |
| 439 device.serial) | 437 device.serial) |
| 440 tombstones_url = logdog_helper.text( | 438 tombstones_url = logdog_helper.text( |
| 441 stream_name, '\n'.join(resolved_tombstones)) | 439 stream_name, '\n'.join(resolved_tombstones)) |
| 442 result.SetLink('tombstones', tombstones_url) | 440 result.SetLink('tombstones', tombstones_url) |
| 443 return results, None | 441 return results, None |
| 444 | 442 |
| 443 def _SaveScreenshot(self, device, screenshot_host_dir, screenshot_device_file, | |
| 444 test_name, results): | |
| 445 if screenshot_host_dir: | |
| 446 screenshot_host_file = os.path.join( | |
| 447 screenshot_host_dir, | |
| 448 '%s-%s.png' % ( | |
| 449 test_name, | |
| 450 time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()))) | |
| 451 if device.FileExists(screenshot_device_file.name): | |
| 452 try: | |
| 453 device.PullFile(screenshot_device_file.name, screenshot_host_file) | |
| 454 finally: | |
| 455 screenshot_device_file.close() | |
| 456 | |
| 457 logging.info( | |
| 458 'Saved screenshot for %s to %s.', | |
| 459 test_name, screenshot_host_file) | |
| 460 if self._test_instance.gs_results_bucket: | |
| 461 link = google_storage_helper.upload( | |
| 462 google_storage_helper.unique_name( | |
| 463 'screenshot', device=device), | |
| 464 screenshot_host_file, | |
| 465 bucket=('%s/screenshots' % | |
| 466 self._test_instance.gs_results_bucket)) | |
| 467 for result in results: | |
| 468 result.SetLink('post_test_screenshot', link) | |
| 469 | |
| 445 #override | 470 #override |
| 446 def _ShouldRetry(self, test): | 471 def _ShouldRetry(self, test): |
| 447 if 'RetryOnFailure' in test.get('annotations', {}): | 472 if 'RetryOnFailure' in test.get('annotations', {}): |
| 448 return True | 473 return True |
| 449 | 474 |
| 450 # TODO(jbudorick): Remove this log message once @RetryOnFailure has been | 475 # TODO(jbudorick): Remove this log message once @RetryOnFailure has been |
| 451 # enabled for a while. See crbug.com/619055 for more details. | 476 # enabled for a while. See crbug.com/619055 for more details. |
| 452 logging.error('Default retries are being phased out. crbug.com/619055') | 477 logging.error('Default retries are being phased out. crbug.com/619055') |
| 453 return False | 478 return False |
| 454 | 479 |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 470 if k in annotations: | 495 if k in annotations: |
| 471 timeout = v | 496 timeout = v |
| 472 break | 497 break |
| 473 else: | 498 else: |
| 474 logging.warning('Using default 1 minute timeout for %s', test_name) | 499 logging.warning('Using default 1 minute timeout for %s', test_name) |
| 475 timeout = 60 | 500 timeout = 60 |
| 476 | 501 |
| 477 timeout *= cls._GetTimeoutScaleFromAnnotations(annotations) | 502 timeout *= cls._GetTimeoutScaleFromAnnotations(annotations) |
| 478 | 503 |
| 479 return timeout | 504 return timeout |
| OLD | NEW |