blob: 73369889623e727f8105b0d690f45784ef2c3292 [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 os
import sys
import time
from gpu_tests import gpu_integration_test
from gpu_tests import context_lost_expectations
from gpu_tests import path_util
from telemetry.core import exceptions
data_path = os.path.join(
path_util.GetChromiumSrcDir(), 'content', 'test', 'data', 'gpu')
wait_timeout = 60 # seconds
harness_script = r"""
var domAutomationController = {};
domAutomationController._loaded = false;
domAutomationController._succeeded = false;
domAutomationController._finished = false;
domAutomationController.send = function(msg) {
msg = msg.toLowerCase();
if (msg == "loaded") {
domAutomationController._loaded = true;
} else if (msg == "success") {
/* Don't squelch earlier failures! */
if (!domAutomationController._finished) {
domAutomationController._succeeded = true;
}
domAutomationController._finished = true;
} else {
/* Always record failures. */
domAutomationController._succeeded = false;
domAutomationController._finished = true;
}
}
domAutomationController.reset = function() {
domAutomationController._succeeded = false;
domAutomationController._finished = false;
}
window.domAutomationController = domAutomationController;
console.log("Harness injected.");
"""
class ContextLostIntegrationTest(gpu_integration_test.GpuIntegrationTest):
_is_asan = False
@classmethod
def Name(cls):
return 'context_lost'
@classmethod
def AddCommandlineArgs(cls, parser):
super(ContextLostIntegrationTest, cls).AddCommandlineArgs(parser)
parser.add_option('--is-asan',
help='Indicates whether currently running an ASAN build',
action='store_true')
@staticmethod
def _AddDefaultArgs(browser_args):
# These are options specified for every test.
return [
'--disable-gpu-process-crash-limit',
# Required to call crashGpuProcess.
'--enable-gpu-benchmarking'] + browser_args
@classmethod
def GenerateGpuTests(cls, options):
cls._is_asan = options.is_asan
tests = (('GpuCrash_GPUProcessCrashesExactlyOncePerVisitToAboutGpuCrash',
'gpu_process_crash.html'),
('ContextLost_WebGLContextLostFromGPUProcessExit',
'webgl.html?query=kill_after_notification'),
('ContextLost_WebGLContextLostFromLoseContextExtension',
'webgl.html?query=WEBGL_lose_context'),
('ContextLost_WebGLContextLostFromQuantity',
'webgl.html?query=forced_quantity_loss'),
('ContextLost_WebGLContextLostFromSelectElement',
'webgl_with_select_element.html'),
('ContextLost_WebGLContextLostInHiddenTab',
'webgl.html?query=kill_after_notification'),
('ContextLost_WebGLBlockedAfterJSNavigation',
'webgl-domain-blocking-page1.html'),
('ContextLost_WebGLUnblockedAfterUserInitiatedReload',
'webgl-domain-unblocking.html'),
('ContextLost_WorkerRAFAfterGPUCrash',
'worker-raf-after-gpu-crash.html'),
('ContextLost_WorkerRAFAfterGPUCrash_OOPD',
'worker-raf-after-gpu-crash.html'),
('ContextLost_WebGL2Blocked',
'webgl2-context-blocked.html'))
for t in tests:
yield (t[0], t[1], ('_' + t[0]))
def RunActualGpuTest(self, test_path, *args):
test_name = args[0]
tab = self.tab
if not tab.browser.supports_tab_control:
self.fail('Browser must support tab control')
getattr(self, test_name)(test_path)
@classmethod
def _CreateExpectations(cls):
return context_lost_expectations.ContextLostExpectations(
is_asan=cls._is_asan)
@classmethod
def SetUpProcess(cls):
super(ContextLostIntegrationTest, cls).SetUpProcess()
# Most of the tests need this, so add it to the default set of
# command line arguments used to launch the browser, to reduce the
# number of browser restarts between tests.
cls.CustomizeBrowserArgs(cls._AddDefaultArgs([
'--disable-domain-blocking-for-3d-apis']))
cls.StartBrowser()
cls.SetStaticServerDirs([data_path])
def _WaitForPageToFinish(self, tab):
try:
tab.WaitForJavaScriptCondition(
'window.domAutomationController._finished', timeout=wait_timeout)
return True
except exceptions.TimeoutException:
return False
def _KillGPUProcess(self, number_of_gpu_process_kills,
check_crash_count):
tab = self.tab
# Doing the GPU process kill operation cooperatively -- in the
# same page's context -- is much more stressful than restarting
# the browser every time.
for x in range(number_of_gpu_process_kills):
expected_kills = x + 1
# Reset the test's state.
tab.EvaluateJavaScript(
'window.domAutomationController.reset()')
# If we're running the GPU process crash test, we need the test
# to have fully reset before crashing the GPU process.
if check_crash_count:
tab.WaitForJavaScriptCondition(
'window.domAutomationController._finished', timeout=wait_timeout)
# Crash the GPU process.
#
# This used to create a new tab and navigate it to
# chrome://gpucrash, but there was enough unreliability
# navigating between these tabs (one of which was created solely
# in order to navigate to chrome://gpucrash) that the simpler
# solution of provoking the GPU process crash from this renderer
# process was chosen.
tab.EvaluateJavaScript('chrome.gpuBenchmarking.crashGpuProcess()')
completed = self._WaitForPageToFinish(tab)
if check_crash_count:
self._CheckCrashCount(tab, expected_kills)
if not completed:
self.fail('Test didn\'t complete (no context lost event?)')
if not tab.EvaluateJavaScript(
'window.domAutomationController._succeeded'):
self.fail('Test failed (context not restored properly?)')
def _CheckCrashCount(self, tab, expected_kills):
system_info = tab.browser.GetSystemInfo()
if not system_info:
self.fail('Browser must support system info')
if not tab.EvaluateJavaScript(
'window.domAutomationController._succeeded'):
self.fail('Test failed (didn\'t render content properly?)')
number_of_crashes = -1
if expected_kills > 0:
# To allow time for a gpucrash to complete, wait up to 20s,
# polling repeatedly.
start_time = time.time()
current_time = time.time()
while current_time - start_time < 20:
system_info = tab.browser.GetSystemInfo()
number_of_crashes = \
system_info.gpu.aux_attributes[u'process_crash_count']
if number_of_crashes >= expected_kills:
break
time.sleep(1)
current_time = time.time()
# Wait 5 more seconds and re-read process_crash_count, in
# attempt to catch latent process crashes.
time.sleep(5)
system_info = tab.browser.GetSystemInfo()
number_of_crashes = \
system_info.gpu.aux_attributes[u'process_crash_count']
if number_of_crashes < expected_kills:
self.fail('Timed out waiting for a gpu process crash')
elif number_of_crashes != expected_kills:
self.fail('Expected %d gpu process crashes; got: %d' %
(expected_kills, number_of_crashes))
def _NavigateAndWaitForLoad(self, test_path):
url = self.UrlOfStaticFilePath(test_path)
tab = self.tab
tab.Navigate(url, script_to_evaluate_on_commit=harness_script)
tab.action_runner.WaitForJavaScriptCondition(
'window.domAutomationController._loaded')
def _WaitForTabAndCheckCompletion(self):
tab = self.tab
completed = self._WaitForPageToFinish(tab)
if not completed:
self.fail('Test didn\'t complete (no context restored event?)')
if not tab.EvaluateJavaScript('window.domAutomationController._succeeded'):
self.fail('Test failed (context not restored properly?)')
# The browser test runner synthesizes methods with the exact name
# given in GenerateGpuTests, so in order to hand-write our tests but
# also go through the _RunGpuTest trampoline, the test needs to be
# slightly differently named.
def _GpuCrash_GPUProcessCrashesExactlyOncePerVisitToAboutGpuCrash(
self, test_path):
self.RestartBrowserIfNecessaryWithArgs(self._AddDefaultArgs([
'--disable-domain-blocking-for-3d-apis']))
self._NavigateAndWaitForLoad(test_path)
self._KillGPUProcess(2, True)
self._RestartBrowser('must restart after tests that kill the GPU process')
def _ContextLost_WebGLContextLostFromGPUProcessExit(self, test_path):
self.RestartBrowserIfNecessaryWithArgs(self._AddDefaultArgs([
'--disable-domain-blocking-for-3d-apis']))
self._NavigateAndWaitForLoad(test_path)
self._KillGPUProcess(1, False)
self._RestartBrowser('must restart after tests that kill the GPU process')
def _ContextLost_WebGLContextLostFromLoseContextExtension(self, test_path):
self.RestartBrowserIfNecessaryWithArgs(self._AddDefaultArgs([
'--disable-domain-blocking-for-3d-apis']))
url = self.UrlOfStaticFilePath(test_path)
tab = self.tab
tab.Navigate(url, script_to_evaluate_on_commit=harness_script)
tab.action_runner.WaitForJavaScriptCondition(
'window.domAutomationController._finished')
def _ContextLost_WebGLContextLostFromQuantity(self, test_path):
self.RestartBrowserIfNecessaryWithArgs(self._AddDefaultArgs([
'--disable-domain-blocking-for-3d-apis']))
self._NavigateAndWaitForLoad(test_path)
# Try to coerce GC to clean up any contexts not attached to the page.
# This method seems unreliable, so the page will also attempt to
# force GC through excessive allocations.
self.tab.CollectGarbage()
self._WaitForTabAndCheckCompletion()
def _ContextLost_WebGLContextLostFromSelectElement(self, test_path):
self.RestartBrowserIfNecessaryWithArgs(self._AddDefaultArgs([
'--disable-domain-blocking-for-3d-apis']))
self._NavigateAndWaitForLoad(test_path)
self._WaitForTabAndCheckCompletion()
def _ContextLost_WebGLContextLostInHiddenTab(self, test_path):
self.RestartBrowserIfNecessaryWithArgs(self._AddDefaultArgs([
'--disable-domain-blocking-for-3d-apis']))
self._NavigateAndWaitForLoad(test_path)
# Test losing a context in a hidden tab. This test passes if the tab
# doesn't crash.
tab = self.tab
dummy_tab = tab.browser.tabs.New()
tab.EvaluateJavaScript('loseContextUsingExtension()')
tab.Activate()
self._WaitForTabAndCheckCompletion()
def _ContextLost_WebGLBlockedAfterJSNavigation(self, test_path):
self.RestartBrowserIfNecessaryWithArgs(self._AddDefaultArgs([]))
self._NavigateAndWaitForLoad(test_path)
tab = self.tab
# Make sure the tab got a WebGL context.
if tab.EvaluateJavaScript('window.domAutomationController._finished'):
# This means the test failed for some reason.
if tab.EvaluateJavaScript('window.domAutomationController._succeeded'):
self.fail('Initial page claimed to succeed early')
else:
self.fail('Initial page failed to get a WebGL context')
# Kill the GPU process in order to get WebGL blocked.
tab.EvaluateJavaScript('chrome.gpuBenchmarking.crashGpuProcess()')
# The original tab will navigate to a new page. Wait for it to
# finish running its onload handler.
# TODO(kbr): figure out when it's OK to evaluate this JavaScript.
# Seems racy to do it immediately after crashing the GPU process.
tab.WaitForJavaScriptCondition('window.initFinished',
timeout=wait_timeout)
# Make sure the page failed to get a GL context.
if tab.EvaluateJavaScript('window.gotGL'):
self.fail(
'Page should have been blocked from getting a new WebGL context')
self._RestartBrowser('must restart after tests that kill the GPU process')
def _ContextLost_WebGLUnblockedAfterUserInitiatedReload(self, test_path):
self.RestartBrowserIfNecessaryWithArgs(self._AddDefaultArgs([]))
self._NavigateAndWaitForLoad(test_path)
tab = self.tab
# Make sure the tab initially got a WebGL context.
if not tab.EvaluateJavaScript('window.domAutomationController._succeeded'):
self.fail('Tab failed to get an initial WebGL context')
# Kill the GPU process in order to get WebGL blocked.
tab.EvaluateJavaScript('chrome.gpuBenchmarking.crashGpuProcess()')
# Wait for the page to receive a context loss event.
tab.WaitForJavaScriptCondition('window.contextLostReceived',
timeout=wait_timeout)
# Make sure WebGL is still blocked.
if not tab.EvaluateJavaScript(
'window.domAutomationController._succeeded'):
self.fail('WebGL should have been blocked after a context loss')
# Reload the page via Telemetry / DevTools. This is treated as a
# user-initiated navigation, so WebGL is unblocked.
self._NavigateAndWaitForLoad(test_path)
# Ensure WebGL is unblocked.
if not tab.EvaluateJavaScript(
'window.domAutomationController._succeeded'):
self.fail(
'WebGL should have been unblocked after a user-initiated navigation')
self._RestartBrowser('must restart after tests that kill the GPU process')
def _ContextLost_WorkerRAFAfterGPUCrash(self, test_path):
self.RestartBrowserIfNecessaryWithArgs(self._AddDefaultArgs([
'--enable-experimental-web-platform-features'
]))
self.RestartBrowserIfNecessaryWithArgs(self._AddDefaultArgs([]))
self._NavigateAndWaitForLoad(test_path)
self._KillGPUProcess(1, False)
self._WaitForTabAndCheckCompletion()
self._RestartBrowser('must restart after tests that kill the GPU process')
def _ContextLost_WorkerRAFAfterGPUCrash_OOPD(self, test_path):
self.RestartBrowserIfNecessaryWithArgs(self._AddDefaultArgs([
'--enable-viz-display-compositor',
'--enable-experimental-web-platform-features'
]))
self._NavigateAndWaitForLoad(test_path)
self._KillGPUProcess(1, False)
self._WaitForTabAndCheckCompletion()
self._RestartBrowser('must restart after tests that kill the GPU process')
def _ContextLost_WebGL2Blocked(self, test_path):
self.RestartBrowserIfNecessaryWithArgs(self._AddDefaultArgs([
'--gpu-driver-bug-list-test-group=3']))
self._NavigateAndWaitForLoad(test_path)
tab = self.tab
tab.EvaluateJavaScript('runTest()')
self._WaitForTabAndCheckCompletion()
# Attempting to create a WebGL 2.0 context when ES 3.0 is
# blacklisted should not cause the GPU process to crash.
self._CheckCrashCount(tab, 0)
def load_tests(loader, tests, pattern):
del loader, tests, pattern # Unused.
return gpu_integration_test.LoadAllTestsInModule(sys.modules[__name__])