Index: content/test/gpu/gpu_tests/context_lost_integration_test.py |
diff --git a/content/test/gpu/gpu_tests/context_lost_integration_test.py b/content/test/gpu/gpu_tests/context_lost_integration_test.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..bdb6797f6368f9f7798e2d4cf37e043a21cef7db |
--- /dev/null |
+++ b/content/test/gpu/gpu_tests/context_lost_integration_test.py |
@@ -0,0 +1,252 @@ |
+# 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 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 |
+from telemetry.core import util |
+ |
+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.setAutomationId = function(id) {} |
+ |
+ 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): |
+ |
+ @classmethod |
+ def Name(cls): |
+ return 'context_lost' |
+ |
+ @classmethod |
+ def CustomizeOptions(cls): |
+ options = cls._finder_options.browser_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') |
+ |
+ @classmethod |
+ def GenerateGpuTests(cls, options): |
+ tests = (('GPUProcessCrashesExactlyOncePerVisitToAboutGpuCrash', |
+ 'gpu_process_crash.html'), |
+ ('WebGLContextLostFromGPUProcessExit', |
+ 'webgl.html?query=kill_after_notification'), |
+ ('WebGLContextLostFromLoseContextExtension', |
+ 'webgl.html?query=WEBGL_lose_context'), |
+ ('WebGLContextLostFromQuantity', |
+ 'webgl.html?query=forced_quantity_loss'), |
+ ('WebGLContextLostFromSelectElement', |
+ 'webgl_with_select_element.html'), |
+ ('WebGLContextLostInHiddenTab', |
+ 'webgl.html?query=kill_after_notification')) |
+ 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() |
+ |
+ @classmethod |
+ def setUpClass(cls): |
+ super(cls, ContextLostIntegrationTest).setUpClass() |
+ cls.CustomizeOptions() |
+ cls.SetBrowserOptions(cls._finder_options) |
+ cls.StartBrowser() |
+ cls.SetStaticServerDir(data_path) |
+ |
+ def _WaitForPageToFinish(self, tab): |
+ try: |
+ util.WaitFor(lambda: tab.EvaluateJavaScript( |
+ 'window.domAutomationController._finished'), 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: |
+ 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 Exception: |
+ print 'Tab crashed while navigating to chrome://gpucrash' |
+ # Activate the original tab and wait for completion. |
+ tab.Activate() |
+ completed = self._WaitForPageToFinish(tab) |
+ |
+ if check_crash_count: |
+ self._CheckCrashCount(tab, expected_kills) |
+ |
+ # The try/except is a workaround for crbug.com/368107. |
+ try: |
+ gpucrash_tab.Close() |
+ except Exception: |
+ print 'Tab crashed while closing chrome://gpucrash' |
+ 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): |
+ if not tab.browser.supports_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 |
+ # 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 _GPUProcessCrashesExactlyOncePerVisitToAboutGpuCrash(self, test_path): |
+ self._NavigateAndWaitForLoad(test_path) |
+ self._KillGPUProcess(2, True) |
+ self._RestartBrowser('must restart after tests that kill the GPU process') |
+ |
+ def _WebGLContextLostFromGPUProcessExit(self, test_path): |
+ self._NavigateAndWaitForLoad(test_path) |
+ self._KillGPUProcess(1, False) |
+ self._RestartBrowser('must restart after tests that kill the GPU process') |
+ |
+ def _WebGLContextLostFromLoseContextExtension(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._finished') |
+ |
+ def _WebGLContextLostFromQuantity(self, test_path): |
+ 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 _WebGLContextLostFromSelectElement(self, test_path): |
+ self._NavigateAndWaitForLoad(test_path) |
+ self._WaitForTabAndCheckCompletion() |
+ |
+ def _WebGLContextLostInHiddenTab(self, test_path): |
+ 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() |