1# Copyright (c) 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. 4import os 5import time 6 7import context_lost_expectations 8 9from telemetry import benchmark as benchmark_module 10from telemetry.core import exceptions 11from telemetry.core import util 12from telemetry.page import page 13from telemetry.page import page_set 14from telemetry.page import page_test 15 16data_path = os.path.join( 17 util.GetChromiumSrcDir(), 'content', 'test', 'data', 'gpu') 18 19wait_timeout = 20 # seconds 20 21harness_script = r""" 22 var domAutomationController = {}; 23 24 domAutomationController._loaded = false; 25 domAutomationController._succeeded = false; 26 domAutomationController._finished = false; 27 28 domAutomationController.setAutomationId = function(id) {} 29 30 domAutomationController.send = function(msg) { 31 msg = msg.toLowerCase() 32 if (msg == "loaded") { 33 domAutomationController._loaded = true; 34 } else if (msg == "success") { 35 domAutomationController._succeeded = true; 36 domAutomationController._finished = true; 37 } else { 38 domAutomationController._succeeded = false; 39 domAutomationController._finished = true; 40 } 41 } 42 43 domAutomationController.reset = function() { 44 domAutomationController._succeeded = false; 45 domAutomationController._finished = false; 46 } 47 48 window.domAutomationController = domAutomationController; 49 console.log("Harness injected."); 50""" 51 52class _ContextLostValidator(page_test.PageTest): 53 def __init__(self): 54 # Strictly speaking this test doesn't yet need a browser restart 55 # after each run, but if more tests are added which crash the GPU 56 # process, then it will. 57 super(_ContextLostValidator, self).__init__( 58 needs_browser_restart_after_each_page=True) 59 60 def CustomizeBrowserOptions(self, options): 61 options.AppendExtraBrowserArgs( 62 '--disable-domain-blocking-for-3d-apis') 63 options.AppendExtraBrowserArgs( 64 '--disable-gpu-process-crash-limit') 65 # Required for about:gpucrash handling from Telemetry. 66 options.AppendExtraBrowserArgs('--enable-gpu-benchmarking') 67 68 def ValidateAndMeasurePage(self, page, tab, results): 69 def WaitForPageToFinish(): 70 print "Waiting for page to finish." 71 try: 72 util.WaitFor(lambda: tab.EvaluateJavaScript( 73 'window.domAutomationController._finished'), wait_timeout) 74 return True 75 except util.TimeoutException: 76 return False 77 78 if page.kill_gpu_process: 79 # Doing the GPU process kill operation cooperatively -- in the 80 # same page's context -- is much more stressful than restarting 81 # the browser every time. 82 for x in range(page.number_of_gpu_process_kills): 83 if not tab.browser.supports_tab_control: 84 raise page_test.Failure('Browser must support tab control') 85 86 expected_kills = x + 1 87 88 # Reset the test's state. 89 tab.EvaluateJavaScript( 90 'window.domAutomationController.reset()'); 91 92 # If we're running the GPU process crash test, we need the 93 # test to have fully reset before crashing the GPU process. 94 if page.check_crash_count: 95 util.WaitFor(lambda: tab.EvaluateJavaScript( 96 'window.domAutomationController._finished'), wait_timeout) 97 98 # Crash the GPU process. 99 gpucrash_tab = tab.browser.tabs.New() 100 # To access these debug URLs from Telemetry, they have to be 101 # written using the chrome:// scheme. 102 # The try/except is a workaround for crbug.com/368107. 103 try: 104 gpucrash_tab.Navigate('chrome://gpucrash') 105 except (exceptions.TabCrashException, Exception): 106 print 'Tab crashed while navigating to chrome://gpucrash' 107 # Activate the original tab and wait for completion. 108 tab.Activate() 109 completed = WaitForPageToFinish() 110 111 if page.check_crash_count: 112 if not tab.browser.supports_system_info: 113 raise page_test.Failure('Browser must support system info') 114 115 if not tab.EvaluateJavaScript( 116 'window.domAutomationController._succeeded'): 117 raise page_test.Failure( 118 'Test failed (didn\'t render content properly?)') 119 120 number_of_crashes = -1 121 # To allow time for a gpucrash to complete, wait up to 20s, 122 # polling repeatedly. 123 start_time = time.time() 124 current_time = time.time() 125 while current_time - start_time < 20: 126 system_info = tab.browser.GetSystemInfo() 127 number_of_crashes = \ 128 system_info.gpu.aux_attributes[u'process_crash_count'] 129 if number_of_crashes >= expected_kills: 130 break 131 time.sleep(1) 132 current_time = time.time() 133 134 # Wait 5 more seconds and re-read process_crash_count, in 135 # attempt to catch latent process crashes. 136 time.sleep(5) 137 system_info = tab.browser.GetSystemInfo() 138 number_of_crashes = \ 139 system_info.gpu.aux_attributes[u'process_crash_count'] 140 141 if number_of_crashes < expected_kills: 142 raise page_test.Failure( 143 'Timed out waiting for a gpu process crash') 144 elif number_of_crashes != expected_kills: 145 raise page_test.Failure( 146 'Expected %d gpu process crashes; got: %d' % 147 (expected_kills, number_of_crashes)) 148 149 # The try/except is a workaround for crbug.com/368107. 150 try: 151 gpucrash_tab.Close() 152 except (exceptions.TabCrashException, Exception): 153 print 'Tab crashed while closing chrome://gpucrash' 154 if not completed: 155 raise page_test.Failure( 156 'Test didn\'t complete (no context lost event?)') 157 if not tab.EvaluateJavaScript( 158 'window.domAutomationController._succeeded'): 159 raise page_test.Failure( 160 'Test failed (context not restored properly?)') 161 elif page.force_garbage_collection: 162 # Try to corce GC to clean up any contexts not attached to the page. 163 # This method seem unreliable, so the page will also attempt to force 164 # GC through excessive allocations. 165 tab.CollectGarbage() 166 completed = WaitForPageToFinish() 167 168 if not completed: 169 raise page_test.Failure( 170 'Test didn\'t complete (no context restored event?)') 171 if not tab.EvaluateJavaScript( 172 'window.domAutomationController._succeeded'): 173 raise page_test.Failure( 174 'Test failed (context not restored properly?)') 175 elif page.hide_tab_and_lose_context: 176 if not tab.browser.supports_tab_control: 177 raise page_test.Failure('Browser must support tab control') 178 179 # Test losing a context in a hidden tab. This test passes if the tab 180 # doesn't crash. 181 dummy_tab = tab.browser.tabs.New() 182 tab.EvaluateJavaScript('loseContextUsingExtension()') 183 tab.Activate() 184 185 completed = WaitForPageToFinish() 186 187 if not completed: 188 raise page_test.Failure('Test didn\'t complete') 189 if not tab.EvaluateJavaScript( 190 'window.domAutomationController._succeeded'): 191 raise page_test.Failure('Test failed') 192 else: 193 completed = WaitForPageToFinish() 194 195 if not completed: 196 raise page_test.Failure('Test didn\'t complete') 197 if not tab.EvaluateJavaScript( 198 'window.domAutomationController._succeeded'): 199 raise page_test.Failure('Test failed') 200 201# Test that navigating to chrome://gpucrash causes the GPU process to crash 202# exactly one time per navigation. 203class GPUProcessCrashesExactlyOnce(page.Page): 204 def __init__(self, page_set, base_dir): 205 super(GPUProcessCrashesExactlyOnce, self).__init__( 206 url='file://gpu_process_crash.html', 207 page_set=page_set, 208 base_dir=base_dir, 209 name='GpuCrash.GPUProcessCrashesExactlyOnce') 210 self.script_to_evaluate_on_commit = harness_script 211 self.kill_gpu_process = True 212 self.number_of_gpu_process_kills = 2 213 self.check_crash_count = True 214 self.force_garbage_collection = False 215 self.hide_tab_and_lose_context = False 216 217 def RunNavigateSteps(self, action_runner): 218 action_runner.NavigateToPage(self) 219 action_runner.WaitForJavaScriptCondition( 220 'window.domAutomationController._loaded') 221 222class WebGLContextLostFromGPUProcessExitPage(page.Page): 223 def __init__(self, page_set, base_dir): 224 super(WebGLContextLostFromGPUProcessExitPage, self).__init__( 225 url='file://webgl.html?query=kill_after_notification', 226 page_set=page_set, 227 base_dir=base_dir, 228 name='ContextLost.WebGLContextLostFromGPUProcessExit') 229 self.script_to_evaluate_on_commit = harness_script 230 self.kill_gpu_process = True 231 self.check_crash_count = False 232 self.number_of_gpu_process_kills = 1 233 self.force_garbage_collection = False 234 self.hide_tab_and_lose_context = False 235 236 def RunNavigateSteps(self, action_runner): 237 action_runner.NavigateToPage(self) 238 action_runner.WaitForJavaScriptCondition( 239 'window.domAutomationController._loaded') 240 241 242class WebGLContextLostFromLoseContextExtensionPage(page.Page): 243 def __init__(self, page_set, base_dir): 244 super(WebGLContextLostFromLoseContextExtensionPage, self).__init__( 245 url='file://webgl.html?query=WEBGL_lose_context', 246 page_set=page_set, 247 base_dir=base_dir, 248 name='ContextLost.WebGLContextLostFromLoseContextExtension') 249 self.script_to_evaluate_on_commit = harness_script 250 self.kill_gpu_process = False 251 self.check_crash_count = False 252 self.force_garbage_collection = False 253 self.hide_tab_and_lose_context = False 254 255 def RunNavigateSteps(self, action_runner): 256 action_runner.NavigateToPage(self) 257 action_runner.WaitForJavaScriptCondition( 258 'window.domAutomationController._finished') 259 260 261class WebGLContextLostInHiddenTabPage(page.Page): 262 def __init__(self, page_set, base_dir): 263 super(WebGLContextLostInHiddenTabPage, self).__init__( 264 url='file://webgl.html?query=kill_after_notification', 265 page_set=page_set, 266 base_dir=base_dir, 267 name='ContextLost.WebGLContextLostInHiddenTab') 268 self.script_to_evaluate_on_commit = harness_script 269 self.kill_gpu_process = False 270 self.check_crash_count = False 271 self.force_garbage_collection = False 272 self.hide_tab_and_lose_context = True 273 274 def RunNavigateSteps(self, action_runner): 275 action_runner.NavigateToPage(self) 276 action_runner.WaitForJavaScriptCondition( 277 'window.domAutomationController._loaded') 278 279 280class WebGLContextLostFromQuantityPage(page.Page): 281 def __init__(self, page_set, base_dir): 282 super(WebGLContextLostFromQuantityPage, self).__init__( 283 url='file://webgl.html?query=forced_quantity_loss', 284 page_set=page_set, 285 base_dir=base_dir, 286 name='ContextLost.WebGLContextLostFromQuantity') 287 self.script_to_evaluate_on_commit = harness_script 288 self.kill_gpu_process = False 289 self.check_crash_count = False 290 self.force_garbage_collection = True 291 self.hide_tab_and_lose_context = False 292 293 def RunNavigateSteps(self, action_runner): 294 action_runner.NavigateToPage(self) 295 action_runner.WaitForJavaScriptCondition( 296 'window.domAutomationController._loaded') 297 298class WebGLContextLostFromSelectElementPage(page.Page): 299 def __init__(self, page_set, base_dir): 300 super(WebGLContextLostFromSelectElementPage, self).__init__( 301 url='file://webgl_with_select_element.html', 302 page_set=page_set, 303 base_dir=base_dir, 304 name='ContextLost.WebGLContextLostFromSelectElement') 305 self.script_to_evaluate_on_commit = harness_script 306 self.kill_gpu_process = False 307 self.check_crash_count = False 308 self.force_garbage_collection = False 309 self.hide_tab_and_lose_context = False 310 311 def RunNavigateSteps(self, action_runner): 312 action_runner.NavigateToPage(self) 313 action_runner.WaitForJavaScriptCondition( 314 'window.domAutomationController._loaded') 315 316class ContextLost(benchmark_module.Benchmark): 317 enabled = True 318 test = _ContextLostValidator 319 320 def CreateExpectations(self, page_set): 321 return context_lost_expectations.ContextLostExpectations() 322 323 # For the record, this would have been another way to get the pages 324 # to repeat. pageset_repeat would be another option. 325 # options = {'page_repeat': 5} 326 def CreatePageSet(self, options): 327 ps = page_set.PageSet( 328 file_path=data_path, 329 user_agent_type='desktop', 330 serving_dirs=set([''])) 331 ps.AddPage(GPUProcessCrashesExactlyOnce(ps, ps.base_dir)) 332 ps.AddPage(WebGLContextLostFromGPUProcessExitPage(ps, ps.base_dir)) 333 ps.AddPage(WebGLContextLostFromLoseContextExtensionPage(ps, ps.base_dir)) 334 ps.AddPage(WebGLContextLostFromQuantityPage(ps, ps.base_dir)) 335 ps.AddPage(WebGLContextLostFromSelectElementPage(ps, ps.base_dir)) 336 ps.AddPage(WebGLContextLostInHiddenTabPage(ps, ps.base_dir)) 337 return ps 338