Bug 1288912: Add tests for native messaging round trip time. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Thu, 28 Jul 2016 20:54:40 -0700
changeset 394139 39c06e925db4bba1e6a2f9d93918885033e5e8ab
parent 394131 97b1b6e04bc92e1efa31b66603fe4f060d161aca
child 526740 4ddd56558d0ce6c35259563748a06739ddd128bc
push id24498
push usermaglione.k@gmail.com
push dateFri, 29 Jul 2016 03:54:55 +0000
reviewersaswan
bugs1288912
milestone50.0a1
Bug 1288912: Add tests for native messaging round trip time. r?aswan MozReview-Commit-ID: 6gOpZhvAZUE
toolkit/components/extensions/NativeMessaging.jsm
toolkit/components/extensions/test/xpcshell/head.js
toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js
toolkit/components/extensions/test/xpcshell/xpcshell.ini
--- a/toolkit/components/extensions/NativeMessaging.jsm
+++ b/toolkit/components/extensions/NativeMessaging.jsm
@@ -210,17 +210,17 @@ this.NativeApp = class extends EventEmit
       }).then(proc => {
         this.startupPromise = null;
         this.proc = proc;
         this._startRead();
         this._startWrite();
         this._startStderrRead();
       }).catch(err => {
         this.startupPromise = null;
-        Cu.reportError(err.message);
+        Cu.reportError(err);
         this._cleanup(err);
       });
   }
 
   // A port is definitely "alive" if this.proc is non-null.  But we have
   // to provide a live port object immediately when connecting so we also
   // need to consider a port alive if proc is null but the startupPromise
   // is still pending.
--- a/toolkit/components/extensions/test/xpcshell/head.js
+++ b/toolkit/components/extensions/test/xpcshell/head.js
@@ -1,14 +1,15 @@
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
-/* exported createHttpServer */
+/* exported createHttpServer, promiseConsoleOutput */
 
+Components.utils.import("resource://gre/modules/Task.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Extension",
                                   "resource://gre/modules/Extension.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionData",
                                   "resource://gre/modules/Extension.jsm");
@@ -47,8 +48,37 @@ function createHttpServer(port = -1) {
   do_register_cleanup(() => {
     return new Promise(resolve => {
       server.stop(resolve);
     });
   });
 
   return server;
 }
+
+var promiseConsoleOutput = Task.async(function* (task) {
+  const DONE = `=== console listener ${Math.random()} done ===`;
+
+  let listener;
+  let messages = [];
+  let awaitListener = new Promise(resolve => {
+    listener = msg => {
+      if (msg == DONE) {
+        resolve();
+      } else {
+        void (msg instanceof Ci.nsIConsoleMessage);
+        messages.push(msg);
+      }
+    };
+  });
+
+  Services.console.registerListener(listener);
+  try {
+    let result = yield task();
+
+    Services.console.logStringMessage(DONE);
+    yield awaitListener;
+
+    return {messages, result};
+  } finally {
+    Services.console.unregisterListener(listener);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js
@@ -0,0 +1,204 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "MockRegistry",
+                                  "resource://testing-common/MockRegistry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+                                  "resource://gre/modules/osfile.jsm");
+
+Cu.import("resource://gre/modules/Subprocess.jsm");
+
+const MAX_ROUND_TRIP_TIME_MS = AppConstants.DEBUG || AppConstants.ASAN ? 36 : 18;
+
+
+let tmpDir = FileUtils.getDir("TmpD", ["NativeMessaging"]);
+tmpDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+
+do_register_cleanup(() => {
+  tmpDir.remove(true);
+});
+
+function getPath(filename) {
+  return OS.Path.join(tmpDir.path, filename);
+}
+
+const ID = "native@tests.mozilla.org";
+
+const ECHO_BODY = String.raw`
+  import struct
+  import sys
+
+  while True:
+      rawlen = sys.stdin.read(4)
+      if len(rawlen) == 0:
+          sys.exit(0)
+
+      msglen = struct.unpack('@I', rawlen)[0]
+      msg = sys.stdin.read(msglen)
+
+      sys.stdout.write(struct.pack('@I', msglen))
+      sys.stdout.write(msg)
+`;
+
+const SCRIPTS = [
+  {
+    name: "echo",
+    description: "A native app that echoes back messages it receives",
+    script: ECHO_BODY.replace(/^ {2}/gm, ""),
+  },
+];
+
+add_task(function* setup() {
+  const PERMS = {unixMode: 0o755};
+
+  let pythonPath = yield Subprocess.pathSearch("python2.7").catch(err => {
+    return Subprocess.pathSearch("python");
+  });
+
+  function* writeManifest(script, scriptPath, path) {
+    let body = `#!${pythonPath} -u\n${script.script}`;
+
+    yield OS.File.writeAtomic(scriptPath, body);
+    yield OS.File.setPermissions(scriptPath, PERMS);
+
+    let manifest = {
+      name: script.name,
+      description: script.description,
+      path,
+      type: "stdio",
+      allowed_extensions: [ID],
+    };
+
+    let manifestPath = getPath(`${script.name}.json`);
+    yield OS.File.writeAtomic(manifestPath, JSON.stringify(manifest));
+
+    return manifestPath;
+  }
+
+  switch (AppConstants.platform) {
+    case "macosx":
+    case "linux":
+      let dirProvider = {
+        getFile(property) {
+          if (property == "XREUserNativeMessaging") {
+            return tmpDir.clone();
+          } else if (property == "XRESysNativeMessaging") {
+            return tmpDir.clone();
+          }
+          return null;
+        },
+      };
+
+      Services.dirsvc.registerProvider(dirProvider);
+      do_register_cleanup(() => {
+        Services.dirsvc.unregisterProvider(dirProvider);
+      });
+
+      for (let script of SCRIPTS) {
+        let path = getPath(`${script.name}.py`);
+
+        yield writeManifest(script, path, path);
+      }
+      break;
+
+    case "win":
+      const REGKEY = String.raw`Software\Mozilla\NativeMessagingHosts`;
+
+      let registry = new MockRegistry();
+      do_register_cleanup(() => {
+        registry.shutdown();
+      });
+
+      for (let script of SCRIPTS) {
+        let batPath = getPath(`${script.name}.bat`);
+        let scriptPath = getPath(`${script.name}.py`);
+
+        let batBody = `@ECHO OFF\n${pythonPath} -u ${scriptPath} %*\n`;
+        yield OS.File.writeAtomic(batPath, batBody);
+
+        let manifestPath = yield writeManifest(script, scriptPath, batPath);
+
+        registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+                          `${REGKEY}\\${script.name}`, "", manifestPath);
+      }
+      break;
+
+    default:
+      ok(false, `Native messaing is not supported on ${AppConstants.platform}`);
+  }
+});
+
+add_task(function* test_round_trip_perf() {
+  let extension = ExtensionTestUtils.loadExtension({
+    background() {
+      let port = browser.runtime.connectNative("echo");
+
+      const COUNT = 1000;
+      let now;
+      let count = 0;
+
+      function next() {
+        port.postMessage({
+          "Lorem": {
+            "ipsum": {
+              "dolor": [
+                "sit amet,",
+                "consectetur adipiscing elit,",
+                "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
+              ],
+              "Ut enim": [
+                "ad minim veniam,",
+                "quis nostrud exercitation ullamco",
+                "laboris nisi ut aliquip ex ea commodo consequat.",
+              ],
+              "Duis": [
+                "aute irure dolor in reprehenderit in",
+                "voluptate velit esse cillum dolore eu",
+                "fugiat nulla pariatur.",
+              ],
+              "Excepteur": [
+                "sint occaecat cupidatat non proident",
+                "sunt in culpa qui officia deserunt",
+                "mollit anim id est laborum.",
+              ],
+            },
+          },
+        });
+      }
+
+      function finish() {
+        let roundTripTime = (Date.now() - now) / COUNT;
+
+        browser.test.sendMessage("result", roundTripTime);
+      }
+
+      port.onMessage.addListener(msg => {
+        if (count == 0) {
+          // Skip the first round, since it includes the time it takes
+          // the app to start up.
+          now = Date.now();
+        }
+
+        if (count++ <= COUNT) {
+          next();
+        } else {
+          finish();
+        }
+      });
+
+      next();
+    },
+    manifest: {
+      permissions: ["nativeMessaging"],
+    },
+  }, ID);
+
+  yield extension.startup();
+
+  let roundTripTime = yield extension.awaitMessage("result");
+  ok(roundTripTime <= MAX_ROUND_TRIP_TIME_MS,
+     `Expected round trip time (${roundTripTime}ms) to be less than ${MAX_ROUND_TRIP_TIME_MS}ms`);
+
+  yield extension.unload();
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -27,16 +27,17 @@ skip-if = os == "android"
 [test_ext_downloads_search.js]
 skip-if = os == "android"
 [test_ext_extension.js]
 [test_ext_idle.js]
 [test_ext_json_parser.js]
 [test_ext_localStorage.js]
 [test_ext_manifest_content_security_policy.js]
 [test_ext_manifest_incognito.js]
+[test_ext_native_messaging.js]
 [test_ext_onmessage_removelistener.js]
 [test_ext_runtime_getPlatformInfo.js]
 [test_ext_runtime_sendMessage.js]
 [test_ext_schemas.js]
 [test_ext_simple.js]
 [test_ext_storage.js]
 [test_getAPILevelForWindow.js]
 [test_locale_converter.js]