# Copyright (c) 2013 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 time import context_lost_expectations from telemetry import benchmark as benchmark_module from telemetry.core import exceptions from telemetry.core import util from telemetry.page import page from telemetry.page import page_set from telemetry.page import page_test data_path = os.path.join( util.GetChromiumSrcDir(), 'content', 'test', 'data', 'gpu') wait_timeout = 20 # seconds harness_script = r""" var domAutomationController = {}; domAutomationController._loaded = false; domAutomationController._succeeded = false; domAutomationController._finished = false; domAutomationController.setAutomationId = function(id) {} domAutomationController.send = function(msg) { msg = msg.toLowerCase() if (msg == "loaded") { domAutomationController._loaded = true; } else if (msg == "success") { domAutomationController._succeeded = true; domAutomationController._finished = true; } else { domAutomationController._succeeded = false; domAutomationController._finished = true; } } domAutomationController.reset = function() { domAutomationController._succeeded = false; domAutomationController._finished = false; } window.domAutomationController = domAutomationController; console.log("Harness injected."); """ class _ContextLostValidator(page_test.PageTest): def __init__(self): # Strictly speaking this test doesn't yet need a browser restart # after each run, but if more tests are added which crash the GPU # process, then it will. super(_ContextLostValidator, self).__init__( needs_browser_restart_after_each_page=True) def CustomizeBrowserOptions(self, options): options.AppendExtraBrowserArgs( '--disable-domain-blocking-for-3d-apis') options.AppendExtraBrowserArgs( '--disable-gpu-process-crash-limit') # Required for about:gpucrash handling from Telemetry. options.AppendExtraBrowserArgs('--enable-gpu-benchmarking') def ValidateAndMeasurePage(self, page, tab, results): def WaitForPageToFinish(): print "Waiting for page to finish." try: util.WaitFor(lambda: tab.EvaluateJavaScript( 'window.domAutomationController._finished'), wait_timeout) return True except util.TimeoutException: return False if page.kill_gpu_process: # 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(page.number_of_gpu_process_kills): if not tab.browser.supports_tab_control: raise page_test.Failure('Browser must support tab control') 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 page.check_crash_count: util.WaitFor(lambda: tab.EvaluateJavaScript( 'window.domAutomationController._finished'), wait_timeout) # Crash the GPU process. gpucrash_tab = tab.browser.tabs.New() # To access these debug URLs from Telemetry, they have to be # written using the chrome:// scheme. # The try/except is a workaround for crbug.com/368107. try: gpucrash_tab.Navigate('chrome://gpucrash') except (exceptions.TabCrashException, Exception): print 'Tab crashed while navigating to chrome://gpucrash' # Activate the original tab and wait for completion. tab.Activate() completed = WaitForPageToFinish() if page.check_crash_count: if not tab.browser.supports_system_info: raise page_test.Failure('Browser must support system info') if not tab.EvaluateJavaScript( 'window.domAutomationController._succeeded'): raise page_test.Failure( 'Test failed (didn\'t render content properly?)') number_of_crashes = -1 # 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: raise page_test.Failure( 'Timed out waiting for a gpu process crash') elif number_of_crashes != expected_kills: raise page_test.Failure( 'Expected %d gpu process crashes; got: %d' % (expected_kills, number_of_crashes)) # The try/except is a workaround for crbug.com/368107. try: gpucrash_tab.Close() except (exceptions.TabCrashException, Exception): print 'Tab crashed while closing chrome://gpucrash' if not completed: raise page_test.Failure( 'Test didn\'t complete (no context lost event?)') if not tab.EvaluateJavaScript( 'window.domAutomationController._succeeded'): raise page_test.Failure( 'Test failed (context not restored properly?)') elif page.force_garbage_collection: # Try to corce GC to clean up any contexts not attached to the page. # This method seem unreliable, so the page will also attempt to force # GC through excessive allocations. tab.CollectGarbage() completed = WaitForPageToFinish() if not completed: raise page_test.Failure( 'Test didn\'t complete (no context restored event?)') if not tab.EvaluateJavaScript( 'window.domAutomationController._succeeded'): raise page_test.Failure( 'Test failed (context not restored properly?)') elif page.hide_tab_and_lose_context: if not tab.browser.supports_tab_control: raise page_test.Failure('Browser must support tab control') # Test losing a context in a hidden tab. This test passes if the tab # doesn't crash. dummy_tab = tab.browser.tabs.New() tab.EvaluateJavaScript('loseContextUsingExtension()') tab.Activate() completed = WaitForPageToFinish() if not completed: raise page_test.Failure('Test didn\'t complete') if not tab.EvaluateJavaScript( 'window.domAutomationController._succeeded'): raise page_test.Failure('Test failed') else: completed = WaitForPageToFinish() if not completed: raise page_test.Failure('Test didn\'t complete') if not tab.EvaluateJavaScript( 'window.domAutomationController._succeeded'): raise page_test.Failure('Test failed') # Test that navigating to chrome://gpucrash causes the GPU process to crash # exactly one time per navigation. class GPUProcessCrashesExactlyOnce(page.Page): def __init__(self, page_set, base_dir): super(GPUProcessCrashesExactlyOnce, self).__init__( url='file://gpu_process_crash.html', page_set=page_set, base_dir=base_dir, name='GpuCrash.GPUProcessCrashesExactlyOnce') self.script_to_evaluate_on_commit = harness_script self.kill_gpu_process = True self.number_of_gpu_process_kills = 2 self.check_crash_count = True self.force_garbage_collection = False self.hide_tab_and_lose_context = False def RunNavigateSteps(self, action_runner): action_runner.NavigateToPage(self) action_runner.WaitForJavaScriptCondition( 'window.domAutomationController._loaded') class WebGLContextLostFromGPUProcessExitPage(page.Page): def __init__(self, page_set, base_dir): super(WebGLContextLostFromGPUProcessExitPage, self).__init__( url='file://webgl.html?query=kill_after_notification', page_set=page_set, base_dir=base_dir, name='ContextLost.WebGLContextLostFromGPUProcessExit') self.script_to_evaluate_on_commit = harness_script self.kill_gpu_process = True self.check_crash_count = False self.number_of_gpu_process_kills = 1 self.force_garbage_collection = False self.hide_tab_and_lose_context = False def RunNavigateSteps(self, action_runner): action_runner.NavigateToPage(self) action_runner.WaitForJavaScriptCondition( 'window.domAutomationController._loaded') class WebGLContextLostFromLoseContextExtensionPage(page.Page): def __init__(self, page_set, base_dir): super(WebGLContextLostFromLoseContextExtensionPage, self).__init__( url='file://webgl.html?query=WEBGL_lose_context', page_set=page_set, base_dir=base_dir, name='ContextLost.WebGLContextLostFromLoseContextExtension') self.script_to_evaluate_on_commit = harness_script self.kill_gpu_process = False self.check_crash_count = False self.force_garbage_collection = False self.hide_tab_and_lose_context = False def RunNavigateSteps(self, action_runner): action_runner.NavigateToPage(self) action_runner.WaitForJavaScriptCondition( 'window.domAutomationController._finished') class WebGLContextLostInHiddenTabPage(page.Page): def __init__(self, page_set, base_dir): super(WebGLContextLostInHiddenTabPage, self).__init__( url='file://webgl.html?query=kill_after_notification', page_set=page_set, base_dir=base_dir, name='ContextLost.WebGLContextLostInHiddenTab') self.script_to_evaluate_on_commit = harness_script self.kill_gpu_process = False self.check_crash_count = False self.force_garbage_collection = False self.hide_tab_and_lose_context = True def RunNavigateSteps(self, action_runner): action_runner.NavigateToPage(self) action_runner.WaitForJavaScriptCondition( 'window.domAutomationController._loaded') class WebGLContextLostFromQuantityPage(page.Page): def __init__(self, page_set, base_dir): super(WebGLContextLostFromQuantityPage, self).__init__( url='file://webgl.html?query=forced_quantity_loss', page_set=page_set, base_dir=base_dir, name='ContextLost.WebGLContextLostFromQuantity') self.script_to_evaluate_on_commit = harness_script self.kill_gpu_process = False self.check_crash_count = False self.force_garbage_collection = True self.hide_tab_and_lose_context = False def RunNavigateSteps(self, action_runner): action_runner.NavigateToPage(self) action_runner.WaitForJavaScriptCondition( 'window.domAutomationController._loaded') class WebGLContextLostFromSelectElementPage(page.Page): def __init__(self, page_set, base_dir): super(WebGLContextLostFromSelectElementPage, self).__init__( url='file://webgl_with_select_element.html', page_set=page_set, base_dir=base_dir, name='ContextLost.WebGLContextLostFromSelectElement') self.script_to_evaluate_on_commit = harness_script self.kill_gpu_process = False self.check_crash_count = False self.force_garbage_collection = False self.hide_tab_and_lose_context = False def RunNavigateSteps(self, action_runner): action_runner.NavigateToPage(self) action_runner.WaitForJavaScriptCondition( 'window.domAutomationController._loaded') class ContextLost(benchmark_module.Benchmark): enabled = True test = _ContextLostValidator def CreateExpectations(self, page_set): return context_lost_expectations.ContextLostExpectations() # For the record, this would have been another way to get the pages # to repeat. pageset_repeat would be another option. # options = {'page_repeat': 5} def CreatePageSet(self, options): ps = page_set.PageSet( file_path=data_path, user_agent_type='desktop', serving_dirs=set([''])) ps.AddPage(GPUProcessCrashesExactlyOnce(ps, ps.base_dir)) ps.AddPage(WebGLContextLostFromGPUProcessExitPage(ps, ps.base_dir)) ps.AddPage(WebGLContextLostFromLoseContextExtensionPage(ps, ps.base_dir)) ps.AddPage(WebGLContextLostFromQuantityPage(ps, ps.base_dir)) ps.AddPage(WebGLContextLostFromSelectElementPage(ps, ps.base_dir)) ps.AddPage(WebGLContextLostInHiddenTabPage(ps, ps.base_dir)) return ps