| 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 1cc82f961405df3dcc7533a7ff1551c743fca76d..7bfe3f5974efed2439e332afc0cd94f7ccd968ab 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,7 @@ import logging
|
| import os
|
| import posixpath
|
| import re
|
| +import tempfile
|
| import time
|
|
|
| from devil.android import device_errors
|
| @@ -14,15 +15,25 @@ from devil.android.sdk import shared_prefs
|
| from devil.utils import reraiser_thread
|
| from pylib import valgrind_tools
|
| from pylib.android import logdog_logcat_monitor
|
| +from pylib.constants import host_paths
|
| from pylib.base import base_test_result
|
| from pylib.instrumentation import instrumentation_test_instance
|
| from pylib.local.device import local_device_environment
|
| from pylib.local.device import local_device_test_run
|
| +from pylib.utils import google_storage_helper
|
| from pylib.utils import logdog_helper
|
| from py_trace_event import trace_event
|
| from py_utils import contextlib_ext
|
| +from py_utils import tempfile_ext
|
| import tombstones
|
|
|
| +try:
|
| + from PIL import Image # pylint: disable=import-error
|
| + from PIL import ImageChops # pylint: disable=import-error
|
| + can_compute_diffs = True
|
| +except ImportError:
|
| + can_compute_diffs = False
|
| +
|
| _TAG = 'test_runner_py'
|
|
|
| TIMEOUT_ANNOTATIONS = [
|
| @@ -35,6 +46,15 @@ TIMEOUT_ANNOTATIONS = [
|
| ('SmallTest', 1 * 60),
|
| ]
|
|
|
| +_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')
|
| +
|
| +RENDER_TESTS_RESULTS_DIR = {
|
| + 'ChromePublicTest': 'chrome/test/data/android/render_tests'
|
| +}
|
|
|
| # TODO(jbudorick): Make this private once the instrumentation test_runner is
|
| # deprecated.
|
| @@ -321,6 +341,8 @@ class LocalDeviceInstrumentationTestRun(
|
| if logcat_url:
|
| result.SetLink('logcat', logcat_url)
|
|
|
| + self._ProcessRenderTestResults(device, results)
|
| +
|
| # Update the result name if the test used flags.
|
| if flags:
|
| for r in results:
|
| @@ -352,11 +374,19 @@ class LocalDeviceInstrumentationTestRun(
|
| file_name = '%s-%s.png' % (
|
| test_display_name,
|
| time.strftime('%Y%m%dT%H%M%S', time.localtime()))
|
| - saved_dir = device.TakeScreenshot(
|
| + screenshot_file = device.TakeScreenshot(
|
| os.path.join(self._test_instance.screenshot_dir, file_name))
|
| logging.info(
|
| 'Saved screenshot for %s to %s.',
|
| - test_display_name, saved_dir)
|
| + test_display_name, screenshot_file)
|
| + if self._test_instance.should_save_images:
|
| + link = google_storage_helper.upload(
|
| + google_storage_helper.unique_name('screenshot', device=device),
|
| + screenshot_file,
|
| + bucket='chromium-render-tests')
|
| + for result in results:
|
| + result.SetLink('failure_screenshot', link)
|
| +
|
| logging.info('detected failure in %s. raw output:', test_display_name)
|
| for l in output:
|
| logging.info(' %s', l)
|
| @@ -368,7 +398,6 @@ class LocalDeviceInstrumentationTestRun(
|
| else None)
|
| device.ClearApplicationState(self._test_instance.package_info.package,
|
| permissions=permissions)
|
| -
|
| else:
|
| logging.debug('raw output from %s:', test_display_name)
|
| for l in output:
|
| @@ -396,6 +425,95 @@ class LocalDeviceInstrumentationTestRun(
|
| result.SetLink('tombstones', tombstones_url)
|
| return results, None
|
|
|
| + def _ProcessRenderTestResults(self, device, results):
|
| + render_results_dir = RENDER_TESTS_RESULTS_DIR.get(self._test_instance.suite)
|
| + if not render_results_dir:
|
| + return
|
| +
|
| + failure_images_device_dir = posixpath.join(
|
| + device.GetExternalStoragePath(),
|
| + 'chromium_tests_root', render_results_dir, 'failures')
|
| + if not device.FileExists(failure_images_device_dir):
|
| + return
|
| +
|
| + if self._test_instance.should_save_images:
|
| + with tempfile_ext.NamedTemporaryDirectory() as temp_dir:
|
| + device.PullFile(failure_images_device_dir, temp_dir)
|
| + device.RemovePath(failure_images_device_dir, recursive=True)
|
| +
|
| + 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_filename, device=device),
|
| + failure_filepath,
|
| + bucket='chromium-render-tests')
|
| +
|
| + golden_filepath = os.path.join(
|
| + host_paths.DIR_SOURCE_ROOT, render_results_dir,
|
| + failure_filename)
|
| + if not os.path.exists(golden_filepath):
|
| + logging.error('Cannot find golden image for %s', failure_filename)
|
| + continue
|
| + golden_link = google_storage_helper.upload(
|
| + google_storage_helper.unique_name(
|
| + failure_filename, device=device),
|
| + golden_filepath,
|
| + bucket='chromium-render-tests')
|
| +
|
| + if can_compute_diffs:
|
| + diff_filename = '_diff'.join(
|
| + os.path.splitext(failure_filename))
|
| + diff_filepath = os.path.join(temp_dir, diff_filename)
|
| + (ImageChops.difference(
|
| + Image.open(failure_filepath), Image.open(golden_filepath))
|
| + .convert('L')
|
| + .point(lambda i: 255 if i else 0)
|
| + .save(diff_filepath))
|
| + diff_link = google_storage_helper.upload(
|
| + google_storage_helper.unique_name(
|
| + diff_filename, device=device),
|
| + diff_filepath,
|
| + bucket='chromium-render-tests')
|
| + else:
|
| + diff_link = ''
|
| + logging.error('Error importing PIL library. Image diffs for '
|
| + 'render test results will not be computed.')
|
| +
|
| + with tempfile.NamedTemporaryFile(suffix='.html') as temp_html:
|
| + temp_html.write('''
|
| + <html>
|
| + <table>
|
| + <tr>
|
| + <th>Failure</th>
|
| + <th>Golden</th>
|
| + <th>Diff</th>
|
| + </tr>
|
| + <tr>
|
| + <td><img src="%s"/></td>
|
| + <td><img src="%s"/></td>
|
| + <td><img src="%s"/></td>
|
| + </tr>
|
| + </table>
|
| + </html>
|
| + ''' % (failure_link, golden_link, diff_link))
|
| + html_results_link = google_storage_helper.upload(
|
| + google_storage_helper.unique_name(
|
| + 'render_html', device=device),
|
| + temp_html.name,
|
| + bucket='chromium-render-tests')
|
| + for result in results:
|
| + result.SetLink(failure_filename, html_results_link)
|
| +
|
| #override
|
| def _ShouldRetry(self, test):
|
| if 'RetryOnFailure' in test.get('annotations', {}):
|
|
|