Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(202)

Side by Side Diff: build/android/pylib/local/device/local_device_instrumentation_test_run.py

Issue 2933993002: Add local results details pages.
Patch Set: Add --local-output arg which enables local results detail pages. Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
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])
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698