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 re | |
| 11 import shutil | |
| 12 import sys | |
| 13 import tempfile | |
| 14 from PIL import ImageChops | |
| 15 from PIL import Image | |
| 16 | |
| 17 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) | |
| 18 import devil_chromium | |
| 19 from devil.android import device_utils | |
| 20 from devil.utils import cmd_helper | |
| 21 from pylib.constants import host_paths | |
| 22 | |
| 23 sys.path.append(os.path.abspath( | |
| 24 os.path.join(host_paths.DIR_SOURCE_ROOT, 'build'))) | |
| 25 import find_depot_tools # pylint: disable=import-error,unused-import | |
| 26 import download_from_google_storage | |
| 27 | |
| 28 sys.path.append(os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party')) | |
| 29 import jinja2 # pylint: disable=F0401 | |
| 30 | |
| 31 _RE_IMAGE_NAME = re.compile( | |
| 32 r'(?P<test_class>\w+)\.(?P<description>\w+)\.' | |
| 33 r'(?P<device_model>\w+)\.(?P<orientation>port|land)\.png') | |
| 34 | |
| 35 _RENDER_TEST_BASE_URL = 'https://storage.googleapis.com/chromium-render-tests/' | |
| 36 _RENDER_TEST_BUCKET = 'gs://chromium-render-tests/' | |
| 37 | |
| 38 _JINJA_TEMPLATE_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| 39 _JINJA_TEMPLATE_FILENAME = 'render_webpage.html.jinja2' | |
| 40 | |
| 41 | |
| 42 def _UploadImage(upload_dir, image_file): | |
| 43 """Upload image to the render tests GS bucket. | |
|
the real yoland
2016/10/13 22:03:38
Would it be better to have recipe control the name
| |
| 44 | |
| 45 Returns: | |
| 46 Web link to the uploaded image. | |
| 47 """ | |
| 48 google_storage_path = os.path.join( | |
| 49 _RENDER_TEST_BUCKET, upload_dir, os.path.basename(image_file)) | |
| 50 google_storage_url = os.path.join( | |
| 51 _RENDER_TEST_BASE_URL, upload_dir, os.path.basename(image_file)) | |
| 52 cmd_helper.RunCmd( | |
| 53 [download_from_google_storage.GSUTIL_DEFAULT_PATH, | |
| 54 'cp', image_file, google_storage_path]) | |
| 55 return google_storage_url | |
| 56 | |
| 57 | |
| 58 def _ComputeImageDiff(failure_image, golden_image): | |
| 59 """Compute mask showing which pixels are different between two images.""" | |
| 60 diff_image = ImageChops.difference(failure_image, golden_image) | |
| 61 diff_image = diff_image.convert('L') | |
|
the real yoland
2016/10/13 22:03:38
nit: maybe just
```
return ImageChops.difference(f
mikecase (-- gone --)
2016/10/18 16:00:12
Done
| |
| 62 diff_image = diff_image.point(lambda i: 255 if i else 0) | |
|
PEConn
2016/10/14 09:08:35
ImageMagick can do a really nice diff where you've
mikecase (-- gone --)
2016/10/18 16:00:12
I've never used ImageMagick. Is it available in Ch
PEConn
2016/10/19 10:34:54
ImageMagick is a command line tool (it may be alre
| |
| 63 return diff_image | |
| 64 | |
| 65 | |
| 66 def ProcessRenderTestResults(devices, render_results_dir, | |
| 67 upload_dir, html_file): | |
| 68 """Grabs render results from device and generates webpage displaying results. | |
| 69 | |
| 70 Args: | |
| 71 devices: List of DeviceUtils objects to grab results from. | |
| 72 render_results_dir: Directory on the devices to look for results. Assumes | |
| 73 directory given contains the golden images for the tests and a directory | |
| 74 "failures" containing images for failed tests. | |
| 75 upload_dir: Directory to upload the render test results to. | |
| 76 html_file: File to write the test results to. | |
| 77 """ | |
| 78 results_dict = {} | |
| 79 | |
| 80 temp_dir = None | |
| 81 try: | |
| 82 temp_dir = tempfile.mkdtemp() | |
| 83 for device in devices: | |
| 84 device.adb.Pull(render_results_dir, temp_dir) | |
| 85 | |
| 86 failure_dir = os.path.join(temp_dir, 'failures') | |
| 87 | |
| 88 for failure_filename in os.listdir(failure_dir): | |
| 89 m = _RE_IMAGE_NAME.match(failure_filename) | |
| 90 if not m: | |
| 91 continue | |
| 92 | |
| 93 # Check to make sure we have golden image for this failure. | |
| 94 golden_file = os.path.join(temp_dir, failure_filename) | |
|
the real yoland
2016/10/13 22:03:38
hmm, out of the curiosity, when is the golden imag
PEConn
2016/10/14 09:08:35
The goldens are stored in src/chrome/test/data/and
the real yoland
2016/10/14 18:37:37
+peconn
Not sure how the entire of the process wo
PEConn
2016/10/19 10:34:54
I can't think of how it would reduce the flexibili
| |
| 95 if not os.path.exists(golden_file): | |
| 96 logging.error('Cannot find golden image for %s', failure_filename) | |
| 97 continue | |
| 98 | |
| 99 failure_file = os.path.join(failure_dir, failure_filename) | |
| 100 | |
| 101 # Compute image diff between failure and golden. | |
| 102 diff_image = _ComputeImageDiff( | |
| 103 Image.open(failure_file), Image.open(golden_file)) | |
| 104 diff_filename = '_diff'.join( | |
| 105 os.path.splitext(os.path.basename(failure_file))) | |
| 106 diff_file = os.path.join(temp_dir, diff_filename) | |
| 107 diff_image.save(diff_file) | |
| 108 | |
| 109 failure_image_url = _UploadImage(upload_dir, failure_file) | |
| 110 golden_image_url = _UploadImage(upload_dir, golden_file) | |
| 111 diff_image_url = _UploadImage(upload_dir, diff_file) | |
| 112 | |
| 113 test_class = m.group('test_class') | |
| 114 device_model = m.group('device_model') | |
| 115 | |
| 116 if test_class not in results_dict: | |
|
the real yoland
2016/10/13 22:03:38
you can use collections.defaultdict here
results_d
the real yoland
2016/10/14 18:59:25
you have to change it back to dict at the end thou
mikecase (-- gone --)
2016/10/18 16:00:13
Done
| |
| 117 results_dict[test_class] = {} | |
| 118 | |
| 119 if device_model not in results_dict[test_class]: | |
| 120 results_dict[test_class][device_model] = [] | |
| 121 | |
| 122 results_dict[test_class][device_model].append({ | |
| 123 'description': m.group('description'), | |
| 124 'orientation': m.group('orientation'), | |
| 125 'failure_image': failure_image_url, | |
| 126 'golden_image': golden_image_url, | |
| 127 'diff_image': diff_image_url, | |
| 128 }) | |
| 129 | |
| 130 jinja2_env = jinja2.Environment( | |
| 131 loader=jinja2.FileSystemLoader(_JINJA_TEMPLATE_DIR), | |
| 132 trim_blocks=True) | |
| 133 template = jinja2_env.get_template(_JINJA_TEMPLATE_FILENAME) | |
| 134 processed_template_output = template.render(full_results=results_dict) | |
| 135 with open(html_file, "wb") as f: | |
| 136 f.write(processed_template_output) | |
| 137 finally: | |
| 138 if temp_dir: | |
| 139 shutil.rmtree(temp_dir) | |
| 140 | |
| 141 def main(): | |
| 142 parser = argparse.ArgumentParser() | |
| 143 | |
| 144 parser.add_argument('--render-results-dir', | |
| 145 required=True, | |
| 146 help='Path on device to look for render test images') | |
|
PEConn
2016/10/11 12:20:07
At the moment all the render results go in 'chrome
mikecase (-- gone --)
2016/10/18 16:00:13
Noted
| |
| 147 parser.add_argument('--output-html-file', | |
| 148 required=True, | |
| 149 help='File to output the results webpage.') | |
| 150 parser.add_argument('-d', '--device', dest='devices', action='append', | |
| 151 default=[], | |
| 152 help='Device to look for render test results on. ' | |
| 153 'Default is to look on all connected devices.') | |
| 154 parser.add_argument('--adb-path', type=os.path.abspath, | |
| 155 help='Absolute path to the adb binary to use.') | |
| 156 parser.add_argument('--upload-dir', | |
| 157 help='Root path to upload files to GS. Recommend this ' | |
| 158 'be set to a combination of buildername and build ' | |
| 159 'number for bots.') | |
| 160 | |
| 161 args = parser.parse_args() | |
| 162 devil_chromium.Initialize(adb_path=args.adb_path) | |
| 163 devices = device_utils.DeviceUtils.HealthyDevices(device_arg=args.devices) | |
| 164 ProcessRenderTestResults( | |
| 165 devices, args.render_results_dir, args.upload_dir, args.output_html_file) | |
| 166 | |
| 167 | |
| 168 if __name__ == '__main__': | |
| 169 sys.exit(main()) | |
| OLD | NEW |