Home | History | Annotate | Download | only in gpu_tests
      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.
      4 
      5 """Base classes for a test and validator which upload results
      6 (reference images, error images) to cloud storage."""
      7 
      8 import os
      9 import re
     10 import tempfile
     11 
     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
     16 
     17 test_data_dir = os.path.abspath(os.path.join(
     18     os.path.dirname(__file__), '..', '..', 'data', 'gpu'))
     19 
     20 default_generated_data_dir = os.path.join(test_data_dir, 'generated')
     21 
     22 error_image_cloud_storage_bucket = 'chromium-browser-gpu-tests'
     23 
     24 def _CompareScreenshotSamples(screenshot, expectations, device_pixel_ratio):
     25   for expectation in expectations:
     26     location = expectation["location"]
     27     x = int(location[0] * device_pixel_ratio)
     28     y = int(location[1] * device_pixel_ratio)
     29 
     30     if x < 0 or y < 0 or x > screenshot.width or y > screenshot.height:
     31       raise page_test.Failure(
     32           'Expected pixel location [%d, %d] is out of range on [%d, %d] image' %
     33           (x, y, screenshot.width, screenshot.height))
     34 
     35     actual_color = screenshot.GetPixelColor(x, y)
     36     expected_color = bitmap.RgbaColor(
     37         expectation["color"][0],
     38         expectation["color"][1],
     39         expectation["color"][2])
     40     if not actual_color.IsEqual(expected_color, expectation["tolerance"]):
     41       raise page_test.Failure('Expected pixel at ' + str(location) +
     42           ' to be ' +
     43           str(expectation["color"]) + " but got [" +
     44           str(actual_color.r) + ", " +
     45           str(actual_color.g) + ", " +
     46           str(actual_color.b) + "]")
     47 
     48 class ValidatorBase(page_test.PageTest):
     49   def __init__(self):
     50     super(ValidatorBase, self).__init__()
     51     # Parameters for cloud storage reference images.
     52     self.vendor_id = None
     53     self.device_id = None
     54     self.vendor_string = None
     55     self.device_string = None
     56     self.msaa = False
     57 
     58   ###
     59   ### Routines working with the local disk (only used for local
     60   ### testing without a cloud storage account -- the bots do not use
     61   ### this code path).
     62   ###
     63 
     64   def _UrlToImageName(self, url):
     65     image_name = re.sub(r'^(http|https|file)://(/*)', '', url)
     66     image_name = re.sub(r'\.\./', '', image_name)
     67     image_name = re.sub(r'(\.|/|-)', '_', image_name)
     68     return image_name
     69 
     70   def _WriteImage(self, image_path, png_image):
     71     output_dir = os.path.dirname(image_path)
     72     if not os.path.exists(output_dir):
     73       os.makedirs(output_dir)
     74     png_image.WritePngFile(image_path)
     75 
     76   def _WriteErrorImages(self, img_dir, img_name, screenshot, ref_png):
     77     full_image_name = img_name + '_' + str(self.options.build_revision)
     78     full_image_name = full_image_name + '.png'
     79 
     80     # Always write the failing image.
     81     self._WriteImage(
     82         os.path.join(img_dir, 'FAIL_' + full_image_name), screenshot)
     83 
     84     if ref_png:
     85       # Save the reference image.
     86       # This ensures that we get the right revision number.
     87       self._WriteImage(
     88           os.path.join(img_dir, full_image_name), ref_png)
     89 
     90       # Save the difference image.
     91       diff_png = screenshot.Diff(ref_png)
     92       self._WriteImage(
     93           os.path.join(img_dir, 'DIFF_' + full_image_name), diff_png)
     94 
     95   ###
     96   ### Cloud storage code path -- the bots use this.
     97   ###
     98 
     99   def _ComputeGpuInfo(self, tab):
    100     if ((self.vendor_id and self.device_id) or
    101         (self.vendor_string and self.device_string)):
    102       return
    103     browser = tab.browser
    104     if not browser.supports_system_info:
    105       raise Exception('System info must be supported by the browser')
    106     system_info = browser.GetSystemInfo()
    107     if not system_info.gpu:
    108       raise Exception('GPU information was absent')
    109     device = system_info.gpu.devices[0]
    110     if device.vendor_id and device.device_id:
    111       self.vendor_id = device.vendor_id
    112       self.device_id = device.device_id
    113     elif device.vendor_string and device.device_string:
    114       self.vendor_string = device.vendor_string
    115       self.device_string = device.device_string
    116     else:
    117       raise Exception('GPU device information was incomplete')
    118     self.msaa = not (
    119         'disable_multisampling' in system_info.gpu.driver_bug_workarounds)
    120 
    121   def _FormatGpuInfo(self, tab):
    122     self._ComputeGpuInfo(tab)
    123     msaa_string = '_msaa' if self.msaa else '_non_msaa'
    124     if self.vendor_id:
    125       return '%s_%04x_%04x%s' % (
    126         self.options.os_type, self.vendor_id, self.device_id, msaa_string)
    127     else:
    128       return '%s_%s_%s%s' % (
    129         self.options.os_type, self.vendor_string, self.device_string,
    130         msaa_string)
    131 
    132   def _FormatReferenceImageName(self, img_name, page, tab):
    133     return '%s_v%s_%s.png' % (
    134       img_name,
    135       page.revision,
    136       self._FormatGpuInfo(tab))
    137 
    138   def _UploadBitmapToCloudStorage(self, bucket, name, bitmap, public=False):
    139     # This sequence of steps works on all platforms to write a temporary
    140     # PNG to disk, following the pattern in bitmap_unittest.py. The key to
    141     # avoiding PermissionErrors seems to be to not actually try to write to
    142     # the temporary file object, but to re-open its name for all operations.
    143     temp_file = tempfile.NamedTemporaryFile().name
    144     bitmap.WritePngFile(temp_file)
    145     cloud_storage.Insert(bucket, name, temp_file, publicly_readable=public)
    146 
    147   def _ConditionallyUploadToCloudStorage(self, img_name, page, tab, screenshot):
    148     """Uploads the screenshot to cloud storage as the reference image
    149     for this test, unless it already exists. Returns True if the
    150     upload was actually performed."""
    151     if not self.options.refimg_cloud_storage_bucket:
    152       raise Exception('--refimg-cloud-storage-bucket argument is required')
    153     cloud_name = self._FormatReferenceImageName(img_name, page, tab)
    154     if not cloud_storage.Exists(self.options.refimg_cloud_storage_bucket,
    155                                 cloud_name):
    156       self._UploadBitmapToCloudStorage(self.options.refimg_cloud_storage_bucket,
    157                                        cloud_name,
    158                                        screenshot)
    159       return True
    160     return False
    161 
    162   def _DownloadFromCloudStorage(self, img_name, page, tab):
    163     """Downloads the reference image for the given test from cloud
    164     storage, returning it as a Telemetry Bitmap object."""
    165     # TODO(kbr): there's a race condition between the deletion of the
    166     # temporary file and gsutil's overwriting it.
    167     if not self.options.refimg_cloud_storage_bucket:
    168       raise Exception('--refimg-cloud-storage-bucket argument is required')
    169     temp_file = tempfile.NamedTemporaryFile().name
    170     cloud_storage.Get(self.options.refimg_cloud_storage_bucket,
    171                       self._FormatReferenceImageName(img_name, page, tab),
    172                       temp_file)
    173     return bitmap.Bitmap.FromPngFile(temp_file)
    174 
    175   def _UploadErrorImagesToCloudStorage(self, image_name, screenshot, ref_img):
    176     """For a failing run, uploads the failing image, reference image (if
    177     supplied), and diff image (if reference image was supplied) to cloud
    178     storage. This subsumes the functionality of the
    179     archive_gpu_pixel_test_results.py script."""
    180     machine_name = re.sub('\W+', '_', self.options.test_machine_name)
    181     upload_dir = '%s_%s_telemetry' % (self.options.build_revision, machine_name)
    182     base_bucket = '%s/runs/%s' % (error_image_cloud_storage_bucket, upload_dir)
    183     image_name_with_revision = '%s_%s.png' % (
    184       image_name, self.options.build_revision)
    185     self._UploadBitmapToCloudStorage(
    186       base_bucket + '/gen', image_name_with_revision, screenshot,
    187       public=True)
    188     if ref_img:
    189       self._UploadBitmapToCloudStorage(
    190         base_bucket + '/ref', image_name_with_revision, ref_img, public=True)
    191       diff_img = screenshot.Diff(ref_img)
    192       self._UploadBitmapToCloudStorage(
    193         base_bucket + '/diff', image_name_with_revision, diff_img,
    194         public=True)
    195     print ('See http://%s.commondatastorage.googleapis.com/'
    196            'view_test_results.html?%s for this run\'s test results') % (
    197       error_image_cloud_storage_bucket, upload_dir)
    198 
    199   def _ValidateScreenshotSamples(self, url,
    200                                  screenshot, expectations, device_pixel_ratio):
    201     """Samples the given screenshot and verifies pixel color values.
    202        The sample locations and expected color values are given in expectations.
    203        In case any of the samples do not match the expected color, it raises
    204        a Failure and dumps the screenshot locally or cloud storage depending on
    205        what machine the test is being run."""
    206     try:
    207       _CompareScreenshotSamples(screenshot, expectations, device_pixel_ratio)
    208     except page_test.Failure:
    209       image_name = self._UrlToImageName(url)
    210       if self.options.test_machine_name:
    211         self._UploadErrorImagesToCloudStorage(image_name, screenshot, None)
    212       else:
    213         self._WriteErrorImages(self.options.generated_dir, image_name,
    214                                screenshot, None)
    215       raise
    216 
    217 
    218 class TestBase(test.Test):
    219   @classmethod
    220   def AddTestCommandLineArgs(cls, group):
    221     group.add_option('--build-revision',
    222         help='Chrome revision being tested.',
    223         default="unknownrev")
    224     group.add_option('--upload-refimg-to-cloud-storage',
    225         dest='upload_refimg_to_cloud_storage',
    226         action='store_true', default=False,
    227         help='Upload resulting images to cloud storage as reference images')
    228     group.add_option('--download-refimg-from-cloud-storage',
    229         dest='download_refimg_from_cloud_storage',
    230         action='store_true', default=False,
    231         help='Download reference images from cloud storage')
    232     group.add_option('--refimg-cloud-storage-bucket',
    233         help='Name of the cloud storage bucket to use for reference images; '
    234         'required with --upload-refimg-to-cloud-storage and '
    235         '--download-refimg-from-cloud-storage. Example: '
    236         '"chromium-gpu-archive/reference-images"')
    237     group.add_option('--os-type',
    238         help='Type of operating system on which the pixel test is being run, '
    239         'used only to distinguish different operating systems with the same '
    240         'graphics card. Any value is acceptable, but canonical values are '
    241         '"win", "mac", and "linux", and probably, eventually, "chromeos" '
    242         'and "android").',
    243         default='')
    244     group.add_option('--test-machine-name',
    245         help='Name of the test machine. Specifying this argument causes this '
    246         'script to upload failure images and diffs to cloud storage directly, '
    247         'instead of relying on the archive_gpu_pixel_test_results.py script.',
    248         default='')
    249     group.add_option('--generated-dir',
    250         help='Overrides the default on-disk location for generated test images '
    251         '(only used for local testing without a cloud storage account)',
    252         default=default_generated_data_dir)
    253