michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: "use strict"; michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: const Cr = Components.results; michael@0: michael@0: const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); michael@0: const Services = devtools.require("Services"); michael@0: const { ActorPool, createExtraActors, appendExtraActors } = devtools.require("devtools/server/actors/common"); michael@0: const DevToolsUtils = devtools.require("devtools/toolkit/DevToolsUtils.js"); michael@0: const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); michael@0: michael@0: // Always log packets when running tests. runxpcshelltests.py will throw michael@0: // the output away anyway, unless you give it the --verbose flag. michael@0: Services.prefs.setBoolPref("devtools.debugger.log", true); michael@0: // Enable remote debugging for the relevant tests. michael@0: Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true); michael@0: michael@0: function tryImport(url) { michael@0: try { michael@0: Cu.import(url); michael@0: } catch (e) { michael@0: dump("Error importing " + url + "\n"); michael@0: dump(DevToolsUtils.safeErrorString(e) + "\n"); michael@0: throw e; michael@0: } michael@0: } michael@0: michael@0: tryImport("resource://gre/modules/devtools/dbg-server.jsm"); michael@0: tryImport("resource://gre/modules/devtools/dbg-client.jsm"); michael@0: tryImport("resource://gre/modules/devtools/Loader.jsm"); michael@0: tryImport("resource://gre/modules/devtools/Console.jsm"); michael@0: michael@0: function testExceptionHook(ex) { michael@0: try { michael@0: do_report_unexpected_exception(ex); michael@0: } catch(ex) { michael@0: return {throw: ex} michael@0: } michael@0: return undefined; michael@0: } michael@0: michael@0: // Convert an nsIScriptError 'aFlags' value into an appropriate string. michael@0: function scriptErrorFlagsToKind(aFlags) { michael@0: var kind; michael@0: if (aFlags & Ci.nsIScriptError.warningFlag) michael@0: kind = "warning"; michael@0: if (aFlags & Ci.nsIScriptError.exceptionFlag) michael@0: kind = "exception"; michael@0: else michael@0: kind = "error"; michael@0: michael@0: if (aFlags & Ci.nsIScriptError.strictFlag) michael@0: kind = "strict " + kind; michael@0: michael@0: return kind; michael@0: } michael@0: michael@0: // Redeclare dbg_assert with a fatal behavior. michael@0: function dbg_assert(cond, e) { michael@0: if (!cond) { michael@0: throw e; michael@0: } michael@0: } michael@0: michael@0: // Register a console listener, so console messages don't just disappear michael@0: // into the ether. michael@0: let errorCount = 0; michael@0: let listener = { michael@0: observe: function (aMessage) { michael@0: errorCount++; michael@0: try { michael@0: // If we've been given an nsIScriptError, then we can print out michael@0: // something nicely formatted, for tools like Emacs to pick up. michael@0: var scriptError = aMessage.QueryInterface(Ci.nsIScriptError); michael@0: dump(aMessage.sourceName + ":" + aMessage.lineNumber + ": " + michael@0: scriptErrorFlagsToKind(aMessage.flags) + ": " + michael@0: aMessage.errorMessage + "\n"); michael@0: var string = aMessage.errorMessage; michael@0: } catch (x) { michael@0: // Be a little paranoid with message, as the whole goal here is to lose michael@0: // no information. michael@0: try { michael@0: var string = "" + aMessage.message; michael@0: } catch (x) { michael@0: var string = ""; michael@0: } michael@0: } michael@0: michael@0: // Make sure we exit all nested event loops so that the test can finish. michael@0: while (DebuggerServer.xpcInspector.eventLoopNestLevel > 0) { michael@0: DebuggerServer.xpcInspector.exitNestedEventLoop(); michael@0: } michael@0: do_throw("head_dbg.js got console message: " + string + "\n"); michael@0: } michael@0: }; michael@0: michael@0: let consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService); michael@0: consoleService.registerListener(listener); michael@0: michael@0: function check_except(func) michael@0: { michael@0: try { michael@0: func(); michael@0: } catch (e) { michael@0: do_check_true(true); michael@0: return; michael@0: } michael@0: dump("Should have thrown an exception: " + func.toString()); michael@0: do_check_true(false); michael@0: } michael@0: michael@0: function testGlobal(aName) { michael@0: let systemPrincipal = Cc["@mozilla.org/systemprincipal;1"] michael@0: .createInstance(Ci.nsIPrincipal); michael@0: michael@0: let sandbox = Cu.Sandbox(systemPrincipal); michael@0: sandbox.__name = aName; michael@0: return sandbox; michael@0: } michael@0: michael@0: function addTestGlobal(aName) michael@0: { michael@0: let global = testGlobal(aName); michael@0: DebuggerServer.addTestGlobal(global); michael@0: return global; michael@0: } michael@0: michael@0: // List the DebuggerClient |aClient|'s tabs, look for one whose title is michael@0: // |aTitle|, and apply |aCallback| to the packet's entry for that tab. michael@0: function getTestTab(aClient, aTitle, aCallback) { michael@0: aClient.listTabs(function (aResponse) { michael@0: for (let tab of aResponse.tabs) { michael@0: if (tab.title === aTitle) { michael@0: aCallback(tab); michael@0: return; michael@0: } michael@0: } michael@0: aCallback(null); michael@0: }); michael@0: } michael@0: michael@0: // Attach to |aClient|'s tab whose title is |aTitle|; pass |aCallback| the michael@0: // response packet and a TabClient instance referring to that tab. michael@0: function attachTestTab(aClient, aTitle, aCallback) { michael@0: getTestTab(aClient, aTitle, function (aTab) { michael@0: aClient.attachTab(aTab.actor, aCallback); michael@0: }); michael@0: } michael@0: michael@0: // Attach to |aClient|'s tab whose title is |aTitle|, and then attach to michael@0: // that tab's thread. Pass |aCallback| the thread attach response packet, a michael@0: // TabClient referring to the tab, and a ThreadClient referring to the michael@0: // thread. michael@0: function attachTestThread(aClient, aTitle, aCallback) { michael@0: attachTestTab(aClient, aTitle, function (aResponse, aTabClient) { michael@0: function onAttach(aResponse, aThreadClient) { michael@0: aCallback(aResponse, aTabClient, aThreadClient); michael@0: } michael@0: aTabClient.attachThread({ useSourceMaps: true }, onAttach); michael@0: }); michael@0: } michael@0: michael@0: // Attach to |aClient|'s tab whose title is |aTitle|, attach to the tab's michael@0: // thread, and then resume it. Pass |aCallback| the thread's response to michael@0: // the 'resume' packet, a TabClient for the tab, and a ThreadClient for the michael@0: // thread. michael@0: function attachTestTabAndResume(aClient, aTitle, aCallback) { michael@0: attachTestThread(aClient, aTitle, function(aResponse, aTabClient, aThreadClient) { michael@0: aThreadClient.resume(function (aResponse) { michael@0: aCallback(aResponse, aTabClient, aThreadClient); michael@0: }); michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Initialize the testing debugger server. michael@0: */ michael@0: function initTestDebuggerServer() michael@0: { michael@0: DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/root.js"); michael@0: DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/script.js"); michael@0: DebuggerServer.addActors("resource://test/testactors.js"); michael@0: // Allow incoming connections. michael@0: DebuggerServer.init(function () { return true; }); michael@0: } michael@0: michael@0: function initTestTracerServer() michael@0: { michael@0: DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/root.js"); michael@0: DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/script.js"); michael@0: DebuggerServer.addActors("resource://test/testactors.js"); michael@0: DebuggerServer.registerModule("devtools/server/actors/tracer"); michael@0: // Allow incoming connections. michael@0: DebuggerServer.init(function () { return true; }); michael@0: } michael@0: michael@0: function finishClient(aClient) michael@0: { michael@0: aClient.close(function() { michael@0: do_test_finished(); michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Takes a relative file path and returns the absolute file url for it. michael@0: */ michael@0: function getFileUrl(aName, aAllowMissing=false) { michael@0: let file = do_get_file(aName, aAllowMissing); michael@0: return Services.io.newFileURI(file).spec; michael@0: } michael@0: michael@0: /** michael@0: * Returns the full path of the file with the specified name in a michael@0: * platform-independent and URL-like form. michael@0: */ michael@0: function getFilePath(aName, aAllowMissing=false) michael@0: { michael@0: let file = do_get_file(aName, aAllowMissing); michael@0: let path = Services.io.newFileURI(file).spec; michael@0: let filePrePath = "file://"; michael@0: if ("nsILocalFileWin" in Ci && michael@0: file instanceof Ci.nsILocalFileWin) { michael@0: filePrePath += "/"; michael@0: } michael@0: return path.slice(filePrePath.length); michael@0: } michael@0: michael@0: Cu.import("resource://gre/modules/NetUtil.jsm"); michael@0: michael@0: /** michael@0: * Returns the full text contents of the given file. michael@0: */ michael@0: function readFile(aFileName) { michael@0: let f = do_get_file(aFileName); michael@0: let s = Cc["@mozilla.org/network/file-input-stream;1"] michael@0: .createInstance(Ci.nsIFileInputStream); michael@0: s.init(f, -1, -1, false); michael@0: try { michael@0: return NetUtil.readInputStreamToString(s, s.available()); michael@0: } finally { michael@0: s.close(); michael@0: } michael@0: } michael@0: michael@0: function writeFile(aFileName, aContent) { michael@0: let file = do_get_file(aFileName, true); michael@0: let stream = Cc["@mozilla.org/network/file-output-stream;1"] michael@0: .createInstance(Ci.nsIFileOutputStream); michael@0: stream.init(file, -1, -1, 0); michael@0: try { michael@0: do { michael@0: let numWritten = stream.write(aContent, aContent.length); michael@0: aContent = aContent.slice(numWritten); michael@0: } while (aContent.length > 0); michael@0: } finally { michael@0: stream.close(); michael@0: } michael@0: } michael@0: michael@0: function connectPipeTracing() { michael@0: return new TracingTransport(DebuggerServer.connectPipe()); michael@0: } michael@0: michael@0: function TracingTransport(childTransport) { michael@0: this.hooks = null; michael@0: this.child = childTransport; michael@0: this.child.hooks = this; michael@0: michael@0: this.expectations = []; michael@0: this.packets = []; michael@0: this.checkIndex = 0; michael@0: } michael@0: michael@0: function deepEqual(a, b) { michael@0: if (a === b) michael@0: return true; michael@0: if (typeof a != "object" || typeof b != "object") michael@0: return false; michael@0: if (a === null || b === null) michael@0: return false; michael@0: if (Object.keys(a).length != Object.keys(b).length) michael@0: return false; michael@0: for (let k in a) { michael@0: if (!deepEqual(a[k], b[k])) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: TracingTransport.prototype = { michael@0: // Remove actor names michael@0: normalize: function(packet) { michael@0: return JSON.parse(JSON.stringify(packet, (key, value) => { michael@0: if (key === "to" || key === "from" || key === "actor") { michael@0: return ""; michael@0: } michael@0: return value; michael@0: })); michael@0: }, michael@0: send: function(packet) { michael@0: this.packets.push({ michael@0: type: "sent", michael@0: packet: this.normalize(packet) michael@0: }); michael@0: return this.child.send(packet); michael@0: }, michael@0: close: function() { michael@0: return this.child.close(); michael@0: }, michael@0: ready: function() { michael@0: return this.child.ready(); michael@0: }, michael@0: onPacket: function(packet) { michael@0: this.packets.push({ michael@0: type: "received", michael@0: packet: this.normalize(packet) michael@0: }); michael@0: this.hooks.onPacket(packet); michael@0: }, michael@0: onClosed: function() { michael@0: this.hooks.onClosed(); michael@0: }, michael@0: michael@0: expectSend: function(expected) { michael@0: let packet = this.packets[this.checkIndex++]; michael@0: do_check_eq(packet.type, "sent"); michael@0: do_check_true(deepEqual(packet.packet, this.normalize(expected))); michael@0: }, michael@0: michael@0: expectReceive: function(expected) { michael@0: let packet = this.packets[this.checkIndex++]; michael@0: do_check_eq(packet.type, "received"); michael@0: do_check_true(deepEqual(packet.packet, this.normalize(expected))); michael@0: }, michael@0: michael@0: // Write your tests, call dumpLog at the end, inspect the output, michael@0: // then sprinkle the calls through the right places in your test. michael@0: dumpLog: function() { michael@0: for (let entry of this.packets) { michael@0: if (entry.type === "sent") { michael@0: dump("trace.expectSend(" + entry.packet + ");\n"); michael@0: } else { michael@0: dump("trace.expectReceive(" + entry.packet + ");\n"); michael@0: } michael@0: } michael@0: } michael@0: }; michael@0: michael@0: function StubTransport() { } michael@0: StubTransport.prototype.ready = function () {}; michael@0: StubTransport.prototype.send = function () {}; michael@0: StubTransport.prototype.close = function () {}; michael@0: michael@0: function executeSoon(aFunc) { michael@0: Services.tm.mainThread.dispatch({ michael@0: run: DevToolsUtils.makeInfallible(aFunc) michael@0: }, Ci.nsIThread.DISPATCH_NORMAL); michael@0: }