Bug 1295732 - Allow system add-ons updates to override defaults per addon ID. r=aswan, a=ritu
authorRobert Helmer <rhelmer@mozilla.com>
Thu, 25 Aug 2016 10:14:00 -0400
changeset 347860 295ba38f59c87aa0ece0a187e403e71adcf5c9b7
parent 347859 5eb305dd00ae7a6f949727e7a1c82d11e5310f21
child 347861 fa1adee0fb582bc6ae5542047d09b02a37e2a6f9
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan, ritu
bugs1295732
milestone50.0a2
Bug 1295732 - Allow system add-ons updates to override defaults per addon ID. r=aswan, a=ritu
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/internal/XPIProviderUtils.js
toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
toolkit/mozapps/extensions/test/xpcshell/test_system_update.js
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -3051,17 +3051,16 @@ this.XPIProvider = {
     if (setMatches(addonList, defaultAddons)) {
       logger.info("Resetting system add-ons.");
       systemAddonLocation.resetAddonSet();
       yield systemAddonLocation.cleanDirectories();
       return;
     }
 
     // Download all the add-ons
-    // Bug 1204158: If we already have some of these locally then just use those
     let downloadAddon = Task.async(function*(item) {
       try {
         let sourceAddon = updatedAddons.get(item.spec.id);
         if (sourceAddon && sourceAddon.version == item.spec.version) {
           // Copying the file to a temporary location has some benefits. If the
           // file is locked and cannot be read then we'll fall back to
           // downloading a fresh copy. It also means we don't have to remember
           // whether to delete the temporary copy later.
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -2029,24 +2029,20 @@ this.XPIDatabaseReconcile = {
       }
     }
 
     // Validate the updated system add-ons
     let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS];
     let addons = currentAddons.get(KEY_APP_SYSTEM_ADDONS) || new Map();
 
     let hideLocation;
-    if (systemAddonLocation.isActive() && systemAddonLocation.isValid(addons)) {
-      // Hide the system add-on defaults
-      logger.info("Hiding the default system add-ons.");
-      hideLocation = KEY_APP_SYSTEM_DEFAULTS;
-    }
-    else {
-      // Hide the system add-on updates
-      logger.info("Hiding the updated system add-ons.");
+
+    if (!systemAddonLocation.isValid(addons)) {
+      // Hide the system add-on updates if any are invalid.
+      logger.info("One or more updated system add-ons invalid, falling back to defaults.");
       hideLocation = KEY_APP_SYSTEM_ADDONS;
     }
 
     let previousVisible = this.getVisibleAddons(previousAddons);
     let currentVisible = this.flattenByID(currentAddons, hideLocation);
     let sawActiveTheme = false;
     XPIProvider.bootstrappedAddons = {};
 
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
@@ -1,15 +1,15 @@
 // Tests that we reset to the default system add-ons correctly when switching
 // application versions
 const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet";
 
 BootstrapMonitor.init();
 
-const featureDir = FileUtils.getDir("ProfD", ["features"]);
+const updatesDir = FileUtils.getDir("ProfD", ["features"]);
 
 // Build the test sets
 var dir = FileUtils.getDir("ProfD", ["sysfeatures", "app1"], true);
 do_get_file("data/system_addons/system1_1.xpi").copyTo(dir, "system1@tests.mozilla.org.xpi");
 do_get_file("data/system_addons/system2_1.xpi").copyTo(dir, "system2@tests.mozilla.org.xpi");
 
 dir = FileUtils.getDir("ProfD", ["sysfeatures", "app2"], true);
 do_get_file("data/system_addons/system1_2.xpi").copyTo(dir, "system1@tests.mozilla.org.xpi");
@@ -25,27 +25,34 @@ registerDirectory("XREAppFeat", distroDi
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "0");
 
 function makeUUID() {
   let uuidGen = AM_Cc["@mozilla.org/uuid-generator;1"].
                 getService(AM_Ci.nsIUUIDGenerator);
   return uuidGen.generateUUID().toString();
 }
 
-function* check_installed(inProfile, ...versions) {
-  let expectedDir = inProfile ? featureDir : distroDir;
-
-  for (let i = 0; i < versions.length; i++) {
+function* check_installed(conditions) {
+  for (let i = 0; i < conditions.length; i++) {
+    let condition = conditions[i];
     let id = "system" + (i + 1) + "@tests.mozilla.org";
     let addon = yield promiseAddonByID(id);
 
-    if (versions[i]) {
+    if (!("isUpgrade" in condition) || !("version" in condition)) {
+      throw Error("condition must contain isUpgrade and version");
+    }
+    let isUpgrade = conditions[i].isUpgrade;
+    let version = conditions[i].version;
+
+    let expectedDir = isUpgrade ? updatesDir : distroDir;
+
+    if (version) {
       // Add-on should be installed
       do_check_neq(addon, null);
-      do_check_eq(addon.version, versions[i]);
+      do_check_eq(addon.version, version);
       do_check_true(addon.isActive);
       do_check_false(addon.foreignInstall);
       do_check_false(hasFlag(addon.permissions, AddonManager.PERM_CAN_UPGRADE));
       do_check_false(hasFlag(addon.permissions, AddonManager.PERM_CAN_UNINSTALL));
       do_check_true(addon.hidden);
       do_check_true(addon.isSystem);
 
       // Verify the add-ons file is in the right place
@@ -53,25 +60,25 @@ function* check_installed(inProfile, ...
       file.append(id + ".xpi");
       do_check_true(file.exists());
       do_check_true(file.isFile());
 
       let uri = addon.getResourceURI(null);
       do_check_true(uri instanceof AM_Ci.nsIFileURL);
       do_check_eq(uri.file.path, file.path);
 
-      if (inProfile) {
+      if (isUpgrade) {
         do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SYSTEM);
       }
 
       // Verify the add-on actually started
-      BootstrapMonitor.checkAddonStarted(id, versions[i]);
+      BootstrapMonitor.checkAddonStarted(id, version);
     }
     else {
-      if (inProfile) {
+      if (isUpgrade) {
         // Add-on should not be installed
         do_check_eq(addon, null);
       }
       else {
         // Either add-on should not be installed or it shouldn't be active
         do_check_true(!addon || !addon.isActive);
       }
 
@@ -84,207 +91,285 @@ function* check_installed(inProfile, ...
     }
   }
 }
 
 // Test with a missing features directory
 add_task(function* test_missing_app_dir() {
   startupManager();
 
-  yield check_installed(false, null, null, null);
+  let conditions = [
+      { isUpgrade: false, version: null },
+      { isUpgrade: false, version: null },
+      { isUpgrade: false, version: null },
+  ];
 
-  do_check_false(featureDir.exists());
+  yield check_installed(conditions);
+
+  do_check_false(updatesDir.exists());
 
   yield promiseShutdownManager();
 });
 
 // Add some features in a new version
 add_task(function* test_new_version() {
   gAppInfo.version = "1";
   distroDir.leafName = "app1";
   startupManager();
 
-  yield check_installed(false, "1.0", "1.0", null);
+  let conditions = [
+      { isUpgrade: false, version: "1.0" },
+      { isUpgrade: false, version: "1.0" },
+      { isUpgrade: false, version: null },
+  ];
 
-  do_check_false(featureDir.exists());
+  yield check_installed(conditions);
+
+  do_check_false(updatesDir.exists());
 
   yield promiseShutdownManager();
 });
 
 // Another new version swaps one feature and upgrades another
 add_task(function* test_upgrade() {
   gAppInfo.version = "2";
   distroDir.leafName = "app2";
   startupManager();
 
-  yield check_installed(false, "2.0", null, "1.0");
+  let conditions = [
+      { isUpgrade: false, version: "2.0" },
+      { isUpgrade: false, version: null },
+      { isUpgrade: false, version: "1.0" },
+  ];
 
-  do_check_false(featureDir.exists());
+  yield check_installed(conditions);
+
+  do_check_false(updatesDir.exists());
 
   yield promiseShutdownManager();
 });
 
 // Downgrade
 add_task(function* test_downgrade() {
   gAppInfo.version = "1";
   distroDir.leafName = "app1";
   startupManager();
 
-  yield check_installed(false, "1.0", "1.0", null);
+  let conditions = [
+      { isUpgrade: false, version: "1.0" },
+      { isUpgrade: false, version: "1.0" },
+      { isUpgrade: false, version: null },
+  ];
 
-  do_check_false(featureDir.exists());
+  yield check_installed(conditions);
+
+  do_check_false(updatesDir.exists());
 
   yield promiseShutdownManager();
 });
 
 // Fake a mid-cycle install
 add_task(function* test_updated() {
   // Create a random dir to install into
   let dirname = makeUUID();
   FileUtils.getDir("ProfD", ["features", dirname], true);
-  featureDir.append(dirname);
+  updatesDir.append(dirname);
 
   // Copy in the system add-ons
-  let file = do_get_file("data/system_addons/system2_1.xpi");
-  file.copyTo(featureDir, "system2@tests.mozilla.org.xpi");
-  file = do_get_file("data/system_addons/system3_1.xpi");
-  file.copyTo(featureDir, "system3@tests.mozilla.org.xpi");
+    let file = do_get_file("data/system_addons/system2_2.xpi");
+  file.copyTo(updatesDir, "system2@tests.mozilla.org.xpi");
+  file = do_get_file("data/system_addons/system3_2.xpi");
+  file.copyTo(updatesDir, "system3@tests.mozilla.org.xpi");
 
   // Inject it into the system set
   let addonSet = {
     schema: 1,
-    directory: featureDir.leafName,
+    directory: updatesDir.leafName,
     addons: {
       "system2@tests.mozilla.org": {
-        version: "1.0"
+        version: "2.0"
       },
       "system3@tests.mozilla.org": {
-        version: "1.0"
+        version: "2.0"
       },
     }
   };
   Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, JSON.stringify(addonSet));
 
   startupManager(false);
 
-  yield check_installed(true, null, "1.0", "1.0");
+  let conditions = [
+      { isUpgrade: false, version: "1.0" },
+      { isUpgrade: true, version: "2.0" },
+      { isUpgrade: true, version: "2.0" },
+  ];
+
+  yield check_installed(conditions);
 
   yield promiseShutdownManager();
 });
 
 // Entering safe mode should disable the updated system add-ons and use the
 // default system add-ons
 add_task(function* safe_mode_disabled() {
   gAppInfo.inSafeMode = true;
   startupManager(false);
 
-  yield check_installed(false, "1.0", "1.0", null);
+  let conditions = [
+      { isUpgrade: false, version: "1.0" },
+      { isUpgrade: false, version: "1.0" },
+      { isUpgrade: false, version: null },
+  ];
+
+  yield check_installed(conditions);
 
   yield promiseShutdownManager();
 });
 
 // Leaving safe mode should re-enable the updated system add-ons
 add_task(function* normal_mode_enabled() {
   gAppInfo.inSafeMode = false;
   startupManager(false);
 
-  yield check_installed(true, null, "1.0", "1.0");
+  let conditions = [
+      { isUpgrade: false, version: "1.0" },
+      { isUpgrade: true, version: "2.0" },
+      { isUpgrade: true, version: "2.0" },
+  ];
+
+  yield check_installed(conditions);
 
   yield promiseShutdownManager();
 });
 
 // An additional add-on in the directory should be ignored
 add_task(function* test_skips_additional() {
   // Copy in the system add-ons
-  let file = do_get_file("data/system_addons/system1_1.xpi");
-  file.copyTo(featureDir, "system1@tests.mozilla.org.xpi");
+  let file = do_get_file("data/system_addons/system4_1.xpi");
+  file.copyTo(updatesDir, "system4@tests.mozilla.org.xpi");
 
   startupManager(false);
 
-  yield check_installed(true, null, "1.0", "1.0");
+  let conditions = [
+      { isUpgrade: false, version: "1.0" },
+      { isUpgrade: true, version: "2.0" },
+      { isUpgrade: true, version: "2.0" },
+  ];
+
+  yield check_installed(conditions);
 
   yield promiseShutdownManager();
 });
 
 // Missing add-on should revert to the default set
 add_task(function* test_revert() {
-  manuallyUninstall(featureDir, "system2@tests.mozilla.org");
+  manuallyUninstall(updatesDir, "system2@tests.mozilla.org");
 
   // With the add-on physically gone from disk we won't see uninstall events
   BootstrapMonitor.clear("system2@tests.mozilla.org");
 
   startupManager(false);
 
   // With system add-on 2 gone the updated set is now invalid so it reverts to
   // the default set which is system add-ons 1 and 2.
-  yield check_installed(false, "1.0", "1.0", null);
+  let conditions = [
+      { isUpgrade: false, version: "1.0" },
+      { isUpgrade: false, version: "1.0" },
+      { isUpgrade: false, version: null },
+  ];
+
+  yield check_installed(conditions);
 
   yield promiseShutdownManager();
 });
 
 // Putting it back will make the set work again
 add_task(function* test_reuse() {
-  let file = do_get_file("data/system_addons/system2_1.xpi");
-  file.copyTo(featureDir, "system2@tests.mozilla.org.xpi");
+  let file = do_get_file("data/system_addons/system2_2.xpi");
+  file.copyTo(updatesDir, "system2@tests.mozilla.org.xpi");
 
   startupManager(false);
 
-  yield check_installed(true, null, "1.0", "1.0");
+  let conditions = [
+      { isUpgrade: false, version: "1.0" },
+      { isUpgrade: true, version: "2.0" },
+      { isUpgrade: true, version: "2.0" },
+  ];
+
+  yield check_installed(conditions);
 
   yield promiseShutdownManager();
 });
 
 // Making the pref corrupt should revert to the default set
 add_task(function* test_corrupt_pref() {
   Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, "foo");
 
   startupManager(false);
 
-  yield check_installed(false, "1.0", "1.0", null);
+  let conditions = [
+      { isUpgrade: false, version: "1.0" },
+      { isUpgrade: false, version: "1.0" },
+      { isUpgrade: false, version: null },
+  ];
+
+  yield check_installed(conditions);
 
   yield promiseShutdownManager();
 });
 
 // An add-on with a bad certificate should cause us to use the default set
 add_task(function* test_bad_profile_cert() {
   let file = do_get_file("data/system_addons/system1_1_badcert.xpi");
-  file.copyTo(featureDir, "system1@tests.mozilla.org.xpi");
+  file.copyTo(updatesDir, "system1@tests.mozilla.org.xpi");
 
   // Inject it into the system set
   let addonSet = {
     schema: 1,
-    directory: featureDir.leafName,
+    directory: updatesDir.leafName,
     addons: {
       "system1@tests.mozilla.org": {
         version: "2.0"
       },
       "system2@tests.mozilla.org": {
         version: "1.0"
       },
       "system3@tests.mozilla.org": {
         version: "1.0"
       },
     }
   };
   Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, JSON.stringify(addonSet));
 
   startupManager(false);
 
-  yield check_installed(false, "1.0", "1.0", null);
+  let conditions = [
+      { isUpgrade: false, version: "1.0" },
+      { isUpgrade: false, version: "1.0" },
+      { isUpgrade: false, version: null },
+  ];
+
+  yield check_installed(conditions);
 
   yield promiseShutdownManager();
 });
 
 // Switching to app defaults that contain a bad certificate should still work
 add_task(function* test_bad_app_cert() {
   gAppInfo.version = "3";
   distroDir.leafName = "app3";
   startupManager();
 
   // Add-on will still be present
   let addon = yield promiseAddonByID("system1@tests.mozilla.org");
   do_check_neq(addon, null);
   do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_NOT_REQUIRED);
 
-  yield check_installed(false, "1.0", null, "1.0");
+  let conditions = [
+      { isUpgrade: false, version: "1.0" },
+      { isUpgrade: false, version: null },
+      { isUpgrade: false, version: "1.0" },
+  ];
+
+  yield check_installed(conditions);
 
   yield promiseShutdownManager();
 });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_update.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_update.js
@@ -5,35 +5,35 @@ const PREF_SYSTEM_ADDON_UPDATE_URL    = 
 const PREF_XPI_STATE                  = "extensions.xpiState";
 const PREF_APP_UPDATE_ENABLED         = "app.update.enabled";
 
 Components.utils.import("resource://testing-common/httpd.js");
 const { computeHash } = Components.utils.import("resource://gre/modules/addons/ProductAddonChecker.jsm");
 
 BootstrapMonitor.init();
 
-const featureDir = FileUtils.getDir("ProfD", ["features"], false);
+const updatesDir = FileUtils.getDir("ProfD", ["features"], false);
 
-function getCurrentFeatureDir() {
-  let dir = featureDir.clone();
+function getCurrentUpdatesDir() {
+  let dir = updatesDir.clone();
   let set = JSON.parse(Services.prefs.getCharPref(PREF_SYSTEM_ADDON_SET));
   dir.append(set.directory);
   return dir;
 }
 
-function clearFeatureDir() {
+function clearUpdatesDir() {
   // Delete any existing directories
-  if (featureDir.exists())
-    featureDir.remove(true);
+  if (updatesDir.exists())
+    updatesDir.remove(true);
 
   Services.prefs.clearUserPref(PREF_SYSTEM_ADDON_SET);
 }
 
-function buildPrefilledFeatureDir() {
-  clearFeatureDir();
+function buildPrefilledUpdatesDir() {
+  clearUpdatesDir();
 
   // Build the test set
   let dir = FileUtils.getDir("ProfD", ["features", "prefilled"], true);
 
   do_get_file("data/system_addons/system2_2.xpi").copyTo(dir, "system2@tests.mozilla.org.xpi");
   do_get_file("data/system_addons/system3_2.xpi").copyTo(dir, "system3@tests.mozilla.org.xpi");
 
   // Mark these in the past so the startup file scan notices when files have changed properly
@@ -141,62 +141,65 @@ function* build_xml(addons) {
     }
     xml += `  </addons>\n`;
   }
   xml += `</updates>\n`;
 
   return xml;
 }
 
-function* check_installed(inProfile, ...versions) {
-  let expectedDir = inProfile ? getCurrentFeatureDir() : distroDir;
-
-  for (let i = 0; i < versions.length; i++) {
+function* check_installed(conditions) {
+  for (let i = 0; i < conditions.length; i++) {
+    let condition = conditions[i];
     let id = "system" + (i + 1) + "@tests.mozilla.org";
     let addon = yield promiseAddonByID(id);
 
-    if (versions[i]) {
-      do_print(`Checking state of add-on ${id}, expecting version ${versions[i]}`);
+    if (!("isUpgrade" in condition) || !("version" in condition)) {
+      throw Error("condition must contain isUpgrade and version");
+    }
+    let isUpgrade = conditions[i].isUpgrade;
+    let version = conditions[i].version;
+
+    let expectedDir = isUpgrade ? getCurrentUpdatesDir() : distroDir;
+
+    if (version) {
+      do_print(`Checking state of add-on ${id}, expecting version ${version}`);
 
       // Add-on should be installed
       do_check_neq(addon, null);
-      do_check_eq(addon.version, versions[i]);
+      do_check_eq(addon.version, version);
       do_check_true(addon.isActive);
       do_check_false(addon.foreignInstall);
       do_check_true(addon.hidden);
       do_check_true(addon.isSystem);
 
       // Verify the add-ons file is in the right place
       let file = expectedDir.clone();
       file.append(id + ".xpi");
       do_check_true(file.exists());
       do_check_true(file.isFile());
 
       let uri = addon.getResourceURI(null);
       do_check_true(uri instanceof AM_Ci.nsIFileURL);
       do_check_eq(uri.file.path, file.path);
 
-      if (inProfile) {
+      if (isUpgrade) {
         do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SYSTEM);
       }
 
       // Verify the add-on actually started
-      BootstrapMonitor.checkAddonStarted(id, versions[i]);
+      BootstrapMonitor.checkAddonStarted(id, version);
     }
     else {
       do_print(`Checking state of add-on ${id}, expecting it to be missing`);
 
-      if (inProfile) {
+      if (isUpgrade) {
         // Add-on should not be installed
         do_check_eq(addon, null);
       }
-      else {
-        // Either add-on should not be installed or it shouldn't be active
-        do_check_true(!addon || !addon.isActive);
-      }
 
       BootstrapMonitor.checkAddonNotStarted(id);
 
       if (addon)
         BootstrapMonitor.checkAddonInstalled(id);
       else
         BootstrapMonitor.checkAddonNotInstalled(id);
     }
@@ -210,47 +213,70 @@ function* check_installed(inProfile, ...
  *
  * setup:        A task to setup the profile into the initial state.
  * initialState: The initial expected system add-on state after setup has run.
  */
 const TEST_CONDITIONS = {
   // Runs tests with no updated or default system add-ons initially installed
   blank: {
     setup: function*() {
-      clearFeatureDir();
+      clearUpdatesDir();
       distroDir.leafName = "empty";
     },
-    initialState: [false, null, null, null, null, null],
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ],
   },
-
   // Runs tests with default system add-ons installed
   withAppSet: {
     setup: function*() {
-      clearFeatureDir();
+      clearUpdatesDir();
       distroDir.leafName = "prefilled";
     },
-    initialState: [false, null, "2.0", "2.0", null, null],
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: "2.0"},
+      { isUpgrade: false, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
   },
 
   // Runs tests with updated system add-ons installed
   withProfileSet: {
     setup: function*() {
-      buildPrefilledFeatureDir();
+      buildPrefilledUpdatesDir();
       distroDir.leafName = "empty";
     },
-    initialState: [true, null, "2.0", "2.0", null, null],
+    initialState: [
+      { isUpgrade: false, version: null},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
   },
 
   // Runs tests with both default and updated system add-ons installed
   withBothSets: {
     setup: function*() {
-      buildPrefilledFeatureDir();
+      buildPrefilledUpdatesDir();
       distroDir.leafName = "hidden";
     },
-    initialState: [true, null, "2.0", "2.0", null, null],
+    initialState: [
+      { isUpgrade: false, version: "1.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: true, version: "2.0"},
+      { isUpgrade: false, version: null},
+      { isUpgrade: false, version: null}
+    ]
   },
 };
 
 
 /**
  * The tests to run. Each test must define an updateList or test. The following
  * properties are used:
  *
@@ -276,49 +302,165 @@ const TESTS = {
     },
   },
 
   // Test that a blank response does nothing
   blank: {
     updateList: null,
   },
 
-  // Test that an empty list updates to an empty set of system add-ons
+  // Test that an empty list removes existing updates, leaving defaults.
   empty: {
     updateList: [],
-    finalState: [true, null, null, null, null, null]
+    finalState: {
+      blank: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null}
+      ],
+      withAppSet: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: "2.0"},
+        { isUpgrade: false, version: "2.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null}
+      ],
+      withProfileSet: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null}
+      ],
+      withBothSets: [
+        { isUpgrade: false, version: "1.0"},
+        { isUpgrade: false, version: "1.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        // Set this to `true` to so `verify_state()` expects a blank profile dir
+        { isUpgrade: true, version: null}
+      ]
+    },
   },
-
   // Tests that a new set of system add-ons gets installed
   newset: {
     updateList: [
       { id: "system4@tests.mozilla.org", version: "1.0", path: "system4_1.xpi" },
       { id: "system5@tests.mozilla.org", version: "1.0", path: "system5_1.xpi" }
     ],
-    finalState: [true, null, null, null, "1.0", "1.0"]
+    finalState: {
+      blank: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "1.0"},
+        { isUpgrade: true, version: "1.0"}
+      ],
+      withAppSet: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: "2.0"},
+        { isUpgrade: false, version: "2.0"},
+        { isUpgrade: true, version: "1.0"},
+        { isUpgrade: true, version: "1.0"}
+      ],
+      withProfileSet: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "1.0"},
+        { isUpgrade: true, version: "1.0"}
+      ],
+      withBothSets: [
+        { isUpgrade: false, version: "1.0"},
+        { isUpgrade: false, version: "1.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "1.0"},
+        { isUpgrade: true, version: "1.0"}
+      ]
+    }
   },
 
   // Tests that an upgraded set of system add-ons gets installed
   upgrades: {
     updateList: [
       { id: "system2@tests.mozilla.org", version: "3.0", path: "system2_3.xpi" },
       { id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi" }
     ],
-    finalState: [true, null, "3.0", "3.0", null, null]
+    finalState: {
+      blank: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null}
+      ],
+      withAppSet: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null}
+      ],
+      withProfileSet: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null}
+      ],
+      withBothSets: [
+        { isUpgrade: false, version: "1.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: false, version: null}
+      ]
+    }
   },
 
   // Tests that a set of system add-ons, some new, some existing gets installed
   overlapping: {
     updateList: [
-      { id: "system1@tests.mozilla.org", version: "1.0", path: "system1_2.xpi" },
-      { id: "system2@tests.mozilla.org", version: "1.0", path: "system2_2.xpi" },
-      { id: "system3@tests.mozilla.org", version: "1.0", path: "system3_3.xpi" },
+      { id: "system1@tests.mozilla.org", version: "2.0", path: "system1_2.xpi" },
+      { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
+      { id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi" },
       { id: "system4@tests.mozilla.org", version: "1.0", path: "system4_1.xpi" }
     ],
-    finalState: [true, "2.0", "2.0", "3.0", "1.0", null]
+    finalState: {
+      blank: [
+        { isUpgrade: true, version: "2.0"},
+        { isUpgrade: true, version: "2.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "1.0"},
+        { isUpgrade: false, version: null}
+      ],
+      withAppSet: [
+        { isUpgrade: true, version: "2.0"},
+        { isUpgrade: true, version: "2.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "1.0"},
+        { isUpgrade: false, version: null}
+      ],
+      withProfileSet: [
+        { isUpgrade: true, version: "2.0"},
+        { isUpgrade: true, version: "2.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "1.0"},
+        { isUpgrade: false, version: null}
+      ],
+      withBothSets: [
+        { isUpgrade: true, version: "2.0"},
+        { isUpgrade: true, version: "2.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "1.0"},
+        { isUpgrade: false, version: null}
+      ]
+    }
   },
 
   // Specifying an incorrect version should stop us updating anything
   badVersion: {
     fails: true,
     updateList: [
       { id: "system2@tests.mozilla.org", version: "4.0", path: "system2_3.xpi" },
       { id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi" }
@@ -345,17 +487,46 @@ const TESTS = {
 
   // Correct sizes and hashes should work
   checkSizeHash: {
     updateList: [
       { id: "system2@tests.mozilla.org", version: "3.0", path: "system2_3.xpi", size: 4672 },
       { id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi", hashFunction: "sha1", hashValue: "2df604b37b13766c0e04f1b7f59800e038f46cd5" },
       { id: "system5@tests.mozilla.org", version: "1.0", path: "system5_1.xpi", size: 4671, hashFunction: "sha1", hashValue: "f13dcaa8bfacaa222189bcbb0074972c05ceb621" }
     ],
-    finalState: [true, null, "3.0", "3.0", null, "1.0"]
+    finalState: {
+      blank: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "1.0"}
+      ],
+      withAppSet: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "1.0"}
+      ],
+      withProfileSet: [
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "1.0"}
+      ],
+      withBothSets: [
+        { isUpgrade: false, version: "1.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: true, version: "3.0"},
+        { isUpgrade: false, version: null},
+        { isUpgrade: true, version: "1.0"}
+      ]
+    }
   },
 
   // A bad certificate should stop updates
   badCert: {
     fails: true,
     updateList: [
       { id: "system1@tests.mozilla.org", version: "1.0", path: "system1_1_badcert.xpi" },
       { id: "system3@tests.mozilla.org", version: "1.0", path: "system3_1.xpi" }
@@ -367,18 +538,18 @@ add_task(function* setup() {
   // Initialise the profile
   startupManager();
   yield promiseShutdownManager();
 })
 
 function* get_directories() {
   let subdirs = [];
 
-  if (yield OS.File.exists(featureDir.path)) {
-    let iterator = new OS.File.DirectoryIterator(featureDir.path);
+  if (yield OS.File.exists(updatesDir.path)) {
+    let iterator = new OS.File.DirectoryIterator(updatesDir.path);
     yield iterator.forEach(entry => {
       if (entry.isDir) {
         subdirs.push(entry);
       }
     });
     iterator.close();
   }
 
@@ -394,50 +565,54 @@ function* setup_conditions(setup) {
 
   do_print("Setting up conditions.");
   yield setup.setup();
 
   startupManager(false);
 
   // Make sure the initial state is correct
   do_print("Checking initial state.");
-  yield check_installed(...setup.initialState);
+  yield check_installed(setup.initialState);
 }
 
 function* verify_state(initialState, finalState = undefined) {
   let expectedDirs = 0;
 
   // If the initial state was using the profile set then that directory will
   // still exist.
-  if (initialState[0])
+
+  if (initialState.some(a => a.isUpgrade)) {
     expectedDirs++;
+  }
 
   if (finalState == undefined) {
     finalState = initialState;
   }
-  else {
+  else if (finalState.some(a => a.isUpgrade)) {
     // If the new state is using the profile then that directory will exist.
-    if (finalState[0])
-      expectedDirs++;
+    expectedDirs++;
   }
 
   do_print("Checking final state.");
 
   let dirs = yield get_directories();
   do_check_eq(dirs.length, expectedDirs);
 
   // Bug 1204156: Currently switching to the new state requires a restart
   // yield check_installed(...finalState);
 
   // Check that the new state is active after a restart
   yield promiseRestartManager();
-  yield check_installed(...finalState);
+  yield check_installed(finalState);
 }
 
-function* exec_test(setup, test) {
+function* exec_test(setupName, testName) {
+  let setup = TEST_CONDITIONS[setupName];
+  let test = TESTS[testName];
+
   yield setup_conditions(setup);
 
   try {
     if ("test" in test) {
       yield test.test();
     }
     else {
       yield install_system_addons(yield build_xml(test.updateList));
@@ -448,45 +623,56 @@ function* exec_test(setup, test) {
     }
   }
   catch (e) {
     if (!test.fails) {
       do_throw(e);
     }
   }
 
-  yield verify_state(setup.initialState, test.finalState);
+  // some tests have a different expected combination of default
+  // and updated add-ons.
+  if (test.finalState && setupName in test.finalState) {
+    yield verify_state(setup.initialState, test.finalState[setupName]);
+  }
+  else {
+    yield verify_state(setup.initialState, test.finalState);
+  }
 
   yield promiseShutdownManager();
 }
 
-for (let setup of Object.keys(TEST_CONDITIONS)) {
-  for (let test of Object.keys(TESTS)) {
-    add_task(function*() {
-      do_print("Running test " + setup + " " + test);
+add_task(function*() {
+  for (let setup of Object.keys(TEST_CONDITIONS)) {
+    for (let test of Object.keys(TESTS)) {
+        do_print("Running test " + setup + " " + test);
 
-      yield exec_test(TEST_CONDITIONS[setup], TESTS[test]);
-    });
+        yield exec_test(setup, test);
+    }
   }
-}
+});
 
 // Some custom tests
-
 // Test that the update check is performed as part of the regular add-on update
 // check
 add_task(function* test_addon_update() {
   yield setup_conditions(TEST_CONDITIONS.blank);
 
   yield update_all_addons(yield build_xml([
     { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
     { id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" }
   ]));
 
-  yield verify_state(TEST_CONDITIONS.blank.initialState,
-                     [true, null, "2.0", "2.0", null, null]);
+  yield verify_state(TEST_CONDITIONS.blank.initialState, [
+    {isUpgrade: false, version: null},
+    {isUpgrade: true, version: "2.0"},
+    {isUpgrade: true, version: "2.0"},
+    {isUpgrade: false, version: null},
+    {isUpgrade: false, version: null}
+  ]);
 
   yield promiseShutdownManager();
 });
 
 // Disabling app updates should block system add-on updates
 add_task(function* test_app_update_disabled() {
   yield setup_conditions(TEST_CONDITIONS.blank);
 
@@ -543,18 +729,23 @@ add_task(function* test_match_default_re
 
   yield install_system_addons(yield build_xml([
     { id: "system1@tests.mozilla.org", version: "1.0", path: "system1_1.xpi" },
     { id: "system2@tests.mozilla.org", version: "1.0", path: "system2_1.xpi" }
   ]));
 
   // This should revert to the default set instead of installing new versions
   // into an updated set.
-  yield verify_state(TEST_CONDITIONS.withBothSets.initialState,
-                     [false, "1.0", "1.0", null, null, null]);
+  yield verify_state(TEST_CONDITIONS.withBothSets.initialState, [
+    {isUpgrade: false, version: "1.0"},
+    {isUpgrade: false, version: "1.0"},
+    {isUpgrade: false, version: null},
+    {isUpgrade: false, version: null},
+    {isUpgrade: false, version: null}
+  ]);
 
   yield promiseShutdownManager();
 });
 
 // Tests that a set that matches the current set works
 add_task(function* test_match_current() {
   yield setup_conditions(TEST_CONDITIONS.withBothSets);
 
@@ -577,18 +768,23 @@ add_task(function* test_no_download() {
   yield setup_conditions(TEST_CONDITIONS.withBothSets);
 
   // The missing file here is unneeded since there is a local version already
   yield install_system_addons(yield build_xml([
     { id: "system2@tests.mozilla.org", version: "2.0", path: "missing.xpi" },
     { id: "system4@tests.mozilla.org", version: "1.0", path: "system4_1.xpi" }
   ]));
 
-  yield verify_state(TEST_CONDITIONS.withBothSets.initialState,
-                     [true, null, "2.0", null, "1.0", null]);
+  yield verify_state(TEST_CONDITIONS.withBothSets.initialState, [
+    {isUpgrade: false, version: "1.0"},
+    {isUpgrade: true, version: "2.0"},
+    {isUpgrade: false, version: null},
+    {isUpgrade: true, version: "1.0"},
+    {isUpgrade: false, version: null}
+  ]);
 
   yield promiseShutdownManager();
 });
 
 // Tests that a second update before a restart works
 add_task(function* test_double_update() {
   yield setup_conditions(TEST_CONDITIONS.withAppSet);
 
@@ -597,34 +793,43 @@ add_task(function* test_double_update() 
     { id: "system3@tests.mozilla.org", version: "1.0", path: "system3_1.xpi" }
   ]));
 
   yield install_system_addons(yield build_xml([
     { id: "system3@tests.mozilla.org", version: "2.0", path: "system3_2.xpi" },
     { id: "system4@tests.mozilla.org", version: "1.0", path: "system4_1.xpi" }
   ]));
 
-  yield verify_state(TEST_CONDITIONS.withAppSet.initialState,
-                     [true, null, null, "2.0", "1.0", null]);
+  yield verify_state(TEST_CONDITIONS.withAppSet.initialState, [
+    {isUpgrade: false, version: null},
+    {isUpgrade: false, version: "2.0"},
+    {isUpgrade: true, version: "2.0"},
+    {isUpgrade: true, version: "1.0"},
+    {isUpgrade: false, version: null}
+  ]);
 
   yield promiseShutdownManager();
 });
 
 // A second update after a restart will delete the original unused set
 add_task(function* test_update_purges() {
   yield setup_conditions(TEST_CONDITIONS.withBothSets);
 
   yield install_system_addons(yield build_xml([
     { id: "system2@tests.mozilla.org", version: "2.0", path: "system2_2.xpi" },
     { id: "system3@tests.mozilla.org", version: "1.0", path: "system3_1.xpi" }
   ]));
 
-  yield verify_state(TEST_CONDITIONS.withBothSets.initialState,
-                     [true, null, "2.0", "1.0", null, null]);
+  yield verify_state(TEST_CONDITIONS.withBothSets.initialState, [
+    {isUpgrade: false, version: "1.0"},
+    {isUpgrade: true, version: "2.0"},
+    {isUpgrade: true, version: "1.0"},
+    {isUpgrade: false, version: null},
+    {isUpgrade: false, version: null}
+  ]);
 
   yield install_system_addons(yield build_xml(null));
 
   let dirs = yield get_directories();
   do_check_eq(dirs.length, 1);
 
   yield promiseShutdownManager();
 });
-