Bug 1493104 - Add unit test for AddonAwareADBScanner;r=ladybenko,daisuke
☠☠ backed out by c24bf7117053 ☠ ☠
authorJulian Descottes <jdescottes@mozilla.com>
Wed, 10 Oct 2018 11:32:11 +0000
changeset 496178 a6cbba097c8a8300e51d3675b8e098756bc37a63
parent 496177 8df2a288391c37d980e5ec0ddbf98c449ec12e6c
child 496179 c24bf71170530c4eadb3f8c05c42f6c08e95e036
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersladybenko, daisuke
bugs1493104
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1493104 - Add unit test for AddonAwareADBScanner;r=ladybenko,daisuke Depends on D7876 Differential Revision: https://phabricator.services.mozilla.com/D7877
devtools/shared/adb/addon-aware-adb-scanner.js
devtools/shared/adb/test/test_adb.js
devtools/shared/adb/test/test_addon-aware-adb-scanner.js
devtools/shared/adb/test/xpcshell-head.js
devtools/shared/adb/test/xpcshell.ini
--- a/devtools/shared/adb/addon-aware-adb-scanner.js
+++ b/devtools/shared/adb/addon-aware-adb-scanner.js
@@ -17,46 +17,57 @@ loader.lazyRequireGetter(this, "ADBScann
  * It implements the following public API of ADBScanner:
  * - enable
  * - disable
  * - scan
  * - listRuntimes
  * - event "runtime-list-updated"
  */
 class AddonAwareADBScanner extends EventEmitter {
-  constructor() {
+  /**
+   * Parameters are provided only to allow tests to replace actual implementations with
+   * mocks.
+   *
+   * @param {ADBScanner} scanner
+   *        Only provided in tests for mocks
+   * @param {ADBAddon} addon
+   *        Only provided in tests for mocks
+   */
+  constructor(scanner = new ADBScanner(), addon = adbAddon) {
     super();
 
     this._onScannerListUpdated = this._onScannerListUpdated.bind(this);
     this._onAddonUpdate = this._onAddonUpdate.bind(this);
 
-    this._scanner = new ADBScanner();
+    this._scanner = scanner;
     this._scanner.on("runtime-list-updated", this._onScannerListUpdated);
+
+    this._addon = addon;
   }
 
   /**
    * Only forward the enable() call if the addon is installed, because ADBScanner::enable
    * only works if the addon is installed.
    */
   enable() {
-    if (adbAddon.status === "installed") {
+    if (this._addon.status === "installed") {
       this._scanner.enable();
     }
 
     // Remove any previous listener, to make sure we only add one listener if enable() is
     // called several times.
-    adbAddon.off("update", this._onAddonUpdate);
+    this._addon.off("update", this._onAddonUpdate);
 
-    adbAddon.on("update", this._onAddonUpdate);
+    this._addon.on("update", this._onAddonUpdate);
   }
 
   disable() {
     this._scanner.disable();
 
-    adbAddon.off("update", this._onAddonUpdate);
+    this._addon.off("update", this._onAddonUpdate);
   }
 
   /**
    * Scan for USB devices.
    *
    * @return {Promise} Promise that will resolve when the scan is completed.
    */
   scan() {
@@ -68,17 +79,17 @@ class AddonAwareADBScanner extends Event
    *
    * @return {Array} Array of currently detected runtimes.
    */
   listRuntimes() {
     return this._scanner.listRuntimes();
   }
 
   _onAddonUpdate() {
-    if (adbAddon.status === "installed") {
+    if (this._addon.status === "installed") {
       this._scanner.enable();
     } else {
       this._scanner.disable();
     }
   }
 
   _onScannerListUpdated() {
     this.emit("runtime-list-updated");
--- a/devtools/shared/adb/test/test_adb.js
+++ b/devtools/shared/adb/test/test_adb.js
@@ -1,8 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
 "use strict";
 
 const EventEmitter = require("devtools/shared/event-emitter");
 const { ExtensionTestUtils } = ChromeUtils.import("resource://testing-common/ExtensionXPCShellUtils.jsm", {});
 const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
 const { getFileForBinary } = require("devtools/shared/adb/adb-binary");
 const { check } = require("devtools/shared/adb/adb-running-checker");
 const { ADB } = require("devtools/shared/adb/adb");
new file mode 100644
--- /dev/null
+++ b/devtools/shared/adb/test/test_addon-aware-adb-scanner.js
@@ -0,0 +1,221 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const EventEmitter = require("devtools/shared/event-emitter");
+const { AddonAwareADBScanner } = require("devtools/shared/adb/addon-aware-adb-scanner");
+
+/**
+ * For the scanner mock, we create an object with spies for each of the public methods
+ * used by the AddonAwareADBScanner, and the ability to emit events.
+ */
+function prepareMockScanner() {
+  const mockScanner = {
+    enable: sinon.spy(),
+    disable: sinon.spy(),
+    scan: sinon.spy(),
+    listRuntimes: sinon.spy()
+  };
+  EventEmitter.decorate(mockScanner);
+  return mockScanner;
+}
+
+/**
+ * For the addon mock, we simply need an object that is able to emit events and has a
+ * status.
+ */
+function prepareMockAddon() {
+  const mockAddon = {
+    status: "unknown",
+  };
+  EventEmitter.decorate(mockAddon);
+  return mockAddon;
+}
+
+/**
+ * Prepare all mocks needed for the scanner tests.
+ */
+function prepareMocks() {
+  const mockScanner = prepareMockScanner();
+  const mockAddon = prepareMockAddon();
+  const addonAwareAdbScanner = new AddonAwareADBScanner(mockScanner, mockAddon);
+  return { addonAwareAdbScanner, mockAddon, mockScanner };
+}
+
+/**
+ * This test covers basic usage of enable() on the AddonAwareADBScanner, and checks the
+ * different behaviors based on the addon status.
+ */
+add_task(async function testCallingEnable() {
+  const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
+
+  // Check that enable() is not called if the addon is uninstalled
+  mockAddon.status = "uninstalled";
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.notCalled, "enable() was not called");
+  mockScanner.enable.reset();
+
+  // Check that enable() is called if the addon is installed
+  mockAddon.status = "installed";
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.called, "enable() was called");
+  mockScanner.enable.reset();
+});
+
+/**
+ * This test checks that enable()/disable() methods from the internal ADBScanner are
+ * called when the addon is installed or uninstalled.
+ */
+add_task(async function testUpdatingAddonEnablesDisablesScanner() {
+  const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
+
+  // Enable the addon aware scanner
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.notCalled, "enable() was not called initially");
+
+  // Check that enable() is called automatically when the addon is installed
+  mockAddon.status = "installed";
+  mockAddon.emit("update");
+  ok(mockScanner.enable.called, "enable() was called when installing the addon");
+  ok(mockScanner.disable.notCalled, "disable() was not called when installing the addon");
+  mockScanner.enable.reset();
+  mockScanner.disable.reset();
+
+  // Check that disabled() is called automatically when the addon is uninstalled
+  mockAddon.status = "uninstalled";
+  mockAddon.emit("update");
+  ok(mockScanner.enable.notCalled, "enable() was not called when uninstalling the addon");
+  ok(mockScanner.disable.called, "disable() was called when uninstalling the addon");
+  mockScanner.enable.reset();
+  mockScanner.disable.reset();
+
+  // Check that enable() is called again when the addon is reinstalled
+  mockAddon.status = "installed";
+  mockAddon.emit("update");
+  ok(mockScanner.enable.called, "enable() was called when installing the addon");
+  ok(mockScanner.disable.notCalled, "disable() was not called when installing the addon");
+  mockScanner.enable.reset();
+  mockScanner.disable.reset();
+});
+
+/**
+ * This test checks that disable() is forwarded from the AddonAwareADBScanner to the real
+ * scanner even if the addon is uninstalled. We might miss the addon uninstall
+ * notification, so it is safer to always proceed with disabling.
+ */
+add_task(async function testScannerIsDisabledWhenMissingAddonUpdate() {
+  const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
+
+  // Enable the addon aware scanner
+  mockAddon.status = "installed";
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.called, "enable() was called initially");
+  mockScanner.enable.reset();
+
+  // Uninstall the addon without firing any event
+  mockAddon.status = "uninstalled";
+
+  // Programmatically call disable, check that the scanner's disable is called even though
+  // the addon was uninstalled.
+  addonAwareAdbScanner.disable();
+  ok(mockScanner.disable.called, "disable() was called when uninstalling the addon");
+  mockScanner.disable.reset();
+});
+
+/**
+ * This test checks that when the AddonAwareADBScanner is disabled, then enable/disable
+ * are not called on the inner scanner when the addon status changes.
+ */
+add_task(async function testInnerEnableIsNotCalledIfNotStarted() {
+  const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
+
+  // Check that enable() is not called on the inner scanner when the addon is installed
+  // if the AddonAwareADBScanner was not enabled
+  mockAddon.status = "installed";
+  mockAddon.emit("update");
+  ok(mockScanner.enable.notCalled, "enable() was not called");
+
+  // Same for disable() and "uninstall"
+  mockAddon.status = "uninstalled";
+  mockAddon.emit("update");
+  ok(mockScanner.disable.notCalled, "disable() was not called");
+
+  // Enable the addon aware scanner
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.notCalled, "enable() was not called");
+  ok(mockScanner.disable.notCalled, "disable() was not called");
+});
+
+/**
+ * This test checks that when the AddonAwareADBScanner is disabled, installing the addon
+ * no longer enables the internal ADBScanner.
+ */
+add_task(async function testEnableIsNoLongerCalledAfterDisabling() {
+  const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
+
+  // Start with the addon installed
+  mockAddon.status = "installed";
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.called, "enable() was called since addon was already installed");
+  mockScanner.enable.reset();
+
+  // Here we call enable again to check that we will not add too many events.
+  // A single call to disable() should stop all listeners, even if we called enable()
+  // several times.
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.called, "enable() was called again");
+  mockScanner.enable.reset();
+
+  // Disable the scanner
+  addonAwareAdbScanner.disable();
+  ok(mockScanner.disable.called, "disable() was called");
+  mockScanner.disable.reset();
+
+  // Emit an addon update event
+  mockAddon.emit("update");
+  ok(mockScanner.enable.notCalled,
+    "enable() is not called since the main scanner is disabled");
+});
+
+/**
+ * Basic check that the "runtime-list-updated" event is forwarded.
+ */
+add_task(async function testListUpdatedEventForwarding() {
+  const { mockScanner, addonAwareAdbScanner } = prepareMocks();
+
+  const spy = sinon.spy();
+  addonAwareAdbScanner.on("runtime-list-updated", spy);
+  mockScanner.emit("runtime-list-updated");
+  ok(spy.called, "The runtime-list-updated event was forwarded from ADBScanner");
+  addonAwareAdbScanner.off("runtime-list-updated", spy);
+});
+
+/**
+ * Basic check that calls to scan() are forwarded.
+ */
+add_task(async function testScanCallForwarding() {
+  const { mockScanner, addonAwareAdbScanner } = prepareMocks();
+
+  ok(mockScanner.scan.notCalled, "ADBScanner scan() is not called initially");
+
+  addonAwareAdbScanner.scan();
+  mockScanner.emit("runtime-list-updated");
+  ok(mockScanner.scan.called, "ADBScanner scan() was called");
+  mockScanner.scan.reset();
+});
+
+/**
+ * Basic check that calls to scan() are forwarded.
+ */
+add_task(async function testListRuntimesCallForwarding() {
+  const { mockScanner, addonAwareAdbScanner } = prepareMocks();
+
+  ok(mockScanner.listRuntimes.notCalled,
+    "ADBScanner listRuntimes() is not called initially");
+
+  addonAwareAdbScanner.listRuntimes();
+  mockScanner.emit("runtime-list-updated");
+  ok(mockScanner.listRuntimes.called, "ADBScanner listRuntimes() was called");
+  mockScanner.scan.reset();
+});
--- a/devtools/shared/adb/test/xpcshell-head.js
+++ b/devtools/shared/adb/test/xpcshell-head.js
@@ -1,8 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /* eslint no-unused-vars: [2, {"vars": "local"}] */
 
 const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
+const Services = require("Services");
+
+// ================================================
+// Load mocking/stubbing library, sinon
+// docs: http://sinonjs.org/releases/v2.3.2/
+ChromeUtils.import("resource://gre/modules/Timer.jsm");
+Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js", this);
+/* globals sinon */
+// ================================================
--- a/devtools/shared/adb/test/xpcshell.ini
+++ b/devtools/shared/adb/test/xpcshell.ini
@@ -3,8 +3,9 @@ tags = devtools
 head = xpcshell-head.js
 firefox-appdir = browser
 skip-if = toolkit == 'android'
 support-files =
   adb.py
 
 [test_adb.js]
 run-sequentially = An extension having the same id is installed/uninstalled in different tests
+[test_addon-aware-adb-scanner.js]