blob: e3d578e1204f497e65a4a2617b76f11304a6d9cc [file] [log] [blame]
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import re
from telemetry.testing import serially_executed_browser_test_case
from telemetry.util import screenshot
from typ import json_results
from gpu_tests import exception_formatter
from gpu_tests import gpu_helper
_START_BROWSER_RETRIES = 3
ResultType = json_results.ResultType
# Please expand the following lists when we expand to new bot configs.
_SUPPORTED_WIN_VERSIONS = ['win7', 'win10']
_SUPPORTED_WIN_VERSIONS_WITH_DIRECT_COMPOSITION = ['win10']
_SUPPORTED_WIN_GPU_VENDORS = [0x8086, 0x10de, 0x1002]
_SUPPORTED_WIN_INTEL_GPUS = [0x5912]
_SUPPORTED_WIN_INTEL_GPUS_WITH_YUY2_OVERLAYS = [0x5912]
_SUPPORTED_WIN_INTEL_GPUS_WITH_NV12_OVERLAYS = [0x5912]
class GpuIntegrationTest(
serially_executed_browser_test_case.SeriallyExecutedBrowserTestCase):
_cached_expectations = None
_also_run_disabled_tests = False
_disable_log_uploads = False
# Several of the tests in this directory need to be able to relaunch
# the browser on demand with a new set of command line arguments
# than were originally specified. To enable this, the necessary
# static state is hoisted here.
# We store a deep copy of the original browser finder options in
# order to be able to restart the browser multiple times, with a
# different set of command line arguments each time.
_original_finder_options = None
# We keep track of the set of command line arguments used to launch
# the browser most recently in order to figure out whether we need
# to relaunch it, if a new pixel test requires a different set of
# arguments.
_last_launched_browser_args = set()
@classmethod
def SetUpProcess(cls):
super(GpuIntegrationTest, cls).SetUpProcess()
cls._original_finder_options = cls._finder_options.Copy()
@classmethod
def AddCommandlineArgs(cls, parser):
"""Adds command line arguments understood by the test harness.
Subclasses overriding this method must invoke the superclass's
version!"""
parser.add_option(
'--disable-log-uploads',
dest='disable_log_uploads',
action='store_true', default=False,
help='Disables uploads of logs to cloud storage')
@classmethod
def CustomizeBrowserArgs(cls, browser_args):
"""Customizes the browser's command line arguments.
NOTE that redefining this method in subclasses will NOT do what
you expect! Do not attempt to redefine this method!
"""
if not browser_args:
browser_args = []
cls._finder_options = cls._original_finder_options.Copy()
browser_options = cls._finder_options.browser_options
# If requested, disable uploading of failure logs to cloud storage.
if cls._disable_log_uploads:
browser_options.logs_cloud_bucket = None
# A non-sandboxed, 15-seconds-delayed gpu process is currently running in
# the browser to collect gpu info. A command line switch is added here to
# skip this gpu process for all gpu integration tests to prevent any
# interference with the test results.
browser_args.append(
'--disable-gpu-process-for-dx12-vulkan-info-collection')
# Append the new arguments.
browser_options.AppendExtraBrowserArgs(browser_args)
cls._last_launched_browser_args = set(browser_args)
cls.SetBrowserOptions(cls._finder_options)
@classmethod
def RestartBrowserIfNecessaryWithArgs(cls, browser_args, force_restart=False):
if not browser_args:
browser_args = []
elif '--disable-gpu' in browser_args:
# Some platforms require GPU process, so browser fails to launch with
# --disable-gpu mode, therefore, even test expectations fail to evaluate.
browser_args = list(browser_args)
os_name = cls.browser.platform.GetOSName()
if os_name == 'android' or os_name == 'chromeos':
browser_args.remove('--disable-gpu')
if force_restart or set(browser_args) != cls._last_launched_browser_args:
logging.info('Restarting browser with arguments: ' + str(browser_args))
cls.StopBrowser()
cls.CustomizeBrowserArgs(browser_args)
cls.StartBrowser()
@classmethod
def RestartBrowserWithArgs(cls, browser_args):
cls.RestartBrowserIfNecessaryWithArgs(browser_args, force_restart=True)
# The following is the rest of the framework for the GPU integration tests.
@classmethod
def GenerateTestCases__RunGpuTest(cls, options):
cls._disable_log_uploads = options.disable_log_uploads
for test_name, url, args in cls.GenerateGpuTests(options):
yield test_name, (url, test_name, args)
@classmethod
def StartBrowser(cls):
# We still need to retry the browser's launch even though
# desktop_browser_finder does so too, because it wasn't possible
# to push the fetch of the first tab into the lower retry loop
# without breaking Telemetry's unit tests, and that hook is used
# to implement the gpu_integration_test_unittests.
for x in range(1, _START_BROWSER_RETRIES+1): # Index from 1 instead of 0.
try:
super(GpuIntegrationTest, cls).StartBrowser()
cls.tab = cls.browser.tabs[0]
return
except Exception:
logging.exception('Browser start failed (attempt %d of %d). Backtrace:',
x, _START_BROWSER_RETRIES)
# If we are on the last try and there is an exception take a screenshot
# to try and capture more about the browser failure and raise
if x == _START_BROWSER_RETRIES:
url = screenshot.TryCaptureScreenShotAndUploadToCloudStorage(
cls.platform)
if url is not None:
logging.info("GpuIntegrationTest screenshot of browser failure " +
"located at " + url)
else:
logging.warning("GpuIntegrationTest unable to take screenshot.")
# Stop the browser to make sure it's in an
# acceptable state to try restarting it.
if cls.browser:
cls.StopBrowser()
# Re-raise the last exception thrown. Only happens if all the retries
# fail.
raise
@classmethod
def _RestartBrowser(cls, reason):
logging.warning('Restarting browser due to '+ reason)
cls.StopBrowser()
cls.SetBrowserOptions(cls._finder_options)
cls.StartBrowser()
def _RunGpuTest(self, url, test_name, *args):
expected_results, should_retry_on_failure = (
self.GetExpectationsForTest())
try:
# TODO(nednguyen): For some reason the arguments are getting wrapped
# in another tuple sometimes (like in the WebGL extension tests).
# Perhaps only if multiple arguments are yielded in the test
# generator?
if len(args) == 1 and isinstance(args[0], tuple):
args = args[0]
self.RunActualGpuTest(url, *args)
except Exception:
if ResultType.Failure in expected_results or should_retry_on_failure:
if should_retry_on_failure:
# For robustness, shut down the browser and restart it
# between flaky test failures, to make sure any state
# doesn't propagate to the next iteration.
self._RestartBrowser('flaky test failure')
else:
msg = 'Expected exception while running %s' % test_name
exception_formatter.PrintFormattedException(msg=msg)
# Even though this is a known failure, the browser might still
# be in a bad state; for example, certain kinds of timeouts
# will affect the next test. Restart the browser to prevent
# these kinds of failures propagating to the next test.
self._RestartBrowser('expected test failure')
else:
# This is not an expected exception or test failure, so print
# the detail to the console.
exception_formatter.PrintFormattedException()
# Symbolize any crash dump (like from the GPU process) that
# might have happened but wasn't detected above. Note we don't
# do this for either 'fail' or 'flaky' expectations because
# there are still quite a few flaky failures in the WebGL test
# expectations, and since minidump symbolization is slow
# (upwards of one minute on a fast laptop), symbolizing all the
# stacks could slow down the tests' running time unacceptably.
self.browser.LogSymbolizedUnsymbolizedMinidumps(logging.ERROR)
# This failure might have been caused by a browser or renderer
# crash, so restart the browser to make sure any state doesn't
# propagate to the next test iteration.
self._RestartBrowser('unexpected test failure')
self.fail()
else:
if ResultType.Failure in expected_results:
logging.warning(
'%s was expected to fail, but passed.\n', test_name)
@classmethod
def GenerateGpuTests(cls, options):
"""Subclasses must implement this to yield (test_name, url, args)
tuples of tests to run."""
raise NotImplementedError
def RunActualGpuTest(self, file_path, *args):
"""Subclasses must override this to run the actual test at the given
URL. file_path is a path on the local file system that may need to
be resolved via UrlOfStaticFilePath.
"""
raise NotImplementedError
def GetOverlayBotConfig(self):
"""Returns expected bot config for DirectComposition and overlay support.
This is only meaningful on Windows platform.
The rules to determine bot config are:
1) Only win10 or newer supports DirectComposition
2) Only Intel supports hardware overlays with DirectComposition
3) Currently the Win/Intel GPU bot supports YUY2 and NV12 overlays
"""
if self.browser is None:
raise Exception("Browser doesn't exist")
system_info = self.browser.GetSystemInfo()
if system_info is None:
raise Exception("Browser doesn't support GetSystemInfo")
gpu = system_info.gpu.devices[0]
if gpu is None:
raise Exception("System Info doesn't have a gpu")
gpu_vendor_id = gpu.vendor_id
gpu_device_id = gpu.device_id
os_version = self.browser.platform.GetOSVersionName()
if os_version is None:
raise Exception("browser.platform.GetOSVersionName() returns None")
os_version = os_version.lower()
config = {
'direct_composition': False,
'supports_overlays': False,
'yuy2_overlay_support': 'NONE',
'nv12_overlay_support': 'NONE',
}
assert os_version in _SUPPORTED_WIN_VERSIONS
assert gpu_vendor_id in _SUPPORTED_WIN_GPU_VENDORS
if os_version in _SUPPORTED_WIN_VERSIONS_WITH_DIRECT_COMPOSITION:
config['direct_composition'] = True
if gpu_vendor_id == 0x8086:
config['supports_overlays'] = True
assert gpu_device_id in _SUPPORTED_WIN_INTEL_GPUS
if gpu_device_id in _SUPPORTED_WIN_INTEL_GPUS_WITH_YUY2_OVERLAYS:
config['yuy2_overlay_support'] = 'SCALING'
if gpu_device_id in _SUPPORTED_WIN_INTEL_GPUS_WITH_NV12_OVERLAYS:
config['nv12_overlay_support'] = 'SCALING'
return config
@classmethod
def GenerateTags(cls, finder_options, possible_browser):
# If no expectations file paths are returned from cls.ExpectationsFiles()
# then an empty list will be returned from this function. If tags are
# returned and there are no expectations files, then Typ will raise
# an exception.
if not cls.ExpectationsFiles():
return []
with possible_browser.BrowserSession(
finder_options.browser_options) as browser:
return cls.GetPlatformTags(browser)
@classmethod
def GetPlatformTags(cls, browser):
"""This function will take a Browser instance as an argument.
It will call the super classes implementation of GetPlatformTags() to get
a list of tags. Then it will add the gpu vendor, gpu device id,
angle renderer, and command line decoder tags to that list before
returning it.
"""
tags = super(GpuIntegrationTest, cls).GetPlatformTags(browser)
system_info = browser.GetSystemInfo()
if system_info:
gpu_info = system_info.gpu
gpu_vendor = gpu_helper.GetGpuVendorString(gpu_info)
gpu_device_id = gpu_helper.GetGpuDeviceId(gpu_info)
# The gpu device id tag will contain both the vendor and device id
# separated by a '-'.
try:
# If the device id is an integer then it will be added as
# a hexadecimal to the tag
gpu_device_tag = '%s-0x%x' % (gpu_vendor, gpu_device_id)
except TypeError:
# if the device id is not an integer it will be added as
# a string to the tag.
gpu_device_tag = '%s-%s' % (gpu_vendor, gpu_device_id)
angle_renderer = gpu_helper.GetANGLERenderer(gpu_info)
cmd_decoder = gpu_helper.GetCommandDecoder(gpu_info)
# all spaces and underscores in the tag will be replaced by dashes
tags.extend([re.sub('[ _]', '-', tag) for tag in [
gpu_vendor, gpu_device_tag, angle_renderer, cmd_decoder]])
# If additional options have been set via '--extra-browser-args' check for
# those which map to expectation tags. The '_browser_backend' attribute may
# not exist in unit tests.
if (hasattr(browser, '_browser_backend') and
browser._browser_backend.browser_options.extra_browser_args):
skia_renderer = gpu_helper.GetSkiaRenderer(\
browser._browser_backend.browser_options.extra_browser_args)
tags.extend([skia_renderer])
use_vulkan = gpu_helper.GetVulkan(\
browser._browser_backend.browser_options.extra_browser_args)
tags.extend([use_vulkan])
return tags
@classmethod
def _EnsureTabIsAvailable(cls):
try:
cls.tab = cls.browser.tabs[0]
except Exception:
# restart the browser to make sure a failure in a test doesn't
# propagate to the next test iteration.
logging.exception("Failure during browser startup")
cls._RestartBrowser('failure in setup')
raise
def setUp(self):
self._EnsureTabIsAvailable()
@staticmethod
def GetJSONResultsDelimiter():
return '/'
def LoadAllTestsInModule(module):
# Just delegates to serially_executed_browser_test_case to reduce the
# number of imports in other files.
return serially_executed_browser_test_case.LoadAllTestsInModule(module)