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 contextlib | 5 import contextlib |
| 6 import logging | 6 import logging |
| 7 import os | 7 import os |
| 8 import posixpath | 8 import posixpath |
| 9 import re | 9 import re |
| 10 import tempfile | 10 import tempfile |
| 11 import time | 11 import time |
| 12 | 12 |
| 13 from devil.android import device_errors | 13 from devil.android import device_errors |
| 14 from devil.android import device_temp_file | 14 from devil.android import device_temp_file |
| 15 from devil.android import flag_changer | 15 from devil.android import flag_changer |
| 16 from devil.android import logcat_monitor | |
| 16 from devil.android.sdk import shared_prefs | 17 from devil.android.sdk import shared_prefs |
| 17 from devil.utils import reraiser_thread | 18 from devil.utils import reraiser_thread |
| 18 from pylib import valgrind_tools | 19 from pylib import valgrind_tools |
| 19 from pylib.android import logdog_logcat_monitor | |
| 20 from pylib.base import base_test_result | 20 from pylib.base import base_test_result |
| 21 from pylib.constants import host_paths | 21 from pylib.constants import host_paths |
| 22 from pylib.instrumentation import instrumentation_test_instance | 22 from pylib.instrumentation import instrumentation_test_instance |
| 23 from pylib.local.device import local_device_environment | 23 from pylib.local.device import local_device_environment |
| 24 from pylib.local.device import local_device_test_run | 24 from pylib.local.device import local_device_test_run |
| 25 from pylib.utils import google_storage_helper | 25 from pylib.utils import test_output_saver_factory |
| 26 from pylib.utils import logdog_helper | |
| 27 from py_trace_event import trace_event | 26 from py_trace_event import trace_event |
| 28 from py_utils import contextlib_ext | 27 from py_utils import contextlib_ext |
| 29 from py_utils import tempfile_ext | 28 from py_utils import tempfile_ext |
| 30 import tombstones | 29 import tombstones |
| 31 | 30 |
| 32 with host_paths.SysPath( | 31 with host_paths.SysPath( |
| 33 os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party'), 0): | 32 os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party'), 0): |
| 34 import jinja2 # pylint: disable=import-error | 33 import jinja2 # pylint: disable=import-error |
| 35 import markupsafe # pylint: disable=import-error,unused-import | 34 import markupsafe # pylint: disable=import-error,unused-import |
| 36 | 35 |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 101 logging.exception('Error while attempting to dismiss crash dialog.') | 100 logging.exception('Error while attempting to dismiss crash dialog.') |
| 102 return False | 101 return False |
| 103 | 102 |
| 104 | 103 |
| 105 _CURRENT_FOCUS_CRASH_RE = re.compile( | 104 _CURRENT_FOCUS_CRASH_RE = re.compile( |
| 106 r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}') | 105 r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}') |
| 107 | 106 |
| 108 | 107 |
| 109 class LocalDeviceInstrumentationTestRun( | 108 class LocalDeviceInstrumentationTestRun( |
| 110 local_device_test_run.LocalDeviceTestRun): | 109 local_device_test_run.LocalDeviceTestRun): |
| 111 def __init__(self, env, test_instance): | 110 def __init__(self, env, test_instance, test_output_saver): |
| 112 super(LocalDeviceInstrumentationTestRun, self).__init__(env, test_instance) | 111 super(LocalDeviceInstrumentationTestRun, self).__init__( |
| 112 env, test_instance, test_output_saver) | |
| 113 self._flag_changers = {} | 113 self._flag_changers = {} |
| 114 self._ui_capture_dir = dict() | 114 self._ui_capture_dir = dict() |
| 115 | 115 |
| 116 #override | 116 #override |
| 117 def TestPackage(self): | 117 def TestPackage(self): |
| 118 return self._test_instance.suite | 118 return self._test_instance.suite |
| 119 | 119 |
| 120 #override | 120 #override |
| 121 def SetUp(self): | 121 def SetUp(self): |
| 122 @local_device_environment.handle_shard_failures_with( | 122 @local_device_environment.handle_shard_failures_with( |
| (...skipping 279 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 402 self._flag_changers[str(device)].PushFlags( | 402 self._flag_changers[str(device)].PushFlags( |
| 403 add=flags_to_add, remove=flags_to_remove) | 403 add=flags_to_add, remove=flags_to_remove) |
| 404 | 404 |
| 405 time_ms = lambda: int(time.time() * 1e3) | 405 time_ms = lambda: int(time.time() * 1e3) |
| 406 start_ms = time_ms() | 406 start_ms = time_ms() |
| 407 | 407 |
| 408 stream_name = 'logcat_%s_%s_%s' % ( | 408 stream_name = 'logcat_%s_%s_%s' % ( |
| 409 test_name.replace('#', '.'), | 409 test_name.replace('#', '.'), |
| 410 time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()), | 410 time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()), |
| 411 device.serial) | 411 device.serial) |
| 412 logmon = logdog_logcat_monitor.LogdogLogcatMonitor( | 412 with tempfile.NamedTemporaryFile() as logcat_tmp: |
| 413 device.adb, stream_name, filter_specs=LOGCAT_FILTERS) | 413 with logcat_monitor.LogcatMonitor( |
|
jbudorick
2017/06/20 14:12:54
I'm wondering if there are situations in which we
mikecase (-- gone --)
2017/07/10 17:11:11
Well, the default is the NoopOutputSaver, which do
| |
| 414 | 414 device.adb, filter_specs=LOGCAT_FILTERS, output_file=logcat_tmp.name): |
| 415 with contextlib_ext.Optional( | 415 with _LogTestEndpoints(device, test_name): |
| 416 logmon, self._test_instance.should_save_logcat): | 416 with contextlib_ext.Optional( |
|
jbudorick
2017/06/20 14:12:54
Does this option do anything after your change?
mikecase (-- gone --)
2017/07/10 17:11:10
Probably not. Would need to get rid of it I suppos
| |
| 417 with _LogTestEndpoints(device, test_name): | 417 trace_event.trace(test_name), |
| 418 with contextlib_ext.Optional( | 418 self._env.trace_output): |
| 419 trace_event.trace(test_name), | 419 output = device.StartInstrumentation( |
| 420 self._env.trace_output): | 420 target, raw=True, extras=extras, timeout=timeout, retries=0) |
| 421 output = device.StartInstrumentation( | 421 logcat_url = self._test_output_saver.Save( |
| 422 target, raw=True, extras=extras, timeout=timeout, retries=0) | 422 logcat_tmp.name, stream_name, 'logcat', |
| 423 | 423 test_output_saver_factory.Datatype.TEXT) |
| 424 logcat_url = logmon.GetLogcatURL() | |
| 425 duration_ms = time_ms() - start_ms | 424 duration_ms = time_ms() - start_ms |
| 426 if flags_to_add or flags_to_remove: | 425 if flags_to_add or flags_to_remove: |
| 427 self._flag_changers[str(device)].Restore() | 426 self._flag_changers[str(device)].Restore() |
| 428 if test_timeout_scale: | 427 if test_timeout_scale: |
| 429 valgrind_tools.SetChromeTimeoutScale( | 428 valgrind_tools.SetChromeTimeoutScale( |
| 430 device, self._test_instance.timeout_scale) | 429 device, self._test_instance.timeout_scale) |
| 431 | 430 |
| 432 # TODO(jbudorick): Make instrumentation tests output a JSON so this | 431 # TODO(jbudorick): Make instrumentation tests output a JSON so this |
| 433 # doesn't have to parse the output. | 432 # doesn't have to parse the output. |
| 434 result_code, result_bundle, statuses = ( | 433 result_code, result_bundle, statuses = ( |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 514 if result.GetType() == base_test_result.ResultType.CRASH: | 513 if result.GetType() == base_test_result.ResultType.CRASH: |
| 515 if not tombstones_url: | 514 if not tombstones_url: |
| 516 resolved_tombstones = tombstones.ResolveTombstones( | 515 resolved_tombstones = tombstones.ResolveTombstones( |
| 517 device, | 516 device, |
| 518 resolve_all_tombstones=True, | 517 resolve_all_tombstones=True, |
| 519 include_stack_symbols=False, | 518 include_stack_symbols=False, |
| 520 wipe_tombstones=True) | 519 wipe_tombstones=True) |
| 521 stream_name = 'tombstones_%s_%s' % ( | 520 stream_name = 'tombstones_%s_%s' % ( |
| 522 time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()), | 521 time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()), |
| 523 device.serial) | 522 device.serial) |
| 524 tombstones_url = logdog_helper.text( | 523 with tempfile.NamedTemporaryFile() as tombstone_tmp: |
| 525 stream_name, '\n'.join(resolved_tombstones)) | 524 tombstone_tmp.write('\n'.join(resolved_tombstones)) |
| 525 tombstones_url = self._test_output_saver.Save( | |
| 526 tombstone_tmp.name, | |
| 527 stream_name, 'tombstones', | |
| 528 test_output_saver_factory.Datatype.TEXT) | |
| 526 result.SetLink('tombstones', tombstones_url) | 529 result.SetLink('tombstones', tombstones_url) |
| 527 return results, None | 530 return results, None |
| 528 | 531 |
| 529 def _SaveScreenshot(self, device, screenshot_host_dir, screenshot_device_file, | 532 def _SaveScreenshot(self, device, screenshot_host_dir, screenshot_device_file, |
| 530 test_name, results): | 533 test_name, results): |
| 531 if screenshot_host_dir: | 534 if screenshot_host_dir: |
| 532 screenshot_host_file = os.path.join( | 535 screenshot_host_file = os.path.join( |
| 533 screenshot_host_dir, | 536 screenshot_host_dir, |
| 534 '%s-%s.png' % ( | 537 '%s-%s.png' % ( |
| 535 test_name, | 538 test_name, |
| 536 time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()))) | 539 time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()))) |
| 537 if device.FileExists(screenshot_device_file.name): | 540 if device.FileExists(screenshot_device_file.name): |
| 538 try: | 541 try: |
| 539 device.PullFile(screenshot_device_file.name, screenshot_host_file) | 542 device.PullFile(screenshot_device_file.name, screenshot_host_file) |
| 540 finally: | 543 finally: |
| 541 screenshot_device_file.close() | 544 screenshot_device_file.close() |
| 542 | 545 |
| 543 logging.info( | 546 logging.info( |
| 544 'Saved screenshot for %s to %s.', | 547 'Saved screenshot for %s to %s.', |
| 545 test_name, screenshot_host_file) | 548 test_name, screenshot_host_file) |
| 546 if self._test_instance.gs_results_bucket: | 549 if self._test_instance.gs_results_bucket: |
| 547 link = google_storage_helper.upload( | 550 link = self._test_output_saver.Save( |
|
jbudorick
2017/06/20 14:12:54
This only saves the screenshot if we've set a resu
mikecase (-- gone --)
2017/07/10 17:11:11
Thanks for this catch. I had fixed this for the re
| |
| 548 google_storage_helper.unique_name( | 551 screenshot_host_file, os.path.join('screenshots', test_name), |
| 549 'screenshot', device=device), | 552 test_output_saver_factory.Datatype.IMAGE) |
| 550 screenshot_host_file, | |
| 551 bucket=('%s/screenshots' % | |
| 552 self._test_instance.gs_results_bucket)) | |
| 553 for result in results: | 553 for result in results: |
| 554 result.SetLink('post_test_screenshot', link) | 554 result.SetLink('post_test_screenshot', link) |
| 555 | 555 |
| 556 def _ProcessRenderTestResults( | 556 def _ProcessRenderTestResults( |
| 557 self, device, render_tests_device_output_dir, results): | 557 self, device, render_tests_device_output_dir, results): |
| 558 # If GS results bucket is specified, will archive render result images. | |
| 559 # If render image dir is specified, will pull the render result image from | |
| 560 # the device and leave in the directory. | |
| 561 if not (bool(self._test_instance.gs_results_bucket) or | |
| 562 bool(self._test_instance.render_results_dir)): | |
| 563 return | |
| 564 | 558 |
| 565 failure_images_device_dir = posixpath.join( | 559 failure_images_device_dir = posixpath.join( |
| 566 render_tests_device_output_dir, 'failures') | 560 render_tests_device_output_dir, 'failures') |
| 567 if not device.FileExists(failure_images_device_dir): | 561 if not device.FileExists(failure_images_device_dir): |
| 568 return | 562 return |
| 569 | 563 |
| 570 diff_images_device_dir = posixpath.join( | 564 diff_images_device_dir = posixpath.join( |
| 571 render_tests_device_output_dir, 'diffs') | 565 render_tests_device_output_dir, 'diffs') |
| 572 | 566 |
| 573 golden_images_device_dir = posixpath.join( | 567 golden_images_device_dir = posixpath.join( |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 589 device.PullFile(diff_images_device_dir, render_host_dir) | 583 device.PullFile(diff_images_device_dir, render_host_dir) |
| 590 else: | 584 else: |
| 591 logging.error('Diff images not found on device.') | 585 logging.error('Diff images not found on device.') |
| 592 | 586 |
| 593 if device.FileExists(golden_images_device_dir): | 587 if device.FileExists(golden_images_device_dir): |
| 594 device.PullFile(golden_images_device_dir, render_host_dir) | 588 device.PullFile(golden_images_device_dir, render_host_dir) |
| 595 else: | 589 else: |
| 596 logging.error('Golden images not found on device.') | 590 logging.error('Golden images not found on device.') |
| 597 | 591 |
| 598 # Upload results to Google Storage. | 592 # Upload results to Google Storage. |
| 599 if self._test_instance.gs_results_bucket: | 593 self._SaveRenderTestResults(render_host_dir, results) |
| 600 self._UploadRenderTestResults(render_host_dir, results) | |
| 601 | 594 |
| 602 def _UploadRenderTestResults(self, render_host_dir, results): | 595 def _SaveRenderTestResults(self, render_host_dir, results): |
| 603 render_tests_bucket = ( | |
| 604 self._test_instance.gs_results_bucket + '/render_tests') | |
| 605 | 596 |
| 606 for failure_filename in os.listdir( | 597 for failure_filename in os.listdir( |
| 607 os.path.join(render_host_dir, 'failures')): | 598 os.path.join(render_host_dir, 'failures')): |
| 608 m = RE_RENDER_IMAGE_NAME.match(failure_filename) | 599 m = RE_RENDER_IMAGE_NAME.match(failure_filename) |
| 609 if not m: | 600 if not m: |
| 610 logging.warning('Unexpected file in render test failures: %s', | 601 logging.warning('Unexpected file in render test failures: %s', |
| 611 failure_filename) | 602 failure_filename) |
| 612 continue | 603 continue |
| 613 | 604 |
| 614 failure_filepath = os.path.join( | 605 failure_filepath = os.path.join( |
| 615 render_host_dir, 'failures', failure_filename) | 606 render_host_dir, 'failures', failure_filename) |
| 616 failure_link = google_storage_helper.upload_content_addressed( | 607 failure_link = self._test_output_saver.Save( |
| 617 failure_filepath, bucket=render_tests_bucket) | 608 failure_filepath, failure_filename, 'render_tests', |
| 609 test_output_saver_factory.Datatype.IMAGE) | |
| 610 | |
| 618 | 611 |
| 619 golden_filepath = os.path.join( | 612 golden_filepath = os.path.join( |
| 620 render_host_dir, 'goldens', failure_filename) | 613 render_host_dir, 'goldens', failure_filename) |
| 621 if os.path.exists(golden_filepath): | 614 if os.path.exists(golden_filepath): |
| 622 golden_link = google_storage_helper.upload_content_addressed( | 615 golden_link = self._test_output_saver.Save( |
| 623 golden_filepath, bucket=render_tests_bucket) | 616 golden_filepath, 'golden_%s' % failure_filename, 'render_tests', |
| 617 test_output_saver_factory.Datatype.IMAGE) | |
| 624 else: | 618 else: |
| 625 golden_link = '' | 619 golden_link = '' |
| 626 | 620 |
| 627 diff_filepath = os.path.join( | 621 diff_filepath = os.path.join( |
| 628 render_host_dir, 'diffs', failure_filename) | 622 render_host_dir, 'diffs', failure_filename) |
| 629 if os.path.exists(diff_filepath): | 623 if os.path.exists(diff_filepath): |
| 630 diff_link = google_storage_helper.upload_content_addressed( | 624 diff_link = self._test_output_saver.Save( |
| 631 diff_filepath, bucket=render_tests_bucket) | 625 diff_filepath, 'diff_%s' % failure_filename, 'render_tests', |
| 626 test_output_saver_factory.Datatype.IMAGE) | |
| 632 else: | 627 else: |
| 633 diff_link = '' | 628 diff_link = '' |
| 634 | 629 |
| 635 with tempfile.NamedTemporaryFile(suffix='.html') as temp_html: | 630 with tempfile.NamedTemporaryFile(suffix='.html') as temp_html: |
| 636 jinja2_env = jinja2.Environment( | 631 jinja2_env = jinja2.Environment( |
| 637 loader=jinja2.FileSystemLoader(_JINJA_TEMPLATE_DIR), | 632 loader=jinja2.FileSystemLoader(_JINJA_TEMPLATE_DIR), |
| 638 trim_blocks=True) | 633 trim_blocks=True) |
| 639 template = jinja2_env.get_template(_JINJA_TEMPLATE_FILENAME) | 634 template = jinja2_env.get_template(_JINJA_TEMPLATE_FILENAME) |
| 640 # pylint: disable=no-member | 635 # pylint: disable=no-member |
| 641 processed_template_output = template.render( | 636 processed_template_output = template.render( |
| 642 test_name=failure_filename, | 637 test_name=failure_filename, |
| 643 failure_link=failure_link, | 638 failure_link=failure_link, |
| 644 golden_link=golden_link, | 639 golden_link=golden_link, |
| 645 diff_link=diff_link) | 640 diff_link=diff_link) |
| 646 | 641 |
| 647 temp_html.write(processed_template_output) | 642 temp_html.write(processed_template_output) |
| 648 temp_html.flush() | 643 temp_html.flush() |
| 649 html_results_link = google_storage_helper.upload_content_addressed( | 644 html_results_link = self._test_output_saver.Save( |
| 650 temp_html.name, | 645 temp_html.name, 'render.html', 'render_tests', |
| 651 bucket=render_tests_bucket, | 646 test_output_saver_factory.Datatype.HTML) |
| 652 content_type='text/html') | |
| 653 for result in results: | 647 for result in results: |
| 654 result.SetLink(failure_filename, html_results_link) | 648 result.SetLink(failure_filename, html_results_link) |
| 655 | 649 |
| 656 #override | 650 #override |
| 657 def _ShouldRetry(self, test): | 651 def _ShouldRetry(self, test): |
| 658 if 'RetryOnFailure' in test.get('annotations', {}): | 652 if 'RetryOnFailure' in test.get('annotations', {}): |
| 659 return True | 653 return True |
| 660 | 654 |
| 661 # TODO(jbudorick): Remove this log message once @RetryOnFailure has been | 655 # TODO(jbudorick): Remove this log message once @RetryOnFailure has been |
| 662 # enabled for a while. See crbug.com/619055 for more details. | 656 # enabled for a while. See crbug.com/619055 for more details. |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 688 timeout *= cls._GetTimeoutScaleFromAnnotations(annotations) | 682 timeout *= cls._GetTimeoutScaleFromAnnotations(annotations) |
| 689 | 683 |
| 690 return timeout | 684 return timeout |
| 691 | 685 |
| 692 def _IsRenderTest(test): | 686 def _IsRenderTest(test): |
| 693 """Determines if a test or list of tests has a RenderTest amongst them.""" | 687 """Determines if a test or list of tests has a RenderTest amongst them.""" |
| 694 if not isinstance(test, list): | 688 if not isinstance(test, list): |
| 695 test = [test] | 689 test = [test] |
| 696 return any([RENDER_TEST_FEATURE_ANNOTATION in t['annotations'].get( | 690 return any([RENDER_TEST_FEATURE_ANNOTATION in t['annotations'].get( |
| 697 FEATURE_ANNOTATION, {}).get('value', ()) for t in test]) | 691 FEATURE_ANNOTATION, {}).get('value', ()) for t in test]) |
| OLD | NEW |