OLD | NEW |
(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 import glob |
| 5 import logging |
| 6 import os |
| 7 import re |
| 8 import sys |
| 9 |
| 10 from gpu_tests import cloud_storage_integration_test_base |
| 11 from gpu_tests import pixel_expectations |
| 12 from gpu_tests import pixel_test_pages |
| 13 |
| 14 from py_utils import cloud_storage |
| 15 from telemetry.util import image_util |
| 16 |
| 17 |
| 18 test_data_dir = os.path.abspath(os.path.join( |
| 19 os.path.dirname(__file__), '..', '..', 'data', 'gpu')) |
| 20 |
| 21 default_reference_image_dir = os.path.join(test_data_dir, 'gpu_reference') |
| 22 |
| 23 test_harness_script = r""" |
| 24 var domAutomationController = {}; |
| 25 |
| 26 domAutomationController._succeeded = false; |
| 27 domAutomationController._finished = false; |
| 28 |
| 29 domAutomationController.setAutomationId = function(id) {} |
| 30 |
| 31 domAutomationController.send = function(msg) { |
| 32 domAutomationController._finished = true; |
| 33 |
| 34 if(msg.toLowerCase() == "success") { |
| 35 domAutomationController._succeeded = true; |
| 36 } else { |
| 37 domAutomationController._succeeded = false; |
| 38 } |
| 39 } |
| 40 |
| 41 window.domAutomationController = domAutomationController; |
| 42 """ |
| 43 |
| 44 |
| 45 class PixelIntegrationTest( |
| 46 cloud_storage_integration_test_base.CloudStorageIntegrationTestBase): |
| 47 |
| 48 # We store a deep copy of the original browser finder options in |
| 49 # order to be able to restart the browser multiple times, with a |
| 50 # different set of command line arguments each time. |
| 51 _original_finder_options = None |
| 52 |
| 53 # We keep track of the set of command line arguments used to launch |
| 54 # the browser most recently in order to figure out whether we need |
| 55 # to relaunch it, if a new pixel test requires a different set of |
| 56 # arguments. |
| 57 _last_launched_browser_args = set() |
| 58 |
| 59 @classmethod |
| 60 def Name(cls): |
| 61 """The name by which this test is invoked on the command line.""" |
| 62 return 'pixel' |
| 63 |
| 64 @classmethod |
| 65 def setUpClass(cls): |
| 66 super(cls, PixelIntegrationTest).setUpClass() |
| 67 cls._original_finder_options = cls._finder_options.Copy() |
| 68 cls.CustomizeBrowserArgs([]) |
| 69 cls.StartBrowser() |
| 70 cls.SetStaticServerDirs([test_data_dir]) |
| 71 |
| 72 @classmethod |
| 73 def CustomizeBrowserArgs(cls, browser_args): |
| 74 if not browser_args: |
| 75 browser_args = [] |
| 76 cls._finder_options = cls._original_finder_options.Copy() |
| 77 browser_options = cls._finder_options.browser_options |
| 78 # All tests receive these options. They aren't recorded in the |
| 79 # _last_launched_browser_args. |
| 80 browser_options.AppendExtraBrowserArgs(['--enable-gpu-benchmarking', |
| 81 '--test-type=gpu']) |
| 82 # Append the new arguments. |
| 83 browser_options.AppendExtraBrowserArgs(browser_args) |
| 84 cls._last_launched_browser_args = set(browser_args) |
| 85 cls.SetBrowserOptions(cls._finder_options) |
| 86 |
| 87 @classmethod |
| 88 def RestartBrowserIfNecessaryWithArgs(cls, browser_args): |
| 89 if not browser_args: |
| 90 browser_args = [] |
| 91 if set(browser_args) != cls._last_launched_browser_args: |
| 92 logging.warning('Restarting browser with arguments: ' + str(browser_args)) |
| 93 cls.StopBrowser() |
| 94 cls.ResetGpuInfo() |
| 95 cls.CustomizeBrowserArgs(browser_args) |
| 96 cls.StartBrowser() |
| 97 cls.tab = cls.browser.tabs[0] |
| 98 |
| 99 @classmethod |
| 100 def AddCommandlineArgs(cls, parser): |
| 101 super(PixelIntegrationTest, cls).AddCommandlineArgs(parser) |
| 102 parser.add_option( |
| 103 '--reference-dir', |
| 104 help='Overrides the default on-disk location for reference images ' |
| 105 '(only used for local testing without a cloud storage account)', |
| 106 default=default_reference_image_dir) |
| 107 |
| 108 @classmethod |
| 109 def _CreateExpectations(cls): |
| 110 return pixel_expectations.PixelExpectations() |
| 111 |
| 112 @classmethod |
| 113 def GenerateGpuTests(cls, options): |
| 114 cls.SetParsedCommandLineOptions(options) |
| 115 name = 'Pixel' |
| 116 pages = pixel_test_pages.ES2AndES3Pages(name) |
| 117 pages += pixel_test_pages.ExperimentalCanvasFeaturesPages(name) |
| 118 if sys.platform.startswith('darwin'): |
| 119 # TOOD(kbr): replace this with CopyPagesWithNewBrowserArgsAndPrefix, |
| 120 # and removeCopyPagesWithNewBrowserArgsAndSuffix. This renaming |
| 121 # will cause all of the ES3 tests to be run back-to-back, |
| 122 # reducing the number of browser restarts and speeding up the |
| 123 # tests. Note that this will require all the ES3 tests to be |
| 124 # temporarily marked failing on macOS, and is too big a change |
| 125 # to do along with the port to the new harness. |
| 126 pages += pixel_test_pages.CopyPagesWithNewBrowserArgsAndSuffix( |
| 127 pixel_test_pages.ES2AndES3Pages(name), |
| 128 ['--enable-unsafe-es3-apis'], 'ES3') |
| 129 pages += pixel_test_pages.MacSpecificPages(name) |
| 130 for p in pages: |
| 131 yield(p.name, p.url, (p)) |
| 132 |
| 133 def RunActualGpuTest(self, test_path, *args): |
| 134 page = args[0] |
| 135 # Some pixel tests require non-standard browser arguments. Need to |
| 136 # check before running each page that it can run in the current |
| 137 # browser instance. |
| 138 self.RestartBrowserIfNecessaryWithArgs(page.browser_args) |
| 139 url = self.UrlOfStaticFilePath(test_path) |
| 140 # This property actually comes off the class, not 'self'. |
| 141 tab = self.tab |
| 142 tab.Navigate(url, script_to_evaluate_on_commit=test_harness_script) |
| 143 tab.action_runner.WaitForJavaScriptCondition( |
| 144 'domAutomationController._finished', timeout_in_seconds=300) |
| 145 if not tab.EvaluateJavaScript('domAutomationController._succeeded'): |
| 146 self.fail('page indicated test failure') |
| 147 if not tab.screenshot_supported: |
| 148 self.fail('Browser does not support screenshot capture') |
| 149 screenshot = tab.Screenshot(5) |
| 150 if screenshot is None: |
| 151 self.fail('Could not capture screenshot') |
| 152 dpr = tab.EvaluateJavaScript('window.devicePixelRatio') |
| 153 if page.test_rect: |
| 154 screenshot = image_util.Crop( |
| 155 screenshot, page.test_rect[0] * dpr, page.test_rect[1] * dpr, |
| 156 page.test_rect[2] * dpr, page.test_rect[3] * dpr) |
| 157 if page.expected_colors: |
| 158 # Use expected colors instead of ref images for validation. |
| 159 self._ValidateScreenshotSamples( |
| 160 tab, page.name, screenshot, page.expected_colors, dpr) |
| 161 return |
| 162 image_name = self._UrlToImageName(page.name) |
| 163 if self.GetParsedCommandLineOptions().upload_refimg_to_cloud_storage: |
| 164 if self._ConditionallyUploadToCloudStorage(image_name, page, tab, |
| 165 screenshot): |
| 166 # This is the new reference image; there's nothing to compare against. |
| 167 ref_png = screenshot |
| 168 else: |
| 169 # There was a preexisting reference image, so we might as well |
| 170 # compare against it. |
| 171 ref_png = self._DownloadFromCloudStorage(image_name, page, tab) |
| 172 elif self.GetParsedCommandLineOptions().download_refimg_from_cloud_storage: |
| 173 # This bot doesn't have the ability to properly generate a |
| 174 # reference image, so download it from cloud storage. |
| 175 try: |
| 176 ref_png = self._DownloadFromCloudStorage(image_name, page, tab) |
| 177 except cloud_storage.NotFoundError: |
| 178 # There is no reference image yet in cloud storage. This |
| 179 # happens when the revision of the test is incremented or when |
| 180 # a new test is added, because the trybots are not allowed to |
| 181 # produce reference images, only the bots on the main |
| 182 # waterfalls. Report this as a failure so the developer has to |
| 183 # take action by explicitly suppressing the failure and |
| 184 # removing the suppression once the reference images have been |
| 185 # generated. Otherwise silent failures could happen for long |
| 186 # periods of time. |
| 187 self.fail('Could not find image %s in cloud storage' % image_name) |
| 188 else: |
| 189 # Legacy path using on-disk results. |
| 190 ref_png = self._GetReferenceImage( |
| 191 self.GetParsedCommandLineOptions().reference_dir, |
| 192 image_name, page.revision, screenshot) |
| 193 |
| 194 # Test new snapshot against existing reference image |
| 195 if not image_util.AreEqual(ref_png, screenshot, tolerance=page.tolerance): |
| 196 if self.GetParsedCommandLineOptions().test_machine_name: |
| 197 self._UploadErrorImagesToCloudStorage(image_name, screenshot, ref_png) |
| 198 else: |
| 199 self._WriteErrorImages( |
| 200 self.GetParsedCommandLineOptions().generated_dir, image_name, |
| 201 screenshot, ref_png) |
| 202 self.fail('Reference image did not match captured screen') |
| 203 |
| 204 def _DeleteOldReferenceImages(self, ref_image_path, cur_revision): |
| 205 if not cur_revision: |
| 206 return |
| 207 |
| 208 old_revisions = glob.glob(ref_image_path + "_*.png") |
| 209 for rev_path in old_revisions: |
| 210 m = re.match(r'^.*_(\d+)\.png$', rev_path) |
| 211 if m and int(m.group(1)) < cur_revision: |
| 212 print 'Found deprecated reference image. Deleting rev ' + m.group(1) |
| 213 os.remove(rev_path) |
| 214 |
| 215 def _GetReferenceImage(self, img_dir, img_name, cur_revision, screenshot): |
| 216 if not cur_revision: |
| 217 cur_revision = 0 |
| 218 |
| 219 image_path = os.path.join(img_dir, img_name) |
| 220 |
| 221 self._DeleteOldReferenceImages(image_path, cur_revision) |
| 222 |
| 223 image_path = image_path + '_v' + str(cur_revision) + '.png' |
| 224 |
| 225 try: |
| 226 ref_png = image_util.FromPngFile(image_path) |
| 227 # This can raise a couple of exceptions including IOError and ValueError. |
| 228 except Exception: |
| 229 ref_png = None |
| 230 |
| 231 if ref_png is not None: |
| 232 return ref_png |
| 233 |
| 234 print ('Reference image not found. Writing tab contents as reference to: ' + |
| 235 image_path) |
| 236 |
| 237 self._WriteImage(image_path, screenshot) |
| 238 return screenshot |
OLD | NEW |