Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(61)

Side by Side Diff: build/android/render_tests/process_render_test_results.py

Issue 2406593002: Add script to process results from Android render tests. (Closed)
Patch Set: Made script fail kinda gracefully if PIL fails to import Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | build/android/render_tests/render_webpage.html.jinja2 » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 collections
9 import logging
10 import os
11 import posixpath
12 import re
13 import shutil
14 import sys
15 import tempfile
16 import zipfile
17
18 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir))
19 import devil_chromium
20 from devil.android import device_utils
21 from devil.utils import cmd_helper
22 from pylib.constants import host_paths
23
24 sys.path.append(os.path.join(host_paths.DIR_SOURCE_ROOT, 'build'))
25 import find_depot_tools # pylint: disable=import-error
26
27 sys.path.append(os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party'))
28 import jinja2 # pylint: disable=import-error
29
30 _RE_IMAGE_NAME = re.compile(
31 r'(?P<test_class>\w+)\.'
32 r'(?P<description>\w+)\.'
33 r'(?P<device_model>\w+)\.'
34 r'(?P<orientation>port|land)\.png')
35
36 _RENDER_TEST_BASE_URL = 'https://storage.googleapis.com/chromium-render-tests/'
37 _RENDER_TEST_BUCKET = 'gs://chromium-render-tests/'
38
39 _JINJA_TEMPLATE_DIR = os.path.dirname(os.path.abspath(__file__))
40 _JINJA_TEMPLATE_FILENAME = 'render_webpage.html.jinja2'
41
42
43 def _UploadFiles(upload_dir, files):
44 """Upload files to the render tests GS bucket."""
45 google_storage_upload_dir = os.path.join(_RENDER_TEST_BUCKET, upload_dir)
46 cmd = [os.path.join(find_depot_tools.DEPOT_TOOLS_PATH, 'gsutil.py'),
47 '-m', 'cp']
48 cmd.extend(files)
49 cmd.append(google_storage_upload_dir)
50 cmd_helper.RunCmd(cmd)
51
52
53 def _GoogleStorageUrl(upload_dir, filename):
54 return os.path.join(
55 _RENDER_TEST_BASE_URL, upload_dir, os.path.basename(filename))
56
57
58 def _SaveImageDiff(failure_image_file, golden_image_file, diff_output_file):
59 """Saves mask showing which pixels are different between two images.
60
61 Args:
62 failure_image_file: File containing failure image.
63 golden_image_file: File containing golden image.
64 diff_output_file: File to output image diff.
65 """
66 try:
67 from PIL import ImageChops # pylint: disable=import-error
68 from PIL import Image # pylint: disable=import-error
69
70 diff_image = (ImageChops.difference(Image.open(failure_image_file),
71 Image.open(golden_image_file))
72 .convert('L')
73 .point(lambda i: 255 if i else 0))
74 diff_image.save(diff_output_file)
75 except ImportError:
76 logging.exception('Error importing PIL library. Image diffs will not be '
77 'displayed properly unless PIL module is installed.')
78
79
80 def ProcessRenderTestResults(devices, render_results_dir,
81 upload_dir, html_file):
82 """Grabs render results from device and generates webpage displaying results.
83
84 Args:
85 devices: List of DeviceUtils objects to grab results from.
86 render_results_path: Path where render test results are storage.
87 Will look for failures render test results on the device in
88 /sdcard/chromium_tests_root/<render_results_path>/failures/
89 and will look for golden images at Chromium src/<render_results_path>/.
90 upload_dir: Directory to upload the render test results to.
91 html_file: File to write the test results to.
92 """
93 results_dict = collections.defaultdict(lambda: collections.defaultdict(list))
94
95 diff_upload_dir = os.path.join(upload_dir, 'diffs')
96 failure_upload_dir = os.path.join(upload_dir, 'failures')
97 golden_upload_dir = os.path.join(upload_dir, 'goldens')
98
99 diff_images = []
100 failure_images = []
101 golden_images = []
102
103 temp_dir = None
104 try:
105 temp_dir = tempfile.mkdtemp()
106
107 for device in devices:
108 failures_device_dir = posixpath.join(
109 device.GetExternalStoragePath(),
110 'chromium_tests_root', render_results_dir, 'failures')
111 device.PullFile(failures_device_dir, temp_dir)
112
113 for failure_filename in os.listdir(temp_dir):
114 m = _RE_IMAGE_NAME.match(failure_filename)
115 if not m:
116 logging.warning(
117 'Unexpected file in render test failures, %s', failure_filename)
118 continue
119 failure_file = os.path.join(temp_dir, failure_filename)
120
121 # Check to make sure we have golden image for this failure.
122 golden_file = os.path.join(
123 host_paths.DIR_SOURCE_ROOT, render_results_dir, failure_filename)
124 if not os.path.exists(golden_file):
125 logging.error('Cannot find golden image for %s', failure_filename)
126 continue
127
128 # Compute image diff between failure and golden.
129 diff_filename = '_diff'.join(
130 os.path.splitext(os.path.basename(failure_file)))
131 diff_file = os.path.join(temp_dir, diff_filename)
132 _SaveImageDiff(failure_file, golden_file, diff_file)
133
134 diff_images.append(diff_file)
jbudorick 2016/11/07 22:49:08 Should this still be populated if we fail to creat
mikecase (-- gone --) 2016/11/10 22:43:03 Changed so that now if PIL is not available, we wo
135 failure_images.append(failure_file)
136 golden_images.append(golden_file)
137
138 test_class = m.group('test_class')
139 device_model = m.group('device_model')
140 results_dict[test_class][device_model].append({
141 'description': m.group('description'),
142 'orientation': m.group('orientation'),
143 'diff_image': _GoogleStorageUrl(diff_upload_dir, diff_file),
jbudorick 2016/11/07 22:49:08 Same.
144 'failure_image': _GoogleStorageUrl(failure_upload_dir, failure_file),
145 'golden_image': _GoogleStorageUrl(golden_upload_dir, golden_file),
146 })
147
148 _UploadFiles(diff_upload_dir, diff_images)
149 _UploadFiles(failure_upload_dir, failure_images)
150 _UploadFiles(golden_upload_dir, golden_images)
151
152 if failure_images:
153 failures_zipfile = os.path.join(temp_dir, 'failures.zip')
154 with zipfile.ZipFile(failures_zipfile, mode='w') as zf:
155 for failure_file in failure_images:
156 zf.write(failure_file, os.path.join(
157 render_results_dir, os.path.basename(failure_file)))
158 failure_zip_url = _GoogleStorageUrl(upload_dir, failures_zipfile)
159 _UploadFiles(upload_dir, [failures_zipfile])
160 else:
161 failure_zip_url = None
162
163 jinja2_env = jinja2.Environment(
164 loader=jinja2.FileSystemLoader(_JINJA_TEMPLATE_DIR),
165 trim_blocks=True)
166 template = jinja2_env.get_template(_JINJA_TEMPLATE_FILENAME)
167 # pylint: disable=no-member
168 processed_template_output = template.render(
169 full_results=dict(results_dict), failure_zip_url=failure_zip_url)
170 # pylint: enable=no-member
171 with open(html_file, 'wb') as f:
172 f.write(processed_template_output)
173 finally:
174 if temp_dir:
175 shutil.rmtree(temp_dir)
176
177
178 def main():
179 parser = argparse.ArgumentParser()
180
181 parser.add_argument('--render-results-dir',
182 required=True,
183 help='Path on device to look for render test images')
184 parser.add_argument('--output-html-file',
185 required=True,
186 help='File to output the results webpage.')
187 parser.add_argument('-d', '--device', dest='devices', action='append',
188 default=[],
189 help='Device to look for render test results on. '
190 'Default is to look on all connected devices.')
191 parser.add_argument('--adb-path', type=os.path.abspath,
192 help='Absolute path to the adb binary to use.')
193 parser.add_argument('--buildername', type=str, required=True,
194 help='Bot buildername. Used to generate path to upload '
195 'render test results')
196 parser.add_argument('--build-number', type=str, required=True,
197 help='Bot build number. Used to generate path to upload '
198 'render test results')
199
200 args = parser.parse_args()
201 devil_chromium.Initialize(adb_path=args.adb_path)
202 devices = device_utils.DeviceUtils.HealthyDevices(device_arg=args.devices)
203
204 upload_dir = os.path.join(args.buildername, args.build_number)
205 ProcessRenderTestResults(
206 devices, args.render_results_dir, upload_dir, args.output_html_file)
207
208
209 if __name__ == '__main__':
210 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | build/android/render_tests/render_webpage.html.jinja2 » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698