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

Side by Side Diff: content/test/gpu/gpu_tests/cloud_storage_integration_test_base.py

Issue 2363343002: Ported pixel_test to new gpu_integration_test harness. (Closed)
Patch Set: Update revision for OffscreenCanvasWebGLGreenBox. Created 4 years, 2 months 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
OLDNEW
(Empty)
1 # Copyright 2016 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Base classes for a test which uploads results (reference images,
6 error images) to cloud storage."""
7
8 import logging
9 import os
10 import re
11 import tempfile
12
13 from py_utils import cloud_storage
14 from telemetry.util import image_util
15 from telemetry.util import rgba_color
16
17 from gpu_tests import gpu_integration_test
18
19 test_data_dir = os.path.abspath(os.path.join(
20 os.path.dirname(__file__), '..', '..', 'data', 'gpu'))
21
22 default_generated_data_dir = os.path.join(test_data_dir, 'generated')
23
24 error_image_cloud_storage_bucket = 'chromium-browser-gpu-tests'
25
26
27 class _ReferenceImageParameters(object):
28 def __init__(self):
29 # Parameters for cloud storage reference images.
30 self.vendor_id = None
31 self.device_id = None
32 self.vendor_string = None
33 self.device_string = None
34 self.msaa = False
35 self.model_name = None
36
37
38 class CloudStorageIntegrationTestBase(gpu_integration_test.GpuIntegrationTest):
39 # This class is abstract; don't warn about the superclass's abstract
40 # methods that aren't overridden.
41 # pylint: disable=abstract-method
42
43 # This information is class-scoped, so that it can be shared across
44 # invocations of tests; but it's zapped every time the browser is
45 # restarted with different command line arguments.
46 _reference_image_parameters = None
47
48 # The command line options (which are passed to subclasses'
49 # GenerateGpuTests) *must* be configured here, via a call to
50 # SetParsedCommandLineOptions. If they are not, an error will be
51 # raised when running the tests.
52 _parsed_command_line_options = None
53
54 @classmethod
55 def SetParsedCommandLineOptions(cls, options):
56 cls._parsed_command_line_options = options
57
58 @classmethod
59 def GetParsedCommandLineOptions(cls):
60 return cls._parsed_command_line_options
61
62 @classmethod
63 def AddCommandlineArgs(cls, parser):
64 parser.add_option(
65 '--build-revision',
66 help='Chrome revision being tested.',
67 default="unknownrev")
68 parser.add_option(
69 '--upload-refimg-to-cloud-storage',
70 dest='upload_refimg_to_cloud_storage',
71 action='store_true', default=False,
72 help='Upload resulting images to cloud storage as reference images')
73 parser.add_option(
74 '--download-refimg-from-cloud-storage',
75 dest='download_refimg_from_cloud_storage',
76 action='store_true', default=False,
77 help='Download reference images from cloud storage')
78 parser.add_option(
79 '--refimg-cloud-storage-bucket',
80 help='Name of the cloud storage bucket to use for reference images; '
81 'required with --upload-refimg-to-cloud-storage and '
82 '--download-refimg-from-cloud-storage. Example: '
83 '"chromium-gpu-archive/reference-images"')
84 parser.add_option(
85 '--os-type',
86 help='Type of operating system on which the pixel test is being run, '
87 'used only to distinguish different operating systems with the same '
88 'graphics card. Any value is acceptable, but canonical values are '
89 '"win", "mac", and "linux", and probably, eventually, "chromeos" '
90 'and "android").',
91 default='')
92 parser.add_option(
93 '--test-machine-name',
94 help='Name of the test machine. Specifying this argument causes this '
95 'script to upload failure images and diffs to cloud storage directly, '
96 'instead of relying on the archive_gpu_pixel_test_results.py script.',
97 default='')
98 parser.add_option(
99 '--generated-dir',
100 help='Overrides the default on-disk location for generated test images '
101 '(only used for local testing without a cloud storage account)',
102 default=default_generated_data_dir)
103
104 def _CompareScreenshotSamples(self, tab, screenshot, expected_colors,
105 device_pixel_ratio, test_machine_name):
106 # First scan through the expected_colors and see if there are any scale
107 # factor overrides that would preempt the device pixel ratio. This
108 # is mainly a workaround for complex tests like the Maps test.
109 for expectation in expected_colors:
110 if 'scale_factor_overrides' in expectation:
111 for override in expectation['scale_factor_overrides']:
112 # Require exact matches to avoid confusion, because some
113 # machine models and names might be subsets of others
114 # (e.g. Nexus 5 vs Nexus 5X).
115 if ('device_type' in override and
116 (tab.browser.platform.GetDeviceTypeName() ==
117 override['device_type'])):
118 logging.warning(
119 'Overriding device_pixel_ratio ' + str(device_pixel_ratio) +
120 ' with scale factor ' + str(override['scale_factor']) +
121 ' for device type ' + override['device_type'])
122 device_pixel_ratio = override['scale_factor']
123 break
124 if (test_machine_name and 'machine_name' in override and
125 override["machine_name"] == test_machine_name):
126 logging.warning(
127 'Overriding device_pixel_ratio ' + str(device_pixel_ratio) +
128 ' with scale factor ' + str(override['scale_factor']) +
129 ' for machine name ' + test_machine_name)
130 device_pixel_ratio = override['scale_factor']
131 break
132 # Only support one "scale_factor_overrides" in the expectation format.
133 break
134 for expectation in expected_colors:
135 if "scale_factor_overrides" in expectation:
136 continue
137 location = expectation["location"]
138 size = expectation["size"]
139 x0 = int(location[0] * device_pixel_ratio)
140 x1 = int((location[0] + size[0]) * device_pixel_ratio)
141 y0 = int(location[1] * device_pixel_ratio)
142 y1 = int((location[1] + size[1]) * device_pixel_ratio)
143 for x in range(x0, x1):
144 for y in range(y0, y1):
145 if (x < 0 or y < 0 or x >= image_util.Width(screenshot) or
146 y >= image_util.Height(screenshot)):
147 self.fail(
148 ('Expected pixel location [%d, %d] is out of range on ' +
149 '[%d, %d] image') %
150 (x, y, image_util.Width(screenshot),
151 image_util.Height(screenshot)))
152
153 actual_color = image_util.GetPixelColor(screenshot, x, y)
154 expected_color = rgba_color.RgbaColor(
155 expectation["color"][0],
156 expectation["color"][1],
157 expectation["color"][2])
158 if not actual_color.IsEqual(expected_color, expectation["tolerance"]):
159 self.fail('Expected pixel at ' + str(location) +
160 ' (actual pixel (' + str(x) + ', ' + str(y) + ')) ' +
161 ' to be ' +
162 str(expectation["color"]) + " but got [" +
163 str(actual_color.r) + ", " +
164 str(actual_color.g) + ", " +
165 str(actual_color.b) + "]")
166
167 ###
168 ### Routines working with the local disk (only used for local
169 ### testing without a cloud storage account -- the bots do not use
170 ### this code path).
171 ###
172
173 def _UrlToImageName(self, url):
174 image_name = re.sub(r'^(http|https|file)://(/*)', '', url)
175 image_name = re.sub(r'\.\./', '', image_name)
176 image_name = re.sub(r'(\.|/|-)', '_', image_name)
177 return image_name
178
179 def _WriteImage(self, image_path, png_image):
180 output_dir = os.path.dirname(image_path)
181 if not os.path.exists(output_dir):
182 os.makedirs(output_dir)
183 image_util.WritePngFile(png_image, image_path)
184
185 def _WriteErrorImages(self, img_dir, img_name, screenshot, ref_png):
186 full_image_name = img_name + '_' + str(
187 self.GetParsedCommandLineOptions().build_revision)
188 full_image_name = full_image_name + '.png'
189
190 # Always write the failing image.
191 self._WriteImage(
192 os.path.join(img_dir, 'FAIL_' + full_image_name), screenshot)
193
194 if ref_png is not None:
195 # Save the reference image.
196 # This ensures that we get the right revision number.
197 self._WriteImage(
198 os.path.join(img_dir, full_image_name), ref_png)
199
200 # Save the difference image.
201 diff_png = image_util.Diff(screenshot, ref_png)
202 self._WriteImage(
203 os.path.join(img_dir, 'DIFF_' + full_image_name), diff_png)
204
205 ###
206 ### Cloud storage code path -- the bots use this.
207 ###
208
209 @classmethod
210 def ResetGpuInfo(cls):
211 cls._reference_image_parameters = None
212
213 @classmethod
214 def _ComputeGpuInfo(cls, tab):
215 if cls._reference_image_parameters:
216 return
217 browser = cls.browser
218 if not browser.supports_system_info:
219 raise Exception('System info must be supported by the browser')
220 system_info = browser.GetSystemInfo()
221 if not system_info.gpu:
222 raise Exception('GPU information was absent')
223 device = system_info.gpu.devices[0]
224 cls._reference_image_parameters = _ReferenceImageParameters()
225 params = cls._reference_image_parameters
226 if device.vendor_id and device.device_id:
227 params.vendor_id = device.vendor_id
228 params.device_id = device.device_id
229 elif device.vendor_string and device.device_string:
230 params.vendor_string = device.vendor_string
231 params.device_string = device.device_string
232 else:
233 raise Exception('GPU device information was incomplete')
234 # TODO(senorblanco): This should probably be checking
235 # for the presence of the extensions in system_info.gpu_aux_attributes
236 # in order to check for MSAA, rather than sniffing the blacklist.
237 params.msaa = not (
238 ('disable_chromium_framebuffer_multisample' in
239 system_info.gpu.driver_bug_workarounds) or
240 ('disable_multisample_render_to_texture' in
241 system_info.gpu.driver_bug_workarounds))
242 params.model_name = system_info.model_name
243
244 @classmethod
245 def _FormatGpuInfo(cls, tab):
246 cls._ComputeGpuInfo(tab)
247 params = cls._reference_image_parameters
248 msaa_string = '_msaa' if params.msaa else '_non_msaa'
249 if params.vendor_id:
250 return '%s_%04x_%04x%s' % (
251 cls.GetParsedCommandLineOptions().os_type, params.vendor_id,
252 params.device_id, msaa_string)
253 else:
254 # This is the code path for Android devices. Include the model
255 # name (e.g. "Nexus 9") in the GPU string to disambiguate
256 # multiple devices on the waterfall which might have the same
257 # device string ("NVIDIA Tegra") but different screen
258 # resolutions and device pixel ratios.
259 return '%s_%s_%s_%s%s' % (
260 cls.GetParsedCommandLineOptions().os_type,
261 params.vendor_string, params.device_string,
262 params.model_name, msaa_string)
263
264 @classmethod
265 def _FormatReferenceImageName(cls, img_name, page, tab):
266 return '%s_v%s_%s.png' % (
267 img_name,
268 page.revision,
269 cls._FormatGpuInfo(tab))
270
271 @classmethod
272 def _UploadBitmapToCloudStorage(cls, bucket, name, bitmap, public=False):
273 # This sequence of steps works on all platforms to write a temporary
274 # PNG to disk, following the pattern in bitmap_unittest.py. The key to
275 # avoiding PermissionErrors seems to be to not actually try to write to
276 # the temporary file object, but to re-open its name for all operations.
277 temp_file = tempfile.NamedTemporaryFile(suffix='.png').name
278 image_util.WritePngFile(bitmap, temp_file)
279 cloud_storage.Insert(bucket, name, temp_file, publicly_readable=public)
280
281 @classmethod
282 def _ConditionallyUploadToCloudStorage(cls, img_name, page, tab, screenshot):
283 """Uploads the screenshot to cloud storage as the reference image
284 for this test, unless it already exists. Returns True if the
285 upload was actually performed."""
286 if not cls.GetParsedCommandLineOptions().refimg_cloud_storage_bucket:
287 raise Exception('--refimg-cloud-storage-bucket argument is required')
288 cloud_name = cls._FormatReferenceImageName(img_name, page, tab)
289 if not cloud_storage.Exists(
290 cls.GetParsedCommandLineOptions().refimg_cloud_storage_bucket,
291 cloud_name):
292 cls._UploadBitmapToCloudStorage(
293 cls.GetParsedCommandLineOptions().refimg_cloud_storage_bucket,
294 cloud_name,
295 screenshot)
296 return True
297 return False
298
299 @classmethod
300 def _DownloadFromCloudStorage(cls, img_name, page, tab):
301 """Downloads the reference image for the given test from cloud
302 storage, returning it as a Telemetry Bitmap object."""
303 # TODO(kbr): there's a race condition between the deletion of the
304 # temporary file and gsutil's overwriting it.
305 if not cls.GetParsedCommandLineOptions().refimg_cloud_storage_bucket:
306 raise Exception('--refimg-cloud-storage-bucket argument is required')
307 temp_file = tempfile.NamedTemporaryFile(suffix='.png').name
308 cloud_storage.Get(
309 cls.GetParsedCommandLineOptions().refimg_cloud_storage_bucket,
310 cls._FormatReferenceImageName(img_name, page, tab),
311 temp_file)
312 return image_util.FromPngFile(temp_file)
313
314 @classmethod
315 def _UploadErrorImagesToCloudStorage(cls, image_name, screenshot, ref_img):
316 """For a failing run, uploads the failing image, reference image (if
317 supplied), and diff image (if reference image was supplied) to cloud
318 storage. This subsumes the functionality of the
319 archive_gpu_pixel_test_results.py script."""
320 machine_name = re.sub(r'\W+', '_',
321 cls.GetParsedCommandLineOptions().test_machine_name)
322 upload_dir = '%s_%s_telemetry' % (
323 cls.GetParsedCommandLineOptions().build_revision, machine_name)
324 base_bucket = '%s/runs/%s' % (error_image_cloud_storage_bucket, upload_dir)
325 image_name_with_revision = '%s_%s.png' % (
326 image_name, cls.GetParsedCommandLineOptions().build_revision)
327 cls._UploadBitmapToCloudStorage(
328 base_bucket + '/gen', image_name_with_revision, screenshot,
329 public=True)
330 if ref_img is not None:
331 cls._UploadBitmapToCloudStorage(
332 base_bucket + '/ref', image_name_with_revision, ref_img, public=True)
333 diff_img = image_util.Diff(screenshot, ref_img)
334 cls._UploadBitmapToCloudStorage(
335 base_bucket + '/diff', image_name_with_revision, diff_img,
336 public=True)
337 print ('See http://%s.commondatastorage.googleapis.com/'
338 'view_test_results.html?%s for this run\'s test results') % (
339 error_image_cloud_storage_bucket, upload_dir)
340
341 def _ValidateScreenshotSamples(self, tab, url,
342 screenshot, expectations, device_pixel_ratio):
343 """Samples the given screenshot and verifies pixel color values.
344 The sample locations and expected color values are given in expectations.
345 In case any of the samples do not match the expected color, it raises
346 a Failure and dumps the screenshot locally or cloud storage depending on
347 what machine the test is being run."""
348 try:
349 self._CompareScreenshotSamples(
350 tab, screenshot, expectations,
351 device_pixel_ratio,
352 self.GetParsedCommandLineOptions().test_machine_name)
353 except Exception:
354 # An exception raised from self.fail() indicates a failure.
355 image_name = self._UrlToImageName(url)
356 if self.GetParsedCommandLineOptions().test_machine_name:
357 self._UploadErrorImagesToCloudStorage(image_name, screenshot, None)
358 else:
359 self._WriteErrorImages(
360 self.GetParsedCommandLineOptions().generated_dir, image_name,
361 screenshot, None)
362 raise
OLDNEW
« no previous file with comments | « content/test/gpu/generate_buildbot_json.py ('k') | content/test/gpu/gpu_tests/gpu_integration_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698