diff options
Diffstat (limited to 'devtools/shared/transport/tests')
12 files changed, 1292 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..42794c7712 --- /dev/null +++ b/devtools/shared/transport/tests/xpcshell/head_dbg.js @@ -0,0 +1,177 @@ +/* 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.import("resource://gre/modules/NetUtil.jsm"); + +// 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.ini b/devtools/shared/transport/tests/xpcshell/xpcshell.ini new file mode 100644 index 0000000000..fc369615c2 --- /dev/null +++ b/devtools/shared/transport/tests/xpcshell/xpcshell.ini @@ -0,0 +1,17 @@ +[DEFAULT] +tags = devtools +head = head_dbg.js +firefox-appdir = browser +skip-if = toolkit == '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] |