From b05e78acd677adff3aa6e71343f2554e8ac33bf6 Mon Sep 17 00:00:00 2001 From: Julian Descottes Date: Fri, 5 Oct 2018 15:16:39 +0200 Subject: [PATCH] Bug 1493104 - Add unit test for AddonAwareADBScanner;r=ladybenko,daisuke --HG-- extra : rebase_source : b508f59719bea9fc9b827155f8b872052fa69469 --- .../shared/adb/addon-aware-adb-scanner.js | 25 +- devtools/shared/adb/test/test_adb.js | 3 + .../adb/test/test_addon-aware-adb-scanner.js | 221 ++++++++++++++++++ devtools/shared/adb/test/xpcshell-head.js | 9 + devtools/shared/adb/test/xpcshell.ini | 1 + 5 files changed, 252 insertions(+), 7 deletions(-) create mode 100644 devtools/shared/adb/test/test_addon-aware-adb-scanner.js diff --git a/devtools/shared/adb/addon-aware-adb-scanner.js b/devtools/shared/adb/addon-aware-adb-scanner.js index 9af87d673310..873adfa5f93b 100644 --- a/devtools/shared/adb/addon-aware-adb-scanner.js +++ b/devtools/shared/adb/addon-aware-adb-scanner.js @@ -22,14 +22,25 @@ loader.lazyRequireGetter(this, "ADBScanner", "devtools/shared/adb/adb-scanner", * - 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; } /** @@ -37,21 +48,21 @@ class AddonAwareADBScanner extends EventEmitter { * 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); } /** @@ -73,7 +84,7 @@ class AddonAwareADBScanner extends EventEmitter { } _onAddonUpdate() { - if (adbAddon.status === "installed") { + if (this._addon.status === "installed") { this._scanner.enable(); } else { this._scanner.disable(); diff --git a/devtools/shared/adb/test/test_adb.js b/devtools/shared/adb/test/test_adb.js index 0fcefdbfc5c5..70e7538ea64d 100644 --- a/devtools/shared/adb/test/test_adb.js +++ b/devtools/shared/adb/test/test_adb.js @@ -1,3 +1,6 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + "use strict"; const EventEmitter = require("devtools/shared/event-emitter"); diff --git a/devtools/shared/adb/test/test_addon-aware-adb-scanner.js b/devtools/shared/adb/test/test_addon-aware-adb-scanner.js new file mode 100644 index 000000000000..76250ef277db --- /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(); +}); diff --git a/devtools/shared/adb/test/xpcshell-head.js b/devtools/shared/adb/test/xpcshell-head.js index a65bc4aeccc7..f9154e2b56d9 100644 --- a/devtools/shared/adb/test/xpcshell-head.js +++ b/devtools/shared/adb/test/xpcshell-head.js @@ -6,3 +6,12 @@ /* 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 */ +// ================================================ diff --git a/devtools/shared/adb/test/xpcshell.ini b/devtools/shared/adb/test/xpcshell.ini index 4e929a839aee..36cd8b349925 100644 --- a/devtools/shared/adb/test/xpcshell.ini +++ b/devtools/shared/adb/test/xpcshell.ini @@ -8,3 +8,4 @@ support-files = [test_adb.js] run-sequentially = An extension having the same id is installed/uninstalled in different tests +[test_addon-aware-adb-scanner.js]