Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python | |
| 2 # | |
| 3 # Copyright 2016 The Chromium Authors. All rights reserved. | |
| 4 # Use of this source code is governed by a BSD-style license that can be | |
| 5 # found in the LICENSE file. | |
| 6 | |
| 7 import argparse | |
| 8 import logging | |
| 9 import os | |
| 10 import posixpath | |
| 11 import re | |
| 12 import shutil | |
| 13 import sys | |
| 14 import tempfile | |
| 15 import zipfile | |
| 16 from PIL import ImageChops | |
| 17 from PIL import Image | |
| 18 from collections import defaultdict | |
|
jbudorick
2016/10/19 16:11:32
nit: import collections, then use collections.defa
mikecase (-- gone --)
2016/10/19 19:04:44
Done
| |
| 19 | |
| 20 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) | |
| 21 import devil_chromium | |
| 22 from devil.android import device_utils | |
| 23 from devil.utils import cmd_helper | |
| 24 from pylib.constants import host_paths | |
| 25 | |
| 26 sys.path.append(os.path.abspath( | |
|
jbudorick
2016/10/19 16:11:32
nit: This doesn't need to be abspath, DIR_SOURCE_R
mikecase (-- gone --)
2016/10/19 19:04:44
Done
| |
| 27 os.path.join(host_paths.DIR_SOURCE_ROOT, 'build'))) | |
| 28 import find_depot_tools # pylint: disable=import-error,unused-import | |
| 29 import download_from_google_storage | |
| 30 | |
| 31 sys.path.append(os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party')) | |
| 32 import jinja2 # pylint: disable=import-error | |
| 33 | |
| 34 _RE_IMAGE_NAME = re.compile( | |
| 35 r'(?P<test_class>\w+)\.(?P<description>\w+)\.' | |
|
jbudorick
2016/10/19 16:11:32
super nit: I would split this s.t. each capture is
mikecase (-- gone --)
2016/10/19 19:04:44
Done
| |
| 36 r'(?P<device_model>\w+)\.(?P<orientation>port|land)\.png') | |
| 37 | |
| 38 _RENDER_TEST_BASE_URL = 'https://storage.googleapis.com/chromium-render-tests/' | |
| 39 _RENDER_TEST_BUCKET = 'gs://chromium-render-tests/' | |
| 40 | |
| 41 _JINJA_TEMPLATE_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| 42 _JINJA_TEMPLATE_FILENAME = 'render_webpage.html.jinja2' | |
| 43 | |
| 44 _CHROMIUM_SRC = os.path.join( | |
|
jbudorick
2016/10/19 16:11:32
This is host_paths.DIR_SOURCE_ROOT.
mikecase (-- gone --)
2016/10/19 19:04:43
Done
| |
| 45 os.path.dirname(__file__), os.pardir, os.pardir, os.pardir) | |
| 46 | |
| 47 | |
| 48 def _UploadFiles(upload_dir, files): | |
| 49 """Upload files to the render tests GS bucket.""" | |
| 50 google_storage_upload_dir = os.path.join(_RENDER_TEST_BUCKET, upload_dir) | |
| 51 cmd = [download_from_google_storage.GSUTIL_DEFAULT_PATH, '-m', 'cp'] | |
|
jbudorick
2016/10/19 16:11:33
using "download_from_google_storage" to upload fil
mikecase (-- gone --)
2016/10/19 19:04:44
I thought it was silly too.
upload_to_google_stor
| |
| 52 cmd.extend(files) | |
| 53 cmd.append(google_storage_upload_dir) | |
| 54 cmd_helper.RunCmd(cmd) | |
| 55 | |
| 56 | |
| 57 def _GoogleStorageUrl(upload_dir, filename): | |
| 58 return os.path.join( | |
| 59 _RENDER_TEST_BASE_URL, upload_dir, os.path.basename(filename)) | |
| 60 | |
| 61 | |
| 62 def _ComputeImageDiff(failure_image, golden_image): | |
| 63 """Compute mask showing which pixels are different between two images.""" | |
| 64 return (ImageChops.difference(failure_image, golden_image) | |
| 65 .convert('L') | |
| 66 .point(lambda i: 255 if i else 0)) | |
| 67 | |
| 68 | |
| 69 def ProcessRenderTestResults(devices, render_results_dir, | |
| 70 upload_dir, html_file): | |
| 71 """Grabs render results from device and generates webpage displaying results. | |
| 72 | |
| 73 Args: | |
| 74 devices: List of DeviceUtils objects to grab results from. | |
| 75 render_results_path: Path where render test results are storage. | |
| 76 Will look for failures render test results on the device in | |
| 77 /sdcard/chromium_tests_root/<render_results_path>/failures/ | |
| 78 and will look for golden images at Chromium src/<render_results_path>/. | |
| 79 upload_dir: Directory to upload the render test results to. | |
| 80 html_file: File to write the test results to. | |
| 81 """ | |
| 82 results_dict = defaultdict(lambda: defaultdict(list)) | |
| 83 | |
| 84 diff_upload_dir = os.path.join(upload_dir, 'diffs') | |
| 85 failure_upload_dir = os.path.join(upload_dir, 'failures') | |
| 86 golden_upload_dir = os.path.join(upload_dir, 'goldens') | |
| 87 | |
| 88 image_files = { | |
|
jbudorick
2016/10/19 16:11:33
Why aren't these just three separate lists?
mikecase (-- gone --)
2016/10/19 19:04:44
Done
| |
| 89 'diffs': [], | |
| 90 'failures': [], | |
| 91 'goldens': [], | |
| 92 } | |
| 93 | |
| 94 temp_dir = None | |
| 95 try: | |
| 96 temp_dir = tempfile.mkdtemp() | |
| 97 | |
| 98 for device in devices: | |
| 99 failures_device_dir = posixpath.join( | |
| 100 device.GetExternalStoragePath(), | |
| 101 'chromium_tests_root', render_results_dir, 'failures') | |
| 102 device.adb.Pull(failures_device_dir, temp_dir) | |
|
jbudorick
2016/10/19 16:11:32
device.PullFile plz
mikecase (-- gone --)
2016/10/19 19:04:44
Hmmm, didn't know this would work. Name and docstr
| |
| 103 | |
| 104 for failure_filename in os.listdir(temp_dir): | |
| 105 m = _RE_IMAGE_NAME.match(failure_filename) | |
| 106 if not m: | |
|
jbudorick
2016/10/19 16:11:33
This should log something if we hit an unexpected
mikecase (-- gone --)
2016/10/19 19:04:44
Done
| |
| 107 continue | |
| 108 failure_file = os.path.join(temp_dir, failure_filename) | |
| 109 | |
| 110 # Check to make sure we have golden image for this failure. | |
| 111 golden_file = os.path.join( | |
| 112 _CHROMIUM_SRC, render_results_dir, failure_filename) | |
| 113 if not os.path.exists(golden_file): | |
| 114 logging.error('Cannot find golden image for %s', failure_filename) | |
| 115 continue | |
| 116 | |
| 117 # Compute image diff between failure and golden. | |
| 118 diff_image = _ComputeImageDiff( | |
| 119 Image.open(failure_file), Image.open(golden_file)) | |
| 120 diff_filename = '_diff'.join( | |
| 121 os.path.splitext(os.path.basename(failure_file))) | |
| 122 diff_file = os.path.join(temp_dir, diff_filename) | |
| 123 diff_image.save(diff_file) | |
| 124 | |
| 125 image_files['diffs'].append(diff_file) | |
| 126 image_files['failures'].append(failure_file) | |
| 127 image_files['goldens'].append(golden_file) | |
|
mikecase (-- gone --)
2016/10/18 16:00:13
I dont upload the file immediately since it is muc
| |
| 128 | |
| 129 test_class = m.group('test_class') | |
| 130 device_model = m.group('device_model') | |
| 131 results_dict[test_class][device_model].append({ | |
| 132 'description': m.group('description'), | |
| 133 'orientation': m.group('orientation'), | |
| 134 'diff_image': _GoogleStorageUrl(diff_upload_dir, diff_file), | |
| 135 'failure_image': _GoogleStorageUrl(failure_upload_dir, failure_file), | |
| 136 'golden_image': _GoogleStorageUrl(golden_upload_dir, golden_file), | |
| 137 }) | |
| 138 | |
| 139 failures_zipfile = os.path.join(temp_dir, 'failures.zip') | |
|
mikecase (-- gone --)
2016/10/19 19:04:44
Added check so that no zip is created/uploaded if
| |
| 140 with zipfile.ZipFile(failures_zipfile, mode='w') as zf: | |
| 141 for failure_file in image_files['failures']: | |
| 142 zf.write(failure_file, os.path.join( | |
| 143 render_results_dir, os.path.basename(failure_file))) | |
| 144 failure_zip_url = _GoogleStorageUrl(upload_dir, failures_zipfile) | |
| 145 | |
| 146 _UploadFiles(diff_upload_dir, image_files['diffs']) | |
| 147 _UploadFiles(failure_upload_dir, image_files['failures']) | |
| 148 _UploadFiles(golden_upload_dir, image_files['goldens']) | |
| 149 _UploadFiles(upload_dir, [failures_zipfile]) | |
| 150 | |
| 151 jinja2_env = jinja2.Environment( | |
| 152 loader=jinja2.FileSystemLoader(_JINJA_TEMPLATE_DIR), | |
| 153 trim_blocks=True) | |
| 154 template = jinja2_env.get_template(_JINJA_TEMPLATE_FILENAME) | |
| 155 processed_template_output = template.render( | |
| 156 full_results=dict(results_dict), failure_zip_url=failure_zip_url) | |
| 157 with open(html_file, "wb") as f: | |
|
jbudorick
2016/10/19 16:11:33
nit: single quotes
mikecase (-- gone --)
2016/10/19 19:04:44
Done
| |
| 158 f.write(processed_template_output) | |
| 159 finally: | |
| 160 if temp_dir: | |
|
jbudorick
2016/10/19 16:11:32
we really ought to extract https://codesearch.chro
mikecase (-- gone --)
2016/10/19 19:04:44
Ack
| |
| 161 shutil.rmtree(temp_dir) | |
| 162 | |
| 163 | |
| 164 def main(): | |
| 165 parser = argparse.ArgumentParser() | |
| 166 | |
| 167 parser.add_argument('--render-results-dir', | |
| 168 required=True, | |
| 169 help='Path on device to look for render test images') | |
| 170 parser.add_argument('--output-html-file', | |
| 171 required=True, | |
| 172 help='File to output the results webpage.') | |
| 173 parser.add_argument('-d', '--device', dest='devices', action='append', | |
| 174 default=[], | |
| 175 help='Device to look for render test results on. ' | |
| 176 'Default is to look on all connected devices.') | |
| 177 parser.add_argument('--adb-path', type=os.path.abspath, | |
| 178 help='Absolute path to the adb binary to use.') | |
| 179 parser.add_argument('--buildername', type=str, required=True, | |
| 180 help='Bot buildername. Used to generate path to upload ' | |
| 181 'render test results') | |
| 182 parser.add_argument('--build-number', type=str, required=True, | |
| 183 help='Bot build number. Used to generate path to upload ' | |
| 184 'render test results') | |
| 185 | |
| 186 args = parser.parse_args() | |
| 187 devil_chromium.Initialize(adb_path=args.adb_path) | |
| 188 devices = device_utils.DeviceUtils.HealthyDevices(device_arg=args.devices) | |
| 189 | |
| 190 upload_dir = os.path.join(args.buildername, args.build_number) | |
| 191 ProcessRenderTestResults( | |
| 192 devices, args.render_results_dir, upload_dir, args.output_html_file) | |
| 193 | |
| 194 | |
| 195 if __name__ == '__main__': | |
| 196 sys.exit(main()) | |
| OLD | NEW |