| Index: build/android/render_tests/process_render_test_results.py
|
| diff --git a/build/android/render_tests/process_render_test_results.py b/build/android/render_tests/process_render_test_results.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..9ab0d1b8677e8b805eeab63b37a22edc1cc3ea87
|
| --- /dev/null
|
| +++ b/build/android/render_tests/process_render_test_results.py
|
| @@ -0,0 +1,214 @@
|
| +#!/usr/bin/env python
|
| +#
|
| +# Copyright 2016 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +
|
| +import argparse
|
| +import collections
|
| +import logging
|
| +import os
|
| +import posixpath
|
| +import re
|
| +import shutil
|
| +import sys
|
| +import tempfile
|
| +import zipfile
|
| +
|
| +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir))
|
| +import devil_chromium
|
| +from devil.android import device_utils
|
| +from devil.utils import cmd_helper
|
| +from pylib.constants import host_paths
|
| +
|
| +sys.path.append(os.path.join(host_paths.DIR_SOURCE_ROOT, 'build'))
|
| +import find_depot_tools # pylint: disable=import-error
|
| +
|
| +sys.path.append(os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party'))
|
| +import jinja2 # pylint: disable=import-error
|
| +
|
| +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
|
| + logging.exception('Error importing PIL library. Image diffs will not be '
|
| + 'displayed properly unless PIL module is installed.')
|
| +
|
| +_RE_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_TEST_BASE_URL = 'https://storage.googleapis.com/chromium-render-tests/'
|
| +_RENDER_TEST_BUCKET = 'gs://chromium-render-tests/'
|
| +
|
| +_JINJA_TEMPLATE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| +_JINJA_TEMPLATE_FILENAME = 'render_webpage.html.jinja2'
|
| +
|
| +
|
| +def _UploadFiles(upload_dir, files):
|
| + """Upload files to the render tests GS bucket."""
|
| + if files:
|
| + google_storage_upload_dir = os.path.join(_RENDER_TEST_BUCKET, upload_dir)
|
| + cmd = [os.path.join(find_depot_tools.DEPOT_TOOLS_PATH, 'gsutil.py'),
|
| + '-m', 'cp']
|
| + cmd.extend(files)
|
| + cmd.append(google_storage_upload_dir)
|
| + cmd_helper.RunCmd(cmd)
|
| +
|
| +
|
| +def _GoogleStorageUrl(upload_dir, filename):
|
| + return os.path.join(
|
| + _RENDER_TEST_BASE_URL, upload_dir, os.path.basename(filename))
|
| +
|
| +
|
| +def _ComputeImageDiff(failure_image, golden_image):
|
| + """Compute mask showing which pixels are different between two images."""
|
| + return (ImageChops.difference(failure_image, golden_image)
|
| + .convert('L')
|
| + .point(lambda i: 255 if i else 0))
|
| +
|
| +
|
| +def ProcessRenderTestResults(devices, render_results_dir,
|
| + upload_dir, html_file):
|
| + """Grabs render results from device and generates webpage displaying results.
|
| +
|
| + Args:
|
| + devices: List of DeviceUtils objects to grab results from.
|
| + render_results_path: Path where render test results are storage.
|
| + Will look for failures render test results on the device in
|
| + /sdcard/chromium_tests_root/<render_results_path>/failures/
|
| + and will look for golden images at Chromium src/<render_results_path>/.
|
| + upload_dir: Directory to upload the render test results to.
|
| + html_file: File to write the test results to.
|
| + """
|
| + results_dict = collections.defaultdict(lambda: collections.defaultdict(list))
|
| +
|
| + diff_upload_dir = os.path.join(upload_dir, 'diffs')
|
| + failure_upload_dir = os.path.join(upload_dir, 'failures')
|
| + golden_upload_dir = os.path.join(upload_dir, 'goldens')
|
| +
|
| + diff_images = []
|
| + failure_images = []
|
| + golden_images = []
|
| +
|
| + temp_dir = None
|
| + try:
|
| + temp_dir = tempfile.mkdtemp()
|
| +
|
| + for device in devices:
|
| + failures_device_dir = posixpath.join(
|
| + device.GetExternalStoragePath(),
|
| + 'chromium_tests_root', render_results_dir, 'failures')
|
| + device.PullFile(failures_device_dir, temp_dir)
|
| +
|
| + for failure_filename in os.listdir(os.path.join(temp_dir, 'failures')):
|
| + m = _RE_IMAGE_NAME.match(failure_filename)
|
| + if not m:
|
| + logging.warning(
|
| + 'Unexpected file in render test failures, %s', failure_filename)
|
| + continue
|
| + failure_file = os.path.join(temp_dir, 'failures', failure_filename)
|
| +
|
| + # Check to make sure we have golden image for this failure.
|
| + golden_file = os.path.join(
|
| + host_paths.DIR_SOURCE_ROOT, render_results_dir, failure_filename)
|
| + if not os.path.exists(golden_file):
|
| + logging.error('Cannot find golden image for %s', failure_filename)
|
| + continue
|
| +
|
| + # Compute image diff between failure and golden.
|
| + if can_compute_diffs:
|
| + diff_image = _ComputeImageDiff(
|
| + Image.open(failure_file), Image.open(golden_file))
|
| + diff_filename = '_diff'.join(
|
| + os.path.splitext(os.path.basename(failure_file)))
|
| + diff_file = os.path.join(temp_dir, diff_filename)
|
| + diff_image.save(diff_file)
|
| + diff_images.append(diff_file)
|
| +
|
| + failure_images.append(failure_file)
|
| + golden_images.append(golden_file)
|
| +
|
| + test_class = m.group('test_class')
|
| + device_model = m.group('device_model')
|
| +
|
| + results_entry = {
|
| + 'description': m.group('description'),
|
| + 'orientation': m.group('orientation'),
|
| + 'failure_image': _GoogleStorageUrl(failure_upload_dir, failure_file),
|
| + 'golden_image': _GoogleStorageUrl(golden_upload_dir, golden_file),
|
| + }
|
| + if can_compute_diffs:
|
| + results_entry.update(
|
| + {'diff_image': _GoogleStorageUrl(diff_upload_dir, diff_file)})
|
| + results_dict[test_class][device_model].append(results_entry)
|
| +
|
| + if can_compute_diffs:
|
| + _UploadFiles(diff_upload_dir, diff_images)
|
| + _UploadFiles(failure_upload_dir, failure_images)
|
| + _UploadFiles(golden_upload_dir, golden_images)
|
| +
|
| + if failure_images:
|
| + failures_zipfile = os.path.join(temp_dir, 'failures.zip')
|
| + with zipfile.ZipFile(failures_zipfile, mode='w') as zf:
|
| + for failure_file in failure_images:
|
| + zf.write(failure_file, os.path.join(
|
| + render_results_dir, os.path.basename(failure_file)))
|
| + failure_zip_url = _GoogleStorageUrl(upload_dir, failures_zipfile)
|
| + _UploadFiles(upload_dir, [failures_zipfile])
|
| + else:
|
| + failure_zip_url = None
|
| +
|
| + 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(
|
| + full_results=dict(results_dict),
|
| + failure_zip_url=failure_zip_url, show_diffs=can_compute_diffs)
|
| + # pylint: enable=no-member
|
| + with open(html_file, 'wb') as f:
|
| + f.write(processed_template_output)
|
| + finally:
|
| + if temp_dir:
|
| + shutil.rmtree(temp_dir)
|
| +
|
| +
|
| +def main():
|
| + parser = argparse.ArgumentParser()
|
| +
|
| + parser.add_argument('--render-results-dir',
|
| + required=True,
|
| + help='Path on device to look for render test images')
|
| + parser.add_argument('--output-html-file',
|
| + required=True,
|
| + help='File to output the results webpage.')
|
| + parser.add_argument('-d', '--device', dest='devices', action='append',
|
| + default=[],
|
| + help='Device to look for render test results on. '
|
| + 'Default is to look on all connected devices.')
|
| + parser.add_argument('--adb-path', type=os.path.abspath,
|
| + help='Absolute path to the adb binary to use.')
|
| + parser.add_argument('--buildername', type=str, required=True,
|
| + help='Bot buildername. Used to generate path to upload '
|
| + 'render test results')
|
| + parser.add_argument('--build-number', type=str, required=True,
|
| + help='Bot build number. Used to generate path to upload '
|
| + 'render test results')
|
| +
|
| + args = parser.parse_args()
|
| + devil_chromium.Initialize(adb_path=args.adb_path)
|
| + devices = device_utils.DeviceUtils.HealthyDevices(device_arg=args.devices)
|
| +
|
| + upload_dir = os.path.join(args.buildername, args.build_number)
|
| + ProcessRenderTestResults(
|
| + devices, args.render_results_dir, upload_dir, args.output_html_file)
|
| +
|
| +
|
| +if __name__ == '__main__':
|
| + sys.exit(main())
|
|
|