diff options
Diffstat (limited to 'devtools/server/tests/unit')
223 files changed, 16750 insertions, 0 deletions
diff --git a/devtools/server/tests/unit/.eslintrc.js b/devtools/server/tests/unit/.eslintrc.js new file mode 100644 index 000000000..012428019 --- /dev/null +++ b/devtools/server/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/server/tests/unit/addons/web-extension-upgrade/manifest.json b/devtools/server/tests/unit/addons/web-extension-upgrade/manifest.json new file mode 100644 index 000000000..f70b11efd --- /dev/null +++ b/devtools/server/tests/unit/addons/web-extension-upgrade/manifest.json @@ -0,0 +1,10 @@ +{ + "manifest_version": 2, + "name": "Test Addons Actor Upgrade", + "version": "1.0", + "applications": { + "gecko": { + "id": "test-addons-actor@mozilla.org" + } + } +} diff --git a/devtools/server/tests/unit/addons/web-extension/manifest.json b/devtools/server/tests/unit/addons/web-extension/manifest.json new file mode 100644 index 000000000..d120cf3da --- /dev/null +++ b/devtools/server/tests/unit/addons/web-extension/manifest.json @@ -0,0 +1,10 @@ +{ + "manifest_version": 2, + "name": "Test Addons Actor", + "version": "1.0", + "applications": { + "gecko": { + "id": "test-addons-actor@mozilla.org" + } + } +} diff --git a/devtools/server/tests/unit/addons/web-extension2/manifest.json b/devtools/server/tests/unit/addons/web-extension2/manifest.json new file mode 100644 index 000000000..57daae29d --- /dev/null +++ b/devtools/server/tests/unit/addons/web-extension2/manifest.json @@ -0,0 +1,10 @@ +{ + "manifest_version": 2, + "name": "Test Addons Actor 2", + "version": "1.0", + "applications": { + "gecko": { + "id": "test-addons-actor2@mozilla.org" + } + } +} diff --git a/devtools/server/tests/unit/babel_and_browserify_script_with_source_map.js b/devtools/server/tests/unit/babel_and_browserify_script_with_source_map.js new file mode 100644 index 000000000..317eb68ca --- /dev/null +++ b/devtools/server/tests/unit/babel_and_browserify_script_with_source_map.js @@ -0,0 +1,79 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ +"use strict"; + +var add = require("./lib/add"); +var subtract = require("./lib/subtract"); +var upperCase = require("upper-case"); + +(function () { + return add(1, 2); +}); + +},{"./lib/add":2,"./lib/subtract":3,"upper-case":4}],2:[function(require,module,exports){ +"use strict"; + +module.exports = function (a, b) { + return a + b; +}; + +},{}],3:[function(require,module,exports){ +"use strict"; + +module.exports = function (a, b) { + return a - b; +}; + +},{}],4:[function(require,module,exports){ +/** + * Special language-specific overrides. + * + * Source: ftp://ftp.unicode.org/Public/UCD/latest/ucd/SpecialCasing.txt + * + * @type {Object} + */ +var LANGUAGES = { + tr: { + regexp: /[\u0069]/g, + map: { + '\u0069': '\u0130' + } + }, + az: { + regexp: /[\u0069]/g, + map: { + '\u0069': '\u0130' + } + }, + lt: { + regexp: /[\u0069\u006A\u012F]\u0307|\u0069\u0307[\u0300\u0301\u0303]/g, + map: { + '\u0069\u0307': '\u0049', + '\u006A\u0307': '\u004A', + '\u012F\u0307': '\u012E', + '\u0069\u0307\u0300': '\u00CC', + '\u0069\u0307\u0301': '\u00CD', + '\u0069\u0307\u0303': '\u0128' + } + } +} + +/** + * Upper case a string. + * + * @param {String} str + * @return {String} + */ +module.exports = function (str, locale) { + var lang = LANGUAGES[locale] + + str = str == null ? '' : String(str) + + if (lang) { + str = str.replace(lang.regexp, function (m) { return lang.map[m] }) + } + + return str.toUpperCase() +} + +},{}]},{},[1]) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3Vzci9sb2NhbC9saWIvbm9kZV9tb2R1bGVzL2Jyb3dzZXJpZnkvbm9kZV9tb2R1bGVzL2Jyb3dzZXItcGFjay9fcHJlbHVkZS5qcyIsIi9Vc2Vycy9qc2FudGVsbC9EZXYvc291cmNlLW1hcC10ZXN0L2luZGV4LmpzIiwiL1VzZXJzL2pzYW50ZWxsL0Rldi9zb3VyY2UtbWFwLXRlc3QvbGliL2FkZC5qcyIsIi9Vc2Vycy9qc2FudGVsbC9EZXYvc291cmNlLW1hcC10ZXN0L2xpYi9zdWJ0cmFjdC5qcyIsIm5vZGVfbW9kdWxlcy91cHBlci1jYXNlL3VwcGVyLWNhc2UuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztBQ0FBLElBQUksR0FBRyxHQUFHLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQztBQUMvQixJQUFJLFFBQVEsR0FBRyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztBQUN6QyxJQUFJLFNBQVMsR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUM7O0FBRXRDLENBQUM7U0FBTSxHQUFHLENBQUMsQ0FBQyxFQUFDLENBQUMsQ0FBQztFQUFBLENBQUU7Ozs7O0FDSmpCLE1BQU0sQ0FBQyxPQUFPLEdBQUcsVUFBVSxDQUFDLEVBQUUsQ0FBQyxFQUFFO0FBQy9CLFNBQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQztDQUNkLENBQUM7Ozs7O0FDRkYsTUFBTSxDQUFDLE9BQU8sR0FBRyxVQUFVLENBQUMsRUFBRSxDQUFDLEVBQUU7QUFDL0IsU0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0NBQ2QsQ0FBQzs7O0FDRkY7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsInZhciBhZGQgPSByZXF1aXJlKFwiLi9saWIvYWRkXCIpO1xudmFyIHN1YnRyYWN0ID0gcmVxdWlyZShcIi4vbGliL3N1YnRyYWN0XCIpO1xudmFyIHVwcGVyQ2FzZSA9IHJlcXVpcmUoXCJ1cHBlci1jYXNlXCIpO1xuXG4oKCkgPT4gYWRkKDEsMikpO1xuIiwibW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoYSwgYikge1xuICByZXR1cm4gYSArIGI7XG59O1xuIiwibW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoYSwgYikge1xuICByZXR1cm4gYSAtIGI7XG59O1xuIiwiLyoqXG4gKiBTcGVjaWFsIGxhbmd1YWdlLXNwZWNpZmljIG92ZXJyaWRlcy5cbiAqXG4gKiBTb3VyY2U6IGZ0cDovL2Z0cC51bmljb2RlLm9yZy9QdWJsaWMvVUNEL2xhdGVzdC91Y2QvU3BlY2lhbENhc2luZy50eHRcbiAqXG4gKiBAdHlwZSB7T2JqZWN0fVxuICovXG52YXIgTEFOR1VBR0VTID0ge1xuICB0cjoge1xuICAgIHJlZ2V4cDogL1tcXHUwMDY5XS9nLFxuICAgIG1hcDoge1xuICAgICAgJ1xcdTAwNjknOiAnXFx1MDEzMCdcbiAgICB9XG4gIH0sXG4gIGF6OiB7XG4gICAgcmVnZXhwOiAvW1xcdTAwNjldL2csXG4gICAgbWFwOiB7XG4gICAgICAnXFx1MDA2OSc6ICdcXHUwMTMwJ1xuICAgIH1cbiAgfSxcbiAgbHQ6IHtcbiAgICByZWdleHA6IC9bXFx1MDA2OVxcdTAwNkFcXHUwMTJGXVxcdTAzMDd8XFx1MDA2OVxcdTAzMDdbXFx1MDMwMFxcdTAzMDFcXHUwMzAzXS9nLFxuICAgIG1hcDoge1xuICAgICAgJ1xcdTAwNjlcXHUwMzA3JzogJ1xcdTAwNDknLFxuICAgICAgJ1xcdTAwNkFcXHUwMzA3JzogJ1xcdTAwNEEnLFxuICAgICAgJ1xcdTAxMkZcXHUwMzA3JzogJ1xcdTAxMkUnLFxuICAgICAgJ1xcdTAwNjlcXHUwMzA3XFx1MDMwMCc6ICdcXHUwMENDJyxcbiAgICAgICdcXHUwMDY5XFx1MDMwN1xcdTAzMDEnOiAnXFx1MDBDRCcsXG4gICAgICAnXFx1MDA2OVxcdTAzMDdcXHUwMzAzJzogJ1xcdTAxMjgnXG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogVXBwZXIgY2FzZSBhIHN0cmluZy5cbiAqXG4gKiBAcGFyYW0gIHtTdHJpbmd9IHN0clxuICogQHJldHVybiB7U3RyaW5nfVxuICovXG5tb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uIChzdHIsIGxvY2FsZSkge1xuICB2YXIgbGFuZyA9IExBTkdVQUdFU1tsb2NhbGVdXG5cbiAgc3RyID0gc3RyID09IG51bGwgPyAnJyA6IFN0cmluZyhzdHIpXG5cbiAgaWYgKGxhbmcpIHtcbiAgICBzdHIgPSBzdHIucmVwbGFjZShsYW5nLnJlZ2V4cCwgZnVuY3Rpb24gKG0pIHsgcmV0dXJuIGxhbmcubWFwW21dIH0pXG4gIH1cblxuICByZXR1cm4gc3RyLnRvVXBwZXJDYXNlKClcbn1cbiJdfQ== diff --git a/devtools/server/tests/unit/head_dbg.js b/devtools/server/tests/unit/head_dbg.js new file mode 100644 index 000000000..57d0eb8ff --- /dev/null +++ b/devtools/server/tests/unit/head_dbg.js @@ -0,0 +1,862 @@ +/* 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; + +// Populate AppInfo before anything (like the shared loader) accesses +// System.appinfo, which is a lazy getter. +const _appInfo = {}; +Cu.import("resource://testing-common/AppInfo.jsm", _appInfo); +_appInfo.updateAppInfo({ + ID: "devtools@tests.mozilla.org", + name: "devtools-tests", + version: "1", + platformVersion: "42", + crashReporter: true, +}); + +const { require, loader } = Cu.import("resource://devtools/shared/Loader.jsm", {}); +const { worker } = Cu.import("resource://devtools/shared/worker/loader.js", {}); +const promise = require("promise"); +const { Task } = require("devtools/shared/task"); +const { console } = require("resource://gre/modules/Console.jsm"); +const { NetUtil } = require("resource://gre/modules/NetUtil.jsm"); + +const Services = require("Services"); +// 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); + +const DevToolsUtils = require("devtools/shared/DevToolsUtils"); +const { DebuggerServer } = require("devtools/server/main"); +const { DebuggerServer: WorkerDebuggerServer } = worker.require("devtools/server/main"); +const { DebuggerClient, ObjectClient } = require("devtools/shared/client/main"); +const { MemoryFront } = require("devtools/shared/fronts/memory"); + +const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {}); + +const systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal); + +var loadSubScript = Cc[ + "@mozilla.org/moz/jssubscript-loader;1" +].getService(Ci.mozIJSSubScriptLoader).loadSubScript; + +/** + * Initializes any test that needs to work with add-ons. + */ +function startupAddonsManager() { + // Create a directory for extensions. + const profileDir = do_get_profile().clone(); + profileDir.append("extensions"); + + const internalManager = Cc["@mozilla.org/addons/integration;1"] + .getService(Ci.nsIObserver) + .QueryInterface(Ci.nsITimerCallback); + + internalManager.observe(null, "addons-startup", null); +} + +/** + * Create a `run_test` function that runs the given generator in a task after + * having attached to a memory actor. When done, the memory actor is detached + * from, the client is finished, and the test is finished. + * + * @param {GeneratorFunction} testGeneratorFunction + * The generator function is passed (DebuggerClient, MemoryFront) + * arguments. + * + * @returns `run_test` function + */ +function makeMemoryActorTest(testGeneratorFunction) { + const TEST_GLOBAL_NAME = "test_MemoryActor"; + + return function run_test() { + do_test_pending(); + startTestDebuggerServer(TEST_GLOBAL_NAME).then(client => { + DebuggerServer.registerModule("devtools/server/actors/heap-snapshot-file", { + prefix: "heapSnapshotFile", + constructor: "HeapSnapshotFileActor", + type: { global: true } + }); + + getTestTab(client, TEST_GLOBAL_NAME, function (tabForm, rootForm) { + if (!tabForm || !rootForm) { + ok(false, "Could not attach to test tab: " + TEST_GLOBAL_NAME); + return; + } + + Task.spawn(function* () { + try { + const memoryFront = new MemoryFront(client, tabForm, rootForm); + yield memoryFront.attach(); + yield* testGeneratorFunction(client, memoryFront); + yield memoryFront.detach(); + } catch (err) { + DevToolsUtils.reportException("makeMemoryActorTest", err); + ok(false, "Got an error: " + err); + } + + finishClient(client); + }); + }); + }); + }; +} + +/** + * Save as makeMemoryActorTest but attaches the MemoryFront to the MemoryActor + * scoped to the full runtime rather than to a tab. + */ +function makeFullRuntimeMemoryActorTest(testGeneratorFunction) { + return function run_test() { + do_test_pending(); + startTestDebuggerServer("test_MemoryActor").then(client => { + DebuggerServer.registerModule("devtools/server/actors/heap-snapshot-file", { + prefix: "heapSnapshotFile", + constructor: "HeapSnapshotFileActor", + type: { global: true } + }); + + getChromeActors(client).then(function (form) { + if (!form) { + ok(false, "Could not attach to chrome actors"); + return; + } + + Task.spawn(function* () { + try { + const rootForm = yield listTabs(client); + const memoryFront = new MemoryFront(client, form, rootForm); + yield memoryFront.attach(); + yield* testGeneratorFunction(client, memoryFront); + yield memoryFront.detach(); + } catch (err) { + DevToolsUtils.reportException("makeMemoryActorTest", err); + ok(false, "Got an error: " + err); + } + + finishClient(client); + }); + }); + }); + }; +} + +function createTestGlobal(name) { + let sandbox = Cu.Sandbox( + Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal) + ); + sandbox.__name = name; + return sandbox; +} + +function connect(client) { + dump("Connecting client.\n"); + return client.connect(); +} + +function close(client) { + dump("Closing client.\n"); + return client.close(); +} + +function listTabs(client) { + dump("Listing tabs.\n"); + return client.listTabs(); +} + +function findTab(tabs, title) { + dump("Finding tab with title '" + title + "'.\n"); + for (let tab of tabs) { + if (tab.title === title) { + return tab; + } + } + return null; +} + +function attachTab(client, tab) { + dump("Attaching to tab with title '" + tab.title + "'.\n"); + return client.attachTab(tab.actor); +} + +function waitForNewSource(threadClient, url) { + dump("Waiting for new source with url '" + url + "'.\n"); + return waitForEvent(threadClient, "newSource", function (packet) { + return packet.source.url === url; + }); +} + +function attachThread(tabClient, options = {}) { + dump("Attaching to thread.\n"); + return tabClient.attachThread(options); +} + +function resume(threadClient) { + dump("Resuming thread.\n"); + return threadClient.resume(); +} + +function getSources(threadClient) { + dump("Getting sources.\n"); + return threadClient.getSources(); +} + +function findSource(sources, url) { + dump("Finding source with url '" + url + "'.\n"); + for (let source of sources) { + if (source.url === url) { + return source; + } + } + return null; +} + +function waitForPause(threadClient) { + dump("Waiting for pause.\n"); + return waitForEvent(threadClient, "paused"); +} + +function setBreakpoint(sourceClient, location) { + dump("Setting breakpoint.\n"); + return sourceClient.setBreakpoint(location); +} + +function dumpn(msg) { + dump("DBG-TEST: " + msg + "\n"); +} + +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) { + try { + 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); + dumpn(aMessage.sourceName + ":" + aMessage.lineNumber + ": " + + scriptErrorFlagsToKind(aMessage.flags) + ": " + + aMessage.errorMessage); + 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 + && DebuggerServer.xpcInspector + && DebuggerServer.xpcInspector.eventLoopNestLevel > 0) { + DebuggerServer.xpcInspector.exitNestedEventLoop(); + } + + // In the world before bug 997440, exceptions were getting lost because of + // the arbitrary JSContext being used in nsXPCWrappedJSClass::CallMethod. + // In the new world, the wanderers have returned. However, because of the, + // currently very-broken, exception reporting machinery in + // XPCWrappedJSClass these get reported as errors to the console, even if + // there's actually JS on the stack above that will catch them. If we + // throw an error here because of them our tests start failing. So, we'll + // just dump the message to the logs instead, to make sure the information + // isn't lost. + dumpn("head_dbg.js observed a console message: " + string); + } catch (_) { + // Swallow everything to avoid console reentrancy errors. We did our best + // to log above, but apparently that didn't cut it. + } + } +}; + +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; + } + dumpn("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, aServer = DebuggerServer) +{ + let global = testGlobal(aName); + aServer.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, aResponse); + 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 (aTabResponse, aTabClient) { + function onAttach(aResponse, aThreadClient) { + aCallback(aResponse, aTabClient, aThreadClient, aTabResponse); + } + aTabClient.attachThread({ + useSourceMaps: true, + autoBlackBox: 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 = () => {}) { + return new Promise((resolve, reject) => { + attachTestThread(aClient, aTitle, function (aResponse, aTabClient, aThreadClient) { + aThreadClient.resume(function (aResponse) { + aCallback(aResponse, aTabClient, aThreadClient); + resolve([aResponse, aTabClient, aThreadClient]); + }); + }); + }); +} + +/** + * Initialize the testing debugger server. + */ +function initTestDebuggerServer(aServer = DebuggerServer) +{ + aServer.registerModule("xpcshell-test/testactors"); + // Allow incoming connections. + aServer.init(function () { return true; }); +} + +/** + * Initialize the testing debugger server with a tab whose title is |title|. + */ +function startTestDebuggerServer(title, server = DebuggerServer) { + initTestDebuggerServer(server); + addTestGlobal(title); + DebuggerServer.addTabActors(); + + let transport = DebuggerServer.connectPipe(); + let client = new DebuggerClient(transport); + + return connect(client).then(() => client); +} + +function finishClient(aClient) +{ + aClient.close(function () { + DebuggerServer.destroy(); + do_test_finished(); + }); +} + +// Create a server, connect to it and fetch tab actors for the parent process; +// pass |aCallback| the debugger client and tab actor form with all actor IDs. +function get_chrome_actors(callback) +{ + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + DebuggerServer.allowChromeProcess = true; + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + client.connect() + .then(() => client.getProcess()) + .then(response => { + callback(client, response.form); + }); +} + +function getChromeActors(client, server = DebuggerServer) { + server.allowChromeProcess = true; + return client.getProcess().then(response => response.form); +} + +/** + * 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, aUsePlatformPathSeparator = 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 += "/"; + } + + path = path.slice(filePrePath.length); + + if (aUsePlatformPathSeparator && path.match(/^\w:/)) { + path = path.replace(/\//g, "\\"); + } + + return path; +} + +/** + * 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; +} + +TracingTransport.prototype = { + // Remove actor names + normalize: function (packet) { + return JSON.parse(JSON.stringify(packet, (key, value) => { + if (key === "to" || key === "from" || key === "actor") { + return "<actorid>"; + } + 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"); + deepEqual(packet.packet, this.normalize(expected)); + }, + + expectReceive: function (expected) { + let packet = this.packets[this.checkIndex++]; + do_check_eq(packet.type, "received"); + 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") { + dumpn("trace.expectSend(" + entry.packet + ");"); + } else { + dumpn("trace.expectReceive(" + entry.packet + ");"); + } + } + } +}; + +function StubTransport() { } +StubTransport.prototype.ready = function () {}; +StubTransport.prototype.send = function () {}; +StubTransport.prototype.close = function () {}; + +function executeSoon(aFunc) { + Services.tm.mainThread.dispatch({ + run: DevToolsUtils.makeInfallible(aFunc) + }, Ci.nsIThread.DISPATCH_NORMAL); +} + +// The do_check_* family of functions expect their last argument to be an +// optional stack object. Unfortunately, most tests actually pass a in a string +// containing an error message instead, which causes error reporting to break if +// strict warnings as errors is turned on. To avoid this, we wrap these +// functions here below to ensure the correct number of arguments is passed. +// +// TODO: Remove this once bug 906232 is resolved +// +var do_check_true_old = do_check_true; +var do_check_true = function (condition) { + do_check_true_old(condition); +}; + +var do_check_false_old = do_check_false; +var do_check_false = function (condition) { + do_check_false_old(condition); +}; + +var do_check_eq_old = do_check_eq; +var do_check_eq = function (left, right) { + do_check_eq_old(left, right); +}; + +var do_check_neq_old = do_check_neq; +var do_check_neq = function (left, right) { + do_check_neq_old(left, right); +}; + +var do_check_matches_old = do_check_matches; +var do_check_matches = function (pattern, value) { + do_check_matches_old(pattern, value); +}; + +// Create async version of the object where calling each method +// is equivalent of calling it with asyncall. Mainly useful for +// destructuring objects with methods that take callbacks. +const Async = target => new Proxy(target, Async); +Async.get = (target, name) => + typeof (target[name]) === "function" ? asyncall.bind(null, target[name], target) : + target[name]; + +// Calls async function that takes callback and errorback and returns +// returns promise representing result. +const asyncall = (fn, self, ...args) => + new Promise((...etc) => fn.call(self, ...args, ...etc)); + +const Test = task => () => { + add_task(task); + run_next_test(); +}; + +const assert = do_check_true; + +/** + * Create a promise that is resolved on the next occurence of the given event. + * + * @param DebuggerClient client + * @param String event + * @param Function predicate + * @returns Promise + */ +function waitForEvent(client, type, predicate) { + return new Promise(function (resolve) { + function listener(type, packet) { + if (!predicate(packet)) { + return; + } + client.removeListener(listener); + resolve(packet); + } + + if (predicate) { + client.addListener(type, listener); + } else { + client.addOneTimeListener(type, function (type, packet) { + resolve(packet); + }); + } + }); +} + +/** + * Execute the action on the next tick and return a promise that is resolved on + * the next pause. + * + * When using promises and Task.jsm, we often want to do an action that causes a + * pause and continue the task once the pause has ocurred. Unfortunately, if we + * do the action that causes the pause within the task's current tick we will + * pause before we have a chance to yield the promise that waits for the pause + * and we enter a dead lock. The solution is to create the promise that waits + * for the pause, schedule the action to run on the next tick of the event loop, + * and finally yield the promise. + * + * @param Function action + * @param DebuggerClient client + * @returns Promise + */ +function executeOnNextTickAndWaitForPause(action, client) { + const paused = waitForPause(client); + executeSoon(action); + return paused; +} + +/** + * Interrupt JS execution for the specified thread. + * + * @param ThreadClient threadClient + * @returns Promise + */ +function interrupt(threadClient) { + dumpn("Interrupting."); + return threadClient.interrupt(); +} + +/** + * Resume JS execution for the specified thread and then wait for the next pause + * event. + * + * @param DebuggerClient client + * @param ThreadClient threadClient + * @returns Promise + */ +function resumeAndWaitForPause(client, threadClient) { + const paused = waitForPause(client); + return resume(threadClient).then(() => paused); +} + +/** + * Resume JS execution for a single step and wait for the pause after the step + * has been taken. + * + * @param DebuggerClient client + * @param ThreadClient threadClient + * @returns Promise + */ +function stepIn(client, threadClient) { + dumpn("Stepping in."); + const paused = waitForPause(client); + return threadClient.stepIn() + .then(() => paused); +} + +/** + * Resume JS execution for a step over and wait for the pause after the step + * has been taken. + * + * @param DebuggerClient client + * @param ThreadClient threadClient + * @returns Promise + */ +function stepOver(client, threadClient) { + dumpn("Stepping over."); + return threadClient.stepOver() + .then(() => waitForPause(client)); +} + +/** + * Get the list of `count` frames currently on stack, starting at the index + * `first` for the specified thread. + * + * @param ThreadClient threadClient + * @param Number first + * @param Number count + * @returns Promise + */ +function getFrames(threadClient, first, count) { + dumpn("Getting frames."); + return threadClient.getFrames(first, count); +} + +/** + * Black box the specified source. + * + * @param SourceClient sourceClient + * @returns Promise + */ +function blackBox(sourceClient) { + dumpn("Black boxing source: " + sourceClient.actor); + return sourceClient.blackBox(); +} + +/** + * Stop black boxing the specified source. + * + * @param SourceClient sourceClient + * @returns Promise + */ +function unBlackBox(sourceClient) { + dumpn("Un-black boxing source: " + sourceClient.actor); + return sourceClient.unblackBox(); +} + +/** + * Perform a "source" RDP request with the given SourceClient to get the source + * content and content type. + * + * @param SourceClient sourceClient + * @returns Promise + */ +function getSourceContent(sourceClient) { + dumpn("Getting source content for " + sourceClient.actor); + return sourceClient.source(); +} + +/** + * Get a source at the specified url. + * + * @param ThreadClient threadClient + * @param string url + * @returns Promise<SourceClient> + */ +function getSource(threadClient, url) { + let deferred = promise.defer(); + threadClient.getSources((res) => { + let source = res.sources.filter(function (s) { + return s.url === url; + }); + if (source.length) { + deferred.resolve(threadClient.source(source[0])); + } + else { + deferred.reject(new Error("source not found")); + } + }); + return deferred.promise; +} + +/** + * Do a fake reload which clears the thread debugger + * + * @param TabClient tabClient + * @returns Promise<response> + */ +function reload(tabClient) { + let deferred = promise.defer(); + tabClient._reload({}, deferred.resolve); + return deferred.promise; +} + +/** + * Returns an array of stack location strings given a thread and a sample. + * + * @param object thread + * @param object sample + * @returns object + */ +function getInflatedStackLocations(thread, sample) { + let stackTable = thread.stackTable; + let frameTable = thread.frameTable; + let stringTable = thread.stringTable; + let SAMPLE_STACK_SLOT = thread.samples.schema.stack; + let STACK_PREFIX_SLOT = stackTable.schema.prefix; + let STACK_FRAME_SLOT = stackTable.schema.frame; + let FRAME_LOCATION_SLOT = frameTable.schema.location; + + // Build the stack from the raw data and accumulate the locations in + // an array. + let stackIndex = sample[SAMPLE_STACK_SLOT]; + let locations = []; + while (stackIndex !== null) { + let stackEntry = stackTable.data[stackIndex]; + let frame = frameTable.data[stackEntry[STACK_FRAME_SLOT]]; + locations.push(stringTable[frame[FRAME_LOCATION_SLOT]]); + stackIndex = stackEntry[STACK_PREFIX_SLOT]; + } + + // The profiler tree is inverted, so reverse the array. + return locations.reverse(); +} diff --git a/devtools/server/tests/unit/hello-actor.js b/devtools/server/tests/unit/hello-actor.js new file mode 100644 index 000000000..6d7427f63 --- /dev/null +++ b/devtools/server/tests/unit/hello-actor.js @@ -0,0 +1,18 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const protocol = require("devtools/shared/protocol"); + +const helloSpec = protocol.generateActorSpec({ + typeName: "helloActor", + + methods: { + hello: {} + } +}); + +var HelloActor = protocol.ActorClassWithSpec(helloSpec, { + hello: function () { + return; + } +}); diff --git a/devtools/server/tests/unit/post_init_global_actors.js b/devtools/server/tests/unit/post_init_global_actors.js new file mode 100644 index 000000000..0035e8914 --- /dev/null +++ b/devtools/server/tests/unit/post_init_global_actors.js @@ -0,0 +1,17 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function PostInitGlobalActor(aConnection) {} + +PostInitGlobalActor.prototype = { + actorPrefix: "postInitGlobal", + onPing: function onPing(aRequest) { + return { message: "pong" }; + }, +}; + +PostInitGlobalActor.prototype.requestTypes = { + "ping": PostInitGlobalActor.prototype.onPing, +}; + +DebuggerServer.addGlobalActor(PostInitGlobalActor, "postInitGlobalActor"); diff --git a/devtools/server/tests/unit/post_init_tab_actors.js b/devtools/server/tests/unit/post_init_tab_actors.js new file mode 100644 index 000000000..9b9ddf111 --- /dev/null +++ b/devtools/server/tests/unit/post_init_tab_actors.js @@ -0,0 +1,17 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function PostInitTabActor(aConnection) {} + +PostInitTabActor.prototype = { + actorPostfix: "postInitTab", + onPing: function onPing(aRequest) { + return { message: "pong" }; + }, +}; + +PostInitTabActor.prototype.requestTypes = { + "ping": PostInitTabActor.prototype.onPing, +}; + +DebuggerServer.addGlobalActor(PostInitTabActor, "postInitTabActor"); diff --git a/devtools/server/tests/unit/pre_init_global_actors.js b/devtools/server/tests/unit/pre_init_global_actors.js new file mode 100644 index 000000000..bd4284a70 --- /dev/null +++ b/devtools/server/tests/unit/pre_init_global_actors.js @@ -0,0 +1,17 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function PreInitGlobalActor(aConnection) {} + +PreInitGlobalActor.prototype = { + actorPrefix: "preInitGlobal", + onPing: function onPing(aRequest) { + return { message: "pong" }; + }, +}; + +PreInitGlobalActor.prototype.requestTypes = { + "ping": PreInitGlobalActor.prototype.onPing, +}; + +DebuggerServer.addGlobalActor(PreInitGlobalActor, "preInitGlobalActor"); diff --git a/devtools/server/tests/unit/pre_init_tab_actors.js b/devtools/server/tests/unit/pre_init_tab_actors.js new file mode 100644 index 000000000..628f0fb2f --- /dev/null +++ b/devtools/server/tests/unit/pre_init_tab_actors.js @@ -0,0 +1,17 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function PreInitTabActor(aConnection) {} + +PreInitTabActor.prototype = { + actorPrefix: "preInitTab", + onPing: function onPing(aRequest) { + return { message: "pong" }; + }, +}; + +PreInitTabActor.prototype.requestTypes = { + "ping": PreInitTabActor.prototype.onPing, +}; + +DebuggerServer.addGlobalActor(PreInitTabActor, "preInitTabActor"); diff --git a/devtools/server/tests/unit/registertestactors-01.js b/devtools/server/tests/unit/registertestactors-01.js new file mode 100644 index 000000000..92f511225 --- /dev/null +++ b/devtools/server/tests/unit/registertestactors-01.js @@ -0,0 +1,15 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function Actor() {} + +exports.register = function (handle) { + handle.addTabActor(Actor, "registeredActor1"); + handle.addGlobalActor(Actor, "registeredActor1"); +}; + +exports.unregister = function (handle) { + handle.removeTabActor(Actor); + handle.removeGlobalActor(Actor); +}; + diff --git a/devtools/server/tests/unit/registertestactors-02.js b/devtools/server/tests/unit/registertestactors-02.js new file mode 100644 index 000000000..54f78e508 --- /dev/null +++ b/devtools/server/tests/unit/registertestactors-02.js @@ -0,0 +1,15 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function Actor() {} + +exports.register = function (handle) { + handle.addGlobalActor(Actor, "registeredActor2"); + handle.addTabActor(Actor, "registeredActor2"); +}; + +exports.unregister = function (handle) { + handle.removeTabActor(Actor); + handle.removeGlobalActor(Actor); +}; + diff --git a/devtools/server/tests/unit/registertestactors-03.js b/devtools/server/tests/unit/registertestactors-03.js new file mode 100644 index 000000000..8d42fdbd8 --- /dev/null +++ b/devtools/server/tests/unit/registertestactors-03.js @@ -0,0 +1,40 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var {method, RetVal, Actor, ActorClassWithSpec, Front, FrontClassWithSpec, + generateActorSpec} = require("devtools/shared/protocol"); +var Services = require("Services"); + +const lazySpec = generateActorSpec({ + typeName: "lazy", + + methods: { + hello: { + response: { str: RetVal("string") } + } + } +}); + +exports.LazyActor = ActorClassWithSpec(lazySpec, { + initialize: function (conn, id) { + Actor.prototype.initialize.call(this, conn); + + Services.obs.notifyObservers(null, "actor", "instantiated"); + }, + + hello: function (str) { + return "world"; + } +}); + +Services.obs.notifyObservers(null, "actor", "loaded"); + +exports.LazyFront = FrontClassWithSpec(lazySpec, { + initialize: function (client, form) { + Front.prototype.initialize.call(this, client); + this.actorID = form.lazyActor; + + client.addActorPool(this); + this.manage(this); + } +}); diff --git a/devtools/server/tests/unit/setBreakpoint-on-column-in-gcd-script.js b/devtools/server/tests/unit/setBreakpoint-on-column-in-gcd-script.js new file mode 100644 index 000000000..575915c4f --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-column-in-gcd-script.js @@ -0,0 +1,7 @@ +"use strict"; + +function f() {} + +(function () { + var a = 1; var b = 2; var c = 3; +})(); diff --git a/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-at-end-of-line.js b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-at-end-of-line.js new file mode 100644 index 000000000..4c1b52eb4 --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-at-end-of-line.js @@ -0,0 +1,6 @@ +"use strict"; + +function f() { + var a = 1; var b = 2; + var c = 3; +} diff --git a/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-in-gcd-script.js b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-in-gcd-script.js new file mode 100644 index 000000000..adce39193 --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-in-gcd-script.js @@ -0,0 +1,7 @@ +"use strict"; + +function f() {} + +(function () { + var a = 1; var c = 3; +})(); diff --git a/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets.js b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets.js new file mode 100644 index 000000000..5faefc3c8 --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets.js @@ -0,0 +1,5 @@ +"use strict"; + +function f() { + var a = 1; var c = 3; +} diff --git a/devtools/server/tests/unit/setBreakpoint-on-column.js b/devtools/server/tests/unit/setBreakpoint-on-column.js new file mode 100644 index 000000000..d92231e65 --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-column.js @@ -0,0 +1,5 @@ +"use strict"; + +function f() { + var a = 1; var b = 2; var c = 3; +} diff --git a/devtools/server/tests/unit/setBreakpoint-on-line-in-gcd-script.js b/devtools/server/tests/unit/setBreakpoint-on-line-in-gcd-script.js new file mode 100644 index 000000000..fb96be8ab --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-line-in-gcd-script.js @@ -0,0 +1,9 @@ +"use strict"; + +function f() {} + +(function () { + var a = 1; + var b = 2; + var c = 3; +})(); diff --git a/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-offsets.js b/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-offsets.js new file mode 100644 index 000000000..b30ebb504 --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-offsets.js @@ -0,0 +1,7 @@ +"use strict"; + +function f() { + for (var i = 0; i < 1; ++i) { + ; + } +} diff --git a/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-statements.js b/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-statements.js new file mode 100644 index 000000000..d92231e65 --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-statements.js @@ -0,0 +1,5 @@ +"use strict"; + +function f() { + var a = 1; var b = 2; var c = 3; +} diff --git a/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets-in-gcd-script.js b/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets-in-gcd-script.js new file mode 100644 index 000000000..b03d40079 --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets-in-gcd-script.js @@ -0,0 +1,9 @@ +"use strict"; + +function f() {} + +(function () { + var a = 1; + + var c = 3; +})(); diff --git a/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets.js b/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets.js new file mode 100644 index 000000000..1268cf8db --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets.js @@ -0,0 +1,7 @@ +"use strict"; + +function f() { + var a = 1; + + var c = 3; +} diff --git a/devtools/server/tests/unit/setBreakpoint-on-line.js b/devtools/server/tests/unit/setBreakpoint-on-line.js new file mode 100644 index 000000000..1b15e2a5e --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-line.js @@ -0,0 +1,7 @@ +"use strict"; + +function f() { + var a = 1; + var b = 2; + var c = 3; +} diff --git a/devtools/server/tests/unit/source-map-data/sourcemapped.coffee b/devtools/server/tests/unit/source-map-data/sourcemapped.coffee new file mode 100644 index 000000000..73a400a21 --- /dev/null +++ b/devtools/server/tests/unit/source-map-data/sourcemapped.coffee @@ -0,0 +1,6 @@ +foo = (n) -> + return "foo" + i for i in [0...n] + +[first, second, third] = foo(3) + +debugger
\ No newline at end of file diff --git a/devtools/server/tests/unit/source-map-data/sourcemapped.map b/devtools/server/tests/unit/source-map-data/sourcemapped.map new file mode 100644 index 000000000..dcee3c33c --- /dev/null +++ b/devtools/server/tests/unit/source-map-data/sourcemapped.map @@ -0,0 +1,10 @@ +{ + "version": 3, + "file": "sourcemapped.js", + "sourceRoot": "", + "sources": [ + "sourcemapped.coffee" + ], + "names": [], + "mappings": ";AAAA;CAAA,KAAA,yBAAA;CAAA;CAAA,CAAA,CAAA,MAAO;CACL,IAAA,GAAA;AAAA,CAAA,EAAA,MAA0B,qDAA1B;CAAA,EAAe,EAAR,QAAA;CAAP,IADI;CAAN,EAAM;;CAAN,CAGA,CAAyB,IAAA;;CAEzB,UALA;CAAA" +}
\ No newline at end of file diff --git a/devtools/server/tests/unit/sourcemapped.js b/devtools/server/tests/unit/sourcemapped.js new file mode 100644 index 000000000..94d130903 --- /dev/null +++ b/devtools/server/tests/unit/sourcemapped.js @@ -0,0 +1,16 @@ +// Generated by CoffeeScript 1.6.1 +(function () { + var first, foo, second, third, _ref; + + foo = function (n) { + var i, _i; + for (i = _i = 0; 0 <= n ? _i < n : _i > n; i = 0 <= n ? ++_i : --_i) { + return "foo" + i; + } + }; + + _ref = foo(3), first = _ref[0], second = _ref[1], third = _ref[2]; + + debugger; + +}).call(this); diff --git a/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_01.js b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_01.js new file mode 100644 index 000000000..2fd38d49c --- /dev/null +++ b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_01.js @@ -0,0 +1,18 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that we can tell the memory actor to take a heap snapshot over the RDP +// and then create a HeapSnapshot instance from the resulting file. + +const { OS } = require("resource://gre/modules/osfile.jsm"); + +const run_test = makeMemoryActorTest(function* (client, memoryFront) { + const snapshotFilePath = yield memoryFront.saveHeapSnapshot(); + ok(!!(yield OS.File.stat(snapshotFilePath)), + "Should have the heap snapshot file"); + const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(snapshotFilePath); + ok(snapshot instanceof HeapSnapshot, + "And we should be able to read a HeapSnapshot instance from the file"); +}); diff --git a/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_02.js b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_02.js new file mode 100644 index 000000000..564ec0d06 --- /dev/null +++ b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_02.js @@ -0,0 +1,20 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that we can properly stream heap snapshot files over the RDP as bulk +// data. + +const { OS } = require("resource://gre/modules/osfile.jsm"); + +const run_test = makeMemoryActorTest(function* (client, memoryFront) { + const snapshotFilePath = yield memoryFront.saveHeapSnapshot({ + forceCopy: true + }); + ok(!!(yield OS.File.stat(snapshotFilePath)), + "Should have the heap snapshot file"); + const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(snapshotFilePath); + ok(snapshot instanceof HeapSnapshot, + "And we should be able to read a HeapSnapshot instance from the file"); +}); diff --git a/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_03.js b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_03.js new file mode 100644 index 000000000..e9e81594d --- /dev/null +++ b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_03.js @@ -0,0 +1,18 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that we can save full runtime heap snapshots when attached to the +// ChromeActor or a ChildProcessActor. + +const { OS } = require("resource://gre/modules/osfile.jsm"); + +const run_test = makeFullRuntimeMemoryActorTest(function* (client, memoryFront) { + const snapshotFilePath = yield memoryFront.saveHeapSnapshot(); + ok(!!(yield OS.File.stat(snapshotFilePath)), + "Should have the heap snapshot file"); + const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(snapshotFilePath); + ok(snapshot instanceof HeapSnapshot, + "And we should be able to read a HeapSnapshot instance from the file"); +}); diff --git a/devtools/server/tests/unit/test_actor-registry-actor.js b/devtools/server/tests/unit/test_actor-registry-actor.js new file mode 100644 index 000000000..8b0abfbbb --- /dev/null +++ b/devtools/server/tests/unit/test_actor-registry-actor.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that you can register new actors via the ActorRegistrationActor. + */ + +var gClient; +var gRegistryFront; +var gActorFront; +var gOldPref; + +const { ActorRegistryFront } = require("devtools/shared/fronts/actor-registry"); + +function run_test() +{ + gOldPref = Services.prefs.getBoolPref("devtools.debugger.forbid-certified-apps"); + Services.prefs.setBoolPref("devtools.debugger.forbid-certified-apps", false); + initTestDebuggerServer(); + DebuggerServer.addBrowserActors(); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(getRegistry); + do_test_pending(); +} + +function getRegistry() { + gClient.listTabs((response) => { + gRegistryFront = ActorRegistryFront(gClient, response); + registerNewActor(); + }); +} + +function registerNewActor() { + let options = { + prefix: "helloActor", + constructor: "HelloActor", + type: { global: true } + }; + + gRegistryFront + .registerActor("resource://test/hello-actor.js", options) + .then(actorFront => gActorFront = actorFront) + .then(talkToNewActor) + .then(null, e => { + DevToolsUtils.reportException("registerNewActor", e); + do_check_true(false); + }); +} + +function talkToNewActor() { + gClient.listTabs(({ helloActor }) => { + do_check_true(!!helloActor); + gClient.request({ + to: helloActor, + type: "hello" + }, response => { + do_check_true(!response.error); + unregisterNewActor(); + }); + }); +} + +function unregisterNewActor() { + gActorFront + .unregister() + .then(testActorIsUnregistered) + .then(null, e => { + DevToolsUtils.reportException("unregisterNewActor", e); + do_check_true(false); + }); +} + +function testActorIsUnregistered() { + gClient.listTabs(({ helloActor }) => { + do_check_true(!helloActor); + + Services.prefs.setBoolPref("devtools.debugger.forbid-certified-apps", gOldPref); + finishClient(gClient); + }); +} diff --git a/devtools/server/tests/unit/test_add_actors.js b/devtools/server/tests/unit/test_add_actors.js new file mode 100644 index 000000000..9b90da724 --- /dev/null +++ b/devtools/server/tests/unit/test_add_actors.js @@ -0,0 +1,107 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gClient; +var gActors; + +/** + * The purpose of these tests is to verify that it's possible to add actors + * both before and after the DebuggerServer has been initialized, so addons + * that add actors don't have to poll the object for its initialization state + * in order to add actors after initialization but rather can add actors anytime + * regardless of the object's state. + */ +function run_test() +{ + DebuggerServer.addActors("resource://test/pre_init_global_actors.js"); + DebuggerServer.addActors("resource://test/pre_init_tab_actors.js"); + + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + DebuggerServer.addActors("resource://test/post_init_global_actors.js"); + DebuggerServer.addActors("resource://test/post_init_tab_actors.js"); + + add_test(init); + add_test(test_pre_init_global_actor); + add_test(test_pre_init_tab_actor); + add_test(test_post_init_global_actor); + add_test(test_post_init_tab_actor); + add_test(test_stable_global_actor_instances); + add_test(close_client); + run_next_test(); +} + +function init() +{ + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect() + .then(() => gClient.listTabs()) + .then(aResponse => { + gActors = aResponse; + run_next_test(); + }); +} + +function test_pre_init_global_actor() +{ + gClient.request({ to: gActors.preInitGlobalActor, type: "ping" }, + function onResponse(aResponse) { + do_check_eq(aResponse.message, "pong"); + run_next_test(); + } + ); +} + +function test_pre_init_tab_actor() +{ + gClient.request({ to: gActors.preInitTabActor, type: "ping" }, + function onResponse(aResponse) { + do_check_eq(aResponse.message, "pong"); + run_next_test(); + } + ); +} + +function test_post_init_global_actor() +{ + gClient.request({ to: gActors.postInitGlobalActor, type: "ping" }, + function onResponse(aResponse) { + do_check_eq(aResponse.message, "pong"); + run_next_test(); + } + ); +} + +function test_post_init_tab_actor() +{ + gClient.request({ to: gActors.postInitTabActor, type: "ping" }, + function onResponse(aResponse) { + do_check_eq(aResponse.message, "pong"); + run_next_test(); + } + ); +} + +// Get the object object, from the server side, for a given actor ID +function getActorInstance(connID, actorID) { + return DebuggerServer._connections[connID].getActor(actorID); +} + +function test_stable_global_actor_instances() +{ + // Consider that there is only one connection, + // and the first one is ours + let connID = Object.keys(DebuggerServer._connections)[0]; + let postInitGlobalActor = getActorInstance(connID, gActors.postInitGlobalActor); + let preInitGlobalActor = getActorInstance(connID, gActors.preInitGlobalActor); + gClient.listTabs(function onListTabs(aResponse) { + do_check_eq(postInitGlobalActor, getActorInstance(connID, aResponse.postInitGlobalActor)); + do_check_eq(preInitGlobalActor, getActorInstance(connID, aResponse.preInitGlobalActor)); + run_next_test(); + }); +} + +function close_client() { + gClient.close().then(() => run_next_test()); +} diff --git a/devtools/server/tests/unit/test_addon_reload.js b/devtools/server/tests/unit/test_addon_reload.js new file mode 100644 index 000000000..0187fa3b3 --- /dev/null +++ b/devtools/server/tests/unit/test_addon_reload.js @@ -0,0 +1,98 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const protocol = require("devtools/shared/protocol"); +const {AddonManager} = require("resource://gre/modules/AddonManager.jsm"); + +startupAddonsManager(); + +function promiseAddonEvent(event) { + return new Promise(resolve => { + let listener = { + [event]: function (...args) { + AddonManager.removeAddonListener(listener); + resolve(args); + } + }; + + AddonManager.addAddonListener(listener); + }); +} + +function* findAddonInRootList(client, addonId) { + const result = yield client.listAddons(); + const addonActor = result.addons.filter(addon => addon.id === addonId)[0]; + ok(addonActor, `Found add-on actor for ${addonId}`); + return addonActor; +} + +function* reloadAddon(client, addonActor) { + // The add-on will be re-installed after a successful reload. + const onInstalled = promiseAddonEvent("onInstalled"); + yield client.request({to: addonActor.actor, type: "reload"}); + yield onInstalled; +} + +function getSupportFile(path) { + const allowMissing = false; + return do_get_file(path, allowMissing); +} + +add_task(function* testReloadExitedAddon() { + const client = yield new Promise(resolve => { + get_chrome_actors(client => resolve(client)); + }); + + // Install our main add-on to trigger reloads on. + const addonFile = getSupportFile("addons/web-extension"); + const installedAddon = yield AddonManager.installTemporaryAddon( + addonFile); + + // Install a decoy add-on. + const addonFile2 = getSupportFile("addons/web-extension2"); + const installedAddon2 = yield AddonManager.installTemporaryAddon( + addonFile2); + + let addonActor = yield findAddonInRootList(client, installedAddon.id); + + yield reloadAddon(client, addonActor); + + // Uninstall the decoy add-on, which should cause its actor to exit. + const onUninstalled = promiseAddonEvent("onUninstalled"); + installedAddon2.uninstall(); + const [uninstalledAddon] = yield onUninstalled; + + // Try to re-list all add-ons after a reload. + // This was throwing an exception because of the exited actor. + const newAddonActor = yield findAddonInRootList(client, installedAddon.id); + equal(newAddonActor.id, addonActor.id); + + // The actor id should be the same after the reload + equal(newAddonActor.actor, addonActor.actor); + + const onAddonListChanged = new Promise((resolve) => { + client.addListener("addonListChanged", function listener() { + client.removeListener("addonListChanged", listener); + resolve(); + }); + }); + + // Install an upgrade version of the first add-on. + const addonUpgradeFile = getSupportFile("addons/web-extension-upgrade"); + const upgradedAddon = yield AddonManager.installTemporaryAddon( + addonUpgradeFile); + + // Waiting for addonListChanged unsolicited event + yield onAddonListChanged; + + // re-list all add-ons after an upgrade. + const upgradedAddonActor = yield findAddonInRootList(client, upgradedAddon.id); + equal(upgradedAddonActor.id, addonActor.id); + // The actor id should be the same after the upgrade. + equal(upgradedAddonActor.actor, addonActor.actor); + + // The addon metadata has been updated. + equal(upgradedAddonActor.name, "Test Addons Actor Upgrade"); + + yield close(client); +}); diff --git a/devtools/server/tests/unit/test_addons_actor.js b/devtools/server/tests/unit/test_addons_actor.js new file mode 100644 index 000000000..1815d43c6 --- /dev/null +++ b/devtools/server/tests/unit/test_addons_actor.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const protocol = require("devtools/shared/protocol"); +const {AddonsActor} = require("devtools/server/actors/addons"); +const {AddonsFront} = require("devtools/shared/fronts/addons"); + +startupAddonsManager(); + +function* connect() { + const client = yield new Promise(resolve => { + get_chrome_actors(client => resolve(client)); + }); + const root = yield listTabs(client); + const addonsActor = root.addonsActor; + ok(addonsActor, "Got AddonsActor instance"); + + const addons = AddonsFront(client, {addonsActor}); + return [client, addons]; +} + +add_task(function* testSuccessfulInstall() { + const [client, addons] = yield connect(); + + const allowMissing = false; + const usePlatformSeparator = true; + const addonPath = getFilePath("addons/web-extension", + allowMissing, usePlatformSeparator); + const installedAddon = yield addons.installTemporaryAddon(addonPath); + equal(installedAddon.id, "test-addons-actor@mozilla.org"); + // The returned object is currently not a proper actor. + equal(installedAddon.actor, false); + + const addonList = yield client.listAddons(); + ok(addonList && addonList.addons && addonList.addons.map(a => a.name), + "Received list of add-ons"); + const addon = addonList.addons.filter(a => a.id === installedAddon.id)[0]; + ok(addon, "Test add-on appeared in root install list"); + + yield close(client); +}); + +add_task(function* testNonExistantPath() { + const [client, addons] = yield connect(); + + yield Assert.rejects( + addons.installTemporaryAddon("some-non-existant-path"), + /Could not install add-on.*Component returned failure/); + + yield close(client); +}); diff --git a/devtools/server/tests/unit/test_animation_name.js b/devtools/server/tests/unit/test_animation_name.js new file mode 100644 index 000000000..4cd708fc4 --- /dev/null +++ b/devtools/server/tests/unit/test_animation_name.js @@ -0,0 +1,87 @@ +/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that AnimationPlayerActor.getName returns the right name depending on
+// the type of an animation and the various properties available on it.
+
+const { AnimationPlayerActor } = require("devtools/server/actors/animation");
+
+function run_test() {
+ // Mock a window with just the properties the AnimationPlayerActor uses.
+ let window = {
+ MutationObserver: function () {
+ this.observe = () => {};
+ },
+ Animation: function () {
+ this.effect = {target: getMockNode()};
+ },
+ CSSAnimation: function () {
+ this.effect = {target: getMockNode()};
+ },
+ CSSTransition: function () {
+ this.effect = {target: getMockNode()};
+ }
+ };
+
+ window.CSSAnimation.prototype = Object.create(window.Animation.prototype);
+ window.CSSTransition.prototype = Object.create(window.Animation.prototype);
+
+ // Helper to get a mock DOM node.
+ function getMockNode() {
+ return {
+ ownerDocument: {
+ defaultView: window
+ }
+ };
+ }
+
+ // Objects in this array should contain the following properties:
+ // - desc {String} For logging
+ // - animation {Object} An animation object instantiated from one of the mock
+ // window animation constructors.
+ // - props {Objet} Properties of this object will be added to the animation
+ // object.
+ // - expectedName {String} The expected name returned by
+ // AnimationPlayerActor.getName.
+ const TEST_DATA = [{
+ desc: "Animation with an id",
+ animation: new window.Animation(),
+ props: { id: "animation-id" },
+ expectedName: "animation-id"
+ }, {
+ desc: "Animation without an id",
+ animation: new window.Animation(),
+ props: {},
+ expectedName: ""
+ }, {
+ desc: "CSSTransition with an id",
+ animation: new window.CSSTransition(),
+ props: { id: "transition-with-id", transitionProperty: "width" },
+ expectedName: "transition-with-id"
+ }, {
+ desc: "CSSAnimation with an id",
+ animation: new window.CSSAnimation(),
+ props: { id: "animation-with-id", animationName: "move" },
+ expectedName: "animation-with-id"
+ }, {
+ desc: "CSSTransition without an id",
+ animation: new window.CSSTransition(),
+ props: { transitionProperty: "width" },
+ expectedName: "width"
+ }, {
+ desc: "CSSAnimation without an id",
+ animation: new window.CSSAnimation(),
+ props: { animationName: "move" },
+ expectedName: "move"
+ }];
+
+ for (let { desc, animation, props, expectedName } of TEST_DATA) {
+ do_print(desc);
+ for (let key in props) {
+ animation[key] = props[key];
+ }
+ let actor = AnimationPlayerActor({}, animation);
+ do_check_eq(actor.getName(), expectedName);
+ }
+}
diff --git a/devtools/server/tests/unit/test_animation_type.js b/devtools/server/tests/unit/test_animation_type.js new file mode 100644 index 000000000..0f37755a4 --- /dev/null +++ b/devtools/server/tests/unit/test_animation_type.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test the output of AnimationPlayerActor.getType().
+
+const { ANIMATION_TYPES, AnimationPlayerActor } =
+ require("devtools/server/actors/animation");
+
+function run_test() {
+ // Mock a window with just the properties the AnimationPlayerActor uses.
+ let window = {
+ MutationObserver: function () {
+ this.observe = () => {};
+ },
+ Animation: function () {
+ this.effect = {target: getMockNode()};
+ },
+ CSSAnimation: function () {
+ this.effect = {target: getMockNode()};
+ },
+ CSSTransition: function () {
+ this.effect = {target: getMockNode()};
+ }
+ };
+
+ window.CSSAnimation.prototype = Object.create(window.Animation.prototype);
+ window.CSSTransition.prototype = Object.create(window.Animation.prototype);
+
+ // Helper to get a mock DOM node.
+ function getMockNode() {
+ return {
+ ownerDocument: {
+ defaultView: window
+ }
+ };
+ }
+
+ // Objects in this array should contain the following properties:
+ // - desc {String} For logging
+ // - animation {Object} An animation object instantiated from one of the mock
+ // window animation constructors.
+ // - expectedType {String} The expected type returned by
+ // AnimationPlayerActor.getType.
+ const TEST_DATA = [{
+ desc: "Test CSSAnimation type",
+ animation: new window.CSSAnimation(),
+ expectedType: ANIMATION_TYPES.CSS_ANIMATION
+ }, {
+ desc: "Test CSSTransition type",
+ animation: new window.CSSTransition(),
+ expectedType: ANIMATION_TYPES.CSS_TRANSITION
+ }, {
+ desc: "Test ScriptAnimation type",
+ animation: new window.Animation(),
+ expectedType: ANIMATION_TYPES.SCRIPT_ANIMATION
+ }, {
+ desc: "Test unknown type",
+ animation: {effect: {target: getMockNode()}},
+ expectedType: ANIMATION_TYPES.UNKNOWN
+ }];
+
+ for (let { desc, animation, expectedType } of TEST_DATA) {
+ do_print(desc);
+ let actor = AnimationPlayerActor({}, animation);
+ do_check_eq(actor.getType(), expectedType);
+ }
+}
diff --git a/devtools/server/tests/unit/test_attach.js b/devtools/server/tests/unit/test_attach.js new file mode 100644 index 000000000..a69db2c2b --- /dev/null +++ b/devtools/server/tests/unit/test_attach.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gClient; +var gDebuggee; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = testGlobal("test-1"); + DebuggerServer.addTestGlobal(gDebuggee); + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(function ([aType, aTraits]) { + attachTestTab(gClient, "test-1", function (aReply, aTabClient) { + test_attach(aTabClient); + }); + }); + do_test_pending(); +} + +function test_attach(aTabClient) +{ + aTabClient.attachThread({}, function (aResponse, aThreadClient) { + do_check_eq(aThreadClient.state, "paused"); + aThreadClient.resume(cleanup); + }); +} + +function cleanup() +{ + gClient.addListener("closed", function (aEvent) { + do_test_finished(); + }); + gClient.close(); +} diff --git a/devtools/server/tests/unit/test_blackboxing-01.js b/devtools/server/tests/unit/test_blackboxing-01.js new file mode 100644 index 000000000..d5356c390 --- /dev/null +++ b/devtools/server/tests/unit/test_blackboxing-01.js @@ -0,0 +1,145 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test basic black boxing. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-black-box"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + testBlackBox(); + }); + }); + do_test_pending(); +} + +const BLACK_BOXED_URL = "http://example.com/blackboxme.js"; +const SOURCE_URL = "http://example.com/source.js"; + +const testBlackBox = Task.async(function* () { + let packet = yield executeOnNextTickAndWaitForPause(evalCode, gClient); + let source = gThreadClient.source(packet.frame.where.source); + + yield setBreakpoint(source, { + line: 2 + }); + yield resume(gThreadClient); + + const { sources } = yield getSources(gThreadClient); + let sourceClient = gThreadClient.source( + sources.filter(s => s.url == BLACK_BOXED_URL)[0]); + do_check_true(!sourceClient.isBlackBoxed, + "By default the source is not black boxed."); + + // Test that we can step into `doStuff` when we are not black boxed. + yield runTest( + function onSteppedLocation(aLocation) { + do_check_eq(aLocation.source.url, BLACK_BOXED_URL); + do_check_eq(aLocation.line, 2); + }, + function onDebuggerStatementFrames(aFrames) { + do_check_true(!aFrames.some(f => f.where.source.isBlackBoxed)); + } + ); + + let blackBoxResponse = yield blackBox(sourceClient); + do_check_true(sourceClient.isBlackBoxed); + + // Test that we step through `doStuff` when we are black boxed and its frame + // doesn't show up. + yield runTest( + function onSteppedLocation(aLocation) { + do_check_eq(aLocation.source.url, SOURCE_URL); + do_check_eq(aLocation.line, 4); + }, + function onDebuggerStatementFrames(aFrames) { + for (let f of aFrames) { + if (f.where.source.url == BLACK_BOXED_URL) { + do_check_true(f.where.source.isBlackBoxed); + } else { + do_check_true(!f.where.source.isBlackBoxed); + } + } + } + ); + + let unBlackBoxResponse = yield unBlackBox(sourceClient); + do_check_true(!sourceClient.isBlackBoxed); + + // Test that we can step into `doStuff` again. + yield runTest( + function onSteppedLocation(aLocation) { + do_check_eq(aLocation.source.url, BLACK_BOXED_URL); + do_check_eq(aLocation.line, 2); + }, + function onDebuggerStatementFrames(aFrames) { + do_check_true(!aFrames.some(f => f.where.source.isBlackBoxed)); + } + ); + + finishClient(gClient); +}); + +function evalCode() { + Components.utils.evalInSandbox( + "" + function doStuff(k) { // line 1 + let arg = 15; // line 2 - Step in here + k(arg); // line 3 + }, // line 4 + gDebuggee, + "1.8", + BLACK_BOXED_URL, + 1 + ); + + Components.utils.evalInSandbox( + "" + function runTest() { // line 1 + doStuff( // line 2 - Break here + function (n) { // line 3 - Step through `doStuff` to here + debugger; // line 4 + } // line 5 + ); // line 6 + } + "\n" // line 7 + + "debugger;", // line 8 + gDebuggee, + "1.8", + SOURCE_URL, + 1 + ); +} + +const runTest = Task.async(function* (onSteppedLocation, onDebuggerStatementFrames) { + let packet = yield executeOnNextTickAndWaitForPause(gDebuggee.runTest, + gClient); + do_check_eq(packet.why.type, "breakpoint"); + + yield stepIn(gClient, gThreadClient); + yield stepIn(gClient, gThreadClient); + yield stepIn(gClient, gThreadClient); + + const location = yield getCurrentLocation(); + onSteppedLocation(location); + + packet = yield resumeAndWaitForPause(gClient, gThreadClient); + do_check_eq(packet.why.type, "debuggerStatement"); + + let { frames } = yield getFrames(gThreadClient, 0, 100); + onDebuggerStatementFrames(frames); + + return resume(gThreadClient); +}); + +const getCurrentLocation = Task.async(function* () { + const response = yield getFrames(gThreadClient, 0, 1); + return response.frames[0].where; +}); diff --git a/devtools/server/tests/unit/test_blackboxing-02.js b/devtools/server/tests/unit/test_blackboxing-02.js new file mode 100644 index 000000000..5bfb3641a --- /dev/null +++ b/devtools/server/tests/unit/test_blackboxing-02.js @@ -0,0 +1,113 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we don't hit breakpoints in black boxed sources, and that when we + * unblack box the source again, the breakpoint hasn't disappeared and we will + * hit it again. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-black-box"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_black_box(); + }); + }); + do_test_pending(); +} + +const BLACK_BOXED_URL = "http://example.com/blackboxme.js"; +const SOURCE_URL = "http://example.com/source.js"; + +function test_black_box() +{ + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.eval(aPacket.frame.actor, "doStuff", function (aResponse) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let obj = gThreadClient.pauseGrip(aPacket.why.frameFinished.return); + obj.getDefinitionSite(runWithSource); + }); + }); + + function runWithSource(aPacket) { + let source = gThreadClient.source(aPacket.source); + source.setBreakpoint({ + line: 2 + }, function (aResponse) { + do_check_true(!aResponse.error, "Should be able to set breakpoint."); + gThreadClient.resume(test_black_box_breakpoint); + }); + } + }); + + Components.utils.evalInSandbox( + "" + function doStuff(k) { // line 1 + let arg = 15; // line 2 - Break here + k(arg); // line 3 + }, // line 4 + gDebuggee, + "1.8", + BLACK_BOXED_URL, + 1 + ); + + Components.utils.evalInSandbox( + "" + function runTest() { // line 1 + doStuff( // line 2 + function (n) { // line 3 + debugger; // line 5 + } // line 6 + ); // line 7 + } // line 8 + + "\n debugger;", // line 9 + gDebuggee, + "1.8", + SOURCE_URL, + 1 + ); +} + +function test_black_box_breakpoint() { + gThreadClient.getSources(function ({error, sources}) { + do_check_true(!error, "Should not get an error: " + error); + let sourceClient = gThreadClient.source(sources.filter(s => s.url == BLACK_BOXED_URL)[0]); + sourceClient.blackBox(function ({error}) { + do_check_true(!error, "Should not get an error: " + error); + + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "debuggerStatement", + "We should pass over the breakpoint since the source is black boxed."); + gThreadClient.resume(test_unblack_box_breakpoint.bind(null, sourceClient)); + }); + gDebuggee.runTest(); + }); + }); +} + +function test_unblack_box_breakpoint(aSourceClient) { + aSourceClient.unblackBox(function ({error}) { + do_check_true(!error, "Should not get an error: " + error); + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "breakpoint", + "We should hit the breakpoint again"); + + // We will hit the debugger statement on resume, so do this nastiness to skip over it. + gClient.addOneTimeListener( + "paused", + gThreadClient.resume.bind( + gThreadClient, + finishClient.bind(null, gClient))); + gThreadClient.resume(); + }); + gDebuggee.runTest(); + }); +} diff --git a/devtools/server/tests/unit/test_blackboxing-03.js b/devtools/server/tests/unit/test_blackboxing-03.js new file mode 100644 index 000000000..48f178777 --- /dev/null +++ b/devtools/server/tests/unit/test_blackboxing-03.js @@ -0,0 +1,102 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we don't stop at debugger statements inside black boxed sources. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gBpClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-black-box"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_black_box(); + }); + }); + do_test_pending(); +} + +const BLACK_BOXED_URL = "http://example.com/blackboxme.js"; +const SOURCE_URL = "http://example.com/source.js"; + +function test_black_box() +{ + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + source.setBreakpoint({ + line: 4 + }, function ({error}, bpClient) { + gBpClient = bpClient; + do_check_true(!error, "Should not get an error: " + error); + gThreadClient.resume(test_black_box_dbg_statement); + }); + }); + + Components.utils.evalInSandbox( + "" + function doStuff(k) { // line 1 + debugger; // line 2 - Break here + k(100); // line 3 + }, // line 4 + gDebuggee, + "1.8", + BLACK_BOXED_URL, + 1 + ); + + Components.utils.evalInSandbox( + "" + function runTest() { // line 1 + doStuff( // line 2 + function (n) { // line 3 + Math.abs(n); // line 4 - Break here + } // line 5 + ); // line 6 + } // line 7 + + "\n debugger;", // line 8 + gDebuggee, + "1.8", + SOURCE_URL, + 1 + ); +} + +function test_black_box_dbg_statement() { + gThreadClient.getSources(function ({error, sources}) { + do_check_true(!error, "Should not get an error: " + error); + let sourceClient = gThreadClient.source(sources.filter(s => s.url == BLACK_BOXED_URL)[0]); + + sourceClient.blackBox(function ({error}) { + do_check_true(!error, "Should not get an error: " + error); + + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "breakpoint", + "We should pass over the debugger statement."); + gBpClient.remove(function ({error}) { + do_check_true(!error, "Should not get an error: " + error); + gThreadClient.resume(test_unblack_box_dbg_statement.bind(null, sourceClient)); + }); + }); + gDebuggee.runTest(); + }); + }); +} + +function test_unblack_box_dbg_statement(aSourceClient) { + aSourceClient.unblackBox(function ({error}) { + do_check_true(!error, "Should not get an error: " + error); + + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "debuggerStatement", + "We should stop at the debugger statement again"); + finishClient(gClient); + }); + gDebuggee.runTest(); + }); +} diff --git a/devtools/server/tests/unit/test_blackboxing-04.js b/devtools/server/tests/unit/test_blackboxing-04.js new file mode 100644 index 000000000..fbfaf2881 --- /dev/null +++ b/devtools/server/tests/unit/test_blackboxing-04.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test behavior of blackboxing sources we are currently paused in. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-black-box"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_black_box(); + }); + }); + do_test_pending(); +} + +const BLACK_BOXED_URL = "http://example.com/blackboxme.js"; +const SOURCE_URL = "http://example.com/source.js"; + +function test_black_box() +{ + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.eval(aPacket.frame.actor, "doStuff", function (aResponse) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let obj = gThreadClient.pauseGrip(aPacket.why.frameFinished.return); + obj.getDefinitionSite(runWithSource); + }); + }); + + function runWithSource(aPacket) { + let source = gThreadClient.source(aPacket.source); + source.setBreakpoint({ + line: 2 + }, function (aResponse) { + do_check_true(!aResponse.error, "Should be able to set breakpoint."); + test_black_box_paused(); + }); + } + }); + + Components.utils.evalInSandbox( + "" + function doStuff(k) { // line 1 + debugger; // line 2 + k(100); // line 3 + }, // line 4 + gDebuggee, + "1.8", + BLACK_BOXED_URL, + 1 + ); + + Components.utils.evalInSandbox( + "" + function runTest() { // line 1 + doStuff( // line 2 + function (n) { // line 3 + return n; // line 4 + } // line 5 + ); // line 6 + } // line 7 + + "\n runTest();", // line 8 + gDebuggee, + "1.8", + SOURCE_URL, + 1 + ); +} + +function test_black_box_paused() { + gThreadClient.getSources(function ({error, sources}) { + do_check_true(!error, "Should not get an error: " + error); + let sourceClient = gThreadClient.source(sources.filter(s => s.url == BLACK_BOXED_URL)[0]); + + sourceClient.blackBox(function ({error, pausedInSource}) { + do_check_true(!error, "Should not get an error: " + error); + do_check_true(pausedInSource, "We should be notified that we are currently paused in this source"); + finishClient(gClient); + }); + }); +} diff --git a/devtools/server/tests/unit/test_blackboxing-05.js b/devtools/server/tests/unit/test_blackboxing-05.js new file mode 100644 index 000000000..fa8142e87 --- /dev/null +++ b/devtools/server/tests/unit/test_blackboxing-05.js @@ -0,0 +1,84 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test exceptions inside black boxed sources. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-black-box"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + // XXX: We have to do an executeSoon so that the error isn't caught and + // reported by DebuggerClient.requester (because we are using the local + // transport and share a stack) which causes the test to fail. + Services.tm.mainThread.dispatch({ + run: test_black_box + }, Ci.nsIThread.DISPATCH_NORMAL); + }); + }); + do_test_pending(); +} + +const BLACK_BOXED_URL = "http://example.com/blackboxme.js"; +const SOURCE_URL = "http://example.com/source.js"; + +function test_black_box() +{ + gClient.addOneTimeListener("paused", test_black_box_exception); + + Components.utils.evalInSandbox( + "" + function doStuff(k) { // line 1 + throw new Error("wu tang clan ain't nuthin' ta fuck wit"); // line 2 + k(100); // line 3 + }, // line 4 + gDebuggee, + "1.8", + BLACK_BOXED_URL, + 1 + ); + + Components.utils.evalInSandbox( + "" + function runTest() { // line 1 + doStuff( // line 2 + function (n) { // line 3 + debugger; // line 4 + } // line 5 + ); // line 6 + } // line 7 + + "\ndebugger;\n" // line 8 + + "try { runTest() } catch (ex) { }", // line 9 + gDebuggee, + "1.8", + SOURCE_URL, + 1 + ); +} + +function test_black_box_exception() { + gThreadClient.getSources(function ({error, sources}) { + do_check_true(!error, "Should not get an error: " + error); + let sourceClient = gThreadClient.source(sources.filter(s => s.url == BLACK_BOXED_URL)[0]); + + sourceClient.blackBox(function ({error}) { + do_check_true(!error, "Should not get an error: " + error); + gThreadClient.pauseOnExceptions(true); + + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.frame.where.source.url, SOURCE_URL, + "We shouldn't pause while in the black boxed source."); + finishClient(gClient); + }); + + gThreadClient.resume(); + }); + }); +} diff --git a/devtools/server/tests/unit/test_blackboxing-06.js b/devtools/server/tests/unit/test_blackboxing-06.js new file mode 100644 index 000000000..9384f2cc2 --- /dev/null +++ b/devtools/server/tests/unit/test_blackboxing-06.js @@ -0,0 +1,102 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can black box source mapped sources. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-black-box"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + + promise.resolve(setup_code()) + .then(black_box_code) + .then(run_code) + .then(test_correct_location) + .then(null, function (error) { + do_check_true(false, "Should not get an error, got " + error); + }) + .then(function () { + finishClient(gClient); + }); + }); + }); + do_test_pending(); +} + +function setup_code() { + let { code, map } = (new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "" + function a() { + return b(); + }), + "\n", + new SourceNode(1, 0, "b.js", "" + function b() { + debugger; // Don't want to stop here. + return c(); + }), + "\n", + new SourceNode(1, 0, "c.js", "" + function c() { + debugger; // Want to stop here. + }), + "\n" + ])).toStringWithSourceMap({ + file: "abc.js", + sourceRoot: "http://example.com/" + }); + + code += "//# sourceMappingURL=data:text/json," + map.toString(); + + Components.utils.evalInSandbox(code, + gDebuggee, + "1.8", + "http://example.com/abc.js"); +} + +function black_box_code() { + const d = promise.defer(); + + gThreadClient.getSources(function ({ sources, error }) { + do_check_true(!error, "Shouldn't get an error getting sources"); + const source = sources.filter((s) => { + return s.url.indexOf("b.js") !== -1; + })[0]; + do_check_true(!!source, "We should have our source in the sources list"); + + gThreadClient.source(source).blackBox(function ({ error }) { + do_check_true(!error, "Should not get an error black boxing"); + d.resolve(true); + }); + }); + + return d.promise; +} + +function run_code() { + const d = promise.defer(); + + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + d.resolve(aPacket); + gThreadClient.resume(); + }); + gDebuggee.a(); + + return d.promise; +} + +function test_correct_location(aPacket) { + do_check_eq(aPacket.why.type, "debuggerStatement", + "Should hit a debugger statement."); + do_check_eq(aPacket.frame.where.source.url, "http://example.com/c.js", + "Should have skipped over the debugger statement in the black boxed source"); +} diff --git a/devtools/server/tests/unit/test_blackboxing-07.js b/devtools/server/tests/unit/test_blackboxing-07.js new file mode 100644 index 000000000..da3147021 --- /dev/null +++ b/devtools/server/tests/unit/test_blackboxing-07.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that sources whose URL ends with ".min.js" automatically get black + * boxed. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-black-box"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + testBlackBox(); + }); + }); + do_test_pending(); +} + +const BLACK_BOXED_URL = "http://example.com/black-boxed.min.js"; +const SOURCE_URL = "http://example.com/source.js"; + +const testBlackBox = Task.async(function* () { + yield executeOnNextTickAndWaitForPause(evalCode, gClient); + + const { sources } = yield getSources(gThreadClient); + equal(sources.length, 2); + + const blackBoxedSource = sources.filter(s => s.url === BLACK_BOXED_URL)[0]; + equal(blackBoxedSource.isBlackBoxed, true); + + const regularSource = sources.filter(s => s.url === SOURCE_URL)[0]; + equal(regularSource.isBlackBoxed, false); + + finishClient(gClient); +}); + +function evalCode() { + Components.utils.evalInSandbox( + "" + function blackBoxed() {}, + gDebuggee, + "1.8", + BLACK_BOXED_URL, + 1 + ); + + Components.utils.evalInSandbox( + "" + function source() {} + + "\ndebugger;", + gDebuggee, + "1.8", + SOURCE_URL, + 1 + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-01.js b/devtools/server/tests/unit/test_breakpoint-01.js new file mode 100644 index 000000000..9a20257a3 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-01.js @@ -0,0 +1,75 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic breakpoint functionality. + */ +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_breakpoint(); + }); + }); +} + +function test_simple_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { + line: gDebuggee.line0 + 3 + }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + + }); + + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + }); + + Cu.evalInSandbox( + "var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "var a = 1;\n" + // line0 + 2 + "var b = 2;\n", // line0 + 3 + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-02.js b/devtools/server/tests/unit/test_breakpoint-02.js new file mode 100644 index 000000000..d2b220d83 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-02.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that setting breakpoints when the debuggee is running works. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_breakpoint_running(); + }); + }); +} + +function test_breakpoint_running() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let location = { line: gDebuggee.line0 + 3 }; + + gThreadClient.resume(); + + // Setting the breakpoint later should interrupt the debuggee. + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "interrupted"); + }); + + let source = gThreadClient.source(aPacket.frame.where.source); + source.setBreakpoint(location, function (aResponse) { + // Eval scripts don't stick around long enough for the breakpoint to be set, + // so just make sure we got the expected response from the actor. + do_check_neq(aResponse.error, "noScript"); + + do_execute_soon(function () { + gClient.close().then(gCallback); + }); + }); + }); + + Cu.evalInSandbox( + "var line0 = Error().lineNumber;\n" + + "debugger;\n" + + "var a = 1;\n" + // line0 + 2 + "var b = 2;\n", // line0 + 3 + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-03.js b/devtools/server/tests/unit/test_breakpoint-03.js new file mode 100644 index 000000000..b1792866b --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-03.js @@ -0,0 +1,96 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that setting a breakpoint on a line without code will skip + * forward when we know the script isn't GCed (the debugger is connected, + * so it's kept alive). + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, + "test-stack", + function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_skip_breakpoint(); + }); + }); +} + +var test_no_skip_breakpoint = Task.async(function*(source, location) { + let [aResponse, bpClient] = yield source.setBreakpoint( + Object.assign({}, location, { noSliding: true }) + ); + + do_check_true(!aResponse.actualLocation); + do_check_eq(bpClient.location.line, gDebuggee.line0 + 3); + yield bpClient.remove(); +}); + +var test_skip_breakpoint = function() { + gThreadClient.addOneTimeListener("paused", Task.async(function *(aEvent, aPacket) { + let location = { line: gDebuggee.line0 + 3 }; + let source = gThreadClient.source(aPacket.frame.where.source); + + // First, make sure that we can disable sliding with the + // `noSliding` option. + yield test_no_skip_breakpoint(source, location); + + // Now make sure that the breakpoint properly slides forward one line. + const [aResponse, bpClient] = yield source.setBreakpoint(location); + do_check_true(!!aResponse.actualLocation); + do_check_eq(aResponse.actualLocation.source.actor, source.actor); + do_check_eq(aResponse.actualLocation.line, location.line + 1); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line + 1); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + gThreadClient.resume(); + })); + + // Use `evalInSandbox` to make the debugger treat it as normal + // globally-scoped code, where breakpoint sliding rules apply. + Cu.evalInSandbox( + "var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "var a = 1;\n" + // line0 + 2 + "// A comment.\n" + // line0 + 3 + "var b = 2;", // line0 + 4 + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-04.js b/devtools/server/tests/unit/test_breakpoint-04.js new file mode 100644 index 000000000..9004c092b --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-04.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that setting a breakpoint in a line in a child script works. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_child_breakpoint(); + }); + }); +} + +function test_child_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 3 }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + // actualLocation is not returned when breakpoints don't skip forward. + do_check_eq(aResponse.actualLocation, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + + }); + + Cu.evalInSandbox( + "var line0 = Error().lineNumber;\n" + + "function foo() {\n" + // line0 + 1 + " this.a = 1;\n" + // line0 + 2 + " this.b = 2;\n" + // line0 + 3 + "}\n" + // line0 + 4 + "debugger;\n" + // line0 + 5 + "foo();\n", // line0 + 6 + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-05.js b/devtools/server/tests/unit/test_breakpoint-05.js new file mode 100644 index 000000000..9e04d0271 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-05.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that setting a breakpoint in a line without code in a child script + * will skip forward. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_child_skip_breakpoint(); + }); + }); +} + +function test_child_skip_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 3 }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + // Check that the breakpoint has properly skipped forward one line. + do_check_eq(aResponse.actualLocation.source.actor, source.actor); + do_check_eq(aResponse.actualLocation.line, location.line + 1); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line + 1); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + }); + + Cu.evalInSandbox( + "var line0 = Error().lineNumber;\n" + + "function foo() {\n" + // line0 + 1 + " this.a = 1;\n" + // line0 + 2 + " // A comment.\n" + // line0 + 3 + " this.b = 2;\n" + // line0 + 4 + "}\n" + // line0 + 5 + "debugger;\n" + // line0 + 6 + "foo();\n", // line0 + 7 + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-06.js b/devtools/server/tests/unit/test_breakpoint-06.js new file mode 100644 index 000000000..aa92b1a5f --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-06.js @@ -0,0 +1,89 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that setting a breakpoint in a line without code in a deeply-nested + * child script will skip forward. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_nested_breakpoint(); + }); + }); +} + +function test_nested_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 5 }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + // Check that the breakpoint has properly skipped forward one line. + do_check_eq(aResponse.actualLocation.source.actor, source.actor); + do_check_eq(aResponse.actualLocation.line, location.line + 1); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line + 1); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + + }); + + Cu.evalInSandbox( + "var line0 = Error().lineNumber;\n" + + "function foo() {\n" + // line0 + 1 + " function bar() {\n" + // line0 + 2 + " function baz() {\n" + // line0 + 3 + " this.a = 1;\n" + // line0 + 4 + " // A comment.\n" + // line0 + 5 + " this.b = 2;\n" + // line0 + 6 + " }\n" + // line0 + 7 + " baz();\n" + // line0 + 8 + " }\n" + // line0 + 9 + " bar();\n" + // line0 + 10 + "}\n" + // line0 + 11 + "debugger;\n" + // line0 + 12 + "foo();\n", // line0 + 13 + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-07.js b/devtools/server/tests/unit/test_breakpoint-07.js new file mode 100644 index 000000000..008f1424d --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-07.js @@ -0,0 +1,85 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that setting a breakpoint in a line without code in the second child + * script will skip forward. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_second_child_skip_breakpoint(); + }); + }); +} + +function test_second_child_skip_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 6 }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + // Check that the breakpoint has properly skipped forward one line. + do_check_eq(aResponse.actualLocation.source.actor, source.actor); + do_check_eq(aResponse.actualLocation.line, location.line + 1); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line + 1); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + }); + + Cu.evalInSandbox( + "var line0 = Error().lineNumber;\n" + + "function foo() {\n" + // line0 + 1 + " bar();\n" + // line0 + 2 + "}\n" + // line0 + 3 + "function bar() {\n" + // line0 + 4 + " this.a = 1;\n" + // line0 + 5 + " // A comment.\n" + // line0 + 6 + " this.b = 2;\n" + // line0 + 7 + "}\n" + // line0 + 8 + "debugger;\n" + // line0 + 9 + "foo();\n", // line0 + 10 + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-08.js b/devtools/server/tests/unit/test_breakpoint-08.js new file mode 100644 index 000000000..6215a61ac --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-08.js @@ -0,0 +1,96 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that setting a breakpoint in a line without code in a child script + * will skip forward, in a file with two scripts. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_child_skip_breakpoint(); + }); + }); +} + +function test_child_skip_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.eval(aPacket.frame.actor, "foo", function (aResponse) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let obj = gThreadClient.pauseGrip(aPacket.why.frameFinished.return); + obj.getDefinitionSite(runWithBreakpoint); + }); + }); + + function runWithBreakpoint(aPacket) { + let source = gThreadClient.source(aPacket.source); + let location = { line: gDebuggee.line0 + 3 }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + // Check that the breakpoint has properly skipped forward one line. + do_check_eq(aResponse.actualLocation.source.actor, source.actor); + do_check_eq(aResponse.actualLocation.line, location.line + 1); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line + 1); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + } + }); + + Cu.evalInSandbox("var line0 = Error().lineNumber;\n" + + "function foo() {\n" + // line0 + 1 + " this.a = 1;\n" + // line0 + 2 + " // A comment.\n" + // line0 + 3 + " this.b = 2;\n" + // line0 + 4 + "}\n", // line0 + 5 + gDebuggee, + "1.7", + "script1.js"); + + Cu.evalInSandbox("var line1 = Error().lineNumber;\n" + + "debugger;\n" + // line1 + 1 + "foo();\n", // line1 + 2 + gDebuggee, + "1.7", + "script2.js"); +} diff --git a/devtools/server/tests/unit/test_breakpoint-09.js b/devtools/server/tests/unit/test_breakpoint-09.js new file mode 100644 index 000000000..8bea375b9 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-09.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that removing a breakpoint works. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_remove_breakpoint(); + }); + }); +} + +function test_remove_breakpoint() +{ + let done = false; + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 2 }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + done = true; + gThreadClient.addOneTimeListener("paused", + function (aEvent, aPacket) { + // The breakpoint should not be hit again. + gThreadClient.resume(function () { + do_check_true(false); + }); + }); + gThreadClient.resume(); + }); + + }); + // Continue until the breakpoint is hit. + gThreadClient.resume(); + + }); + + }); + + Cu.evalInSandbox("var line0 = Error().lineNumber;\n" + + "function foo(stop) {\n" + // line0 + 1 + " this.a = 1;\n" + // line0 + 2 + " if (stop) return;\n" + // line0 + 3 + " delete this.a;\n" + // line0 + 4 + " foo(true);\n" + // line0 + 5 + "}\n" + // line0 + 6 + "debugger;\n" + // line1 + 7 + "foo();\n", // line1 + 8 + gDebuggee); + if (!done) { + do_check_true(false); + } + gClient.close().then(gCallback); +} diff --git a/devtools/server/tests/unit/test_breakpoint-10.js b/devtools/server/tests/unit/test_breakpoint-10.js new file mode 100644 index 000000000..c69576767 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-10.js @@ -0,0 +1,89 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that setting a breakpoint in a line with multiple entry points + * triggers no matter which entry point we reach. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_child_breakpoint(); + }); + }); +} + +function test_child_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 3 }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + // actualLocation is not returned when breakpoints don't skip forward. + do_check_eq(aResponse.actualLocation, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.i, 0); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.i, 1); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + // Continue until the breakpoint is hit again. + gThreadClient.resume(); + + }); + // Continue until the breakpoint is hit. + gThreadClient.resume(); + + }); + + }); + + + Cu.evalInSandbox("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "var a, i = 0;\n" + // line0 + 2 + "for (i = 1; i <= 2; i++) {\n" + // line0 + 3 + " a = i;\n" + // line0 + 4 + "}\n", // line0 + 5 + gDebuggee); +} diff --git a/devtools/server/tests/unit/test_breakpoint-11.js b/devtools/server/tests/unit/test_breakpoint-11.js new file mode 100644 index 000000000..480b95984 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-11.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that setting a breakpoint in a line with bytecodes in multiple + * scripts, sets the breakpoint in all of them (bug 793214). + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_child_breakpoint(); + }); + }); +} + +function test_child_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 2 }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + // actualLocation is not returned when breakpoints don't skip forward. + do_check_eq(aResponse.actualLocation, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a.b, 1); + do_check_eq(gDebuggee.res, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + // Continue until the breakpoint is hit again. + gThreadClient.resume(); + + }); + // Continue until the breakpoint is hit. + gThreadClient.resume(); + + }); + + }); + + + Cu.evalInSandbox("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "var a = { b: 1, f: function() { return 2; } };\n" + // line0+2 + "var res = a.f();\n", // line0 + 3 + gDebuggee); +} diff --git a/devtools/server/tests/unit/test_breakpoint-12.js b/devtools/server/tests/unit/test_breakpoint-12.js new file mode 100644 index 000000000..fb147da9f --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-12.js @@ -0,0 +1,113 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that setting a breakpoint twice in a line without bytecodes works + * as expected. + */ + +const NUM_BREAKPOINTS = 10; +var gDebuggee; +var gClient; +var gThreadClient; +var gBpActor; +var gCount; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + gCount = 1; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_child_skip_breakpoint(); + }); + }); +} + +function test_child_skip_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 3}; + + source.setBreakpoint(location, function (aResponse, bpClient) { + // Check that the breakpoint has properly skipped forward one line. + do_check_eq(aResponse.actualLocation.source.actor, source.actor); + do_check_eq(aResponse.actualLocation.line, location.line + 1); + gBpActor = aResponse.actor; + + // Set more breakpoints at the same location. + set_breakpoints(source, location); + }); + + }); + + Cu.evalInSandbox("var line0 = Error().lineNumber;\n" + + "function foo() {\n" + // line0 + 1 + " this.a = 1;\n" + // line0 + 2 + " // A comment.\n" + // line0 + 3 + " this.b = 2;\n" + // line0 + 4 + "}\n" + // line0 + 5 + "debugger;\n" + // line0 + 6 + "foo();\n", // line0 + 7 + gDebuggee); +} + +// Set many breakpoints at the same location. +function set_breakpoints(source, location) { + do_check_neq(gCount, NUM_BREAKPOINTS); + source.setBreakpoint(location, function (aResponse, bpClient) { + // Check that the breakpoint has properly skipped forward one line. + do_check_eq(aResponse.actualLocation.source.actor, source.actor); + do_check_eq(aResponse.actualLocation.line, location.line + 1); + // Check that the same breakpoint actor was returned. + do_check_eq(aResponse.actor, gBpActor); + + if (++gCount < NUM_BREAKPOINTS) { + set_breakpoints(source, location); + return; + } + + // After setting all the breakpoints, check that only one has effectively + // remained. + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line + 1); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // We don't expect any more pauses after the breakpoint was hit once. + do_check_true(false); + }); + gThreadClient.resume(function () { + // Give any remaining breakpoints a chance to trigger. + do_timeout(1000, function () { + gClient.close().then(gCallback); + }); + }); + + }); + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + +} diff --git a/devtools/server/tests/unit/test_breakpoint-13.js b/devtools/server/tests/unit/test_breakpoint-13.js new file mode 100644 index 000000000..cdc4c9091 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-13.js @@ -0,0 +1,115 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that execution doesn't pause twice while stepping, when encountering + * either a breakpoint or a debugger statement. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_breakpoint(); + }); + }); +} + +function test_simple_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 2 }; + + source.setBreakpoint(location, Task.async(function* (aResponse, bpClient) { + const testCallbacks = [ + function (aPacket) { + // Check that the stepping worked. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + function (aPacket) { + // Entered the foo function call frame. + do_check_eq(aPacket.frame.where.line, location.line); + do_check_neq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + function (aPacket) { + // At the end of the foo function call frame. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_neq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + function (aPacket) { + // Check that the breakpoint wasn't the reason for this pause, but + // that the frame is about to be popped while stepping. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_neq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.type, "resumeLimit"); + do_check_eq(aPacket.why.frameFinished.return.type, "undefined"); + }, + function (aPacket) { + // The foo function call frame was just popped from the stack. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5); + do_check_eq(aPacket.why.type, "resumeLimit"); + do_check_eq(aPacket.poppedFrames.length, 1); + }, + function (aPacket) { + // Check that the debugger statement wasn't the reason for this pause. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 6); + do_check_neq(aPacket.why.type, "debuggerStatement"); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + function (aPacket) { + // Check that the debugger statement wasn't the reason for this pause. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 7); + do_check_neq(aPacket.why.type, "debuggerStatement"); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + ]; + + for (let callback of testCallbacks) { + let waiter = waitForPause(gThreadClient); + gThreadClient.stepIn(); + let packet = yield waiter; + callback(packet); + } + + // Remove the breakpoint and finish. + let waiter = waitForPause(gThreadClient); + gThreadClient.stepIn(); + yield waiter; + bpClient.remove(() => gThreadClient.resume(() => gClient.close().then(gCallback))); + })); + }); + + Cu.evalInSandbox("var line0 = Error().lineNumber;\n" + + "function foo() {\n" + // line0 + 1 + " this.a = 1;\n" + // line0 + 2 <-- Breakpoint is set here. + "}\n" + // line0 + 3 + "debugger;\n" + // line0 + 4 + "foo();\n" + // line0 + 5 + "debugger;\n" + // line0 + 6 + "var b = 2;\n", // line0 + 7 + gDebuggee); +} diff --git a/devtools/server/tests/unit/test_breakpoint-14.js b/devtools/server/tests/unit/test_breakpoint-14.js new file mode 100644 index 000000000..aa86975b6 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-14.js @@ -0,0 +1,113 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that a breakpoint or a debugger statement cause execution to pause even + * in a stepped-over function. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_breakpoint(); + }); + }); +} + +function test_simple_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 2 }; + + source.setBreakpoint(location, Task.async(function* (aResponse, bpClient) { + const testCallbacks = [ + function (aPacket) { + // Check that the stepping worked. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + function (aPacket) { + // Reached the breakpoint. + do_check_eq(aPacket.frame.where.line, location.line); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_neq(aPacket.why.type, "resumeLimit"); + }, + function (aPacket) { + // Stepped to the closing brace of the function. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + function (aPacket) { + // The frame is about to be popped while stepping. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_neq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.type, "resumeLimit"); + do_check_eq(aPacket.why.frameFinished.return.type, "undefined"); + }, + function (aPacket) { + // The foo function call frame was just popped from the stack. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5); + do_check_eq(aPacket.why.type, "resumeLimit"); + do_check_eq(aPacket.poppedFrames.length, 1); + }, + function (aPacket) { + // Check that the debugger statement wasn't the reason for this pause. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 6); + do_check_neq(aPacket.why.type, "debuggerStatement"); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + function (aPacket) { + // Check that the debugger statement wasn't the reason for this pause. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 7); + do_check_neq(aPacket.why.type, "debuggerStatement"); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + ]; + + for (let callback of testCallbacks) { + let waiter = waitForPause(gThreadClient); + gThreadClient.stepOver(); + let packet = yield waiter; + callback(packet); + } + + // Remove the breakpoint and finish. + let waiter = waitForPause(gThreadClient); + gThreadClient.stepOver(); + yield waiter; + bpClient.remove(() => gThreadClient.resume(() => gClient.close().then(gCallback))); + })); + }); + + Cu.evalInSandbox("var line0 = Error().lineNumber;\n" + + "function foo() {\n" + // line0 + 1 + " this.a = 1;\n" + // line0 + 2 <-- Breakpoint is set here. + "}\n" + // line0 + 3 + "debugger;\n" + // line0 + 4 + "foo();\n" + // line0 + 5 + "debugger;\n" + // line0 + 6 + "var b = 2;\n", // line0 + 7 + gDebuggee); +} diff --git a/devtools/server/tests/unit/test_breakpoint-15.js b/devtools/server/tests/unit/test_breakpoint-15.js new file mode 100644 index 000000000..6a3ab7c6f --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-15.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that adding a breakpoint in the same place returns the same actor. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + testSameBreakpoint(); + }); + }); + do_test_pending(); +} + +const SOURCE_URL = "http://example.com/source.js"; + +const testSameBreakpoint = Task.async(function* () { + let packet = yield executeOnNextTickAndWaitForPause(evalCode, gClient); + let source = gThreadClient.source(packet.frame.where.source); + + // Whole line + let wholeLineLocation = { + line: 2 + }; + + let [firstResponse, firstBpClient] = yield setBreakpoint(source, wholeLineLocation); + let [secondResponse, secondBpClient] = yield setBreakpoint(source, wholeLineLocation); + + do_check_eq(firstBpClient.actor, secondBpClient.actor, "Should get the same actor w/ whole line breakpoints"); + + // Specific column + + let columnLocation = { + line: 2, + column: 6 + }; + + [firstResponse, firstBpClient] = yield setBreakpoint(source, columnLocation); + [secondResponse, secondBpClient] = yield setBreakpoint(source, columnLocation); + + do_check_eq(secondBpClient.actor, secondBpClient.actor, "Should get the same actor column breakpoints"); + + finishClient(gClient); +}); + +function evalCode() { + Components.utils.evalInSandbox( + "" + function doStuff(k) { // line 1 + let arg = 15; // line 2 - Step in here + k(arg); // line 3 + } + "\n" // line 4 + + "debugger;", // line 5 + gDebuggee, + "1.8", + SOURCE_URL, + 1 + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-16.js b/devtools/server/tests/unit/test_breakpoint-16.js new file mode 100644 index 000000000..43a9086ec --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-16.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that we can set breakpoints in columns, not just lines. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-breakpoints", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, + "test-breakpoints", + function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_column_breakpoint(); + }); + }); +} + +function test_column_breakpoint() +{ + // Debugger statement + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { + line: gDebuggee.line0 + 1, + column: 55 + }; + let timesBreakpointHit = 0; + + source.setBreakpoint(location, function (aResponse, bpClient) { + gThreadClient.addListener("paused", function onPaused(aEvent, aPacket) { + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line); + do_check_eq(aPacket.frame.where.column, location.column); + + do_check_eq(gDebuggee.acc, timesBreakpointHit); + do_check_eq(aPacket.frame.environment.bindings.variables.i.value, + timesBreakpointHit); + + if (++timesBreakpointHit === 3) { + gThreadClient.removeListener("paused", onPaused); + bpClient.remove(function (aResponse) { + gThreadClient.resume(() => gClient.close().then(gCallback)); + }); + } else { + gThreadClient.resume(); + } + }); + + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + + }); + + + Components.utils.evalInSandbox( + "var line0 = Error().lineNumber;\n" + + "(function () { debugger; this.acc = 0; for (var i = 0; i < 3; i++) this.acc++; }());", + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-17.js b/devtools/server/tests/unit/test_breakpoint-17.js new file mode 100644 index 000000000..944627e61 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-17.js @@ -0,0 +1,120 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that when we add 2 breakpoints to the same line at different columns and + * then remove one of them, we don't remove them both. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, do_test_finished); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-breakpoints", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-breakpoints", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_breakpoints_columns(); + }); + }); +} + +const code = +"(" + function (global) { + global.foo = function () { + Math.abs(-1); Math.log(0.5); + debugger; + }; + debugger; +} + "(this))"; + +const firstLocation = { + line: 3, + column: 4 +}; + +const secondLocation = { + line: 3, + column: 18 +}; + +function test_breakpoints_columns() { + gClient.addOneTimeListener("paused", set_breakpoints); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", "http://example.com/", 1); +} + +function set_breakpoints(aEvent, aPacket) { + let first, second; + let source = gThreadClient.source(aPacket.frame.where.source); + + source.setBreakpoint(firstLocation, function ({ error, actualLocation }, + aBreakpointClient) { + do_check_true(!error, "Should not get an error setting the breakpoint"); + do_check_true(!actualLocation, "Should not get an actualLocation"); + first = aBreakpointClient; + + source.setBreakpoint(secondLocation, function ({ error, actualLocation }, + aBreakpointClient) { + do_check_true(!error, "Should not get an error setting the breakpoint"); + do_check_true(!actualLocation, "Should not get an actualLocation"); + second = aBreakpointClient; + + test_different_actors(first, second); + }); + }); +} + +function test_different_actors(aFirst, aSecond) { + do_check_neq(aFirst.actor, aSecond.actor, + "Each breakpoint should have a different actor"); + test_remove_one(aFirst, aSecond); +} + +function test_remove_one(aFirst, aSecond) { + aFirst.remove(function ({error}) { + do_check_true(!error, "Should not get an error removing a breakpoint"); + + let hitSecond; + gClient.addListener("paused", function _onPaused(aEvent, {why, frame}) { + if (why.type == "breakpoint") { + hitSecond = true; + do_check_eq(why.actors.length, 1, + "Should only be paused because of one breakpoint actor"); + do_check_eq(why.actors[0], aSecond.actor, + "Should be paused because of the correct breakpoint actor"); + do_check_eq(frame.where.line, secondLocation.line, + "Should be at the right line"); + do_check_eq(frame.where.column, secondLocation.column, + "Should be at the right column"); + gThreadClient.resume(); + return; + } + + if (why.type == "debuggerStatement") { + gClient.removeListener("paused", _onPaused); + do_check_true(hitSecond, + "We should still hit `second`, but not `first`."); + + gClient.close().then(gCallback); + return; + } + + do_check_true(false, "Should never get here"); + }); + + gThreadClient.resume(() => gDebuggee.foo()); + }); +} diff --git a/devtools/server/tests/unit/test_breakpoint-18.js b/devtools/server/tests/unit/test_breakpoint-18.js new file mode 100644 index 000000000..d153d3eff --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-18.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that we only break on offsets that are entry points for the line we are + * breaking on. Bug 907278. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-breakpoints", aServer); + gDebuggee.console = { log: x => void x }; + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, + "test-breakpoints", + function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + setUpCode(); + }); + }); +} + +function setUpCode() { + gClient.addOneTimeListener("paused", setBreakpoint); + Cu.evalInSandbox( + "debugger;\n" + + function test() { + console.log("foo bar"); + debugger; + }, + gDebuggee, + "1.8", + "http://example.com/", + 1 + ); +} + +function setBreakpoint(aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + gClient.addOneTimeListener("resumed", runCode); + + source.setBreakpoint({ line: 2 }, ({ error }) => { + do_check_true(!error); + gThreadClient.resume(); + }); +} + +function runCode() { + gClient.addOneTimeListener("paused", testBPHit); + gDebuggee.test(); +} + +function testBPHit(event, { why }) { + do_check_eq(why.type, "breakpoint"); + gClient.addOneTimeListener("paused", testDbgStatement); + gThreadClient.resume(); +} + +function testDbgStatement(event, { why }) { + // Should continue to the debugger statement. + do_check_eq(why.type, "debuggerStatement"); + // Not break on another offset from the same line (that isn't an entry point + // to the line) + do_check_neq(why.type, "breakpoint"); + gClient.close().then(gCallback); +} diff --git a/devtools/server/tests/unit/test_breakpoint-19.js b/devtools/server/tests/unit/test_breakpoint-19.js new file mode 100644 index 000000000..da04a5268 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-19.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that setting a breakpoint in a not-yet-existing script doesn't throw + * an error (see bug 897567). Also make sure that this breakpoint works. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-breakpoints", aServer); + gDebuggee.console = { log: x => void x }; + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, + "test-breakpoints", + function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + testBreakpoint(); + }); + }); +} + +const URL = "test.js"; + +function setUpCode() { + Cu.evalInSandbox( + "" + function test() { // 1 + var a = 1; // 2 + debugger; // 3 + } + // 4 + "\ndebugger;", // 5 + gDebuggee, + "1.8", + URL + ); +} + +const testBreakpoint = Task.async(function* () { + let source = yield getSource(gThreadClient, URL); + let [response, bpClient] = yield setBreakpoint(source, {line: 2}); + ok(!response.error); + + let actor = response.actor; + ok(actor); + + yield executeOnNextTickAndWaitForPause(setUpCode, gClient); + yield resume(gThreadClient); + + let packet = yield executeOnNextTickAndWaitForPause(gDebuggee.test, gClient); + equal(packet.why.type, "breakpoint"); + notEqual(packet.why.actors.indexOf(actor), -1); + + finishClient(gClient); +}); diff --git a/devtools/server/tests/unit/test_breakpoint-20.js b/devtools/server/tests/unit/test_breakpoint-20.js new file mode 100644 index 000000000..b70282dae --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-20.js @@ -0,0 +1,108 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Verify that when two of the "same" source are loaded concurrently (like e10s + * frame scripts), breakpoints get hit in scripts defined by all sources. + */ + +var gDebuggee; +var gClient; +var gTraceClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-breakpoints"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestThread(gClient, "test-breakpoints", testBreakpoint); + }); + do_test_pending(); +} + +const testBreakpoint = Task.async(function* (threadResponse, tabClient, threadClient, tabResponse) { + evalSetupCode(); + + // Load the test source once. + + evalTestCode(); + equal(gDebuggee.functions.length, 1, + "The test code should have added a function."); + + // Set a breakpoint in the test source. + + const source = yield getSource(threadClient, "test.js"); + const [response, bpClient] = yield setBreakpoint(source, { + line: 3 + }); + ok(!response.error, "Shouldn't get an error setting the BP."); + ok(!response.actualLocation, + "Shouldn't get an actualLocation, the location we provided was good."); + const bpActor = response.actor; + + yield resume(threadClient); + + // Load the test source again. + + evalTestCode(); + equal(gDebuggee.functions.length, 2, + "The test code should have added another function."); + + // Should hit our breakpoint in a script defined by the first instance of the + // test source. + + const bpPause1 = yield executeOnNextTickAndWaitForPause(gDebuggee.functions[0], + gClient); + equal(bpPause1.why.type, "breakpoint", + "Should pause because of hitting our breakpoint (not debugger statement)."); + equal(bpPause1.why.actors[0], bpActor, + "And the breakpoint actor should be correct."); + const dbgStmtPause1 = yield executeOnNextTickAndWaitForPause(() => resume(threadClient), + gClient); + equal(dbgStmtPause1.why.type, "debuggerStatement", + "And we should hit the debugger statement after the pause."); + yield resume(threadClient); + + // Should also hit our breakpoint in a script defined by the second instance + // of the test source. + + const bpPause2 = yield executeOnNextTickAndWaitForPause(gDebuggee.functions[1], + gClient); + equal(bpPause2.why.type, "breakpoint", + "Should pause because of hitting our breakpoint (not debugger statement)."); + equal(bpPause2.why.actors[0], bpActor, + "And the breakpoint actor should be correct."); + const dbgStmtPause2 = yield executeOnNextTickAndWaitForPause(() => resume(threadClient), + gClient); + equal(dbgStmtPause2.why.type, "debuggerStatement", + "And we should hit the debugger statement after the pause."); + + finishClient(gClient); +}); + +function evalSetupCode() { + Cu.evalInSandbox( + "this.functions = [];", + gDebuggee, + "1.8", + "setup.js", + 1 + ); +} + +function evalTestCode() { + Cu.evalInSandbox( + ` // 1 + this.functions.push(function () { // 2 + var setBreakpointHere = 1; // 3 + debugger; // 4 + }); // 5 + `, + gDebuggee, + "1.8", + "test.js", + 1 + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-21.js b/devtools/server/tests/unit/test_breakpoint-21.js new file mode 100644 index 000000000..e5f2e9e4a --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-21.js @@ -0,0 +1,85 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 1122064 - make sure that scripts introduced via onNewScripts + * properly populate the `ScriptStore` with all there nested child + * scripts, so you can set breakpoints on deeply nested scripts + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-breakpoints", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, + "test-breakpoints", + function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test(); + }); + }); +} + +const test = Task.async(function* () { + // Populate the `ScriptStore` so that we only test that the script + // is added through `onNewScript` + yield getSources(gThreadClient); + + let packet = yield executeOnNextTickAndWaitForPause(evalCode, gClient); + let source = gThreadClient.source(packet.frame.where.source); + let location = { + line: gDebuggee.line0 + 8 + }; + + let [res, bpClient] = yield setBreakpoint(source, location); + ok(!res.error); + + yield resume(gThreadClient); + packet = yield waitForPause(gClient); + do_check_eq(packet.type, "paused"); + do_check_eq(packet.why.type, "breakpoint"); + do_check_eq(packet.why.actors[0], bpClient.actor); + do_check_eq(packet.frame.where.source.actor, source.actor); + do_check_eq(packet.frame.where.line, location.line); + + yield resume(gThreadClient); + finishClient(gClient); +}); + +function evalCode() { + // Start a new script + Components.utils.evalInSandbox( + "var line0 = Error().lineNumber;\n(" + function () { + debugger; + var a = (function () { + return (function () { + return (function () { + return (function () { + return (function () { + var x = 10; // This line gets a breakpoint + return 1; + })(); + })(); + })(); + })(); + })(); + } + ")()", + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-actor-map.js b/devtools/server/tests/unit/test_breakpoint-actor-map.js new file mode 100644 index 000000000..d1d149648 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-actor-map.js @@ -0,0 +1,180 @@ +/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the functionality of the BreakpointActorMap object. + +const { BreakpointActorMap } = require("devtools/server/actors/script"); + +function run_test() { + test_get_actor(); + test_set_actor(); + test_delete_actor(); + test_find_actors(); + test_duplicate_actors(); +} + +function test_get_actor() { + let bpStore = new BreakpointActorMap(); + let location = { + originalSourceActor: { actor: "actor1" }, + originalLine: 3 + }; + let columnLocation = { + originalSourceActor: { actor: "actor2" }, + originalLine: 5, + originalColumn: 15 + }; + + // Shouldn't have breakpoint + do_check_eq(null, bpStore.getActor(location), + "Breakpoint not added and shouldn't exist."); + + bpStore.setActor(location, {}); + do_check_true(!!bpStore.getActor(location), + "Breakpoint added but not found in Breakpoint Store."); + + bpStore.deleteActor(location); + do_check_eq(null, bpStore.getActor(location), + "Breakpoint removed but still exists."); + + // Same checks for breakpoint with a column + do_check_eq(null, bpStore.getActor(columnLocation), + "Breakpoint with column not added and shouldn't exist."); + + bpStore.setActor(columnLocation, {}); + do_check_true(!!bpStore.getActor(columnLocation), + "Breakpoint with column added but not found in Breakpoint Store."); + + bpStore.deleteActor(columnLocation); + do_check_eq(null, bpStore.getActor(columnLocation), + "Breakpoint with column removed but still exists in Breakpoint Store."); +} + +function test_set_actor() { + // Breakpoint with column + let bpStore = new BreakpointActorMap(); + let location = { + originalSourceActor: { actor: "actor1" }, + originalLine: 10, + originalColumn: 9 + }; + bpStore.setActor(location, {}); + do_check_true(!!bpStore.getActor(location), + "We should have the column breakpoint we just added"); + + // Breakpoint without column (whole line breakpoint) + location = { + originalSourceActor: { actor: "actor2" }, + originalLine: 103 + }; + bpStore.setActor(location, {}); + do_check_true(!!bpStore.getActor(location), + "We should have the whole line breakpoint we just added"); +} + +function test_delete_actor() { + // Breakpoint with column + let bpStore = new BreakpointActorMap(); + let location = { + originalSourceActor: { actor: "actor1" }, + originalLine: 10, + originalColumn: 9 + }; + bpStore.setActor(location, {}); + bpStore.deleteActor(location); + do_check_eq(bpStore.getActor(location), null, + "We should not have the column breakpoint anymore"); + + // Breakpoint without column (whole line breakpoint) + location = { + originalSourceActor: { actor: "actor2" }, + originalLine: 103 + }; + bpStore.setActor(location, {}); + bpStore.deleteActor(location); + do_check_eq(bpStore.getActor(location), null, + "We should not have the whole line breakpoint anymore"); +} + +function test_find_actors() { + let bps = [ + { originalSourceActor: { actor: "actor1" }, originalLine: 10 }, + { originalSourceActor: { actor: "actor1" }, originalLine: 10, originalColumn: 3 }, + { originalSourceActor: { actor: "actor1" }, originalLine: 10, originalColumn: 10 }, + { originalSourceActor: { actor: "actor1" }, originalLine: 23, originalColumn: 89 }, + { originalSourceActor: { actor: "actor2" }, originalLine: 10, originalColumn: 1 }, + { originalSourceActor: { actor: "actor2" }, originalLine: 20, originalColumn: 5 }, + { originalSourceActor: { actor: "actor2" }, originalLine: 30, originalColumn: 34 }, + { originalSourceActor: { actor: "actor2" }, originalLine: 40, originalColumn: 56 } + ]; + + let bpStore = new BreakpointActorMap(); + + for (let bp of bps) { + bpStore.setActor(bp, bp); + } + + // All breakpoints + + let bpSet = new Set(bps); + for (let bp of bpStore.findActors()) { + bpSet.delete(bp); + } + do_check_eq(bpSet.size, 0, + "Should be able to iterate over all breakpoints"); + + // Breakpoints by URL + + bpSet = new Set(bps.filter(bp => { return bp.originalSourceActor.actorID === "actor1"; })); + for (let bp of bpStore.findActors({ originalSourceActor: { actorID: "actor1" } })) { + bpSet.delete(bp); + } + do_check_eq(bpSet.size, 0, + "Should be able to filter the iteration by url"); + + // Breakpoints by URL and line + + bpSet = new Set(bps.filter(bp => { return bp.originalSourceActor.actorID === "actor1" && bp.originalLine === 10; })); + let first = true; + for (let bp of bpStore.findActors({ originalSourceActor: { actorID: "actor1" }, originalLine: 10 })) { + if (first) { + do_check_eq(bp.originalColumn, undefined, + "Should always get the whole line breakpoint first"); + first = false; + } else { + do_check_neq(bp.originalColumn, undefined, + "Should not get the whole line breakpoint any time other than first."); + } + bpSet.delete(bp); + } + do_check_eq(bpSet.size, 0, + "Should be able to filter the iteration by url and line"); +} + +function test_duplicate_actors() { + let bpStore = new BreakpointActorMap(); + + // Breakpoint with column + let location = { + originalSourceActor: { actorID: "foo-actor" }, + originalLine: 10, + originalColumn: 9 + }; + bpStore.setActor(location, {}); + bpStore.setActor(location, {}); + do_check_eq(bpStore.size, 1, "We should have only 1 column breakpoint"); + bpStore.deleteActor(location); + + // Breakpoint without column (whole line breakpoint) + location = { + originalSourceActor: { actorID: "foo-actor" }, + originalLine: 15 + }; + bpStore.setActor(location, {}); + bpStore.setActor(location, {}); + do_check_eq(bpStore.size, 1, "We should have only 1 whole line breakpoint"); + bpStore.deleteActor(location); +} diff --git a/devtools/server/tests/unit/test_client_close.js b/devtools/server/tests/unit/test_client_close.js new file mode 100644 index 000000000..84747e85b --- /dev/null +++ b/devtools/server/tests/unit/test_client_close.js @@ -0,0 +1,39 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gClient; +var gDebuggee; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = testGlobal("test-1"); + DebuggerServer.addTestGlobal(gDebuggee); + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(function (aType, aTraits) { + attachTestTab(gClient, "test-1", function (aReply, aTabClient) { + test_close(transport); + }); + }); + do_test_pending(); +} + +function test_close(aTransport) +{ + // Check that, if we fake a transport shutdown + // (like if a device is unplugged) + // the client is automatically closed, + // and we can still call client.close. + let onClosed = function () { + gClient.removeListener("closed", onClosed); + ok(true, "Client emitted 'closed' event"); + gClient.close().then(function () { + ok(true, "client.close() successfully called its callback"); + do_test_finished(); + }); + }; + gClient.addListener("closed", onClosed); + aTransport.close(); +} diff --git a/devtools/server/tests/unit/test_client_request.js b/devtools/server/tests/unit/test_client_request.js new file mode 100644 index 000000000..c0c2c3a92 --- /dev/null +++ b/devtools/server/tests/unit/test_client_request.js @@ -0,0 +1,214 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test the DebuggerClient.request API. + +var gClient, gActorId; + +function TestActor(conn) { + this.conn = conn; +} +TestActor.prototype = { + actorPrefix: "test", + + hello: function () { + return {hello: "world"}; + }, + + error: function () { + return {error: "code", message: "human message"}; + } +}; +TestActor.prototype.requestTypes = { + "hello": TestActor.prototype.hello, + "error": TestActor.prototype.error +}; + +function run_test() +{ + DebuggerServer.addGlobalActor(TestActor); + + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + add_test(init); + add_test(test_client_request_callback); + add_test(test_client_request_promise); + add_test(test_client_request_promise_error); + add_test(test_client_request_event_emitter); + add_test(test_close_client_while_sending_requests); + add_test(test_client_request_after_close); + add_test(test_client_request_after_close_callback); + run_next_test(); +} + +function init() +{ + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect() + .then(() => gClient.listTabs()) + .then(aResponse => { + gActorId = aResponse.test; + run_next_test(); + }); +} + +function checkStack(expectedName) { + if (!Services.prefs.getBoolPref("javascript.options.asyncstack")) { + do_print("Async stacks are disabled."); + return; + } + + let stack = Components.stack; + while (stack) { + do_print(stack.name); + if (stack.name == expectedName) { + // Reached back to outer function before request + ok(true, "Complete stack"); + return; + } + stack = stack.asyncCaller || stack.caller; + } + ok(false, "Incomplete stack"); +} + +function test_client_request_callback() +{ + // Test that DebuggerClient.request accepts a `onResponse` callback as 2nd argument + gClient.request({ + to: gActorId, + type: "hello" + }, response => { + do_check_eq(response.from, gActorId); + do_check_eq(response.hello, "world"); + checkStack("test_client_request_callback"); + run_next_test(); + }); +} + +function test_client_request_promise() +{ + // Test that DebuggerClient.request returns a promise that resolves on response + let request = gClient.request({ + to: gActorId, + type: "hello" + }); + + request.then(response => { + do_check_eq(response.from, gActorId); + do_check_eq(response.hello, "world"); + checkStack("test_client_request_promise"); + run_next_test(); + }); +} + +function test_client_request_promise_error() +{ + // Test that DebuggerClient.request returns a promise that reject when server + // returns an explicit error message + let request = gClient.request({ + to: gActorId, + type: "error" + }); + + request.then(() => { + do_throw("Promise shouldn't be resolved on error"); + }, response => { + do_check_eq(response.from, gActorId); + do_check_eq(response.error, "code"); + do_check_eq(response.message, "human message"); + checkStack("test_client_request_promise_error"); + run_next_test(); + }); +} + +function test_client_request_event_emitter() +{ + // Test that DebuggerClient.request returns also an EventEmitter object + let request = gClient.request({ + to: gActorId, + type: "hello" + }); + request.on("json-reply", reply => { + do_check_eq(reply.from, gActorId); + do_check_eq(reply.hello, "world"); + checkStack("test_client_request_event_emitter"); + run_next_test(); + }); +} + +function test_close_client_while_sending_requests() { + // First send a first request that will be "active" + // while the connection is closed. + // i.e. will be sent but no response received yet. + let activeRequest = gClient.request({ + to: gActorId, + type: "hello" + }); + + // Pile up a second one that will be "pending". + // i.e. won't event be sent. + let pendingRequest = gClient.request({ + to: gActorId, + type: "hello" + }); + + let expectReply = promise.defer(); + gClient.expectReply("root", function (response) { + do_check_eq(response.error, "connectionClosed"); + do_check_eq(response.message, "server side packet can't be received as the connection just closed."); + expectReply.resolve(); + }); + + gClient.close().then(() => { + activeRequest.then(() => { + ok(false, "First request unexpectedly succeed while closing the connection"); + }, response => { + do_check_eq(response.error, "connectionClosed"); + do_check_eq(response.message, "'hello' active request packet to '" + gActorId + "' can't be sent as the connection just closed."); + }) + .then(() => pendingRequest) + .then(() => { + ok(false, "Second request unexpectedly succeed while closing the connection"); + }, response => { + do_check_eq(response.error, "connectionClosed"); + do_check_eq(response.message, "'hello' pending request packet to '" + gActorId + "' can't be sent as the connection just closed."); + }) + .then(() => expectReply.promise) + .then(run_next_test); + }); +} + +function test_client_request_after_close() +{ + // Test that DebuggerClient.request fails after we called client.close() + // (with promise API) + let request = gClient.request({ + to: gActorId, + type: "hello" + }); + + request.then(response => { + ok(false, "Request succeed even after client.close"); + }, response => { + ok(true, "Request failed after client.close"); + do_check_eq(response.error, "connectionClosed"); + ok(response.message.match(/'hello' request packet to '.*' can't be sent as the connection is closed./)); + run_next_test(); + }); +} + +function test_client_request_after_close_callback() +{ + // Test that DebuggerClient.request fails after we called client.close() + // (with callback API) + let request = gClient.request({ + to: gActorId, + type: "hello" + }, response => { + ok(true, "Request failed after client.close"); + do_check_eq(response.error, "connectionClosed"); + ok(response.message.match(/'hello' request packet to '.*' can't be sent as the connection is closed./)); + run_next_test(); + }); +} diff --git a/devtools/server/tests/unit/test_conditional_breakpoint-01.js b/devtools/server/tests/unit/test_conditional_breakpoint-01.js new file mode 100644 index 000000000..4661bb0c4 --- /dev/null +++ b/devtools/server/tests/unit/test_conditional_breakpoint-01.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check conditional breakpoint when condition evaluates to true. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-conditional-breakpoint"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-conditional-breakpoint", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_breakpoint(); + }); + }); + do_test_pending(); +} + +function test_simple_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + source.setBreakpoint({ + line: 3, + condition: "a === 1" + }, function (aResponse, bpClient) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.frame.where.line, 3); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + }); + // Continue until the breakpoint is hit. + gThreadClient.resume(); + + }); + + }); + + Components.utils.evalInSandbox("debugger;\n" + // 1 + "var a = 1;\n" + // 2 + "var b = 2;\n", // 3 + gDebuggee, + "1.8", + "test.js", + 1); +} diff --git a/devtools/server/tests/unit/test_conditional_breakpoint-02.js b/devtools/server/tests/unit/test_conditional_breakpoint-02.js new file mode 100644 index 000000000..873f76159 --- /dev/null +++ b/devtools/server/tests/unit/test_conditional_breakpoint-02.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check conditional breakpoint when condition evaluates to false. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-conditional-breakpoint"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-conditional-breakpoint", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_breakpoint(); + }); + }); + do_test_pending(); +} + +function test_simple_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + source.setBreakpoint({ + line: 3, + condition: "a === 2" + }, function (aResponse, bpClient) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.why.type, "debuggerStatement"); + do_check_eq(aPacket.frame.where.line, 4); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + }); + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + }); + + Components.utils.evalInSandbox("debugger;\n" + // 1 + "var a = 1;\n" + // 2 + "var b = 2;\n" + // 3 + "debugger;", // 4 + gDebuggee, + "1.8", + "test.js", + 1); +} diff --git a/devtools/server/tests/unit/test_conditional_breakpoint-03.js b/devtools/server/tests/unit/test_conditional_breakpoint-03.js new file mode 100644 index 000000000..d9cf13e00 --- /dev/null +++ b/devtools/server/tests/unit/test_conditional_breakpoint-03.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check conditional breakpoint when condition throws and make sure it pauses + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-conditional-breakpoint"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-conditional-breakpoint", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_breakpoint(); + }); + }); + do_test_pending(); +} + +function test_simple_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + source.setBreakpoint({ + line: 3, + condition: "throw new Error()" + }, function (aResponse, bpClient) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.why.type, "breakpointConditionThrown"); + do_check_eq(aPacket.frame.where.line, 3); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + }); + // Continue until the breakpoint is hit. + gThreadClient.resume(); + + }); + + }); + + Components.utils.evalInSandbox("debugger;\n" + // 1 + "var a = 1;\n" + // 2 + "var b = 2;\n", // 3 + gDebuggee, + "1.8", + "test.js", + 1); +} diff --git a/devtools/server/tests/unit/test_dbgactor.js b/devtools/server/tests/unit/test_dbgactor.js new file mode 100644 index 000000000..b22b8446b --- /dev/null +++ b/devtools/server/tests/unit/test_dbgactor.js @@ -0,0 +1,116 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gClient; +var gDebuggee; + +const xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector); + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = testGlobal("test-1"); + DebuggerServer.addTestGlobal(gDebuggee); + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.addListener("connected", function (aEvent, aType, aTraits) { + gClient.listTabs((aResponse) => { + do_check_true("tabs" in aResponse); + for (let tab of aResponse.tabs) { + if (tab.title == "test-1") { + test_attach_tab(tab.actor); + return false; + } + } + do_check_true(false); // We should have found our tab in the list. + return undefined; + }); + }); + + gClient.connect(); + + do_test_pending(); +} + +// Attach to |aTabActor|, and check the response. +function test_attach_tab(aTabActor) +{ + gClient.request({ to: aTabActor, type: "attach" }, function (aResponse) { + do_check_false("error" in aResponse); + do_check_eq(aResponse.from, aTabActor); + do_check_eq(aResponse.type, "tabAttached"); + do_check_true(typeof aResponse.threadActor === "string"); + + test_attach_thread(aResponse.threadActor); + }); +} + +// Attach to |aThreadActor|, check the response, and resume it. +function test_attach_thread(aThreadActor) +{ + gClient.request({ to: aThreadActor, type: "attach" }, function (aResponse) { + do_check_false("error" in aResponse); + do_check_eq(aResponse.from, aThreadActor); + do_check_eq(aResponse.type, "paused"); + do_check_true("why" in aResponse); + do_check_eq(aResponse.why.type, "attached"); + + test_resume_thread(aThreadActor); + }); +} + +// Resume |aThreadActor|, and see that it stops at the 'debugger' +// statement. +function test_resume_thread(aThreadActor) +{ + // Allow the client to resume execution. + gClient.request({ to: aThreadActor, type: "resume" }, function (aResponse) { + do_check_false("error" in aResponse); + do_check_eq(aResponse.from, aThreadActor); + do_check_eq(aResponse.type, "resumed"); + + do_check_eq(xpcInspector.eventLoopNestLevel, 0); + + // Now that we know we're resumed, we can make the debuggee do something. + Cu.evalInSandbox("var a = true; var b = false; debugger; var b = true;", gDebuggee); + // Now make sure that we've run the code after the debugger statement... + do_check_true(gDebuggee.b); + }); + + gClient.addListener("paused", function (aName, aPacket) { + do_check_eq(aName, "paused"); + do_check_false("error" in aPacket); + do_check_eq(aPacket.from, aThreadActor); + do_check_eq(aPacket.type, "paused"); + do_check_true("actor" in aPacket); + do_check_true("why" in aPacket); + do_check_eq(aPacket.why.type, "debuggerStatement"); + + // Reach around the protocol to check that the debuggee is in the state + // we expect. + do_check_true(gDebuggee.a); + do_check_false(gDebuggee.b); + + do_check_eq(xpcInspector.eventLoopNestLevel, 1); + + // Let the debuggee continue execution. + gClient.request({ to: aThreadActor, type: "resume" }, cleanup); + }); +} + +function cleanup() +{ + gClient.addListener("closed", function (aEvent, aResult) { + do_test_finished(); + }); + + try { + let xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector); + do_check_eq(xpcInspector.eventLoopNestLevel, 0); + } catch (e) { + dump(e); + } + + gClient.close(); +} diff --git a/devtools/server/tests/unit/test_dbgclient_debuggerstatement.js b/devtools/server/tests/unit/test_dbgclient_debuggerstatement.js new file mode 100644 index 000000000..40468cb1d --- /dev/null +++ b/devtools/server/tests/unit/test_dbgclient_debuggerstatement.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gClient; +var gTabClient; +var gDebuggee; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = testGlobal("test-1"); + DebuggerServer.addTestGlobal(gDebuggee); + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(function ([aType, aTraits]) { + attachTestTab(gClient, "test-1", function (aReply, aTabClient) { + gTabClient = aTabClient; + test_threadAttach(aReply.threadActor); + }); + }); + do_test_pending(); +} + +function test_threadAttach(aThreadActorID) +{ + do_print("Trying to attach to thread " + aThreadActorID); + gTabClient.attachThread({}, function (aResponse, aThreadClient) { + do_check_eq(aThreadClient.state, "paused"); + do_check_eq(aThreadClient.actor, aThreadActorID); + aThreadClient.resume(function () { + do_check_eq(aThreadClient.state, "attached"); + test_debugger_statement(aThreadClient); + }); + }); +} + +function test_debugger_statement(aThreadClient) +{ + aThreadClient.addListener("paused", function (aEvent, aPacket) { + do_check_eq(aThreadClient.state, "paused"); + // Reach around the protocol to check that the debuggee is in the state + // we expect. + do_check_true(gDebuggee.a); + do_check_false(gDebuggee.b); + + let xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector); + do_check_eq(xpcInspector.eventLoopNestLevel, 1); + + aThreadClient.resume(cleanup); + }); + + Cu.evalInSandbox("var a = true; var b = false; debugger; var b = true;", gDebuggee); + + // Now make sure that we've run the code after the debugger statement... + do_check_true(gDebuggee.b); +} + +function cleanup() +{ + gClient.addListener("closed", function (aEvent) { + do_test_finished(); + }); + + try { + let xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector); + do_check_eq(xpcInspector.eventLoopNestLevel, 0); + } catch (e) { + dump(e); + } + + gClient.close(); +} diff --git a/devtools/server/tests/unit/test_dbgglobal.js b/devtools/server/tests/unit/test_dbgglobal.js new file mode 100644 index 000000000..ff4291932 --- /dev/null +++ b/devtools/server/tests/unit/test_dbgglobal.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() +{ + // Should get an exception if we try to interact with DebuggerServer + // before we initialize it... + check_except(function () { + DebuggerServer.createListener(); + }); + check_except(DebuggerServer.closeAllListeners); + check_except(DebuggerServer.connectPipe); + + // Allow incoming connections. + DebuggerServer.init(); + + // These should still fail because we haven't added a createRootActor + // implementation yet. + check_except(function () { + DebuggerServer.createListener(); + }); + check_except(DebuggerServer.closeAllListeners); + check_except(DebuggerServer.connectPipe); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + + // Now they should work. + DebuggerServer.createListener(); + DebuggerServer.closeAllListeners(); + + // Make sure we got the test's root actor all set up. + let client1 = DebuggerServer.connectPipe(); + client1.hooks = { + onPacket: function (aPacket1) { + do_check_eq(aPacket1.from, "root"); + do_check_eq(aPacket1.applicationType, "xpcshell-tests"); + + // Spin up a second connection, make sure it has its own root + // actor. + let client2 = DebuggerServer.connectPipe(); + client2.hooks = { + onPacket: function (aPacket2) { + do_check_eq(aPacket2.from, "root"); + do_check_neq(aPacket1.testConnectionPrefix, + aPacket2.testConnectionPrefix); + client2.close(); + }, + onClosed: function (aResult) { + client1.close(); + }, + }; + client2.ready(); + }, + + onClosed: function (aResult) { + do_test_finished(); + }, + }; + + client1.ready(); + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_eval-01.js b/devtools/server/tests/unit/test_eval-01.js new file mode 100644 index 000000000..b11903f87 --- /dev/null +++ b/devtools/server/tests/unit/test_eval-01.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic eval resume/re-pause + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_eval(); + }); + }); + do_test_pending(); +} + +function test_simple_eval() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let arg1Actor = aPacket.frame.arguments[0].actor; + gThreadClient.eval(null, "({ obj: true })", function (aResponse) { + do_check_eq(aResponse.type, "resumed"); + // Expect a pause notification immediately. + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value... + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "clientEvaluated"); + do_check_eq(aPacket.why.frameFinished.return.type, "object"); + do_check_eq(aPacket.why.frameFinished.return.class, "Object"); + + // Make sure the previous pause lifetime was correctly dropped. + gClient.request({ to: arg1Actor, type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "noSuchActor"); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + }); + + }); + + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1) { debugger; } + stopMe({obj: true}); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_eval-02.js b/devtools/server/tests/unit/test_eval-02.js new file mode 100644 index 000000000..386ea5c98 --- /dev/null +++ b/devtools/server/tests/unit/test_eval-02.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check eval resume/re-pause with a throw. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_throw_eval(); + }); + }); + do_test_pending(); +} + +function test_throw_eval() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.eval(null, "throw 'failure'", function (aResponse) { + do_check_eq(aResponse.type, "resumed"); + // Expect a pause notification immediately. + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value... + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "clientEvaluated"); + do_check_eq(aPacket.why.frameFinished.throw, "failure"); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1) { debugger; } + stopMe({obj: true}); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_eval-03.js b/devtools/server/tests/unit/test_eval-03.js new file mode 100644 index 000000000..2234259aa --- /dev/null +++ b/devtools/server/tests/unit/test_eval-03.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check syntax errors in an eval. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_syntax_error_eval(); + }); + }); + do_test_pending(); +} + +function test_syntax_error_eval() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.eval(null, "%$@!@#", function (aResponse) { + do_check_eq(aResponse.type, "resumed"); + // Expect a pause notification immediately. + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value... + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "clientEvaluated"); + do_check_eq(aPacket.why.frameFinished.throw.type, "object"); + do_check_eq(aPacket.why.frameFinished.throw.class, "Error"); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1) { debugger; } + stopMe({obj: true}); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_eval-04.js b/devtools/server/tests/unit/test_eval-04.js new file mode 100644 index 000000000..77cb58d97 --- /dev/null +++ b/devtools/server/tests/unit/test_eval-04.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check evals against different frames. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_syntax_error_eval(); + }); + }); + do_test_pending(); +} + +function test_syntax_error_eval() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + + gThreadClient.getFrames(0, 2, function (aResponse) { + let frame0 = aResponse.frames[0]; + let frame1 = aResponse.frames[1]; + + // Eval against the top frame... + gThreadClient.eval(frame0.actor, "arg", function (aResponse) { + do_check_eq(aResponse.type, "resumed"); + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // 'arg' should have been evaluated in frame0 + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "clientEvaluated"); + do_check_eq(aPacket.why.frameFinished.return, "arg0"); + + // Now eval against the second frame. + gThreadClient.eval(frame1.actor, "arg", function (aResponse) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // 'arg' should have been evaluated in frame1 + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.frameFinished.return, "arg1"); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + }); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function frame0(arg) { + debugger; + } + function frame1(arg) { + frame0("arg0"); + } + frame1("arg1"); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_eval-05.js b/devtools/server/tests/unit/test_eval-05.js new file mode 100644 index 000000000..b199e4afb --- /dev/null +++ b/devtools/server/tests/unit/test_eval-05.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check pauses within evals. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_syntax_error_eval(); + }); + }); + do_test_pending(); +} + +function test_syntax_error_eval() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.eval(null, "debugger", function (aResponse) { + // Expect a resume then a debuggerStatement pause. + do_check_eq(aResponse.type, "resumed"); + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "debuggerStatement"); + // Resume from the debugger statement should immediately re-pause + // with a clientEvaluated reason. + gThreadClient.resume(function (aPacket) { + do_check_eq(aPacket.type, "resumed"); + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "clientEvaluated"); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + }); + }); + }); + gDebuggee.eval("(" + function () { + function stopMe(arg) { + debugger; + } + stopMe(); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_eventlooplag_actor.js b/devtools/server/tests/unit/test_eventlooplag_actor.js new file mode 100644 index 000000000..d2acdd8f8 --- /dev/null +++ b/devtools/server/tests/unit/test_eventlooplag_actor.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test the eventLoopLag actor. + */ + +"use strict"; + +function run_test() +{ + let {EventLoopLagFront} = require("devtools/shared/fronts/eventlooplag"); + + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + // As seen in EventTracer.cpp + let threshold = 20; + let interval = 10; + + + let front; + let client = new DebuggerClient(DebuggerServer.connectPipe()); + + // Start tracking event loop lags. + client.connect().then(function () { + client.listTabs(function (resp) { + front = new EventLoopLagFront(client, resp); + front.start().then(success => { + do_check_true(success); + front.once("event-loop-lag", gotLagEvent); + do_execute_soon(lag); + }); + }); + }); + + // Force a lag + function lag() { + let start = new Date(); + let duration = threshold + interval + 1; + while (true) { + if (((new Date()) - start) > duration) { + break; + } + } + } + + // Got a lag event. The test will time out if the actor + // fails to detect the lag. + function gotLagEvent(time) { + do_print("lag: " + time); + do_check_true(time >= threshold); + front.stop().then(() => { + finishClient(client); + }); + } + + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_forwardingprefix.js b/devtools/server/tests/unit/test_forwardingprefix.js new file mode 100644 index 000000000..885a99db8 --- /dev/null +++ b/devtools/server/tests/unit/test_forwardingprefix.js @@ -0,0 +1,196 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Exercise prefix-based forwarding of packets to other transports. */ + +const { RootActor } = require("devtools/server/actors/root"); + +var gMainConnection, gMainTransport; +var gSubconnection1, gSubconnection2; +var gClient; + +function run_test() +{ + DebuggerServer.init(); + + add_test(createMainConnection); + add_test(TestNoForwardingYet); + add_test(createSubconnection1); + add_test(TestForwardPrefix1OnlyRoot); + add_test(createSubconnection2); + add_test(TestForwardPrefix12OnlyRoot); + add_test(TestForwardPrefix12WithActor1); + add_test(TestForwardPrefix12WithActor12); + run_next_test(); +} + +/* + * Create a pipe connection, and return an object |{ conn, transport }|, + * where |conn| is the new DebuggerServerConnection instance, and + * |transport| is the client side of the transport on which it communicates + * (that is, packets sent on |transport| go to the new connection, and + * |transport|'s hooks receive replies). + * + * |aPrefix| is optional; if present, it's the prefix (minus the '/') for + * actors in the new connection. + */ +function newConnection(aPrefix) +{ + var conn; + DebuggerServer.createRootActor = function (aConn) { + conn = aConn; + return new RootActor(aConn, {}); + }; + + var transport = DebuggerServer.connectPipe(aPrefix); + + return { conn: conn, transport: transport }; +} + +/* Create the main connection for these tests. */ +function createMainConnection() +{ + ({ conn: gMainConnection, transport: gMainTransport } = newConnection()); + gClient = new DebuggerClient(gMainTransport); + gClient.connect().then(([aType, aTraits]) => run_next_test()); +} + +/* + * Exchange 'echo' messages with five actors: + * - root + * - prefix1/root + * - prefix1/actor + * - prefix2/root + * - prefix2/actor + * + * Expect proper echos from those named in |aReachables|, and 'noSuchActor' + * errors from the others. When we've gotten all our replies (errors or + * otherwise), call |aCompleted|. + * + * To avoid deep stacks, we call aCompleted from the next tick. + */ +function tryActors(aReachables, aCompleted) { + let count = 0; + + let outerActor; + for (outerActor of [ "root", + "prefix1/root", "prefix1/actor", + "prefix2/root", "prefix2/actor" ]) { + /* + * Let each callback capture its own iteration's value; outerActor is + * local to the whole loop, not to a single iteration. + */ + let actor = outerActor; + + count++; + + gClient.request({ to: actor, type: "echo", value: "tango"}, // phone home + (aResponse) => { + if (aReachables.has(actor)) + do_check_matches({ from: actor, to: actor, type: "echo", value: "tango" }, aResponse); + else + do_check_matches({ from: actor, error: "noSuchActor", message: "No such actor for ID: " + actor }, aResponse); + + if (--count == 0) + do_execute_soon(aCompleted, "tryActors callback " + aCompleted.name); + }); + } +} + +/* + * With no forwarding established, sending messages to root should work, + * but sending messages to prefixed actor names, or anyone else, should get + * an error. + */ +function TestNoForwardingYet() +{ + tryActors(new Set(["root"]), run_next_test); +} + +/* + * Create a new pipe connection which forwards its reply packets to + * gMainConnection's client, and to which gMainConnection forwards packets + * directed to actors whose names begin with |aPrefix + '/'|, and. + * + * Return an object { conn, transport }, as for newConnection. + */ +function newSubconnection(aPrefix) +{ + let { conn, transport } = newConnection(aPrefix); + transport.hooks = { + onPacket: (aPacket) => gMainConnection.send(aPacket), + onClosed: () => {} + }; + gMainConnection.setForwarding(aPrefix, transport); + + return { conn: conn, transport: transport }; +} + +/* Create a second root actor, to which we can forward things. */ +function createSubconnection1() +{ + let { conn, transport } = newSubconnection("prefix1"); + gSubconnection1 = conn; + transport.ready(); + gClient.expectReply("prefix1/root", (aReply) => run_next_test()); +} + +// Establish forwarding, but don't put any actors in that server. +function TestForwardPrefix1OnlyRoot() +{ + tryActors(new Set(["root", "prefix1/root"]), run_next_test); +} + +/* Create a third root actor, to which we can forward things. */ +function createSubconnection2() +{ + let { conn, transport } = newSubconnection("prefix2"); + gSubconnection2 = conn; + transport.ready(); + gClient.expectReply("prefix2/root", (aReply) => run_next_test()); +} + +function TestForwardPrefix12OnlyRoot() +{ + tryActors(new Set(["root", "prefix1/root", "prefix2/root"]), run_next_test); +} + +// A dumb actor that implements 'echo'. +// +// It's okay that both subconnections' actors behave identically, because +// the reply-sending code attaches the replying actor's name to the packet, +// so simply matching the 'from' field in the reply ensures that we heard +// from the right actor. +function EchoActor(aConnection) +{ + this.conn = aConnection; +} +EchoActor.prototype.actorPrefix = "EchoActor"; +EchoActor.prototype.onEcho = function (aRequest) { + /* + * Request packets are frozen. Copy aRequest, so that + * DebuggerServerConnection.onPacket can attach a 'from' property. + */ + return JSON.parse(JSON.stringify(aRequest)); +}; +EchoActor.prototype.requestTypes = { + "echo": EchoActor.prototype.onEcho +}; + +function TestForwardPrefix12WithActor1() +{ + let actor = new EchoActor(gSubconnection1); + actor.actorID = "prefix1/actor"; + gSubconnection1.addActor(actor); + + tryActors(new Set(["root", "prefix1/root", "prefix1/actor", "prefix2/root"]), run_next_test); +} + +function TestForwardPrefix12WithActor12() +{ + let actor = new EchoActor(gSubconnection2); + actor.actorID = "prefix2/actor"; + gSubconnection2.addActor(actor); + + tryActors(new Set(["root", "prefix1/root", "prefix1/actor", "prefix2/root", "prefix2/actor"]), run_next_test); +} diff --git a/devtools/server/tests/unit/test_frameactor-01.js b/devtools/server/tests/unit/test_frameactor-01.js new file mode 100644 index 000000000..ad37a8ab5 --- /dev/null +++ b/devtools/server/tests/unit/test_frameactor-01.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Verify that we get a frame actor along with a debugger statement. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_true(!!aPacket.frame); + do_check_true(!!aPacket.frame.actor); + do_check_eq(aPacket.frame.callee.name, "stopMe"); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe() { + debugger; + } + stopMe(); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_frameactor-02.js b/devtools/server/tests/unit/test_frameactor-02.js new file mode 100644 index 000000000..f2890adac --- /dev/null +++ b/devtools/server/tests/unit/test_frameactor-02.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Verify that two pauses in a row will keep the same frame actor. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket1) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket2) { + do_check_eq(aPacket1.frame.actor, aPacket2.frame.actor); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + gThreadClient.resume(); + }); + + gDebuggee.eval("(" + function () { + function stopMe() { + debugger; + debugger; + } + stopMe(); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_frameactor-03.js b/devtools/server/tests/unit/test_frameactor-03.js new file mode 100644 index 000000000..0d7739d5a --- /dev/null +++ b/devtools/server/tests/unit/test_frameactor-03.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Verify that a frame actor is properly expired when the frame goes away. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket1) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket2) { + let poppedFrames = aPacket2.poppedFrames; + do_check_eq(typeof (poppedFrames), typeof ([])); + do_check_true(poppedFrames.indexOf(aPacket1.frame.actor) >= 0); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + gThreadClient.resume(); + }); + + gDebuggee.eval("(" + function () { + function stopMe() { + debugger; + } + stopMe(); + debugger; + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_frameactor-04.js b/devtools/server/tests/unit/test_frameactor-04.js new file mode 100644 index 000000000..b4faa96e0 --- /dev/null +++ b/devtools/server/tests/unit/test_frameactor-04.js @@ -0,0 +1,91 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Verify the "frames" request on the thread. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +var gFrames = [ + // Function calls... + { type: "call", callee: { name: "depth3" } }, + { type: "call", callee: { name: "depth2" } }, + { type: "call", callee: { name: "depth1" } }, + + // Anonymous function call in our eval... + { type: "call", callee: { name: undefined } }, + + // The eval itself. + { type: "eval", callee: { name: undefined } }, +]; + +var gSliceTests = [ + { start: 0, count: undefined, resetActors: true }, + { start: 0, count: 1 }, + { start: 2, count: 2 }, + { start: 1, count: 15 }, + { start: 15, count: undefined }, +]; + +function test_frame_slice() { + if (gSliceTests.length == 0) { + gThreadClient.resume(function () { finishClient(gClient); }); + return; + } + + let test = gSliceTests.shift(); + gThreadClient.getFrames(test.start, test.count, function (aResponse) { + var testFrames = gFrames.slice(test.start, test.count ? test.start + test.count : undefined); + do_check_eq(testFrames.length, aResponse.frames.length); + for (var i = 0; i < testFrames.length; i++) { + let expected = testFrames[i]; + let actual = aResponse.frames[i]; + + if (test.resetActors) { + expected.actor = actual.actor; + } + + for (let key of ["type", "callee-name"]) { + do_check_eq(expected[key] || undefined, actual[key]); + } + } + test_frame_slice(); + }); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket1) { + test_frame_slice(); + }); + + gDebuggee.eval("(" + function () { + function depth3() { + debugger; + } + function depth2() { + depth3(); + } + function depth1() { + depth2(); + } + depth1(); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_frameactor-05.js b/devtools/server/tests/unit/test_frameactor-05.js new file mode 100644 index 000000000..feece598e --- /dev/null +++ b/devtools/server/tests/unit/test_frameactor-05.js @@ -0,0 +1,89 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Verify that frame actors retrieved with the frames request + * are included in the pause packet's popped-frames property. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_frame_slice() { + if (gSliceTests.length == 0) { + gThreadClient.resume(function () { finishClient(gClient); }); + return; + } + + let test = gSliceTests.shift(); + gThreadClient.getFrames(test.start, test.count, function (aResponse) { + var testFrames = gFrames.slice(test.start, test.count ? test.start + test.count : undefined); + do_check_eq(testFrames.length, aResponse.frames.length); + for (var i = 0; i < testFrames.length; i++) { + let expected = testFrames[i]; + let actual = aResponse.frames[i]; + + if (test.resetActors) { + expected.actor = actual.actor; + } + + for (var key in expected) { + do_check_eq(expected[key], actual[key]); + } + } + test_frame_slice(); + }); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket1) { + gThreadClient.getFrames(0, null, function (aFrameResponse) { + do_check_eq(aFrameResponse.frames.length, 5); + // Now wait for the next pause, after which the three + // youngest actors should be popped.. + let expectPopped = aFrameResponse.frames.slice(0, 3).map(frame => frame.actor); + expectPopped.sort(); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPausePacket) { + let popped = aPausePacket.poppedFrames.sort(); + do_check_eq(popped.length, 3); + for (let i = 0; i < 3; i++) { + do_check_eq(expectPopped[i], popped[i]); + } + + gThreadClient.resume(function () { finishClient(gClient); }); + }); + gThreadClient.resume(); + }); + }); + + gDebuggee.eval("(" + function () { + function depth3() { + debugger; + } + function depth2() { + depth3(); + } + function depth1() { + depth2(); + } + depth1(); + debugger; + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_framearguments-01.js b/devtools/server/tests/unit/test_framearguments-01.js new file mode 100644 index 000000000..e075d2c25 --- /dev/null +++ b/devtools/server/tests/unit/test_framearguments-01.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check a frame actor's arguments property. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame["arguments"]; + do_check_eq(args.length, 6); + do_check_eq(args[0], 42); + do_check_eq(args[1], true); + do_check_eq(args[2], "nasu"); + do_check_eq(args[3].type, "null"); + do_check_eq(args[4].type, "undefined"); + do_check_eq(args[5].type, "object"); + do_check_eq(args[5].class, "Object"); + do_check_true(!!args[5].actor); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(aNumber, aBool, aString, aNull, aUndefined, aObject) { + debugger; + } + stopMe(42, true, "nasu", null, undefined, { foo: "bar" }); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_framebindings-01.js b/devtools/server/tests/unit/test_framebindings-01.js new file mode 100644 index 000000000..ae62f8ff1 --- /dev/null +++ b/devtools/server/tests/unit/test_framebindings-01.js @@ -0,0 +1,77 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check a frame actor's bindings property. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let bindings = aPacket.frame.environment.bindings; + let args = bindings.arguments; + let vars = bindings.variables; + + do_check_eq(args.length, 6); + do_check_eq(args[0].aNumber.value, 42); + do_check_eq(args[1].aBool.value, true); + do_check_eq(args[2].aString.value, "nasu"); + do_check_eq(args[3].aNull.value.type, "null"); + do_check_eq(args[4].aUndefined.value.type, "undefined"); + do_check_eq(args[5].aObject.value.type, "object"); + do_check_eq(args[5].aObject.value.class, "Object"); + do_check_true(!!args[5].aObject.value.actor); + + do_check_eq(vars.a.value, 1); + do_check_eq(vars.b.value, true); + do_check_eq(vars.c.value.type, "object"); + do_check_eq(vars.c.value.class, "Object"); + do_check_true(!!vars.c.value.actor); + + let objClient = gThreadClient.pauseGrip(vars.c.value); + objClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.a.configurable, true); + do_check_eq(aResponse.ownProperties.a.enumerable, true); + do_check_eq(aResponse.ownProperties.a.writable, true); + do_check_eq(aResponse.ownProperties.a.value, "a"); + + do_check_eq(aResponse.ownProperties.b.configurable, true); + do_check_eq(aResponse.ownProperties.b.enumerable, true); + do_check_eq(aResponse.ownProperties.b.writable, true); + do_check_eq(aResponse.ownProperties.b.value.type, "undefined"); + do_check_false("class" in aResponse.ownProperties.b.value); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(aNumber, aBool, aString, aNull, aUndefined, aObject) { + var a = 1; + var b = true; + var c = { a: "a", b: undefined }; + debugger; + } + stopMe(42, true, "nasu", null, undefined, { foo: "bar" }); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_framebindings-02.js b/devtools/server/tests/unit/test_framebindings-02.js new file mode 100644 index 000000000..552670349 --- /dev/null +++ b/devtools/server/tests/unit/test_framebindings-02.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check a frame actor's parent bindings. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let parentEnv = aPacket.frame.environment.parent; + let bindings = parentEnv.bindings; + let args = bindings.arguments; + let vars = bindings.variables; + do_check_neq(parentEnv, undefined); + do_check_eq(args.length, 0); + do_check_eq(vars.stopMe.value.type, "object"); + do_check_eq(vars.stopMe.value.class, "Function"); + do_check_true(!!vars.stopMe.value.actor); + + // Skip the global lexical scope. + parentEnv = parentEnv.parent.parent; + do_check_neq(parentEnv, undefined); + let objClient = gThreadClient.pauseGrip(parentEnv.object); + objClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.Object.value.type, "object"); + do_check_eq(aResponse.ownProperties.Object.value.class, "Function"); + do_check_true(!!aResponse.ownProperties.Object.value.actor); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(aNumber, aBool, aString, aNull, aUndefined, aObject) { + var a = 1; + var b = true; + var c = { a: "a" }; + debugger; + } + stopMe(42, true, "nasu", null, undefined, { foo: "bar" }); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_framebindings-03.js b/devtools/server/tests/unit/test_framebindings-03.js new file mode 100644 index 000000000..1ec51570d --- /dev/null +++ b/devtools/server/tests/unit/test_framebindings-03.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check a |with| frame actor's bindings. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let env = aPacket.frame.environment; + do_check_neq(env, undefined); + + let parentEnv = env.parent; + do_check_neq(parentEnv, undefined); + + let bindings = parentEnv.bindings; + let args = bindings.arguments; + let vars = bindings.variables; + do_check_eq(args.length, 1); + do_check_eq(args[0].aNumber.value, 10); + do_check_eq(vars.r.value, 10); + do_check_eq(vars.a.value, Math.PI * 100); + do_check_eq(vars.arguments.value.class, "Arguments"); + do_check_true(!!vars.arguments.value.actor); + + let objClient = gThreadClient.pauseGrip(env.object); + objClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.PI.value, Math.PI); + do_check_eq(aResponse.ownProperties.cos.value.type, "object"); + do_check_eq(aResponse.ownProperties.cos.value.class, "Function"); + do_check_true(!!aResponse.ownProperties.cos.value.actor); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(aNumber) { + var a; + var r = aNumber; + with (Math) { + a = PI * r * r; + debugger; + } + } + stopMe(10); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_framebindings-04.js b/devtools/server/tests/unit/test_framebindings-04.js new file mode 100644 index 000000000..963a12055 --- /dev/null +++ b/devtools/server/tests/unit/test_framebindings-04.js @@ -0,0 +1,84 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check the environment bindongs of a |with| within a |with|. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let env = aPacket.frame.environment; + do_check_neq(env, undefined); + + let objClient = gThreadClient.pauseGrip(env.object); + objClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.one.value, 1); + do_check_eq(aResponse.ownProperties.two.value, 2); + do_check_eq(aResponse.ownProperties.foo, undefined); + + let parentEnv = env.parent; + do_check_neq(parentEnv, undefined); + + let parentClient = gThreadClient.pauseGrip(parentEnv.object); + parentClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.PI.value, Math.PI); + do_check_eq(aResponse.ownProperties.cos.value.type, "object"); + do_check_eq(aResponse.ownProperties.cos.value.class, "Function"); + do_check_true(!!aResponse.ownProperties.cos.value.actor); + + parentEnv = parentEnv.parent; + do_check_neq(parentEnv, undefined); + + let bindings = parentEnv.bindings; + let args = bindings.arguments; + let vars = bindings.variables; + do_check_eq(args.length, 1); + do_check_eq(args[0].aNumber.value, 10); + do_check_eq(vars.r.value, 10); + do_check_eq(vars.a.value, Math.PI * 100); + do_check_eq(vars.arguments.value.class, "Arguments"); + do_check_true(!!vars.arguments.value.actor); + do_check_eq(vars.foo.value, 2 * Math.PI); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + + }); + + gDebuggee.eval("(" + function () { + function stopMe(aNumber) { + var a, obj = { one: 1, two: 2 }; + var r = aNumber; + with (Math) { + a = PI * r * r; + with (obj) { + var foo = two * PI; + debugger; + } + } + } + stopMe(10); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_framebindings-05.js b/devtools/server/tests/unit/test_framebindings-05.js new file mode 100644 index 000000000..9827c617a --- /dev/null +++ b/devtools/server/tests/unit/test_framebindings-05.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check the environment bindings of a |with| in global scope. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let env = aPacket.frame.environment; + do_check_neq(env, undefined); + + let objClient = gThreadClient.pauseGrip(env.object); + objClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.PI.value, Math.PI); + do_check_eq(aResponse.ownProperties.cos.value.type, "object"); + do_check_eq(aResponse.ownProperties.cos.value.class, "Function"); + do_check_true(!!aResponse.ownProperties.cos.value.actor); + + // Skip the global lexical scope. + let parentEnv = env.parent.parent; + do_check_neq(parentEnv, undefined); + + let parentClient = gThreadClient.pauseGrip(parentEnv.object); + parentClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.a.value, Math.PI * 100); + do_check_eq(aResponse.ownProperties.r.value, 10); + do_check_eq(aResponse.ownProperties.Object.value.type, "object"); + do_check_eq(aResponse.ownProperties.Object.value.class, "Function"); + do_check_true(!!aResponse.ownProperties.Object.value.actor); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + }); + + gDebuggee.eval("var a, r = 10;\n" + + "with (Math) {\n" + + " a = PI * r * r;\n" + + " debugger;\n" + + "}"); +} diff --git a/devtools/server/tests/unit/test_framebindings-06.js b/devtools/server/tests/unit/test_framebindings-06.js new file mode 100644 index 000000000..9d8478a29 --- /dev/null +++ b/devtools/server/tests/unit/test_framebindings-06.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_banana_environment(); + }); + }); + do_test_pending(); +} + +function test_banana_environment() +{ + + gThreadClient.addOneTimeListener("paused", + function (aEvent, aPacket) { + equal(aPacket.type, "paused"); + let env = aPacket.frame.environment; + equal(env.type, "function"); + equal(env.function.name, "banana3"); + let parent = env.parent; + equal(parent.type, "block"); + ok("banana3" in parent.bindings.variables); + parent = parent.parent; + equal(parent.type, "function"); + equal(parent.function.name, "banana2"); + parent = parent.parent; + equal(parent.type, "block"); + ok("banana2" in parent.bindings.variables); + parent = parent.parent; + equal(parent.type, "function"); + equal(parent.function.name, "banana"); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + gDebuggee.eval("\ + function banana(x) { \n\ + return function banana2(y) { \n\ + return function banana3(z) { \n\ + debugger; \n\ + }; \n\ + }; \n\ + } \n\ + banana('x')('y')('z'); \n\ + "); +} diff --git a/devtools/server/tests/unit/test_framebindings-07.js b/devtools/server/tests/unit/test_framebindings-07.js new file mode 100644 index 000000000..bdfc36d97 --- /dev/null +++ b/devtools/server/tests/unit/test_framebindings-07.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +// Test that the EnvironmentClient's getBindings() method works as expected. +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-bindings"); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-bindings", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_banana_environment(); + }); + }); + do_test_pending(); +} + +function test_banana_environment() +{ + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let environment = aPacket.frame.environment; + do_check_eq(environment.type, "function"); + + let parent = environment.parent; + do_check_eq(parent.type, "block"); + + let grandpa = parent.parent; + do_check_eq(grandpa.type, "function"); + + let envClient = gThreadClient.environment(environment); + envClient.getBindings(aResponse => { + do_check_eq(aResponse.bindings.arguments[0].z.value, "z"); + + let parentClient = gThreadClient.environment(parent); + parentClient.getBindings(aResponse => { + do_check_eq(aResponse.bindings.variables.banana3.value.class, "Function"); + + let grandpaClient = gThreadClient.environment(grandpa); + grandpaClient.getBindings(aResponse => { + do_check_eq(aResponse.bindings.arguments[0].y.value, "y"); + gThreadClient.resume(() => finishClient(gClient)); + }); + }); + }); + }); + + gDebuggee.eval("\ + function banana(x) { \n\ + return function banana2(y) { \n\ + return function banana3(z) { \n\ + debugger; \n\ + }; \n\ + }; \n\ + } \n\ + banana('x')('y')('z'); \n\ + "); +} diff --git a/devtools/server/tests/unit/test_frameclient-01.js b/devtools/server/tests/unit/test_frameclient-01.js new file mode 100644 index 000000000..a441c9ade --- /dev/null +++ b/devtools/server/tests/unit/test_frameclient-01.js @@ -0,0 +1,53 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("framesadded", function () { + do_check_eq(gThreadClient.cachedFrames.length, 3); + do_check_true(gThreadClient.moreFrames); + do_check_false(gThreadClient.fillFrames(3)); + + do_check_true(gThreadClient.fillFrames(30)); + gThreadClient.addOneTimeListener("framesadded", function () { + do_check_false(gThreadClient.moreFrames); + do_check_eq(gThreadClient.cachedFrames.length, 7); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + do_check_true(gThreadClient.fillFrames(3)); + }); + + gDebuggee.eval("(" + function () { + var recurseLeft = 5; + function recurse() { + if (--recurseLeft == 0) { + debugger; + return; + } + recurse(); + } + recurse(); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_frameclient-02.js b/devtools/server/tests/unit/test_frameclient-02.js new file mode 100644 index 000000000..a257e5960 --- /dev/null +++ b/devtools/server/tests/unit/test_frameclient-02.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Ask for exactly the number of frames we expect. + gThreadClient.addOneTimeListener("framesadded", function () { + do_check_false(gThreadClient.moreFrames); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + do_check_true(gThreadClient.fillFrames(3)); + }); + + gDebuggee.eval("(" + function () { + var recurseLeft = 1; + function recurse() { + if (--recurseLeft == 0) { + debugger; + return; + } + recurse(); + } + recurse(); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_functiongrips-01.js b/devtools/server/tests/unit/test_functiongrips-01.js new file mode 100644 index 000000000..c41a7cad5 --- /dev/null +++ b/devtools/server/tests/unit/test_functiongrips-01.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_named_function(); + }); + }); + do_test_pending(); +} + +function test_named_function() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + do_check_eq(args[0].class, "Function"); + do_check_eq(args[0].name, "stopMe"); + do_check_eq(args[0].displayName, "stopMe"); + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getParameterNames(function (aResponse) { + do_check_eq(aResponse.parameterNames.length, 1); + do_check_eq(aResponse.parameterNames[0], "arg1"); + + gThreadClient.resume(test_inferred_name_function); + }); + + }); + + gDebuggee.eval("stopMe(stopMe)"); +} + +function test_inferred_name_function() { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + do_check_eq(args[0].class, "Function"); + // No name for an anonymous function, but it should have an inferred name. + do_check_eq(args[0].name, undefined); + do_check_eq(args[0].displayName, "o.m"); + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getParameterNames(function (aResponse) { + do_check_eq(aResponse.parameterNames.length, 3); + do_check_eq(aResponse.parameterNames[0], "foo"); + do_check_eq(aResponse.parameterNames[1], "bar"); + do_check_eq(aResponse.parameterNames[2], "baz"); + + gThreadClient.resume(test_anonymous_function); + }); + }); + + gDebuggee.eval("var o = { m: function(foo, bar, baz) { } }; stopMe(o.m)"); +} + +function test_anonymous_function() { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + do_check_eq(args[0].class, "Function"); + // No name for an anonymous function, and no inferred name, either. + do_check_eq(args[0].name, undefined); + do_check_eq(args[0].displayName, undefined); + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getParameterNames(function (aResponse) { + do_check_eq(aResponse.parameterNames.length, 3); + do_check_eq(aResponse.parameterNames[0], "foo"); + do_check_eq(aResponse.parameterNames[1], "bar"); + do_check_eq(aResponse.parameterNames[2], "baz"); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + + gDebuggee.eval("stopMe(function(foo, bar, baz) { })"); +} + diff --git a/devtools/server/tests/unit/test_get-executable-lines-source-map.js b/devtools/server/tests/unit/test_get-executable-lines-source-map.js new file mode 100644 index 000000000..bca8eebee --- /dev/null +++ b/devtools/server/tests/unit/test_get-executable-lines-source-map.js @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test if getExecutableLines return correct information + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const SOURCE_MAPPED_FILE = getFileUrl("sourcemapped.js"); + +function run_test() { + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-get-executable-lines"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function _onConnect() { + attachTestTabAndResume( + gClient, + "test-get-executable-lines", + function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_executable_lines(); + } + ); + }); + + do_test_pending(); +} + +function test_executable_lines() { + gThreadClient.addOneTimeListener("newSource", function _onNewSource(evt, packet) { + do_check_eq(evt, "newSource"); + + gThreadClient.getSources(function ({error, sources}) { + do_check_true(!error); + let source = gThreadClient.source(sources[0]); + source.getExecutableLines(function (lines) { + do_check_true(arrays_equal([1, 2, 4, 6], lines)); + finishClient(gClient); + }); + }); + }); + + let code = readFile("sourcemapped.js") + "\n//# sourceMappingURL=" + + getFileUrl("source-map-data/sourcemapped.map"); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + SOURCE_MAPPED_FILE, 1); +} + +function arrays_equal(a, b) { + return !(a < b || b < a); +} diff --git a/devtools/server/tests/unit/test_get-executable-lines.js b/devtools/server/tests/unit/test_get-executable-lines.js new file mode 100644 index 000000000..233fb6ada --- /dev/null +++ b/devtools/server/tests/unit/test_get-executable-lines.js @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test if getExecutableLines return correct information + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const SOURCE_MAPPED_FILE = getFileUrl("sourcemapped.js"); + +function run_test() { + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-get-executable-lines"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function _onConnect() { + attachTestTabAndResume( + gClient, + "test-get-executable-lines", + function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_executable_lines(); + } + ); + }); + + do_test_pending(); +} + +function test_executable_lines() { + gThreadClient.addOneTimeListener("newSource", function _onNewSource(evt, packet) { + do_check_eq(evt, "newSource"); + + gThreadClient.getSources(function ({error, sources}) { + do_check_true(!error); + let source = gThreadClient.source(sources[0]); + source.getExecutableLines(function (lines) { + do_check_true(arrays_equal([2, 5, 7, 8, 10, 12, 14, 16], lines)); + finishClient(gClient); + }); + }); + }); + + let code = readFile("sourcemapped.js"); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + SOURCE_MAPPED_FILE, 1); +} + +function arrays_equal(a, b) { + return !(a < b || b < a); +} diff --git a/devtools/server/tests/unit/test_getRuleText.js b/devtools/server/tests/unit/test_getRuleText.js new file mode 100644 index 000000000..fe735928d --- /dev/null +++ b/devtools/server/tests/unit/test_getRuleText.js @@ -0,0 +1,137 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const {getRuleText} = require("devtools/server/actors/styles"); + +const TEST_DATA = [ + { + desc: "Empty input", + input: "", + line: 1, + column: 1, + throws: true + }, + { + desc: "Simplest test case", + input: "#id{color:red;background:yellow;}", + line: 1, + column: 1, + expected: {offset: 4, text: "color:red;background:yellow;"} + }, + { + desc: "Multiple rules test case", + input: "#id{color:red;background:yellow;}.class-one .class-two { position:absolute; line-height: 45px}", + line: 1, + column: 34, + expected: {offset: 56, text: " position:absolute; line-height: 45px"} + }, + { + desc: "Unclosed rule", + input: "#id{color:red;background:yellow;", + line: 1, + column: 1, + expected: {offset: 4, text: "color:red;background:yellow;"} + }, + { + desc: "Null input", + input: null, + line: 1, + column: 1, + throws: true + }, + { + desc: "Missing loc", + input: "#id{color:red;background:yellow;}", + throws: true + }, + { + desc: "Multi-lines CSS", + input: [ + "/* this is a multi line css */", + "body {", + " color: green;", + " background-repeat: no-repeat", + "}", + " /*something else here */", + "* {", + " color: purple;", + "}" + ].join("\n"), + line: 7, + column: 1, + expected: {offset: 116, text: "\n color: purple;\n"} + }, + { + desc: "Multi-lines CSS and multi-line rule", + input: [ + "/* ", + "* some comments", + "*/", + "", + "body {", + " margin: 0;", + " padding: 15px 15px 2px 15px;", + " color: red;", + "}", + "", + "#header .btn, #header .txt {", + " font-size: 100%;", + "}", + "", + "#header #information {", + " color: #dddddd;", + " font-size: small;", + "}", + ].join("\n"), + line: 5, + column: 1, + expected: { + offset: 30, + text: "\n margin: 0;\n padding: 15px 15px 2px 15px;\n color: red;\n"} + }, + { + desc: "Content string containing a } character", + input: " #id{border:1px solid red;content: '}';color:red;}", + line: 1, + column: 4, + expected: {offset: 7, text: "border:1px solid red;content: '}';color:red;"} + }, + { + desc: "Rule contains no tokens", + input: "div{}", + line: 1, + column: 1, + expected: {offset: 4, text: ""} + }, +]; + +function run_test() { + for (let test of TEST_DATA) { + do_print("Starting test: " + test.desc); + do_print("Input string " + test.input); + let output; + try { + output = getRuleText(test.input, test.line, test.column); + if (test.throws) { + do_print("Test should have thrown"); + do_check_true(false); + } + } catch (e) { + do_print("getRuleText threw an exception with the given input string"); + if (test.throws) { + do_print("Exception expected"); + do_check_true(true); + } else { + do_print("Exception unexpected\n" + e); + do_check_true(false); + } + } + if (output) { + deepEqual(output, test.expected); + } + } +} diff --git a/devtools/server/tests/unit/test_getTextAtLineColumn.js b/devtools/server/tests/unit/test_getTextAtLineColumn.js new file mode 100644 index 000000000..16ec47608 --- /dev/null +++ b/devtools/server/tests/unit/test_getTextAtLineColumn.js @@ -0,0 +1,35 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const {getTextAtLineColumn} = require("devtools/server/actors/styles"); + +const TEST_DATA = [ + { + desc: "simplest", + input: "#id{color:red;background:yellow;}", + line: 1, + column: 5, + expected: {offset: 4, text: "color:red;background:yellow;}"} + }, + { + desc: "multiple lines", + input: "one\n two\n three", + line: 3, + column: 3, + expected: {offset: 11, text: "three"} + }, +]; + +function run_test() { + for (let test of TEST_DATA) { + do_print("Starting test: " + test.desc); + do_print("Input string " + test.input); + + let output = getTextAtLineColumn(test.input, test.line, test.column); + deepEqual(output, test.expected); + } +} diff --git a/devtools/server/tests/unit/test_getyoungestframe.js b/devtools/server/tests/unit/test_getyoungestframe.js new file mode 100644 index 000000000..035ab5b0c --- /dev/null +++ b/devtools/server/tests/unit/test_getyoungestframe.js @@ -0,0 +1,30 @@ +function run_test() +{ + Components.utils.import("resource://gre/modules/jsdebugger.jsm"); + addDebuggerToGlobal(this); + var xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector); + var g = testGlobal("test1"); + + var dbg = new Debugger(); + dbg.uncaughtExceptionHook = testExceptionHook; + + dbg.addDebuggee(g); + dbg.onDebuggerStatement = function (aFrame) { + do_check_true(aFrame === dbg.getNewestFrame()); + // Execute from the nested event loop, dbg.getNewestFrame() won't + // be working anymore. + + do_execute_soon(function () { + try { + do_check_true(aFrame === dbg.getNewestFrame()); + } finally { + xpcInspector.exitNestedEventLoop("test"); + } + }); + xpcInspector.enterNestedEventLoop("test"); + }; + + g.eval("function debuggerStatement() { debugger; }; debuggerStatement();"); + + dbg.enabled = false; +} diff --git a/devtools/server/tests/unit/test_ignore_caught_exceptions.js b/devtools/server/tests/unit/test_ignore_caught_exceptions.js new file mode 100644 index 000000000..a4b221823 --- /dev/null +++ b/devtools/server/tests/unit/test_ignore_caught_exceptions.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that setting ignoreCaughtExceptions will cause the debugger to ignore + * caught exceptions, but not uncaught ones. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "exception"); + do_check_eq(aPacket.why.exception, "bar"); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + gThreadClient.pauseOnExceptions(true, true); + gThreadClient.resume(); + }); + + try { + gDebuggee.eval("(" + function () { + debugger; + try { + throw "foo"; + } catch (e) {} + throw "bar"; + } + ")()"); + } catch (e) {} +} diff --git a/devtools/server/tests/unit/test_ignore_no_interface_exceptions.js b/devtools/server/tests/unit/test_ignore_no_interface_exceptions.js new file mode 100644 index 000000000..5aaa31de3 --- /dev/null +++ b/devtools/server/tests/unit/test_ignore_no_interface_exceptions.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that the debugger automatically ignores NS_ERROR_NO_INTERFACE + * exceptions, but not normal ones. + */ + + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-no-interface"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-no-interface", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.pauseOnExceptions(true, false, function () { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "exception"); + do_check_eq(aPacket.why.exception, 42); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + gDebuggee.eval("(" + function () { + function QueryInterface() { + throw Components.results.NS_ERROR_NO_INTERFACE; + } + function stopMe() { + throw 42; + } + try { + QueryInterface(); + } catch (e) {} + try { + stopMe(); + } catch (e) {} + } + ")()"); + }); +} diff --git a/devtools/server/tests/unit/test_interrupt.js b/devtools/server/tests/unit/test_interrupt.js new file mode 100644 index 000000000..34835cc0a --- /dev/null +++ b/devtools/server/tests/unit/test_interrupt.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gClient; +var gDebuggee; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = testGlobal("test-1"); + DebuggerServer.addTestGlobal(gDebuggee); + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(function (aType, aTraits) { + attachTestTab(gClient, "test-1", test_attach); + }); + do_test_pending(); +} + +function test_attach(aResponse, aTabClient) +{ + aTabClient.attachThread({}, function (aResponse, aThreadClient) { + do_check_eq(aThreadClient.paused, true); + aThreadClient.resume(function () { + test_interrupt(aThreadClient); + }); + }); +} + +function test_interrupt(aThreadClient) +{ + do_check_eq(aThreadClient.paused, false); + aThreadClient.interrupt(function (aResponse) { + do_check_eq(aThreadClient.paused, true); + aThreadClient.resume(function () { + do_check_eq(aThreadClient.paused, false); + cleanup(); + }); + }); +} + +function cleanup() +{ + gClient.addListener("closed", function (aEvent) { + do_test_finished(); + }); + gClient.close(); +} + diff --git a/devtools/server/tests/unit/test_layout-reflows-observer.js b/devtools/server/tests/unit/test_layout-reflows-observer.js new file mode 100644 index 000000000..ff6c07b26 --- /dev/null +++ b/devtools/server/tests/unit/test_layout-reflows-observer.js @@ -0,0 +1,286 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test the LayoutChangesObserver + +var { + getLayoutChangesObserver, + releaseLayoutChangesObserver, + LayoutChangesObserver +} = require("devtools/server/actors/reflow"); + +// Override set/clearTimeout on LayoutChangesObserver to avoid depending on +// time in this unit test. This means that LayoutChangesObserver.eventLoopTimer +// will be the timeout callback instead of the timeout itself, so test cases +// will need to execute it to fake a timeout +LayoutChangesObserver.prototype._setTimeout = cb => cb; +LayoutChangesObserver.prototype._clearTimeout = function () {}; + +// Mock the tabActor since we only really want to test the LayoutChangesObserver +// and don't want to depend on a window object, nor want to test protocol.js +function MockTabActor() { + this.window = new MockWindow(); + this.windows = [this.window]; + this.attached = true; +} + +function MockWindow() {} +MockWindow.prototype = { + QueryInterface: function () { + let self = this; + return { + getInterface: function () { + return { + QueryInterface: function () { + if (!self.docShell) { + self.docShell = new MockDocShell(); + } + return self.docShell; + } + }; + } + }; + }, + setTimeout: function (cb) { + // Simply return the cb itself so that we can execute it in the test instead + // of depending on a real timeout + return cb; + }, + clearTimeout: function () {} +}; + +function MockDocShell() { + this.observer = null; +} +MockDocShell.prototype = { + addWeakReflowObserver: function (observer) { + this.observer = observer; + }, + removeWeakReflowObserver: function () {}, + get chromeEventHandler() { + return { + addEventListener: (type, cb) => { + if (type === "resize") { + this.resizeCb = cb; + } + }, + removeEventListener: (type, cb) => { + if (type === "resize" && cb === this.resizeCb) { + this.resizeCb = null; + } + } + }; + }, + mockResize: function () { + if (this.resizeCb) { + this.resizeCb(); + } + } +}; + +function run_test() { + instancesOfObserversAreSharedBetweenWindows(); + eventsAreBatched(); + noEventsAreSentWhenThereAreNoReflowsAndLoopTimeouts(); + observerIsAlreadyStarted(); + destroyStopsObserving(); + stoppingAndStartingSeveralTimesWorksCorrectly(); + reflowsArentStackedWhenStopped(); + stackedReflowsAreResetOnStop(); +} + +function instancesOfObserversAreSharedBetweenWindows() { + do_print("Checking that when requesting twice an instances of the observer " + + "for the same TabActor, the instance is shared"); + + do_print("Checking 2 instances of the observer for the tabActor 1"); + let tabActor1 = new MockTabActor(); + let obs11 = getLayoutChangesObserver(tabActor1); + let obs12 = getLayoutChangesObserver(tabActor1); + do_check_eq(obs11, obs12); + + do_print("Checking 2 instances of the observer for the tabActor 2"); + let tabActor2 = new MockTabActor(); + let obs21 = getLayoutChangesObserver(tabActor2); + let obs22 = getLayoutChangesObserver(tabActor2); + do_check_eq(obs21, obs22); + + do_print("Checking that observers instances for 2 different tabActors are " + + "different"); + do_check_neq(obs11, obs21); + + releaseLayoutChangesObserver(tabActor1); + releaseLayoutChangesObserver(tabActor1); + releaseLayoutChangesObserver(tabActor2); + releaseLayoutChangesObserver(tabActor2); +} + +function eventsAreBatched() { + do_print("Checking that reflow events are batched and only sent when the " + + "timeout expires"); + + // Note that in this test, we mock the TabActor and its window property, so we + // also mock the setTimeout/clearTimeout mechanism and just call the callback + // manually + let tabActor = new MockTabActor(); + let observer = getLayoutChangesObserver(tabActor); + + let reflowsEvents = []; + let onReflows = (event, reflows) => reflowsEvents.push(reflows); + observer.on("reflows", onReflows); + + let resizeEvents = []; + let onResize = () => resizeEvents.push("resize"); + observer.on("resize", onResize); + + do_print("Fake one reflow event"); + tabActor.window.docShell.observer.reflow(); + do_print("Checking that no batched reflow event has been emitted"); + do_check_eq(reflowsEvents.length, 0); + + do_print("Fake another reflow event"); + tabActor.window.docShell.observer.reflow(); + do_print("Checking that still no batched reflow event has been emitted"); + do_check_eq(reflowsEvents.length, 0); + + do_print("Fake a few of resize events too"); + tabActor.window.docShell.mockResize(); + tabActor.window.docShell.mockResize(); + tabActor.window.docShell.mockResize(); + do_print("Checking that still no batched resize event has been emitted"); + do_check_eq(resizeEvents.length, 0); + + do_print("Faking timeout expiration and checking that events are sent"); + observer.eventLoopTimer(); + do_check_eq(reflowsEvents.length, 1); + do_check_eq(reflowsEvents[0].length, 2); + do_check_eq(resizeEvents.length, 1); + + observer.off("reflows", onReflows); + observer.off("resize", onResize); + releaseLayoutChangesObserver(tabActor); +} + +function noEventsAreSentWhenThereAreNoReflowsAndLoopTimeouts() { + do_print("Checking that if no reflows were detected and the event batching " + + "loop expires, then no reflows event is sent"); + + let tabActor = new MockTabActor(); + let observer = getLayoutChangesObserver(tabActor); + + let reflowsEvents = []; + let onReflows = (event, reflows) => reflowsEvents.push(reflows); + observer.on("reflows", onReflows); + + do_print("Faking timeout expiration and checking for reflows"); + observer.eventLoopTimer(); + do_check_eq(reflowsEvents.length, 0); + + observer.off("reflows", onReflows); + releaseLayoutChangesObserver(tabActor); +} + +function observerIsAlreadyStarted() { + do_print("Checking that the observer is already started when getting it"); + + let tabActor = new MockTabActor(); + let observer = getLayoutChangesObserver(tabActor); + do_check_true(observer.isObserving); + + observer.stop(); + do_check_false(observer.isObserving); + + observer.start(); + do_check_true(observer.isObserving); + + releaseLayoutChangesObserver(tabActor); +} + +function destroyStopsObserving() { + do_print("Checking that the destroying the observer stops it"); + + let tabActor = new MockTabActor(); + let observer = getLayoutChangesObserver(tabActor); + do_check_true(observer.isObserving); + + observer.destroy(); + do_check_false(observer.isObserving); + + releaseLayoutChangesObserver(tabActor); +} + +function stoppingAndStartingSeveralTimesWorksCorrectly() { + do_print("Checking that the stopping and starting several times the observer" + + " works correctly"); + + let tabActor = new MockTabActor(); + let observer = getLayoutChangesObserver(tabActor); + + do_check_true(observer.isObserving); + observer.start(); + observer.start(); + observer.start(); + do_check_true(observer.isObserving); + + observer.stop(); + do_check_false(observer.isObserving); + + observer.stop(); + observer.stop(); + do_check_false(observer.isObserving); + + releaseLayoutChangesObserver(tabActor); +} + +function reflowsArentStackedWhenStopped() { + do_print("Checking that when stopped, reflows aren't stacked in the observer"); + + let tabActor = new MockTabActor(); + let observer = getLayoutChangesObserver(tabActor); + + do_print("Stoping the observer"); + observer.stop(); + + do_print("Faking reflows"); + tabActor.window.docShell.observer.reflow(); + tabActor.window.docShell.observer.reflow(); + tabActor.window.docShell.observer.reflow(); + + do_print("Checking that reflows aren't recorded"); + do_check_eq(observer.reflows.length, 0); + + do_print("Starting the observer and faking more reflows"); + observer.start(); + tabActor.window.docShell.observer.reflow(); + tabActor.window.docShell.observer.reflow(); + tabActor.window.docShell.observer.reflow(); + + do_print("Checking that reflows are recorded"); + do_check_eq(observer.reflows.length, 3); + + releaseLayoutChangesObserver(tabActor); +} + +function stackedReflowsAreResetOnStop() { + do_print("Checking that stacked reflows are reset on stop"); + + let tabActor = new MockTabActor(); + let observer = getLayoutChangesObserver(tabActor); + + tabActor.window.docShell.observer.reflow(); + do_check_eq(observer.reflows.length, 1); + + observer.stop(); + do_check_eq(observer.reflows.length, 0); + + tabActor.window.docShell.observer.reflow(); + do_check_eq(observer.reflows.length, 0); + + observer.start(); + do_check_eq(observer.reflows.length, 0); + + tabActor.window.docShell.observer.reflow(); + do_check_eq(observer.reflows.length, 1); + + releaseLayoutChangesObserver(tabActor); +} diff --git a/devtools/server/tests/unit/test_listsources-01.js b/devtools/server/tests/unit/test_listsources-01.js new file mode 100644 index 000000000..231e6a1e4 --- /dev/null +++ b/devtools/server/tests/unit/test_listsources-01.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic getSources functionality. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +var gNumTimesSourcesSent = 0; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.request = (function (request) { + return function (aRequest, aOnResponse) { + if (aRequest.type === "sources") { + ++gNumTimesSourcesSent; + } + return request.call(this, aRequest, aOnResponse); + }; + }(gClient.request)); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_listsources(); + }); + }); + do_test_pending(); +} + +function test_simple_listsources() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.getSources(function (aResponse) { + do_check_true(aResponse.sources.some(function (s) { + return s.url && s.url.match(/test_listsources-01.js/); + })); + + do_check_true(gNumTimesSourcesSent <= 1, + "Should only send one sources request at most, even though we" + + " might have had to send one to determine feature support."); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + + Components.utils.evalInSandbox("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "var a = 1;\n" + // line0 + 2 + "var b = 2;\n", // line0 + 3 + gDebuggee); +} diff --git a/devtools/server/tests/unit/test_listsources-02.js b/devtools/server/tests/unit/test_listsources-02.js new file mode 100644 index 000000000..190a5e31b --- /dev/null +++ b/devtools/server/tests/unit/test_listsources-02.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check getting sources before there are any. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +var gNumTimesSourcesSent = 0; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.request = (function (request) { + return function (aRequest, aOnResponse) { + if (aRequest.type === "sources") { + ++gNumTimesSourcesSent; + } + return request.call(this, aRequest, aOnResponse); + }; + }(gClient.request)); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_listing_zero_sources(); + }); + }); + do_test_pending(); +} + +function test_listing_zero_sources() +{ + gThreadClient.getSources(function (aPacket) { + do_check_true(!aPacket.error); + do_check_true(!!aPacket.sources); + do_check_eq(aPacket.sources.length, 0); + + do_check_true(gNumTimesSourcesSent <= 1, + "Should only send one sources request at most, even though we" + + " might have had to send one to determine feature support."); + + finishClient(gClient); + }); +} diff --git a/devtools/server/tests/unit/test_listsources-03.js b/devtools/server/tests/unit/test_listsources-03.js new file mode 100644 index 000000000..72ebb5e1c --- /dev/null +++ b/devtools/server/tests/unit/test_listsources-03.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check getSources functionality when there are lots of sources. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-sources"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-sources", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_listsources(); + }); + }); + do_test_pending(); +} + +function test_simple_listsources() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.getSources(function (aResponse) { + do_check_true( + !aResponse.error, + "There shouldn't be an error fetching large amounts of sources."); + + do_check_true(aResponse.sources.some(function (s) { + return s.url.match(/foo-999.js$/); + })); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + + for (let i = 0; i < 1000; i++) { + Cu.evalInSandbox("function foo###() {return ###;}".replace(/###/g, i), + gDebuggee, + "1.8", + "http://example.com/foo-" + i + ".js", + 1); + } + gDebuggee.eval("debugger;"); +} diff --git a/devtools/server/tests/unit/test_listsources-04.js b/devtools/server/tests/unit/test_listsources-04.js new file mode 100644 index 000000000..6da99a6ce --- /dev/null +++ b/devtools/server/tests/unit/test_listsources-04.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check getSources functionality with sourcemaps. + */ + +const {SourceNode} = require("source-map"); + +function run_test() { + run_test_with_server(DebuggerServer, function () { + // Bug 1304144 - This test does not run in a worker because the + // `rpc` method which talks to the main thread does not work. + // run_test_with_server(WorkerDebuggerServer, do_test_finished); + do_test_finished(); + }); + do_test_pending(); +} + +function run_test_with_server(server, cb) { + Task.spawn(function*() { + initTestDebuggerServer(server); + const debuggee = addTestGlobal("test-sources", server); + const client = new DebuggerClient(server.connectPipe()); + yield client.connect(); + const [,,threadClient] = yield attachTestTabAndResume(client, "test-sources"); + + yield threadClient.reconfigure({ useSourceMaps: true }); + addSources(debuggee); + + threadClient.getSources(Task.async(function* (res) { + do_check_true(res.sources.length === 3, "3 sources exist"); + + yield threadClient.reconfigure({ useSourceMaps: false }); + + threadClient.getSources(function(res) { + do_check_true(res.sources.length === 1, "1 source exist"); + client.close().then(cb); + }); + })); + }); +} + +function addSources(debuggee) { + let { code, map } = (new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"), + new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"), + new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"), + ])).toStringWithSourceMap({ + file: "abc.js", + sourceRoot: "http://example.com/www/js/" + }); + + code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString()); + + Components.utils.evalInSandbox(code, debuggee, "1.8", + "http://example.com/www/js/abc.js", 1); +} diff --git a/devtools/server/tests/unit/test_longstringactor.js b/devtools/server/tests/unit/test_longstringactor.js new file mode 100644 index 000000000..18b928910 --- /dev/null +++ b/devtools/server/tests/unit/test_longstringactor.js @@ -0,0 +1,104 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { LongStringActor } = require("devtools/server/actors/object"); + +function run_test() { + test_LSA_disconnect(); + test_LSA_grip(); + test_LSA_onSubstring(); +} + +const TEST_STRING = "This is a very long string!"; + +function makeMockLongStringActor() +{ + let string = TEST_STRING; + let actor = new LongStringActor(string); + actor.actorID = "longString1"; + actor.registeredPool = { + longStringActors: { + [string]: actor + } + }; + return actor; +} + +function test_LSA_disconnect() +{ + let actor = makeMockLongStringActor(); + do_check_eq(actor.registeredPool.longStringActors[TEST_STRING], actor); + + actor.disconnect(); + do_check_eq(actor.registeredPool.longStringActors[TEST_STRING], void 0); +} + +function test_LSA_substring() +{ + let actor = makeMockLongStringActor(); + do_check_eq(actor._substring(0, 4), TEST_STRING.substring(0, 4)); + do_check_eq(actor._substring(6, 9), TEST_STRING.substring(6, 9)); + do_check_eq(actor._substring(0, TEST_STRING.length), TEST_STRING); +} + +function test_LSA_grip() +{ + let actor = makeMockLongStringActor(); + + let grip = actor.grip(); + do_check_eq(grip.type, "longString"); + do_check_eq(grip.initial, TEST_STRING.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH)); + do_check_eq(grip.length, TEST_STRING.length); + do_check_eq(grip.actor, actor.actorID); +} + +function test_LSA_onSubstring() +{ + let actor = makeMockLongStringActor(); + let response; + + // From the start + response = actor.onSubstring({ + start: 0, + end: 4 + }); + do_check_eq(response.from, actor.actorID); + do_check_eq(response.substring, TEST_STRING.substring(0, 4)); + + // In the middle + response = actor.onSubstring({ + start: 5, + end: 8 + }); + do_check_eq(response.from, actor.actorID); + do_check_eq(response.substring, TEST_STRING.substring(5, 8)); + + // Whole string + response = actor.onSubstring({ + start: 0, + end: TEST_STRING.length + }); + do_check_eq(response.from, actor.actorID); + do_check_eq(response.substring, TEST_STRING); + + // Negative index + response = actor.onSubstring({ + start: -5, + end: TEST_STRING.length + }); + do_check_eq(response.from, actor.actorID); + do_check_eq(response.substring, + TEST_STRING.substring(-5, TEST_STRING.length)); + + // Past the end + response = actor.onSubstring({ + start: TEST_STRING.length - 5, + end: 100 + }); + do_check_eq(response.from, actor.actorID); + do_check_eq(response.substring, + TEST_STRING.substring(TEST_STRING.length - 5, 100)); +} diff --git a/devtools/server/tests/unit/test_longstringgrips-01.js b/devtools/server/tests/unit/test_longstringgrips-01.js new file mode 100644 index 000000000..b8e6789c7 --- /dev/null +++ b/devtools/server/tests/unit/test_longstringgrips-01.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_longstring_grip(); + }); + }); + do_test_pending(); +} + +function test_longstring_grip() +{ + let longString = "All I want is to be a monkey of moderate intelligence who" + + " wears a suit... that's why I'm transferring to business school! Maybe I" + + " love you so much, I love you no matter who you are pretending to be." + + " Enough about your promiscuous mother, Hermes! We have bigger problems." + + " For example, if you killed your grandfather, you'd cease to exist! What" + + " kind of a father would I be if I said no? Yep, I remember. They came in" + + " last at the Olympics, then retired to promote alcoholic beverages! And" + + " remember, don't do anything that affects anything, unless it turns out" + + " you were supposed to, in which case, for the love of God, don't not do" + + " it!"; + + DebuggerServer.LONG_STRING_LENGTH = 200; + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + do_check_eq(args.length, 1); + let grip = args[0]; + + try { + do_check_eq(grip.type, "longString"); + do_check_eq(grip.length, longString.length); + do_check_eq(grip.initial, longString.substr(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH)); + + let longStringClient = gThreadClient.pauseLongString(grip); + longStringClient.substring(22, 28, function (aResponse) { + try { + do_check_eq(aResponse.substring, "monkey"); + } finally { + gThreadClient.resume(function () { + finishClient(gClient); + }); + } + }); + } catch (error) { + gThreadClient.resume(function () { + finishClient(gClient); + do_throw(error); + }); + } + }); + + gDebuggee.eval('stopMe("' + longString + '")'); +} + diff --git a/devtools/server/tests/unit/test_longstringgrips-02.js b/devtools/server/tests/unit/test_longstringgrips-02.js new file mode 100644 index 000000000..01f9c1b8f --- /dev/null +++ b/devtools/server/tests/unit/test_longstringgrips-02.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume( + gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_longstring_grip(); + }); + }); + do_test_pending(); +} + +function test_longstring_grip() +{ + DebuggerServer.LONG_STRING_LENGTH = 200; + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + try { + let fakeLongStringGrip = { + type: "longString", + length: 1000000, + actor: "123fakeActor123", + initial: "" + }; + let longStringClient = gThreadClient.pauseLongString(fakeLongStringGrip); + longStringClient.substring(22, 28, function (aResponse) { + try { + do_check_true(!!aResponse.error, + "We should not get a response, but an error."); + } finally { + gThreadClient.resume(function () { + finishClient(gClient); + }); + } + }); + } catch (error) { + gThreadClient.resume(function () { + finishClient(gClient); + do_throw(error); + }); + } + }); + + gDebuggee.eval("stopMe()"); +} + diff --git a/devtools/server/tests/unit/test_monitor_actor.js b/devtools/server/tests/unit/test_monitor_actor.js new file mode 100644 index 000000000..17c272d80 --- /dev/null +++ b/devtools/server/tests/unit/test_monitor_actor.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test the monitor actor. + */ + +"use strict"; + +function run_test() +{ + let EventEmitter = require("devtools/shared/event-emitter"); + + function MonitorClient(client, form) { + this.client = client; + this.actor = form.monitorActor; + this.events = ["update"]; + + EventEmitter.decorate(this); + client.registerClient(this); + } + MonitorClient.prototype.destroy = function () { + this.client.unregisterClient(this); + }; + MonitorClient.prototype.start = function (callback) { + this.client.request({ + to: this.actor, + type: "start" + }, callback); + }; + MonitorClient.prototype.stop = function (callback) { + this.client.request({ + to: this.actor, + type: "stop" + }, callback); + }; + + let monitor, client; + + // Start the monitor actor. + get_chrome_actors((c, form) => { + client = c; + monitor = new MonitorClient(client, form); + monitor.on("update", gotUpdate); + monitor.start(update); + }); + + let time = Date.now(); + + function update() { + let event = { + graph: "Test", + curve: "test", + value: 42, + time: time, + }; + Services.obs.notifyObservers(null, "devtools-monitor-update", JSON.stringify(event)); + } + + function gotUpdate(type, packet) { + packet.data.forEach(function (event) { + // Ignore updates that were not sent by this test. + if (event.graph === "Test") { + do_check_eq(event.curve, "test"); + do_check_eq(event.value, 42); + do_check_eq(event.time, time); + monitor.stop(function (aResponse) { + monitor.destroy(); + finishClient(client); + }); + } + }); + } + + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_nativewrappers.js b/devtools/server/tests/unit/test_nativewrappers.js new file mode 100644 index 000000000..fbadfcdec --- /dev/null +++ b/devtools/server/tests/unit/test_nativewrappers.js @@ -0,0 +1,30 @@ +function run_test() +{ + Components.utils.import("resource://gre/modules/jsdebugger.jsm"); + addDebuggerToGlobal(this); + var g = testGlobal("test1"); + + var dbg = new Debugger(); + dbg.addDebuggee(g); + dbg.onDebuggerStatement = function (aFrame) { + let args = aFrame.arguments; + try { + args[0]; + do_check_true(true); + } catch (ex) { + do_check_true(false); + } + }; + + g.eval("function stopMe(arg) {debugger;}"); + + g2 = testGlobal("test2"); + g2.g = g; + g2.eval("(" + function createBadEvent() { + let parser = Components.classes["@mozilla.org/xmlextras/domparser;1"].createInstance(Components.interfaces.nsIDOMParser); + let doc = parser.parseFromString("<foo></foo>", "text/xml"); + g.stopMe(doc.createEvent("MouseEvent")); + } + ")()"); + + dbg.enabled = false; +} diff --git a/devtools/server/tests/unit/test_nesting-01.js b/devtools/server/tests/unit/test_nesting-01.js new file mode 100644 index 000000000..e515f051e --- /dev/null +++ b/devtools/server/tests/unit/test_nesting-01.js @@ -0,0 +1,48 @@ +/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that we can nest event loops when needed in +// ThreadActor.prototype.unsafeSynchronize. + +var gClient; +var gThreadActor; + +function run_test() { + initTestDebuggerServer(); + let gDebuggee = addTestGlobal("test-nesting"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-nesting", function (aResponse, aTabClient, aThreadClient) { + // Reach over the protocol connection and get a reference to the thread actor. + gThreadActor = aThreadClient._transport._serverConnection.getActor(aThreadClient._actor); + + test_nesting(); + }); + }); + do_test_pending(); +} + +function test_nesting() { + const thread = gThreadActor; + const { resolve, reject, promise: p } = promise.defer(); + + let currentStep = 0; + + executeSoon(function () { + // Should be on the first step + do_check_eq(++currentStep, 1); + // We should have one nested event loop from unsfeSynchronize + do_check_eq(thread._nestedEventLoops.size, 1); + resolve(true); + }); + + do_check_eq(thread.unsafeSynchronize(p), true); + + // Should be on the second step + do_check_eq(++currentStep, 2); + // There shouldn't be any nested event loops anymore + do_check_eq(thread._nestedEventLoops.size, 0); + + finishClient(gClient); +} diff --git a/devtools/server/tests/unit/test_nesting-02.js b/devtools/server/tests/unit/test_nesting-02.js new file mode 100644 index 000000000..928331be5 --- /dev/null +++ b/devtools/server/tests/unit/test_nesting-02.js @@ -0,0 +1,81 @@ +/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that we can nest event loops and then automatically exit nested event +// loops when requested. + +var gClient; +var gThreadActor; + +function run_test() { + initTestDebuggerServer(); + let gDebuggee = addTestGlobal("test-nesting"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-nesting", function (aResponse, aTabClient, aThreadClient) { + // Reach over the protocol connection and get a reference to the thread + // actor. + gThreadActor = aThreadClient._transport._serverConnection.getActor(aThreadClient._actor); + + test_nesting(); + }); + }); + do_test_pending(); +} + +function test_nesting() { + const thread = gThreadActor; + const { resolve, reject, promise: p } = promise.defer(); + + // The following things should happen (in order): + // 1. In the new event loop (created by unsafeSynchronize) + // 2. Resolve the promise (shouldn't exit any event loops) + // 3. Exit the event loop (should also then exit unsafeSynchronize's event loop) + // 4. Be after the unsafeSynchronize call + let currentStep = 0; + + executeSoon(function () { + let eventLoop; + + executeSoon(function () { + // Should be at step 2 + do_check_eq(++currentStep, 2); + // Before resolving, should have the unsafeSynchronize event loop and the + // one just created. + do_check_eq(thread._nestedEventLoops.size, 2); + + executeSoon(function () { + // Should be at step 3 + do_check_eq(++currentStep, 3); + // Before exiting the manually created event loop, should have the + // unsafeSynchronize event loop and the manual event loop. + do_check_eq(thread._nestedEventLoops.size, 2); + // Should have the event loop + do_check_true(!!eventLoop); + eventLoop.resolve(); + }); + + resolve(true); + // Shouldn't exit any event loops because a new one started since the call + // to unsafeSynchronize + do_check_eq(thread._nestedEventLoops.size, 2); + }); + + // Should be at step 1 + do_check_eq(++currentStep, 1); + // Should have only the unsafeSynchronize event loop + do_check_eq(thread._nestedEventLoops.size, 1); + eventLoop = thread._nestedEventLoops.push(); + eventLoop.enter(); + }); + + do_check_eq(thread.unsafeSynchronize(p), true); + + // Should be on the fourth step + do_check_eq(++currentStep, 4); + // There shouldn't be any nested event loops anymore + do_check_eq(thread._nestedEventLoops.size, 0); + + finishClient(gClient); +} diff --git a/devtools/server/tests/unit/test_nesting-03.js b/devtools/server/tests/unit/test_nesting-03.js new file mode 100644 index 000000000..6a0e5a66b --- /dev/null +++ b/devtools/server/tests/unit/test_nesting-03.js @@ -0,0 +1,51 @@ +/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that we can detect nested event loops in tabs with the same URL. + +var gClient1, gClient2, gThreadClient1, gThreadClient2; + +function run_test() { + initTestDebuggerServer(); + addTestGlobal("test-nesting1"); + addTestGlobal("test-nesting1"); + // Conect the first client to the first debuggee. + gClient1 = new DebuggerClient(DebuggerServer.connectPipe()); + gClient1.connect(function () { + attachTestThread(gClient1, "test-nesting1", function (aResponse, aTabClient, aThreadClient) { + gThreadClient1 = aThreadClient; + start_second_connection(); + }); + }); + do_test_pending(); +} + +function start_second_connection() { + gClient2 = new DebuggerClient(DebuggerServer.connectPipe()); + gClient2.connect(function () { + attachTestThread(gClient2, "test-nesting1", function (aResponse, aTabClient, aThreadClient) { + gThreadClient2 = aThreadClient; + test_nesting(); + }); + }); +} + +function test_nesting() { + const { resolve, reject, promise: p } = promise.defer(); + + gThreadClient1.resume(aResponse => { + do_check_eq(aResponse.error, "wrongOrder"); + gThreadClient2.resume(aResponse => { + do_check_true(!aResponse.error); + do_check_eq(aResponse.from, gThreadClient2.actor); + + gThreadClient1.resume(aResponse => { + do_check_true(!aResponse.error); + do_check_eq(aResponse.from, gThreadClient1.actor); + + gClient1.close(() => finishClient(gClient2)); + }); + }); + }); +} diff --git a/devtools/server/tests/unit/test_new_source-01.js b/devtools/server/tests/unit/test_new_source-01.js new file mode 100644 index 000000000..aa2498371 --- /dev/null +++ b/devtools/server/tests/unit/test_new_source-01.js @@ -0,0 +1,40 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic newSource packet sent from server. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_new_source(); + }); + }); + do_test_pending(); +} + +function test_simple_new_source() +{ + gThreadClient.addOneTimeListener("newSource", function (aEvent, aPacket) { + do_check_eq(aEvent, "newSource"); + do_check_eq(aPacket.type, "newSource"); + do_check_true(!!aPacket.source); + do_check_true(!!aPacket.source.url.match(/test_new_source-01.js$/)); + + finishClient(gClient); + }); + + Components.utils.evalInSandbox(function inc(n) { + return n + 1; + }.toString(), gDebuggee); +} diff --git a/devtools/server/tests/unit/test_nodelistactor.js b/devtools/server/tests/unit/test_nodelistactor.js new file mode 100644 index 000000000..4d9ec1a7a --- /dev/null +++ b/devtools/server/tests/unit/test_nodelistactor.js @@ -0,0 +1,26 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that a NodeListActor initialized with null nodelist doesn't cause +// exceptions when calling NodeListActor.form. + +const { NodeListActor } = require("devtools/server/actors/inspector"); + +function run_test() { + check_actor_for_list(null); + check_actor_for_list([]); + check_actor_for_list(["fakenode"]); +} + +function check_actor_for_list(nodelist) { + do_print("Checking NodeListActor with nodelist '" + nodelist + "' works."); + let actor = new NodeListActor({}, nodelist); + let form = actor.form(); + + // No exception occured as a exceptions abort the test. + ok(true, "No exceptions occured."); + equal(form.length, nodelist ? nodelist.length : 0, + "NodeListActor reported correct length."); +} diff --git a/devtools/server/tests/unit/test_nsjsinspector.js b/devtools/server/tests/unit/test_nsjsinspector.js new file mode 100644 index 000000000..14a99a308 --- /dev/null +++ b/devtools/server/tests/unit/test_nsjsinspector.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test the basic functionality of the nsIJSInspector component. +var gCount = 0; +const MAX = 10; +var inspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector); +var tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager); + +// Emulate 10 simultaneously-debugged windows from 3 separate client connections. +var requestor = (count) => ({ + url:"http://foo/bar/" + count, + connection: "conn" + (count % 3) +}); + +function run_test() +{ + test_nesting(); +} + +function test_nesting() +{ + do_check_eq(inspector.eventLoopNestLevel, 0); + + tm.currentThread.dispatch({ run: enterEventLoop}, 0); + + do_check_eq(inspector.enterNestedEventLoop(requestor(gCount)), 0); + do_check_eq(inspector.eventLoopNestLevel, 0); + do_check_eq(inspector.lastNestRequestor, null); +} + +function enterEventLoop() { + if (gCount++ < MAX) { + tm.currentThread.dispatch({ run: enterEventLoop}, 0); + + let r = Object.create(requestor(gCount)); + + do_check_eq(inspector.eventLoopNestLevel, gCount); + do_check_eq(inspector.lastNestRequestor.url, requestor(gCount - 1).url); + do_check_eq(inspector.lastNestRequestor.connection, requestor(gCount - 1).connection); + do_check_eq(inspector.enterNestedEventLoop(requestor(gCount)), gCount); + } else { + do_check_eq(gCount, MAX + 1); + tm.currentThread.dispatch({ run: exitEventLoop}, 0); + } +} + +function exitEventLoop() { + if (inspector.lastNestRequestor != null) { + do_check_eq(inspector.lastNestRequestor.url, requestor(gCount - 1).url); + do_check_eq(inspector.lastNestRequestor.connection, requestor(gCount - 1).connection); + if (gCount-- > 1) { + tm.currentThread.dispatch({ run: exitEventLoop}, 0); + } + + do_check_eq(inspector.exitNestedEventLoop(), gCount); + do_check_eq(inspector.eventLoopNestLevel, gCount); + } +} diff --git a/devtools/server/tests/unit/test_objectgrips-01.js b/devtools/server/tests/unit/test_objectgrips-01.js new file mode 100644 index 000000000..e1857e5b8 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-01.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + do_check_eq(args[0].class, "Object"); + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getOwnPropertyNames(function (aResponse) { + do_check_eq(aResponse.ownPropertyNames.length, 3); + do_check_eq(aResponse.ownPropertyNames[0], "a"); + do_check_eq(aResponse.ownPropertyNames[1], "b"); + do_check_eq(aResponse.ownPropertyNames[2], "c"); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + + }); + + gDebuggee.eval("stopMe({ a: 1, b: true, c: 'foo' })"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-02.js b/devtools/server/tests/unit/test_objectgrips-02.js new file mode 100644 index 000000000..649d52c64 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-02.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + do_check_eq(args[0].class, "Object"); + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getPrototype(function (aResponse) { + do_check_true(aResponse.prototype != undefined); + + let protoClient = gThreadClient.pauseGrip(aResponse.prototype); + protoClient.getOwnPropertyNames(function (aResponse) { + do_check_eq(aResponse.ownPropertyNames.length, 2); + do_check_eq(aResponse.ownPropertyNames[0], "b"); + do_check_eq(aResponse.ownPropertyNames[1], "c"); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + }); + + gDebuggee.eval(function Constr() { + this.a = 1; + }.toString()); + gDebuggee.eval("Constr.prototype = { b: true, c: 'foo' }; var o = new Constr(); stopMe(o)"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-03.js b/devtools/server/tests/unit/test_objectgrips-03.js new file mode 100644 index 000000000..8b19db713 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-03.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + do_check_eq(args[0].class, "Object"); + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getProperty("x", function (aResponse) { + do_check_eq(aResponse.descriptor.configurable, true); + do_check_eq(aResponse.descriptor.enumerable, true); + do_check_eq(aResponse.descriptor.writable, true); + do_check_eq(aResponse.descriptor.value, 10); + + objClient.getProperty("y", function (aResponse) { + do_check_eq(aResponse.descriptor.configurable, true); + do_check_eq(aResponse.descriptor.enumerable, true); + do_check_eq(aResponse.descriptor.writable, true); + do_check_eq(aResponse.descriptor.value, "kaiju"); + + objClient.getProperty("a", function (aResponse) { + do_check_eq(aResponse.descriptor.configurable, true); + do_check_eq(aResponse.descriptor.enumerable, true); + do_check_eq(aResponse.descriptor.get.type, "object"); + do_check_eq(aResponse.descriptor.get.class, "Function"); + do_check_eq(aResponse.descriptor.set.type, "undefined"); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + }); + + }); + + gDebuggee.eval("stopMe({ x: 10, y: 'kaiju', get a() { return 42; } })"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-04.js b/devtools/server/tests/unit/test_objectgrips-04.js new file mode 100644 index 000000000..1662358e0 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-04.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + do_check_eq(args[0].class, "Object"); + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.x.configurable, true); + do_check_eq(aResponse.ownProperties.x.enumerable, true); + do_check_eq(aResponse.ownProperties.x.writable, true); + do_check_eq(aResponse.ownProperties.x.value, 10); + + do_check_eq(aResponse.ownProperties.y.configurable, true); + do_check_eq(aResponse.ownProperties.y.enumerable, true); + do_check_eq(aResponse.ownProperties.y.writable, true); + do_check_eq(aResponse.ownProperties.y.value, "kaiju"); + + do_check_eq(aResponse.ownProperties.a.configurable, true); + do_check_eq(aResponse.ownProperties.a.enumerable, true); + do_check_eq(aResponse.ownProperties.a.get.type, "object"); + do_check_eq(aResponse.ownProperties.a.get.class, "Function"); + do_check_eq(aResponse.ownProperties.a.set.type, "undefined"); + + do_check_true(aResponse.prototype != undefined); + + let protoClient = gThreadClient.pauseGrip(aResponse.prototype); + protoClient.getOwnPropertyNames(function (aResponse) { + do_check_true(aResponse.ownPropertyNames.toString != undefined); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + }); + + gDebuggee.eval("stopMe({ x: 10, y: 'kaiju', get a() { return 42; } })"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-05.js b/devtools/server/tests/unit/test_objectgrips-05.js new file mode 100644 index 000000000..5bbb37d88 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-05.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This test checks that frozen objects report themselves as frozen in their + * grip. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1, arg2) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let obj1 = aPacket.frame.arguments[0]; + do_check_true(obj1.frozen); + + let obj1Client = gThreadClient.pauseGrip(obj1); + do_check_true(obj1Client.isFrozen); + + let obj2 = aPacket.frame.arguments[1]; + do_check_false(obj2.frozen); + + let obj2Client = gThreadClient.pauseGrip(obj2); + do_check_false(obj2Client.isFrozen); + + gThreadClient.resume(_ => { + gClient.close().then(gCallback); + }); + }); + + gDebuggee.eval("(" + function () { + let obj1 = {}; + Object.freeze(obj1); + stopMe(obj1, {}); + } + "())"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-06.js b/devtools/server/tests/unit/test_objectgrips-06.js new file mode 100644 index 000000000..bb9888ab8 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-06.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This test checks that sealed objects report themselves as sealed in their + * grip. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1, arg2) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let obj1 = aPacket.frame.arguments[0]; + do_check_true(obj1.sealed); + + let obj1Client = gThreadClient.pauseGrip(obj1); + do_check_true(obj1Client.isSealed); + + let obj2 = aPacket.frame.arguments[1]; + do_check_false(obj2.sealed); + + let obj2Client = gThreadClient.pauseGrip(obj2); + do_check_false(obj2Client.isSealed); + + gThreadClient.resume(_ => { + gClient.close().then(gCallback); + }); + }); + + gDebuggee.eval("(" + function () { + let obj1 = {}; + Object.seal(obj1); + stopMe(obj1, {}); + } + "())"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-07.js b/devtools/server/tests/unit/test_objectgrips-07.js new file mode 100644 index 000000000..6d9ac11fb --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-07.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This test checks that objects which are not extensible report themselves as + * such. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1, arg2, arg3, arg4) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let [f, s, ne, e] = aPacket.frame.arguments; + let [fClient, sClient, neClient, eClient] = aPacket.frame.arguments.map( + a => gThreadClient.pauseGrip(a)); + + do_check_false(f.extensible); + do_check_false(fClient.isExtensible); + + do_check_false(s.extensible); + do_check_false(sClient.isExtensible); + + do_check_false(ne.extensible); + do_check_false(neClient.isExtensible); + + do_check_true(e.extensible); + do_check_true(eClient.isExtensible); + + gThreadClient.resume(_ => { + gClient.close().then(gCallback); + }); + }); + + gDebuggee.eval("(" + function () { + let f = {}; + Object.freeze(f); + let s = {}; + Object.seal(s); + let ne = {}; + Object.preventExtensions(ne); + stopMe(f, s, ne, {}); + } + "())"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-08.js b/devtools/server/tests/unit/test_objectgrips-08.js new file mode 100644 index 000000000..ecaa7146d --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-08.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + do_check_eq(args[0].class, "Object"); + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.a.configurable, true); + do_check_eq(aResponse.ownProperties.a.enumerable, true); + do_check_eq(aResponse.ownProperties.a.writable, true); + do_check_eq(aResponse.ownProperties.a.value.type, "Infinity"); + + do_check_eq(aResponse.ownProperties.b.configurable, true); + do_check_eq(aResponse.ownProperties.b.enumerable, true); + do_check_eq(aResponse.ownProperties.b.writable, true); + do_check_eq(aResponse.ownProperties.b.value.type, "-Infinity"); + + do_check_eq(aResponse.ownProperties.c.configurable, true); + do_check_eq(aResponse.ownProperties.c.enumerable, true); + do_check_eq(aResponse.ownProperties.c.writable, true); + do_check_eq(aResponse.ownProperties.c.value.type, "NaN"); + + do_check_eq(aResponse.ownProperties.d.configurable, true); + do_check_eq(aResponse.ownProperties.d.enumerable, true); + do_check_eq(aResponse.ownProperties.d.writable, true); + do_check_eq(aResponse.ownProperties.d.value.type, "-0"); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + gDebuggee.eval("stopMe({ a: Infinity, b: -Infinity, c: NaN, d: -0 })"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-09.js b/devtools/server/tests/unit/test_objectgrips-09.js new file mode 100644 index 000000000..498154b1e --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-09.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/** + * This tests exercises getProtypesAndProperties message accepted + * by a thread actor. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1, arg2) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + gThreadClient.getPrototypesAndProperties([args[0].actor, args[1].actor], function (aResponse) { + let obj1 = aResponse.actors[args[0].actor]; + let obj2 = aResponse.actors[args[1].actor]; + do_check_eq(obj1.ownProperties.x.configurable, true); + do_check_eq(obj1.ownProperties.x.enumerable, true); + do_check_eq(obj1.ownProperties.x.writable, true); + do_check_eq(obj1.ownProperties.x.value, 10); + + do_check_eq(obj1.ownProperties.y.configurable, true); + do_check_eq(obj1.ownProperties.y.enumerable, true); + do_check_eq(obj1.ownProperties.y.writable, true); + do_check_eq(obj1.ownProperties.y.value, "kaiju"); + + do_check_eq(obj2.ownProperties.z.configurable, true); + do_check_eq(obj2.ownProperties.z.enumerable, true); + do_check_eq(obj2.ownProperties.z.writable, true); + do_check_eq(obj2.ownProperties.z.value, 123); + + do_check_true(obj1.prototype != undefined); + do_check_true(obj2.prototype != undefined); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + + }); + + gDebuggee.eval("stopMe({ x: 10, y: 'kaiju'}, { z: 123 })"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-10.js b/devtools/server/tests/unit/test_objectgrips-10.js new file mode 100644 index 000000000..a5d1b18c6 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-10.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +// Test that closures can be inspected. + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-closures"); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-closures", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); + do_test_pending(); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let person = aPacket.frame.environment.bindings.variables.person; + + do_check_eq(person.value.class, "Object"); + + let personClient = gThreadClient.pauseGrip(person.value); + personClient.getPrototypeAndProperties(aResponse => { + do_check_eq(aResponse.ownProperties.getName.value.class, "Function"); + + do_check_eq(aResponse.ownProperties.getAge.value.class, "Function"); + + do_check_eq(aResponse.ownProperties.getFoo.value.class, "Function"); + + let getNameClient = gThreadClient.pauseGrip(aResponse.ownProperties.getName.value); + let getAgeClient = gThreadClient.pauseGrip(aResponse.ownProperties.getAge.value); + let getFooClient = gThreadClient.pauseGrip(aResponse.ownProperties.getFoo.value); + getNameClient.getScope(aResponse => { + do_check_eq(aResponse.scope.bindings.arguments[0].name.value, "Bob"); + + getAgeClient.getScope(aResponse => { + do_check_eq(aResponse.scope.bindings.arguments[1].age.value, 58); + + getFooClient.getScope(aResponse => { + do_check_eq(aResponse.scope.bindings.variables.foo.value, 10); + + gThreadClient.resume(() => finishClient(gClient)); + }); + }); + }); + }); + + }); + + gDebuggee.eval("(" + function () { + var PersonFactory = function (name, age) { + var foo = 10; + return { + getName: function () { return name; }, + getAge: function () { return age; }, + getFoo: function () { foo = Date.now(); return foo; } + }; + }; + var person = new PersonFactory("Bob", 58); + debugger; + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_objectgrips-11.js b/devtools/server/tests/unit/test_objectgrips-11.js new file mode 100644 index 000000000..1ad5c353a --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-11.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that we get the magic properties on Error objects. + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); + do_test_pending(); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getOwnPropertyNames(function (aResponse) { + var opn = aResponse.ownPropertyNames; + do_check_eq(opn.length, 4); + opn.sort(); + do_check_eq(opn[0], "columnNumber"); + do_check_eq(opn[1], "fileName"); + do_check_eq(opn[2], "lineNumber"); + do_check_eq(opn[3], "message"); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + }); + + gDebuggee.eval("stopMe(new TypeError('error message text'))"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-12.js b/devtools/server/tests/unit/test_objectgrips-12.js new file mode 100644 index 000000000..32d4d47e0 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-12.js @@ -0,0 +1,162 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test getDisplayString. + +Cu.import("resource://testing-common/PromiseTestUtils.jsm", this); + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_display_string(); + }); + }); + do_test_pending(); +} + +function test_display_string() +{ + const testCases = [ + { + input: "new Boolean(true)", + output: "true" + }, + { + input: "new Number(5)", + output: "5" + }, + { + input: "new String('foo')", + output: "foo" + }, + { + input: "new Map()", + output: "[object Map]" + }, + { + input: "[,,,,,,,]", + output: ",,,,,," + }, + { + input: "[1, 2, 3]", + output: "1,2,3" + }, + { + input: "[undefined, null, true, 'foo', 5]", + output: ",,true,foo,5" + }, + { + input: "[{},{}]", + output: "[object Object],[object Object]" + }, + { + input: "(" + function () { + const arr = [1]; + arr.push(arr); + return arr; + } + ")()", + output: "1," + }, + { + input: "{}", + output: "[object Object]" + }, + { + input: "Object.create(null)", + output: "[object Object]" + }, + { + input: "new Error('foo')", + output: "Error: foo" + }, + { + input: "new SyntaxError()", + output: "SyntaxError" + }, + { + input: "new ReferenceError('')", + output: "ReferenceError" + }, + { + input: "(" + function () { + const err = new Error("bar"); + err.name = "foo"; + return err; + } + ")()", + output: "foo: bar" + }, + { + input: "() => {}", + output: "() => {}" + }, + { + input: "function (foo, bar) {}", + output: "function (foo, bar) {}" + }, + { + input: "function foo(bar) {}", + output: "function foo(bar) {}" + }, + { + input: "Array", + output: Array + "" + }, + { + input: "/foo[bar]/g", + output: "/foo[bar]/g" + }, + { + input: "new Proxy({}, {})", + output: "[object Object]" + }, + { + input: "Promise.resolve(5)", + output: "Promise (fulfilled: 5)" + }, + { + // This rejection is left uncaught, see expectUncaughtRejection below. + input: "Promise.reject(new Error())", + output: "Promise (rejected: Error)" + }, + { + input: "new Promise(function () {})", + output: "Promise (pending)" + } + ]; + + PromiseTestUtils.expectUncaughtRejection(/Error/); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + const args = aPacket.frame.arguments; + + (function loop() { + const objClient = gThreadClient.pauseGrip(args.pop()); + objClient.getDisplayString(function ({ displayString }) { + do_check_eq(displayString, testCases.pop().output); + if (args.length) { + loop(); + } else { + gThreadClient.resume(function () { + finishClient(gClient); + }); + } + }); + })(); + }); + + const inputs = testCases.map(({ input }) => input).join(","); + gDebuggee.eval("stopMe(" + inputs + ")"); +} diff --git a/devtools/server/tests/unit/test_objectgrips-13.js b/devtools/server/tests/unit/test_objectgrips-13.js new file mode 100644 index 000000000..166e8a0d5 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-13.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that ObjectClient.prototype.getDefinitionSite and the "definitionSite" +// request work properly. + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + Components.utils.evalInSandbox(function stopMe() { + debugger; + }.toString(), gDebuggee); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + add_pause_listener(); + }); + }); + do_test_pending(); +} + +function add_pause_listener() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + const [funcGrip, objGrip] = aPacket.frame.arguments; + const func = gThreadClient.pauseGrip(funcGrip); + const obj = gThreadClient.pauseGrip(objGrip); + test_definition_site(func, obj); + }); + + eval_code(); +} + +function eval_code() { + Components.utils.evalInSandbox([ + "this.line0 = Error().lineNumber;", + "function f() {}", + "stopMe(f, {});" + ].join("\n"), gDebuggee); +} + +function test_definition_site(func, obj) { + func.getDefinitionSite(({ error, source, line, column }) => { + do_check_true(!error); + do_check_eq(source.url, getFilePath("test_objectgrips-13.js")); + do_check_eq(line, gDebuggee.line0 + 1); + do_check_eq(column, 0); + + test_bad_definition_site(obj); + }); +} + +function test_bad_definition_site(obj) { + try { + obj._client.request("definitionSite", () => do_check_true(false)); + } catch (e) { + gThreadClient.resume(() => finishClient(gClient)); + } +} diff --git a/devtools/server/tests/unit/test_pause_exceptions-01.js b/devtools/server/tests/unit/test_pause_exceptions-01.js new file mode 100644 index 000000000..56ee6816d --- /dev/null +++ b/devtools/server/tests/unit/test_pause_exceptions-01.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that setting pauseOnExceptions to true will cause the debuggee to pause + * when an exceptions is thrown. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "exception"); + do_check_eq(aPacket.why.exception, 42); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + gThreadClient.pauseOnExceptions(true); + gThreadClient.resume(); + }); + + gDebuggee.eval("(" + function () { + function stopMe() { + debugger; + throw 42; + } + try { + stopMe(); + } catch (e) {} + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_pause_exceptions-02.js b/devtools/server/tests/unit/test_pause_exceptions-02.js new file mode 100644 index 000000000..fa9b419f0 --- /dev/null +++ b/devtools/server/tests/unit/test_pause_exceptions-02.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that setting pauseOnExceptions to true when the debugger isn't in a + * paused state will cause the debuggee to pause when an exceptions is thrown. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.pauseOnExceptions(true, false, function () { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "exception"); + do_check_eq(aPacket.why.exception, 42); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe() { + throw 42; + } + try { + stopMe(); + } catch (e) {} + } + ")()"); + }); +} diff --git a/devtools/server/tests/unit/test_pauselifetime-01.js b/devtools/server/tests/unit/test_pauselifetime-01.js new file mode 100644 index 000000000..71c2ddae7 --- /dev/null +++ b/devtools/server/tests/unit/test_pauselifetime-01.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that pause-lifetime grips go away correctly after a resume. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let pauseActor = aPacket.actor; + + // Make a bogus request to the pause-liftime actor. Should get + // unrecognized-packet-type (and not no-such-actor). + gClient.request({ to: pauseActor, type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "unrecognizedPacketType"); + + gThreadClient.resume(function () { + // Now that we've resumed, should get no-such-actor for the + // same request. + gClient.request({ to: pauseActor, type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "noSuchActor"); + finishClient(gClient); + }); + }); + + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe() { + debugger; + } + stopMe(); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_pauselifetime-02.js b/devtools/server/tests/unit/test_pauselifetime-02.js new file mode 100644 index 000000000..6c90725bb --- /dev/null +++ b/devtools/server/tests/unit/test_pauselifetime-02.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that pause-lifetime grips go away correctly after a resume. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + let objActor = args[0].actor; + do_check_eq(args[0].class, "Object"); + do_check_true(!!objActor); + + // Make a bogus request to the grip actor. Should get + // unrecognized-packet-type (and not no-such-actor). + gClient.request({ to: objActor, type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "unrecognizedPacketType"); + + gThreadClient.resume(function () { + // Now that we've resumed, should get no-such-actor for the + // same request. + gClient.request({ to: objActor, type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "noSuchActor"); + finishClient(gClient); + }); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(aObject) { + debugger; + } + stopMe({ foo: "bar" }); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_pauselifetime-03.js b/devtools/server/tests/unit/test_pauselifetime-03.js new file mode 100644 index 000000000..9fca887b7 --- /dev/null +++ b/devtools/server/tests/unit/test_pauselifetime-03.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that pause-lifetime grip clients are marked invalid after a resume. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + let objActor = args[0].actor; + do_check_eq(args[0].class, "Object"); + do_check_true(!!objActor); + + let objClient = gThreadClient.pauseGrip(args[0]); + do_check_true(objClient.valid); + + // Make a bogus request to the grip actor. Should get + // unrecognized-packet-type (and not no-such-actor). + gClient.request({ to: objActor, type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "unrecognizedPacketType"); + do_check_true(objClient.valid); + + gThreadClient.resume(function () { + // Now that we've resumed, should get no-such-actor for the + // same request. + gClient.request({ to: objActor, type: "bogusRequest" }, function (aResponse) { + do_check_false(objClient.valid); + do_check_eq(aResponse.error, "noSuchActor"); + finishClient(gClient); + }); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(aObject) { + debugger; + } + stopMe({ foo: "bar" }); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_pauselifetime-04.js b/devtools/server/tests/unit/test_pauselifetime-04.js new file mode 100644 index 000000000..c863da921 --- /dev/null +++ b/devtools/server/tests/unit/test_pauselifetime-04.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that requesting a pause actor for the same value multiple + * times returns the same actor. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + let objActor1 = args[0].actor; + + gThreadClient.getFrames(0, 1, function (aResponse) { + let frame = aResponse.frames[0]; + do_check_eq(objActor1, frame.arguments[0].actor); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(aObject) { + debugger; + } + stopMe({ foo: "bar" }); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_profiler_activation-01.js b/devtools/server/tests/unit/test_profiler_activation-01.js new file mode 100644 index 000000000..31efbb5e3 --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_activation-01.js @@ -0,0 +1,89 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests whether the profiler module and actor have the correct state on + * initialization, activation, and when a clients' connection closes. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); +const MAX_PROFILER_ENTRIES = 10000000; + +function run_test() +{ + // Ensure the profiler is not running when the test starts (it could + // happen if the MOZ_PROFILER_STARTUP environment variable is set). + Profiler.StopProfiler(); + + get_chrome_actors((client1, form1) => { + let actor1 = form1.profilerActor; + get_chrome_actors((client2, form2) => { + let actor2 = form2.profilerActor; + test_activate(client1, actor1, client2, actor2, () => { + do_test_finished(); + }); + }); + }); + + do_test_pending(); +} + +function test_activate(client1, actor1, client2, actor2, callback) { + // Profiler should be inactive at this point. + client1.request({ to: actor1, type: "isActive" }, response => { + do_check_false(Profiler.IsActive()); + do_check_false(response.isActive); + do_check_eq(response.currentTime, undefined); + do_check_true(typeof response.position === "number"); + do_check_true(typeof response.totalSize === "number"); + do_check_true(typeof response.generation === "number"); + + // Start the profiler on the first connection.... + client1.request({ to: actor1, type: "startProfiler", entries: MAX_PROFILER_ENTRIES }, response => { + do_check_true(Profiler.IsActive()); + do_check_true(response.started); + do_check_true(typeof response.position === "number"); + do_check_true(typeof response.totalSize === "number"); + do_check_true(typeof response.generation === "number"); + do_check_true(response.position >= 0 && response.position < response.totalSize); + do_check_true(response.totalSize === MAX_PROFILER_ENTRIES); + + // On the next connection just make sure the actor has been instantiated. + client2.request({ to: actor2, type: "isActive" }, response => { + do_check_true(Profiler.IsActive()); + do_check_true(response.isActive); + do_check_true(response.currentTime > 0); + do_check_true(typeof response.position === "number"); + do_check_true(typeof response.totalSize === "number"); + do_check_true(typeof response.generation === "number"); + do_check_true(response.position >= 0 && response.position < response.totalSize); + do_check_true(response.totalSize === MAX_PROFILER_ENTRIES); + + let origConnectionClosed = DebuggerServer._connectionClosed; + + DebuggerServer._connectionClosed = function (conn) { + origConnectionClosed.call(this, conn); + + // The first client is the only actor that started the profiler, + // however the second client can request the accumulated profile data + // at any moment, so the profiler module shouldn't have deactivated. + do_check_true(Profiler.IsActive()); + + DebuggerServer._connectionClosed = function (conn) { + origConnectionClosed.call(this, conn); + + // Now there are no open clients at all, it should *definitely* + // be deactivated by now. + do_check_false(Profiler.IsActive()); + + callback(); + }; + client2.close(); + }; + client1.close(); + }); + }); + }); +} diff --git a/devtools/server/tests/unit/test_profiler_activation-02.js b/devtools/server/tests/unit/test_profiler_activation-02.js new file mode 100644 index 000000000..cf06b1e06 --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_activation-02.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests whether the profiler actor correctly handles the case where the + * built-in module was already started. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); +const WAIT_TIME = 1000; // ms + +function run_test() +{ + // Ensure the profiler is already running when the test starts. + Profiler.StartProfiler(1000000, 1, ["js"], 1); + + DevToolsUtils.waitForTime(WAIT_TIME).then(() => { + + get_chrome_actors((client, form) => { + let actor = form.profilerActor; + test_start_time(client, actor, () => { + client.close().then(do_test_finished); + }); + }); + }); + + do_test_pending(); +} + +function test_start_time(client, actor, callback) { + // Profiler should already be active at this point. + client.request({ to: actor, type: "isActive" }, firstResponse => { + do_check_true(Profiler.IsActive()); + do_check_true(firstResponse.isActive); + do_check_true(firstResponse.currentTime > 0); + + client.request({ to: actor, type: "getProfile" }, secondResponse => { + do_check_true("profile" in secondResponse); + do_check_true(secondResponse.currentTime > firstResponse.currentTime); + + callback(); + }); + }); +} diff --git a/devtools/server/tests/unit/test_profiler_bufferstatus.js b/devtools/server/tests/unit/test_profiler_bufferstatus.js new file mode 100644 index 000000000..9c86bf817 --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_bufferstatus.js @@ -0,0 +1,127 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests if the profiler actor returns its buffer status via getBufferInfo. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); +const INITIAL_WAIT_TIME = 100; // ms +const MAX_WAIT_TIME = 20000; // ms +const MAX_PROFILER_ENTRIES = 10000000; + +function run_test() +{ + // Ensure the profiler is not running when the test starts (it could + // happen if the MOZ_PROFILER_STARTUP environment variable is set). + Profiler.StopProfiler(); + + get_chrome_actors((client, form) => { + let actor = form.profilerActor; + check_empty_buffer(client, actor, () => { + activate_profiler(client, actor, startTime => { + wait_for_samples(client, actor, () => { + check_buffer(client, actor, () => { + deactivate_profiler(client, actor, () => { + client.close().then(do_test_finished); + }); + }); + }); + }); + }); + }); + + do_test_pending(); +} + +function check_buffer(client, actor, callback) +{ + client.request({ to: actor, type: "isActive" }, response => { + do_check_true(typeof response.position === "number"); + do_check_true(typeof response.totalSize === "number"); + do_check_true(typeof response.generation === "number"); + do_check_true(response.position > 0 && response.position < response.totalSize); + do_check_true(response.totalSize === MAX_PROFILER_ENTRIES); + // There's no way we'll fill the buffer in this test. + do_check_true(response.generation === 0); + + callback(); + }); +} + +function check_empty_buffer(client, actor, callback) +{ + client.request({ to: actor, type: "isActive" }, response => { + do_check_false(Profiler.IsActive()); + do_check_false(response.isActive); + do_check_true(response.position === void 0); + do_check_true(response.totalSize === void 0); + do_check_true(response.generation === void 0); + do_check_false(response.isActive); + do_check_eq(response.currentTime, undefined); + calback(); + }); +} + +function activate_profiler(client, actor, callback) +{ + client.request({ to: actor, type: "startProfiler", entries: MAX_PROFILER_ENTRIES }, response => { + do_check_true(response.started); + client.request({ to: actor, type: "isActive" }, response => { + do_check_true(response.isActive); + callback(response.currentTime); + }); + }); +} + +function deactivate_profiler(client, actor, callback) +{ + client.request({ to: actor, type: "stopProfiler" }, response => { + do_check_false(response.started); + client.request({ to: actor, type: "isActive" }, response => { + do_check_false(response.isActive); + callback(); + }); + }); +} + +function wait_for_samples(client, actor, callback) +{ + function attempt(delay) + { + // No idea why, but Components.stack.sourceLine returns null. + let funcLine = Components.stack.lineNumber - 3; + + // Spin for the requested time, then take a sample. + let start = Date.now(); + let stack; + do_print("Attempt: delay = " + delay); + while (Date.now() - start < delay) { stack = Components.stack; } + do_print("Attempt: finished waiting."); + + client.request({ to: actor, type: "getProfile" }, response => { + // At this point, we may or may not have samples, depending on + // whether the spin loop above has given the profiler enough time + // to get started. + if (response.profile.threads[0].samples.length == 0) { + if (delay < MAX_WAIT_TIME) { + // Double the spin-wait time and try again. + do_print("Attempt: no samples, going around again."); + return attempt(delay * 2); + } else { + // We've waited long enough, so just fail. + do_print("Attempt: waited a long time, but no samples were collected."); + do_print("Giving up."); + do_check_true(false); + return; + } + } + callback(); + }); + } + + // Start off with a 100 millisecond delay. + attempt(INITIAL_WAIT_TIME); +} diff --git a/devtools/server/tests/unit/test_profiler_close.js b/devtools/server/tests/unit/test_profiler_close.js new file mode 100644 index 000000000..a8b3040fd --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_close.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests whether the profiler module is kept active when there are multiple + * client consumers and one requests deactivation. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); + +function run_test() +{ + get_chrome_actors((client1, form1) => { + let actor1 = form1.profilerActor; + get_chrome_actors((client2, form2) => { + let actor2 = form2.profilerActor; + test_close(client1, actor1, client2, actor2, () => { + client1.close(() => { + client2.close(() => { + do_test_finished(); + }); + }); + }); + }); + }); + + do_test_pending(); +} + +function activate_profiler(client, actor, callback) +{ + client.request({ to: actor, type: "startProfiler" }, response => { + do_check_true(response.started); + do_check_true(Profiler.IsActive()); + + client.request({ to: actor, type: "isActive" }, response => { + do_check_true(response.isActive); + callback(); + }); + }); +} + +function deactivate_profiler(client, actor, callback) +{ + client.request({ to: actor, type: "stopProfiler" }, response => { + do_check_false(response.started); + do_check_true(Profiler.IsActive()); + + client.request({ to: actor, type: "isActive" }, response => { + do_check_true(response.isActive); + callback(); + }); + }); +} + +function test_close(client1, actor1, client2, actor2, callback) +{ + activate_profiler(client1, actor1, () => { + activate_profiler(client2, actor2, () => { + deactivate_profiler(client1, actor1, () => { + deactivate_profiler(client2, actor2, () => { + callback(); + }); + }); + }); + }); +} diff --git a/devtools/server/tests/unit/test_profiler_data.js b/devtools/server/tests/unit/test_profiler_data.js new file mode 100644 index 000000000..2a79eed1f --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_data.js @@ -0,0 +1,110 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests if the profiler actor can correctly retrieve a profile after + * it is activated. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); +const INITIAL_WAIT_TIME = 100; // ms +const MAX_WAIT_TIME = 20000; // ms + +function run_test() +{ + get_chrome_actors((client, form) => { + let actor = form.profilerActor; + activate_profiler(client, actor, startTime => { + test_data(client, actor, startTime, () => { + deactivate_profiler(client, actor, () => { + client.close().then(do_test_finished); + }); + }); + }); + }); + + do_test_pending(); +} + +function activate_profiler(client, actor, callback) +{ + client.request({ to: actor, type: "startProfiler" }, response => { + do_check_true(response.started); + client.request({ to: actor, type: "isActive" }, response => { + do_check_true(response.isActive); + callback(response.currentTime); + }); + }); +} + +function deactivate_profiler(client, actor, callback) +{ + client.request({ to: actor, type: "stopProfiler" }, response => { + do_check_false(response.started); + client.request({ to: actor, type: "isActive" }, response => { + do_check_false(response.isActive); + callback(); + }); + }); +} + +function test_data(client, actor, startTime, callback) +{ + function attempt(delay) + { + // No idea why, but Components.stack.sourceLine returns null. + let funcLine = Components.stack.lineNumber - 3; + + // Spin for the requested time, then take a sample. + let start = Date.now(); + let stack; + do_print("Attempt: delay = " + delay); + while (Date.now() - start < delay) { stack = Components.stack; } + do_print("Attempt: finished waiting."); + + client.request({ to: actor, type: "getProfile", startTime }, response => { + // Any valid getProfile response should have the following top + // level structure. + do_check_eq(typeof response.profile, "object"); + do_check_eq(typeof response.profile.meta, "object"); + do_check_eq(typeof response.profile.meta.platform, "string"); + do_check_eq(typeof response.profile.threads, "object"); + do_check_eq(typeof response.profile.threads[0], "object"); + do_check_eq(typeof response.profile.threads[0].samples, "object"); + + // At this point, we may or may not have samples, depending on + // whether the spin loop above has given the profiler enough time + // to get started. + if (response.profile.threads[0].samples.length == 0) { + if (delay < MAX_WAIT_TIME) { + // Double the spin-wait time and try again. + do_print("Attempt: no samples, going around again."); + return attempt(delay * 2); + } else { + // We've waited long enough, so just fail. + do_print("Attempt: waited a long time, but no samples were collected."); + do_print("Giving up."); + do_check_true(false); + return; + } + } + + // Now check the samples. At least one sample is expected to + // have been in the busy wait above. + let loc = stack.name + " (" + stack.filename + ":" + funcLine + ")"; + let thread0 = response.profile.threads[0]; + do_check_true(thread0.samples.data.some(sample => { + let frames = getInflatedStackLocations(thread0, sample); + return frames.length != 0 && + frames.some(location => (location == loc)); + })); + + callback(); + }); + } + + // Start off with a 100 millisecond delay. + attempt(INITIAL_WAIT_TIME); +} diff --git a/devtools/server/tests/unit/test_profiler_events-01.js b/devtools/server/tests/unit/test_profiler_events-01.js new file mode 100644 index 000000000..b8ca592b9 --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_events-01.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests the event notification service for the profiler actor. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); +const { ProfilerFront } = require("devtools/shared/fronts/profiler"); + +function run_test() { + run_next_test(); +} + +add_task(function* () { + let [client, form] = yield getChromeActors(); + let front = new ProfilerFront(client, form); + + let events = [0, 0, 0, 0]; + front.on("console-api-profiler", () => events[0]++); + front.on("profiler-started", () => events[1]++); + front.on("profiler-stopped", () => events[2]++); + client.addListener("eventNotification", (type, response) => { + do_check_true(type === "eventNotification"); + events[3]++; + }); + + yield front.startProfiler(); + yield front.stopProfiler(); + + // All should be empty without binding events + do_check_true(events[0] === 0); + do_check_true(events[1] === 0); + do_check_true(events[2] === 0); + do_check_true(events[3] === 0); + + let ret = yield front.registerEventNotifications({ events: ["console-api-profiler", "profiler-started", "profiler-stopped"] }); + do_check_true(ret.registered.length === 3); + + yield front.startProfiler(); + do_check_true(events[0] === 0); + do_check_true(events[1] === 1); + do_check_true(events[2] === 0); + do_check_true(events[3] === 1, "compatibility events supported for eventNotifications"); + + yield front.stopProfiler(); + do_check_true(events[0] === 0); + do_check_true(events[1] === 1); + do_check_true(events[2] === 1); + do_check_true(events[3] === 2, "compatibility events supported for eventNotifications"); + + ret = yield front.unregisterEventNotifications({ events: ["console-api-profiler", "profiler-started", "profiler-stopped"] }); + do_check_true(ret.registered.length === 3); +}); + +function getChromeActors() { + let deferred = promise.defer(); + get_chrome_actors((client, form) => deferred.resolve([client, form])); + return deferred.promise; +} diff --git a/devtools/server/tests/unit/test_profiler_events-02.js b/devtools/server/tests/unit/test_profiler_events-02.js new file mode 100644 index 000000000..fed702043 --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_events-02.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests the event notification service for the profiler actor. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); +const MAX_PROFILER_ENTRIES = 10000000; +const { ProfilerFront } = require("devtools/shared/fronts/profiler"); +const { waitForTime } = DevToolsUtils; + +function run_test() { + run_next_test(); +} + +add_task(function* () { + let [client, form] = yield getChromeActors(); + let front = new ProfilerFront(client, form); + + // Ensure the profiler is not running when the test starts (it could + // happen if the MOZ_PROFILER_STARTUP environment variable is set). + Profiler.StopProfiler(); + let eventsCalled = 0; + let handledThreeTimes = promise.defer(); + + front.on("profiler-status", (response) => { + dump("'profiler-status' fired\n"); + do_check_true(typeof response.position === "number"); + do_check_true(typeof response.totalSize === "number"); + do_check_true(typeof response.generation === "number"); + do_check_true(response.position > 0 && response.position < response.totalSize); + do_check_true(response.totalSize === MAX_PROFILER_ENTRIES); + // There's no way we'll fill the buffer in this test. + do_check_true(response.generation === 0); + + eventsCalled++; + if (eventsCalled > 2) { + handledThreeTimes.resolve(); + } + }); + + yield front.setProfilerStatusInterval(1); + dump("Set the profiler-status event interval to 1\n"); + yield front.startProfiler(); + yield waitForTime(500); + yield front.stopProfiler(); + + do_check_true(eventsCalled === 0, "No 'profiler-status' events should be fired before registering."); + + let ret = yield front.registerEventNotifications({ events: ["profiler-status"] }); + do_check_true(ret.registered.length === 1); + + yield front.startProfiler(); + yield handledThreeTimes.promise; + yield front.stopProfiler(); + do_check_true(eventsCalled >= 3, "profiler-status fired atleast three times while recording"); + + let totalEvents = eventsCalled; + yield waitForTime(50); + do_check_true(totalEvents === eventsCalled, "No more profiler-status events after recording."); +}); + +function getChromeActors() { + let deferred = promise.defer(); + get_chrome_actors((client, form) => deferred.resolve([client, form])); + return deferred.promise; +} diff --git a/devtools/server/tests/unit/test_profiler_getbufferinfo.js b/devtools/server/tests/unit/test_profiler_getbufferinfo.js new file mode 100644 index 000000000..1ec536738 --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_getbufferinfo.js @@ -0,0 +1,123 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests if the profiler actor returns its buffer status via getBufferInfo. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); +const INITIAL_WAIT_TIME = 100; // ms +const MAX_WAIT_TIME = 20000; // ms +const MAX_PROFILER_ENTRIES = 10000000; + +function run_test() +{ + // Ensure the profiler is not running when the test starts (it could + // happen if the MOZ_PROFILER_STARTUP environment variable is set). + Profiler.StopProfiler(); + + get_chrome_actors((client, form) => { + let actor = form.profilerActor; + check_empty_buffer(client, actor, () => { + activate_profiler(client, actor, startTime => { + wait_for_samples(client, actor, () => { + check_buffer(client, actor, () => { + deactivate_profiler(client, actor, () => { + client.close().then(do_test_finished); + }); + }); + }); + }); + }); + }); + + do_test_pending(); +} + +function check_empty_buffer(client, actor, callback) +{ + client.request({ to: actor, type: "getBufferInfo" }, response => { + do_check_true(response.position === 0); + do_check_true(response.totalSize === 0); + do_check_true(response.generation === 0); + callback(); + }); +} + +function check_buffer(client, actor, callback) +{ + client.request({ to: actor, type: "getBufferInfo" }, response => { + do_check_true(typeof response.position === "number"); + do_check_true(typeof response.totalSize === "number"); + do_check_true(typeof response.generation === "number"); + do_check_true(response.position > 0 && response.position < response.totalSize); + do_check_true(response.totalSize === MAX_PROFILER_ENTRIES); + // There's no way we'll fill the buffer in this test. + do_check_true(response.generation === 0); + + callback(); + }); +} + +function activate_profiler(client, actor, callback) +{ + client.request({ to: actor, type: "startProfiler", entries: MAX_PROFILER_ENTRIES }, response => { + do_check_true(response.started); + client.request({ to: actor, type: "isActive" }, response => { + do_check_true(response.isActive); + callback(response.currentTime); + }); + }); +} + +function deactivate_profiler(client, actor, callback) +{ + client.request({ to: actor, type: "stopProfiler" }, response => { + do_check_false(response.started); + client.request({ to: actor, type: "isActive" }, response => { + do_check_false(response.isActive); + callback(); + }); + }); +} + +function wait_for_samples(client, actor, callback) +{ + function attempt(delay) + { + // No idea why, but Components.stack.sourceLine returns null. + let funcLine = Components.stack.lineNumber - 3; + + // Spin for the requested time, then take a sample. + let start = Date.now(); + let stack; + do_print("Attempt: delay = " + delay); + while (Date.now() - start < delay) { stack = Components.stack; } + do_print("Attempt: finished waiting."); + + client.request({ to: actor, type: "getProfile" }, response => { + // At this point, we may or may not have samples, depending on + // whether the spin loop above has given the profiler enough time + // to get started. + if (response.profile.threads[0].samples.length == 0) { + if (delay < MAX_WAIT_TIME) { + // Double the spin-wait time and try again. + do_print("Attempt: no samples, going around again."); + return attempt(delay * 2); + } else { + // We've waited long enough, so just fail. + do_print("Attempt: waited a long time, but no samples were collected."); + do_print("Giving up."); + do_check_true(false); + return; + } + } + callback(); + }); + } + + // Start off with a 100 millisecond delay. + attempt(INITIAL_WAIT_TIME); +} diff --git a/devtools/server/tests/unit/test_profiler_getfeatures.js b/devtools/server/tests/unit/test_profiler_getfeatures.js new file mode 100644 index 000000000..5b37e7d55 --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_getfeatures.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests whether the profiler responds to "getFeatures" adequately. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); + +function run_test() +{ + get_chrome_actors((client, form) => { + let actor = form.profilerActor; + test_getfeatures(client, actor, () => { + client.close().then(() => { + do_test_finished(); + }); + }); + }); + + do_test_pending(); +} + +function test_getfeatures(client, actor, callback) +{ + client.request({ to: actor, type: "getFeatures" }, response => { + do_check_eq(typeof response.features, "object"); + do_check_true(response.features.length >= 1); + do_check_eq(typeof response.features[0], "string"); + do_check_true(response.features.indexOf("js") != -1); + callback(); + }); +} diff --git a/devtools/server/tests/unit/test_profiler_getsharedlibraryinformation.js b/devtools/server/tests/unit/test_profiler_getsharedlibraryinformation.js new file mode 100644 index 000000000..a36577320 --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_getsharedlibraryinformation.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests whether the profiler responds to "getSharedLibraryInformation" adequately. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); + +function run_test() +{ + get_chrome_actors((client, form) => { + let actor = form.profilerActor; + test_getsharedlibraryinformation(client, actor, () => { + client.close().then(() => { + do_test_finished(); + }); + }); + }); + + do_test_pending(); +} + +function test_getsharedlibraryinformation(client, actor, callback) +{ + client.request({ to: actor, type: "getSharedLibraryInformation" }, response => { + do_check_eq(typeof response.sharedLibraryInformation, "string"); + let libs = []; + try { + libs = JSON.parse(response.sharedLibraryInformation); + } catch (e) { + do_check_true(false); + } + do_check_eq(typeof libs, "object"); + do_check_true(libs.length >= 1); + do_check_eq(typeof libs[0], "object"); + do_check_eq(typeof libs[0].name, "string"); + do_check_eq(typeof libs[0].start, "number"); + do_check_eq(typeof libs[0].end, "number"); + do_check_true(libs[0].start <= libs[0].end); + callback(); + }); +} diff --git a/devtools/server/tests/unit/test_promise_state-01.js b/devtools/server/tests/unit/test_promise_state-01.js new file mode 100644 index 000000000..a525560ab --- /dev/null +++ b/devtools/server/tests/unit/test_promise_state-01.js @@ -0,0 +1,40 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that the preview in a Promise's grip is correct when the Promise is + * pending. + */ + +function run_test() +{ + initTestDebuggerServer(); + const debuggee = addTestGlobal("test-promise-state"); + const client = new DebuggerClient(DebuggerServer.connectPipe()); + client.connect().then(function () { + attachTestTabAndResume(client, "test-promise-state", function (response, tabClient, threadClient) { + Task.spawn(function* () { + const packet = yield executeOnNextTickAndWaitForPause(() => evalCode(debuggee), client); + + const grip = packet.frame.environment.bindings.variables.p; + ok(grip.value.preview); + equal(grip.value.class, "Promise"); + equal(grip.value.promiseState.state, "pending"); + + finishClient(client); + }); + }); + }); + do_test_pending(); +} + +function evalCode(debuggee) { + Components.utils.evalInSandbox( + "doTest();\n" + + function doTest() { + var p = new Promise(function () {}); + debugger; + }, + debuggee + ); +} diff --git a/devtools/server/tests/unit/test_promise_state-02.js b/devtools/server/tests/unit/test_promise_state-02.js new file mode 100644 index 000000000..cf44f1946 --- /dev/null +++ b/devtools/server/tests/unit/test_promise_state-02.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that the preview in a Promise's grip is correct when the Promise is + * fulfilled. + */ + +function run_test() +{ + initTestDebuggerServer(); + const debuggee = addTestGlobal("test-promise-state"); + const client = new DebuggerClient(DebuggerServer.connectPipe()); + client.connect().then(function () { + attachTestTabAndResume(client, "test-promise-state", function (response, tabClient, threadClient) { + Task.spawn(function* () { + const packet = yield executeOnNextTickAndWaitForPause(() => evalCode(debuggee), client); + + const grip = packet.frame.environment.bindings.variables.p; + ok(grip.value.preview); + equal(grip.value.class, "Promise"); + equal(grip.value.promiseState.state, "fulfilled"); + equal(grip.value.promiseState.value.actorID, packet.frame.arguments[0].actorID, + "The promise's fulfilled state value should be the same value passed to the then function"); + + finishClient(client); + }); + }); + }); + do_test_pending(); +} + +function evalCode(debuggee) { + Components.utils.evalInSandbox( + "doTest();\n" + + function doTest() { + var resolved = Promise.resolve({}); + resolved.then(() => { + var p = resolved; + debugger; + }); + }, + debuggee + ); +} diff --git a/devtools/server/tests/unit/test_promise_state-03.js b/devtools/server/tests/unit/test_promise_state-03.js new file mode 100644 index 000000000..cf64e3e27 --- /dev/null +++ b/devtools/server/tests/unit/test_promise_state-03.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that the preview in a Promise's grip is correct when the Promise is + * rejected. + */ + +function run_test() +{ + initTestDebuggerServer(); + const debuggee = addTestGlobal("test-promise-state"); + const client = new DebuggerClient(DebuggerServer.connectPipe()); + client.connect().then(function () { + attachTestTabAndResume(client, "test-promise-state", function (response, tabClient, threadClient) { + Task.spawn(function* () { + const packet = yield executeOnNextTickAndWaitForPause(() => evalCode(debuggee), client); + + const grip = packet.frame.environment.bindings.variables.p; + ok(grip.value.preview); + equal(grip.value.class, "Promise"); + equal(grip.value.promiseState.state, "rejected"); + equal(grip.value.promiseState.reason.actorID, packet.frame.arguments[0].actorID, + "The promise's rejected state reason should be the same value passed to the then function"); + + finishClient(client); + }); + }); + }); + do_test_pending(); +} + +function evalCode(debuggee) { + Components.utils.evalInSandbox( + "doTest();\n" + + function doTest() { + var resolved = Promise.reject(new Error("uh oh")); + resolved.then(null, () => { + var p = resolved; + debugger; + }); + }, + debuggee + ); +} diff --git a/devtools/server/tests/unit/test_promises_actor_attach.js b/devtools/server/tests/unit/test_promises_actor_attach.js new file mode 100644 index 000000000..17c2a1f41 --- /dev/null +++ b/devtools/server/tests/unit/test_promises_actor_attach.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can attach and detach to the PromisesActor under the correct + * states. + */ + +const { PromisesFront } = require("devtools/shared/fronts/promises"); + +add_task(function* () { + let client = yield startTestDebuggerServer("promises-actor-test"); + let chromeActors = yield getChromeActors(client); + + // We have to attach the chrome TabActor before playing with the PromiseActor + yield attachTab(client, chromeActors); + yield testAttach(client, chromeActors); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "promises-actor-test"); + ok(targetTab, "Found our target tab."); + + let [ tabResponse ] = yield attachTab(client, targetTab); + + yield testAttach(client, tabResponse); + + yield close(client); +}); + +function* testAttach(client, parent) { + let promises = PromisesFront(client, parent); + + try { + yield promises.detach(); + ok(false, "Should not be able to detach when in a detached state."); + } catch (e) { + ok(true, "Expected detach to fail when already in a detached state."); + } + + yield promises.attach(); + ok(true, "Expected attach to succeed."); + + try { + yield promises.attach(); + ok(false, "Should not be able to attach when in an attached state."); + } catch (e) { + ok(true, "Expected attach to fail when already in an attached state."); + } + + yield promises.detach(); + ok(true, "Expected detach to succeed."); +} diff --git a/devtools/server/tests/unit/test_promises_actor_exist.js b/devtools/server/tests/unit/test_promises_actor_exist.js new file mode 100644 index 000000000..13eef3e99 --- /dev/null +++ b/devtools/server/tests/unit/test_promises_actor_exist.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that the PromisesActor exists in the TabActors and ChromeActors. + */ + +add_task(function* () { + let client = yield startTestDebuggerServer("promises-actor-test"); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "promises-actor-test"); + ok(targetTab, "Found our target tab."); + + // Attach to the TabActor and check the response + client.request({ to: targetTab.actor, type: "attach" }, response => { + ok(!("error" in response), "Expect no error in response."); + ok(response.from, targetTab.actor, + "Expect the target TabActor in response form field."); + ok(response.type, "tabAttached", + "Expect tabAttached in the response type."); + is(typeof response.promisesActor === "string", + "Should have a tab context PromisesActor."); + }); + + let chromeActors = yield getChromeActors(client); + ok(typeof chromeActors.promisesActor === "string", + "Should have a chrome context PromisesActor."); +}); diff --git a/devtools/server/tests/unit/test_promises_actor_list_promises.js b/devtools/server/tests/unit/test_promises_actor_list_promises.js new file mode 100644 index 000000000..f5b273121 --- /dev/null +++ b/devtools/server/tests/unit/test_promises_actor_list_promises.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can get the list of all live Promise objects from the + * PromisesActor. + */ + +"use strict"; + +const { PromisesFront } = require("devtools/shared/fronts/promises"); +const SECRET = "MyLittleSecret"; + +add_task(function* () { + let client = yield startTestDebuggerServer("promises-actor-test"); + let chromeActors = yield getChromeActors(client); + + // We have to attach the chrome TabActor before playing with the PromiseActor + yield attachTab(client, chromeActors); + yield testListPromises(client, chromeActors, v => + new Promise(resolve => resolve(v))); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "promises-actor-test"); + ok(targetTab, "Found our target tab."); + + yield testListPromises(client, targetTab, v => { + const debuggee = DebuggerServer.getTestGlobal("promises-actor-test"); + return debuggee.Promise.resolve(v); + }); + + yield close(client); +}); + +function* testListPromises(client, form, makePromise) { + let resolution = SECRET + Math.random(); + let promise = makePromise(resolution); + let front = PromisesFront(client, form); + + yield front.attach(); + + let promises = yield front.listPromises(); + + let found = false; + for (let p of promises) { + equal(p.type, "object", "Expect type to be Object"); + equal(p.class, "Promise", "Expect class to be Promise"); + equal(typeof p.promiseState.creationTimestamp, "number", + "Expect creation timestamp to be a number"); + equal(typeof p.promiseState.timeToSettle, "number", + "Expect time to settle to be a number"); + + if (p.promiseState.state === "fulfilled" && + p.promiseState.value === resolution) { + found = true; + } + } + + ok(found, "Found our promise"); + yield front.detach(); + // Appease eslint + void promise; +} diff --git a/devtools/server/tests/unit/test_promises_actor_onnewpromise.js b/devtools/server/tests/unit/test_promises_actor_onnewpromise.js new file mode 100644 index 000000000..04b3e6510 --- /dev/null +++ b/devtools/server/tests/unit/test_promises_actor_onnewpromise.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can get the list of all new Promise objects from the + * PromisesActor onNewPromise event handler. + */ + +"use strict"; + +const { PromisesFront } = require("devtools/shared/fronts/promises"); + +var events = require("sdk/event/core"); + +add_task(function* () { + let client = yield startTestDebuggerServer("promises-actor-test"); + let chromeActors = yield getChromeActors(client); + + ok(Promise.toString().includes("native code"), "Expect native DOM Promise"); + + // We have to attach the chrome TabActor before playing with the PromiseActor + yield attachTab(client, chromeActors); + yield testNewPromisesEvent(client, chromeActors, + v => new Promise(resolve => resolve(v))); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "promises-actor-test"); + ok(targetTab, "Found our target tab."); + + yield testNewPromisesEvent(client, targetTab, v => { + const debuggee = DebuggerServer.getTestGlobal("promises-actor-test"); + return debuggee.Promise.resolve(v); + }); + + yield close(client); +}); + +function* testNewPromisesEvent(client, form, makePromise) { + let front = PromisesFront(client, form); + let resolution = "MyLittleSecret" + Math.random(); + let found = false; + + yield front.attach(); + yield front.listPromises(); + + let onNewPromise = new Promise(resolve => { + events.on(front, "new-promises", promises => { + for (let p of promises) { + equal(p.type, "object", "Expect type to be Object"); + equal(p.class, "Promise", "Expect class to be Promise"); + equal(typeof p.promiseState.creationTimestamp, "number", + "Expect creation timestamp to be a number"); + + if (p.promiseState.state === "fulfilled" && + p.promiseState.value === resolution) { + found = true; + resolve(); + } else { + dump("Found non-target promise\n"); + } + } + }); + }); + + let promise = makePromise(resolution); + + yield onNewPromise; + ok(found, "Found our new promise"); + yield front.detach(); + // Appease eslint + void promise; +} diff --git a/devtools/server/tests/unit/test_promises_actor_onpromisesettled.js b/devtools/server/tests/unit/test_promises_actor_onpromisesettled.js new file mode 100644 index 000000000..ab4774733 --- /dev/null +++ b/devtools/server/tests/unit/test_promises_actor_onpromisesettled.js @@ -0,0 +1,92 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can get the list of Promise objects that have settled from the + * PromisesActor onPromiseSettled event handler. + */ + +"use strict"; + +Cu.import("resource://testing-common/PromiseTestUtils.jsm", this); + +const { PromisesFront } = require("devtools/shared/fronts/promises"); + +var events = require("sdk/event/core"); + +add_task(function* () { + let client = yield startTestDebuggerServer("promises-actor-test"); + let chromeActors = yield getChromeActors(client); + + ok(Promise.toString().includes("native code"), "Expect native DOM Promise"); + + // We have to attach the chrome TabActor before playing with the PromiseActor + yield attachTab(client, chromeActors); + yield testPromisesSettled(client, chromeActors, + v => new Promise(resolve => resolve(v)), + v => new Promise((resolve, reject) => reject(v))); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "promises-actor-test"); + ok(targetTab, "Found our target tab."); + + yield testPromisesSettled(client, targetTab, v => { + const debuggee = DebuggerServer.getTestGlobal("promises-actor-test"); + return debuggee.Promise.resolve(v); + }, v => { + const debuggee = DebuggerServer.getTestGlobal("promises-actor-test"); + return debuggee.Promise.reject(v); + }); + + yield close(client); +}); + +function* testPromisesSettled(client, form, makeResolvePromise, + makeRejectPromise) { + let front = PromisesFront(client, form); + let resolution = "MyLittleSecret" + Math.random(); + + yield front.attach(); + yield front.listPromises(); + + let onPromiseSettled = oncePromiseSettled(front, resolution, true, false); + let resolvedPromise = makeResolvePromise(resolution); + let foundResolvedPromise = yield onPromiseSettled; + ok(foundResolvedPromise, "Found our resolved promise"); + + PromiseTestUtils.expectUncaughtRejection(r => r.message == resolution); + onPromiseSettled = oncePromiseSettled(front, resolution, false, true); + let rejectedPromise = makeRejectPromise(resolution); + let foundRejectedPromise = yield onPromiseSettled; + ok(foundRejectedPromise, "Found our rejected promise"); + + yield front.detach(); + // Appease eslint + void resolvedPromise; + void rejectedPromise; +} + +function oncePromiseSettled(front, resolution, resolveValue, rejectValue) { + return new Promise(resolve => { + events.on(front, "promises-settled", promises => { + for (let p of promises) { + equal(p.type, "object", "Expect type to be Object"); + equal(p.class, "Promise", "Expect class to be Promise"); + equal(typeof p.promiseState.creationTimestamp, "number", + "Expect creation timestamp to be a number"); + equal(typeof p.promiseState.timeToSettle, "number", + "Expect time to settle to be a number"); + + if (p.promiseState.state === "fulfilled" && + p.promiseState.value === resolution) { + resolve(resolveValue); + } else if (p.promiseState.state === "rejected" && + p.promiseState.reason === resolution) { + resolve(rejectValue); + } else { + dump("Found non-target promise\n"); + } + } + }); + }); +} diff --git a/devtools/server/tests/unit/test_promises_client_getdependentpromises.js b/devtools/server/tests/unit/test_promises_client_getdependentpromises.js new file mode 100644 index 000000000..8900cf81c --- /dev/null +++ b/devtools/server/tests/unit/test_promises_client_getdependentpromises.js @@ -0,0 +1,112 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can get the list of dependent promises from the ObjectClient. + */ + +"use strict"; + +const { PromisesFront } = require("devtools/shared/fronts/promises"); + +var events = require("sdk/event/core"); + +add_task(function* () { + let client = yield startTestDebuggerServer("test-promises-dependentpromises"); + let chromeActors = yield getChromeActors(client); + yield attachTab(client, chromeActors); + + ok(Promise.toString().includes("native code"), "Expect native DOM Promise."); + + yield testGetDependentPromises(client, chromeActors, () => { + let p = new Promise(() => {}); + p.name = "p"; + let q = p.then(); + q.name = "q"; + let r = p.then(null, () => {}); + r.name = "r"; + + return p; + }); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "test-promises-dependentpromises"); + ok(targetTab, "Found our target tab."); + yield attachTab(client, targetTab); + + yield testGetDependentPromises(client, targetTab, () => { + const debuggee = + DebuggerServer.getTestGlobal("test-promises-dependentpromises"); + + let p = new debuggee.Promise(() => {}); + p.name = "p"; + let q = p.then(); + q.name = "q"; + let r = p.then(null, () => {}); + r.name = "r"; + + return p; + }); + + yield close(client); +}); + +function* testGetDependentPromises(client, form, makePromises) { + let front = PromisesFront(client, form); + + yield front.attach(); + yield front.listPromises(); + + // Get the grip for promise p + let onNewPromise = new Promise(resolve => { + events.on(front, "new-promises", promises => { + for (let p of promises) { + if (p.preview.ownProperties.name && + p.preview.ownProperties.name.value === "p") { + resolve(p); + } + } + }); + }); + + let promise = makePromises(); + + let grip = yield onNewPromise; + ok(grip, "Found our promise p."); + + let objectClient = new ObjectClient(client, grip); + ok(objectClient, "Got Object Client."); + + // Get the dependent promises for promise p and assert that the list of + // dependent promises is correct + yield new Promise(resolve => { + objectClient.getDependentPromises(response => { + let dependentNames = response.promises.map(p => + p.preview.ownProperties.name.value); + let expectedDependentNames = ["q", "r"]; + + equal(dependentNames.length, expectedDependentNames.length, + "Got expected number of dependent promises."); + + for (let i = 0; i < dependentNames.length; i++) { + equal(dependentNames[i], expectedDependentNames[i], + "Got expected dependent name."); + } + + for (let p of response.promises) { + equal(p.type, "object", "Expect type to be Object."); + equal(p.class, "Promise", "Expect class to be Promise."); + equal(typeof p.promiseState.creationTimestamp, "number", + "Expect creation timestamp to be a number."); + ok(!p.promiseState.timeToSettle, + "Expect time to settle to be undefined."); + } + + resolve(); + }); + }); + + yield front.detach(); + // Appease eslint + void promise; +} diff --git a/devtools/server/tests/unit/test_promises_object_creationtimestamp.js b/devtools/server/tests/unit/test_promises_object_creationtimestamp.js new file mode 100644 index 000000000..1360be56a --- /dev/null +++ b/devtools/server/tests/unit/test_promises_object_creationtimestamp.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we get the approximate time range for promise creation timestamp. + */ + +"use strict"; + +const { PromisesFront } = require("devtools/shared/fronts/promises"); + +var events = require("sdk/event/core"); + +add_task(function* () { + let client = yield startTestDebuggerServer("promises-object-test"); + let chromeActors = yield getChromeActors(client); + + ok(Promise.toString().includes("native code"), "Expect native DOM Promise."); + + // We have to attach the chrome TabActor before playing with the PromiseActor + yield attachTab(client, chromeActors); + yield testPromiseCreationTimestamp(client, chromeActors, v => { + return new Promise(resolve => resolve(v)); + }); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "promises-object-test"); + ok(targetTab, "Found our target tab."); + + yield testPromiseCreationTimestamp(client, targetTab, v => { + const debuggee = DebuggerServer.getTestGlobal("promises-object-test"); + return debuggee.Promise.resolve(v); + }); + + yield close(client); +}); + +function* testPromiseCreationTimestamp(client, form, makePromise) { + let front = PromisesFront(client, form); + let resolution = "MyLittleSecret" + Math.random(); + + yield front.attach(); + yield front.listPromises(); + + let onNewPromise = new Promise(resolve => { + events.on(front, "new-promises", promises => { + for (let p of promises) { + if (p.promiseState.state === "fulfilled" && + p.promiseState.value === resolution) { + resolve(p); + } + } + }); + }); + + let start = Date.now(); + let promise = makePromise(resolution); + let end = Date.now(); + + let grip = yield onNewPromise; + ok(grip, "Found our new promise."); + + let creationTimestamp = grip.promiseState.creationTimestamp; + + ok(start - 1 <= creationTimestamp && creationTimestamp <= end + 1, + "Expect promise creation timestamp to be within elapsed time range."); + + yield front.detach(); + // Appease eslint + void promise; +} diff --git a/devtools/server/tests/unit/test_promises_object_timetosettle-01.js b/devtools/server/tests/unit/test_promises_object_timetosettle-01.js new file mode 100644 index 000000000..1b3240e3d --- /dev/null +++ b/devtools/server/tests/unit/test_promises_object_timetosettle-01.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test whether or not we get the time to settle depending on the state of the + * promise. + */ + +"use strict"; + +const { PromisesFront } = require("devtools/shared/fronts/promises"); + +var events = require("sdk/event/core"); + +add_task(function* () { + let client = yield startTestDebuggerServer("test-promises-timetosettle"); + let chromeActors = yield getChromeActors(client); + + ok(Promise.toString().includes("native code"), "Expect native DOM Promise."); + + // We have to attach the chrome TabActor before playing with the PromiseActor + yield attachTab(client, chromeActors); + yield testGetTimeToSettle(client, chromeActors, () => { + let p = new Promise(() => {}); + p.name = "p"; + let q = p.then(); + q.name = "q"; + + return p; + }); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "test-promises-timetosettle"); + ok(targetTab, "Found our target tab."); + + yield testGetTimeToSettle(client, targetTab, () => { + const debuggee = + DebuggerServer.getTestGlobal("test-promises-timetosettle"); + + let p = new debuggee.Promise(() => {}); + p.name = "p"; + let q = p.then(); + q.name = "q"; + + return p; + }); + + yield close(client); +}); + +function* testGetTimeToSettle(client, form, makePromises) { + let front = PromisesFront(client, form); + + yield front.attach(); + yield front.listPromises(); + + let onNewPromise = new Promise(resolve => { + events.on(front, "new-promises", promises => { + for (let p of promises) { + if (p.promiseState.state === "pending") { + ok(!p.promiseState.timeToSettle, + "Expect no time to settle for unsettled promise."); + } else { + ok(p.promiseState.timeToSettle, + "Expect time to settle for settled promise."); + equal(typeof p.promiseState.timeToSettle, "number", + "Expect time to settle to be a number."); + } + } + resolve(); + }); + }); + + let promise = makePromises(); + + yield onNewPromise; + yield front.detach(); + // Appease eslint + void promise; +} diff --git a/devtools/server/tests/unit/test_promises_object_timetosettle-02.js b/devtools/server/tests/unit/test_promises_object_timetosettle-02.js new file mode 100644 index 000000000..10224d0b9 --- /dev/null +++ b/devtools/server/tests/unit/test_promises_object_timetosettle-02.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we get the expected settlement time for promise time to settle. + */ + +"use strict"; + +const { PromisesFront } = require("devtools/shared/fronts/promises"); +const { setTimeout } = require("sdk/timers"); + +var events = require("sdk/event/core"); + +add_task(function* () { + let client = yield startTestDebuggerServer("test-promises-timetosettle"); + let chromeActors = yield getChromeActors(client); + yield attachTab(client, chromeActors); + + ok(Promise.toString().includes("native code"), "Expect native DOM Promise."); + + // We have to attach the chrome TabActor before playing with the PromiseActor + yield attachTab(client, chromeActors); + yield testGetTimeToSettle(client, chromeActors, + v => new Promise(resolve => setTimeout(() => resolve(v), 100))); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "test-promises-timetosettle"); + ok(targetTab, "Found our target tab."); + yield attachTab(client, targetTab); + + yield testGetTimeToSettle(client, targetTab, v => { + const debuggee = + DebuggerServer.getTestGlobal("test-promises-timetosettle"); + return new debuggee.Promise(resolve => setTimeout(() => resolve(v), 100)); + }); + + yield close(client); +}); + +function* testGetTimeToSettle(client, form, makePromise) { + let front = PromisesFront(client, form); + let resolution = "MyLittleSecret" + Math.random(); + let found = false; + + yield front.attach(); + yield front.listPromises(); + + let onNewPromise = new Promise(resolve => { + events.on(front, "promises-settled", promises => { + for (let p of promises) { + if (p.promiseState.state === "fulfilled" && + p.promiseState.value === resolution) { + let timeToSettle = Math.floor(p.promiseState.timeToSettle / 100) * 100; + ok(timeToSettle >= 100, + "Expect time to settle for resolved promise to be " + + "at least 100ms, got " + timeToSettle + "ms."); + found = true; + resolve(); + } else { + dump("Found non-target promise.\n"); + } + } + }); + }); + + let promise = makePromise(resolution); + + yield onNewPromise; + ok(found, "Found our new promise."); + yield front.detach(); + // Appease eslint + void promise; +} diff --git a/devtools/server/tests/unit/test_protocolSpec.js b/devtools/server/tests/unit/test_protocolSpec.js new file mode 100644 index 000000000..cc0746387 --- /dev/null +++ b/devtools/server/tests/unit/test_protocolSpec.js @@ -0,0 +1,17 @@ +const run_test = Test(function* () { + initTestDebuggerServer(); + const connection = DebuggerServer.connectPipe(); + const client = Async(new DebuggerClient(connection)); + + yield client.connect(); + + const response = yield client.request({ + to: "root", + type: "protocolDescription" + }); + + assert(response.from == "root"); + assert(typeof (response.types) === "object"); + + yield client.close(); +}); diff --git a/devtools/server/tests/unit/test_protocol_abort.js b/devtools/server/tests/unit/test_protocol_abort.js new file mode 100644 index 000000000..bb25d1b2c --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_abort.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Outstanding requests should be rejected when the connection aborts + * unexpectedly. + */ + +var protocol = require("devtools/shared/protocol"); +var {Arg, Option, RetVal} = protocol; +var events = require("sdk/event/core"); + +function simpleHello() { + return { + from: "root", + applicationType: "xpcshell-tests", + traits: [], + }; +} + +const rootSpec = protocol.generateActorSpec({ + typeName: "root", + + methods: { + simpleReturn: { + response: { value: RetVal() } + } + } +}); + +var RootActor = protocol.ActorClassWithSpec(rootSpec, { + typeName: "root", + initialize: function (conn) { + protocol.Actor.prototype.initialize.call(this, conn); + // Root actor owns itself. + this.manage(this); + this.actorID = "root"; + this.sequence = 0; + }, + + sayHello: simpleHello, + + simpleReturn: function () { + return this.sequence++; + } +}); + +var RootFront = protocol.FrontClassWithSpec(rootSpec, { + initialize: function (client) { + this.actorID = "root"; + protocol.Front.prototype.initialize.call(this, client); + // Root owns itself. + this.manage(this); + } +}); + +function run_test() { + DebuggerServer.createRootActor = RootActor; + DebuggerServer.init(); + + let trace = connectPipeTracing(); + let client = new DebuggerClient(trace); + let rootClient; + + client.connect().then(([applicationType, traits]) => { + rootClient = RootFront(client); + + rootClient.simpleReturn().then(() => { + ok(false, "Connection was aborted, request shouldn't resolve"); + do_test_finished(); + }, e => { + let error = e.toString(); + ok(true, "Connection was aborted, request rejected correctly"); + ok(error.includes("Request stack:"), "Error includes request stack"); + ok(error.includes("test_protocol_abort.js"), "Stack includes this test"); + do_test_finished(); + }); + + trace.close(); + }); + + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_protocol_async.js b/devtools/server/tests/unit/test_protocol_async.js new file mode 100644 index 000000000..75f053863 --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_async.js @@ -0,0 +1,184 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure we get replies in the same order that we sent their + * requests even when earlier requests take several event ticks to + * complete. + */ + +var protocol = require("devtools/shared/protocol"); +var {Arg, Option, RetVal} = protocol; +var events = require("sdk/event/core"); + +function simpleHello() { + return { + from: "root", + applicationType: "xpcshell-tests", + traits: [], + }; +} + +const rootSpec = protocol.generateActorSpec({ + typeName: "root", + + methods: { + simpleReturn: { + response: { value: RetVal() }, + }, + promiseReturn: { + request: { toWait: Arg(0, "number") }, + response: { value: RetVal("number") }, + }, + simpleThrow: { + response: { value: RetVal("number") } + }, + promiseThrow: { + response: { value: RetVal("number") }, + } + } +}); + +var RootActor = protocol.ActorClassWithSpec(rootSpec, { + initialize: function (conn) { + protocol.Actor.prototype.initialize.call(this, conn); + // Root actor owns itself. + this.manage(this); + this.actorID = "root"; + this.sequence = 0; + }, + + sayHello: simpleHello, + + simpleReturn: function () { + return this.sequence++; + }, + + promiseReturn: function (toWait) { + // Guarantee that this resolves after simpleReturn returns. + let deferred = promise.defer(); + let sequence = this.sequence++; + + // Wait until the number of requests specified by toWait have + // happened, to test queuing. + let check = () => { + if ((this.sequence - sequence) < toWait) { + do_execute_soon(check); + return; + } + deferred.resolve(sequence); + }; + do_execute_soon(check); + + return deferred.promise; + }, + + simpleThrow: function () { + throw new Error(this.sequence++); + }, + + promiseThrow: function () { + // Guarantee that this resolves after simpleReturn returns. + let deferred = promise.defer(); + let sequence = this.sequence++; + // This should be enough to force a failure if the code is broken. + do_timeout(150, () => { + deferred.reject(sequence++); + }); + return deferred.promise; + } +}); + +var RootFront = protocol.FrontClassWithSpec(rootSpec, { + initialize: function (client) { + this.actorID = "root"; + protocol.Front.prototype.initialize.call(this, client); + // Root owns itself. + this.manage(this); + } +}); + +function run_test() +{ + DebuggerServer.createRootActor = RootActor; + DebuggerServer.init(); + + let trace = connectPipeTracing(); + let client = new DebuggerClient(trace); + let rootClient; + + client.connect().then(([applicationType, traits]) => { + rootClient = RootFront(client); + + let calls = []; + let sequence = 0; + + // Execute a call that won't finish processing until 2 + // more calls have happened + calls.push(rootClient.promiseReturn(2).then(ret => { + do_check_eq(sequence, 0); // Check right return order + do_check_eq(ret, sequence++); // Check request handling order + })); + + // Put a few requests into the backlog + + calls.push(rootClient.simpleReturn().then(ret => { + do_check_eq(sequence, 1); // Check right return order + do_check_eq(ret, sequence++); // Check request handling order + })); + + calls.push(rootClient.simpleReturn().then(ret => { + do_check_eq(sequence, 2); // Check right return order + do_check_eq(ret, sequence++); // Check request handling order + })); + + calls.push(rootClient.simpleThrow().then(() => { + do_check_true(false, "simpleThrow shouldn't succeed!"); + }, error => { + do_check_eq(sequence++, 3); // Check right return order + })); + + // While packets are sent in the correct order, rejection handlers + // registered in "Promise.jsm" may be invoked later than fulfillment + // handlers, meaning that we can't check the actual order with certainty. + let deferAfterRejection = promise.defer(); + + calls.push(rootClient.promiseThrow().then(() => { + do_check_true(false, "promiseThrow shouldn't succeed!"); + }, error => { + do_check_eq(sequence++, 4); // Check right return order + do_check_true(true, "simple throw should throw"); + deferAfterRejection.resolve(); + })); + + calls.push(rootClient.simpleReturn().then(ret => { + return deferAfterRejection.promise.then(function () { + do_check_eq(sequence, 5); // Check right return order + do_check_eq(ret, sequence++); // Check request handling order + }); + })); + + // Break up the backlog with a long request that waits + // for another simpleReturn before completing + calls.push(rootClient.promiseReturn(1).then(ret => { + return deferAfterRejection.promise.then(function () { + do_check_eq(sequence, 6); // Check right return order + do_check_eq(ret, sequence++); // Check request handling order + }); + })); + + calls.push(rootClient.simpleReturn().then(ret => { + return deferAfterRejection.promise.then(function () { + do_check_eq(sequence, 7); // Check right return order + do_check_eq(ret, sequence++); // Check request handling order + }); + })); + + promise.all(calls).then(() => { + client.close().then(() => { + do_test_finished(); + }); + }); + }); + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_protocol_children.js b/devtools/server/tests/unit/test_protocol_children.js new file mode 100644 index 000000000..67773ebef --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_children.js @@ -0,0 +1,559 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test simple requests using the protocol helpers. + */ +var protocol = require("devtools/shared/protocol"); +var {preEvent, types, Arg, Option, RetVal} = protocol; + +var events = require("sdk/event/core"); + +function simpleHello() { + return { + from: "root", + applicationType: "xpcshell-tests", + traits: [], + }; +} + +var testTypes = {}; + +// Predeclaring the actor type so that it can be used in the +// implementation of the child actor. +types.addActorType("childActor"); + +const childSpec = protocol.generateActorSpec({ + typeName: "childActor", + + events: { + "event1" : { + a: Arg(0), + b: Arg(1), + c: Arg(2) + }, + "event2" : { + a: Arg(0), + b: Arg(1), + c: Arg(2) + }, + "named-event": { + type: "namedEvent", + a: Arg(0), + b: Arg(1), + c: Arg(2) + }, + "object-event": { + type: "objectEvent", + detail: Arg(0, "childActor#detail1"), + }, + "array-object-event": { + type: "arrayObjectEvent", + detail: Arg(0, "array:childActor#detail2"), + } + }, + + methods: { + echo: { + request: { str: Arg(0) }, + response: { str: RetVal("string") }, + }, + getDetail1: { + // This also exercises return-value-as-packet. + response: RetVal("childActor#detail1"), + }, + getDetail2: { + // This also exercises return-value-as-packet. + response: RetVal("childActor#detail2"), + }, + getIDDetail: { + response: { + idDetail: RetVal("childActor#actorid") + } + }, + getIntArray: { + request: { inputArray: Arg(0, "array:number") }, + response: RetVal("array:number") + }, + getSibling: { + request: { id: Arg(0) }, + response: { sibling: RetVal("childActor") } + }, + emitEvents: { + response: { value: "correct response" }, + }, + release: { + release: true + } + } +}); + +var ChildActor = protocol.ActorClassWithSpec(childSpec, { + // Actors returned by this actor should be owned by the root actor. + marshallPool: function () { return this.parent(); }, + + toString: function () { return "[ChildActor " + this.childID + "]"; }, + + initialize: function (conn, id) { + protocol.Actor.prototype.initialize.call(this, conn); + this.childID = id; + }, + + destroy: function () { + protocol.Actor.prototype.destroy.call(this); + this.destroyed = true; + }, + + form: function (detail) { + if (detail === "actorid") { + return this.actorID; + } + return { + actor: this.actorID, + childID: this.childID, + detail: detail + }; + }, + + echo: function (str) { + return str; + }, + + getDetail1: function () { + return this; + }, + + getDetail2: function () { + return this; + }, + + getIDDetail: function () { + return this; + }, + + getIntArray: function (inputArray) { + // Test that protocol.js converts an iterator to an array. + let f = function* () { + for (let i of inputArray) { + yield 2 * i; + } + }; + return f(); + }, + + getSibling: function (id) { + return this.parent().getChild(id); + }, + + emitEvents: function () { + events.emit(this, "event1", 1, 2, 3); + events.emit(this, "event2", 4, 5, 6); + events.emit(this, "named-event", 1, 2, 3); + events.emit(this, "object-event", this); + events.emit(this, "array-object-event", [this]); + }, + + release: function () { }, +}); + +var ChildFront = protocol.FrontClassWithSpec(childSpec, { + initialize: function (client, form) { + protocol.Front.prototype.initialize.call(this, client, form); + }, + + destroy: function () { + this.destroyed = true; + protocol.Front.prototype.destroy.call(this); + }, + + marshallPool: function () { return this.parent(); }, + + toString: function () { return "[child front " + this.childID + "]"; }, + + form: function (form, detail) { + if (detail === "actorid") { + return; + } + this.childID = form.childID; + this.detail = form.detail; + }, + + onEvent1: preEvent("event1", function (a, b, c) { + this.event1arg3 = c; + }), + + onEvent2a: preEvent("event2", function (a, b, c) { + return promise.resolve().then(() => this.event2arg3 = c); + }), + + onEvent2b: preEvent("event2", function (a, b, c) { + this.event2arg2 = b; + }), +}); + +types.addDictType("manyChildrenDict", { + child5: "childActor", + more: "array:childActor", +}); + +types.addLifetime("temp", "_temporaryHolder"); + +const rootSpec = protocol.generateActorSpec({ + typeName: "root", + + methods: { + getChild: { + request: { str: Arg(0) }, + response: { actor: RetVal("childActor") }, + }, + getChildren: { + request: { ids: Arg(0, "array:string") }, + response: { children: RetVal("array:childActor") }, + }, + getChildren2: { + request: { ids: Arg(0, "array:childActor") }, + response: { children: RetVal("array:childActor") }, + }, + getManyChildren: { + response: RetVal("manyChildrenDict") + }, + getTemporaryChild: { + request: { id: Arg(0) }, + response: { child: RetVal("temp:childActor") } + }, + clearTemporaryChildren: {} + } +}); + +var rootActor = null; +var RootActor = protocol.ActorClassWithSpec(rootSpec, { + toString: function () { return "[root actor]"; }, + + initialize: function (conn) { + rootActor = this; + this.actorID = "root"; + this._children = {}; + protocol.Actor.prototype.initialize.call(this, conn); + // Root actor owns itself. + this.manage(this); + }, + + sayHello: simpleHello, + + getChild: function (id) { + if (id in this._children) { + return this._children[id]; + } + let child = new ChildActor(this.conn, id); + this._children[id] = child; + return child; + }, + + getChildren: function (ids) { + return ids.map(id => this.getChild(id)); + }, + + getChildren2: function (ids) { + let f = function* () { + for (let c of ids) { + yield c; + } + }; + return f(); + }, + + getManyChildren: function () { + return { + foo: "bar", // note that this isn't in the specialization array. + child5: this.getChild("child5"), + more: [ this.getChild("child6"), this.getChild("child7") ] + }; + }, + + // This should remind you of a pause actor. + getTemporaryChild: function (id) { + if (!this._temporaryHolder) { + this._temporaryHolder = this.manage(new protocol.Actor(this.conn)); + } + return new ChildActor(this.conn, id); + }, + + clearTemporaryChildren: function (id) { + if (!this._temporaryHolder) { + return; + } + this._temporaryHolder.destroy(); + delete this._temporaryHolder; + } +}); + +var RootFront = protocol.FrontClassWithSpec(rootSpec, { + toString: function () { return "[root front]"; }, + initialize: function (client) { + this.actorID = "root"; + protocol.Front.prototype.initialize.call(this, client); + // Root actor owns itself. + this.manage(this); + }, + + getTemporaryChild: protocol.custom(function (id) { + if (!this._temporaryHolder) { + this._temporaryHolder = protocol.Front(this.conn); + this._temporaryHolder.actorID = this.actorID + "_temp"; + this._temporaryHolder = this.manage(this._temporaryHolder); + } + return this._getTemporaryChild(id); + }, { + impl: "_getTemporaryChild" + }), + + clearTemporaryChildren: protocol.custom(function () { + if (!this._temporaryHolder) { + return promise.resolve(undefined); + } + this._temporaryHolder.destroy(); + delete this._temporaryHolder; + return this._clearTemporaryChildren(); + }, { + impl: "_clearTemporaryChildren" + }) +}); + +function run_test() +{ + DebuggerServer.createRootActor = (conn => { + return RootActor(conn); + }); + DebuggerServer.init(); + + let trace = connectPipeTracing(); + let client = new DebuggerClient(trace); + client.connect().then(([applicationType, traits]) => { + trace.expectReceive({"from":"<actorid>", "applicationType":"xpcshell-tests", "traits":[]}); + do_check_eq(applicationType, "xpcshell-tests"); + + let rootFront = RootFront(client); + let childFront = null; + + let expectRootChildren = size => { + do_check_eq(rootActor._poolMap.size, size + 1); + do_check_eq(rootFront._poolMap.size, size + 1); + if (childFront) { + do_check_eq(childFront._poolMap.size, 0); + } + }; + + rootFront.getChild("child1").then(ret => { + trace.expectSend({"type":"getChild", "str":"child1", "to":"<actorid>"}); + trace.expectReceive({"actor":"<actorid>", "from":"<actorid>"}); + + childFront = ret; + do_check_true(childFront instanceof ChildFront); + do_check_eq(childFront.childID, "child1"); + expectRootChildren(1); + }).then(() => { + // Request the child again, make sure the same is returned. + return rootFront.getChild("child1"); + }).then(ret => { + trace.expectSend({"type":"getChild", "str":"child1", "to":"<actorid>"}); + trace.expectReceive({"actor":"<actorid>", "from":"<actorid>"}); + + expectRootChildren(1); + do_check_true(ret === childFront); + }).then(() => { + return childFront.echo("hello"); + }).then(ret => { + trace.expectSend({"type":"echo", "str":"hello", "to":"<actorid>"}); + trace.expectReceive({"str":"hello", "from":"<actorid>"}); + + do_check_eq(ret, "hello"); + }).then(() => { + return childFront.getDetail1(); + }).then(ret => { + trace.expectSend({"type":"getDetail1", "to":"<actorid>"}); + trace.expectReceive({"actor":"<actorid>", "childID":"child1", "detail":"detail1", "from":"<actorid>"}); + do_check_true(ret === childFront); + do_check_eq(childFront.detail, "detail1"); + }).then(() => { + return childFront.getDetail2(); + }).then(ret => { + trace.expectSend({"type":"getDetail2", "to":"<actorid>"}); + trace.expectReceive({"actor":"<actorid>", "childID":"child1", "detail":"detail2", "from":"<actorid>"}); + do_check_true(ret === childFront); + do_check_eq(childFront.detail, "detail2"); + }).then(() => { + return childFront.getIDDetail(); + }).then(ret => { + trace.expectSend({"type":"getIDDetail", "to":"<actorid>"}); + trace.expectReceive({"idDetail": childFront.actorID, "from":"<actorid>"}); + do_check_true(ret === childFront); + }).then(() => { + return childFront.getSibling("siblingID"); + }).then(ret => { + trace.expectSend({"type":"getSibling", "id":"siblingID", "to":"<actorid>"}); + trace.expectReceive({"sibling":{"actor":"<actorid>", "childID":"siblingID"}, "from":"<actorid>"}); + + expectRootChildren(2); + }).then(ret => { + return rootFront.getTemporaryChild("temp1").then(temp1 => { + trace.expectSend({"type":"getTemporaryChild", "id":"temp1", "to":"<actorid>"}); + trace.expectReceive({"child":{"actor":"<actorid>", "childID":"temp1"}, "from":"<actorid>"}); + + // At this point we expect two direct children, plus the temporary holder + // which should hold 1 itself. + do_check_eq(rootActor._temporaryHolder.__poolMap.size, 1); + do_check_eq(rootFront._temporaryHolder.__poolMap.size, 1); + + expectRootChildren(3); + return rootFront.getTemporaryChild("temp2").then(temp2 => { + trace.expectSend({"type":"getTemporaryChild", "id":"temp2", "to":"<actorid>"}); + trace.expectReceive({"child":{"actor":"<actorid>", "childID":"temp2"}, "from":"<actorid>"}); + + // Same amount of direct children, and an extra in the temporary holder. + expectRootChildren(3); + do_check_eq(rootActor._temporaryHolder.__poolMap.size, 2); + do_check_eq(rootFront._temporaryHolder.__poolMap.size, 2); + + // Get the children of the temporary holder... + let checkActors = rootActor._temporaryHolder.__poolMap.values(); + let checkFronts = rootFront._temporaryHolder.__poolMap.values(); + + // Now release the temporary holders and expect them to drop again. + return rootFront.clearTemporaryChildren().then(() => { + trace.expectSend({"type":"clearTemporaryChildren", "to":"<actorid>"}); + trace.expectReceive({"from":"<actorid>"}); + + expectRootChildren(2); + do_check_false(!!rootActor._temporaryHolder); + do_check_false(!!rootFront._temporaryHolder); + for (let checkActor of checkActors) { + do_check_true(checkActor.destroyed); + do_check_true(checkActor.destroyed); + } + }); + }); + }); + }).then(ret => { + return rootFront.getChildren(["child1", "child2"]); + }).then(ret => { + trace.expectSend({"type":"getChildren", "ids":["child1", "child2"], "to":"<actorid>"}); + trace.expectReceive({"children":[{"actor":"<actorid>", "childID":"child1"}, {"actor":"<actorid>", "childID":"child2"}], "from":"<actorid>"}); + + expectRootChildren(3); + do_check_true(ret[0] === childFront); + do_check_true(ret[1] !== childFront); + do_check_true(ret[1] instanceof ChildFront); + + // On both children, listen to events. We're only + // going to trigger events on the first child, so an event + // triggered on the second should cause immediate failures. + + let set = new Set(["event1", "event2", "named-event", "object-event", "array-object-event"]); + + childFront.on("event1", (a, b, c) => { + do_check_eq(a, 1); + do_check_eq(b, 2); + do_check_eq(c, 3); + // Verify that the pre-event handler was called. + do_check_eq(childFront.event1arg3, 3); + set.delete("event1"); + }); + childFront.on("event2", (a, b, c) => { + do_check_eq(a, 4); + do_check_eq(b, 5); + do_check_eq(c, 6); + // Verify that the async pre-event handler was called, + // setting the property before this handler was called. + do_check_eq(childFront.event2arg3, 6); + // And check that the sync preEvent with the same name is also + // executed + do_check_eq(childFront.event2arg2, 5); + set.delete("event2"); + }); + childFront.on("named-event", (a, b, c) => { + do_check_eq(a, 1); + do_check_eq(b, 2); + do_check_eq(c, 3); + set.delete("named-event"); + }); + childFront.on("object-event", (obj) => { + do_check_true(obj === childFront); + do_check_eq(childFront.detail, "detail1"); + set.delete("object-event"); + }); + childFront.on("array-object-event", (array) => { + do_check_true(array[0] === childFront); + do_check_eq(childFront.detail, "detail2"); + set.delete("array-object-event"); + }); + + let fail = function () { + do_throw("Unexpected event"); + }; + ret[1].on("event1", fail); + ret[1].on("event2", fail); + ret[1].on("named-event", fail); + ret[1].on("object-event", fail); + ret[1].on("array-object-event", fail); + + return childFront.emitEvents().then(() => { + trace.expectSend({"type":"emitEvents", "to":"<actorid>"}); + trace.expectReceive({"type":"event1", "a":1, "b":2, "c":3, "from":"<actorid>"}); + trace.expectReceive({"type":"event2", "a":4, "b":5, "c":6, "from":"<actorid>"}); + trace.expectReceive({"type":"namedEvent", "a":1, "b":2, "c":3, "from":"<actorid>"}); + trace.expectReceive({"type":"objectEvent", "detail":{"actor":"<actorid>", "childID":"child1", "detail":"detail1"}, "from":"<actorid>"}); + trace.expectReceive({"type":"arrayObjectEvent", "detail":[{"actor":"<actorid>", "childID":"child1", "detail":"detail2"}], "from":"<actorid>"}); + trace.expectReceive({"value":"correct response", "from":"<actorid>"}); + + + do_check_eq(set.size, 0); + }); + }).then(ret => { + return rootFront.getManyChildren(); + }).then(ret => { + trace.expectSend({"type":"getManyChildren", "to":"<actorid>"}); + trace.expectReceive({"foo":"bar", "child5":{"actor":"<actorid>", "childID":"child5"}, "more":[{"actor":"<actorid>", "childID":"child6"}, {"actor":"<actorid>", "childID":"child7"}], "from":"<actorid>"}); + + // Check all the crazy stuff we did in getManyChildren + do_check_eq(ret.foo, "bar"); + do_check_eq(ret.child5.childID, "child5"); + do_check_eq(ret.more[0].childID, "child6"); + do_check_eq(ret.more[1].childID, "child7"); + }).then(() => { + // Test accepting a generator. + let f = function* () { + for (let i of [1, 2, 3, 4, 5]) { + yield i; + } + }; + return childFront.getIntArray(f()); + }).then((ret) => { + do_check_eq(ret.length, 5); + let expected = [2, 4, 6, 8, 10]; + for (let i = 0; i < 5; ++i) { + do_check_eq(ret[i], expected[i]); + } + }).then(() => { + return rootFront.getChildren(["child1", "child2"]); + }).then(ids => { + let f = function* () { + for (let id of ids) { + yield id; + } + }; + return rootFront.getChildren2(f()); + }).then(ret => { + do_check_eq(ret.length, 2); + do_check_true(ret[0] === childFront); + do_check_true(ret[1] !== childFront); + do_check_true(ret[1] instanceof ChildFront); + }).then(() => { + client.close().then(() => { + do_test_finished(); + }); + }).then(null, err => { + do_report_unexpected_exception(err, "Failure executing test"); + }); + }); + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_protocol_formtype.js b/devtools/server/tests/unit/test_protocol_formtype.js new file mode 100644 index 000000000..27ac0bee9 --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_formtype.js @@ -0,0 +1,177 @@ +var protocol = require("devtools/shared/protocol"); +var {Arg, Option, RetVal} = protocol; + +protocol.types.addActorType("child"); +protocol.types.addActorType("root"); + +const childSpec = protocol.generateActorSpec({ + typeName: "child", + + methods: { + getChild: { + response: RetVal("child") + } + } +}); + +// The child actor doesn't provide a form description +var ChildActor = protocol.ActorClassWithSpec(childSpec, { + initialize(conn) { + protocol.Actor.prototype.initialize.call(this, conn); + }, + + form(detail) { + return { + actor: this.actorID, + extra: "extra" + }; + }, + + getChild: function () { + return this; + } +}); + +var ChildFront = protocol.FrontClassWithSpec(childSpec, { + initialize(client) { + protocol.Front.prototype.initialize.call(this, client); + }, + + form(v, ctx, detail) { + this.extra = v.extra; + } +}); + +const rootSpec = protocol.generateActorSpec({ + typeName: "root", + + // Basic form type, relies on implicit DictType creation + formType: { + childActor: "child" + }, + + // This detail uses explicit DictType creation + "formType#detail1": protocol.types.addDictType("RootActorFormTypeDetail1", { + detailItem: "child" + }), + + // This detail a string type. + "formType#actorid": "string", + + methods: { + getDefault: { + response: RetVal("root") + }, + getDetail1: { + response: RetVal("root#detail1") + }, + getDetail2: { + response: { + item: RetVal("root#actorid") + } + }, + getUnknownDetail: { + response: RetVal("root#unknownDetail") + } + } +}); + +// The root actor does provide a form description. +var RootActor = protocol.ActorClassWithSpec(rootSpec, { + initialize(conn) { + protocol.Actor.prototype.initialize.call(this, conn); + this.manage(this); + this.child = new ChildActor(); + }, + + sayHello() { + return { + from: "root", + applicationType: "xpcshell-tests", + traits: [] + }; + }, + + form(detail) { + if (detail === "detail1") { + return { + actor: this.actorID, + detailItem: this.child + }; + } else if (detail === "actorid") { + return this.actorID; + } + + return { + actor: this.actorID, + childActor: this.child + }; + }, + + getDefault: function () { + return this; + }, + + getDetail1: function () { + return this; + }, + + getDetail2: function () { + return this; + }, + + getUnknownDetail: function () { + return this; + } +}); + +var RootFront = protocol.FrontClassWithSpec(rootSpec, { + initialize(client) { + this.actorID = "root"; + protocol.Front.prototype.initialize.call(this, client); + + // Root owns itself. + this.manage(this); + }, + + form(v, ctx, detail) { + this.lastForm = v; + } +}); + +const run_test = Test(function* () { + DebuggerServer.createRootActor = (conn => { + return RootActor(conn); + }); + DebuggerServer.init(); + + const connection = DebuggerServer.connectPipe(); + const conn = new DebuggerClient(connection); + const client = Async(conn); + + yield client.connect(); + + let rootFront = RootFront(conn); + + // Trigger some methods that return forms. + let retval = yield rootFront.getDefault(); + do_check_true(retval instanceof RootFront); + do_check_true(rootFront.lastForm.childActor instanceof ChildFront); + + retval = yield rootFront.getDetail1(); + do_check_true(retval instanceof RootFront); + do_check_true(rootFront.lastForm.detailItem instanceof ChildFront); + + retval = yield rootFront.getDetail2(); + do_check_true(retval instanceof RootFront); + do_check_true(typeof (rootFront.lastForm) === "string"); + + // getUnknownDetail should fail, since no typeName is specified. + try { + yield rootFront.getUnknownDetail(); + do_check_true(false); + } catch (ex) { + } + + yield client.close(); +}); diff --git a/devtools/server/tests/unit/test_protocol_longstring.js b/devtools/server/tests/unit/test_protocol_longstring.js new file mode 100644 index 000000000..c37f4251e --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_longstring.js @@ -0,0 +1,218 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test simple requests using the protocol helpers. + */ +var protocol = require("devtools/shared/protocol"); +var {RetVal, Arg, Option} = protocol; +var events = require("sdk/event/core"); +var {LongStringActor} = require("devtools/server/actors/string"); + +// The test implicitly relies on this. +require("devtools/shared/fronts/string"); + +function simpleHello() { + return { + from: "root", + applicationType: "xpcshell-tests", + traits: [], + }; +} + +DebuggerServer.LONG_STRING_LENGTH = DebuggerServer.LONG_STRING_INITIAL_LENGTH = DebuggerServer.LONG_STRING_READ_LENGTH = 5; + +var SHORT_STR = "abc"; +var LONG_STR = "abcdefghijklmnop"; + +var rootActor = null; + +const rootSpec = protocol.generateActorSpec({ + typeName: "root", + + events: { + "string-event": { + str: Arg(0, "longstring") + } + }, + + methods: { + shortString: { + response: { value: RetVal("longstring") }, + }, + longString: { + response: { value: RetVal("longstring") }, + }, + emitShortString: { + oneway: true, + }, + emitLongString: { + oneway: true, + } + } +}); + +var RootActor = protocol.ActorClassWithSpec(rootSpec, { + initialize: function (conn) { + rootActor = this; + protocol.Actor.prototype.initialize.call(this, conn); + // Root actor owns itself. + this.manage(this); + this.actorID = "root"; + }, + + sayHello: simpleHello, + + shortString: function () { + return new LongStringActor(this.conn, SHORT_STR); + }, + + longString: function () { + return new LongStringActor(this.conn, LONG_STR); + }, + + emitShortString: function () { + events.emit(this, "string-event", new LongStringActor(this.conn, SHORT_STR)); + }, + + emitLongString: function () { + events.emit(this, "string-event", new LongStringActor(this.conn, LONG_STR)); + }, +}); + +var RootFront = protocol.FrontClassWithSpec(rootSpec, { + initialize: function (client) { + this.actorID = "root"; + protocol.Front.prototype.initialize.call(this, client); + // Root owns itself. + this.manage(this); + } +}); + +function run_test() +{ + DebuggerServer.createRootActor = (conn => { + return RootActor(conn); + }); + + DebuggerServer.init(); + let trace = connectPipeTracing(); + let client = new DebuggerClient(trace); + let rootClient; + + let strfront = null; + + let expectRootChildren = function (size) { + do_check_eq(rootActor.__poolMap.size, size + 1); + do_check_eq(rootClient.__poolMap.size, size + 1); + }; + + + client.connect().then(([applicationType, traits]) => { + rootClient = RootFront(client); + + // Root actor has no children yet. + expectRootChildren(0); + + trace.expectReceive({"from":"<actorid>", "applicationType":"xpcshell-tests", "traits":[]}); + do_check_eq(applicationType, "xpcshell-tests"); + rootClient.shortString().then(ret => { + trace.expectSend({"type":"shortString", "to":"<actorid>"}); + trace.expectReceive({"value":"abc", "from":"<actorid>"}); + + // Should only own the one reference (itself) at this point. + expectRootChildren(0); + strfront = ret; + }).then(() => { + return strfront.string(); + }).then(ret => { + do_check_eq(ret, SHORT_STR); + }).then(() => { + return rootClient.longString(); + }).then(ret => { + trace.expectSend({"type":"longString", "to":"<actorid>"}); + trace.expectReceive({"value":{"type":"longString", "actor":"<actorid>", "length":16, "initial":"abcde"}, "from":"<actorid>"}); + + strfront = ret; + // Should own a reference to itself and an extra string now. + expectRootChildren(1); + }).then(() => { + return strfront.string(); + }).then(ret => { + trace.expectSend({"type":"substring", "start":5, "end":10, "to":"<actorid>"}); + trace.expectReceive({"substring":"fghij", "from":"<actorid>"}); + trace.expectSend({"type":"substring", "start":10, "end":15, "to":"<actorid>"}); + trace.expectReceive({"substring":"klmno", "from":"<actorid>"}); + trace.expectSend({"type":"substring", "start":15, "end":20, "to":"<actorid>"}); + trace.expectReceive({"substring":"p", "from":"<actorid>"}); + + do_check_eq(ret, LONG_STR); + }).then(() => { + return strfront.release(); + }).then(() => { + trace.expectSend({"type":"release", "to":"<actorid>"}); + trace.expectReceive({"from":"<actorid>"}); + + // That reference should be removed now. + expectRootChildren(0); + }).then(() => { + let deferred = promise.defer(); + rootClient.once("string-event", (str) => { + trace.expectSend({"type":"emitShortString", "to":"<actorid>"}); + trace.expectReceive({"type":"string-event", "str":"abc", "from":"<actorid>"}); + + do_check_true(!!str); + strfront = str; + // Shouldn't generate any new references + expectRootChildren(0); + // will generate no packets. + strfront.string().then((value) => { deferred.resolve(value); }); + }); + rootClient.emitShortString(); + return deferred.promise; + }).then(value => { + do_check_eq(value, SHORT_STR); + }).then(() => { + // Will generate no packets + return strfront.release(); + }).then(() => { + let deferred = promise.defer(); + rootClient.once("string-event", (str) => { + trace.expectSend({"type":"emitLongString", "to":"<actorid>"}); + trace.expectReceive({"type":"string-event", "str":{"type":"longString", "actor":"<actorid>", "length":16, "initial":"abcde"}, "from":"<actorid>"}); + + do_check_true(!!str); + // Should generate one new reference + expectRootChildren(1); + strfront = str; + strfront.string().then((value) => { + trace.expectSend({"type":"substring", "start":5, "end":10, "to":"<actorid>"}); + trace.expectReceive({"substring":"fghij", "from":"<actorid>"}); + trace.expectSend({"type":"substring", "start":10, "end":15, "to":"<actorid>"}); + trace.expectReceive({"substring":"klmno", "from":"<actorid>"}); + trace.expectSend({"type":"substring", "start":15, "end":20, "to":"<actorid>"}); + trace.expectReceive({"substring":"p", "from":"<actorid>"}); + + deferred.resolve(value); + }); + }); + rootClient.emitLongString(); + return deferred.promise; + }).then(value => { + do_check_eq(value, LONG_STR); + }).then(() => { + return strfront.release(); + }).then(() => { + trace.expectSend({"type":"release", "to":"<actorid>"}); + trace.expectReceive({"from":"<actorid>"}); + expectRootChildren(0); + }).then(() => { + client.close().then(() => { + do_test_finished(); + }); + }).then(null, err => { + do_report_unexpected_exception(err, "Failure executing test"); + }); + }); + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_protocol_simple.js b/devtools/server/tests/unit/test_protocol_simple.js new file mode 100644 index 000000000..c85003954 --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_simple.js @@ -0,0 +1,319 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test simple requests using the protocol helpers. + */ + +var protocol = require("devtools/shared/protocol"); +var {Arg, Option, RetVal} = protocol; +var events = require("sdk/event/core"); + +function simpleHello() { + return { + from: "root", + applicationType: "xpcshell-tests", + traits: [], + }; +} + +const rootSpec = protocol.generateActorSpec({ + typeName: "root", + + events: { + "oneway": { a: Arg(0) }, + "falsyOptions": { + zero: Option(0), + farce: Option(0) + } + }, + + methods: { + simpleReturn: { + response: { value: RetVal() }, + }, + promiseReturn: { + response: { value: RetVal("number") }, + }, + simpleArgs: { + request: { + firstArg: Arg(0), + secondArg: Arg(1), + }, + response: RetVal() + }, + nestedArgs: { + request: { + firstArg: Arg(0), + nest: { + secondArg: Arg(1), + nest: { + thirdArg: Arg(2) + } + } + }, + response: RetVal() + }, + optionArgs: { + request: { + option1: Option(0), + option2: Option(0) + }, + response: RetVal() + }, + optionalArgs: { + request: { + a: Arg(0), + b: Arg(1, "nullable:number") + }, + response: { + value: RetVal("number") + }, + }, + arrayArgs: { + request: { + a: Arg(0, "array:number") + }, + response: { + arrayReturn: RetVal("array:number") + }, + }, + nestedArrayArgs: { + request: { a: Arg(0, "array:array:number") }, + response: { value: RetVal("array:array:number") }, + }, + renamedEcho: { + request: { + type: "echo", + a: Arg(0), + }, + response: { + value: RetVal("string") + }, + }, + testOneWay: { + request: { a: Arg(0) }, + oneway: true + }, + emitFalsyOptions: { + oneway: true + } + } +}); + +var RootActor = protocol.ActorClassWithSpec(rootSpec, { + initialize: function (conn) { + protocol.Actor.prototype.initialize.call(this, conn); + // Root actor owns itself. + this.manage(this); + this.actorID = "root"; + }, + + sayHello: simpleHello, + + simpleReturn: function () { + return 1; + }, + + promiseReturn: function () { + return promise.resolve(1); + }, + + simpleArgs: function (a, b) { + return { firstResponse: a + 1, secondResponse: b + 1 }; + }, + + nestedArgs: function (a, b, c) { + return { a: a, b: b, c: c }; + }, + + optionArgs: function (options) { + return { option1: options.option1, option2: options.option2 }; + }, + + optionalArgs: function (a, b = 200) { + return b; + }, + + arrayArgs: function (a) { + return a; + }, + + nestedArrayArgs: function (a) { + return a; + }, + + /** + * Test that the 'type' part of the request packet works + * correctly when the type isn't the same as the method name + */ + renamedEcho: function (a) { + if (this.conn.currentPacket.type != "echo") { + return "goodbye"; + } + return a; + }, + + testOneWay: function (a) { + // Emit to show that we got this message, because there won't be a response. + events.emit(this, "oneway", a); + }, + + emitFalsyOptions: function () { + events.emit(this, "falsyOptions", { zero: 0, farce: false }); + } +}); + +var RootFront = protocol.FrontClassWithSpec(rootSpec, { + initialize: function (client) { + this.actorID = "root"; + protocol.Front.prototype.initialize.call(this, client); + // Root owns itself. + this.manage(this); + } +}); + +function run_test() +{ + DebuggerServer.createRootActor = (conn => { + return RootActor(conn); + }); + DebuggerServer.init(); + + check_except(() => { + let badActor = ActorClassWithSpec({}, { + missing: preEvent("missing-event", function () { + }) + }); + }); + + protocol.types.getType("array:array:array:number"); + protocol.types.getType("array:array:array:number"); + + check_except(() => protocol.types.getType("unknown")); + check_except(() => protocol.types.getType("array:unknown")); + check_except(() => protocol.types.getType("unknown:number")); + let trace = connectPipeTracing(); + let client = new DebuggerClient(trace); + let rootClient; + + client.connect().then(([applicationType, traits]) => { + trace.expectReceive({"from":"<actorid>", "applicationType":"xpcshell-tests", "traits":[]}); + do_check_eq(applicationType, "xpcshell-tests"); + + rootClient = RootFront(client); + + rootClient.simpleReturn().then(ret => { + trace.expectSend({"type":"simpleReturn", "to":"<actorid>"}); + trace.expectReceive({"value":1, "from":"<actorid>"}); + do_check_eq(ret, 1); + }).then(() => { + return rootClient.promiseReturn(); + }).then(ret => { + trace.expectSend({"type":"promiseReturn", "to":"<actorid>"}); + trace.expectReceive({"value":1, "from":"<actorid>"}); + do_check_eq(ret, 1); + }).then(() => { + // Missing argument should throw an exception + check_except(() => { + rootClient.simpleArgs(5); + }); + + return rootClient.simpleArgs(5, 10); + }).then(ret => { + trace.expectSend({"type":"simpleArgs", "firstArg":5, "secondArg":10, "to":"<actorid>"}); + trace.expectReceive({"firstResponse":6, "secondResponse":11, "from":"<actorid>"}); + do_check_eq(ret.firstResponse, 6); + do_check_eq(ret.secondResponse, 11); + }).then(() => { + return rootClient.nestedArgs(1, 2, 3); + }).then(ret => { + trace.expectSend({"type":"nestedArgs", "firstArg":1, "nest":{"secondArg":2, "nest":{"thirdArg":3}}, "to":"<actorid>"}); + trace.expectReceive({"a":1, "b":2, "c":3, "from":"<actorid>"}); + do_check_eq(ret.a, 1); + do_check_eq(ret.b, 2); + do_check_eq(ret.c, 3); + }).then(() => { + return rootClient.optionArgs({ + "option1": 5, + "option2": 10 + }); + }).then(ret => { + trace.expectSend({"type":"optionArgs", "option1":5, "option2":10, "to":"<actorid>"}); + trace.expectReceive({"option1":5, "option2":10, "from":"<actorid>"}); + do_check_eq(ret.option1, 5); + do_check_eq(ret.option2, 10); + }).then(() => { + return rootClient.optionArgs({}); + }).then(ret => { + trace.expectSend({"type":"optionArgs", "to":"<actorid>"}); + trace.expectReceive({"from":"<actorid>"}); + do_check_true(typeof (ret.option1) === "undefined"); + do_check_true(typeof (ret.option2) === "undefined"); + }).then(() => { + // Explicitly call an optional argument... + return rootClient.optionalArgs(5, 10); + }).then(ret => { + trace.expectSend({"type":"optionalArgs", "a":5, "b":10, "to":"<actorid>"}); + trace.expectReceive({"value":10, "from":"<actorid>"}); + do_check_eq(ret, 10); + }).then(() => { + // Now don't pass the optional argument, expect the default. + return rootClient.optionalArgs(5); + }).then(ret => { + trace.expectSend({"type":"optionalArgs", "a":5, "to":"<actorid>"}); + trace.expectReceive({"value":200, "from":"<actorid>"}); + do_check_eq(ret, 200); + }).then(ret => { + return rootClient.arrayArgs([0, 1, 2, 3, 4, 5]); + }).then(ret => { + trace.expectSend({"type":"arrayArgs", "a":[0, 1, 2, 3, 4, 5], "to":"<actorid>"}); + trace.expectReceive({"arrayReturn":[0, 1, 2, 3, 4, 5], "from":"<actorid>"}); + do_check_eq(ret[0], 0); + do_check_eq(ret[5], 5); + }).then(() => { + return rootClient.arrayArgs([[5]]); + }).then(ret => { + trace.expectSend({"type":"arrayArgs", "a":[[5]], "to":"<actorid>"}); + trace.expectReceive({"arrayReturn":[[5]], "from":"<actorid>"}); + do_check_eq(ret[0][0], 5); + }).then(() => { + return rootClient.renamedEcho("hello"); + }).then(str => { + trace.expectSend({"type":"echo", "a":"hello", "to":"<actorid>"}); + trace.expectReceive({"value":"hello", "from":"<actorid>"}); + + do_check_eq(str, "hello"); + + let deferred = promise.defer(); + rootClient.on("oneway", (response) => { + trace.expectSend({"type":"testOneWay", "a":"hello", "to":"<actorid>"}); + trace.expectReceive({"type":"oneway", "a":"hello", "from":"<actorid>"}); + + do_check_eq(response, "hello"); + deferred.resolve(); + }); + do_check_true(typeof (rootClient.testOneWay("hello")) === "undefined"); + return deferred.promise; + }).then(() => { + let deferred = promise.defer(); + rootClient.on("falsyOptions", res => { + trace.expectSend({"type":"emitFalsyOptions", "to":"<actorid>"}); + trace.expectReceive({"type":"falsyOptions", "farce":false, "zero": 0, "from":"<actorid>"}); + + do_check_true(res.zero === 0); + do_check_true(res.farce === false); + deferred.resolve(); + }); + rootClient.emitFalsyOptions(); + return deferred.promise; + }).then(() => { + client.close().then(() => { + do_test_finished(); + }); + }).then(null, err => { + do_report_unexpected_exception(err, "Failure executing test"); + }); + }); + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_protocol_stack.js b/devtools/server/tests/unit/test_protocol_stack.js new file mode 100644 index 000000000..a81f99a8e --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_stack.js @@ -0,0 +1,98 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Client request stacks should span the entire process from before making the + * request to handling the reply from the server. The server frames are not + * included, nor can they be in most cases, since the server can be a remote + * device. + */ + +var protocol = require("devtools/shared/protocol"); +var {Arg, Option, RetVal} = protocol; +var events = require("sdk/event/core"); + +function simpleHello() { + return { + from: "root", + applicationType: "xpcshell-tests", + traits: [], + }; +} + +const rootSpec = protocol.generateActorSpec({ + typeName: "root", + + methods: { + simpleReturn: { + response: { value: RetVal() }, + } + } +}); + +var RootActor = protocol.ActorClassWithSpec(rootSpec, { + initialize: function (conn) { + protocol.Actor.prototype.initialize.call(this, conn); + // Root actor owns itself. + this.manage(this); + this.actorID = "root"; + this.sequence = 0; + }, + + sayHello: simpleHello, + + simpleReturn: function () { + return this.sequence++; + } +}); + +var RootFront = protocol.FrontClassWithSpec(rootSpec, { + initialize: function (client) { + this.actorID = "root"; + protocol.Front.prototype.initialize.call(this, client); + // Root owns itself. + this.manage(this); + } +}); + +function run_test() { + if (!Services.prefs.getBoolPref("javascript.options.asyncstack")) { + do_print("Async stacks are disabled."); + return; + } + + DebuggerServer.createRootActor = RootActor; + DebuggerServer.init(); + + let trace = connectPipeTracing(); + let client = new DebuggerClient(trace); + let rootClient; + + client.connect().then(function onConnect() { + rootClient = RootFront(client); + + rootClient.simpleReturn().then(() => { + let stack = Components.stack; + while (stack) { + do_print(stack.name); + if (stack.name == "onConnect") { + // Reached back to outer function before request + ok(true, "Complete stack"); + return; + } + stack = stack.asyncCaller || stack.caller; + } + ok(false, "Incomplete stack"); + }, () => { + ok(false, "Request failed unexpectedly"); + }).then(() => { + client.close().then(() => { + do_test_finished(); + }); + }); + }); + + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_protocol_unregister.js b/devtools/server/tests/unit/test_protocol_unregister.js new file mode 100644 index 000000000..5b32dd0a3 --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_unregister.js @@ -0,0 +1,44 @@ +const {types} = require("devtools/shared/protocol"); + + +function run_test() +{ + types.addType("test", { + read: (v) => { return "successful read: " + v; }, + write: (v) => { return "successful write: " + v; } + }); + + // Verify the type registered correctly. + + let type = types.getType("test"); + let arrayType = types.getType("array:test"); + do_check_eq(type.read("foo"), "successful read: foo"); + do_check_eq(arrayType.read(["foo"])[0], "successful read: foo"); + + types.removeType("test"); + + do_check_eq(type.name, "DEFUNCT:test"); + try { + types.getType("test"); + do_check_true(false, "getType should fail"); + } catch (ex) { + do_check_eq(ex.toString(), "Error: Unknown type: test"); + } + + try { + type.read("foo"); + do_check_true(false, "type.read should have thrown an exception."); + } catch (ex) { + do_check_eq(ex.toString(), "Error: Using defunct type: test"); + } + + try { + arrayType.read(["foo"]); + do_check_true(false, "array:test.read should have thrown an exception."); + } catch (ex) { + do_check_eq(ex.toString(), "Error: Using defunct type: test"); + } + +} + + diff --git a/devtools/server/tests/unit/test_reattach-thread.js b/devtools/server/tests/unit/test_reattach-thread.js new file mode 100644 index 000000000..6d089e896 --- /dev/null +++ b/devtools/server/tests/unit/test_reattach-thread.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that reattaching to a previously detached thread works. + */ + +var gClient, gDebuggee, gThreadClient, gTabClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = testGlobal("test-reattach"); + DebuggerServer.addTestGlobal(gDebuggee); + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(() => { + attachTestTab(gClient, "test-reattach", (aReply, aTabClient) => { + gTabClient = aTabClient; + test_attach(); + }); + }); + do_test_pending(); +} + +function test_attach() +{ + gTabClient.attachThread({}, (aResponse, aThreadClient) => { + do_check_eq(aThreadClient.state, "paused"); + gThreadClient = aThreadClient; + aThreadClient.resume(test_detach); + }); +} + +function test_detach() +{ + gThreadClient.detach(() => { + do_check_eq(gThreadClient.state, "detached"); + do_check_eq(gTabClient.thread, null); + test_reattach(); + }); +} + +function test_reattach() +{ + gTabClient.attachThread({}, (aResponse, aThreadClient) => { + do_check_neq(gThreadClient, aThreadClient); + do_check_eq(aThreadClient.state, "paused"); + do_check_eq(gTabClient.thread, aThreadClient); + aThreadClient.resume(cleanup); + }); +} + +function cleanup() +{ + gClient.close().then(do_test_finished); +} diff --git a/devtools/server/tests/unit/test_registerClient.js b/devtools/server/tests/unit/test_registerClient.js new file mode 100644 index 000000000..c018e4454 --- /dev/null +++ b/devtools/server/tests/unit/test_registerClient.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test the DebuggerClient.registerClient API + +var EventEmitter = require("devtools/shared/event-emitter"); + +var gClient; +var gActors; +var gTestClient; + +function TestActor(conn) { + this.conn = conn; +} +TestActor.prototype = { + actorPrefix: "test", + + start: function () { + this.conn.sendActorEvent(this.actorID, "foo", { + hello: "world" + }); + return {}; + } +}; +TestActor.prototype.requestTypes = { + "start": TestActor.prototype.start +}; + +function TestClient(client, form) { + this.client = client; + this.actor = form.test; + this.events = ["foo"]; + EventEmitter.decorate(this); + client.registerClient(this); + + this.detached = false; +} +TestClient.prototype = { + start: function () { + this.client.request({ + to: this.actor, + type: "start" + }); + }, + + detach: function (onDone) { + this.detached = true; + onDone(); + } +}; + +function run_test() +{ + DebuggerServer.addGlobalActor(TestActor); + + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + add_test(init); + add_test(test_client_events); + add_test(close_client); + run_next_test(); +} + +function init() +{ + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect() + .then(() => gClient.listTabs()) + .then(aResponse => { + gActors = aResponse; + gTestClient = new TestClient(gClient, aResponse); + run_next_test(); + }); +} + +function test_client_events() +{ + // Test DebuggerClient.registerClient and DebuggerServerConnection.sendActorEvent + gTestClient.on("foo", function (type, data) { + do_check_eq(type, "foo"); + do_check_eq(data.hello, "world"); + run_next_test(); + }); + gTestClient.start(); +} + +function close_client() { + gClient.close().then(() => { + // Check that client.detach method is call on client destruction + do_check_true(gTestClient.detached); + run_next_test(); + }); +} + diff --git a/devtools/server/tests/unit/test_register_actor.js b/devtools/server/tests/unit/test_register_actor.js new file mode 100644 index 000000000..8f3a243eb --- /dev/null +++ b/devtools/server/tests/unit/test_register_actor.js @@ -0,0 +1,113 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); + +function check_actors(expect) { + do_check_eq(expect, DebuggerServer.tabActorFactories.hasOwnProperty("registeredActor1")); + do_check_eq(expect, DebuggerServer.tabActorFactories.hasOwnProperty("registeredActor2")); + + do_check_eq(expect, DebuggerServer.globalActorFactories.hasOwnProperty("registeredActor2")); + do_check_eq(expect, DebuggerServer.globalActorFactories.hasOwnProperty("registeredActor1")); +} + +function run_test() +{ + // Allow incoming connections. + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + add_test(test_deprecated_api); + add_test(test_lazy_api); + add_test(cleanup); + run_next_test(); +} + +function test_deprecated_api() { + // The xpcshell-test/ path maps to resource://test/ + DebuggerServer.registerModule("xpcshell-test/registertestactors-01"); + DebuggerServer.registerModule("xpcshell-test/registertestactors-02"); + + check_actors(true); + + check_except(() => { + DebuggerServer.registerModule("xpcshell-test/registertestactors-01"); + }); + check_except(() => { + DebuggerServer.registerModule("xpcshell-test/registertestactors-02"); + }); + + DebuggerServer.unregisterModule("xpcshell-test/registertestactors-01"); + DebuggerServer.unregisterModule("xpcshell-test/registertestactors-02"); + check_actors(false); + + DebuggerServer.registerModule("xpcshell-test/registertestactors-01"); + DebuggerServer.registerModule("xpcshell-test/registertestactors-02"); + check_actors(true); + + run_next_test(); +} + +// Bug 988237: Test the new lazy actor loading +function test_lazy_api() { + let isActorLoaded = false; + let isActorInstanciated = false; + function onActorEvent(subject, topic, data) { + if (data == "loaded") { + isActorLoaded = true; + } else if (data == "instantiated") { + isActorInstanciated = true; + } + } + Services.obs.addObserver(onActorEvent, "actor", false); + DebuggerServer.registerModule("xpcshell-test/registertestactors-03", { + prefix: "lazy", + constructor: "LazyActor", + type: { global: true, tab: true } + }); + // The actor is immediatly registered, but not loaded + do_check_true(DebuggerServer.tabActorFactories.hasOwnProperty("lazyActor")); + do_check_true(DebuggerServer.globalActorFactories.hasOwnProperty("lazyActor")); + do_check_false(isActorLoaded); + do_check_false(isActorInstanciated); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + client.connect().then(function onConnect() { + client.listTabs(onListTabs); + }); + function onListTabs(aResponse) { + // On listTabs, the actor is still not loaded, + // but we can see its name in the list of available actors + do_check_false(isActorLoaded); + do_check_false(isActorInstanciated); + do_check_true("lazyActor" in aResponse); + + let {LazyFront} = require("xpcshell-test/registertestactors-03"); + let front = LazyFront(client, aResponse); + front.hello().then(onRequest); + } + function onRequest(aResponse) { + do_check_eq(aResponse, "world"); + + // Finally, the actor is loaded on the first request being made to it + do_check_true(isActorLoaded); + do_check_true(isActorInstanciated); + + Services.obs.removeObserver(onActorEvent, "actor", false); + client.close().then(() => run_next_test()); + } +} + +function cleanup() { + DebuggerServer.destroy(); + + // Check that all actors are unregistered on server destruction + check_actors(false); + do_check_false(DebuggerServer.tabActorFactories.hasOwnProperty("lazyActor")); + do_check_false(DebuggerServer.globalActorFactories.hasOwnProperty("lazyActor")); + + run_next_test(); +} + diff --git a/devtools/server/tests/unit/test_requestTypes.js b/devtools/server/tests/unit/test_requestTypes.js new file mode 100644 index 000000000..694e276bc --- /dev/null +++ b/devtools/server/tests/unit/test_requestTypes.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { RootActor } = require("devtools/server/actors/root"); + +function test_requestTypes_request(aClient, anActor) +{ + aClient.request({ to: "root", type: "requestTypes" }, function (aResponse) { + var expectedRequestTypes = Object.keys(RootActor. + prototype. + requestTypes); + + do_check_true(Array.isArray(aResponse.requestTypes)); + do_check_eq(JSON.stringify(aResponse.requestTypes), + JSON.stringify(expectedRequestTypes)); + + aClient.close().then(() => { + do_test_finished(); + }); + }); +} + +function run_test() +{ + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + var client = new DebuggerClient(DebuggerServer.connectPipe()); + client.connect().then(function () { + test_requestTypes_request(client); + }); + + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_safe-getter.js b/devtools/server/tests/unit/test_safe-getter.js new file mode 100644 index 000000000..08949154d --- /dev/null +++ b/devtools/server/tests/unit/test_safe-getter.js @@ -0,0 +1,25 @@ +function run_test() { + Components.utils.import("resource://gre/modules/jsdebugger.jsm"); + addDebuggerToGlobal(this); + var g = testGlobal("test"); + var dbg = new Debugger(); + var gw = dbg.addDebuggee(g); + + g.eval(` + // This is not a CCW. + Object.defineProperty(this, "bar", { + get: function() { return "bar"; }, + configurable: true, + enumerable: true + }); + + Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + + // This is a CCW. + XPCOMUtils.defineLazyGetter(this, "foo", function() { return "foo"; }); + `); + + // Neither scripted getter should be considered safe. + assert(!DevToolsUtils.hasSafeGetter(gw.getOwnPropertyDescriptor("bar"))); + assert(!DevToolsUtils.hasSafeGetter(gw.getOwnPropertyDescriptor("foo"))); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-column-in-gcd-script.js b/devtools/server/tests/unit/test_setBreakpoint-on-column-in-gcd-script.js new file mode 100644 index 000000000..a9d82e434 --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-column-in-gcd-script.js @@ -0,0 +1,58 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-column-in-gcd-script.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + let global = testGlobal("test"); + loadSubScript(SOURCE_URL, global); + Cu.forceGC(); Cu.forceGC(); Cu.forceGC(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + DebuggerServer.addTestGlobal(global); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let { sources } = yield getSources(threadClient); + let source = findSource(sources, SOURCE_URL); + let sourceClient = threadClient.source(source); + + let location = { line: 6, column: 17 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_true(packet.isPending); + do_check_false("actualLocation" in packet); + + packet = yield executeOnNextTickAndWaitForPause(function () { + reload(tabClient).then(function () { + loadSubScript(SOURCE_URL, global); + }); + }, client); + do_check_eq(packet.type, "paused"); + let why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + let frame = packet.frame; + let where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, location.line); + do_check_eq(where.column, location.column); + let variables = frame.environment.bindings.variables; + do_check_eq(variables.a.value, 1); + do_check_eq(variables.b.value.type, "undefined"); + do_check_eq(variables.c.value.type, "undefined"); + yield resume(threadClient); + + yield close(client); + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-column-with-no-offsets-at-end-of-line.js b/devtools/server/tests/unit/test_setBreakpoint-on-column-with-no-offsets-at-end-of-line.js new file mode 100644 index 000000000..6185cf589 --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-column-with-no-offsets-at-end-of-line.js @@ -0,0 +1,39 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-column-with-no-offsets-at-end-of-line.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + + let global = createTestGlobal("test"); + DebuggerServer.addTestGlobal(global); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let promise = waitForNewSource(threadClient, SOURCE_URL); + loadSubScript(SOURCE_URL, global); + let { source } = yield promise; + let sourceClient = threadClient.source(source); + + let location = { line: 4, column: 23 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_true(packet.isPending); + do_check_false("actualLocation" in packet); + + Cu.evalInSandbox("f()", global); + yield close(client); + + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-column.js b/devtools/server/tests/unit/test_setBreakpoint-on-column.js new file mode 100644 index 000000000..bee9fe004 --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-column.js @@ -0,0 +1,57 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-column.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + + let global = createTestGlobal("test"); + DebuggerServer.addTestGlobal(global); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let promise = waitForNewSource(threadClient, SOURCE_URL); + loadSubScript(SOURCE_URL, global); + let { source } = yield promise; + let sourceClient = threadClient.source(source); + + let location = { line: 4, column: 17 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_false(packet.isPending); + do_check_false("actualLocation" in packet); + + packet = yield executeOnNextTickAndWaitForPause(function () { + Cu.evalInSandbox("f()", global); + }, client); + do_check_eq(packet.type, "paused"); + let why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + let frame = packet.frame; + let where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, location.line); + do_check_eq(where.column, location.column); + let variables = frame.environment.bindings.variables; + do_check_eq(variables.a.value, 1); + do_check_eq(variables.b.value.type, "undefined"); + do_check_eq(variables.c.value.type, "undefined"); + + yield resume(threadClient); + yield close(client); + + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line-in-gcd-script.js b/devtools/server/tests/unit/test_setBreakpoint-on-line-in-gcd-script.js new file mode 100644 index 000000000..8479c797e --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-in-gcd-script.js @@ -0,0 +1,57 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-line-in-gcd-script.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + let global = createTestGlobal("test"); + loadSubScript(SOURCE_URL, global); + Cu.forceGC(); Cu.forceGC(); Cu.forceGC(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + DebuggerServer.addTestGlobal(global); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let { sources } = yield getSources(threadClient); + let source = findSource(sources, SOURCE_URL); + let sourceClient = threadClient.source(source); + + let location = { line: 7 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_true(packet.isPending); + do_check_false("actualLocation" in packet); + + packet = yield executeOnNextTickAndWaitForPause(function () { + reload(tabClient).then(function () { + loadSubScript(SOURCE_URL, global); + }); + }, client); + do_check_eq(packet.type, "paused"); + let why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + let frame = packet.frame; + let where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, location.line); + let variables = frame.environment.bindings.variables; + do_check_eq(variables.a.value, 1); + do_check_eq(variables.b.value.type, "undefined"); + do_check_eq(variables.c.value.type, "undefined"); + yield resume(threadClient); + + yield close(client); + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-offsets.js b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-offsets.js new file mode 100644 index 000000000..2f5c1b9aa --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-offsets.js @@ -0,0 +1,70 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-line-with-multiple-offsets.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + + let global = createTestGlobal("test"); + DebuggerServer.addTestGlobal(global); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let promise = waitForNewSource(threadClient, SOURCE_URL); + loadSubScript(SOURCE_URL, global); + let { source } = yield promise; + let sourceClient = threadClient.source(source); + + let location = { line: 4 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_false(packet.isPending); + do_check_false("actualLocation" in packet); + + packet = yield executeOnNextTickAndWaitForPause(function () { + Cu.evalInSandbox("f()", global); + }, client); + do_check_eq(packet.type, "paused"); + let why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + let frame = packet.frame; + let where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, location.line); + let variables = frame.environment.bindings.variables; + do_check_eq(variables.i.value.type, "undefined"); + + packet = yield executeOnNextTickAndWaitForPause(function () { + resume(threadClient); + }, client); + do_check_eq(packet.type, "paused"); + why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + frame = packet.frame; + where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, location.line); + variables = frame.environment.bindings.variables; + do_check_eq(variables.i.value, 0); + + yield resume(threadClient); + yield close(client); + + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-statements.js b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-statements.js new file mode 100644 index 000000000..104152441 --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-statements.js @@ -0,0 +1,57 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-line-with-multiple-statements.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + + let global = createTestGlobal("test"); + DebuggerServer.addTestGlobal(global); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let promise = waitForNewSource(threadClient, SOURCE_URL); + loadSubScript(SOURCE_URL, global); + let { source } = yield promise; + let sourceClient = threadClient.source(source); + + let location = { line: 4 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_false(packet.isPending); + do_check_false("actualLocation" in packet); + + packet = yield executeOnNextTickAndWaitForPause(function () { + Cu.evalInSandbox("f()", global); + }, client); + do_check_eq(packet.type, "paused"); + let why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + let frame = packet.frame; + let where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, location.line); + let variables = frame.environment.bindings.variables; + do_check_eq(variables.a.value.type, "undefined"); + do_check_eq(variables.b.value.type, "undefined"); + do_check_eq(variables.c.value.type, "undefined"); + + yield resume(threadClient); + yield close(client); + + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets-in-gcd-script.js b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets-in-gcd-script.js new file mode 100644 index 000000000..2e841fe19 --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets-in-gcd-script.js @@ -0,0 +1,58 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-line-with-no-offsets-in-gcd-script.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + let global = createTestGlobal("test"); + loadSubScript(SOURCE_URL, global); + Cu.forceGC(); Cu.forceGC(); Cu.forceGC(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + DebuggerServer.addTestGlobal(global); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let { sources } = yield getSources(threadClient); + let source = findSource(sources, SOURCE_URL); + let sourceClient = threadClient.source(source); + + let location = { line: 7 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_true(packet.isPending); + do_check_false("actualLocation" in packet); + + packet = yield executeOnNextTickAndWaitForPause(function () { + reload(tabClient).then(function () { + loadSubScript(SOURCE_URL, global); + }); + }, client); + do_check_eq(packet.type, "paused"); + let why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + let frame = packet.frame; + let where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, 8); + let variables = frame.environment.bindings.variables; + do_check_eq(variables.a.value, 1); + do_check_eq(variables.c.value.type, "undefined"); + + yield resume(threadClient); + yield close(client); + + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets.js b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets.js new file mode 100644 index 000000000..5959b23ef --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets.js @@ -0,0 +1,57 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-line-with-no-offsets.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + + let global = createTestGlobal("test"); + DebuggerServer.addTestGlobal(global); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let promise = waitForNewSource(threadClient, SOURCE_URL); + loadSubScript(SOURCE_URL, global); + let { source } = yield promise; + let sourceClient = threadClient.source(source); + + let location = { line: 5 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_false(packet.isPending); + do_check_true("actualLocation" in packet); + let actualLocation = packet.actualLocation; + do_check_eq(actualLocation.line, 6); + + packet = yield executeOnNextTickAndWaitForPause(function () { + Cu.evalInSandbox("f()", global); + }, client); + do_check_eq(packet.type, "paused"); + let why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + let frame = packet.frame; + let where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, actualLocation.line); + let variables = frame.environment.bindings.variables; + do_check_eq(variables.a.value, 1); + do_check_eq(variables.c.value.type, "undefined"); + + yield resume(threadClient); + yield close(client); + + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line.js b/devtools/server/tests/unit/test_setBreakpoint-on-line.js new file mode 100644 index 000000000..1dab6a633 --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-line.js @@ -0,0 +1,57 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-line.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + + let global = createTestGlobal("test"); + DebuggerServer.addTestGlobal(global); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let promise = waitForNewSource(threadClient, SOURCE_URL); + loadSubScript(SOURCE_URL, global); + let { source } = yield promise; + let sourceClient = threadClient.source(source); + + let location = { line: 5 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_false(packet.isPending); + do_check_false("actualLocation" in packet); + + packet = yield executeOnNextTickAndWaitForPause(function () { + Cu.evalInSandbox("f()", global); + }, client); + do_check_eq(packet.type, "paused"); + let why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + let frame = packet.frame; + let where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, location.line); + let variables = frame.environment.bindings.variables; + do_check_eq(variables.a.value, 1); + do_check_eq(variables.b.value.type, "undefined"); + do_check_eq(variables.c.value.type, "undefined"); + + yield resume(threadClient); + yield close(client); + + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_source-01.js b/devtools/server/tests/unit/test_source-01.js new file mode 100644 index 000000000..3401cc660 --- /dev/null +++ b/devtools/server/tests/unit/test_source-01.js @@ -0,0 +1,78 @@ +/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +// This test ensures that we can create SourceActors and SourceClients properly, +// and that they can communicate over the protocol to fetch the source text for +// a given script. + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + Cu.evalInSandbox( + "" + function stopMe(arg1) { + debugger; + }, + gDebuggee, + "1.8", + getFileUrl("test_source-01.js") + ); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_source(); + }); + }); + do_test_pending(); +} + +const SOURCE_URL = "http://example.com/foobar.js"; +const SOURCE_CONTENT = "stopMe()"; + +function test_source() +{ + DebuggerServer.LONG_STRING_LENGTH = 200; + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.getSources(function (aResponse) { + do_check_true(!!aResponse); + do_check_true(!!aResponse.sources); + + let source = aResponse.sources.filter(function (s) { + return s.url === SOURCE_URL; + })[0]; + + do_check_true(!!source); + + let sourceClient = gThreadClient.source(source); + sourceClient.source(function (aResponse) { + do_check_true(!!aResponse); + do_check_true(!aResponse.error); + do_check_true(!!aResponse.contentType); + do_check_true(aResponse.contentType.includes("javascript")); + + do_check_true(!!aResponse.source); + do_check_eq(SOURCE_CONTENT, + aResponse.source); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + }); + + Cu.evalInSandbox( + SOURCE_CONTENT, + gDebuggee, + "1.8", + SOURCE_URL + ); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-01.js b/devtools/server/tests/unit/test_sourcemaps-01.js new file mode 100644 index 000000000..4d92bf9cc --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-01.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic source map integration with the "newSource" packet in the RDP. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_source_map(); + }); + }); + do_test_pending(); +} + +function test_simple_source_map() +{ + // Because we are source mapping, we should be notified of a.js, b.js, and + // c.js as sources, and shouldn't receive abc.js or test_sourcemaps-01.js. + let expectedSources = new Set(["http://example.com/www/js/a.js", + "http://example.com/www/js/b.js", + "http://example.com/www/js/c.js"]); + + gThreadClient.addListener("newSource", function _onNewSource(aEvent, aPacket) { + do_check_eq(aEvent, "newSource"); + do_check_eq(aPacket.type, "newSource"); + do_check_true(!!aPacket.source); + + do_check_true(expectedSources.has(aPacket.source.url), + "The source url should be one of our original sources."); + expectedSources.delete(aPacket.source.url); + + if (expectedSources.size === 0) { + gClient.removeListener("newSource", _onNewSource); + finishClient(gClient); + } + }); + + let { code, map } = (new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"), + new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"), + new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"), + ])).toStringWithSourceMap({ + file: "abc.js", + sourceRoot: "http://example.com/www/js/" + }); + + code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString()); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/www/js/abc.js", 1); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-02.js b/devtools/server/tests/unit/test_sourcemaps-02.js new file mode 100644 index 000000000..2a343afaa --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-02.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic source map integration with the "sources" packet in the RDP. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_source_map(); + }); + }); + do_test_pending(); +} + +function test_simple_source_map() +{ + // Because we are source mapping, we should be notified of a.js, b.js, and + // c.js as sources, and shouldn"t receive abc.js or test_sourcemaps-01.js. + let expectedSources = new Set(["http://example.com/www/js/a.js", + "http://example.com/www/js/b.js", + "http://example.com/www/js/c.js"]); + + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.getSources(function (aResponse) { + do_check_true(!aResponse.error, "Should not get an error"); + + for (let s of aResponse.sources) { + do_check_neq(s.url, "http://example.com/www/js/abc.js", + "Shouldn't get the generated source's url."); + expectedSources.delete(s.url); + } + + do_check_eq(expectedSources.size, 0, + "Should have found all the expected sources sources by now."); + + finishClient(gClient); + }); + }); + + let { code, map } = (new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"), + new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"), + new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"), + new SourceNode(1, 0, "d.js", "debugger;\n") + ])).toStringWithSourceMap({ + file: "abc.js", + sourceRoot: "http://example.com/www/js/" + }); + + code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString()); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/www/js/abc.js", 1); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-03.js b/devtools/server/tests/unit/test_sourcemaps-03.js new file mode 100644 index 000000000..2fbd99aad --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-03.js @@ -0,0 +1,137 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check setting breakpoints in source mapped sources. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_source_map(); + }); + }); + do_test_pending(); +} + +function testBreakpointMapping(aName, aCallback) +{ + Task.spawn(function* () { + let response = yield waitForPause(gThreadClient); + do_check_eq(response.why.type, "debuggerStatement"); + + const source = yield getSource(gThreadClient, "http://example.com/www/js/" + aName + ".js"); + response = yield setBreakpoint(source, { + // Setting the breakpoint on an empty line so that it is pushed down one + // line and we can check the source mapped actualLocation later. + line: 3 + }); + + // Should not slide breakpoints for sourcemapped sources + do_check_true(!response.actualLocation); + + yield setBreakpoint(source, { line: 4 }); + + // The eval will cause us to resume, then we get an unsolicited pause + // because of our breakpoint, we resume again to finish the eval, and + // finally receive our last pause which has the result of the client + // evaluation. + response = yield gThreadClient.eval(null, aName + "()"); + do_check_eq(response.type, "resumed"); + + response = yield waitForPause(gThreadClient); + do_check_eq(response.why.type, "breakpoint"); + // Assert that we paused because of the breakpoint at the correct + // location in the code by testing that the value of `ret` is still + // undefined. + do_check_eq(response.frame.environment.bindings.variables.ret.value.type, + "undefined"); + + response = yield resume(gThreadClient); + + response = yield waitForPause(gThreadClient); + do_check_eq(response.why.type, "clientEvaluated"); + do_check_eq(response.why.frameFinished.return, aName); + + response = yield resume(gThreadClient); + + aCallback(); + }); + + gDebuggee.eval("(" + function () { + debugger; + } + "());"); +} + +function test_simple_source_map() +{ + let expectedSources = new Set([ + "http://example.com/www/js/a.js", + "http://example.com/www/js/b.js", + "http://example.com/www/js/c.js" + ]); + + gThreadClient.addListener("newSource", function _onNewSource(aEvent, aPacket) { + expectedSources.delete(aPacket.source.url); + if (expectedSources.size > 0) { + return; + } + gThreadClient.removeListener("newSource", _onNewSource); + + testBreakpointMapping("a", function () { + testBreakpointMapping("b", function () { + testBreakpointMapping("c", function () { + finishClient(gClient); + }); + }); + }); + }); + + let a = new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "function a() {\n"), + new SourceNode(2, 0, "a.js", " var ret;\n"), + new SourceNode(3, 0, "a.js", " // Empty line\n"), + new SourceNode(4, 0, "a.js", " ret = 'a';\n"), + new SourceNode(5, 0, "a.js", " return ret;\n"), + new SourceNode(6, 0, "a.js", "}\n") + ]); + let b = new SourceNode(null, null, null, [ + new SourceNode(1, 0, "b.js", "function b() {\n"), + new SourceNode(2, 0, "b.js", " var ret;\n"), + new SourceNode(3, 0, "b.js", " // Empty line\n"), + new SourceNode(4, 0, "b.js", " ret = 'b';\n"), + new SourceNode(5, 0, "b.js", " return ret;\n"), + new SourceNode(6, 0, "b.js", "}\n") + ]); + let c = new SourceNode(null, null, null, [ + new SourceNode(1, 0, "c.js", "function c() {\n"), + new SourceNode(2, 0, "c.js", " var ret;\n"), + new SourceNode(3, 0, "c.js", " // Empty line\n"), + new SourceNode(4, 0, "c.js", " ret = 'c';\n"), + new SourceNode(5, 0, "c.js", " return ret;\n"), + new SourceNode(6, 0, "c.js", "}\n") + ]); + + let { code, map } = (new SourceNode(null, null, null, [ + a, b, c + ])).toStringWithSourceMap({ + file: "http://example.com/www/js/abc.js", + sourceRoot: "http://example.com/www/js/" + }); + + code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString()); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/www/js/abc.js", 1); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-04.js b/devtools/server/tests/unit/test_sourcemaps-04.js new file mode 100644 index 000000000..5fecb44c8 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-04.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that absolute source map urls work. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_absolute_source_map(); + }); + }); + do_test_pending(); +} + +function test_absolute_source_map() +{ + gThreadClient.addOneTimeListener("newSource", function _onNewSource(aEvent, aPacket) { + do_check_eq(aEvent, "newSource"); + do_check_eq(aPacket.type, "newSource"); + do_check_true(!!aPacket.source); + + do_check_true(aPacket.source.url.indexOf("sourcemapped.coffee") !== -1, + "The new source should be a coffee file."); + do_check_eq(aPacket.source.url.indexOf("sourcemapped.js"), -1, + "The new source should not be a js file."); + + finishClient(gClient); + }); + + code = readFile("sourcemapped.js") + + "\n//# sourceMappingURL=" + getFileUrl("source-map-data/sourcemapped.map"); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + getFileUrl("sourcemapped.js"), 1); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-05.js b/devtools/server/tests/unit/test_sourcemaps-05.js new file mode 100644 index 000000000..edc9178db --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-05.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that relative source map urls work. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_relative_source_map(); + }); + }); + do_test_pending(); +} + +function test_relative_source_map() +{ + gThreadClient.addOneTimeListener("newSource", function _onNewSource(aEvent, aPacket) { + do_check_eq(aEvent, "newSource"); + do_check_eq(aPacket.type, "newSource"); + do_check_true(!!aPacket.source); + + do_check_true(aPacket.source.url.indexOf("sourcemapped.coffee") !== -1, + "The new source should be a coffee file."); + do_check_eq(aPacket.source.url.indexOf("sourcemapped.js"), -1, + "The new source should not be a js file."); + + finishClient(gClient); + }); + + code = readFile("sourcemapped.js") + + "\n//# sourceMappingURL=source-map-data/sourcemapped.map"; + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + getFileUrl("sourcemapped.js"), 1); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-06.js b/devtools/server/tests/unit/test_sourcemaps-06.js new file mode 100644 index 000000000..41b518753 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-06.js @@ -0,0 +1,94 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that we can load sources whose content is embedded in the + * "sourcesContent" field of a source map. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_source_content(); + }); + }); + do_test_pending(); +} + +function test_source_content() +{ + let numNewSources = 0; + + gThreadClient.addListener("newSource", function _onNewSource(aEvent, aPacket) { + if (++numNewSources !== 3) { + return; + } + gThreadClient.removeListener("newSource", _onNewSource); + + gThreadClient.getSources(function (aResponse) { + do_check_true(!aResponse.error, "Should not get an error"); + + testContents(aResponse.sources, 0, (timesCalled) => { + do_check_eq(timesCalled, 3); + finishClient(gClient); + }); + }); + }); + + let node = new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"), + new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"), + new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"), + ]); + + node.setSourceContent("a.js", "content for a.js"); + node.setSourceContent("b.js", "content for b.js"); + node.setSourceContent("c.js", "content for c.js"); + + let { code, map } = node.toStringWithSourceMap({ + file: "abc.js" + }); + + code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString()); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/www/js/abc.js", 1); +} + +function testContents(sources, timesCalled, callback) { + if (sources.length === 0) { + callback(timesCalled); + return; + } + + + let source = sources[0]; + let sourceClient = gThreadClient.source(sources[0]); + + if (sourceClient.url) { + sourceClient.source((aResponse) => { + do_check_true(!aResponse.error, + "Should not get an error loading the source from sourcesContent"); + + let expectedContent = "content for " + source.url; + do_check_eq(aResponse.source, expectedContent, + "Should have the expected source content"); + + testContents(sources.slice(1), timesCalled + 1, callback); + }); + } + else { + testContents(sources.slice(1), timesCalled, callback); + } +} diff --git a/devtools/server/tests/unit/test_sourcemaps-07.js b/devtools/server/tests/unit/test_sourcemaps-07.js new file mode 100644 index 000000000..b8a9462c0 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-07.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we don't permanently cache sources from source maps. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_cached_original_sources(); + }); + }); + do_test_pending(); +} + +function test_cached_original_sources() +{ + writeFile("temp.js", "initial content"); + + gThreadClient.addOneTimeListener("newSource", onNewSource); + + let node = new SourceNode(1, 0, + getFileUrl("temp.js"), + "function funcFromTemp() {}\n"); + let { code, map } = node.toStringWithSourceMap({ + file: "abc.js" + }); + code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString()); + + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/www/js/abc.js", 1); +} + +function onNewSource(aEvent, aPacket) { + let sourceClient = gThreadClient.source(aPacket.source); + sourceClient.source(function (aResponse) { + do_check_true(!aResponse.error, + "Should not be an error grabbing the source"); + do_check_eq(aResponse.source, "initial content", + "The correct source content should be sent"); + + writeFile("temp.js", "new content"); + + sourceClient.source(function (aResponse) { + do_check_true(!aResponse.error, + "Should not be an error grabbing the source"); + do_check_eq(aResponse.source, "new content", + "The correct source content should not be cached, so we should get the new content"); + + do_get_file("temp.js").remove(false); + finishClient(gClient); + }); + }); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-08.js b/devtools/server/tests/unit/test_sourcemaps-08.js new file mode 100644 index 000000000..b23665e43 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-08.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Regression test for bug 882986 regarding sourcesContent and absolute source + * URLs. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_source_maps(); + }); + }); + do_test_pending(); +} + +function test_source_maps() +{ + gThreadClient.addOneTimeListener("newSource", function (aEvent, aPacket) { + let sourceClient = gThreadClient.source(aPacket.source); + sourceClient.source(function ({error, source}) { + do_check_true(!error, "should be able to grab the source"); + do_check_eq(source, "foo", + "Should load the source from the sourcesContent field"); + finishClient(gClient); + }); + }); + + let code = "'nothing here';\n"; + code += "//# sourceMappingURL=data:text/json," + JSON.stringify({ + version: 3, + file: "foo.js", + sources: ["/a"], + names: [], + mappings: "AACA", + sourcesContent: ["foo"] + }); + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/foo.js", 1); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-09.js b/devtools/server/tests/unit/test_sourcemaps-09.js new file mode 100644 index 000000000..c317cf723 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-09.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that source maps and breakpoints work with minified javascript. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_minified(); + }); + }); + do_test_pending(); +} + +function test_minified() +{ + let newSourceFired = false; + + gThreadClient.addOneTimeListener("newSource", function _onNewSource(aEvent, aPacket) { + do_check_eq(aEvent, "newSource"); + do_check_eq(aPacket.type, "newSource"); + do_check_true(!!aPacket.source); + + do_check_eq(aPacket.source.url, "http://example.com/foo.js", + "The new source should be foo.js"); + do_check_eq(aPacket.source.url.indexOf("foo.min.js"), -1, + "The new source should not be the minified file"); + + newSourceFired = true; + }); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aEvent, "paused"); + do_check_eq(aPacket.why.type, "debuggerStatement"); + + let location = { + line: 5 + }; + + getSource(gThreadClient, "http://example.com/foo.js").then(source => { + source.setBreakpoint(location, function (aResponse, bpClient) { + do_check_true(!aResponse.error); + testHitBreakpoint(); + }); + }); + }); + + // This is the original foo.js, which was then minified with uglifyjs version + // 2.2.5 and the "--mangle" option. + // + // (function () { + // debugger; + // function foo(n) { + // var bar = n + n; + // var unused = null; + // return bar; + // } + // for (var i = 0; i < 10; i++) { + // foo(i); + // } + // }()); + + let code = '(function(){debugger;function r(r){var n=r+r;var u=null;return n}for(var n=0;n<10;n++){r(n)}})();\n//# sourceMappingURL=data:text/json,{"file":"foo.min.js","version":3,"sources":["foo.js"],"names":["foo","n","bar","unused","i"],"mappings":"CAAC,WACC,QACA,SAASA,GAAIC,GACX,GAAIC,GAAMD,EAAIA,CACd,IAAIE,GAAS,IACb,OAAOD,GAET,IAAK,GAAIE,GAAI,EAAGA,EAAI,GAAIA,IAAK,CAC3BJ,EAAII"}'; + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/foo.min.js", 1); +} + +function testHitBreakpoint(timesHit = 0) { + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + ++timesHit; + + do_check_eq(aEvent, "paused"); + do_check_eq(aPacket.why.type, "breakpoint"); + + if (timesHit === 10) { + gThreadClient.resume(() => finishClient(gClient)); + } else { + testHitBreakpoint(timesHit); + } + }); + + gThreadClient.resume(); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-10.js b/devtools/server/tests/unit/test_sourcemaps-10.js new file mode 100644 index 000000000..e955dc8fb --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-10.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that we source map frame locations for the frame we are paused at. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() { + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + promise.resolve(define_code()) + .then(run_code) + .then(test_frame_location) + .then(null, error => { + dump(error + "\n"); + dump(error.stack); + do_check_true(false); + }) + .then(() => { + finishClient(gClient); + }); + }); + }); + do_test_pending(); +} + +function define_code() { + let { code, map } = (new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "function a() {\n"), + new SourceNode(2, 0, "a.js", " b();\n"), + new SourceNode(3, 0, "a.js", "}\n"), + new SourceNode(1, 0, "b.js", "function b() {\n"), + new SourceNode(2, 0, "b.js", " c();\n"), + new SourceNode(3, 0, "b.js", "}\n"), + new SourceNode(1, 0, "c.js", "function c() {\n"), + new SourceNode(2, 0, "c.js", " debugger;\n"), + new SourceNode(3, 0, "c.js", "}\n"), + ])).toStringWithSourceMap({ + file: "abc.js", + sourceRoot: "http://example.com/www/js/" + }); + + code += "//# sourceMappingURL=data:text/json," + map.toString(); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/www/js/abc.js", 1); +} + +function run_code() { + const d = promise.defer(); + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + d.resolve(aPacket); + gThreadClient.resume(); + }); + gDebuggee.a(); + return d.promise; +} + +function test_frame_location({ frame: { where: { source, line, column } } }) { + do_check_eq(source.url, "http://example.com/www/js/c.js"); + do_check_eq(line, 2); + do_check_eq(column, 0); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-11.js b/devtools/server/tests/unit/test_sourcemaps-11.js new file mode 100644 index 000000000..e598c4c09 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-11.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that we source map frame locations returned by "frames" requests. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() { + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + promise.resolve(define_code()) + .then(run_code) + .then(test_frames) + .then(null, error => { + dump(error + "\n"); + dump(error.stack); + do_check_true(false); + }) + .then(() => { + finishClient(gClient); + }); + }); + }); + do_test_pending(); +} + +function define_code() { + let { code, map } = (new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "function a() {\n"), + new SourceNode(2, 0, "a.js", " b();\n"), + new SourceNode(3, 0, "a.js", "}\n"), + new SourceNode(1, 0, "b.js", "function b() {\n"), + new SourceNode(2, 0, "b.js", " c();\n"), + new SourceNode(3, 0, "b.js", "}\n"), + new SourceNode(1, 0, "c.js", "function c() {\n"), + new SourceNode(2, 0, "c.js", " debugger;\n"), + new SourceNode(3, 0, "c.js", "}\n"), + ])).toStringWithSourceMap({ + file: "abc.js", + sourceRoot: "http://example.com/www/js/" + }); + + code += "//# sourceMappingURL=data:text/json," + map.toString(); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/www/js/abc.js", 1); +} + +function run_code() { + const d = promise.defer(); + gClient.addOneTimeListener("paused", function () { + gThreadClient.getFrames(0, 3, function (aResponse) { + d.resolve(aResponse); + gThreadClient.resume(); + }); + }); + gDebuggee.a(); + return d.promise; +} + +function test_frames({ error, frames }) { + do_check_true(!error); + do_check_eq(frames.length, 3); + check_frame(frames[0], "http://example.com/www/js/c.js"); + check_frame(frames[1], "http://example.com/www/js/b.js"); + check_frame(frames[2], "http://example.com/www/js/a.js"); +} + +function check_frame({ where: { source, line, column } }, aExpectedUrl) { + do_check_eq(source.url, aExpectedUrl); + do_check_eq(line, 2); + do_check_eq(column, 0); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-12.js b/devtools/server/tests/unit/test_sourcemaps-12.js new file mode 100644 index 000000000..cb7f29920 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-12.js @@ -0,0 +1,75 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that we continue stepping when a single original source's line + * corresponds to multiple generated js lines. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() { + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + define_code(); + }); + }); + do_test_pending(); +} + +function define_code() { + let { code, map } = (new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "function runTest() {\n"), + // A bunch of js lines map to the same original source line. + new SourceNode(2, 0, "a.js", " debugger;\n"), + new SourceNode(2, 0, "a.js", " var sum = 0;\n"), + new SourceNode(2, 0, "a.js", " for (var i = 0; i < 5; i++) {\n"), + new SourceNode(2, 0, "a.js", " sum += i;\n"), + new SourceNode(2, 0, "a.js", " }\n"), + // And now we have a new line in the original source that we should stop at. + new SourceNode(3, 0, "a.js", " sum;\n"), + new SourceNode(3, 0, "a.js", "}\n"), + ])).toStringWithSourceMap({ + file: "abc.js", + sourceRoot: "http://example.com/" + }); + + code += "//# sourceMappingURL=data:text/json," + map.toString(); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/abc.js", 1); + + run_code(); +} + +function run_code() { + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "debuggerStatement"); + step_in(); + }); + gDebuggee.runTest(); +} + +function step_in() { + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "resumeLimit"); + let { frame: { environment, where: { source, line } } } = aPacket; + // Stepping should have moved us to the next source mapped line. + do_check_eq(source.url, "http://example.com/a.js"); + do_check_eq(line, 3); + // Which should have skipped over the for loop in the generated js and sum + // should be calculated. + do_check_eq(environment.bindings.variables.sum.value, 10); + finishClient(gClient); + }); + gThreadClient.stepIn(); +} + diff --git a/devtools/server/tests/unit/test_sourcemaps-13.js b/devtools/server/tests/unit/test_sourcemaps-13.js new file mode 100644 index 000000000..203731fda --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-13.js @@ -0,0 +1,105 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we don't permanently cache source maps across reloads. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gTabClient; + +const {SourceNode} = require("source-map"); + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + gTabClient = aTabClient; + setup_code(); + }); + }); + do_test_pending(); +} + +// The MAP_FILE_NAME is .txt so that the OS will definitely have an extension -> +// content type mapping for the extension. If it doesn't (like .map or .json), +// it logs console errors, which cause the test to fail. See bug 907839. +const MAP_FILE_NAME = "temporary-generated.txt"; + +const TEMP_FILE_1 = "temporary1.js"; +const TEMP_FILE_2 = "temporary2.js"; +const TEMP_GENERATED_SOURCE = "temporary-generated.js"; + +function setup_code() { + let node = new SourceNode(1, 0, + getFileUrl(TEMP_FILE_1, true), + "function temporary1() {}\n"); + let { code, map } = node.toStringWithSourceMap({ + file: getFileUrl(TEMP_GENERATED_SOURCE, true) + }); + + code += "//# sourceMappingURL=" + getFileUrl(MAP_FILE_NAME, true); + writeFile(MAP_FILE_NAME, map.toString()); + + Cu.evalInSandbox(code, + gDebuggee, + "1.8", + getFileUrl(TEMP_GENERATED_SOURCE, true), + 1); + + test_initial_sources(); +} + +function test_initial_sources() { + gThreadClient.getSources(function ({ error, sources }) { + do_check_true(!error); + sources = sources.filter(source => source.url); + do_check_eq(sources.length, 1); + do_check_eq(sources[0].url, getFileUrl(TEMP_FILE_1, true)); + reload(gTabClient).then(setup_new_code); + }); +} + +function setup_new_code() { + let node = new SourceNode(1, 0, + getFileUrl(TEMP_FILE_2, true), + "function temporary2() {}\n"); + let { code, map } = node.toStringWithSourceMap({ + file: getFileUrl(TEMP_GENERATED_SOURCE, true) + }); + + code += "\n//# sourceMappingURL=" + getFileUrl(MAP_FILE_NAME, true); + writeFile(MAP_FILE_NAME, map.toString()); + + gThreadClient.addOneTimeListener("newSource", test_new_sources); + Cu.evalInSandbox(code, + gDebuggee, + "1.8", + getFileUrl(TEMP_GENERATED_SOURCE, true), + 1); +} + +function test_new_sources() { + gThreadClient.getSources(function ({ error, sources }) { + do_check_true(!error); + sources = sources.filter(source => source.url); + + // Should now have TEMP_FILE_2 as a source. + do_check_eq(sources.length, 1); + let s = sources.filter(s => s.url === getFileUrl(TEMP_FILE_2, true))[0]; + do_check_true(!!s); + + finish_test(); + }); +} + +function finish_test() { + do_get_file(MAP_FILE_NAME).remove(false); + finishClient(gClient); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-16.js b/devtools/server/tests/unit/test_sourcemaps-16.js new file mode 100644 index 000000000..4df9ece23 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-16.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Verify that we can load the contents of every source in a source map produced + * by babel and browserify. + */ + +var gDebuggee; +var gClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-sourcemaps"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestThread(gClient, "test-sourcemaps", testSourcemap); + }); + do_test_pending(); +} + +const testSourcemap = Task.async(function* (threadResponse, tabClient, threadClient, tabResponse) { + evalTestCode(); + + const { sources } = yield getSources(threadClient); + + for (let form of sources) { + let sourceResponse = yield getSourceContent(threadClient.source(form)); + ok(sourceResponse, "Should be able to get the source response"); + ok(sourceResponse.source, "Should have the source text as well"); + } + + finishClient(gClient); +}); + +const TEST_FILE = "babel_and_browserify_script_with_source_map.js"; + +function evalTestCode() { + const testFileContents = readFile(TEST_FILE); + Cu.evalInSandbox(testFileContents, + gDebuggee, + "1.8", + getFileUrl(TEST_FILE), + 1); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-17.js b/devtools/server/tests/unit/test_sourcemaps-17.js new file mode 100644 index 000000000..85da95972 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-17.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that we properly handle frames that cannot be sourcemapped + * when using sourcemaps. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() { + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_source_map(); + }); + }); + do_test_pending(); +} + +function test_source_map() { + // Set up debuggee code. + const a = new SourceNode(1, 1, "a.js", "function a() { b(); }"); + const b = new SourceNode(null, null, null, "function b() { c(); }"); + const c = new SourceNode(1, 1, "c.js", "function c() { d(); }"); + const d = new SourceNode(null, null, null, "function d() { e(); }"); + const e = new SourceNode(1, 1, "e.js", "function e() { debugger; }"); + const { map, code } = (new SourceNode(null, null, null, [a, b, c, d, e])).toStringWithSourceMap({ + file: "root.js", + sourceRoot: "root", + }); + Components.utils.evalInSandbox( + code + "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString()), + gDebuggee, + "1.8", + "http://example.com/www/js/abc.js", + 1 + ); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.getFrames(0, 50, function ({ error, frames }) { + do_check_true(!error); + do_check_eq(frames.length, 4); + // b.js should be skipped + do_check_eq(frames[0].where.source.url, "http://example.com/www/root/e.js"); + do_check_eq(frames[1].where.source.url, "http://example.com/www/root/c.js"); + do_check_eq(frames[2].where.source.url, "http://example.com/www/root/a.js"); + do_check_eq(frames[3].where.source.url, null); + + finishClient(gClient); + }); + }); + + // Trigger it. + gDebuggee.eval("a()"); +} diff --git a/devtools/server/tests/unit/test_stepping-01.js b/devtools/server/tests/unit/test_stepping-01.js new file mode 100644 index 000000000..593a485a1 --- /dev/null +++ b/devtools/server/tests/unit/test_stepping-01.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic step-over functionality. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_stepping(); + }); + }); +} + +function test_simple_stepping() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 2); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, undefined); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + // When leaving a stack frame the line number doesn't change. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, 2); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + gThreadClient.stepOver(); + }); + gThreadClient.stepOver(); + + }); + gThreadClient.stepOver(); + + }); + + gDebuggee.eval("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "var a = 1;\n" + // line0 + 2 + "var b = 2;\n"); // line0 + 3 +} diff --git a/devtools/server/tests/unit/test_stepping-02.js b/devtools/server/tests/unit/test_stepping-02.js new file mode 100644 index 000000000..eaee099b9 --- /dev/null +++ b/devtools/server/tests/unit/test_stepping-02.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic step-in functionality. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_stepping(); + }); + }); +} + +function test_simple_stepping() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 2); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, undefined); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + // When leaving a stack frame the line number doesn't change. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, 2); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + gThreadClient.stepIn(); + }); + gThreadClient.stepIn(); + + }); + gThreadClient.stepIn(); + + }); + + gDebuggee.eval("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "var a = 1;\n" + // line0 + 2 + "var b = 2;\n"); // line0 + 3 +} diff --git a/devtools/server/tests/unit/test_stepping-03.js b/devtools/server/tests/unit/test_stepping-03.js new file mode 100644 index 000000000..6123928d7 --- /dev/null +++ b/devtools/server/tests/unit/test_stepping-03.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic step-out functionality. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_stepping(); + }); + }); +} + +function test_simple_stepping() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, 2); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + gThreadClient.stepOut(); + + }); + + gDebuggee.eval("var line0 = Error().lineNumber;\n" + + "function f() {\n" + // line0 + 1 + " debugger;\n" + // line0 + 2 + " this.a = 1;\n" + // line0 + 3 + " this.b = 2;\n" + // line0 + 4 + "}\n" + // line0 + 5 + "f();\n"); // line0 + 6 +} diff --git a/devtools/server/tests/unit/test_stepping-04.js b/devtools/server/tests/unit/test_stepping-04.js new file mode 100644 index 000000000..efe52200d --- /dev/null +++ b/devtools/server/tests/unit/test_stepping-04.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that stepping over a function call does not pause inside the function. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_stepping(); + }); + }); +} + +function test_simple_stepping() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, undefined); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 6); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + gThreadClient.stepOver(); + + }); + gThreadClient.stepOver(); + + }); + + gDebuggee.eval("var line0 = Error().lineNumber;\n" + + "function f() {\n" + // line0 + 1 + " this.a = 1;\n" + // line0 + 2 + "}\n" + // line0 + 3 + "debugger;\n" + // line0 + 4 + "f();\n" + // line0 + 5 + "let b = 2;\n"); // line0 + 6 +} diff --git a/devtools/server/tests/unit/test_stepping-05.js b/devtools/server/tests/unit/test_stepping-05.js new file mode 100644 index 000000000..2ab4570af --- /dev/null +++ b/devtools/server/tests/unit/test_stepping-05.js @@ -0,0 +1,101 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that stepping in the last statement of the last frame doesn't + * cause an unexpected pause, when another JS frame is pushed on the stack + * (bug 785689). + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_stepping_last(); + }); + }); +} + +function test_stepping_last() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 2); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, undefined); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + // When leaving a stack frame the line number doesn't change. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, 2); + + gThreadClient.stepIn(function () { + test_next_pause(); + }); + }); + gThreadClient.stepIn(); + }); + gThreadClient.stepIn(); + + }); + gThreadClient.stepIn(); + + }); + + gDebuggee.eval("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "var a = 1;\n" + // line0 + 2 + "var b = 2;\n"); // line0 + 3 +} + +function test_next_pause() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + // Before fixing bug 785689, the type was resumeLimit. + do_check_eq(aPacket.why.type, "debuggerStatement"); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + + gDebuggee.eval("debugger;"); +} diff --git a/devtools/server/tests/unit/test_stepping-06.js b/devtools/server/tests/unit/test_stepping-06.js new file mode 100644 index 000000000..49689f830 --- /dev/null +++ b/devtools/server/tests/unit/test_stepping-06.js @@ -0,0 +1,99 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that stepping out of a function returns the right return value. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + // XXX: We have to do an executeSoon so that the error isn't caught and + // reported by DebuggerClient.requester (because we are using the local + // transport and share a stack) which causes the test to fail. + Services.tm.mainThread.dispatch({ + run: test_simple_stepping + }, Ci.nsIThread.DISPATCH_NORMAL); + }); + }); +} + +function test_simple_stepping() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check that the return value is 10. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5); + do_check_eq(aPacket.why.type, "resumeLimit"); + do_check_eq(aPacket.why.frameFinished.return, 10); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check that the return value is undefined. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 8); + do_check_eq(aPacket.why.type, "resumeLimit"); + do_check_eq(aPacket.why.frameFinished.return.type, "undefined"); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check that the exception was thrown. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 11); + do_check_eq(aPacket.why.type, "resumeLimit"); + do_check_eq(aPacket.why.frameFinished.throw, "ah"); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + gThreadClient.stepOut(); + }); + gThreadClient.resume(); + }); + gThreadClient.stepOut(); + }); + gThreadClient.resume(); + }); + gThreadClient.stepOut(); + + }); + + gDebuggee.eval("var line0 = Error().lineNumber;\n" + + "function f() {\n" + // line0 + 1 + " debugger;\n" + // line0 + 2 + " var a = 10;\n" + // line0 + 3 + " return a;\n" + // line0 + 4 + "}\n" + // line0 + 5 + "function g() {\n" + // line0 + 6 + " debugger;\n" + // line0 + 7 + "}\n" + // line0 + 8 + "function h() {\n" + // line0 + 9 + " debugger;\n" + // line0 + 10 + " throw 'ah';\n" + // line0 + 11 + " return 2;\n" + // line0 + 12 + "}\n" + // line0 + 13 + "f();\n" + // line0 + 14 + "g();\n" + // line0 + 15 + "try { h() } catch (ex) { };\n"); // line0 + 16 +} diff --git a/devtools/server/tests/unit/test_stepping-07.js b/devtools/server/tests/unit/test_stepping-07.js new file mode 100644 index 000000000..9ad0d79ff --- /dev/null +++ b/devtools/server/tests/unit/test_stepping-07.js @@ -0,0 +1,92 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that stepping over an implicit return makes sense. Bug 1155966. + */ + +var gDebuggee; +var gClient; +var gCallback; + +function run_test() { + do_test_pending(); + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); +} + +function run_test_with_server(aServer, aCallback) { + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stepping", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect(testSteppingAndReturns); +} + +const testSteppingAndReturns = Task.async(function* () { + const [attachResponse, tabClient, threadClient] = yield attachTestTabAndResume(gClient, "test-stepping"); + ok(!attachResponse.error, "Should not get an error attaching"); + + dumpn("Evaluating test code and waiting for first debugger statement"); + const dbgStmt1 = yield executeOnNextTickAndWaitForPause(evaluateTestCode, gClient); + equal(dbgStmt1.frame.where.line, 3, + "Should be at debugger statement on line 3"); + + dumpn("Testing stepping with implicit return"); + const step1 = yield stepOver(gClient, threadClient); + equal(step1.frame.where.line, 4, "Should step to line 4"); + const step2 = yield stepOver(gClient, threadClient); + equal(step2.frame.where.line, 7, + "Should step to line 7, the implicit return at the last line of the function"); + // This assertion doesn't pass yet. You would need to do *another* + // step at the end of this function to get the frameFinished. + // See bug 923975. + // + // ok(step2.why.frameFinished, "This should be the implicit function return"); + + dumpn("Continuing and waiting for second debugger statement"); + const dbgStmt2 = yield resumeAndWaitForPause(gClient, threadClient); + equal(dbgStmt2.frame.where.line, 12, + "Should be at debugger statement on line 3"); + + dumpn("Testing stepping with explicit return"); + const step3 = yield stepOver(gClient, threadClient); + equal(step3.frame.where.line, 13, "Should step to line 13"); + const step4 = yield stepOver(gClient, threadClient); + equal(step4.frame.where.line, 15, "Should step out of the function from line 15"); + // This step is a bit funny, see bug 1013219 for details. + const step5 = yield stepOver(gClient, threadClient); + equal(step5.frame.where.line, 15, "Should step out of the function from line 15"); + ok(step5.why.frameFinished, "This should be the explicit function return"); + + finishClient(gClient, gCallback); +}); + +function evaluateTestCode() { + Cu.evalInSandbox( + ` // 1 + function implicitReturn() { // 2 + debugger; // 3 + if (this.someUndefinedProperty) { // 4 + yikes(); // 5 + } // 6 + } // 7 + // 8 + var yes = true; // 9 + function explicitReturn() { // 10 + if (yes) { // 11 + debugger; // 12 + return 1; // 13 + } // 14 + } // 15 + // 16 + implicitReturn(); // 17 + explicitReturn(); // 18 + `, // 19 + gDebuggee, + "1.8", + "test_stepping-07-test-code.js", + 1 + ); +} diff --git a/devtools/server/tests/unit/test_symbols-01.js b/devtools/server/tests/unit/test_symbols-01.js new file mode 100644 index 000000000..8ea26086a --- /dev/null +++ b/devtools/server/tests/unit/test_symbols-01.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can represent ES6 Symbols over the RDP. + */ + +const URL = "foo.js"; + +function run_test() { + initTestDebuggerServer(); + const debuggee = addTestGlobal("test-symbols"); + const client = new DebuggerClient(DebuggerServer.connectPipe()); + + client.connect().then(function () { + attachTestTabAndResume(client, "test-symbols", function (response, tabClient, threadClient) { + add_task(testSymbols.bind(null, client, debuggee)); + run_next_test(); + }); + }); + + do_test_pending(); +} + +function* testSymbols(client, debuggee) { + const evalCode = () => { + Components.utils.evalInSandbox( + "(" + function () { + var symbolWithName = Symbol("Chris"); + var symbolWithoutName = Symbol(); + var iteratorSymbol = Symbol.iterator; + debugger; + } + "())", + debuggee, + "1.8", + URL, + 1 + ); + }; + + const packet = yield executeOnNextTickAndWaitForPause(evalCode, client); + const { + symbolWithName, + symbolWithoutName, + iteratorSymbol + } = packet.frame.environment.bindings.variables; + + equal(symbolWithName.value.type, "symbol"); + equal(symbolWithName.value.name, "Chris"); + + equal(symbolWithoutName.value.type, "symbol"); + ok(!("name" in symbolWithoutName.value)); + + equal(iteratorSymbol.value.type, "symbol"); + equal(iteratorSymbol.value.name, "Symbol.iterator"); + + finishClient(client); +} diff --git a/devtools/server/tests/unit/test_symbols-02.js b/devtools/server/tests/unit/test_symbols-02.js new file mode 100644 index 000000000..f80dc3322 --- /dev/null +++ b/devtools/server/tests/unit/test_symbols-02.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we don't run debuggee code when getting symbol names. + */ + +const URL = "foo.js"; + +function run_test() { + initTestDebuggerServer(); + const debuggee = addTestGlobal("test-symbols"); + const client = new DebuggerClient(DebuggerServer.connectPipe()); + + client.connect().then(function () { + attachTestTabAndResume(client, "test-symbols", function (response, tabClient, threadClient) { + add_task(testSymbols.bind(null, client, debuggee)); + run_next_test(); + }); + }); + + do_test_pending(); +} + +function* testSymbols(client, debuggee) { + const evalCode = () => { + Components.utils.evalInSandbox( + "(" + function () { + Symbol.prototype.toString = () => { + throw new Error("lololol"); + }; + var sym = Symbol("le troll"); + debugger; + } + "())", + debuggee, + "1.8", + URL, + 1 + ); + }; + + const packet = yield executeOnNextTickAndWaitForPause(evalCode, client); + const { sym } = packet.frame.environment.bindings.variables; + + equal(sym.value.type, "symbol"); + equal(sym.value.name, "le troll"); + + finishClient(client); +} diff --git a/devtools/server/tests/unit/test_threadlifetime-01.js b/devtools/server/tests/unit/test_threadlifetime-01.js new file mode 100644 index 000000000..1325a45fa --- /dev/null +++ b/devtools/server/tests/unit/test_threadlifetime-01.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that thread-lifetime grips last past a resume. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_thread_lifetime(); + }); + }); + do_test_pending(); +} + +function test_thread_lifetime() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let pauseGrip = aPacket.frame.arguments[0]; + + // Create a thread-lifetime actor for this object. + gClient.request({ to: pauseGrip.actor, type: "threadGrip" }, function (aResponse) { + // Successful promotion won't return an error. + do_check_eq(aResponse.error, undefined); + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Verify that the promoted actor is returned again. + do_check_eq(pauseGrip.actor, aPacket.frame.arguments[0].actor); + // Now that we've resumed, should get unrecognizePacketType for the + // promoted grip. + gClient.request({ to: pauseGrip.actor, type: "bogusRequest"}, function (aResponse) { + do_check_eq(aResponse.error, "unrecognizedPacketType"); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + gThreadClient.resume(); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1) { + debugger; + debugger; + } + stopMe({obj: true}); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_threadlifetime-02.js b/devtools/server/tests/unit/test_threadlifetime-02.js new file mode 100644 index 000000000..a7d21a7f9 --- /dev/null +++ b/devtools/server/tests/unit/test_threadlifetime-02.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that thread-lifetime grips last past a resume. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_thread_lifetime(); + }); + }); + do_test_pending(); +} + +function test_thread_lifetime() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let pauseGrip = aPacket.frame.arguments[0]; + + // Create a thread-lifetime actor for this object. + gClient.request({ to: pauseGrip.actor, type: "threadGrip" }, function (aResponse) { + // Successful promotion won't return an error. + do_check_eq(aResponse.error, undefined); + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Verify that the promoted actor is returned again. + do_check_eq(pauseGrip.actor, aPacket.frame.arguments[0].actor); + // Now that we've resumed, release the thread-lifetime grip. + gClient.release(pauseGrip.actor, function (aResponse) { + gClient.request({ to: pauseGrip.actor, type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "noSuchActor"); + gThreadClient.resume(function (aResponse) { + finishClient(gClient); + }); + }); + }); + }); + gThreadClient.resume(); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1) { + debugger; + debugger; + } + stopMe({obj: true}); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_threadlifetime-03.js b/devtools/server/tests/unit/test_threadlifetime-03.js new file mode 100644 index 000000000..22b707ff8 --- /dev/null +++ b/devtools/server/tests/unit/test_threadlifetime-03.js @@ -0,0 +1,81 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that thread-lifetime grips last past a resume. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_thread_lifetime(); + }); + }); + do_test_pending(); +} + +function test_thread_lifetime() +{ + // Get three thread-lifetime grips. + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let grips = []; + + let handler = function (aResponse) { + if (aResponse.error) { + do_check_eq(aResponse.error, ""); + finishClient(gClient); + } + grips.push(aResponse.from); + if (grips.length == 3) { + test_release_many(grips); + } + }; + for (let i = 0; i < 3; i++) { + gClient.request({ to: aPacket.frame.arguments[i].actor, type: "threadGrip" }, + handler); + } + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1, arg2, arg3) { + debugger; + } + stopMe({obj: 1}, {obj: 2}, {obj: 3}); + } + ")()"); +} + +function test_release_many(grips) +{ + // Release the first two grips, leave the third alive. + + let release = [grips[0], grips[1]]; + + gThreadClient.releaseMany(release, function (aResponse) { + // First two actors should return a noSuchActor error, because + // they're gone now. + gClient.request({ to: grips[0], type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "noSuchActor"); + gClient.request({ to: grips[1], type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "noSuchActor"); + + // Last actor should return unrecognizedPacketType, because it's still + // alive. + gClient.request({ to: grips[2], type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "unrecognizedPacketType"); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + }); + }); +} diff --git a/devtools/server/tests/unit/test_threadlifetime-04.js b/devtools/server/tests/unit/test_threadlifetime-04.js new file mode 100644 index 000000000..aff8b525c --- /dev/null +++ b/devtools/server/tests/unit/test_threadlifetime-04.js @@ -0,0 +1,53 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that requesting a thread-lifetime actor twice for the same + * value returns the same actor. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_thread_lifetime(); + }); + }); + do_test_pending(); +} + +function test_thread_lifetime() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let pauseGrip = aPacket.frame.arguments[0]; + + gClient.request({ to: pauseGrip.actor, type: "threadGrip" }, function (aResponse) { + // Successful promotion won't return an error. + do_check_eq(aResponse.error, undefined); + + let threadGrip1 = aResponse.from; + + gClient.request({ to: pauseGrip.actor, type: "threadGrip" }, function (aResponse) { + do_check_eq(threadGrip1, aResponse.from); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1) { + debugger; + } + stopMe({obj: true}); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_threadlifetime-05.js b/devtools/server/tests/unit/test_threadlifetime-05.js new file mode 100644 index 000000000..6697947c1 --- /dev/null +++ b/devtools/server/tests/unit/test_threadlifetime-05.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that releasing a pause-lifetime actorin a releaseMany returns an + * error, but releases all the thread-lifetime actors. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gPauseGrip; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_thread_lifetime(); + }); + }); + do_test_pending(); +} + +function arg_grips(aFrameArgs, aOnResponse) { + let grips = []; + let handler = function (aResponse) { + if (aResponse.error) { + grips.push(aResponse.error); + } else { + grips.push(aResponse.from); + } + if (grips.length == aFrameArgs.length) { + aOnResponse(grips); + } + }; + for (let i = 0; i < aFrameArgs.length; i++) { + gClient.request({ to: aFrameArgs[i].actor, type: "threadGrip" }, + handler); + } +} + +function test_thread_lifetime() +{ + // Get two thread-lifetime grips. + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + + let frameArgs = [ aPacket.frame.arguments[0], aPacket.frame.arguments[1] ]; + gPauseGrip = aPacket.frame.arguments[2]; + arg_grips(frameArgs, function (aGrips) { + release_grips(frameArgs, aGrips); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1, arg2, arg3) { + debugger; + } + stopMe({obj: 1}, {obj: 2}, {obj: 3}); + } + ")()"); +} + + +function release_grips(aFrameArgs, aThreadGrips) +{ + // Release all actors with releaseMany... + let release = [aThreadGrips[0], aThreadGrips[1], gPauseGrip.actor]; + gThreadClient.releaseMany(release, function (aResponse) { + do_check_eq(aResponse.error, "notReleasable"); + // Now ask for thread grips again, they should not exist. + arg_grips(aFrameArgs, function (aNewGrips) { + for (let i = 0; i < aNewGrips.length; i++) { + do_check_eq(aNewGrips[i], "noSuchActor"); + } + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); +} diff --git a/devtools/server/tests/unit/test_threadlifetime-06.js b/devtools/server/tests/unit/test_threadlifetime-06.js new file mode 100644 index 000000000..dba0156f9 --- /dev/null +++ b/devtools/server/tests/unit/test_threadlifetime-06.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that promoting multiple pause-lifetime actors to thread-lifetime actors + * works as expected. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_thread_lifetime(); + }); + }); + do_test_pending(); +} + +function test_thread_lifetime() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let actors = []; + let last; + for (let aGrip of aPacket.frame.arguments) { + actors.push(aGrip.actor); + last = aGrip.actor; + } + + // Create thread-lifetime actors for these objects. + gThreadClient.threadGrips(actors, function (aResponse) { + // Successful promotion won't return an error. + do_check_eq(aResponse.error, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Verify that the promoted actors are returned again. + actors.forEach(function (actor, i) { + do_check_eq(actor, aPacket.frame.arguments[i].actor); + }); + // Now that we've resumed, release the thread-lifetime grips. + gThreadClient.releaseMany(actors, function (aResponse) { + // Successful release won't return an error. + do_check_eq(aResponse.error, undefined); + + gClient.request({ to: last, type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "noSuchActor"); + gThreadClient.resume(function (aResponse) { + finishClient(gClient); + }); + }); + }); + }); + gThreadClient.resume(); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1, arg2, arg3) { + debugger; + debugger; + } + stopMe({obj: 1}, {obj: 2}, {obj: 3}); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_unsafeDereference.js b/devtools/server/tests/unit/test_unsafeDereference.js new file mode 100644 index 000000000..d7afe645f --- /dev/null +++ b/devtools/server/tests/unit/test_unsafeDereference.js @@ -0,0 +1,134 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +// Test Debugger.Object.prototype.unsafeDereference in the presence of +// interesting cross-compartment wrappers. +// +// This is not really a debugger server test; it's more of a Debugger test. +// But we need xpcshell and Components.utils.Sandbox to get +// cross-compartment wrappers with interesting properties, and this is the +// xpcshell test directory most closely related to the JS Debugger API. + +Components.utils.import("resource://gre/modules/jsdebugger.jsm"); +addDebuggerToGlobal(this); + +// Add a method to Debugger.Object for fetching value properties +// conveniently. +Debugger.Object.prototype.getProperty = function (aName) { + let desc = this.getOwnPropertyDescriptor(aName); + if (!desc) + return undefined; + if (!desc.value) { + throw Error("Debugger.Object.prototype.getProperty: " + + "not a value property: " + aName); + } + return desc.value; +}; + +function run_test() { + // Create a low-privilege sandbox, and a chrome-privilege sandbox. + let contentBox = Components.utils.Sandbox("http://www.example.com"); + let chromeBox = Components.utils.Sandbox(this); + + // Create an objects in this compartment, and one in each sandbox. We'll + // refer to the objects as "mainObj", "contentObj", and "chromeObj", in + // variable and property names. + var mainObj = { name: "mainObj" }; + Components.utils.evalInSandbox('var contentObj = { name: "contentObj" };', + contentBox); + Components.utils.evalInSandbox('var chromeObj = { name: "chromeObj" };', + chromeBox); + + // Give each global a pointer to all the other globals' objects. + contentBox.mainObj = chromeBox.mainObj = mainObj; + var contentObj = chromeBox.contentObj = contentBox.contentObj; + var chromeObj = contentBox.chromeObj = chromeBox.chromeObj; + + // First, a whole bunch of basic sanity checks, to ensure that JavaScript + // evaluated in various scopes really does see the world the way this + // test expects it to. + + // The objects appear as global variables in the sandbox, and as + // the sandbox object's properties in chrome. + do_check_true(Components.utils.evalInSandbox("mainObj", contentBox) + === contentBox.mainObj); + do_check_true(Components.utils.evalInSandbox("contentObj", contentBox) + === contentBox.contentObj); + do_check_true(Components.utils.evalInSandbox("chromeObj", contentBox) + === contentBox.chromeObj); + do_check_true(Components.utils.evalInSandbox("mainObj", chromeBox) + === chromeBox.mainObj); + do_check_true(Components.utils.evalInSandbox("contentObj", chromeBox) + === chromeBox.contentObj); + do_check_true(Components.utils.evalInSandbox("chromeObj", chromeBox) + === chromeBox.chromeObj); + + // We (the main global) can see properties of all objects in all globals. + do_check_true(contentBox.mainObj.name === "mainObj"); + do_check_true(contentBox.contentObj.name === "contentObj"); + do_check_true(contentBox.chromeObj.name === "chromeObj"); + + // chromeBox can see properties of all objects in all globals. + do_check_eq(Components.utils.evalInSandbox("mainObj.name", chromeBox), + "mainObj"); + do_check_eq(Components.utils.evalInSandbox("contentObj.name", chromeBox), + "contentObj"); + do_check_eq(Components.utils.evalInSandbox("chromeObj.name", chromeBox), + "chromeObj"); + + // contentBox can see properties of the content object, but not of either + // chrome object, because by default, content -> chrome wrappers hide all + // object properties. + do_check_eq(Components.utils.evalInSandbox("mainObj.name", contentBox), + undefined); + do_check_eq(Components.utils.evalInSandbox("contentObj.name", contentBox), + "contentObj"); + do_check_eq(Components.utils.evalInSandbox("chromeObj.name", contentBox), + undefined); + + // When viewing an object in compartment A from the vantage point of + // compartment B, Debugger should give the same results as debuggee code + // would. + + // Create a debugger, debugging our two sandboxes. + let dbg = new Debugger; + + // Create Debugger.Object instances referring to the two sandboxes, as + // seen from their own compartments. + let contentBoxDO = dbg.addDebuggee(contentBox); + let chromeBoxDO = dbg.addDebuggee(chromeBox); + + // Use Debugger to view the objects from contentBox. We should get the + // same D.O instance from both getProperty and makeDebuggeeValue, and the + // same property visibility we checked for above. + let mainFromContentDO = contentBoxDO.getProperty("mainObj"); + do_check_eq(mainFromContentDO, contentBoxDO.makeDebuggeeValue(mainObj)); + do_check_eq(mainFromContentDO.getProperty("name"), undefined); + do_check_eq(mainFromContentDO.unsafeDereference(), mainObj); + + let contentFromContentDO = contentBoxDO.getProperty("contentObj"); + do_check_eq(contentFromContentDO, contentBoxDO.makeDebuggeeValue(contentObj)); + do_check_eq(contentFromContentDO.getProperty("name"), "contentObj"); + do_check_eq(contentFromContentDO.unsafeDereference(), contentObj); + + let chromeFromContentDO = contentBoxDO.getProperty("chromeObj"); + do_check_eq(chromeFromContentDO, contentBoxDO.makeDebuggeeValue(chromeObj)); + do_check_eq(chromeFromContentDO.getProperty("name"), undefined); + do_check_eq(chromeFromContentDO.unsafeDereference(), chromeObj); + + // Similarly, viewing from chromeBox. + let mainFromChromeDO = chromeBoxDO.getProperty("mainObj"); + do_check_eq(mainFromChromeDO, chromeBoxDO.makeDebuggeeValue(mainObj)); + do_check_eq(mainFromChromeDO.getProperty("name"), "mainObj"); + do_check_eq(mainFromChromeDO.unsafeDereference(), mainObj); + + let contentFromChromeDO = chromeBoxDO.getProperty("contentObj"); + do_check_eq(contentFromChromeDO, chromeBoxDO.makeDebuggeeValue(contentObj)); + do_check_eq(contentFromChromeDO.getProperty("name"), "contentObj"); + do_check_eq(contentFromChromeDO.unsafeDereference(), contentObj); + + let chromeFromChromeDO = chromeBoxDO.getProperty("chromeObj"); + do_check_eq(chromeFromChromeDO, chromeBoxDO.makeDebuggeeValue(chromeObj)); + do_check_eq(chromeFromChromeDO.getProperty("name"), "chromeObj"); + do_check_eq(chromeFromChromeDO.unsafeDereference(), chromeObj); +} diff --git a/devtools/server/tests/unit/test_xpcshell_debugging.js b/devtools/server/tests/unit/test_xpcshell_debugging.js new file mode 100644 index 000000000..7026a02b3 --- /dev/null +++ b/devtools/server/tests/unit/test_xpcshell_debugging.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test the xpcshell-test debug support. Ideally we should have this test +// next to the xpcshell support code, but that's tricky... + +function run_test() { + let testFile = do_get_file("xpcshell_debugging_script.js"); + + // _setupDebuggerServer is from xpcshell-test's head.js + let testResumed = false; + let DebuggerServer = _setupDebuggerServer([testFile.path], () => testResumed = true); + let transport = DebuggerServer.connectPipe(); + let client = new DebuggerClient(transport); + client.connect().then(() => { + // Even though we have no tabs, listTabs gives us the chromeDebugger. + client.getProcess().then(response => { + let actor = response.form.actor; + client.attachTab(actor, (response, tabClient) => { + tabClient.attachThread(null, (response, threadClient) => { + threadClient.addOneTimeListener("paused", (event, packet) => { + equal(packet.why.type, "breakpoint", + "yay - hit the breakpoint at the first line in our script"); + // Resume again - next stop should be our "debugger" statement. + threadClient.addOneTimeListener("paused", (event, packet) => { + equal(packet.why.type, "debuggerStatement", + "yay - hit the 'debugger' statement in our script"); + threadClient.resume(() => { + finishClient(client); + }); + }); + threadClient.resume(); + }); + // tell the thread to do the initial resume. This would cause the + // xpcshell test harness to resume and load the file under test. + threadClient.resume(response => { + // should have been told to resume the test itself. + ok(testResumed); + // Now load our test script. + load(testFile.path); + // and our "paused" listener above should get hit. + }); + }); + }); + }); + }); + do_test_pending(); +} diff --git a/devtools/server/tests/unit/testactors.js b/devtools/server/tests/unit/testactors.js new file mode 100644 index 000000000..39564eeef --- /dev/null +++ b/devtools/server/tests/unit/testactors.js @@ -0,0 +1,176 @@ +/* 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 { TabSources } = require("devtools/server/actors/utils/TabSources"); +const promise = require("promise"); +const makeDebugger = require("devtools/server/actors/utils/make-debugger"); + +var gTestGlobals = []; +DebuggerServer.addTestGlobal = function (aGlobal) { + gTestGlobals.push(aGlobal); +}; + +DebuggerServer.getTestGlobal = function (name) { + for (let g of gTestGlobals) { + if (g.__name == name) { + return g; + } + } + + return null; +}; + +// 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._global.wrappedJSObject = aGlobal; + this.threadActor = new ThreadActor(this, this._global); + this.conn.addActor(this.threadActor); + this._attached = false; + this._extraActors = {}; + this.makeDebugger = makeDebugger.bind(null, { + findDebuggees: () => [this._global], + shouldAddNewGlobalAsDebuggee: g => g.hostAnnotations && + g.hostAnnotations.type == "document" && + g.hostAnnotations.element === this._global + + }); +} + +TestTabActor.prototype = { + constructor: TestTabActor, + actorPrefix: "TestTabActor", + + get window() { + return this._global; + }, + + get url() { + return this._global.__name; + }, + + get sources() { + if (!this._sources) { + this._sources = new TabSources(this.threadActor); + } + return this._sources; + }, + + 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" }; + }, + + onReload: function (aRequest) { + this.sources.reset({ sourceMaps: true }); + this.threadActor.clearDebuggees(); + this.threadActor.dbg.addDebuggees(); + return {}; + }, + + removeActorByName: function (aName) { + const actor = this._extraActors[aName]; + if (this._tabActorPool) { + this._tabActorPool.removeActor(actor); + } + delete this._extraActors[aName]; + }, + + /* Support for DebuggerServer.addTabActor. */ + _createExtraActors: createExtraActors, + _appendExtraActors: appendExtraActors +}; + +TestTabActor.prototype.requestTypes = { + "attach": TestTabActor.prototype.onAttach, + "detach": TestTabActor.prototype.onDetach, + "reload": TestTabActor.prototype.onReload +}; + +exports.register = function (handle) { + handle.setRootActor(createRootActor); +}; + +exports.unregister = function (handle) { + handle.setRootActor(null); +}; diff --git a/devtools/server/tests/unit/tracerlocations.js b/devtools/server/tests/unit/tracerlocations.js new file mode 100644 index 000000000..aa4677c0f --- /dev/null +++ b/devtools/server/tests/unit/tracerlocations.js @@ -0,0 +1,8 @@ +// For JS Tracer tests dealing with source locations. + +function foo(x) { + x += 6; + return "bar"; +} + +foo(42); diff --git a/devtools/server/tests/unit/xpcshell.ini b/devtools/server/tests/unit/xpcshell.ini new file mode 100644 index 000000000..703aa48fb --- /dev/null +++ b/devtools/server/tests/unit/xpcshell.ini @@ -0,0 +1,232 @@ +[DEFAULT] +tags = devtools +head = head_dbg.js +tail = +firefox-appdir = browser +skip-if = toolkit == 'android' + +support-files = + babel_and_browserify_script_with_source_map.js + source-map-data/sourcemapped.coffee + source-map-data/sourcemapped.map + post_init_global_actors.js + post_init_tab_actors.js + pre_init_global_actors.js + pre_init_tab_actors.js + registertestactors-01.js + registertestactors-02.js + registertestactors-03.js + sourcemapped.js + testactors.js + hello-actor.js + setBreakpoint-on-column.js + setBreakpoint-on-column-in-gcd-script.js + setBreakpoint-on-column-with-no-offsets.js + setBreakpoint-on-column-with-no-offsets-at-end-of-line.js + setBreakpoint-on-column-with-no-offsets-in-gcd-script.js + setBreakpoint-on-line.js + setBreakpoint-on-line-in-gcd-script.js + setBreakpoint-on-line-with-multiple-offsets.js + setBreakpoint-on-line-with-multiple-statements.js + setBreakpoint-on-line-with-no-offsets.js + setBreakpoint-on-line-with-no-offsets-in-gcd-script.js + addons/web-extension/manifest.json + addons/web-extension-upgrade/manifest.json + addons/web-extension2/manifest.json + +[test_addon_reload.js] +[test_addons_actor.js] +[test_animation_name.js] +[test_animation_type.js] +[test_actor-registry-actor.js] +[test_nesting-01.js] +[test_nesting-02.js] +[test_nesting-03.js] +[test_forwardingprefix.js] +[test_getyoungestframe.js] +[test_nsjsinspector.js] +[test_dbgactor.js] +[test_dbgglobal.js] +[test_dbgclient_debuggerstatement.js] +[test_attach.js] +[test_MemoryActor_saveHeapSnapshot_01.js] +[test_MemoryActor_saveHeapSnapshot_02.js] +[test_MemoryActor_saveHeapSnapshot_03.js] +[test_reattach-thread.js] +[test_blackboxing-01.js] +[test_blackboxing-02.js] +[test_blackboxing-03.js] +[test_blackboxing-04.js] +[test_blackboxing-05.js] +[test_blackboxing-06.js] +[test_blackboxing-07.js] +[test_frameactor-01.js] +[test_frameactor-02.js] +[test_frameactor-03.js] +[test_frameactor-04.js] +[test_frameactor-05.js] +[test_framearguments-01.js] +[test_getRuleText.js] +[test_getTextAtLineColumn.js] +[test_pauselifetime-01.js] +[test_pauselifetime-02.js] +[test_pauselifetime-03.js] +[test_pauselifetime-04.js] +[test_threadlifetime-01.js] +[test_threadlifetime-02.js] +[test_threadlifetime-03.js] +[test_threadlifetime-04.js] +[test_threadlifetime-05.js] +[test_threadlifetime-06.js] +[test_functiongrips-01.js] +[test_frameclient-01.js] +[test_frameclient-02.js] +[test_nativewrappers.js] +[test_nodelistactor.js] +[test_eval-01.js] +[test_eval-02.js] +[test_eval-03.js] +[test_eval-04.js] +[test_eval-05.js] +[test_promises_actor_attach.js] +[test_promises_actor_exist.js] +[test_promises_actor_list_promises.js] +[test_promises_actor_onnewpromise.js] +[test_promises_actor_onpromisesettled.js] +[test_promises_client_getdependentpromises.js] +[test_promises_object_creationtimestamp.js] +[test_promises_object_timetosettle-01.js] +[test_promises_object_timetosettle-02.js] +[test_protocol_abort.js] +[test_protocol_async.js] +[test_protocol_children.js] +[test_protocol_formtype.js] +[test_protocol_longstring.js] +[test_protocol_simple.js] +[test_protocol_stack.js] +[test_protocol_unregister.js] +[test_breakpoint-01.js] +[test_register_actor.js] +[test_breakpoint-02.js] +[test_breakpoint-03.js] +[test_breakpoint-04.js] +[test_breakpoint-05.js] +[test_breakpoint-06.js] +[test_breakpoint-07.js] +[test_breakpoint-08.js] +[test_breakpoint-09.js] +[test_breakpoint-10.js] +[test_breakpoint-11.js] +[test_breakpoint-12.js] +[test_breakpoint-13.js] +[test_breakpoint-14.js] +[test_breakpoint-15.js] +[test_breakpoint-16.js] +[test_breakpoint-17.js] +[test_breakpoint-18.js] +[test_breakpoint-19.js] +skip-if = true +reason = bug 1104838 +[test_breakpoint-20.js] +[test_breakpoint-21.js] +[test_conditional_breakpoint-01.js] +[test_conditional_breakpoint-02.js] +[test_conditional_breakpoint-03.js] +[test_eventlooplag_actor.js] +skip-if = true +reason = only ran on B2G +[test_listsources-01.js] +[test_listsources-02.js] +[test_listsources-03.js] +[test_listsources-04.js] +[test_new_source-01.js] +[test_sourcemaps-01.js] +[test_sourcemaps-02.js] +[test_sourcemaps-03.js] +[test_sourcemaps-04.js] +[test_sourcemaps-05.js] +[test_sourcemaps-06.js] +[test_sourcemaps-07.js] +[test_sourcemaps-08.js] +[test_sourcemaps-09.js] +[test_sourcemaps-10.js] +[test_sourcemaps-11.js] +[test_sourcemaps-12.js] +[test_sourcemaps-13.js] +[test_sourcemaps-16.js] +[test_sourcemaps-17.js] +[test_objectgrips-01.js] +[test_objectgrips-02.js] +[test_objectgrips-03.js] +[test_objectgrips-04.js] +[test_objectgrips-05.js] +[test_objectgrips-06.js] +[test_objectgrips-07.js] +[test_objectgrips-08.js] +[test_objectgrips-09.js] +[test_objectgrips-10.js] +[test_objectgrips-11.js] +[test_objectgrips-12.js] +[test_objectgrips-13.js] +[test_promise_state-01.js] +[test_promise_state-02.js] +[test_promise_state-03.js] +[test_interrupt.js] +[test_stepping-01.js] +[test_stepping-02.js] +[test_stepping-03.js] +[test_stepping-04.js] +[test_stepping-05.js] +[test_stepping-06.js] +[test_stepping-07.js] +[test_framebindings-01.js] +[test_framebindings-02.js] +[test_framebindings-03.js] +[test_framebindings-04.js] +[test_framebindings-05.js] +[test_framebindings-06.js] +[test_framebindings-07.js] +[test_pause_exceptions-01.js] +[test_pause_exceptions-02.js] +[test_longstringactor.js] +[test_longstringgrips-01.js] +[test_longstringgrips-02.js] +[test_source-01.js] +[test_breakpoint-actor-map.js] +[test_profiler_activation-01.js] +[test_profiler_activation-02.js] +[test_profiler_close.js] +[test_profiler_data.js] +[test_profiler_events-01.js] +[test_profiler_events-02.js] +[test_profiler_getbufferinfo.js] +[test_profiler_getfeatures.js] +[test_profiler_getsharedlibraryinformation.js] +[test_unsafeDereference.js] +[test_add_actors.js] +[test_ignore_caught_exceptions.js] +[test_ignore_no_interface_exceptions.js] +[test_requestTypes.js] +reason = bug 937197 +[test_layout-reflows-observer.js] +[test_protocolSpec.js] +[test_registerClient.js] +[test_client_request.js] +[test_monitor_actor.js] +[test_symbols-01.js] +[test_symbols-02.js] +[test_get-executable-lines.js] +[test_get-executable-lines-source-map.js] +[test_xpcshell_debugging.js] +support-files = xpcshell_debugging_script.js +[test_setBreakpoint-on-column.js] +[test_setBreakpoint-on-column-in-gcd-script.js] +[test_setBreakpoint-on-column-with-no-offsets-at-end-of-line.js] +[test_setBreakpoint-on-line.js] +[test_setBreakpoint-on-line-in-gcd-script.js] +[test_setBreakpoint-on-line-with-multiple-offsets.js] +[test_setBreakpoint-on-line-with-multiple-statements.js] +[test_setBreakpoint-on-line-with-no-offsets.js] +[test_setBreakpoint-on-line-with-no-offsets-in-gcd-script.js] +[test_safe-getter.js] +[test_client_close.js] diff --git a/devtools/server/tests/unit/xpcshell_debugging_script.js b/devtools/server/tests/unit/xpcshell_debugging_script.js new file mode 100644 index 000000000..de2870e96 --- /dev/null +++ b/devtools/server/tests/unit/xpcshell_debugging_script.js @@ -0,0 +1,9 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This is a file that test_xpcshell_debugging.js debugs. + +// We should hit this dump as it is the first debuggable line +dump("hello from the debugee!\n"); + +debugger; // and why not check we hit this!? |