Bug 1346902 - Update browser toolbox integration test for new debugger. r=jlast draft
authorJ. Ryan Stinnett <jryans@gmail.com>
Tue, 25 Apr 2017 22:12:30 +0200
changeset 572741 7718e849298c2674ea66b6c5143a27cb6a578e83
parent 572740 4486217797a6edaa657e0b3e855af746a0e6b7cb
child 627116 3f0cd2ad5c5c7ad0944bdf2fb111f6b00714bed5
push id57173
push userbmo:poirot.alex@gmail.com
push dateThu, 04 May 2017 17:36:07 +0000
reviewersjlast
bugs1346902
milestone55.0a1
Bug 1346902 - Update browser toolbox integration test for new debugger. r=jlast MozReview-Commit-ID: 8k8bIqSBKGf
devtools/client/debugger/new/test/mochitest/head.js
devtools/client/framework/test/browser.ini
devtools/client/framework/test/browser_browser_toolbox_debugger.js
devtools/client/framework/test/test_browser_toolbox_debugger.js
devtools/client/framework/toolbox-process-window.js
--- a/devtools/client/debugger/new/test/mochitest/head.js
+++ b/devtools/client/debugger/new/test/mochitest/head.js
@@ -170,17 +170,18 @@ function waitForSources(dbg, ...sources)
   }
 
   info("Waiting on sources: " + sources.join(", "));
   const { selectors: { getSources }, store } = dbg;
   return Promise.all(
     sources.map(url => {
       function sourceExists(state) {
         return getSources(state).some(s => {
-          return s.get("url").includes(url);
+          let u = s.get("url");
+          return u && u.includes(url);
         });
       }
 
       if (!sourceExists(store.getState())) {
         return waitForState(dbg, sourceExists);
       }
     })
   );
@@ -207,18 +208,19 @@ function assertPausedLocation(dbg, sourc
   is(getSelectedSource(getState()).get("id"), source.id);
 
   // Check the pause location
   const location = getPause(getState()).getIn(["frame", "location"]);
   is(location.get("sourceId"), source.id);
   is(location.get("line"), line);
 
   // Check the debug line
+  let cm = dbg.win.document.querySelector(".CodeMirror").CodeMirror;
   ok(
-    dbg.win.cm.lineInfo(line - 1).wrapClass.includes("debug-line"),
+    cm.lineInfo(line - 1).wrapClass.includes("debug-line"),
     "Line is highlighted as paused"
   );
 }
 
 /**
  * Assert that the debugger is highlighting the correct location.
  *
  * @memberof mochitest/asserts
@@ -351,17 +353,20 @@ function findSource(dbg, url) {
   if (typeof url !== "string") {
     // Support passing in a source object itelf all APIs that use this
     // function support both styles
     const source = url;
     return source;
   }
 
   const sources = dbg.selectors.getSources(dbg.getState());
-  const source = sources.find(s => s.get("url").includes(url));
+  const source = sources.find(s => {
+    let u = s.get("url");
+    return u && u.includes(url);
+  });
 
   if (!source) {
     throw new Error("Unable to find source: " + url);
   }
 
   return source.toJS();
 }
 
--- a/devtools/client/framework/test/browser.ini
+++ b/devtools/client/framework/test/browser.ini
@@ -18,16 +18,17 @@ support-files =
   shared-head.js
   shared-redux-head.js
   helper_disable_cache.js
   doc_theme.css
   doc_viewsource.html
   browser_toolbox_options_enable_serviceworkers_testing_frame_script.js
   browser_toolbox_options_enable_serviceworkers_testing.html
   serviceworker.js
+  test_browser_toolbox_debugger.js
 
 [browser_browser_toolbox.js]
 [browser_browser_toolbox_debugger.js]
 [browser_devtools_api.js]
 [browser_devtools_api_destroy.js]
 [browser_dynamic_tool_enabling.js]
 [browser_ignore_toolbox_network_requests.js]
 [browser_keybindings_01.js]
--- a/devtools/client/framework/test/browser_browser_toolbox_debugger.js
+++ b/devtools/client/framework/test/browser_browser_toolbox_debugger.js
@@ -1,16 +1,25 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+// This test asserts that the new debugger works from the browser toolbox process
+// Its pass a big piece of Javascript string to the browser toolbox process via
+// MOZ_TOOLBOX_TEST_SCRIPT env variable. It does that as test resources fetched from
+// chrome://mochitests/ package isn't available from browser toolbox process.
+
 // On debug test runner, it takes about 50s to run the test.
 requestLongerTimeout(4);
 
 const { setInterval, clearInterval } = require("sdk/timers");
+const { fetch } = require("devtools/shared/DevToolsUtils");
+
+const debuggerHeadURL = CHROME_URL_ROOT + "../../debugger/new/test/mochitest/head.js";
+const testScriptURL = CHROME_URL_ROOT + "test_browser_toolbox_debugger.js";
 
 add_task(function* runTest() {
   yield new Promise(done => {
     let options = {"set": [
       ["devtools.debugger.prompt-connection", false],
       ["devtools.debugger.remote-enabled", true],
       ["devtools.chrome.enabled", true],
       // Test-only pref to allow passing `testScript` argument to the browser
@@ -36,83 +45,74 @@ add_task(function* runTest() {
 
   // Execute the function every second in order to trigger the breakpoint
   let interval = setInterval(s.plop, 1000);
 
   // Be careful, this JS function is going to be executed in the browser toolbox,
   // which lives in another process. So do not try to use any scope variable!
   let env = Components.classes["@mozilla.org/process/environment;1"]
                       .getService(Components.interfaces.nsIEnvironment);
-  let testScript = function () {
-    const { Task } = Components.utils.import("resource://gre/modules/Task.jsm", {});
-    dump("Opening the browser toolbox and debugger panel\n");
-    let window, document;
-    let testUrl = "http://mozilla.org/browser-toolbox-test.js";
-    Task.spawn(function* () {
-      dump("Waiting for debugger load\n");
-      let panel = yield toolbox.selectTool("jsdebugger");
-      let window = panel.panelWin;
-      let document = window.document;
-
-      yield window.once(window.EVENTS.SOURCE_SHOWN);
-
-      dump("Loaded, selecting the test script to debug\n");
-      let item = document.querySelector(`.dbg-source-item[tooltiptext="${testUrl}"]`);
-      let onSourceShown = window.once(window.EVENTS.SOURCE_SHOWN);
-      item.click();
-      yield onSourceShown;
-
-      dump("Selected, setting a breakpoint\n");
-      let { Sources, editor } = window.DebuggerView;
-      let onBreak = window.once(window.EVENTS.FETCHED_SCOPES);
-      editor.emit("gutterClick", 1);
-      yield onBreak;
-
-      dump("Paused, asserting breakpoint position\n");
-      let url = Sources.selectedItem.attachment.source.url;
-      if (url != testUrl) {
-        throw new Error("Breaking on unexpected script: " + url);
+  // First inject a very minimal head, with simplest assertion methods
+  // and very common globals
+  let testHead = (function () {
+    const info = msg => dump(msg + "\n");
+    const is = (a, b, description) => {
+      let msg = "'" + JSON.stringify(a) + "' is equal to '" + JSON.stringify(b) + "'";
+      if (description) {
+        msg += " - " + description;
       }
-      let cursor = editor.getCursor();
-      if (cursor.line != 1) {
-        throw new Error("Breaking on unexpected line: " + cursor.line);
+      if (a !== b) {
+        msg = "FAILURE: " + msg;
+        dump(msg + "\n");
+        throw new Error(msg);
+      } else {
+        msg = "SUCCESS: " + msg;
+        dump(msg + "\n");
+      }
+    };
+    const ok = (a, description) => {
+      let msg = "'" + JSON.stringify(a) + "' is true";
+      if (description) {
+        msg += " - " + description;
       }
-
-      dump("Now, stepping over\n");
-      let stepOver = window.document.querySelector("#step-over");
-      let onFetchedScopes = window.once(window.EVENTS.FETCHED_SCOPES);
-      stepOver.click();
-      yield onFetchedScopes;
-
-      dump("Stepped, asserting step position\n");
-      url = Sources.selectedItem.attachment.source.url;
-      if (url != testUrl) {
-        throw new Error("Stepping on unexpected script: " + url);
+      if (!a) {
+        msg = "FAILURE: " + msg;
+        dump(msg + "\n");
+        throw new Error(msg);
+      } else {
+        msg = "SUCCESS: " + msg;
+        dump(msg + "\n");
       }
-      cursor = editor.getCursor();
-      if (cursor.line != 2) {
-        throw new Error("Stepping on unexpected line: " + cursor.line);
-      }
+    };
+    const registerCleanupFunction = () => {};
 
-      dump("Resume script execution\n");
-      let resume = window.document.querySelector("#resume");
-      let onResume = toolbox.target.once("thread-resumed");
-      resume.click();
-      yield onResume;
+    const Cu = Components.utils;
+    const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+    const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+    const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+  }).toSource().replace(/^\(function \(\) \{|\}\)$/g, "");
+  // Stringify testHead's function and remove `(function {` prefix and `})` suffix
+  // to ensure inner symbols gets exposed to next pieces of code
 
-      dump("Close the browser toolbox\n");
-      toolbox.destroy();
+  // Then inject new debugger head file
+  let { content } = yield fetch(debuggerHeadURL);
+  let debuggerHead = content;
+  // We remove its import of shared-head, which isn't available in browser toolbox process
+  // And isn't needed thanks to testHead's symbols
+  debuggerHead = debuggerHead.replace(/Services.scriptloader.loadSubScript[^\)]*\);/, "");
 
-    }).catch(error => {
-      dump("Error while running code in the browser toolbox process:\n");
-      dump(error + "\n");
-      dump("stack:\n" + error.stack + "\n");
-    });
-  };
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
+  // Finally, fetch the debugger test script that is going to be execute in the browser
+  // toolbox process
+  let testScript = (yield fetch(testScriptURL)).content;
+  let source =
+    "try {" + testHead + debuggerHead + testScript + "} catch (e) {" +
+    "  dump('Exception: '+ e + ' at ' + e.fileName + ':' + " +
+    "       e.lineNumber + '\\nStack: ' + e.stack + '\\n');" +
+    "}";
+  env.set("MOZ_TOOLBOX_TEST_SCRIPT", source);
   registerCleanupFunction(() => {
     env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
   });
 
   let { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
   // Use two promises, one for each BrowserToolboxProcess.init callback
   // arguments, to ensure that we wait for toolbox run and close events.
   let closePromise;
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/test_browser_toolbox_debugger.js
@@ -0,0 +1,48 @@
+/* global toolbox */
+
+info(`START: ${new Error().lineNumber}`);
+
+let testUrl = "http://mozilla.org/browser-toolbox-test.js";
+
+Task.spawn(function* () {
+  Services.prefs.clearUserPref("devtools.debugger.tabs")
+  Services.prefs.clearUserPref("devtools.debugger.pending-selected-location")
+
+  info("Waiting for debugger load");
+  yield toolbox.selectTool("jsdebugger");
+  let dbg = createDebuggerContext(toolbox);
+  let window = dbg.win;
+  let document = window.document;
+
+  yield waitForSources(dbg, testUrl);
+//  yield waitForSourceCount(dbg, 6);
+
+  info("Loaded, selecting the test script to debug");
+  // First expand the domain
+  let domain = [...document.querySelectorAll(".tree-node")].find(node => {
+    return node.textContent == "mozilla.org";
+  });
+  let arrow = domain.querySelector(".arrow");
+  arrow.click();
+  let script = [...document.querySelectorAll(".tree-node")].find(node => {
+    return node.textContent.includes("browser-toolbox-test.js");
+  });
+  script = script.querySelector(".node");
+  script.click();
+
+  let onPaused = waitForPaused(dbg);
+  yield addBreakpoint(dbg, "browser-toolbox-test.js", 2);
+
+  yield onPaused;
+
+  assertPausedLocation(dbg, "browser-toolbox-test.js", 2);
+
+  yield stepIn(dbg);
+
+  assertPausedLocation(dbg, "browser-toolbox-test.js", 3);
+
+  yield resume(dbg);
+
+  info("Close the browser toolbox");
+  toolbox.destroy();
+});
--- a/devtools/client/framework/toolbox-process-window.js
+++ b/devtools/client/framework/toolbox-process-window.js
@@ -152,26 +152,26 @@ function bindToolboxHandlers() {
   // Once the debugger panel opens listen for thread pause / resume.
   gToolbox.getPanelWhenReady("jsdebugger").then(panel => {
     setupThreadListeners(panel);
   });
 #endif
 }
 
 function setupThreadListeners(panel) {
-  updateBadgeText(panel._controller.activeThread.state == "paused");
+  updateBadgeText(panel._selectors().getPause(panel._getState()));
 
   let onPaused = updateBadgeText.bind(null, true);
   let onResumed = updateBadgeText.bind(null, false);
-  panel.target.on("thread-paused", onPaused);
-  panel.target.on("thread-resumed", onResumed);
+  gToolbox.target.on("thread-paused", onPaused);
+  gToolbox.target.on("thread-resumed", onResumed);
 
   panel.once("destroyed", () => {
-    panel.off("thread-paused", onPaused);
-    panel.off("thread-resumed", onResumed);
+    gToolbox.target.off("thread-paused", onPaused);
+    gToolbox.target.off("thread-resumed", onResumed);
   });
 }
 
 function updateBadgeText(paused) {
   let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"].getService(Ci.nsIMacDockSupport);
   dockSupport.badgeText = paused ? "▐▐ " : " ▶";
 }