Bug 1290598: Migrate native messaging tests to xpcshell. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 29 Jul 2016 18:48:37 -0700
changeset 394687 12ce3e486fbad367b30b595657e51f18d10b6810
parent 394686 df7e30ac15ad8b6a63e9061ee9f33032bb55d5b8
child 394688 5afc118a7b27ae71c9a5b34360180ef270b014a3
push id24624
push usermaglione.k@gmail.com
push dateSat, 30 Jul 2016 19:15:18 +0000
reviewersaswan
bugs1290598
milestone50.0a1
Bug 1290598: Migrate native messaging tests to xpcshell. r?aswan MozReview-Commit-ID: 4Uz73l8qGVE
toolkit/components/extensions/NativeMessaging.jsm
toolkit/components/extensions/test/mochitest/chrome.ini
toolkit/components/extensions/test/mochitest/test_chrome_ext_native_messaging.html
toolkit/components/extensions/test/xpcshell/head_native_messaging.js
toolkit/components/extensions/test/xpcshell/native_messaging.ini
toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js
--- a/toolkit/components/extensions/NativeMessaging.jsm
+++ b/toolkit/components/extensions/NativeMessaging.jsm
@@ -238,17 +238,19 @@ this.NativeApp = class extends EventEmit
           throw new Error(`Native application tried to send a message of ${len} bytes, which exceeds the limit of ${this.maxRead} bytes.`);
         }
         return this.proc.stdout.readJSON(len);
       }).then(msg => {
         this.emit("message", msg);
         this.readPromise = null;
         this._startRead();
       }).catch(err => {
-        Cu.reportError(err.message);
+        if (err.errorCode != Subprocess.ERROR_END_OF_FILE) {
+          Cu.reportError(err instanceof Error ? err : err.message);
+        }
         this._cleanup(err);
       });
   }
 
   _startWrite() {
     if (this.sendQueue.length == 0) {
       return;
     }
@@ -419,16 +421,22 @@ this.NativeApp = class extends EventEmit
       this.on("disconnect", (what, err) => { reject(err); });
     });
 
     let result = this.startupPromise.then(() => {
       this.send(msg);
       return responsePromise;
     });
 
+    result.catch(() => {
+      // Prevent the response promise from being reported as an
+      // unchecked rejection if the startup promise fails.
+      responsePromise.catch(() => {});
+    });
+
     result.then(() => {
       this._cleanup();
     }, () => {
       this._cleanup();
     });
 
     return result;
   }
--- a/toolkit/components/extensions/test/mochitest/chrome.ini
+++ b/toolkit/components/extensions/test/mochitest/chrome.ini
@@ -4,25 +4,23 @@ support-files =
   head.js
   file_sample.html
 
 [test_chrome_ext_background_debug_global.html]
 skip-if = (os == 'android') # android doesn't have devtools
 [test_chrome_ext_background_page.html]
 skip-if = (toolkit == 'android') # android doesn't have devtools
 [test_chrome_ext_eventpage_warning.html]
-[test_chrome_ext_native_messaging.html]
-skip-if = os == "android"  # native messaging is not supported on android
 [test_chrome_ext_contentscript_unrecognizedprop_warning.html]
 skip-if = (os == 'android') # browser.tabs is undefined. Bug 1258975 on android.
 [test_chrome_ext_trustworthy_origin.html]
 [test_chrome_ext_webnavigation_resolved_urls.html]
 skip-if = (os == 'android') # browser.tabs is undefined. Bug 1258975 on android.
 [test_chrome_ext_shutdown_cleanup.html]
 [test_chrome_native_messaging_paths.html]
 skip-if = os != "mac" && os != "linux"
 [test_ext_cookies_expiry.html]
 skip-if = buildapp == 'b2g'
 [test_ext_cookies_permissions.html]
 skip-if = buildapp == 'b2g'
 [test_ext_jsversion.html]
 skip-if = buildapp == 'b2g'
-[test_ext_schema.html]
\ No newline at end of file
+[test_ext_schema.html]
--- a/toolkit/components/extensions/test/xpcshell/head_native_messaging.js
+++ b/toolkit/components/extensions/test/xpcshell/head_native_messaging.js
@@ -1,21 +1,21 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 /* globals AppConstants, FileUtils */
-/* exported setupHosts */
+/* exported getSubprocessCount, setupHosts, waitForSubprocessExit */
 
 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");
+let {Subprocess, SubprocessImpl} = Cu.import("resource://gre/modules/Subprocess.jsm");
 
 
 let tmpDir = FileUtils.getDir("TmpD", ["NativeMessaging"]);
 tmpDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
 
 do_register_cleanup(() => {
   tmpDir.remove(true);
 });
@@ -105,8 +105,17 @@ function* setupHosts(scripts) {
         }
       }
       break;
 
     default:
       ok(false, `Native messaging is not supported on ${AppConstants.platform}`);
   }
 }
+
+
+function getSubprocessCount() {
+  return SubprocessImpl.Process.getWorker().call("getProcesses", [])
+                       .then(result => result.size);
+}
+function waitForSubprocessExit() {
+  return SubprocessImpl.Process.getWorker().call("waitForNoProcesses", []);
+}
--- a/toolkit/components/extensions/test/xpcshell/native_messaging.ini
+++ b/toolkit/components/extensions/test/xpcshell/native_messaging.ini
@@ -2,9 +2,10 @@
 head = head.js head_native_messaging.js
 tail =
 firefox-appdir = browser
 skip-if = toolkit == 'gonk' || appname == "thunderbird" || os == "android"
 subprocess = true
 support-files =
   data/**
 
+[test_ext_native_messaging.js]
 [test_ext_native_messaging_perf.js]
rename from toolkit/components/extensions/test/mochitest/test_chrome_ext_native_messaging.html
rename to toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_native_messaging.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js
@@ -1,269 +1,104 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
-  <script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="chrome_head.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" href="chrome://mochikit/contents/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-/* globals OS */
-
-Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/FileUtils.jsm");
-Cu.import("resource://gre/modules/osfile.jsm");
-let {Subprocess, SubprocessImpl} = Cu.import("resource://gre/modules/Subprocess.jsm");
-Components.utils.import("resource://gre/modules/Task.jsm");
-
-if (AppConstants.platform == "win") {
-  Cu.import("resource://testing-common/MockRegistry.jsm");
-}
-
-var promiseConsoleOutput = Task.async(function* (task) {
-  const DONE = "=== extension test console listener done ===";
-
-  let listener;
-  let messages = [];
-  let awaitListener = new Promise(resolve => {
-    listener = msg => {
-      if (msg == DONE) {
-        resolve();
-      } else if (msg instanceof Ci.nsIConsoleMessage) {
-        messages.push(msg.message);
-      }
-    };
-  });
-
-  Services.console.registerListener(listener);
-  try {
-    let result = yield task();
-
-    Services.console.logStringMessage(DONE);
-    yield awaitListener;
-
-    return {messages, result};
-  } finally {
-    Services.console.unregisterListener(listener);
-  }
-});
+/* globals chrome */
 
 const PREF_MAX_READ = "webextensions.native-messaging.max-input-message-bytes";
 const PREF_MAX_WRITE = "webextensions.native-messaging.max-output-message-bytes";
 
-function getSubprocessCount() {
-  return SubprocessImpl.Process.getWorker().call("getProcesses", [])
-                       .then(result => result.size);
-}
-function waitForSubprocessExit() {
-  return SubprocessImpl.Process.getWorker().call("waitForNoProcesses", []);
-}
-
-let dir = FileUtils.getDir("TmpD", ["NativeMessaging"]);
-dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
-info(`Using local directory ${dir.path}\n`);
-
-function getPath(filename) {
-  return OS.Path.join(dir.path, filename);
-}
+const ECHO_BODY = String.raw`
+  import struct
+  import sys
 
-// Set up a couple of native applications and their manifests for
-// test to use.
-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)
 
-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)
+      sys.stdout.write(struct.pack('@I', msglen))
+      sys.stdout.write(msg)
 `;
 
 const INFO_BODY = String.raw`
-import json
-import os
-import struct
-import sys
+  import json
+  import os
+  import struct
+  import sys
 
-msg = json.dumps({"args": sys.argv, "cwd": os.getcwd()})
-sys.stdout.write(struct.pack('@I', len(msg)))
-sys.stdout.write(msg)
-sys.exit(0)
+  msg = json.dumps({"args": sys.argv, "cwd": os.getcwd()})
+  sys.stdout.write(struct.pack('@I', len(msg)))
+  sys.stdout.write(msg)
+  sys.exit(0)
 `;
 
 const WONTDIE_BODY = String.raw`
-import signal
-import struct
-import sys
-import time
+  import signal
+  import struct
+  import sys
+  import time
 
-signal.signal(signal.SIGTERM, signal.SIG_IGN)
+  signal.signal(signal.SIGTERM, signal.SIG_IGN)
 
-def spin():
-    while True:
-        try:
-            signal.pause()
-        except AttributeError:
-            time.sleep(5)
+  def spin():
+      while True:
+          try:
+              signal.pause()
+          except AttributeError:
+              time.sleep(5)
 
-while True:
-    rawlen = sys.stdin.read(4)
-    if len(rawlen) == 0:
-        spin()
+  while True:
+      rawlen = sys.stdin.read(4)
+      if len(rawlen) == 0:
+          spin()
 
-    msglen = struct.unpack('@I', rawlen)[0]
-    msg = sys.stdin.read(msglen)
+      msglen = struct.unpack('@I', rawlen)[0]
+      msg = sys.stdin.read(msglen)
 
-    sys.stdout.write(struct.pack('@I', msglen))
-    sys.stdout.write(msg)
+      sys.stdout.write(struct.pack('@I', msglen))
+      sys.stdout.write(msg)
 `;
 
 const STDERR_LINES = ["hello stderr", "this should be a separate line"];
 let STDERR_MSG = STDERR_LINES.join("\\n");
 
-// Python apparently line-buffers stderr even with the -u arg on
-// Windows.  Dealing with that is more hassle than its worth but
-// on other platforms, we want to keep testing partial lines.
-if (AppConstants.platform == "win") {
-  STDERR_MSG += "\\n";
-}
-
 const STDERR_BODY = String.raw`
-import sys
-sys.stderr.write("${STDERR_MSG}")
+  import sys
+  sys.stderr.write("${STDERR_MSG}")
 `;
 
 const SCRIPTS = [
   {
     name: "echo",
     description: "a native app that echoes back messages it receives",
-    script: ECHO_BODY,
+    script: ECHO_BODY.replace(/^ {2}/gm, ""),
   },
   {
     name: "info",
     description: "a native app that gives some info about how it was started",
-    script: INFO_BODY,
+    script: INFO_BODY.replace(/^ {2}/gm, ""),
   },
   {
     name: "wontdie",
     description: "a native app that does not exit when stdin closes or on SIGTERM",
-    script: WONTDIE_BODY,
+    script: WONTDIE_BODY.replace(/^ {2}/gm, ""),
   },
   {
     name: "stderr",
     description: "a native app that writes to stderr and then exits",
-    script: STDERR_BODY,
+    script: STDERR_BODY.replace(/^ {2}/gm, ""),
   },
 ];
 
 add_task(function* setup() {
-  const PERMS = {unixMode: 0o755};
-  let pythonPath = yield Subprocess.pathSearch("python2.7").catch(err => {
-    if (err.errorCode != Subprocess.ERROR_BAD_EXECUTABLE) {
-      throw err;
-    }
-    return Subprocess.pathSearch("python");
-  });
-
-  switch (AppConstants.platform) {
-    case "macosx":
-    case "linux":
-      let dirProvider = {
-        getFile(property) {
-          if (property == "XREUserNativeMessaging") {
-            return dir.clone();
-          }
-          return null;
-        },
-      };
-
-      Services.dirsvc.registerProvider(dirProvider);
-      SimpleTest.registerCleanupFunction(() => {
-        Services.dirsvc.unregisterProvider(dirProvider);
-        dir.remove(true);
-      });
-
-      for (let script of SCRIPTS) {
-        let path = getPath(`${script.name}.py`);
-        let body = `#!${pythonPath} -u\n${script.script}`;
-        yield OS.File.writeAtomic(path, body);
-        yield OS.File.setPermissions(path, PERMS);
-
-        let manifest = {
-          name: script.name,
-          description: script.description,
-          path,
-          type: "stdio",
-          allowed_extensions: [ID],
-        };
-
-        yield OS.File.writeAtomic(getPath(`${script.name}.json`), JSON.stringify(manifest));
-      }
-      break;
-
-    case "win":
-      const REGKEY = "Software\\Mozilla\\NativeMessagingHosts";
-      let registry = new MockRegistry();
-      SimpleTest.registerCleanupFunction(() => {
-        registry.shutdown();
-      });
-
-      for (let script of SCRIPTS) {
-        let pyPath = getPath(`${script.name}.py`);
-        yield OS.File.writeAtomic(pyPath, script.script);
-
-        let batPath = getPath(`${script.name}.bat`);
-        let batBody = `@ECHO OFF\n${pythonPath} -u ${pyPath} %*\n`;
-        yield OS.File.writeAtomic(batPath, batBody);
-        yield OS.File.setPermissions(pyPath, PERMS);
-
-        let manifest = {
-          name: script.name,
-          description: script.description,
-          path: batPath,
-          type: "stdio",
-          allowed_extensions: [ID],
-        };
-        let manifestPath = getPath(`${script.name}.json`);
-        yield OS.File.writeAtomic(manifestPath, JSON.stringify(manifest));
-
-        registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
-                          `${REGKEY}\\${script.name}`, "", manifestPath);
-
-        // Create a version of the manifest with a relative path
-        let relativeName = `relative.${script.name}`;
-        manifest.name = relativeName;
-        manifest.path = `${script.name}.bat`;
-
-        manifestPath = getPath(`${relativeName}.json`);
-        yield OS.File.writeAtomic(manifestPath, JSON.stringify(manifest));
-
-        registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
-                          `${REGKEY}\\${relativeName}`, "", manifestPath);
-      }
-      break;
-
-    default:
-      ok(false, `Native messaging is not supported on ${AppConstants.platform}`);
-  }
+  yield setupHosts(SCRIPTS);
 });
 
 // Test the basic operation of native messaging with a simple
 // script that echoes back whatever message is sent to it.
 add_task(function* test_happy_path() {
   function background() {
     let port = browser.runtime.connectNative("echo");
     port.onMessage.addListener(msg => {
@@ -278,17 +113,17 @@ add_task(function* test_happy_path() {
         }
         port.postMessage(payload);
       }
     });
     browser.test.sendMessage("ready");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: `(${background})()`,
+    background,
     manifest: {
       permissions: ["nativeMessaging"],
     },
   }, ID);
 
   yield extension.startup();
   yield extension.awaitMessage("ready");
   const tests = [
@@ -322,21 +157,21 @@ add_task(function* test_happy_path() {
       expected: {data: "i have a tojson method"},
       what: "object with toJSON() method",
     },
   ];
   for (let test of tests) {
     extension.sendMessage("send", test.data);
     let response = yield extension.awaitMessage("message");
     let expected = test.expected || test.data;
-    isDeeply(response, expected, `Echoed a message of type ${test.what}`);
+    deepEqual(response, expected, `Echoed a message of type ${test.what}`);
   }
 
   let procCount = yield getSubprocessCount();
-  is(procCount, 1, "subprocess is still running");
+  equal(procCount, 1, "subprocess is still running");
   let exitPromise = waitForSubprocessExit();
   yield extension.unload();
   yield exitPromise;
 });
 
 if (AppConstants.platform == "win") {
   // "relative.echo" has a relative path in the host manifest.
   add_task(function* test_relative_path() {
@@ -346,27 +181,27 @@ if (AppConstants.platform == "win") {
       port.onMessage.addListener(msg => {
         browser.test.assertEq(MSG, msg, "Got expected message back");
         browser.test.sendMessage("done");
       });
       port.postMessage(MSG);
     }
 
     let extension = ExtensionTestUtils.loadExtension({
-      background: `(${background})()`,
+      background,
       manifest: {
         permissions: ["nativeMessaging"],
       },
     }, ID);
 
     yield extension.startup();
     yield extension.awaitMessage("done");
 
     let procCount = yield getSubprocessCount();
-    is(procCount, 1, "subprocess is still running");
+    equal(procCount, 1, "subprocess is still running");
     let exitPromise = waitForSubprocessExit();
     yield extension.unload();
     yield exitPromise;
   });
 }
 
 // Test sendNativeMessage()
 add_task(function* test_sendNativeMessage() {
@@ -385,17 +220,17 @@ add_task(function* test_sendNativeMessag
       let expected = JSON.stringify(MSG);
       let received = JSON.stringify(reply);
       browser.test.assertEq(expected, received, "Received echoed native message");
       browser.test.sendMessage("finished");
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: `(${background})()`,
+    background,
     manifest: {
       permissions: ["nativeMessaging"],
     },
   }, ID);
 
   yield extension.startup();
   yield extension.awaitMessage("finished");
 
@@ -432,117 +267,117 @@ add_task(function* test_disconnect() {
           });
         }
       }
     });
     browser.test.sendMessage("ready");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: `(${background})()`,
+    background,
     manifest: {
       permissions: ["nativeMessaging"],
     },
   }, ID);
 
   yield extension.startup();
   yield extension.awaitMessage("ready");
 
   extension.sendMessage("send", "test");
   let response = yield extension.awaitMessage("message");
-  is(response, "test", "Echoed a string");
+  equal(response, "test", "Echoed a string");
 
   let procCount = yield getSubprocessCount();
-  is(procCount, 1, "subprocess is running");
+  equal(procCount, 1, "subprocess is running");
 
   extension.sendMessage("disconnect");
   response = yield extension.awaitMessage("disconnect-result");
-  is(response.success, true, "disconnect succeeded");
+  equal(response.success, true, "disconnect succeeded");
 
-  info("waiting for subprocess to exit");
+  do_print("waiting for subprocess to exit");
   yield waitForSubprocessExit();
   procCount = yield getSubprocessCount();
-  is(procCount, 0, "subprocess is no longer running");
+  equal(procCount, 0, "subprocess is no longer running");
 
   extension.sendMessage("disconnect");
   response = yield extension.awaitMessage("disconnect-result");
-  is(response.success, false, "second call to disconnect failed");
+  equal(response.success, false, "second call to disconnect failed");
   ok(/already disconnected/.test(response.errmsg), "disconnect error message is reasonable");
 
   yield extension.unload();
 });
 
 // Test the limit on message size for writing
 add_task(function* test_write_limit() {
   Services.prefs.setIntPref(PREF_MAX_WRITE, 10);
   function clearPref() {
     Services.prefs.clearUserPref(PREF_MAX_WRITE);
   }
-  SimpleTest.registerCleanupFunction(clearPref);
+  do_register_cleanup(clearPref);
 
   function background() {
     const PAYLOAD = "0123456789A";
     let port = browser.runtime.connectNative("echo");
     try {
       port.postMessage(PAYLOAD);
       browser.test.sendMessage("result", null);
     } catch (ex) {
       browser.test.sendMessage("result", ex.message);
     }
   }
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: `(${background})()`,
+    background,
     manifest: {
       permissions: ["nativeMessaging"],
     },
   }, ID);
 
   yield extension.startup();
 
   let errmsg = yield extension.awaitMessage("result");
-  isnot(errmsg, null, "native postMessage() failed for overly large message");
+  notEqual(errmsg, null, "native postMessage() failed for overly large message");
 
   yield extension.unload();
   yield waitForSubprocessExit();
 
   clearPref();
 });
 
 // Test the limit on message size for reading
 add_task(function* test_read_limit() {
   Services.prefs.setIntPref(PREF_MAX_READ, 10);
   function clearPref() {
     Services.prefs.clearUserPref(PREF_MAX_READ);
   }
-  SimpleTest.registerCleanupFunction(clearPref);
+  do_register_cleanup(clearPref);
 
   function background() {
     const PAYLOAD = "0123456789A";
     let port = browser.runtime.connectNative("echo");
     port.onDisconnect.addListener(() => {
       browser.test.sendMessage("result", "disconnected");
     });
     port.onMessage.addListener(msg => {
       browser.test.sendMessage("result", "message");
     });
     port.postMessage(PAYLOAD);
   }
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: `(${background})()`,
+    background,
     manifest: {
       permissions: ["nativeMessaging"],
     },
   }, ID);
 
   yield extension.startup();
 
   let result = yield extension.awaitMessage("result");
-  is(result, "disconnected", "native port disconnected on receiving large message");
+  equal(result, "disconnected", "native port disconnected on receiving large message");
 
   yield extension.unload();
   yield waitForSubprocessExit();
 
   clearPref();
 });
 
 // Test that an extension without the nativeMessaging permission cannot
@@ -552,17 +387,17 @@ add_task(function* test_ext_permission()
     browser.test.assertFalse("connectNative" in chrome.runtime, "chrome.runtime.connectNative does not exist without nativeMessaging permission");
     browser.test.assertFalse("connectNative" in browser.runtime, "browser.runtime.connectNative does not exist without nativeMessaging permission");
     browser.test.assertFalse("sendNativeMessage" in chrome.runtime, "chrome.runtime.sendNativeMessage does not exist without nativeMessaging permission");
     browser.test.assertFalse("sendNativeMessage" in browser.runtime, "browser.runtime.sendNativeMessage does not exist without nativeMessaging permission");
     browser.test.sendMessage("finished");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: `(${background})()`,
+    background,
     manifest: {},
   });
 
   yield extension.startup();
   yield extension.awaitMessage("finished");
   yield extension.unload();
 });
 
@@ -576,56 +411,57 @@ add_task(function* test_app_permission()
     });
     port.onMessage.addListener(msg => {
       browser.test.sendMessage("result", "message");
     });
     port.postMessage({test: "test"});
   }
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: `(${background})()`,
+    background,
     manifest: {
       permissions: ["nativeMessaging"],
     },
   }, "somethingelse@tests.mozilla.org");
 
   yield extension.startup();
 
   let result = yield extension.awaitMessage("result");
-  is(result, "disconnected", "connectNative() failed without native app permission");
+  equal(result, "disconnected", "connectNative() failed without native app permission");
 
   yield extension.unload();
 
   let procCount = yield getSubprocessCount();
-  is(procCount, 0, "No child process was started");
+  equal(procCount, 0, "No child process was started");
 });
 
 // Test that the command-line arguments and working directory for the
 // native application are as expected.
 add_task(function* test_child_process() {
   function background() {
     let port = browser.runtime.connectNative("info");
     port.onMessage.addListener(msg => {
       browser.test.sendMessage("result", msg);
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: `(${background})()`,
+    background,
     manifest: {
       permissions: ["nativeMessaging"],
     },
   }, ID);
 
   yield extension.startup();
 
   let msg = yield extension.awaitMessage("result");
-  is(msg.args.length, 2, "Received one command line argument");
-  is(msg.args[1], getPath("info.json"), "Command line argument is the path to the native host manifest");
-  is(msg.cwd, dir.path, "Working directory is the directory containing the native appliation");
+  equal(msg.args.length, 2, "Received one command line argument");
+  equal(msg.args[1], getPath("info.json"), "Command line argument is the path to the native host manifest");
+  equal(msg.cwd.replace(/^\/private\//, ""), tmpDir.path,
+        "Working directory is the directory containing the native appliation");
 
   let exitPromise = waitForSubprocessExit();
   yield extension.unload();
   yield exitPromise;
 });
 
 // Test that an unresponsive native application still gets killed eventually
 add_task(function* test_unresponsive_native_app() {
@@ -640,61 +476,56 @@ add_task(function* test_unresponsive_nat
     port.onMessage.addListener(msg => {
       browser.test.assertEq(msg, MSG, "Received echoed message");
       browser.test.sendMessage("ready");
     });
     port.postMessage(MSG);
   }
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: `(${background})()`,
+    background,
     manifest: {
       permissions: ["nativeMessaging"],
     },
   }, ID);
 
   yield extension.startup();
   yield extension.awaitMessage("ready");
 
   let procCount = yield getSubprocessCount();
-  is(procCount, 1, "subprocess is running");
+  equal(procCount, 1, "subprocess is running");
 
   let exitPromise = waitForSubprocessExit();
   yield extension.unload();
   yield exitPromise;
 
   procCount = yield getSubprocessCount();
-  is(procCount, 0, "subprocess was succesfully killed");
+  equal(procCount, 0, "subprocess was succesfully killed");
 });
 
 add_task(function* test_stderr() {
   function background() {
     let port = browser.runtime.connectNative("stderr");
     port.onDisconnect.addListener(() => {
       browser.test.sendMessage("finished");
     });
   }
 
   let {messages} = yield promiseConsoleOutput(function* () {
     let extension = ExtensionTestUtils.loadExtension({
-      background: `(${background})()`,
+      background,
       manifest: {
         permissions: ["nativeMessaging"],
       },
     }, ID);
 
     yield extension.startup();
     yield extension.awaitMessage("finished");
     yield extension.unload();
+
+    yield waitForSubprocessExit();
   });
 
-  let lines = STDERR_LINES.map(line => messages.findIndex(msg => msg.includes(line)));
-  isnot(lines[0], -1, "Saw first line of stderr output on the console");
-  isnot(lines[1], -1, "Saw second line of stderr output on the console");
-  isnot(lines[0], lines[1], "Stderr output lines are separated in the console");
-
-  yield waitForSubprocessExit();
+  let lines = STDERR_LINES.map(line => messages.findIndex(msg => msg.message.includes(line)));
+  notEqual(lines[0], -1, "Saw first line of stderr output on the console");
+  notEqual(lines[1], -1, "Saw second line of stderr output on the console");
+  notEqual(lines[0], lines[1], "Stderr output lines are separated in the console");
 });
-
-</script>
-
-</body>
-</html>