diff options
Diffstat (limited to 'devtools/shared/transport/tests/unit')
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] |