Bug 1496824 [wpt PR 13367] - Refactor the testharness/testdriver messaging protocol implementation, a=testonly
authorjgraham <james@hoppipolla.co.uk>
Thu, 11 Oct 2018 09:31:31 +0000
changeset 499481 9feefc4d63b2edb086ac4f3fb49338aa8fc07b97
parent 499480 50023456d99683f78270df0e8f5c54e772d08216
child 499482 5100c6c46f28bbafc299040ca39708da6041c872
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1496824, 13367
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1496824 [wpt PR 13367] - Refactor the testharness/testdriver messaging protocol implementation, a=testonly Automatic update from web-platform-testsRefactor the testharness/testdriver messaging protocol implementation (#13367) This causes most of the code to be run once when the runner window is opened rather than being run on each resume. This should be a little more efficient. However the main motivation is to have a single long-lived event handler rather than having to ensure that the event handler is correctly cleaned up after each test. Previously this was not done carefully and it was possible for events from one test to affect subsequent tests. -- wpt-commits: b0d2d2da21dfd03e86c5d710c926ae7df3b9f0d0 wpt-pr: 13367
testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executormarionette.py
testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executorselenium.py
testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executorwebdriver.py
testing/web-platform/tests/tools/wptrunner/wptrunner/executors/runner.js
testing/web-platform/tests/tools/wptrunner/wptrunner/executors/testharness_webdriver.js
testing/web-platform/tests/tools/wptrunner/wptrunner/executors/testharness_webdriver_resume.js
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executormarionette.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executormarionette.py
@@ -104,16 +104,18 @@ class MarionetteBaseProtocolPart(BasePro
                 self.logger.warning(traceback.format_exc(e))
                 break
 
 
 class MarionetteTestharnessProtocolPart(TestharnessProtocolPart):
     def __init__(self, parent):
         super(MarionetteTestharnessProtocolPart, self).__init__(parent)
         self.runner_handle = None
+        with open(os.path.join(here, "runner.js")) as f:
+            self.runner_script = f.read()
 
     def setup(self):
         self.marionette = self.parent.marionette
 
     def load_runner(self, url_protocol):
         # Check if we previously had a test window open, and if we did make sure it's closed
         self.parent.base.execute_script("if (window.win) {window.win.close()}")
         url = urlparse.urljoin(self.parent.executor.server_url(url_protocol),
@@ -124,18 +126,18 @@ class MarionetteTestharnessProtocolPart(
             self.dismiss_alert(lambda: self.marionette.navigate(url))
         except Exception as e:
             self.logger.critical(
                 "Loading initial page %s failed. Ensure that the "
                 "there are no other programs bound to this port and "
                 "that your firewall rules or network setup does not "
                 "prevent access.\e%s" % (url, traceback.format_exc(e)))
             raise
-        self.parent.base.execute_script(
-            "document.title = '%s'" % threading.current_thread().name.replace("'", '"'))
+        format_map = {"title": threading.current_thread().name.replace("'", '"')}
+        self.parent.base.execute_script(self.runner_script % format_map)
 
     def close_old_windows(self, url_protocol):
         handles = self.marionette.window_handles
         runner_handle = None
         try:
             handles.remove(self.runner_handle)
             runner_handle = self.runner_handle
         except ValueError:
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executorselenium.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executorselenium.py
@@ -67,24 +67,26 @@ class SeleniumBaseProtocolPart(BaseProto
             except Exception as e:
                 self.logger.error(traceback.format_exc(e))
                 break
 
 
 class SeleniumTestharnessProtocolPart(TestharnessProtocolPart):
     def setup(self):
         self.webdriver = self.parent.webdriver
+        with open(os.path.join(here, "runner.js")) as f:
+            self.runner_script = f.read()
 
     def load_runner(self, url_protocol):
         url = urlparse.urljoin(self.parent.executor.server_url(url_protocol),
                                "/testharness_runner.html")
         self.logger.debug("Loading %s" % url)
         self.webdriver.get(url)
-        self.webdriver.execute_script("document.title = '%s'" %
-                                      threading.current_thread().name.replace("'", '"'))
+        format_map = {"title": threading.current_thread().name.replace("'", '"')}
+        self.parent.base.execute_script(self.runner_script % format_map)
 
     def close_old_windows(self):
         exclude = self.webdriver.current_window_handle
         handles = [item for item in self.webdriver.window_handles if item != exclude]
         for handle in handles:
             try:
                 self.webdriver.switch_to_window(handle)
                 self.webdriver.close()
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executorwebdriver.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executorwebdriver.py
@@ -47,38 +47,40 @@ class WebDriverBaseProtocolPart(BaseProt
 
     def set_window(self, handle):
         self.webdriver.window_handle = handle
 
     def wait(self):
         while True:
             try:
                 self.webdriver.execute_async_script("")
-            except client.TimeoutException:
+            except (client.TimeoutException, client.ScriptTimeoutException):
                 pass
             except (socket.timeout, client.NoSuchWindowException,
                     client.UnknownErrorException, IOError):
                 break
             except Exception as e:
                 self.logger.error(traceback.format_exc(e))
                 break
 
 
 class WebDriverTestharnessProtocolPart(TestharnessProtocolPart):
     def setup(self):
         self.webdriver = self.parent.webdriver
+        with open(os.path.join(here, "runner.js")) as f:
+            self.runner_script = f.read()
 
     def load_runner(self, url_protocol):
         url = urlparse.urljoin(self.parent.executor.server_url(url_protocol),
                                "/testharness_runner.html")
         self.logger.debug("Loading %s" % url)
 
         self.webdriver.url = url
-        self.webdriver.execute_script("document.title = '%s'" %
-                                      threading.current_thread().name.replace("'", '"'))
+        format_map = {"title": threading.current_thread().name.replace("'", '"')}
+        self.parent.base.execute_script(self.runner_script % format_map)
 
     def close_old_windows(self):
         exclude = self.webdriver.window_handle
         handles = [item for item in self.webdriver.handles if item != exclude]
         for handle in handles:
             try:
                 self.webdriver.window_handle = handle
                 self.webdriver.close()
@@ -120,16 +122,17 @@ class WebDriverSelectorProtocolPart(Sele
         return self.webdriver.find.css(selector)
 
 
 class WebDriverClickProtocolPart(ClickProtocolPart):
     def setup(self):
         self.webdriver = self.parent.webdriver
 
     def element(self, element):
+        self.logger.info("click " + repr(element))
         return element.click()
 
 
 class WebDriverSendKeysProtocolPart(SendKeysProtocolPart):
     def setup(self):
         self.webdriver = self.parent.webdriver
 
     def send_keys(self, element, keys):
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/runner.js
@@ -0,0 +1,59 @@
+document.title = '%(title)s';
+
+window.addEventListener(
+  "message",
+  function(event) {
+    window.message_queue.push(event);
+    window.process_next_event();
+  },
+  false
+);
+
+
+window.process_next_event = function() {
+  /* This function handles the next testdriver event. The presence of
+     window.testdriver_callback is used as a switch; when that function
+     is present we are able to handle the next event and when is is not
+     present we must wait. Therefore to drive the event processing, this
+     function must be called in two circumstances:
+       * Every time there is a new event that we may be able to handle
+       * Every time we set the callback function
+     This function unsets the callback, so no further testdriver actions
+     will be run until it is reset, which wptrunner does after it has
+     completed handling the current action.
+   */
+  if (!window.testdriver_callback) {
+    return;
+  }
+  var event = window.message_queue.shift();
+  if (!event) {
+    return;
+  }
+  var data = event.data;
+
+  var payload = undefined;
+
+  switch(data.type) {
+  case "complete":
+    var tests = event.data.tests;
+    var status = event.data.status;
+
+    var subtest_results = tests.map(function(x) {
+      return [x.name, x.status, x.message, x.stack];
+    });
+    payload = [status.status,
+               status.message,
+               status.stack,
+               subtest_results];
+    clearTimeout(window.timer);
+    break;
+  case "action":
+    payload = data;
+    break;
+  default:
+    return;
+  }
+  var callback = window.testdriver_callback;
+  window.testdriver_callback = null;
+  callback([window.url, data.type, payload]);
+};
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/testharness_webdriver.js
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/testharness_webdriver.js
@@ -1,25 +1,13 @@
 window.timeout_multiplier = %(timeout_multiplier)d;
+window.url = "%(url)s";
+window.win = window.open("%(abs_url)s", "%(window_id)s");
 
 window.message_queue = [];
-
-window.setMessageListener = function(func) {
-  window.current_listener = func;
-  window.addEventListener(
-    "message",
-    func,
-    false
-  );
-};
-
-window.setMessageListener(function(event) {
-  window.message_queue.push(event);
-});
-
-window.win = window.open("%(abs_url)s", "%(window_id)s");
+window.testdriver_callback = null;
 
 if (%(timeout)s != null) {
   window.timer = setTimeout(function() {
     window.win.timeout();
     window.win.close();
   }, %(timeout)s);
 }
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/testharness_webdriver_resume.js
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/testharness_webdriver_resume.js
@@ -1,46 +1,3 @@
 var callback = arguments[arguments.length - 1];
-
-function process_event(event) {
-  var data = event.data;
-
-  var payload = undefined;
-
-  switch(data.type) {
-  case "complete":
-    var tests = event.data.tests;
-    var status = event.data.status;
-
-    var subtest_results = tests.map(function(x) {
-      return [x.name, x.status, x.message, x.stack];
-    });
-    payload = [status.status,
-               status.message,
-               status.stack,
-               subtest_results];
-    clearTimeout(window.timer);
-    break;
-
-  case "action":
-    window.setMessageListener(function(event) {
-      window.message_queue.push(event);
-    });
-    payload = data;
-    break;
-  default:
-    return;
-  }
-
-  callback(["%(url)s", data.type, payload]);
-}
-
-window.removeEventListener("message", window.current_listener);
-if (window.message_queue.length) {
-  var next = window.message_queue.shift();
-  process_event(next);
-} else {
-  window.addEventListener(
-    "message", function f(event) {
-      window.removeEventListener("message", f);
-      process_event(event);
-    }, false);
-}
+window.testdriver_callback = callback;
+window.process_next_event();