1 # Copyright 2013 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.
5 """Base classes for a test and validator which upload results
6 (reference images, error images) to cloud storage."""
12 from telemetry import test
13 from telemetry.core import bitmap
14 from telemetry.page import cloud_storage
15 from telemetry.page import page_test
17 test_data_dir = os.path.abspath(os.path.join(
18 os.path.dirname(__file__), '..', '..', 'data', 'gpu'))
20 default_generated_data_dir = os.path.join(test_data_dir, 'generated')
22 error_image_cloud_storage_bucket = 'chromium-browser-gpu-tests'
24 class ValidatorBase(page_test.PageTest):
25 def __init__(self, test_method_name):
26 super(ValidatorBase, self).__init__(test_method_name)
27 # Parameters for cloud storage reference images.
30 self.vendor_string = None
31 self.device_string = None
35 ### Routines working with the local disk (only used for local
36 ### testing without a cloud storage account -- the bots do not use
40 def _UrlToImageName(self, url):
41 image_name = re.sub(r'^(http|https|file)://(/*)', '', url)
42 image_name = re.sub(r'\.\./', '', image_name)
43 image_name = re.sub(r'(\.|/|-)', '_', image_name)
46 def _WriteImage(self, image_path, png_image):
47 output_dir = os.path.dirname(image_path)
48 if not os.path.exists(output_dir):
49 os.makedirs(output_dir)
50 png_image.WritePngFile(image_path)
52 def _WriteErrorImages(self, img_dir, img_name, screenshot, ref_png):
53 full_image_name = img_name + '_' + str(self.options.build_revision)
54 full_image_name = full_image_name + '.png'
56 # Always write the failing image.
58 os.path.join(img_dir, 'FAIL_' + full_image_name), screenshot)
61 # Save the reference image.
62 # This ensures that we get the right revision number.
64 os.path.join(img_dir, full_image_name), ref_png)
66 # Save the difference image.
67 diff_png = screenshot.Diff(ref_png)
69 os.path.join(img_dir, 'DIFF_' + full_image_name), diff_png)
72 ### Cloud storage code path -- the bots use this.
75 def _ComputeGpuInfo(self, tab):
76 if ((self.vendor_id and self.device_id) or
77 (self.vendor_string and self.device_string)):
80 if not browser.supports_system_info:
81 raise Exception('System info must be supported by the browser')
82 system_info = browser.GetSystemInfo()
83 if not system_info.gpu:
84 raise Exception('GPU information was absent')
85 device = system_info.gpu.devices[0]
86 if device.vendor_id and device.device_id:
87 self.vendor_id = device.vendor_id
88 self.device_id = device.device_id
89 elif device.vendor_string and device.device_string:
90 self.vendor_string = device.vendor_string
91 self.device_string = device.device_string
93 raise Exception('GPU device information was incomplete')
95 'disable_multisampling' in system_info.gpu.driver_bug_workarounds)
97 def _FormatGpuInfo(self, tab):
98 self._ComputeGpuInfo(tab)
99 msaa_string = '_msaa' if self.msaa else '_non_msaa'
101 return '%s_%04x_%04x%s' % (
102 self.options.os_type, self.vendor_id, self.device_id, msaa_string)
104 return '%s_%s_%s%s' % (
105 self.options.os_type, self.vendor_string, self.device_string,
108 def _FormatReferenceImageName(self, img_name, page, tab):
109 return '%s_v%s_%s.png' % (
112 self._FormatGpuInfo(tab))
114 def _UploadBitmapToCloudStorage(self, bucket, name, bitmap, public=False):
115 # This sequence of steps works on all platforms to write a temporary
116 # PNG to disk, following the pattern in bitmap_unittest.py. The key to
117 # avoiding PermissionErrors seems to be to not actually try to write to
118 # the temporary file object, but to re-open its name for all operations.
119 temp_file = tempfile.NamedTemporaryFile().name
120 bitmap.WritePngFile(temp_file)
121 cloud_storage.Insert(bucket, name, temp_file, publicly_readable=public)
123 def _ConditionallyUploadToCloudStorage(self, img_name, page, tab, screenshot):
124 """Uploads the screenshot to cloud storage as the reference image
125 for this test, unless it already exists. Returns True if the
126 upload was actually performed."""
127 if not self.options.refimg_cloud_storage_bucket:
128 raise Exception('--refimg-cloud-storage-bucket argument is required')
129 cloud_name = self._FormatReferenceImageName(img_name, page, tab)
130 if not cloud_storage.Exists(self.options.refimg_cloud_storage_bucket,
132 self._UploadBitmapToCloudStorage(self.options.refimg_cloud_storage_bucket,
138 def _DownloadFromCloudStorage(self, img_name, page, tab):
139 """Downloads the reference image for the given test from cloud
140 storage, returning it as a Telemetry Bitmap object."""
141 # TODO(kbr): there's a race condition between the deletion of the
142 # temporary file and gsutil's overwriting it.
143 if not self.options.refimg_cloud_storage_bucket:
144 raise Exception('--refimg-cloud-storage-bucket argument is required')
145 temp_file = tempfile.NamedTemporaryFile().name
146 cloud_storage.Get(self.options.refimg_cloud_storage_bucket,
147 self._FormatReferenceImageName(img_name, page, tab),
149 return bitmap.Bitmap.FromPngFile(temp_file)
151 def _UploadErrorImagesToCloudStorage(self, image_name, screenshot, ref_img):
152 """For a failing run, uploads the failing image, reference image (if
153 supplied), and diff image (if reference image was supplied) to cloud
154 storage. This subsumes the functionality of the
155 archive_gpu_pixel_test_results.py script."""
156 machine_name = re.sub('\W+', '_', self.options.test_machine_name)
157 upload_dir = '%s_%s_telemetry' % (self.options.build_revision, machine_name)
158 base_bucket = '%s/runs/%s' % (error_image_cloud_storage_bucket, upload_dir)
159 image_name_with_revision = '%s_%s.png' % (
160 image_name, self.options.build_revision)
161 self._UploadBitmapToCloudStorage(
162 base_bucket + '/gen', image_name_with_revision, screenshot,
165 self._UploadBitmapToCloudStorage(
166 base_bucket + '/ref', image_name_with_revision, ref_img, public=True)
167 diff_img = screenshot.Diff(ref_img)
168 self._UploadBitmapToCloudStorage(
169 base_bucket + '/diff', image_name_with_revision, diff_img,
171 print ('See http://%s.commondatastorage.googleapis.com/'
172 'view_test_results.html?%s for this run\'s test results') % (
173 error_image_cloud_storage_bucket, upload_dir)
175 class TestBase(test.Test):
177 def _AddTestCommandLineOptions(parser, option_group):
178 option_group.add_option('--build-revision',
179 help='Chrome revision being tested.',
180 default="unknownrev")
181 option_group.add_option('--upload-refimg-to-cloud-storage',
182 dest='upload_refimg_to_cloud_storage',
183 action='store_true', default=False,
184 help='Upload resulting images to cloud storage as reference images')
185 option_group.add_option('--download-refimg-from-cloud-storage',
186 dest='download_refimg_from_cloud_storage',
187 action='store_true', default=False,
188 help='Download reference images from cloud storage')
189 option_group.add_option('--refimg-cloud-storage-bucket',
190 help='Name of the cloud storage bucket to use for reference images; '
191 'required with --upload-refimg-to-cloud-storage and '
192 '--download-refimg-from-cloud-storage. Example: '
193 '"chromium-gpu-archive/reference-images"')
194 option_group.add_option('--os-type',
195 help='Type of operating system on which the pixel test is being run, '
196 'used only to distinguish different operating systems with the same '
197 'graphics card. Any value is acceptable, but canonical values are '
198 '"win", "mac", and "linux", and probably, eventually, "chromeos" '
201 option_group.add_option('--test-machine-name',
202 help='Name of the test machine. Specifying this argument causes this '
203 'script to upload failure images and diffs to cloud storage directly, '
204 'instead of relying on the archive_gpu_pixel_test_results.py script.',
206 option_group.add_option('--generated-dir',
207 help='Overrides the default on-disk location for generated test images '
208 '(only used for local testing without a cloud storage account)',
209 default=default_generated_data_dir)