summaryrefslogtreecommitdiffstats
path: root/devtools/shared/transport/tests/unit
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/transport/tests/unit')
-rw-r--r--devtools/shared/transport/tests/unit/.eslintrc.js6
-rw-r--r--devtools/shared/transport/tests/unit/head_dbg.js278
-rw-r--r--devtools/shared/transport/tests/unit/test_bulk_error.js92
-rw-r--r--devtools/shared/transport/tests/unit/test_client_server_bulk.js271
-rw-r--r--devtools/shared/transport/tests/unit/test_dbgsocket.js124
-rw-r--r--devtools/shared/transport/tests/unit/test_dbgsocket_connection_drop.js81
-rw-r--r--devtools/shared/transport/tests/unit/test_delimited_read.js26
-rw-r--r--devtools/shared/transport/tests/unit/test_no_bulk.js38
-rw-r--r--devtools/shared/transport/tests/unit/test_packet.js21
-rw-r--r--devtools/shared/transport/tests/unit/test_queue.js177
-rw-r--r--devtools/shared/transport/tests/unit/test_transport_bulk.js148
-rw-r--r--devtools/shared/transport/tests/unit/test_transport_events.js75
-rw-r--r--devtools/shared/transport/tests/unit/testactors-no-bulk.js27
-rw-r--r--devtools/shared/transport/tests/unit/testactors.js131
-rw-r--r--devtools/shared/transport/tests/unit/xpcshell.ini21
15 files changed, 1516 insertions, 0 deletions
diff --git a/devtools/shared/transport/tests/unit/.eslintrc.js b/devtools/shared/transport/tests/unit/.eslintrc.js
new file mode 100644
index 000000000..59adf410a
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the common devtools xpcshell eslintrc config.
+ "extends": "../../../../.eslintrc.xpcshell.js"
+};
diff --git a/devtools/shared/transport/tests/unit/head_dbg.js b/devtools/shared/transport/tests/unit/head_dbg.js
new file mode 100644
index 000000000..1f96ad440
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/head_dbg.js
@@ -0,0 +1,278 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+var CC = Components.Constructor;
+
+const { require } =
+ Cu.import("resource://devtools/shared/Loader.jsm", {});
+const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
+const promise = require("promise");
+const defer = require("devtools/shared/defer");
+const { Task } = require("devtools/shared/task");
+
+const Services = require("Services");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+
+// We do not want to log packets by default, because in some tests,
+// we can be sending large amounts of data. The test harness has
+// trouble dealing with logging all the data, and we end up with
+// intermittent time outs (e.g. bug 775924).
+// Services.prefs.setBoolPref("devtools.debugger.log", true);
+// Services.prefs.setBoolPref("devtools.debugger.log.verbose", true);
+// Enable remote debugging for the relevant tests.
+Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
+
+const { DebuggerServer } = require("devtools/server/main");
+const { DebuggerClient } = require("devtools/shared/client/main");
+
+function testExceptionHook(ex) {
+ try {
+ do_report_unexpected_exception(ex);
+ } catch (ex) {
+ return {throw: ex};
+ }
+ return undefined;
+}
+
+// Convert an nsIScriptError 'aFlags' value into an appropriate string.
+function scriptErrorFlagsToKind(aFlags) {
+ var kind;
+ if (aFlags & Ci.nsIScriptError.warningFlag)
+ kind = "warning";
+ if (aFlags & Ci.nsIScriptError.exceptionFlag)
+ kind = "exception";
+ else
+ kind = "error";
+
+ if (aFlags & Ci.nsIScriptError.strictFlag)
+ kind = "strict " + kind;
+
+ return kind;
+}
+
+// Register a console listener, so console messages don't just disappear
+// into the ether.
+var errorCount = 0;
+var listener = {
+ observe: function (aMessage) {
+ errorCount++;
+ try {
+ // If we've been given an nsIScriptError, then we can print out
+ // something nicely formatted, for tools like Emacs to pick up.
+ var scriptError = aMessage.QueryInterface(Ci.nsIScriptError);
+ dump(aMessage.sourceName + ":" + aMessage.lineNumber + ": " +
+ scriptErrorFlagsToKind(aMessage.flags) + ": " +
+ aMessage.errorMessage + "\n");
+ var string = aMessage.errorMessage;
+ } catch (x) {
+ // Be a little paranoid with message, as the whole goal here is to lose
+ // no information.
+ try {
+ var string = "" + aMessage.message;
+ } catch (x) {
+ var string = "<error converting error message to string>";
+ }
+ }
+
+ // Make sure we exit all nested event loops so that the test can finish.
+ while (DebuggerServer.xpcInspector.eventLoopNestLevel > 0) {
+ DebuggerServer.xpcInspector.exitNestedEventLoop();
+ }
+
+ // Throw in most cases, but ignore the "strict" messages
+ if (!(aMessage.flags & Ci.nsIScriptError.strictFlag)) {
+ do_throw("head_dbg.js got console message: " + string + "\n");
+ }
+ }
+};
+
+var consoleService = Cc["@mozilla.org/consoleservice;1"]
+ .getService(Ci.nsIConsoleService);
+consoleService.registerListener(listener);
+
+function check_except(func) {
+ try {
+ func();
+ } catch (e) {
+ do_check_true(true);
+ return;
+ }
+ dump("Should have thrown an exception: " + func.toString());
+ do_check_true(false);
+}
+
+function testGlobal(aName) {
+ let systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
+ .createInstance(Ci.nsIPrincipal);
+
+ let sandbox = Cu.Sandbox(systemPrincipal);
+ sandbox.__name = aName;
+ return sandbox;
+}
+
+function addTestGlobal(aName)
+{
+ let global = testGlobal(aName);
+ DebuggerServer.addTestGlobal(global);
+ return global;
+}
+
+// List the DebuggerClient |aClient|'s tabs, look for one whose title is
+// |aTitle|, and apply |aCallback| to the packet's entry for that tab.
+function getTestTab(aClient, aTitle, aCallback) {
+ aClient.listTabs(function (aResponse) {
+ for (let tab of aResponse.tabs) {
+ if (tab.title === aTitle) {
+ aCallback(tab);
+ return;
+ }
+ }
+ aCallback(null);
+ });
+}
+
+// Attach to |aClient|'s tab whose title is |aTitle|; pass |aCallback| the
+// response packet and a TabClient instance referring to that tab.
+function attachTestTab(aClient, aTitle, aCallback) {
+ getTestTab(aClient, aTitle, function (aTab) {
+ aClient.attachTab(aTab.actor, aCallback);
+ });
+}
+
+// Attach to |aClient|'s tab whose title is |aTitle|, and then attach to
+// that tab's thread. Pass |aCallback| the thread attach response packet, a
+// TabClient referring to the tab, and a ThreadClient referring to the
+// thread.
+function attachTestThread(aClient, aTitle, aCallback) {
+ attachTestTab(aClient, aTitle, function (aResponse, aTabClient) {
+ function onAttach(aResponse, aThreadClient) {
+ aCallback(aResponse, aTabClient, aThreadClient);
+ }
+ aTabClient.attachThread({ useSourceMaps: true }, onAttach);
+ });
+}
+
+// Attach to |aClient|'s tab whose title is |aTitle|, attach to the tab's
+// thread, and then resume it. Pass |aCallback| the thread's response to
+// the 'resume' packet, a TabClient for the tab, and a ThreadClient for the
+// thread.
+function attachTestTabAndResume(aClient, aTitle, aCallback) {
+ attachTestThread(aClient, aTitle, function (aResponse, aTabClient, aThreadClient) {
+ aThreadClient.resume(function (aResponse) {
+ aCallback(aResponse, aTabClient, aThreadClient);
+ });
+ });
+}
+
+/**
+ * Initialize the testing debugger server.
+ */
+function initTestDebuggerServer() {
+ DebuggerServer.registerModule("devtools/server/actors/script", {
+ prefix: "script",
+ constructor: "ScriptActor",
+ type: { global: true, tab: true }
+ });
+ DebuggerServer.registerModule("xpcshell-test/testactors");
+ // Allow incoming connections.
+ DebuggerServer.init();
+}
+
+function finishClient(aClient) {
+ aClient.close().then(function () {
+ do_test_finished();
+ });
+}
+
+/**
+ * Takes a relative file path and returns the absolute file url for it.
+ */
+function getFileUrl(aName, aAllowMissing = false) {
+ let file = do_get_file(aName, aAllowMissing);
+ return Services.io.newFileURI(file).spec;
+}
+
+/**
+ * Returns the full path of the file with the specified name in a
+ * platform-independent and URL-like form.
+ */
+function getFilePath(aName, aAllowMissing = false) {
+ let file = do_get_file(aName, aAllowMissing);
+ let path = Services.io.newFileURI(file).spec;
+ let filePrePath = "file://";
+ if ("nsILocalFileWin" in Ci &&
+ file instanceof Ci.nsILocalFileWin) {
+ filePrePath += "/";
+ }
+ return path.slice(filePrePath.length);
+}
+
+/**
+ * Wrapper around do_get_file to prefix files with the name of current test to
+ * avoid collisions when running in parallel.
+ */
+function getTestTempFile(fileName, allowMissing) {
+ let thisTest = _TEST_FILE.toString().replace(/\\/g, "/");
+ thisTest = thisTest.substring(thisTest.lastIndexOf("/") + 1);
+ thisTest = thisTest.replace(/\..*$/, "");
+ return do_get_file(fileName + "-" + thisTest, allowMissing);
+}
+
+function writeTestTempFile(aFileName, aContent) {
+ let file = getTestTempFile(aFileName, true);
+ let stream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ stream.init(file, -1, -1, 0);
+ try {
+ do {
+ let numWritten = stream.write(aContent, aContent.length);
+ aContent = aContent.slice(numWritten);
+ } while (aContent.length > 0);
+ } finally {
+ stream.close();
+ }
+}
+
+/** * Transport Factories ***/
+
+var socket_transport = Task.async(function* () {
+ if (!DebuggerServer.listeningSockets) {
+ let AuthenticatorType = DebuggerServer.Authenticators.get("PROMPT");
+ let authenticator = new AuthenticatorType.Server();
+ authenticator.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+ let listener = DebuggerServer.createListener();
+ listener.portOrPath = -1;
+ listener.authenticator = authenticator;
+ yield listener.open();
+ }
+ let port = DebuggerServer._listeners[0].port;
+ do_print("Debugger server port is " + port);
+ return DebuggerClient.socketConnect({ host: "127.0.0.1", port });
+});
+
+function local_transport() {
+ return promise.resolve(DebuggerServer.connectPipe());
+}
+
+/** * Sample Data ***/
+
+var gReallyLong;
+function really_long() {
+ if (gReallyLong) {
+ return gReallyLong;
+ }
+ let ret = "0123456789";
+ for (let i = 0; i < 18; i++) {
+ ret += ret;
+ }
+ gReallyLong = ret;
+ return ret;
+}
diff --git a/devtools/shared/transport/tests/unit/test_bulk_error.js b/devtools/shared/transport/tests/unit/test_bulk_error.js
new file mode 100644
index 000000000..954499291
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_bulk_error.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function run_test() {
+ initTestDebuggerServer();
+ add_test_bulk_actor();
+
+ add_task(function* () {
+ yield test_string_error(socket_transport, json_reply);
+ yield test_string_error(local_transport, json_reply);
+ DebuggerServer.destroy();
+ });
+
+ run_next_test();
+}
+
+/** * Sample Bulk Actor ***/
+
+function TestBulkActor() {}
+
+TestBulkActor.prototype = {
+
+ actorPrefix: "testBulk",
+
+ jsonReply: function ({length, reader, reply, done}) {
+ do_check_eq(length, really_long().length);
+
+ return {
+ allDone: true
+ };
+ }
+
+};
+
+TestBulkActor.prototype.requestTypes = {
+ "jsonReply": TestBulkActor.prototype.jsonReply
+};
+
+function add_test_bulk_actor() {
+ DebuggerServer.addGlobalActor(TestBulkActor);
+}
+
+/** * Tests ***/
+
+var test_string_error = Task.async(function* (transportFactory, onReady) {
+ let transport = yield transportFactory();
+
+ let client = new DebuggerClient(transport);
+ return client.connect().then(([app, traits]) => {
+ do_check_eq(traits.bulk, true);
+ return client.listTabs();
+ }).then(response => {
+ return onReady(client, response);
+ }).then(() => {
+ client.close();
+ transport.close();
+ });
+});
+
+/** * Reply Types ***/
+
+function json_reply(client, response) {
+ let reallyLong = really_long();
+
+ let request = client.startBulkRequest({
+ actor: response.testBulk,
+ type: "jsonReply",
+ length: reallyLong.length
+ });
+
+ // Send bulk data to server
+ let copyDeferred = defer();
+ request.on("bulk-send-ready", ({writer, done}) => {
+ let input = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ input.setData(reallyLong, reallyLong.length);
+ try {
+ writer.copyFrom(input, () => {
+ input.close();
+ done();
+ });
+ do_throw(new Error("Copying should fail, the stream is not async."));
+ } catch (e) {
+ do_check_true(true);
+ copyDeferred.resolve();
+ }
+ });
+
+ return copyDeferred.promise;
+}
diff --git a/devtools/shared/transport/tests/unit/test_client_server_bulk.js b/devtools/shared/transport/tests/unit/test_client_server_bulk.js
new file mode 100644
index 000000000..e4d17d216
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_client_server_bulk.js
@@ -0,0 +1,271 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {});
+var Pipe = CC("@mozilla.org/pipe;1", "nsIPipe", "init");
+
+function run_test() {
+ initTestDebuggerServer();
+ add_test_bulk_actor();
+
+ add_task(function* () {
+ yield test_bulk_request_cs(socket_transport, "jsonReply", "json");
+ yield test_bulk_request_cs(local_transport, "jsonReply", "json");
+ yield test_bulk_request_cs(socket_transport, "bulkEcho", "bulk");
+ yield test_bulk_request_cs(local_transport, "bulkEcho", "bulk");
+ yield test_json_request_cs(socket_transport, "bulkReply", "bulk");
+ yield test_json_request_cs(local_transport, "bulkReply", "bulk");
+ DebuggerServer.destroy();
+ });
+
+ run_next_test();
+}
+
+/** * Sample Bulk Actor ***/
+
+function TestBulkActor(conn) {
+ this.conn = conn;
+}
+
+TestBulkActor.prototype = {
+
+ actorPrefix: "testBulk",
+
+ bulkEcho: function ({actor, type, length, copyTo}) {
+ do_check_eq(length, really_long().length);
+ this.conn.startBulkSend({
+ actor: actor,
+ type: type,
+ length: length
+ }).then(({copyFrom}) => {
+ // We'll just echo back the same thing
+ let pipe = new Pipe(true, true, 0, 0, null);
+ copyTo(pipe.outputStream).then(() => {
+ pipe.outputStream.close();
+ });
+ copyFrom(pipe.inputStream).then(() => {
+ pipe.inputStream.close();
+ });
+ });
+ },
+
+ bulkReply: function ({to, type}) {
+ this.conn.startBulkSend({
+ actor: to,
+ type: type,
+ length: really_long().length
+ }).then(({copyFrom}) => {
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(getTestTempFile("bulk-input")),
+ loadUsingSystemPrincipal: true
+ }, input => {
+ copyFrom(input).then(() => {
+ input.close();
+ });
+ });
+ });
+ },
+
+ jsonReply: function ({length, copyTo}) {
+ do_check_eq(length, really_long().length);
+
+ let outputFile = getTestTempFile("bulk-output", true);
+ outputFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
+
+ let output = FileUtils.openSafeFileOutputStream(outputFile);
+
+ return copyTo(output).then(() => {
+ FileUtils.closeSafeFileOutputStream(output);
+ return verify_files();
+ }).then(() => {
+ return { allDone: true };
+ }, do_throw);
+ }
+
+};
+
+TestBulkActor.prototype.requestTypes = {
+ "bulkEcho": TestBulkActor.prototype.bulkEcho,
+ "bulkReply": TestBulkActor.prototype.bulkReply,
+ "jsonReply": TestBulkActor.prototype.jsonReply
+};
+
+function add_test_bulk_actor() {
+ DebuggerServer.addGlobalActor(TestBulkActor);
+}
+
+/** * Reply Handlers ***/
+
+var replyHandlers = {
+
+ json: function (request) {
+ // Receive JSON reply from server
+ let replyDeferred = defer();
+ request.on("json-reply", (reply) => {
+ do_check_true(reply.allDone);
+ replyDeferred.resolve();
+ });
+ return replyDeferred.promise;
+ },
+
+ bulk: function (request) {
+ // Receive bulk data reply from server
+ let replyDeferred = defer();
+ request.on("bulk-reply", ({length, copyTo}) => {
+ do_check_eq(length, really_long().length);
+
+ let outputFile = getTestTempFile("bulk-output", true);
+ outputFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
+
+ let output = FileUtils.openSafeFileOutputStream(outputFile);
+
+ copyTo(output).then(() => {
+ FileUtils.closeSafeFileOutputStream(output);
+ replyDeferred.resolve(verify_files());
+ });
+ });
+ return replyDeferred.promise;
+ }
+
+};
+
+/** * Tests ***/
+
+var test_bulk_request_cs = Task.async(function* (transportFactory, actorType, replyType) {
+ // Ensure test files are not present from a failed run
+ cleanup_files();
+ writeTestTempFile("bulk-input", really_long());
+
+ let clientDeferred = defer();
+ let serverDeferred = defer();
+ let bulkCopyDeferred = defer();
+
+ let transport = yield transportFactory();
+
+ let client = new DebuggerClient(transport);
+ client.connect().then(([app, traits]) => {
+ do_check_eq(traits.bulk, true);
+ client.listTabs(clientDeferred.resolve);
+ });
+
+ clientDeferred.promise.then(response => {
+ let request = client.startBulkRequest({
+ actor: response.testBulk,
+ type: actorType,
+ length: really_long().length
+ });
+
+ // Send bulk data to server
+ request.on("bulk-send-ready", ({copyFrom}) => {
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(getTestTempFile("bulk-input")),
+ loadUsingSystemPrincipal: true
+ }, input => {
+ copyFrom(input).then(() => {
+ input.close();
+ bulkCopyDeferred.resolve();
+ });
+ });
+ });
+
+ // Set up reply handling for this type
+ replyHandlers[replyType](request).then(() => {
+ client.close();
+ transport.close();
+ });
+ }).then(null, do_throw);
+
+ DebuggerServer.on("connectionchange", (event, type) => {
+ if (type === "closed") {
+ serverDeferred.resolve();
+ }
+ });
+
+ return promise.all([
+ clientDeferred.promise,
+ bulkCopyDeferred.promise,
+ serverDeferred.promise
+ ]);
+});
+
+var test_json_request_cs = Task.async(function* (transportFactory, actorType, replyType) {
+ // Ensure test files are not present from a failed run
+ cleanup_files();
+ writeTestTempFile("bulk-input", really_long());
+
+ let clientDeferred = defer();
+ let serverDeferred = defer();
+
+ let transport = yield transportFactory();
+
+ let client = new DebuggerClient(transport);
+ client.connect((app, traits) => {
+ do_check_eq(traits.bulk, true);
+ client.listTabs(clientDeferred.resolve);
+ });
+
+ clientDeferred.promise.then(response => {
+ let request = client.request({
+ to: response.testBulk,
+ type: actorType
+ });
+
+ // Set up reply handling for this type
+ replyHandlers[replyType](request).then(() => {
+ client.close();
+ transport.close();
+ });
+ }).then(null, do_throw);
+
+ DebuggerServer.on("connectionchange", (event, type) => {
+ if (type === "closed") {
+ serverDeferred.resolve();
+ }
+ });
+
+ return promise.all([
+ clientDeferred.promise,
+ serverDeferred.promise
+ ]);
+});
+
+/** * Test Utils ***/
+
+function verify_files() {
+ let reallyLong = really_long();
+
+ let inputFile = getTestTempFile("bulk-input");
+ let outputFile = getTestTempFile("bulk-output");
+
+ do_check_eq(inputFile.fileSize, reallyLong.length);
+ do_check_eq(outputFile.fileSize, reallyLong.length);
+
+ // Ensure output file contents actually match
+ let compareDeferred = defer();
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(getTestTempFile("bulk-output")),
+ loadUsingSystemPrincipal: true
+ }, input => {
+ let outputData = NetUtil.readInputStreamToString(input, reallyLong.length);
+ // Avoid do_check_eq here so we don't log the contents
+ do_check_true(outputData === reallyLong);
+ input.close();
+ compareDeferred.resolve();
+ });
+
+ return compareDeferred.promise.then(cleanup_files);
+}
+
+function cleanup_files() {
+ let inputFile = getTestTempFile("bulk-input", true);
+ if (inputFile.exists()) {
+ inputFile.remove(false);
+ }
+
+ let outputFile = getTestTempFile("bulk-output", true);
+ if (outputFile.exists()) {
+ outputFile.remove(false);
+ }
+}
diff --git a/devtools/shared/transport/tests/unit/test_dbgsocket.js b/devtools/shared/transport/tests/unit/test_dbgsocket.js
new file mode 100644
index 000000000..79111f877
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_dbgsocket.js
@@ -0,0 +1,124 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gPort;
+var gExtraListener;
+
+function run_test()
+{
+ do_print("Starting test at " + new Date().toTimeString());
+ initTestDebuggerServer();
+
+ add_task(test_socket_conn);
+ add_task(test_socket_shutdown);
+ add_test(test_pipe_conn);
+
+ run_next_test();
+}
+
+function* test_socket_conn()
+{
+ do_check_eq(DebuggerServer.listeningSockets, 0);
+ let AuthenticatorType = DebuggerServer.Authenticators.get("PROMPT");
+ let authenticator = new AuthenticatorType.Server();
+ authenticator.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+ let listener = DebuggerServer.createListener();
+ do_check_true(listener);
+ listener.portOrPath = -1;
+ listener.authenticator = authenticator;
+ listener.open();
+ do_check_eq(DebuggerServer.listeningSockets, 1);
+ gPort = DebuggerServer._listeners[0].port;
+ do_print("Debugger server port is " + gPort);
+ // Open a second, separate listener
+ gExtraListener = DebuggerServer.createListener();
+ gExtraListener.portOrPath = -1;
+ gExtraListener.authenticator = authenticator;
+ gExtraListener.open();
+ do_check_eq(DebuggerServer.listeningSockets, 2);
+
+ do_print("Starting long and unicode tests at " + new Date().toTimeString());
+ let unicodeString = "(╯°□°)╯︵ ┻━┻";
+ let transport = yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: gPort
+ });
+
+ // Assert that connection settings are available on transport object
+ let settings = transport.connectionSettings;
+ do_check_eq(settings.host, "127.0.0.1");
+ do_check_eq(settings.port, gPort);
+
+ let closedDeferred = defer();
+ transport.hooks = {
+ onPacket: function (aPacket) {
+ this.onPacket = function (aPacket) {
+ do_check_eq(aPacket.unicode, unicodeString);
+ transport.close();
+ };
+ // Verify that things work correctly when bigger than the output
+ // transport buffers and when transporting unicode...
+ transport.send({to: "root",
+ type: "echo",
+ reallylong: really_long(),
+ unicode: unicodeString});
+ do_check_eq(aPacket.from, "root");
+ },
+ onClosed: function (aStatus) {
+ closedDeferred.resolve();
+ },
+ };
+ transport.ready();
+ return closedDeferred.promise;
+}
+
+function* test_socket_shutdown()
+{
+ do_check_eq(DebuggerServer.listeningSockets, 2);
+ gExtraListener.close();
+ do_check_eq(DebuggerServer.listeningSockets, 1);
+ do_check_true(DebuggerServer.closeAllListeners());
+ do_check_eq(DebuggerServer.listeningSockets, 0);
+ // Make sure closing the listener twice does nothing.
+ do_check_false(DebuggerServer.closeAllListeners());
+ do_check_eq(DebuggerServer.listeningSockets, 0);
+
+ do_print("Connecting to a server socket at " + new Date().toTimeString());
+ try {
+ let transport = yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: gPort
+ });
+ } catch (e) {
+ if (e.result == Cr.NS_ERROR_CONNECTION_REFUSED ||
+ e.result == Cr.NS_ERROR_NET_TIMEOUT) {
+ // The connection should be refused here, but on slow or overloaded
+ // machines it may just time out.
+ do_check_true(true);
+ return;
+ } else {
+ throw e;
+ }
+ }
+
+ // Shouldn't reach this, should never connect.
+ do_check_true(false);
+}
+
+function test_pipe_conn()
+{
+ let transport = DebuggerServer.connectPipe();
+ transport.hooks = {
+ onPacket: function (aPacket) {
+ do_check_eq(aPacket.from, "root");
+ transport.close();
+ },
+ onClosed: function (aStatus) {
+ run_next_test();
+ }
+ };
+
+ transport.ready();
+}
diff --git a/devtools/shared/transport/tests/unit/test_dbgsocket_connection_drop.js b/devtools/shared/transport/tests/unit/test_dbgsocket_connection_drop.js
new file mode 100644
index 000000000..9221939b1
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_dbgsocket_connection_drop.js
@@ -0,0 +1,81 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Bug 755412 - checks if the server drops the connection on an improperly
+ * framed packet, i.e. when the length header is invalid.
+ */
+
+const { RawPacket } = require("devtools/shared/transport/packets");
+
+function run_test() {
+ do_print("Starting test at " + new Date().toTimeString());
+ initTestDebuggerServer();
+
+ add_task(test_socket_conn_drops_after_invalid_header);
+ add_task(test_socket_conn_drops_after_invalid_header_2);
+ add_task(test_socket_conn_drops_after_too_large_length);
+ add_task(test_socket_conn_drops_after_too_long_header);
+ run_next_test();
+}
+
+function test_socket_conn_drops_after_invalid_header() {
+ return test_helper('fluff30:27:{"to":"root","type":"echo"}');
+}
+
+function test_socket_conn_drops_after_invalid_header_2() {
+ return test_helper('27asd:{"to":"root","type":"echo"}');
+}
+
+function test_socket_conn_drops_after_too_large_length() {
+ // Packet length is limited (semi-arbitrarily) to 1 TiB (2^40)
+ return test_helper("4305724038957487634549823475894325:");
+}
+
+function test_socket_conn_drops_after_too_long_header() {
+ // The packet header is currently limited to no more than 200 bytes
+ let rawPacket = "4305724038957487634549823475894325";
+ for (let i = 0; i < 8; i++) {
+ rawPacket += rawPacket;
+ }
+ return test_helper(rawPacket + ":");
+}
+
+var test_helper = Task.async(function* (payload) {
+ let AuthenticatorType = DebuggerServer.Authenticators.get("PROMPT");
+ let authenticator = new AuthenticatorType.Server();
+ authenticator.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+
+ let listener = DebuggerServer.createListener();
+ listener.portOrPath = -1;
+ listener.authenticator = authenticator;
+ listener.open();
+
+ let transport = yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port
+ });
+ let closedDeferred = defer();
+ transport.hooks = {
+ onPacket: function (aPacket) {
+ this.onPacket = function (aPacket) {
+ do_throw(new Error("This connection should be dropped."));
+ transport.close();
+ };
+
+ // Inject the payload directly into the stream.
+ transport._outgoing.push(new RawPacket(transport, payload));
+ transport._flushOutgoing();
+ },
+ onClosed: function (aStatus) {
+ do_check_true(true);
+ closedDeferred.resolve();
+ },
+ };
+ transport.ready();
+ return closedDeferred.promise;
+});
diff --git a/devtools/shared/transport/tests/unit/test_delimited_read.js b/devtools/shared/transport/tests/unit/test_delimited_read.js
new file mode 100644
index 000000000..a5a7baf7b
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_delimited_read.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const StreamUtils = require("devtools/shared/transport/stream-utils");
+
+const StringInputStream = CC("@mozilla.org/io/string-input-stream;1",
+ "nsIStringInputStream", "setData");
+
+function run_test() {
+ add_task(function* () {
+ yield test_delimited_read("0123:", "0123:");
+ yield test_delimited_read("0123:4567:", "0123:");
+ yield test_delimited_read("012345678901:", "0123456789");
+ yield test_delimited_read("0123/0123", "0123/0123");
+ });
+
+ run_next_test();
+}
+
+/** * Tests ***/
+
+function test_delimited_read(input, expected) {
+ input = new StringInputStream(input, input.length);
+ let result = StreamUtils.delimitedRead(input, ":", 10);
+ do_check_eq(result, expected);
+}
diff --git a/devtools/shared/transport/tests/unit/test_no_bulk.js b/devtools/shared/transport/tests/unit/test_no_bulk.js
new file mode 100644
index 000000000..f621a2a52
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_no_bulk.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function run_test() {
+ DebuggerServer.registerModule("xpcshell-test/testactors-no-bulk");
+ // Allow incoming connections.
+ DebuggerServer.init();
+
+ add_task(function* () {
+ yield test_bulk_send_error(socket_transport);
+ yield test_bulk_send_error(local_transport);
+ DebuggerServer.destroy();
+ });
+
+ run_next_test();
+}
+
+/** * Tests ***/
+
+var test_bulk_send_error = Task.async(function* (transportFactory) {
+ let deferred = defer();
+ let transport = yield transportFactory();
+
+ let client = new DebuggerClient(transport);
+ return client.connect().then(([app, traits]) => {
+ do_check_false(traits.bulk);
+
+ try {
+ client.startBulkRequest();
+ do_throw(new Error("Can't use bulk since server doesn't support it"));
+ } catch (e) {
+ do_check_true(true);
+ }
+
+ });
+});
diff --git a/devtools/shared/transport/tests/unit/test_packet.js b/devtools/shared/transport/tests/unit/test_packet.js
new file mode 100644
index 000000000..7e1896555
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_packet.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { JSONPacket, BulkPacket } =
+ require("devtools/shared/transport/packets");
+
+function run_test() {
+ add_test(test_packet_done);
+ run_next_test();
+}
+
+// Ensure done can be checked without getting an error
+function test_packet_done() {
+ let json = new JSONPacket();
+ do_check_false(!!json.done);
+
+ let bulk = new BulkPacket();
+ do_check_false(!!bulk.done);
+
+ run_next_test();
+}
diff --git a/devtools/shared/transport/tests/unit/test_queue.js b/devtools/shared/transport/tests/unit/test_queue.js
new file mode 100644
index 000000000..5de14baee
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_queue.js
@@ -0,0 +1,177 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test verifies that the transport's queue operates correctly when various
+ * packets are scheduled simultaneously.
+ */
+
+var { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {});
+
+function run_test() {
+ initTestDebuggerServer();
+
+ add_task(function* () {
+ yield test_transport(socket_transport);
+ yield test_transport(local_transport);
+ DebuggerServer.destroy();
+ });
+
+ run_next_test();
+}
+
+/** * Tests ***/
+
+var test_transport = Task.async(function* (transportFactory) {
+ let clientDeferred = defer();
+ let serverDeferred = defer();
+
+ // Ensure test files are not present from a failed run
+ cleanup_files();
+ let reallyLong = really_long();
+ writeTestTempFile("bulk-input", reallyLong);
+
+ do_check_eq(Object.keys(DebuggerServer._connections).length, 0);
+
+ let transport = yield transportFactory();
+
+ // Sending from client to server
+ function write_data({copyFrom}) {
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(getTestTempFile("bulk-input")),
+ loadUsingSystemPrincipal: true
+ }, function (input, status) {
+ copyFrom(input).then(() => {
+ input.close();
+ });
+ });
+ }
+
+ // Receiving on server from client
+ function on_bulk_packet({actor, type, length, copyTo}) {
+ do_check_eq(actor, "root");
+ do_check_eq(type, "file-stream");
+ do_check_eq(length, reallyLong.length);
+
+ let outputFile = getTestTempFile("bulk-output", true);
+ outputFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
+
+ let output = FileUtils.openSafeFileOutputStream(outputFile);
+
+ copyTo(output).then(() => {
+ FileUtils.closeSafeFileOutputStream(output);
+ return verify();
+ }).then(() => {
+ // It's now safe to close
+ transport.hooks.onClosed = () => {
+ clientDeferred.resolve();
+ };
+ transport.close();
+ });
+ }
+
+ // Client
+
+ function send_packets() {
+ // Specifically, we want to ensure that multiple send()s proceed without
+ // causing the transport to die.
+ transport.send({
+ actor: "root",
+ type: "explode"
+ });
+
+ transport.startBulkSend({
+ actor: "root",
+ type: "file-stream",
+ length: reallyLong.length
+ }).then(write_data);
+ }
+
+ transport.hooks = {
+ onPacket: function (packet) {
+ if (packet.error) {
+ transport.hooks.onError(packet);
+ } else if (packet.applicationType) {
+ transport.hooks.onServerHello(packet);
+ } else {
+ do_throw("Unexpected server reply");
+ }
+ },
+
+ onServerHello: function (packet) {
+ // We've received the initial start up packet
+ do_check_eq(packet.from, "root");
+ do_check_eq(packet.applicationType, "xpcshell-tests");
+
+ // Server
+ do_check_eq(Object.keys(DebuggerServer._connections).length, 1);
+ do_print(Object.keys(DebuggerServer._connections));
+ for (let connId in DebuggerServer._connections) {
+ DebuggerServer._connections[connId].onBulkPacket = on_bulk_packet;
+ }
+
+ DebuggerServer.on("connectionchange", (event, type) => {
+ if (type === "closed") {
+ serverDeferred.resolve();
+ }
+ });
+
+ send_packets();
+ },
+
+ onError: function (packet) {
+ // The explode actor doesn't exist
+ do_check_eq(packet.from, "root");
+ do_check_eq(packet.error, "noSuchActor");
+ },
+
+ onClosed: function () {
+ do_throw("Transport closed before we expected");
+ }
+ };
+
+ transport.ready();
+
+ return promise.all([clientDeferred.promise, serverDeferred.promise]);
+});
+
+/** * Test Utils ***/
+
+function verify() {
+ let reallyLong = really_long();
+
+ let inputFile = getTestTempFile("bulk-input");
+ let outputFile = getTestTempFile("bulk-output");
+
+ do_check_eq(inputFile.fileSize, reallyLong.length);
+ do_check_eq(outputFile.fileSize, reallyLong.length);
+
+ // Ensure output file contents actually match
+ let compareDeferred = defer();
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(getTestTempFile("bulk-output")),
+ loadUsingSystemPrincipal: true
+ }, input => {
+ let outputData = NetUtil.readInputStreamToString(input, reallyLong.length);
+ // Avoid do_check_eq here so we don't log the contents
+ do_check_true(outputData === reallyLong);
+ input.close();
+ compareDeferred.resolve();
+ });
+
+ return compareDeferred.promise.then(cleanup_files);
+}
+
+function cleanup_files() {
+ let inputFile = getTestTempFile("bulk-input", true);
+ if (inputFile.exists()) {
+ inputFile.remove(false);
+ }
+
+ let outputFile = getTestTempFile("bulk-output", true);
+ if (outputFile.exists()) {
+ outputFile.remove(false);
+ }
+}
diff --git a/devtools/shared/transport/tests/unit/test_transport_bulk.js b/devtools/shared/transport/tests/unit/test_transport_bulk.js
new file mode 100644
index 000000000..a21216acf
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_transport_bulk.js
@@ -0,0 +1,148 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {});
+
+function run_test() {
+ initTestDebuggerServer();
+
+ add_task(function* () {
+ yield test_bulk_transfer_transport(socket_transport);
+ yield test_bulk_transfer_transport(local_transport);
+ DebuggerServer.destroy();
+ });
+
+ run_next_test();
+}
+
+/** * Tests ***/
+
+/**
+ * This tests a one-way bulk transfer at the transport layer.
+ */
+var test_bulk_transfer_transport = Task.async(function* (transportFactory) {
+ do_print("Starting bulk transfer test at " + new Date().toTimeString());
+
+ let clientDeferred = defer();
+ let serverDeferred = defer();
+
+ // Ensure test files are not present from a failed run
+ cleanup_files();
+ let reallyLong = really_long();
+ writeTestTempFile("bulk-input", reallyLong);
+
+ do_check_eq(Object.keys(DebuggerServer._connections).length, 0);
+
+ let transport = yield transportFactory();
+
+ // Sending from client to server
+ function write_data({copyFrom}) {
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(getTestTempFile("bulk-input")),
+ loadUsingSystemPrincipal: true
+ }, function (input, status) {
+ copyFrom(input).then(() => {
+ input.close();
+ });
+ });
+ }
+
+ // Receiving on server from client
+ function on_bulk_packet({actor, type, length, copyTo}) {
+ do_check_eq(actor, "root");
+ do_check_eq(type, "file-stream");
+ do_check_eq(length, reallyLong.length);
+
+ let outputFile = getTestTempFile("bulk-output", true);
+ outputFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
+
+ let output = FileUtils.openSafeFileOutputStream(outputFile);
+
+ copyTo(output).then(() => {
+ FileUtils.closeSafeFileOutputStream(output);
+ return verify();
+ }).then(() => {
+ // It's now safe to close
+ transport.hooks.onClosed = () => {
+ clientDeferred.resolve();
+ };
+ transport.close();
+ });
+ }
+
+ // Client
+ transport.hooks = {
+ onPacket: function (aPacket) {
+ // We've received the initial start up packet
+ do_check_eq(aPacket.from, "root");
+
+ // Server
+ do_check_eq(Object.keys(DebuggerServer._connections).length, 1);
+ do_print(Object.keys(DebuggerServer._connections));
+ for (let connId in DebuggerServer._connections) {
+ DebuggerServer._connections[connId].onBulkPacket = on_bulk_packet;
+ }
+
+ DebuggerServer.on("connectionchange", (event, type) => {
+ if (type === "closed") {
+ serverDeferred.resolve();
+ }
+ });
+
+ transport.startBulkSend({
+ actor: "root",
+ type: "file-stream",
+ length: reallyLong.length
+ }).then(write_data);
+ },
+
+ onClosed: function () {
+ do_throw("Transport closed before we expected");
+ }
+ };
+
+ transport.ready();
+
+ return promise.all([clientDeferred.promise, serverDeferred.promise]);
+});
+
+/** * Test Utils ***/
+
+function verify() {
+ let reallyLong = really_long();
+
+ let inputFile = getTestTempFile("bulk-input");
+ let outputFile = getTestTempFile("bulk-output");
+
+ do_check_eq(inputFile.fileSize, reallyLong.length);
+ do_check_eq(outputFile.fileSize, reallyLong.length);
+
+ // Ensure output file contents actually match
+ let compareDeferred = defer();
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(getTestTempFile("bulk-output")),
+ loadUsingSystemPrincipal: true
+ }, input => {
+ let outputData = NetUtil.readInputStreamToString(input, reallyLong.length);
+ // Avoid do_check_eq here so we don't log the contents
+ do_check_true(outputData === reallyLong);
+ input.close();
+ compareDeferred.resolve();
+ });
+
+ return compareDeferred.promise.then(cleanup_files);
+}
+
+function cleanup_files() {
+ let inputFile = getTestTempFile("bulk-input", true);
+ if (inputFile.exists()) {
+ inputFile.remove(false);
+ }
+
+ let outputFile = getTestTempFile("bulk-output", true);
+ if (outputFile.exists()) {
+ outputFile.remove(false);
+ }
+}
diff --git a/devtools/shared/transport/tests/unit/test_transport_events.js b/devtools/shared/transport/tests/unit/test_transport_events.js
new file mode 100644
index 000000000..ae20f6cf8
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_transport_events.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function run_test() {
+ initTestDebuggerServer();
+
+ add_task(function* () {
+ yield test_transport_events("socket", socket_transport);
+ yield test_transport_events("local", local_transport);
+ DebuggerServer.destroy();
+ });
+
+ run_next_test();
+}
+
+function* test_transport_events(name, transportFactory) {
+ do_print(`Started testing of transport: ${name}`);
+
+ do_check_eq(Object.keys(DebuggerServer._connections).length, 0);
+
+ let transport = yield transportFactory();
+
+ // Transport expects the hooks to be not null
+ transport.hooks = {
+ onPacket: () => {},
+ onClosed: () => {},
+ };
+
+ let rootReceived = transport.once("packet", (event, packet) => {
+ do_print(`Packet event: ${event} ${JSON.stringify(packet)}`);
+ do_check_eq(event, "packet");
+ do_check_eq(packet.from, "root");
+ });
+
+ transport.ready();
+ yield rootReceived;
+
+ let echoSent = transport.once("send", (event, packet) => {
+ do_print(`Send event: ${event} ${JSON.stringify(packet)}`);
+ do_check_eq(event, "send");
+ do_check_eq(packet.to, "root");
+ do_check_eq(packet.type, "echo");
+ });
+
+ let echoReceived = transport.once("packet", (event, packet) => {
+ do_print(`Packet event: ${event} ${JSON.stringify(packet)}`);
+ do_check_eq(event, "packet");
+ do_check_eq(packet.from, "root");
+ do_check_eq(packet.type, "echo");
+ });
+
+ transport.send({ to: "root", type: "echo" });
+ yield echoSent;
+ yield echoReceived;
+
+ let clientClosed = transport.once("close", (event) => {
+ do_print(`Close event: ${event}`);
+ do_check_eq(event, "close");
+ });
+
+ let serverClosed = DebuggerServer.once("connectionchange", (event, type) => {
+ do_print(`Server closed`);
+ do_check_eq(event, "connectionchange");
+ do_check_eq(type, "closed");
+ });
+
+ transport.close();
+
+ yield clientClosed;
+ yield serverClosed;
+
+ do_print(`Finished testing of transport: ${name}`);
+}
diff --git a/devtools/shared/transport/tests/unit/testactors-no-bulk.js b/devtools/shared/transport/tests/unit/testactors-no-bulk.js
new file mode 100644
index 000000000..d2c8193fe
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/testactors-no-bulk.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { RootActor } = require("devtools/server/actors/root");
+const { DebuggerServer } = require("devtools/server/main");
+
+/**
+ * Root actor that doesn't have the bulk trait.
+ */
+function createRootActor(aConnection) {
+ let root = new RootActor(aConnection, {
+ globalActorFactories: DebuggerServer.globalActorFactories
+ });
+ root.applicationType = "xpcshell-tests";
+ root.traits = {
+ bulk: false
+ };
+ return root;
+}
+
+exports.register = function (handle) {
+ handle.setRootActor(createRootActor);
+};
+
+exports.unregister = function (handle) {
+ handle.setRootActor(null);
+};
diff --git a/devtools/shared/transport/tests/unit/testactors.js b/devtools/shared/transport/tests/unit/testactors.js
new file mode 100644
index 000000000..80d5d4e18
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/testactors.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { ActorPool, appendExtraActors, createExtraActors } =
+ require("devtools/server/actors/common");
+const { RootActor } = require("devtools/server/actors/root");
+const { ThreadActor } = require("devtools/server/actors/script");
+const { DebuggerServer } = require("devtools/server/main");
+const promise = require("promise");
+
+var gTestGlobals = [];
+DebuggerServer.addTestGlobal = function (aGlobal) {
+ gTestGlobals.push(aGlobal);
+};
+
+// A mock tab list, for use by tests. This simply presents each global in
+// gTestGlobals as a tab, and the list is fixed: it never calls its
+// onListChanged handler.
+//
+// As implemented now, we consult gTestGlobals when we're constructed, not
+// when we're iterated over, so tests have to add their globals before the
+// root actor is created.
+function TestTabList(aConnection) {
+ this.conn = aConnection;
+
+ // An array of actors for each global added with
+ // DebuggerServer.addTestGlobal.
+ this._tabActors = [];
+
+ // A pool mapping those actors' names to the actors.
+ this._tabActorPool = new ActorPool(aConnection);
+
+ for (let global of gTestGlobals) {
+ let actor = new TestTabActor(aConnection, global);
+ actor.selected = false;
+ this._tabActors.push(actor);
+ this._tabActorPool.addActor(actor);
+ }
+ if (this._tabActors.length > 0) {
+ this._tabActors[0].selected = true;
+ }
+
+ aConnection.addActorPool(this._tabActorPool);
+}
+
+TestTabList.prototype = {
+ constructor: TestTabList,
+ getList: function () {
+ return promise.resolve([...this._tabActors]);
+ }
+};
+
+function createRootActor(aConnection) {
+ let root = new RootActor(aConnection, {
+ tabList: new TestTabList(aConnection),
+ globalActorFactories: DebuggerServer.globalActorFactories
+ });
+ root.applicationType = "xpcshell-tests";
+ return root;
+}
+
+function TestTabActor(aConnection, aGlobal) {
+ this.conn = aConnection;
+ this._global = aGlobal;
+ this._threadActor = new ThreadActor(this, this._global);
+ this.conn.addActor(this._threadActor);
+ this._attached = false;
+ this._extraActors = {};
+}
+
+TestTabActor.prototype = {
+ constructor: TestTabActor,
+ actorPrefix: "TestTabActor",
+
+ get window() {
+ return { wrappedJSObject: this._global };
+ },
+
+ get url() {
+ return this._global.__name;
+ },
+
+ form: function () {
+ let response = { actor: this.actorID, title: this._global.__name };
+
+ // Walk over tab actors added by extensions and add them to a new ActorPool.
+ let actorPool = new ActorPool(this.conn);
+ this._createExtraActors(DebuggerServer.tabActorFactories, actorPool);
+ if (!actorPool.isEmpty()) {
+ this._tabActorPool = actorPool;
+ this.conn.addActorPool(this._tabActorPool);
+ }
+
+ this._appendExtraActors(response);
+
+ return response;
+ },
+
+ onAttach: function (aRequest) {
+ this._attached = true;
+
+ let response = { type: "tabAttached", threadActor: this._threadActor.actorID };
+ this._appendExtraActors(response);
+
+ return response;
+ },
+
+ onDetach: function (aRequest) {
+ if (!this._attached) {
+ return { "error":"wrongState" };
+ }
+ return { type: "detached" };
+ },
+
+ /* Support for DebuggerServer.addTabActor. */
+ _createExtraActors: createExtraActors,
+ _appendExtraActors: appendExtraActors
+};
+
+TestTabActor.prototype.requestTypes = {
+ "attach": TestTabActor.prototype.onAttach,
+ "detach": TestTabActor.prototype.onDetach
+};
+
+exports.register = function (handle) {
+ handle.setRootActor(createRootActor);
+};
+
+exports.unregister = function (handle) {
+ handle.setRootActor(null);
+};
diff --git a/devtools/shared/transport/tests/unit/xpcshell.ini b/devtools/shared/transport/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..b79906e82
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/xpcshell.ini
@@ -0,0 +1,21 @@
+[DEFAULT]
+tags = devtools
+head = head_dbg.js
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+support-files =
+ testactors.js
+ testactors-no-bulk.js
+
+[test_bulk_error.js]
+[test_client_server_bulk.js]
+[test_dbgsocket.js]
+[test_dbgsocket_connection_drop.js]
+[test_delimited_read.js]
+[test_no_bulk.js]
+[test_packet.js]
+[test_queue.js]
+[test_transport_bulk.js]
+[test_transport_events.js]