Backed out changeset 2a6ee5724361 (bug 1522214) for XPCshell failure in toolkit/components/extensions/test/xpcshell/test_load_all_api_modules.js
authorDorel Luca <dluca@mozilla.com>
Thu, 27 Jun 2019 10:44:29 +0300
changeset 480319 abb8b01b3ed2056ea28857d8e9f85960f7e19558
parent 480318 22c14d55b3e49e8353a49fb5418f90592213635d
child 480320 701877184a2e951648cf47299559733b1489af9b
push id88620
push userdluca@mozilla.com
push dateThu, 27 Jun 2019 07:45:02 +0000
treeherderautoland@abb8b01b3ed2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1522214
milestone69.0a1
backs out2a6ee5724361a4988028e7c7d728f91da71f0a35
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
Backed out changeset 2a6ee5724361 (bug 1522214) for XPCshell failure in toolkit/components/extensions/test/xpcshell/test_load_all_api_modules.js
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ext-toolkit.json
toolkit/components/extensions/jar.mn
toolkit/components/extensions/parent/ext-normandyAddonStudy.js
toolkit/components/extensions/schemas/jar.mn
toolkit/components/extensions/schemas/normandyAddonStudy.json
toolkit/components/extensions/test/xpcshell/test_ext_normandyAddonStudy.js
toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
toolkit/components/normandy/lib/AddonStudies.jsm
toolkit/components/normandy/moz.build
toolkit/components/normandy/test/NormandyTestUtils.jsm
toolkit/components/normandy/test/browser/browser_AddonStudies.js
toolkit/components/normandy/test/browser/browser_ShieldPreferences.js
toolkit/components/normandy/test/browser/browser_about_studies.js
toolkit/components/normandy/test/browser/browser_actions_AddonStudyAction.js
toolkit/components/normandy/test/browser/browser_actions_BranchedAddonStudyAction.js
toolkit/components/normandy/test/browser/head.js
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -117,17 +117,17 @@ const PRIVATE_ALLOWED_PERMISSION = "inte
 // storage used by the browser.storage.local API is not directly accessible from the extension code,
 // it is defined and reserved as "userContextIdInternal.webextStorageLocal" in ContextualIdentityService.jsm).
 const WEBEXT_STORAGE_USER_CONTEXT_ID = -1 >>> 0;
 
 // The maximum time to wait for extension child shutdown blockers to complete.
 const CHILD_SHUTDOWN_TIMEOUT_MS = 8000;
 
 // Permissions that are only available to privileged extensions.
-const PRIVILEGED_PERMS = new Set(["mozillaAddons", "geckoViewAddons", "telemetry", "urlbar", "normandyAddonStudy"]);
+const PRIVILEGED_PERMS = new Set(["mozillaAddons", "geckoViewAddons", "telemetry", "urlbar"]);
 
 /**
  * Classify an individual permission from a webextension manifest
  * as a host/origin permission, an api permission, or a regular permission.
  *
  * @param {string} perm  The permission string to classify
  * @param {boolean} restrictSchemes
  * @param {boolean} isPrivileged whether or not the webextension is privileged
--- a/toolkit/components/extensions/ext-toolkit.json
+++ b/toolkit/components/extensions/ext-toolkit.json
@@ -101,24 +101,16 @@
   "management": {
     "url": "chrome://extensions/content/parent/ext-management.js",
     "schema": "chrome://extensions/content/schemas/management.json",
     "scopes": ["addon_parent"],
     "paths": [
       ["management"]
     ]
   },
-  "normandyAddonStudy": {
-    "url": "chrome://extensions/content/parent/ext-normandyAddonStudy.js",
-    "schema": "chrome://extensions/content/schemas/normandyAddonStudy.json",
-    "scopes": ["addon_parent", "content_parent", "devtools_parent"],
-    "paths": [
-      ["normandyAddonStudy"]
-    ]
-  },
   "notifications": {
     "url": "chrome://extensions/content/parent/ext-notifications.js",
     "schema": "chrome://extensions/content/schemas/notifications.json",
     "scopes": ["addon_parent"],
     "paths": [
       ["notifications"]
     ]
   },
--- a/toolkit/components/extensions/jar.mn
+++ b/toolkit/components/extensions/jar.mn
@@ -24,17 +24,16 @@ toolkit.jar:
     content/extensions/parent/ext-geckoProfiler.js (parent/ext-geckoProfiler.js)
 #endif
     content/extensions/parent/ext-i18n.js (parent/ext-i18n.js)
 #ifndef ANDROID
     content/extensions/parent/ext-identity.js (parent/ext-identity.js)
 #endif
     content/extensions/parent/ext-idle.js (parent/ext-idle.js)
     content/extensions/parent/ext-management.js (parent/ext-management.js)
-    content/extensions/parent/ext-normandyAddonStudy.js (parent/ext-normandyAddonStudy.js)
     content/extensions/parent/ext-notifications.js (parent/ext-notifications.js)
     content/extensions/parent/ext-permissions.js (parent/ext-permissions.js)
     content/extensions/parent/ext-privacy.js (parent/ext-privacy.js)
     content/extensions/parent/ext-protocolHandlers.js (parent/ext-protocolHandlers.js)
     content/extensions/parent/ext-proxy.js (parent/ext-proxy.js)
     content/extensions/parent/ext-runtime.js (parent/ext-runtime.js)
     content/extensions/parent/ext-storage.js (parent/ext-storage.js)
     content/extensions/parent/ext-tabs-base.js (parent/ext-tabs-base.js)
deleted file mode 100644
--- a/toolkit/components/extensions/parent/ext-normandyAddonStudy.js
+++ /dev/null
@@ -1,75 +0,0 @@
-"use strict";
-
-const {AddonStudies} = ChromeUtils.import("resource://normandy/lib/AddonStudies.jsm");
-const {ClientID} = ChromeUtils.import("resource://gre/modules/ClientID.jsm");
-
-ChromeUtils.defineModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
-
-
-this.normandyAddonStudy = class extends ExtensionAPI {
-  getAPI(context) {
-    let {extension} = context;
-
-    return {
-      normandyAddonStudy: {
-        /**
-         * Returns a study object for the current study.
-         *
-         * @returns {Study}
-         */
-        async getStudy() {
-          const studies = await AddonStudies.getAll();
-          return studies.find(study => study.addonId === extension.id);
-        },
-
-        /**
-         * Marks the study as ended and then uninstalls the addon.
-         *
-         * @param {string} reason Why the study is ending
-         */
-        async endStudy(reason) {
-          const study = await this.getStudy();
-
-          // Mark the study as ended
-          await AddonStudies.markAsEnded(study, reason);
-
-          // Uninstall the addon
-          const addon = await AddonManager.getAddonByID(study.addonId);
-          if (addon) {
-            await addon.uninstall();
-          }
-        },
-
-        /**
-         * Returns an object with metadata about the client which may
-         * be required for constructing survey URLs.
-         *
-         * @returns {Object}
-         */
-        async getClientMetadata() {
-          return {
-            updateChannel: Services.appinfo.defaultUpdateChannel,
-            fxVersion: Services.appinfo.version,
-            clientID: await ClientID.getClientID(),
-          };
-        },
-
-        onUnenroll: new EventManager({
-          context,
-          name: "normandyAddonStudy.onUnenroll",
-          register: (fire) => {
-            const listener = async (reason) => {
-              await fire.async(reason);
-            };
-
-            AddonStudies.addUnenrollListener(extension.id, listener);
-
-            return () => {
-              AddonStudies.removeUnenrollListener(extension.id, listener);
-            };
-          },
-        }).api(),
-      },
-    };
-  }
-};
--- a/toolkit/components/extensions/schemas/jar.mn
+++ b/toolkit/components/extensions/schemas/jar.mn
@@ -26,17 +26,16 @@ toolkit.jar:
     content/extensions/schemas/i18n.json
 #ifndef ANDROID
     content/extensions/schemas/identity.json
 #endif
     content/extensions/schemas/idle.json
     content/extensions/schemas/management.json
     content/extensions/schemas/manifest.json
     content/extensions/schemas/native_manifest.json
-    content/extensions/schemas/normandyAddonStudy.json
     content/extensions/schemas/notifications.json
     content/extensions/schemas/permissions.json
     content/extensions/schemas/proxy.json
     content/extensions/schemas/privacy.json
     content/extensions/schemas/runtime.json
     content/extensions/schemas/storage.json
     content/extensions/schemas/telemetry.json
     content/extensions/schemas/test.json
deleted file mode 100644
--- a/toolkit/components/extensions/schemas/normandyAddonStudy.json
+++ /dev/null
@@ -1,130 +0,0 @@
-[
-  {
-    "namespace": "manifest",
-    "types": [{
-      "$extend": "Permission",
-      "choices": [{
-        "type": "string",
-        "enum": [
-          "normandyAddonStudy"
-        ]
-      }]
-    }]
-  },
-  {
-    "namespace": "normandyAddonStudy",
-    "description": "Normandy Study API",
-    "allowedContexts": ["content", "devtools"],
-    "defaultContexts": ["content", "devtools"],
-    "permissions": [
-      "normandyAddonStudy"
-    ],
-    "types": [
-      {
-        "id": "Study",
-        "type": "object",
-        "properties": {
-          "recipeId": {
-            "type": "integer",
-            "description": "The ID of the recipe for the study."
-          },
-          "slug": {
-            "type": "string",
-            "description": "A slug to identify the study."
-          },
-          "userFacingName": {
-            "type": "string",
-            "description": "The name presented on about:studies."
-          },
-          "userFacingDescription": {
-            "type": "string",
-            "description": "The description presented on about:studies."
-          },
-          "branch": {
-            "type": "string",
-            "description": "The study branch in which the user is enrolled."
-          },
-          "active": {
-            "type": "boolean",
-            "description": "The state of the study."
-          },
-          "addonId": {
-            "type": "string",
-            "description": "The ID of the extension installed by the study."
-          },
-          "addonUrl": {
-            "type": "string",
-            "description": "The URL of the XPI that was downloaded and installed by the study."
-          },
-          "addonVersion": {
-            "type": "string",
-            "description": "The version of the extension installed by the study."
-          },
-          "studyStartDate": {
-            "$ref": "extensionTypes.Date",
-            "description": "The start date for the study."
-          },
-          "studyEndDate": {
-            "$ref": "extensionTypes.Date",
-            "description": "The end date for the study."
-          },
-          "extensionApiId": {
-            "type": "integer",
-            "description": "The record ID for the extension in Normandy server's database."
-          },
-          "extensionHash": {
-            "type": "string",
-            "description": "A hash of the extension XPI file."
-          },
-          "extensionHashAlgorithm": {
-            "type": "string",
-            "description": "The algorithm used to hash the extension XPI file."
-          }
-        }
-      }
-    ],
-    "functions": [
-      {
-        "name": "getStudy",
-        "type": "function",
-        "description": "Returns a study object for the current study.",
-        "async": true,
-        "parameters": []
-      },
-      {
-        "name": "endStudy",
-        "type": "function",
-        "description": "Marks the study as ended and then uninstalls the addon.",
-        "async": true,
-        "parameters": [
-          {
-            "type": "string",
-            "name": "reason",
-            "description": "The reason why the study is ending."
-          }
-        ]
-      },
-      {
-        "name": "getClientMetadata",
-        "type": "function",
-        "description": "Returns an object with metadata about the client which may be required for constructing survey URLs.",
-        "async": true,
-        "parameters": []
-      }
-    ],
-    "events": [
-      {
-        "name": "onUnenroll",
-        "type": "function",
-        "description": "Fired when a user unenrolls from a study but before the addon is uninstalled.",
-        "parameters": [
-          {
-            "type": "string",
-            "name": "reason",
-            "description": "The reason why the study is ending."
-          }
-        ]
-      }
-    ]
-  }
-]
deleted file mode 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_normandyAddonStudy.js
+++ /dev/null
@@ -1,176 +0,0 @@
-"use strict";
-
-ChromeUtils.defineModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
-
-const {AddonStudies} = ChromeUtils.import("resource://normandy/lib/AddonStudies.jsm");
-const {NormandyTestUtils} = ChromeUtils.import("resource://testing-common/NormandyTestUtils.jsm");
-const {TestUtils} = ChromeUtils.import("resource://testing-common/TestUtils.jsm");
-
-const {addonStudyFactory} = NormandyTestUtils.factories;
-
-AddonTestUtils.init(this);
-
-// All tests run privileged unless otherwise specified not to.
-function createExtension(backgroundScript, permissions, isPrivileged = true) {
-  let extensionData = {
-    background: backgroundScript,
-    manifest: {
-      applications: {
-        gecko: {
-          id: "test@shield.mozilla.com",
-        },
-      },
-      permissions,
-    },
-    isPrivileged,
-  };
-  return ExtensionTestUtils.loadExtension(extensionData);
-}
-
-async function run(test) {
-  let extension = createExtension(
-    test.backgroundScript,
-    test.permissions || ["normandyAddonStudy"],
-    test.isPrivileged
-  );
-  await extension.startup();
-  if (test.doneSignal) {
-    await extension.awaitFinish(test.doneSignal);
-  } else if (test.validationScript) {
-    await test.validationScript(extension);
-  }
-  await extension.unload();
-}
-
-add_task(async function setup() {
-  await ExtensionTestUtils.startAddonManager();
-});
-
-add_task(async function test_normandyAddonStudy_without_normandyAddonStudy_permission_privileged() {
-  await run({
-    backgroundScript: () => {
-      browser.test.assertTrue(!browser.normandyAddonStudy, "'normandyAddonStudy' permission is required");
-      browser.test.notifyPass("normandyAddonStudy_permission");
-    },
-    permissions: [],
-    doneSignal: "normandyAddonStudy_permission",
-  });
-});
-
-add_task(async function test_normandyAddonStudy_without_privilege() {
-  await run({
-    backgroundScript: () => {
-      browser.test.assertTrue(!browser.normandyAddonStudy, "Extension must be privileged");
-      browser.test.notifyPass("normandyAddonStudy_permission");
-    },
-    isPrivileged: false,
-    doneSignal: "normandyAddonStudy_permission",
-  });
-});
-
-add_task(async function test_getStudy_works() {
-  const study = addonStudyFactory({
-    addonId: "test@shield.mozilla.com",
-  });
-
-  const testWrapper = AddonStudies.withStudies([study]);
-  const test = testWrapper(async () => {
-    await run({
-      backgroundScript: async () => {
-        const result = await browser.normandyAddonStudy.getStudy();
-        browser.test.sendMessage("study", result);
-      },
-      validationScript: async extension => {
-        let studyResult = await extension.awaitMessage("study");
-        deepEqual(studyResult, study, "normandyAddonStudy.getStudy returns the correct study");
-      },
-    });
-  });
-
-  await test();
-});
-
-add_task(async function test_endStudy_works() {
-  const study = addonStudyFactory({
-    addonId: "test@shield.mozilla.com",
-  });
-
-  const testWrapper = AddonStudies.withStudies([study]);
-  const test = testWrapper(async () => {
-    await run({
-      backgroundScript: async () => {
-        await browser.normandyAddonStudy.endStudy("test");
-      },
-      validationScript: async () => {
-        // Check that `AddonStudies.markAsEnded` was called
-        await TestUtils.topicObserved("shield-study-ended", (subject, message) => {
-          return message === `${study.recipeId}`;
-        });
-
-        const addon = await AddonManager.getAddonByID(study.addonId);
-        equal(addon, undefined, "Addon should be uninstalled.");
-      },
-    });
-  });
-
-  await test();
-});
-
-add_task(async function test_getClientMetadata_works() {
-  const study = addonStudyFactory({
-    addonId: "test@shield.mozilla.com",
-    slug: "test-slug",
-    branch: "test-branch",
-  });
-
-  const testWrapper = AddonStudies.withStudies([study]);
-  const test = testWrapper(async () => {
-    await run({
-      backgroundScript: async () => {
-        const metadata = await browser.normandyAddonStudy.getClientMetadata();
-        browser.test.sendMessage("clientMetadata", metadata);
-      },
-      validationScript: async extension => {
-        let clientMetadata = await extension.awaitMessage("clientMetadata");
-
-        ok(
-          clientMetadata.updateChannel === Services.appinfo.defaultUpdateChannel,
-          "clientMetadata contains correct updateChannel",
-        );
-
-        ok(
-          clientMetadata.fxVersion === Services.appinfo.version,
-          "clientMetadata contains correct fxVersion",
-        );
-
-        ok("clientID" in clientMetadata, "clientMetadata contains a clientID");
-      },
-    });
-  });
-
-  await test();
-});
-
-add_task(async function test_onUnenroll_works() {
-  const study = addonStudyFactory({
-    addonId: "test@shield.mozilla.com",
-  });
-
-  const testWrapper = AddonStudies.withStudies([study]);
-  const test = testWrapper(async () => {
-    await run({
-      backgroundScript: async () => {
-        browser.normandyAddonStudy.onUnenroll.addListener(reason => {
-          browser.test.sendMessage("unenrollReason", reason);
-        });
-      },
-      validationScript: async extension => {
-        await AddonStudies.markAsEnded(study, "test");
-        const unenrollReason = await extension.awaitMessage("unenrollReason");
-        equal(unenrollReason, "test", "Unenroll listener should be called.");
-      },
-    });
-  });
-
-  await test();
-});
--- a/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
@@ -434,17 +434,16 @@ const GRANTED_WITHOUT_USER_PROMPT = [
   "contextualIdentities",
   "cookies",
   "geckoProfiler",
   "identity",
   "idle",
   "menus",
   "menus.overrideContext",
   "mozillaAddons",
-  "normandyAddonStudy",
   "search",
   "storage",
   "telemetry",
   "theme",
   "urlbar",
   "webRequest",
   "webRequestBlocking",
 ];
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -71,17 +71,16 @@ skip-if = os == "android" # Not shipped 
 [test_ext_geturl.js]
 [test_ext_idle.js]
 [test_ext_incognito.js]
 [test_ext_localStorage.js]
 [test_ext_management.js]
 skip-if = (os == "win" && !debug) #Bug 1419183 disable on Windows
 [test_ext_management_uninstall_self.js]
 [test_ext_messaging_startup.js]
-[test_ext_normandyAddonStudy.js]
 skip-if = appname == "thunderbird" || (os == "android" && debug)
 [test_ext_onmessage_removelistener.js]
 skip-if = true # This test no longer tests what it is meant to test.
 [test_ext_permission_xhr.js]
 [test_ext_persistent_events.js]
 [test_ext_privacy.js]
 skip-if = appname == "thunderbird" || (os == "android" && debug)
 [test_ext_privacy_disable.js]
--- a/toolkit/components/normandy/lib/AddonStudies.jsm
+++ b/toolkit/components/normandy/lib/AddonStudies.jsm
@@ -340,33 +340,19 @@ var AddonStudies = {
     if (!listeners) {
       listeners = new Set();
       this._unenrollListeners.set(id, listeners);
     }
     listeners.add(listener);
   },
 
   /**
-   * Unregister a callback to be invoked when a given study ends.
-   *
-   * @param {string} id         The extension id
-   * @param {function} listener The callback
-   */
-  removeUnenrollListener(id, listener) {
-    let listeners = this._unenrollListeners.get(id);
-    if (listeners) {
-      listeners.delete(listener);
-    }
-  },
-
-  /**
    * Invoke the unenroll callback (if any) for the given extension
    *
    * @param {string} id The extension id
-   * @param {string} reason Why the study is ending
    *
    * @returns {Promise} A Promise resolved after the unenroll listener
    *                    (if any) has finished its unenroll tasks.
    */
   onUnenroll(id, reason) {
     let callbacks = this._unenrollListeners.get(id);
     let promises = [];
     if (callbacks) {
--- a/toolkit/components/normandy/moz.build
+++ b/toolkit/components/normandy/moz.build
@@ -8,20 +8,16 @@ with Files('**'):
     BUG_COMPONENT = ('Firefox', 'Normandy Client')
 
 JAR_MANIFESTS += ['jar.mn']
 
 EXTRA_JS_MODULES += [
     'ShieldContentProcess.jsm',
 ]
 
-TESTING_JS_MODULES += [
-    'test/NormandyTestUtils.jsm',
-]
-
 XPCOM_MANIFESTS += [
     'components.conf',
 ]
 
 SPHINX_TREES['normandy'] = 'docs'
 
 TEST_DIRS += ['test/browser']
 
deleted file mode 100644
--- a/toolkit/components/normandy/test/NormandyTestUtils.jsm
+++ /dev/null
@@ -1,75 +0,0 @@
-/* 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/. */
-"use strict";
-
-ChromeUtils.import("resource://normandy/lib/AddonStudies.jsm", this);
-
-const FIXTURE_ADDON_ID = "normandydriver-a@example.com";
-
-var EXPORTED_SYMBOLS = ["NormandyTestUtils"];
-
-// Factory IDs
-let _addonStudyFactoryId = 0;
-let _preferenceStudyFactoryId = 0;
-
-const NormandyTestUtils = {
-  factories: {
-    addonStudyFactory(attrs) {
-      for (const key of ["name", "description"]) {
-        if (attrs && attrs[key]) {
-          throw new Error(`${key} is no longer a valid key for addon studies, please update to v2 study schema`);
-        }
-      }
-
-      return Object.assign({
-        recipeId: _addonStudyFactoryId++,
-        slug: "test-study",
-        userFacingName: "Test study",
-        userFacingDescription: "test description",
-        branch: AddonStudies.NO_BRANCHES_MARKER,
-        active: true,
-        addonId: FIXTURE_ADDON_ID,
-        addonUrl: "http://test/addon.xpi",
-        addonVersion: "1.0.0",
-        studyStartDate: new Date(),
-        studyEndDate: null,
-        extensionApiId: 1,
-        extensionHash: "ade1c14196ec4fe0aa0a6ba40ac433d7c8d1ec985581a8a94d43dc58991b5171",
-        extensionHashAlgorithm: "sha256",
-      }, attrs);
-    },
-
-    branchedAddonStudyFactory(attrs) {
-      return NormandyTestUtils.factories.addonStudyFactory(Object.assign({
-        branch: "a",
-      }, attrs));
-    },
-
-    preferenceStudyFactory(attrs) {
-      const defaultPref = {
-        "test.study": {},
-      };
-      const defaultPrefInfo = {
-        preferenceValue: false,
-        preferenceType: "boolean",
-        previousPreferenceValue: undefined,
-        preferenceBranchType: "default",
-      };
-      const preferences = {};
-      for (const [prefName, prefInfo] of Object.entries(attrs.preferences || defaultPref)) {
-        preferences[prefName] = { ...defaultPrefInfo, ...prefInfo };
-      }
-
-      return Object.assign({
-        name: `Test study ${_preferenceStudyFactoryId++}`,
-        branch: "control",
-        expired: false,
-        lastSeen: new Date().toJSON(),
-        experimentType: "exp",
-      }, attrs, {
-        preferences,
-      });
-    },
-  },
-};
--- a/toolkit/components/normandy/test/browser/browser_AddonStudies.js
+++ b/toolkit/components/normandy/test/browser/browser_AddonStudies.js
@@ -2,19 +2,16 @@
 
 ChromeUtils.import("resource://gre/modules/IndexedDB.jsm", this);
 ChromeUtils.import("resource://gre/modules/AddonManager.jsm", this);
 ChromeUtils.import("resource://testing-common/TestUtils.jsm", this);
 ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm", this);
 ChromeUtils.import("resource://normandy/lib/AddonStudies.jsm", this);
 ChromeUtils.import("resource://normandy/lib/TelemetryEvents.jsm", this);
 
-const {NormandyTestUtils} = ChromeUtils.import("resource://testing-common/NormandyTestUtils.jsm");
-const {addonStudyFactory} = NormandyTestUtils.factories;
-
 // Initialize test utils
 AddonTestUtils.initMochitest(this);
 
 decorate_task(
   AddonStudies.withStudies(),
   async function testGetMissing() {
     is(
       await AddonStudies.get("does-not-exist"),
--- a/toolkit/components/normandy/test/browser/browser_ShieldPreferences.js
+++ b/toolkit/components/normandy/test/browser/browser_ShieldPreferences.js
@@ -2,19 +2,16 @@
 
 ChromeUtils.import("resource://gre/modules/Services.jsm", this);
 ChromeUtils.import("resource://normandy/lib/AddonStudies.jsm", this);
 ChromeUtils.import("resource://normandy/lib/PreferenceExperiments.jsm", this);
 ChromeUtils.import("resource://normandy/lib/ShieldPreferences.jsm", this);
 
 const OPT_OUT_STUDIES_ENABLED_PREF = "app.shield.optoutstudies.enabled";
 
-const {NormandyTestUtils} = ChromeUtils.import("resource://testing-common/NormandyTestUtils.jsm");
-const {addonStudyFactory, preferenceStudyFactory} = NormandyTestUtils.factories;
-
 ShieldPreferences.init();
 
 decorate_task(
   withMockPreferences,
   AddonStudies.withStudies([
     addonStudyFactory({active: true}),
     addonStudyFactory({active: true}),
   ]),
--- a/toolkit/components/normandy/test/browser/browser_about_studies.js
+++ b/toolkit/components/normandy/test/browser/browser_about_studies.js
@@ -1,18 +1,15 @@
 "use strict";
 
 ChromeUtils.import("resource://normandy/lib/AddonStudies.jsm", this);
 ChromeUtils.import("resource://normandy/lib/PreferenceExperiments.jsm", this);
 ChromeUtils.import("resource://normandy/lib/RecipeRunner.jsm", this);
 ChromeUtils.import("resource://normandy-content/AboutPages.jsm", this);
 
-const {NormandyTestUtils} = ChromeUtils.import("resource://testing-common/NormandyTestUtils.jsm");
-const {addonStudyFactory, preferenceStudyFactory} = NormandyTestUtils.factories;
-
 function withAboutStudies(testFunc) {
   return async (...args) => (
     BrowserTestUtils.withNewTab("about:studies", async browser => (
       testFunc(...args, browser)
     ))
   );
 }
 
--- a/toolkit/components/normandy/test/browser/browser_actions_AddonStudyAction.js
+++ b/toolkit/components/normandy/test/browser/browser_actions_AddonStudyAction.js
@@ -1,19 +1,16 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm", this);
 ChromeUtils.import("resource://testing-common/TestUtils.jsm", this);
 ChromeUtils.import("resource://normandy/actions/AddonStudyAction.jsm", this);
 ChromeUtils.import("resource://normandy/lib/AddonStudies.jsm", this);
 ChromeUtils.import("resource://normandy/lib/Uptake.jsm", this);
 
-const {NormandyTestUtils} = ChromeUtils.import("resource://testing-common/NormandyTestUtils.jsm");
-const {addonStudyFactory} = NormandyTestUtils.factories;
-
 function addonStudyRecipeFactory(overrides = {}) {
   let args = {
     name: "Fake name",
     description: "fake description",
     addonUrl: "https://example.com/study.xpi",
     extensionApiId: 1,
   };
   if (Object.hasOwnProperty.call(overrides, "arguments")) {
--- a/toolkit/components/normandy/test/browser/browser_actions_BranchedAddonStudyAction.js
+++ b/toolkit/components/normandy/test/browser/browser_actions_BranchedAddonStudyAction.js
@@ -1,19 +1,16 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm", this);
 ChromeUtils.import("resource://testing-common/TestUtils.jsm", this);
 ChromeUtils.import("resource://normandy/actions/BranchedAddonStudyAction.jsm", this);
 ChromeUtils.import("resource://normandy/lib/AddonStudies.jsm", this);
 ChromeUtils.import("resource://normandy/lib/Uptake.jsm", this);
 
-const {NormandyTestUtils} = ChromeUtils.import("resource://testing-common/NormandyTestUtils.jsm");
-const {branchedAddonStudyFactory} = NormandyTestUtils.factories;
-
 function branchedAddonStudyRecipeFactory(overrides = {}) {
   let args = {
     slug: "fake-slug",
     userFacingName: "Fake name",
     userFacingDescription: "fake description",
     branches: [
       {
         slug: "a",
--- a/toolkit/components/normandy/test/browser/head.js
+++ b/toolkit/components/normandy/test/browser/head.js
@@ -249,16 +249,75 @@ this.decorate = function(...args) {
  *       // Do a test
  *     }
  *   );
  */
 this.decorate_task = function(...args) {
   return add_task(decorate(...args));
 };
 
+let _addonStudyFactoryId = 0;
+this.addonStudyFactory = function(attrs = {}) {
+  for (const key of ["name", "description"]) {
+    if (attrs[key]) {
+      throw new Error(`${key} is no longer a valid key for addon studies, please update to v2 study schema`);
+    }
+  }
+
+  return Object.assign({
+    recipeId: _addonStudyFactoryId++,
+    slug: "test-study",
+    userFacingName: "Test study",
+    userFacingDescription: "test description",
+    branch: AddonStudies.NO_BRANCHES_MARKER,
+    active: true,
+    addonId: FIXTURE_ADDON_ID,
+    addonUrl: "http://test/addon.xpi",
+    addonVersion: "1.0.0",
+    studyStartDate: new Date(),
+    studyEndDate: null,
+    extensionApiId: 1,
+    extensionHash: "ade1c14196ec4fe0aa0a6ba40ac433d7c8d1ec985581a8a94d43dc58991b5171",
+    extensionHashAlgorithm: "sha256",
+  }, attrs);
+};
+
+this.branchedAddonStudyFactory = function(attrs) {
+  return this.addonStudyFactory(Object.assign({
+    branch: "a",
+  }, attrs));
+};
+
+let _preferenceStudyFactoryId = 0;
+this.preferenceStudyFactory = function(attrs) {
+  const defaultPref = {
+    "test.study": {},
+  };
+  const defaultPrefInfo = {
+    preferenceValue: false,
+    preferenceType: "boolean",
+    previousPreferenceValue: undefined,
+    preferenceBranchType: "default",
+  };
+  const preferences = {};
+  for (const [prefName, prefInfo] of Object.entries(attrs.preferences || defaultPref)) {
+    preferences[prefName] = { ...defaultPrefInfo, ...prefInfo };
+  }
+
+  return Object.assign({
+    name: `Test study ${_preferenceStudyFactoryId++}`,
+    branch: "control",
+    expired: false,
+    lastSeen: new Date().toJSON(),
+    experimentType: "exp",
+  }, attrs, {
+    preferences,
+  });
+};
+
 this.withStub = function(...stubArgs) {
   return function wrapper(testFunction) {
     return async function wrappedTestFunction(...args) {
       const stub = sinon.stub(...stubArgs);
       try {
         await testFunction(...args, stub);
       } finally {
         stub.restore();