/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; const Cr = Components.results; Cu.import("resource://gre/modules/Services.jsm"); // Always log packets when running tests. runxpcshelltests.py will throw // the output away anyway, unless you give it the --verbose flag. Services.prefs.setBoolPref("devtools.debugger.log", true); // Enable remote debugging for the relevant tests. Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true); Cu.import("resource://gre/modules/devtools/dbg-server.jsm"); Cu.import("resource://gre/modules/devtools/dbg-client.jsm"); Cu.import("resource://gre/modules/devtools/Loader.jsm"); function testExceptionHook(ex) { try { do_report_unexpected_exception(ex); } catch(ex) { return {throw: ex} } } // 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. let errorCount = 0; let listener = { observe: function (aMessage) { errorCount++; var shouldThrow = true; 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; shouldThrow = !aMessage.flags; } 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 = ""; } } // Make sure we exit all nested event loops so that the test can finish. while (DebuggerServer.xpcInspector.eventLoopNestLevel > 0) { DebuggerServer.xpcInspector.exitNestedEventLoop(); } if (shouldThrow) do_throw("head_dbg.js got console message: " + string + "\n"); } }; let 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); Cu.evalInSandbox("this.__name = '" + aName + "'", sandbox); 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) { aClient.attachThread(aResponse.threadActor, function (aResponse, aThreadClient) { aCallback(aResponse, aTabClient, aThreadClient); }, { useSourceMaps: true }); }); } // 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.addActors("resource://gre/modules/devtools/server/actors/script.js"); DebuggerServer.addActors("resource://test/testactors.js"); // Allow incoming connections. DebuggerServer.init(function () { return true; }); } function initSourcesBackwardsCompatDebuggerServer() { DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/root.js"); DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js"); DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/script.js"); DebuggerServer.addActors("resource://test/testcompatactors.js"); DebuggerServer.init(function () { return true; }); } function finishClient(aClient) { aClient.close(function() { do_test_finished(); }); } /** * Takes a relative file path and returns the absolute file url for it. */ function getFileUrl(aName) { let file = do_get_file(aName); 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) { let file = do_get_file(aName); let path = Services.io.newFileURI(file).spec; let filePrePath = "file://"; if ("nsILocalFileWin" in Ci && file instanceof Ci.nsILocalFileWin) { filePrePath += "/"; } return path.slice(filePrePath.length); } Cu.import("resource://gre/modules/NetUtil.jsm"); /** * Returns the full text contents of the given file. */ function readFile(aFileName) { let f = do_get_file(aFileName); let s = Cc["@mozilla.org/network/file-input-stream;1"] .createInstance(Ci.nsIFileInputStream); s.init(f, -1, -1, false); try { return NetUtil.readInputStreamToString(s, s.available()); } finally { s.close(); } } function writeFile(aFileName, aContent) { let file = do_get_file(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(); } } function connectPipeTracing() { return new TracingTransport(DebuggerServer.connectPipe()); } function TracingTransport(childTransport) { this.hooks = null; this.child = childTransport; this.child.hooks = this; this.expectations = []; this.packets = []; this.checkIndex = 0; } function deepEqual(a, b) { if (a === b) return true; if (typeof a != "object" || typeof b != "object") return false; if (a === null || b === null) return false; if (Object.keys(a).length != Object.keys(b).length) return false; for (let k in a) { if (!deepEqual(a[k], b[k])) return false; } return true; } TracingTransport.prototype = { // Remove actor names normalize: function(packet) { return JSON.parse(JSON.stringify(packet, (key, value) => { if (key === "to" || key === "from" || key === "actor") { return ""; } return value; })); }, send: function(packet) { this.packets.push({ type: "sent", packet: this.normalize(packet) }); return this.child.send(packet); }, close: function() { return this.child.close(); }, ready: function() { return this.child.ready(); }, onPacket: function(packet) { this.packets.push({ type: "received", packet: this.normalize(packet) }); this.hooks.onPacket(packet); }, onClosed: function() { this.hooks.onClosed(); }, expectSend: function(expected) { let packet = this.packets[this.checkIndex++]; do_check_eq(packet.type, "sent"); do_check_true(deepEqual(packet.packet, this.normalize(expected))); }, expectReceive: function(expected) { let packet = this.packets[this.checkIndex++]; do_check_eq(packet.type, "received"); do_check_true(deepEqual(packet.packet, this.normalize(expected))); }, // Write your tests, call dumpLog at the end, inspect the output, // then sprinkle the calls through the right places in your test. dumpLog: function() { for (let entry of this.packets) { if (entry.type === "sent") { dump("trace.expectSend(" + entry.packet + ");\n"); } else { dump("trace.expectReceive(" + entry.packet + ");\n"); } } } };