| Index: build/android/pylib/local/device/local_device_instrumentation_test_run.py
|
| diff --git a/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
|
| index 9d6a65d549b14e5723d1a531c4c1fd583f58cd51..d25f242b385619e6c1bc63ec4d06649c4ab03e03 100644
|
| --- a/build/android/pylib/local/device/local_device_instrumentation_test_run.py
|
| +++ b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
|
| @@ -6,6 +6,8 @@ import logging
|
| import os
|
| import posixpath
|
| import re
|
| +import sys
|
| +import tempfile
|
| import time
|
|
|
| from devil.android import device_errors
|
| @@ -16,6 +18,7 @@ from devil.utils import reraiser_thread
|
| from pylib import valgrind_tools
|
| from pylib.android import logdog_logcat_monitor
|
| from pylib.base import base_test_result
|
| +from pylib.constants import host_paths
|
| from pylib.instrumentation import instrumentation_test_instance
|
| from pylib.local.device import local_device_environment
|
| from pylib.local.device import local_device_test_run
|
| @@ -26,6 +29,15 @@ from py_utils import contextlib_ext
|
| from py_utils import tempfile_ext
|
| import tombstones
|
|
|
| +sys.path.append(os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party'))
|
| +import jinja2 # pylint: disable=import-error
|
| +import markupsafe # pylint: disable=import-error,unused-import
|
| +
|
| +
|
| +_JINJA_TEMPLATE_DIR = os.path.join(
|
| + host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'pylib', 'instrumentation')
|
| +_JINJA_TEMPLATE_FILENAME = 'render_test.html.jinja'
|
| +
|
| _TAG = 'test_runner_py'
|
|
|
| TIMEOUT_ANNOTATIONS = [
|
| @@ -43,6 +55,16 @@ LOGCAT_FILTERS = ['*:e', 'chromium:v', 'cr_*:v']
|
| EXTRA_SCREENSHOT_FILE = (
|
| 'org.chromium.base.test.ScreenshotOnFailureStatement.ScreenshotFile')
|
|
|
| +FEATURE_ANNOTATION = 'Feature'
|
| +RENDER_TEST_FEATURE_ANNOTATION = 'RenderTest'
|
| +
|
| +# This needs to be kept in sync with formatting in |RenderUtils.imageName|
|
| +RE_RENDER_IMAGE_NAME = re.compile(
|
| + r'(?P<test_class>\w+)\.'
|
| + r'(?P<description>\w+)\.'
|
| + r'(?P<device_model>\w+)\.'
|
| + r'(?P<orientation>port|land)\.png')
|
| +
|
| # TODO(jbudorick): Make this private once the instrumentation test_runner is
|
| # deprecated.
|
| def DidPackageCrashOnDevice(package_name, device):
|
| @@ -249,7 +271,8 @@ class LocalDeviceInstrumentationTestRun(
|
| def _RunTest(self, device, test):
|
| extras = {}
|
|
|
| - flags = None
|
| + flags_to_add = []
|
| + flags_to_remove = []
|
| test_timeout_scale = None
|
| if self._test_instance.coverage_directory:
|
| coverage_basename = '%s.ec' % ('%s_group' % test[0]['method']
|
| @@ -304,7 +327,8 @@ class LocalDeviceInstrumentationTestRun(
|
| self._test_instance.test_package, self._test_instance.test_runner)
|
| extras['class'] = test_name
|
| if 'flags' in test:
|
| - flags = test['flags']
|
| + flags_to_add.extend(test['flags'].add)
|
| + flags_to_remove.extend(test['flags'].remove)
|
| timeout = self._GetTimeoutFromAnnotations(
|
| test['annotations'], test_display_name)
|
|
|
| @@ -316,10 +340,19 @@ class LocalDeviceInstrumentationTestRun(
|
|
|
| logging.info('preparing to run %s: %s', test_display_name, test)
|
|
|
| - if flags:
|
| + render_tests_device_output_dir = None
|
| + if _IsRenderTest(test):
|
| + # TODO(mikecase): Add DeviceTempDirectory class and use that instead.
|
| + render_tests_device_output_dir = posixpath.join(
|
| + device.GetExternalStoragePath(),
|
| + 'render_test_output_dir')
|
| + flags_to_add.append('--render-test-output-dir=%s' %
|
| + render_tests_device_output_dir)
|
| +
|
| + if flags_to_add or flags_to_remove:
|
| self._CreateFlagChangerIfNeeded(device)
|
| self._flag_changers[str(device)].PushFlags(
|
| - add=flags.add, remove=flags.remove)
|
| + add=flags_to_add, remove=flags_to_remove)
|
|
|
| try:
|
| device.RunShellCommand(
|
| @@ -348,7 +381,7 @@ class LocalDeviceInstrumentationTestRun(
|
| ['log', '-p', 'i', '-t', _TAG, 'END %s' % test_name],
|
| check_return=True)
|
| duration_ms = time_ms() - start_ms
|
| - if flags:
|
| + if flags_to_add or flags_to_remove:
|
| self._flag_changers[str(device)].Restore()
|
| if test_timeout_scale:
|
| valgrind_tools.SetChromeTimeoutScale(
|
| @@ -364,8 +397,19 @@ class LocalDeviceInstrumentationTestRun(
|
| if logcat_url:
|
| result.SetLink('logcat', logcat_url)
|
|
|
| + if _IsRenderTest(test):
|
| + # Render tests do not cause test failure by default. So we have to check
|
| + # to see if any failure images were generated even if the test does not
|
| + # fail.
|
| + try:
|
| + self._ProcessRenderTestResults(
|
| + device, render_tests_device_output_dir, results)
|
| + finally:
|
| + device.RemovePath(render_tests_device_output_dir,
|
| + recursive=True, force=True)
|
| +
|
| # Update the result name if the test used flags.
|
| - if flags:
|
| + if flags_to_add or flags_to_remove:
|
| for r in results:
|
| if r.GetName() == test_name:
|
| r.SetName(test_display_name)
|
| @@ -467,6 +511,97 @@ class LocalDeviceInstrumentationTestRun(
|
| for result in results:
|
| result.SetLink('post_test_screenshot', link)
|
|
|
| + def _ProcessRenderTestResults(
|
| + self, device, render_tests_device_output_dir, results):
|
| + # Will archive test images if we are given a GS bucket to store the results
|
| + # in and are given a results file to output the links to.
|
| + if not bool(self._test_instance.gs_results_bucket):
|
| + return
|
| +
|
| + failure_images_device_dir = posixpath.join(
|
| + render_tests_device_output_dir, 'failures')
|
| +
|
| + if not device.FileExists(failure_images_device_dir):
|
| + return
|
| +
|
| + render_tests_bucket = (
|
| + self._test_instance.gs_results_bucket + '/render_tests')
|
| +
|
| + diff_images_device_dir = posixpath.join(
|
| + render_tests_device_output_dir, 'diffs')
|
| +
|
| + golden_images_device_dir = posixpath.join(
|
| + render_tests_device_output_dir, 'goldens')
|
| +
|
| + with tempfile_ext.NamedTemporaryDirectory() as temp_dir:
|
| + device.PullFile(failure_images_device_dir, temp_dir)
|
| +
|
| + if device.FileExists(diff_images_device_dir):
|
| + device.PullFile(diff_images_device_dir, temp_dir)
|
| + else:
|
| + logging.error('Diff images not found on device.')
|
| +
|
| + if device.FileExists(golden_images_device_dir):
|
| + device.PullFile(golden_images_device_dir, temp_dir)
|
| + else:
|
| + logging.error('Golden images not found on device.')
|
| +
|
| + for failure_filename in os.listdir(os.path.join(temp_dir, 'failures')):
|
| +
|
| + m = RE_RENDER_IMAGE_NAME.match(failure_filename)
|
| + if not m:
|
| + logging.warning('Unexpected file in render test failures: %s',
|
| + failure_filename)
|
| + continue
|
| +
|
| + failure_filepath = os.path.join(temp_dir, 'failures', failure_filename)
|
| + failure_link = google_storage_helper.upload(
|
| + google_storage_helper.unique_name(
|
| + 'failure_%s' % failure_filename, device=device),
|
| + failure_filepath,
|
| + bucket=render_tests_bucket)
|
| +
|
| + golden_filepath = os.path.join(temp_dir, 'goldens', failure_filename)
|
| + if os.path.exists(golden_filepath):
|
| + golden_link = google_storage_helper.upload(
|
| + google_storage_helper.unique_name(
|
| + 'golden_%s' % failure_filename, device=device),
|
| + golden_filepath,
|
| + bucket=render_tests_bucket)
|
| + else:
|
| + golden_link = ''
|
| +
|
| + diff_filepath = os.path.join(temp_dir, 'diffs', failure_filename)
|
| + if os.path.exists(diff_filepath):
|
| + diff_link = google_storage_helper.upload(
|
| + google_storage_helper.unique_name(
|
| + 'diff_%s' % failure_filename, device=device),
|
| + diff_filepath,
|
| + bucket=render_tests_bucket)
|
| + else:
|
| + diff_link = ''
|
| +
|
| + with tempfile.NamedTemporaryFile(suffix='.html') as temp_html:
|
| + jinja2_env = jinja2.Environment(
|
| + loader=jinja2.FileSystemLoader(_JINJA_TEMPLATE_DIR),
|
| + trim_blocks=True)
|
| + template = jinja2_env.get_template(_JINJA_TEMPLATE_FILENAME)
|
| + # pylint: disable=no-member
|
| + processed_template_output = template.render(
|
| + failure_link=failure_link,
|
| + golden_link=golden_link,
|
| + diff_link=diff_link)
|
| +
|
| + temp_html.write(processed_template_output)
|
| + temp_html.flush()
|
| + html_results_link = google_storage_helper.upload(
|
| + google_storage_helper.unique_name('render_html', device=device),
|
| + temp_html.name,
|
| + bucket=render_tests_bucket,
|
| + content_type='text/html')
|
| + for result in results:
|
| + result.SetLink(failure_filename, html_results_link)
|
| +
|
| #override
|
| def _ShouldRetry(self, test):
|
| if 'RetryOnFailure' in test.get('annotations', {}):
|
| @@ -502,3 +637,10 @@ class LocalDeviceInstrumentationTestRun(
|
| timeout *= cls._GetTimeoutScaleFromAnnotations(annotations)
|
|
|
| return timeout
|
| +
|
| +def _IsRenderTest(test):
|
| + """Determines if a test or list of tests has a RenderTest amongst them."""
|
| + if not isinstance(test, list):
|
| + test = [test]
|
| + return any([RENDER_TEST_FEATURE_ANNOTATION in t['annotations'].get(
|
| + FEATURE_ANNOTATION, ()) for t in test])
|
|
|