summaryrefslogtreecommitdiffstats
path: root/devtools/shared/transport/tests/xpcshell
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /devtools/shared/transport/tests/xpcshell
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/shared/transport/tests/xpcshell')
-rw-r--r--devtools/shared/transport/tests/xpcshell/.eslintrc.js6
-rw-r--r--devtools/shared/transport/tests/xpcshell/head_dbg.js179
-rw-r--r--devtools/shared/transport/tests/xpcshell/test_bulk_error.js94
-rw-r--r--devtools/shared/transport/tests/xpcshell/test_client_server_bulk.js312
-rw-r--r--devtools/shared/transport/tests/xpcshell/test_dbgsocket.js163
-rw-r--r--devtools/shared/transport/tests/xpcshell/test_dbgsocket_connection_drop.js86
-rw-r--r--devtools/shared/transport/tests/xpcshell/test_delimited_read.js30
-rw-r--r--devtools/shared/transport/tests/xpcshell/test_packet.js24
-rw-r--r--devtools/shared/transport/tests/xpcshell/test_queue.js198
-rw-r--r--devtools/shared/transport/tests/xpcshell/test_transport_bulk.js169
-rw-r--r--devtools/shared/transport/tests/xpcshell/testactors.js16
-rw-r--r--devtools/shared/transport/tests/xpcshell/xpcshell.toml22
12 files changed, 1299 insertions, 0 deletions
diff --git a/devtools/shared/transport/tests/xpcshell/.eslintrc.js b/devtools/shared/transport/tests/xpcshell/.eslintrc.js
new file mode 100644
index 0000000000..8611c174f5
--- /dev/null
+++ b/devtools/shared/transport/tests/xpcshell/.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/xpcshell/head_dbg.js b/devtools/shared/transport/tests/xpcshell/head_dbg.js
new file mode 100644
index 0000000000..18e235f17e
--- /dev/null
+++ b/devtools/shared/transport/tests/xpcshell/head_dbg.js
@@ -0,0 +1,179 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* exported Cr, CC, NetUtil, errorCount, initTestDevToolsServer,
+ writeTestTempFile, socket_transport, local_transport, really_long
+*/
+
+var CC = Components.Constructor;
+
+const { require } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+);
+const { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+
+// 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 {
+ ActorRegistry,
+} = require("resource://devtools/server/actors/utils/actor-registry.js");
+const {
+ DevToolsServer,
+} = require("resource://devtools/server/devtools-server.js");
+const {
+ DevToolsClient,
+} = require("resource://devtools/client/devtools-client.js");
+const {
+ SocketListener,
+} = require("resource://devtools/shared/security/socket.js");
+
+// Convert an nsIScriptError 'logLevel' value into an appropriate string.
+function scriptErrorLogLevel(message) {
+ switch (message.logLevel) {
+ case Ci.nsIConsoleMessage.info:
+ return "info";
+ case Ci.nsIConsoleMessage.warn:
+ return "warning";
+ default:
+ Assert.equal(message.logLevel, Ci.nsIConsoleMessage.error);
+ return "error";
+ }
+}
+
+// Register a console listener, so console messages don't just disappear
+// into the ether.
+var errorCount = 0;
+var listener = {
+ observe(message) {
+ errorCount++;
+ let string = "";
+ try {
+ // If we've been given an nsIScriptError, then we can print out
+ // something nicely formatted, for tools like Emacs to pick up.
+ message.QueryInterface(Ci.nsIScriptError);
+ dump(
+ message.sourceName +
+ ":" +
+ message.lineNumber +
+ ": " +
+ scriptErrorLogLevel(message) +
+ ": " +
+ message.errorMessage +
+ "\n"
+ );
+ string = message.errorMessage;
+ } catch (x) {
+ // Be a little paranoid with message, as the whole goal here is to lose
+ // no information.
+ try {
+ string = message.message;
+ } catch (e) {
+ string = "<error converting error message to string>";
+ }
+ }
+
+ // Make sure we exit all nested event loops so that the test can finish.
+ while (DevToolsServer.xpcInspector.eventLoopNestLevel > 0) {
+ DevToolsServer.xpcInspector.exitNestedEventLoop();
+ }
+
+ do_throw("head_dbg.js got console message: " + string + "\n");
+ },
+};
+
+Services.console.registerListener(listener);
+
+/**
+ * Initialize the testing devtools server.
+ */
+function initTestDevToolsServer() {
+ ActorRegistry.registerModule("devtools/server/actors/thread", {
+ prefix: "script",
+ constructor: "ScriptActor",
+ type: { global: true, target: true },
+ });
+ const { createRootActor } = require("xpcshell-test/testactors");
+ DevToolsServer.setRootActor(createRootActor);
+ // Allow incoming connections.
+ DevToolsServer.init();
+ // Avoid the server from being destroyed when the last connection closes
+ DevToolsServer.keepAlive = true;
+}
+
+/**
+ * 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(fileName, content) {
+ const file = getTestTempFile(fileName, true);
+ const stream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
+ Ci.nsIFileOutputStream
+ );
+ stream.init(file, -1, -1, 0);
+ try {
+ do {
+ const numWritten = stream.write(content, content.length);
+ content = content.slice(numWritten);
+ } while (content.length);
+ } finally {
+ stream.close();
+ }
+}
+
+/** * Transport Factories ***/
+
+var socket_transport = async function () {
+ if (!DevToolsServer.listeningSockets) {
+ const AuthenticatorType = DevToolsServer.Authenticators.get("PROMPT");
+ const authenticator = new AuthenticatorType.Server();
+ authenticator.allowConnection = () => {
+ return DevToolsServer.AuthenticationResult.ALLOW;
+ };
+ const socketOptions = {
+ authenticator,
+ portOrPath: -1,
+ };
+ const debuggerListener = new SocketListener(DevToolsServer, socketOptions);
+ await debuggerListener.open();
+ }
+ const port = DevToolsServer._listeners[0].port;
+ info("DevTools server port is " + port);
+ return DevToolsClient.socketConnect({ host: "127.0.0.1", port });
+};
+
+function local_transport() {
+ return Promise.resolve(DevToolsServer.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/xpcshell/test_bulk_error.js b/devtools/shared/transport/tests/xpcshell/test_bulk_error.js
new file mode 100644
index 0000000000..52cc826e51
--- /dev/null
+++ b/devtools/shared/transport/tests/xpcshell/test_bulk_error.js
@@ -0,0 +1,94 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function run_test() {
+ initTestDevToolsServer();
+ add_test_bulk_actor();
+
+ add_task(async function () {
+ await test_string_error(socket_transport, json_reply);
+ await test_string_error(local_transport, json_reply);
+ DevToolsServer.destroy();
+ });
+
+ run_next_test();
+}
+
+/** * Sample Bulk Actor ***/
+const { Actor } = require("resource://devtools/shared/protocol/Actor.js");
+class TestBulkActor extends Actor {
+ constructor(conn) {
+ super(conn);
+
+ this.typeName = "testBulk";
+ this.requestTypes = {
+ jsonReply: this.jsonReply,
+ };
+ }
+
+ jsonReply({ length, reader, reply, done }) {
+ Assert.equal(length, really_long().length);
+
+ return {
+ allDone: true,
+ };
+ }
+}
+
+function add_test_bulk_actor() {
+ ActorRegistry.addGlobalActor(
+ {
+ constructorName: "TestBulkActor",
+ constructorFun: TestBulkActor,
+ },
+ "testBulk"
+ );
+}
+
+/** * Tests ***/
+
+var test_string_error = async function (transportFactory, onReady) {
+ const transport = await transportFactory();
+
+ const client = new DevToolsClient(transport);
+ await client.connect();
+ const response = await client.mainRoot.rootForm;
+
+ await onReady(client, response);
+ client.close();
+ transport.close();
+};
+
+/** * Reply Types ***/
+
+function json_reply(client, response) {
+ const reallyLong = really_long();
+
+ const request = client.startBulkRequest({
+ actor: response.testBulk,
+ type: "jsonReply",
+ length: reallyLong.length,
+ });
+
+ // Send bulk data to server
+ return new Promise(resolve => {
+ request.on("bulk-send-ready", ({ writer, done }) => {
+ const 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) {
+ Assert.ok(true);
+ resolve();
+ }
+ });
+ });
+}
diff --git a/devtools/shared/transport/tests/xpcshell/test_client_server_bulk.js b/devtools/shared/transport/tests/xpcshell/test_client_server_bulk.js
new file mode 100644
index 0000000000..22efdc6eba
--- /dev/null
+++ b/devtools/shared/transport/tests/xpcshell/test_client_server_bulk.js
@@ -0,0 +1,312 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+);
+var Pipe = Components.Constructor("@mozilla.org/pipe;1", "nsIPipe", "init");
+
+function run_test() {
+ initTestDevToolsServer();
+ add_test_bulk_actor();
+
+ add_task(async function () {
+ await test_bulk_request_cs(socket_transport, "jsonReply", "json");
+ await test_bulk_request_cs(local_transport, "jsonReply", "json");
+ await test_bulk_request_cs(socket_transport, "bulkEcho", "bulk");
+ await test_bulk_request_cs(local_transport, "bulkEcho", "bulk");
+ await test_json_request_cs(socket_transport, "bulkReply", "bulk");
+ await test_json_request_cs(local_transport, "bulkReply", "bulk");
+ DevToolsServer.destroy();
+ });
+
+ run_next_test();
+}
+
+/** * Sample Bulk Actor ***/
+const { Actor } = require("resource://devtools/shared/protocol/Actor.js");
+class TestBulkActor extends Actor {
+ constructor(conn) {
+ super(conn, { typeName: "testBulk", methods: [] });
+
+ this.requestTypes = {
+ bulkEcho: this.bulkEcho,
+ bulkReply: this.bulkReply,
+ jsonReply: this.jsonReply,
+ };
+ }
+
+ bulkEcho({ actor, type, length, copyTo }) {
+ Assert.equal(length, really_long().length);
+ this.conn
+ .startBulkSend({
+ actor,
+ type,
+ length,
+ })
+ .then(({ copyFrom }) => {
+ // We'll just echo back the same thing
+ const pipe = new Pipe(true, true, 0, 0, null);
+ copyTo(pipe.outputStream).then(() => {
+ pipe.outputStream.close();
+ });
+ copyFrom(pipe.inputStream).then(() => {
+ pipe.inputStream.close();
+ });
+ });
+ }
+
+ bulkReply({ to, type }) {
+ this.conn
+ .startBulkSend({
+ actor: to,
+ type,
+ length: really_long().length,
+ })
+ .then(({ copyFrom }) => {
+ NetUtil.asyncFetch(
+ {
+ uri: NetUtil.newURI(getTestTempFile("bulk-input")),
+ loadUsingSystemPrincipal: true,
+ },
+ input => {
+ copyFrom(input).then(() => {
+ input.close();
+ });
+ }
+ );
+ });
+ }
+
+ jsonReply({ length, copyTo }) {
+ Assert.equal(length, really_long().length);
+
+ const outputFile = getTestTempFile("bulk-output", true);
+ outputFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
+
+ const output = FileUtils.openSafeFileOutputStream(outputFile);
+
+ return copyTo(output)
+ .then(() => {
+ FileUtils.closeSafeFileOutputStream(output);
+ return verify_files();
+ })
+ .then(() => {
+ return { allDone: true };
+ }, do_throw);
+ }
+}
+
+function add_test_bulk_actor() {
+ ActorRegistry.addGlobalActor(
+ {
+ constructorName: "TestBulkActor",
+ constructorFun: TestBulkActor,
+ },
+ "testBulk"
+ );
+}
+
+/** * Reply Handlers ***/
+
+var replyHandlers = {
+ json(request) {
+ // Receive JSON reply from server
+ return new Promise(resolve => {
+ request.on("json-reply", reply => {
+ Assert.ok(reply.allDone);
+ resolve();
+ });
+ });
+ },
+
+ bulk(request) {
+ // Receive bulk data reply from server
+ return new Promise(resolve => {
+ request.on("bulk-reply", ({ length, copyTo }) => {
+ Assert.equal(length, really_long().length);
+
+ const outputFile = getTestTempFile("bulk-output", true);
+ outputFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
+
+ const output = FileUtils.openSafeFileOutputStream(outputFile);
+
+ copyTo(output).then(() => {
+ FileUtils.closeSafeFileOutputStream(output);
+ resolve(verify_files());
+ });
+ });
+ });
+ },
+};
+
+/** * Tests ***/
+
+var test_bulk_request_cs = async function (
+ transportFactory,
+ actorType,
+ replyType
+) {
+ // Ensure test files are not present from a failed run
+ cleanup_files();
+ writeTestTempFile("bulk-input", really_long());
+
+ let clientResolve;
+ const clientDeferred = new Promise(resolve => {
+ clientResolve = resolve;
+ });
+
+ let serverResolve;
+ const serverDeferred = new Promise(resolve => {
+ serverResolve = resolve;
+ });
+
+ let bulkCopyResolve;
+ const bulkCopyDeferred = new Promise(resolve => {
+ bulkCopyResolve = resolve;
+ });
+
+ const transport = await transportFactory();
+
+ const client = new DevToolsClient(transport);
+ client.connect().then(() => {
+ client.mainRoot.rootForm.then(clientResolve);
+ });
+
+ function bulkSendReadyCallback({ copyFrom }) {
+ NetUtil.asyncFetch(
+ {
+ uri: NetUtil.newURI(getTestTempFile("bulk-input")),
+ loadUsingSystemPrincipal: true,
+ },
+ input => {
+ copyFrom(input).then(() => {
+ input.close();
+ bulkCopyResolve();
+ });
+ }
+ );
+ }
+
+ clientDeferred
+ .then(response => {
+ const request = client.startBulkRequest({
+ actor: response.testBulk,
+ type: actorType,
+ length: really_long().length,
+ });
+
+ // Send bulk data to server
+ request.on("bulk-send-ready", bulkSendReadyCallback);
+
+ // Set up reply handling for this type
+ replyHandlers[replyType](request).then(() => {
+ client.close();
+ transport.close();
+ });
+ })
+ .catch(do_throw);
+
+ DevToolsServer.on("connectionchange", type => {
+ if (type === "closed") {
+ serverResolve();
+ }
+ });
+
+ return Promise.all([clientDeferred, bulkCopyDeferred, serverDeferred]);
+};
+
+var test_json_request_cs = async function (
+ transportFactory,
+ actorType,
+ replyType
+) {
+ // Ensure test files are not present from a failed run
+ cleanup_files();
+ writeTestTempFile("bulk-input", really_long());
+
+ let clientResolve;
+ const clientDeferred = new Promise(resolve => {
+ clientResolve = resolve;
+ });
+
+ let serverResolve;
+ const serverDeferred = new Promise(resolve => {
+ serverResolve = resolve;
+ });
+
+ const transport = await transportFactory();
+
+ const client = new DevToolsClient(transport);
+ await client.connect();
+ client.mainRoot.rootForm.then(clientResolve);
+
+ clientDeferred
+ .then(response => {
+ const request = client.request({
+ to: response.testBulk,
+ type: actorType,
+ });
+
+ // Set up reply handling for this type
+ replyHandlers[replyType](request).then(() => {
+ client.close();
+ transport.close();
+ });
+ })
+ .catch(do_throw);
+
+ DevToolsServer.on("connectionchange", type => {
+ if (type === "closed") {
+ serverResolve();
+ }
+ });
+
+ return Promise.all([clientDeferred, serverDeferred]);
+};
+
+/** * Test Utils ***/
+
+function verify_files() {
+ const reallyLong = really_long();
+
+ const inputFile = getTestTempFile("bulk-input");
+ const outputFile = getTestTempFile("bulk-output");
+
+ Assert.equal(inputFile.fileSize, reallyLong.length);
+ Assert.equal(outputFile.fileSize, reallyLong.length);
+
+ // Ensure output file contents actually match
+ return new Promise(resolve => {
+ NetUtil.asyncFetch(
+ {
+ uri: NetUtil.newURI(getTestTempFile("bulk-output")),
+ loadUsingSystemPrincipal: true,
+ },
+ input => {
+ const outputData = NetUtil.readInputStreamToString(
+ input,
+ reallyLong.length
+ );
+ // Avoid do_check_eq here so we don't log the contents
+ Assert.ok(outputData === reallyLong);
+ input.close();
+ resolve();
+ }
+ );
+ }).then(cleanup_files);
+}
+
+function cleanup_files() {
+ const inputFile = getTestTempFile("bulk-input", true);
+ if (inputFile.exists()) {
+ inputFile.remove(false);
+ }
+
+ const outputFile = getTestTempFile("bulk-output", true);
+ if (outputFile.exists()) {
+ outputFile.remove(false);
+ }
+}
diff --git a/devtools/shared/transport/tests/xpcshell/test_dbgsocket.js b/devtools/shared/transport/tests/xpcshell/test_dbgsocket.js
new file mode 100644
index 0000000000..535431aa38
--- /dev/null
+++ b/devtools/shared/transport/tests/xpcshell/test_dbgsocket.js
@@ -0,0 +1,163 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/* global structuredClone */
+
+var gPort;
+var gExtraListener;
+
+function run_test() {
+ info("Starting test at " + new Date().toTimeString());
+ initTestDevToolsServer();
+
+ add_task(test_socket_conn);
+ add_task(test_socket_shutdown);
+ add_test(test_pipe_conn);
+
+ run_next_test();
+}
+
+const { Actor } = require("resource://devtools/shared/protocol/Actor.js");
+class EchoTestActor extends Actor {
+ constructor(conn) {
+ super(conn, { typeName: "EchoTestActor", methods: [] });
+
+ this.requestTypes = {
+ echo: EchoTestActor.prototype.onEcho,
+ };
+ }
+
+ onEcho(request) {
+ /*
+ * Request packets are frozen. Copy request, so that
+ * DevToolsServerConnection.onPacket can attach a 'from' property.
+ */
+ return structuredClone(request);
+ }
+}
+
+async function test_socket_conn() {
+ Assert.equal(DevToolsServer.listeningSockets, 0);
+ const AuthenticatorType = DevToolsServer.Authenticators.get("PROMPT");
+ const authenticator = new AuthenticatorType.Server();
+ authenticator.allowConnection = () => {
+ return DevToolsServer.AuthenticationResult.ALLOW;
+ };
+ const socketOptions = {
+ authenticator,
+ portOrPath: -1,
+ };
+ const listener = new SocketListener(DevToolsServer, socketOptions);
+ Assert.ok(listener);
+ listener.open();
+ Assert.equal(DevToolsServer.listeningSockets, 1);
+ gPort = DevToolsServer._listeners[0].port;
+ info("DevTools server port is " + gPort);
+ // Open a second, separate listener
+ gExtraListener = new SocketListener(DevToolsServer, socketOptions);
+ gExtraListener.open();
+ Assert.equal(DevToolsServer.listeningSockets, 2);
+ Assert.ok(!DevToolsServer.hasConnection());
+
+ info("Starting long and unicode tests at " + new Date().toTimeString());
+ // We can't use EventEmitter.once as this is the second argument we care about...
+ const onConnectionChange = new Promise(res => {
+ DevToolsServer.once("connectionchange", (type, conn) => res(conn));
+ });
+
+ const transport = await DevToolsClient.socketConnect({
+ host: "127.0.0.1",
+ port: gPort,
+ });
+ Assert.ok(DevToolsServer.hasConnection());
+ info("Wait for server connection");
+ const conn = await onConnectionChange;
+
+ // Register a custom actor to do echo requests
+ const actor = new EchoTestActor(conn);
+ actor.actorID = "echo-actor";
+ conn.addActor(actor);
+
+ // Assert that connection settings are available on transport object
+ const settings = transport.connectionSettings;
+ Assert.equal(settings.host, "127.0.0.1");
+ Assert.equal(settings.port, gPort);
+
+ const onDebuggerConnectionClosed = DevToolsServer.once("connectionchange");
+ const unicodeString = "(╯°□°)╯︵ ┻━┻";
+ await new Promise(resolve => {
+ transport.hooks = {
+ onPacket(packet) {
+ this.onPacket = function ({ unicode }) {
+ Assert.equal(unicode, unicodeString);
+ transport.close();
+ };
+ // Verify that things work correctly when bigger than the output
+ // transport buffers and when transporting unicode...
+ transport.send({
+ to: "echo-actor",
+ type: "echo",
+ reallylong: really_long(),
+ unicode: unicodeString,
+ });
+ Assert.equal(packet.from, "root");
+ },
+ onTransportClosed(status) {
+ resolve();
+ },
+ };
+ transport.ready();
+ });
+ const type = await onDebuggerConnectionClosed;
+ Assert.equal(type, "closed");
+ Assert.ok(!DevToolsServer.hasConnection());
+}
+
+async function test_socket_shutdown() {
+ Assert.equal(DevToolsServer.listeningSockets, 2);
+ gExtraListener.close();
+ Assert.equal(DevToolsServer.listeningSockets, 1);
+ Assert.ok(DevToolsServer.closeAllSocketListeners());
+ Assert.equal(DevToolsServer.listeningSockets, 0);
+ // Make sure closing the listener twice does nothing.
+ Assert.ok(!DevToolsServer.closeAllSocketListeners());
+ Assert.equal(DevToolsServer.listeningSockets, 0);
+
+ info("Connecting to a server socket at " + new Date().toTimeString());
+ try {
+ await DevToolsClient.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.
+ Assert.ok(true);
+ return;
+ }
+ throw e;
+ }
+
+ // Shouldn't reach this, should never connect.
+ Assert.ok(false);
+}
+
+function test_pipe_conn() {
+ const transport = DevToolsServer.connectPipe();
+ transport.hooks = {
+ onPacket(packet) {
+ Assert.equal(packet.from, "root");
+ transport.close();
+ },
+ onTransportClosed(status) {
+ run_next_test();
+ },
+ };
+
+ transport.ready();
+}
diff --git a/devtools/shared/transport/tests/xpcshell/test_dbgsocket_connection_drop.js b/devtools/shared/transport/tests/xpcshell/test_dbgsocket_connection_drop.js
new file mode 100644
index 0000000000..e08c2380fb
--- /dev/null
+++ b/devtools/shared/transport/tests/xpcshell/test_dbgsocket_connection_drop.js
@@ -0,0 +1,86 @@
+/**
+ * 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.
+ */
+"use strict";
+
+const {
+ RawPacket,
+} = require("resource://devtools/shared/transport/packets.js");
+
+function run_test() {
+ info("Starting test at " + new Date().toTimeString());
+ initTestDevToolsServer();
+
+ 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 = async function (payload) {
+ const AuthenticatorType = DevToolsServer.Authenticators.get("PROMPT");
+ const authenticator = new AuthenticatorType.Server();
+ authenticator.allowConnection = () => {
+ return DevToolsServer.AuthenticationResult.ALLOW;
+ };
+ const socketOptions = {
+ authenticator,
+ portOrPath: -1,
+ };
+
+ const listener = new SocketListener(DevToolsServer, socketOptions);
+ listener.open();
+
+ const transport = await DevToolsClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port,
+ });
+ return new Promise(resolve => {
+ transport.hooks = {
+ onPacket(packet) {
+ this.onPacket = function () {
+ 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();
+ },
+ onTransportClosed(status) {
+ Assert.ok(true);
+ resolve();
+ },
+ };
+ transport.ready();
+ });
+};
diff --git a/devtools/shared/transport/tests/xpcshell/test_delimited_read.js b/devtools/shared/transport/tests/xpcshell/test_delimited_read.js
new file mode 100644
index 0000000000..fe94f81bbf
--- /dev/null
+++ b/devtools/shared/transport/tests/xpcshell/test_delimited_read.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const StreamUtils = require("resource://devtools/shared/transport/stream-utils.js");
+
+const StringInputStream = Components.Constructor(
+ "@mozilla.org/io/string-input-stream;1",
+ "nsIStringInputStream",
+ "setData"
+);
+
+function run_test() {
+ add_task(async function () {
+ await test_delimited_read("0123:", "0123:");
+ await test_delimited_read("0123:4567:", "0123:");
+ await test_delimited_read("012345678901:", "0123456789");
+ await test_delimited_read("0123/0123", "0123/0123");
+ });
+
+ run_next_test();
+}
+
+/** * Tests ***/
+
+function test_delimited_read(input, expected) {
+ input = new StringInputStream(input, input.length);
+ const result = StreamUtils.delimitedRead(input, ":", 10);
+ Assert.equal(result, expected);
+}
diff --git a/devtools/shared/transport/tests/xpcshell/test_packet.js b/devtools/shared/transport/tests/xpcshell/test_packet.js
new file mode 100644
index 0000000000..459a7a8211
--- /dev/null
+++ b/devtools/shared/transport/tests/xpcshell/test_packet.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const {
+ JSONPacket,
+ BulkPacket,
+} = require("resource://devtools/shared/transport/packets.js");
+
+function run_test() {
+ add_test(test_packet_done);
+ run_next_test();
+}
+
+// Ensure done can be checked without getting an error
+function test_packet_done() {
+ const json = new JSONPacket();
+ Assert.ok(!json.done);
+
+ const bulk = new BulkPacket();
+ Assert.ok(!bulk.done);
+
+ run_next_test();
+}
diff --git a/devtools/shared/transport/tests/xpcshell/test_queue.js b/devtools/shared/transport/tests/xpcshell/test_queue.js
new file mode 100644
index 0000000000..603640e34d
--- /dev/null
+++ b/devtools/shared/transport/tests/xpcshell/test_queue.js
@@ -0,0 +1,198 @@
+/* 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 } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+);
+
+function run_test() {
+ initTestDevToolsServer();
+
+ add_task(async function () {
+ await test_transport(socket_transport);
+ await test_transport(local_transport);
+ DevToolsServer.destroy();
+ });
+
+ run_next_test();
+}
+
+/** * Tests ***/
+
+var test_transport = async function (transportFactory) {
+ let clientResolve;
+ const clientDeferred = new Promise(resolve => {
+ clientResolve = resolve;
+ });
+
+ let serverResolve;
+ const serverDeferred = new Promise(resolve => {
+ serverResolve = resolve;
+ });
+
+ // Ensure test files are not present from a failed run
+ cleanup_files();
+ const reallyLong = really_long();
+ writeTestTempFile("bulk-input", reallyLong);
+
+ Assert.equal(Object.keys(DevToolsServer._connections).length, 0);
+
+ const transport = await 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 }) {
+ Assert.equal(actor, "root");
+ Assert.equal(type, "file-stream");
+ Assert.equal(length, reallyLong.length);
+
+ const outputFile = getTestTempFile("bulk-output", true);
+ outputFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
+
+ const output = FileUtils.openSafeFileOutputStream(outputFile);
+
+ copyTo(output)
+ .then(() => {
+ FileUtils.closeSafeFileOutputStream(output);
+ return verify();
+ })
+ .then(() => {
+ // It's now safe to close
+ transport.hooks.onTransportClosed = () => {
+ clientResolve();
+ };
+ 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(packet) {
+ if (packet.error) {
+ transport.hooks.onError(packet);
+ } else if (packet.applicationType) {
+ transport.hooks.onServerHello(packet);
+ } else {
+ do_throw("Unexpected server reply");
+ }
+ },
+
+ onServerHello(packet) {
+ // We've received the initial start up packet
+ Assert.equal(packet.from, "root");
+ Assert.equal(packet.applicationType, "xpcshell-tests");
+
+ // Server
+ Assert.equal(Object.keys(DevToolsServer._connections).length, 1);
+ info(Object.keys(DevToolsServer._connections));
+ for (const connId in DevToolsServer._connections) {
+ DevToolsServer._connections[connId].onBulkPacket = on_bulk_packet;
+ }
+
+ DevToolsServer.on("connectionchange", type => {
+ if (type === "closed") {
+ serverResolve();
+ }
+ });
+
+ send_packets();
+ },
+
+ onError(packet) {
+ // The explode actor doesn't exist
+ Assert.equal(packet.from, "root");
+ Assert.equal(packet.error, "noSuchActor");
+ },
+
+ onTransportClosed() {
+ do_throw("Transport closed before we expected");
+ },
+ };
+
+ transport.ready();
+
+ return Promise.all([clientDeferred, serverDeferred]);
+};
+
+/** * Test Utils ***/
+
+function verify() {
+ const reallyLong = really_long();
+
+ const inputFile = getTestTempFile("bulk-input");
+ const outputFile = getTestTempFile("bulk-output");
+
+ Assert.equal(inputFile.fileSize, reallyLong.length);
+ Assert.equal(outputFile.fileSize, reallyLong.length);
+
+ // Ensure output file contents actually match
+ return new Promise(resolve => {
+ NetUtil.asyncFetch(
+ {
+ uri: NetUtil.newURI(getTestTempFile("bulk-output")),
+ loadUsingSystemPrincipal: true,
+ },
+ input => {
+ const outputData = NetUtil.readInputStreamToString(
+ input,
+ reallyLong.length
+ );
+ // Avoid do_check_eq here so we don't log the contents
+ Assert.ok(outputData === reallyLong);
+ input.close();
+ resolve();
+ }
+ );
+ }).then(cleanup_files);
+}
+
+function cleanup_files() {
+ const inputFile = getTestTempFile("bulk-input", true);
+ if (inputFile.exists()) {
+ inputFile.remove(false);
+ }
+
+ const outputFile = getTestTempFile("bulk-output", true);
+ if (outputFile.exists()) {
+ outputFile.remove(false);
+ }
+}
diff --git a/devtools/shared/transport/tests/xpcshell/test_transport_bulk.js b/devtools/shared/transport/tests/xpcshell/test_transport_bulk.js
new file mode 100644
index 0000000000..137cbd2679
--- /dev/null
+++ b/devtools/shared/transport/tests/xpcshell/test_transport_bulk.js
@@ -0,0 +1,169 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+);
+
+function run_test() {
+ initTestDevToolsServer();
+
+ add_task(async function () {
+ await test_bulk_transfer_transport(socket_transport);
+ await test_bulk_transfer_transport(local_transport);
+ DevToolsServer.destroy();
+ });
+
+ run_next_test();
+}
+
+/** * Tests ***/
+
+/**
+ * This tests a one-way bulk transfer at the transport layer.
+ */
+var test_bulk_transfer_transport = async function (transportFactory) {
+ info("Starting bulk transfer test at " + new Date().toTimeString());
+
+ let clientResolve;
+ const clientDeferred = new Promise(resolve => {
+ clientResolve = resolve;
+ });
+
+ let serverResolve;
+ const serverDeferred = new Promise(resolve => {
+ serverResolve = resolve;
+ });
+
+ // Ensure test files are not present from a failed run
+ cleanup_files();
+ const reallyLong = really_long();
+ writeTestTempFile("bulk-input", reallyLong);
+
+ Assert.equal(Object.keys(DevToolsServer._connections).length, 0);
+
+ const transport = await 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 }) {
+ Assert.equal(actor, "root");
+ Assert.equal(type, "file-stream");
+ Assert.equal(length, reallyLong.length);
+
+ const outputFile = getTestTempFile("bulk-output", true);
+ outputFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
+
+ const output = FileUtils.openSafeFileOutputStream(outputFile);
+
+ copyTo(output)
+ .then(() => {
+ FileUtils.closeSafeFileOutputStream(output);
+ return verify();
+ })
+ .then(() => {
+ // It's now safe to close
+ transport.hooks.onTransportClosed = () => {
+ clientResolve();
+ };
+ transport.close();
+ });
+ }
+
+ // Client
+ transport.hooks = {
+ onPacket(packet) {
+ // We've received the initial start up packet
+ Assert.equal(packet.from, "root");
+
+ // Server
+ Assert.equal(Object.keys(DevToolsServer._connections).length, 1);
+ info(Object.keys(DevToolsServer._connections));
+ for (const connId in DevToolsServer._connections) {
+ DevToolsServer._connections[connId].onBulkPacket = on_bulk_packet;
+ }
+
+ DevToolsServer.on("connectionchange", type => {
+ if (type === "closed") {
+ serverResolve();
+ }
+ });
+
+ transport
+ .startBulkSend({
+ actor: "root",
+ type: "file-stream",
+ length: reallyLong.length,
+ })
+ .then(write_data);
+ },
+
+ onTransportClosed() {
+ do_throw("Transport closed before we expected");
+ },
+ };
+
+ transport.ready();
+
+ return Promise.all([clientDeferred, serverDeferred]);
+};
+
+/** * Test Utils ***/
+
+function verify() {
+ const reallyLong = really_long();
+
+ const inputFile = getTestTempFile("bulk-input");
+ const outputFile = getTestTempFile("bulk-output");
+
+ Assert.equal(inputFile.fileSize, reallyLong.length);
+ Assert.equal(outputFile.fileSize, reallyLong.length);
+
+ // Ensure output file contents actually match
+ return new Promise(resolve => {
+ NetUtil.asyncFetch(
+ {
+ uri: NetUtil.newURI(getTestTempFile("bulk-output")),
+ loadUsingSystemPrincipal: true,
+ },
+ input => {
+ const outputData = NetUtil.readInputStreamToString(
+ input,
+ reallyLong.length
+ );
+ // Avoid do_check_eq here so we don't log the contents
+ Assert.ok(outputData === reallyLong);
+ input.close();
+ resolve();
+ }
+ );
+ }).then(cleanup_files);
+}
+
+function cleanup_files() {
+ const inputFile = getTestTempFile("bulk-input", true);
+ if (inputFile.exists()) {
+ inputFile.remove(false);
+ }
+
+ const outputFile = getTestTempFile("bulk-output", true);
+ if (outputFile.exists()) {
+ outputFile.remove(false);
+ }
+}
diff --git a/devtools/shared/transport/tests/xpcshell/testactors.js b/devtools/shared/transport/tests/xpcshell/testactors.js
new file mode 100644
index 0000000000..0a35f05287
--- /dev/null
+++ b/devtools/shared/transport/tests/xpcshell/testactors.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const { RootActor } = require("resource://devtools/server/actors/root.js");
+const {
+ ActorRegistry,
+} = require("resource://devtools/server/actors/utils/actor-registry.js");
+
+exports.createRootActor = function createRootActor(connection) {
+ const root = new RootActor(connection, {
+ globalActorFactories: ActorRegistry.globalActorFactories,
+ });
+ root.applicationType = "xpcshell-tests";
+ return root;
+};
diff --git a/devtools/shared/transport/tests/xpcshell/xpcshell.toml b/devtools/shared/transport/tests/xpcshell/xpcshell.toml
new file mode 100644
index 0000000000..38e49ab71c
--- /dev/null
+++ b/devtools/shared/transport/tests/xpcshell/xpcshell.toml
@@ -0,0 +1,22 @@
+[DEFAULT]
+tags = "devtools"
+head = "head_dbg.js"
+firefox-appdir = "browser"
+skip-if = ["os == 'android'"]
+support-files = ["testactors.js"]
+
+["test_bulk_error.js"]
+
+["test_client_server_bulk.js"]
+
+["test_dbgsocket.js"]
+
+["test_dbgsocket_connection_drop.js"]
+
+["test_delimited_read.js"]
+
+["test_packet.js"]
+
+["test_queue.js"]
+
+["test_transport_bulk.js"]