Bug 1288912: Add tests for native messaging round trip time. r?aswan
MozReview-Commit-ID: 6gOpZhvAZUE
--- 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]