summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/update/tests/unit_aus_update
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/update/tests/unit_aus_update')
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/ausReadStrings.js33
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/backgroundUpdateTaskInternalUpdater.js85
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/canCheckForAndCanApplyUpdates.js62
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForDifferentChannel.js60
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForOlderAppVersion.js58
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForSameVersionAndBuildID.js59
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingIncorrectStatus.js57
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupPendingVersionFileIncorrectStatus.js57
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js61
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js63
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesBackgroundTask.js48
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesNonBackgroundTask.js41
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedNoRecovery.js23
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedOffline.js21
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js26
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js38
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/ensureExperimentToRolloutTransitionPerformed.js111
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/head_update.js8
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/languagePackUpdates.js291
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js396
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/onlyDownloadUpdatesThisSession.js69
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/perInstallationPrefs.js238
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js327
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/testConstants.js8
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/updateAutoPrefMigrate.js74
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/updateCheckCombine.js38
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/updateDirectoryMigrate.js246
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js593
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/updateSyncManager.js105
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js26
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/verifyChannelPrefsFile.js38
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini76
32 files changed, 3436 insertions, 0 deletions
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/ausReadStrings.js b/toolkit/mozapps/update/tests/unit_aus_update/ausReadStrings.js
new file mode 100644
index 0000000000..ed331f5ebc
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/ausReadStrings.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const BIN_DIR =
+ AppConstants.platform == "win" ? "test_bug473417-รณ" : "test_bug473417";
+const BIN_EXE = "TestAUSReadStrings" + mozinfo.bin_suffix;
+const tempdir = do_get_tempdir();
+
+function run_test() {
+ let workdir = tempdir.clone();
+ workdir.append(BIN_DIR);
+
+ let paths = [
+ BIN_EXE,
+ "TestAUSReadStrings1.ini",
+ "TestAUSReadStrings2.ini",
+ "TestAUSReadStrings3.ini",
+ "TestAUSReadStrings4.ini",
+ ];
+ for (let i = 0; i < paths.length; i++) {
+ let file = do_get_file("../data/" + paths[i]);
+ file.copyTo(workdir, null);
+ }
+
+ let readStrings = workdir.clone();
+ readStrings.append(BIN_EXE);
+
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(readStrings);
+ process.run(true, [], 0);
+ Assert.equal(process.exitValue, 0);
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/backgroundUpdateTaskInternalUpdater.js b/toolkit/mozapps/update/tests/unit_aus_update/backgroundUpdateTaskInternalUpdater.js
new file mode 100644
index 0000000000..329b8edbea
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/backgroundUpdateTaskInternalUpdater.js
@@ -0,0 +1,85 @@
+/* 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";
+
+/**
+ * This test ensures that we don't resume an update download with the internal
+ * downloader when we are running background updates. Normally, the background
+ * update task won't even run if we can't use BITS. But it is possible for us to
+ * fall back from BITS to the internal downloader. Background update should
+ * prevent this fallback and just abort.
+ *
+ * But interactive Firefox allows that fallback. And once the internal
+ * download has started, the background update task must leave that download
+ * untouched and allow it to finish.
+ */
+
+var TEST_MAR_CONTENTS = "Arbitrary MAR contents";
+
+add_task(async function setup() {
+ setupTestCommon();
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ setUpdateChannel("test_channel");
+
+ // Pretend that this is a background task.
+ const bts = Cc["@mozilla.org/backgroundtasks;1"].getService(
+ Ci.nsIBackgroundTasks
+ );
+ bts.overrideBackgroundTaskNameForTesting("test-task");
+
+ // No need for cleanup needed for changing update files. These will be cleaned
+ // up by removeUpdateFiles.
+ const downloadingMarFile = getUpdateDirFile(FILE_UPDATE_MAR, DIR_DOWNLOADING);
+ await IOUtils.writeUTF8(downloadingMarFile.path, TEST_MAR_CONTENTS);
+
+ writeStatusFile(STATE_DOWNLOADING);
+
+ let patchProps = {
+ state: STATE_DOWNLOADING,
+ bitsResult: Cr.NS_ERROR_FAILURE,
+ };
+ let patches = getLocalPatchString(patchProps);
+ let updateProps = { appVersion: "1.0" };
+ let updates = getLocalUpdateString(updateProps, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+});
+
+add_task(async function backgroundUpdate() {
+ let patches = getRemotePatchString({});
+ let updateString = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updateString);
+
+ let { updates } = await waitForUpdateCheck(true);
+ let bestUpdate = gAUS.selectUpdate(updates);
+ let success = await gAUS.downloadUpdate(bestUpdate, false);
+ Assert.equal(
+ success,
+ false,
+ "We should not attempt to download an update in the background when an " +
+ "internal update download is already in progress."
+ );
+ Assert.equal(
+ readStatusFile(),
+ STATE_DOWNLOADING,
+ "Background update during an internally downloading update should not " +
+ "change update status"
+ );
+ const downloadingMarFile = getUpdateDirFile(FILE_UPDATE_MAR, DIR_DOWNLOADING);
+ Assert.ok(
+ await IOUtils.exists(downloadingMarFile.path),
+ "Downloading MAR should still exist"
+ );
+ Assert.equal(
+ await IOUtils.readUTF8(downloadingMarFile.path),
+ TEST_MAR_CONTENTS,
+ "Downloading MAR should not have been modified"
+ );
+});
+
+add_task(async function finish() {
+ stop_httpserver(doTestFinish);
+});
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/canCheckForAndCanApplyUpdates.js b/toolkit/mozapps/update/tests/unit_aus_update/canCheckForAndCanApplyUpdates.js
new file mode 100644
index 0000000000..b3dcb72d14
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/canCheckForAndCanApplyUpdates.js
@@ -0,0 +1,62 @@
+/* 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/.
+ */
+
+function run_test() {
+ setupTestCommon();
+
+ // Verify write access to the custom app dir
+ debugDump("testing write access to the application directory");
+ let testFile = getCurrentProcessDir();
+ testFile.append("update_write_access_test");
+ testFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
+ Assert.ok(testFile.exists(), MSG_SHOULD_EXIST);
+ testFile.remove(false);
+ Assert.ok(!testFile.exists(), MSG_SHOULD_NOT_EXIST);
+
+ if (AppConstants.platform == "win") {
+ // Create a mutex to prevent being able to check for or apply updates.
+ debugDump("attempting to create mutex");
+ let handle = createMutex(getPerInstallationMutexName());
+ Assert.ok(!!handle, "the update mutex should have been created");
+
+ // Check if available updates cannot be checked for when there is a mutex
+ // for this installation.
+ Assert.ok(
+ !gAUS.canCheckForUpdates,
+ "should not be able to check for " +
+ "updates when there is an update mutex"
+ );
+
+ // Check if updates cannot be applied when there is a mutex for this
+ // installation.
+ Assert.ok(
+ !gAUS.canApplyUpdates,
+ "should not be able to apply updates when there is an update mutex"
+ );
+
+ debugDump("destroying mutex");
+ closeHandle(handle);
+ }
+
+ // Check if available updates can be checked for
+ Assert.ok(gAUS.canCheckForUpdates, "should be able to check for updates");
+ // Check if updates can be applied
+ Assert.ok(gAUS.canApplyUpdates, "should be able to apply updates");
+
+ if (AppConstants.platform == "win") {
+ // Attempt to create a mutex when application update has already created one
+ // with the same name.
+ debugDump("attempting to create mutex");
+ let handle = createMutex(getPerInstallationMutexName());
+
+ Assert.ok(
+ !handle,
+ "should not be able to create the update mutex when " +
+ "the application has created the update mutex"
+ );
+ }
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForDifferentChannel.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForDifferentChannel.js
new file mode 100644
index 0000000000..33df63e7e7
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForDifferentChannel.js
@@ -0,0 +1,60 @@
+/* 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/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+
+ debugDump(
+ "testing removal of an active update for a channel that is not " +
+ "valid due to switching channels (Bug 486275)."
+ );
+
+ let patchProps = { state: STATE_DOWNLOADING };
+ let patches = getLocalPatchString(patchProps);
+ let updateProps = { appVersion: "1.0" };
+ let updates = getLocalUpdateString(updateProps, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_DOWNLOADING);
+
+ setUpdateChannel("original_channel");
+
+ standardInit();
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 1,
+ "the update manager update count" + MSG_SHOULD_EQUAL
+ );
+ let update = gUpdateManager.getUpdateAt(0);
+ Assert.equal(
+ update.state,
+ STATE_FAILED,
+ "the first update state" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.errorCode,
+ ERR_CHANNEL_CHANGE,
+ "the first update errorCode" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.statusText,
+ getString("statusFailed"),
+ "the first update statusText " + MSG_SHOULD_EQUAL
+ );
+ await waitForUpdateXMLFiles();
+
+ let dir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ let statusFile = getUpdateDirFile(FILE_UPDATE_STATUS);
+ Assert.ok(!statusFile.exists(), MSG_SHOULD_NOT_EXIST);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForOlderAppVersion.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForOlderAppVersion.js
new file mode 100644
index 0000000000..3c9f7d1c2e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForOlderAppVersion.js
@@ -0,0 +1,58 @@
+/* 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/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+
+ debugDump(
+ "testing cleanup of an update download in progress for an " +
+ "older version of the application on startup (Bug 485624)"
+ );
+
+ let patchProps = { state: STATE_DOWNLOADING };
+ let patches = getLocalPatchString(patchProps);
+ let updateProps = { appVersion: "0.9" };
+ let updates = getLocalUpdateString(updateProps, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_DOWNLOADING);
+
+ standardInit();
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 1,
+ "the update manager update count" + MSG_SHOULD_EQUAL
+ );
+ let update = gUpdateManager.getUpdateAt(0);
+ Assert.equal(
+ update.state,
+ STATE_FAILED,
+ "the first update state" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.errorCode,
+ ERR_OLDER_VERSION_OR_SAME_BUILD,
+ "the first update errorCode" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.statusText,
+ getString("statusFailed"),
+ "the first update statusText " + MSG_SHOULD_EQUAL
+ );
+ await waitForUpdateXMLFiles();
+
+ let dir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ let statusFile = getUpdateDirFile(FILE_UPDATE_STATUS);
+ Assert.ok(!statusFile.exists(), MSG_SHOULD_NOT_EXIST);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForSameVersionAndBuildID.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForSameVersionAndBuildID.js
new file mode 100644
index 0000000000..0eb1b6c22e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForSameVersionAndBuildID.js
@@ -0,0 +1,59 @@
+/* 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/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+
+ debugDump(
+ "testing removal of an update download in progress for the " +
+ "same version of the application with the same application " +
+ "build id on startup (Bug 536547)"
+ );
+
+ let patchProps = { state: STATE_DOWNLOADING };
+ let patches = getLocalPatchString(patchProps);
+ let updateProps = { appVersion: "1.0", buildID: "2007010101" };
+ let updates = getLocalUpdateString(updateProps, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_DOWNLOADING);
+
+ standardInit();
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 1,
+ "the update manager update count" + MSG_SHOULD_EQUAL
+ );
+ let update = gUpdateManager.getUpdateAt(0);
+ Assert.equal(
+ update.state,
+ STATE_FAILED,
+ "the first update state" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.errorCode,
+ ERR_OLDER_VERSION_OR_SAME_BUILD,
+ "the first update errorCode" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.statusText,
+ getString("statusFailed"),
+ "the first update statusText " + MSG_SHOULD_EQUAL
+ );
+ await waitForUpdateXMLFiles();
+
+ let dir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ let statusFile = getUpdateDirFile(FILE_UPDATE_STATUS);
+ Assert.ok(!statusFile.exists(), MSG_SHOULD_NOT_EXIST);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingIncorrectStatus.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingIncorrectStatus.js
new file mode 100644
index 0000000000..8468ca453c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingIncorrectStatus.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+async function run_test() {
+ setupTestCommon();
+
+ debugDump(
+ "testing update cleanup when reading the status file returns " +
+ "STATUS_NONE and the update xml has an update with " +
+ "STATE_DOWNLOADING (Bug 539717)."
+ );
+
+ let patchProps = { state: STATE_DOWNLOADING };
+ let patches = getLocalPatchString(patchProps);
+ let updates = getLocalUpdateString({}, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_NONE);
+
+ standardInit();
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 1,
+ "the update manager update count" + MSG_SHOULD_EQUAL
+ );
+ let update = gUpdateManager.getUpdateAt(0);
+ Assert.equal(
+ update.state,
+ STATE_FAILED,
+ "the first update state" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.errorCode,
+ ERR_UPDATE_STATE_NONE,
+ "the first update errorCode" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.statusText,
+ getString("statusFailed"),
+ "the first update statusText " + MSG_SHOULD_EQUAL
+ );
+ await waitForUpdateXMLFiles();
+
+ let dir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ let statusFile = getUpdateDirFile(FILE_UPDATE_STATUS);
+ Assert.ok(!statusFile.exists(), MSG_SHOULD_NOT_EXIST);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupPendingVersionFileIncorrectStatus.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupPendingVersionFileIncorrectStatus.js
new file mode 100644
index 0000000000..274f029150
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupPendingVersionFileIncorrectStatus.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+async function run_test() {
+ setupTestCommon();
+
+ debugDump(
+ "testing update cleanup when reading the status file returns " +
+ "STATUS_NONE, the version file is for a newer version, and the " +
+ "update xml has an update with STATE_PENDING (Bug 601701)."
+ );
+
+ let patchProps = { state: STATE_PENDING };
+ let patches = getLocalPatchString(patchProps);
+ let updates = getLocalUpdateString({}, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeVersionFile("99.9");
+
+ // Check that there are no active updates first so the updates directory is
+ // cleaned up by the UpdateManager before the remaining tests.
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 1,
+ "the update manager update count" + MSG_SHOULD_EQUAL
+ );
+ let update = gUpdateManager.getUpdateAt(0);
+ Assert.equal(
+ update.state,
+ STATE_FAILED,
+ "the first update state" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.errorCode,
+ ERR_UPDATE_STATE_NONE,
+ "the first update errorCode" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.statusText,
+ getString("statusFailed"),
+ "the first update statusText " + MSG_SHOULD_EQUAL
+ );
+ await waitForUpdateXMLFiles();
+
+ let dir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ let versionFile = getUpdateDirFile(FILE_UPDATE_VERSION);
+ Assert.ok(!versionFile.exists(), MSG_SHOULD_NOT_EXIST);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js
new file mode 100644
index 0000000000..3a36693dca
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js
@@ -0,0 +1,61 @@
+/* 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/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+
+ debugDump("testing that the update.log is moved after a successful update");
+
+ Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS, 5);
+
+ let patchProps = { state: STATE_PENDING };
+ let patches = getLocalPatchString(patchProps);
+ let updates = getLocalUpdateString({}, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_SUCCEEDED);
+
+ let log = getUpdateDirFile(FILE_UPDATE_LOG);
+ writeFile(log, "Last Update Log");
+
+ standardInit();
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 1,
+ "the update manager update count" + MSG_SHOULD_EQUAL
+ );
+ await waitForUpdateXMLFiles();
+
+ let cancelations = Services.prefs.getIntPref(PREF_APP_UPDATE_CANCELATIONS, 0);
+ Assert.equal(
+ cancelations,
+ 0,
+ "the " + PREF_APP_UPDATE_CANCELATIONS + " preference " + MSG_SHOULD_EQUAL
+ );
+
+ log = getUpdateDirFile(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST);
+
+ log = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(
+ readFile(log),
+ "Last Update Log",
+ "the last update log contents" + MSG_SHOULD_EQUAL
+ );
+
+ log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST);
+
+ let dir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js
new file mode 100644
index 0000000000..9caceccc99
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js
@@ -0,0 +1,63 @@
+/* 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/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+
+ debugDump("testing update logs are first in first out deleted");
+
+ let patchProps = { state: STATE_PENDING };
+ let patches = getLocalPatchString(patchProps);
+ let updates = getLocalUpdateString({}, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_SUCCEEDED);
+
+ let log = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ writeFile(log, "Backup Update Log");
+
+ log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG);
+ writeFile(log, "To Be Deleted Backup Update Log");
+
+ log = getUpdateDirFile(FILE_UPDATE_LOG);
+ writeFile(log, "Last Update Log");
+
+ standardInit();
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 1,
+ "the update manager update count" + MSG_SHOULD_EQUAL
+ );
+ await waitForUpdateXMLFiles();
+
+ log = getUpdateDirFile(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST);
+
+ log = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(
+ readFile(log),
+ "Last Update Log",
+ "the last update log contents" + MSG_SHOULD_EQUAL
+ );
+
+ log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(
+ readFile(log),
+ "Backup Update Log",
+ "the backup update log contents" + MSG_SHOULD_EQUAL
+ );
+
+ let dir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesBackgroundTask.js b/toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesBackgroundTask.js
new file mode 100644
index 0000000000..4dcf559563
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesBackgroundTask.js
@@ -0,0 +1,48 @@
+/* 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";
+
+/**
+ * This test verifies that when Balrog advertises that an update should not
+ * be downloaded in the background, it is not.
+ */
+
+function setup() {
+ setupTestCommon();
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ setUpdateChannel("test_channel");
+
+ // Pretend that this is a background task.
+ const bts = Cc["@mozilla.org/backgroundtasks;1"].getService(
+ Ci.nsIBackgroundTasks
+ );
+ bts.overrideBackgroundTaskNameForTesting("test-task");
+}
+setup();
+
+add_task(async function disableBackgroundUpdatesBackgroundTask() {
+ let patches = getRemotePatchString({});
+ let updateString = getRemoteUpdateString(
+ { disableBackgroundUpdates: "true" },
+ patches
+ );
+ gResponseBody = getRemoteUpdatesXMLString(updateString);
+
+ let { updates } = await waitForUpdateCheck(true);
+ let bestUpdate = gAUS.selectUpdate(updates);
+ let success = await gAUS.downloadUpdate(bestUpdate, false);
+ Assert.equal(
+ success,
+ false,
+ "Update should not download when disableBackgroundUpdates is specified " +
+ "and we are in background task mode."
+ );
+});
+
+add_task(async function finish() {
+ stop_httpserver(doTestFinish);
+});
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesNonBackgroundTask.js b/toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesNonBackgroundTask.js
new file mode 100644
index 0000000000..2f4eec25ff
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesNonBackgroundTask.js
@@ -0,0 +1,41 @@
+/* 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";
+
+/**
+ * This test verifies that when Balrog advertises that an update should not
+ * be downloaded in the background, but we are not running in the background,
+ * the advertisement does not have any effect.
+ */
+
+function setup() {
+ setupTestCommon();
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ setUpdateChannel("test_channel");
+}
+setup();
+
+add_task(async function disableBackgroundUpdatesBackgroundTask() {
+ let patches = getRemotePatchString({});
+ let updateString = getRemoteUpdateString(
+ { disableBackgroundUpdates: "true" },
+ patches
+ );
+ gResponseBody = getRemoteUpdatesXMLString(updateString);
+
+ let { updates } = await waitForUpdateCheck(true);
+
+ initMockIncrementalDownload();
+ gIncrementalDownloadErrorType = 3;
+
+ // This will assert that the download completes successfully.
+ await waitForUpdateDownload(updates, Cr.NS_OK);
+});
+
+add_task(async function finish() {
+ stop_httpserver(doTestFinish);
+});
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedNoRecovery.js b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedNoRecovery.js
new file mode 100644
index 0000000000..15dd39ce0a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedNoRecovery.js
@@ -0,0 +1,23 @@
+/* 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/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+ debugDump("testing mar download with interrupted recovery count exceeded");
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ initMockIncrementalDownload();
+ gIncrementalDownloadErrorType = 0;
+ Services.prefs.setIntPref(PREF_APP_UPDATE_SOCKET_MAXERRORS, 2);
+ Services.prefs.setIntPref(PREF_APP_UPDATE_RETRYTIMEOUT, 0);
+ let patches = getRemotePatchString({});
+ let updates = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 1 }).then(async aArgs => {
+ await waitForUpdateDownload(aArgs.updates, Cr.NS_ERROR_NET_RESET);
+ });
+ stop_httpserver(doTestFinish);
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedOffline.js b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedOffline.js
new file mode 100644
index 0000000000..2c54e058d2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedOffline.js
@@ -0,0 +1,21 @@
+/* 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/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+ debugDump("testing mar download when offline");
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ initMockIncrementalDownload();
+ gIncrementalDownloadErrorType = 4;
+ let patches = getRemotePatchString({});
+ let updates = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 1 }).then(async aArgs => {
+ await waitForUpdateDownload(aArgs.updates, Cr.NS_OK);
+ });
+ stop_httpserver(doTestFinish);
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js
new file mode 100644
index 0000000000..1ed40f9a2a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js
@@ -0,0 +1,26 @@
+/* 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/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+ debugDump("testing mar mar download interrupted recovery");
+ // This test assumes speculative connections enabled.
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
+ });
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ initMockIncrementalDownload();
+ gIncrementalDownloadErrorType = 0;
+ let patches = getRemotePatchString({});
+ let updates = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 1 }).then(async aArgs => {
+ await waitForUpdateDownload(aArgs.updates, Cr.NS_OK);
+ });
+ stop_httpserver(doTestFinish);
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js b/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js
new file mode 100644
index 0000000000..8a386513f3
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js
@@ -0,0 +1,38 @@
+/* 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/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+
+ debugDump(
+ "testing resuming an update download in progress for the same " +
+ "version of the application on startup (Bug 485624)"
+ );
+
+ let patchProps = { state: STATE_DOWNLOADING };
+ let patches = getLocalPatchString(patchProps);
+ let updateProps = { appVersion: "1.0" };
+ let updates = getLocalUpdateString(updateProps, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_DOWNLOADING);
+
+ standardInit();
+
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 0,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ gUpdateManager.downloadingUpdate.state,
+ STATE_DOWNLOADING,
+ "the update manager activeUpdate state attribute" + MSG_SHOULD_EQUAL
+ );
+
+ // Cancel the download early to prevent it writing the update xml files during
+ // shutdown.
+ await gAUS.stopDownload();
+ executeSoon(doTestFinish);
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/ensureExperimentToRolloutTransitionPerformed.js b/toolkit/mozapps/update/tests/unit_aus_update/ensureExperimentToRolloutTransitionPerformed.js
new file mode 100644
index 0000000000..86bc75a7d6
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/ensureExperimentToRolloutTransitionPerformed.js
@@ -0,0 +1,111 @@
+/* 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/.
+ */
+
+ChromeUtils.defineESModuleGetters(this, {
+ BackgroundUpdate: "resource://gre/modules/BackgroundUpdate.sys.mjs",
+});
+
+const transitionPerformedPref = "app.update.background.rolledout";
+const backgroundUpdateEnabledPref = "app.update.background.enabled";
+const defaultPrefValue =
+ UpdateUtils.PER_INSTALLATION_PREFS[backgroundUpdateEnabledPref].defaultValue;
+
+async function testTransition(options) {
+ Services.prefs.clearUserPref(transitionPerformedPref);
+ await UpdateUtils.writeUpdateConfigSetting(
+ backgroundUpdateEnabledPref,
+ options.initialDefaultValue,
+ { setDefaultOnly: true }
+ );
+ await UpdateUtils.writeUpdateConfigSetting(
+ backgroundUpdateEnabledPref,
+ options.initialUserValue
+ );
+ BackgroundUpdate.ensureExperimentToRolloutTransitionPerformed();
+ Assert.equal(
+ await UpdateUtils.readUpdateConfigSetting(backgroundUpdateEnabledPref),
+ options.expectedPostTransitionValue,
+ "Post transition option value does not match the expected value"
+ );
+
+ // Make sure that we only do the transition once.
+
+ // If we change the default value, then change the user value to the same
+ // thing, we will end up with only a default value and no saved user value.
+ // This allows us to ensure that we read the default value back out, if it is
+ // changed.
+ await UpdateUtils.writeUpdateConfigSetting(
+ backgroundUpdateEnabledPref,
+ !defaultPrefValue,
+ { setDefaultOnly: true }
+ );
+ await UpdateUtils.writeUpdateConfigSetting(
+ backgroundUpdateEnabledPref,
+ !defaultPrefValue
+ );
+ BackgroundUpdate.ensureExperimentToRolloutTransitionPerformed();
+ Assert.equal(
+ await UpdateUtils.readUpdateConfigSetting(backgroundUpdateEnabledPref),
+ !defaultPrefValue,
+ "Transition should not change the pref value if it already ran"
+ );
+}
+
+async function run_test() {
+ setupTestCommon(null);
+ standardInit();
+ // The setup functions we use for update testing typically allow for update.
+ // But we are just testing preferences here. We don't want anything to
+ // actually attempt to update. Also, because we are messing with the pref
+ // system itself in this test, we want to make sure to use a pref outside of
+ // that system to disable update.
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_DISABLEDFORTESTING, true);
+
+ const originalBackgroundUpdateEnabled =
+ await UpdateUtils.readUpdateConfigSetting(backgroundUpdateEnabledPref);
+
+ registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref(transitionPerformedPref);
+ await UpdateUtils.writeUpdateConfigSetting(
+ backgroundUpdateEnabledPref,
+ originalBackgroundUpdateEnabled
+ );
+ await UpdateUtils.writeUpdateConfigSetting(
+ backgroundUpdateEnabledPref,
+ defaultPrefValue,
+ { setDefaultOnly: true }
+ );
+ });
+
+ await testTransition({
+ initialDefaultValue: true,
+ initialUserValue: true,
+ expectedPostTransitionValue: true,
+ });
+
+ // Make sure we don't interfere with a user's choice to turn the feature off.
+ await testTransition({
+ initialDefaultValue: true,
+ initialUserValue: false,
+ expectedPostTransitionValue: false,
+ });
+
+ // In this case, there effectively is no user value since the user value
+ // equals the default value. So the effective value should change after
+ // the transition switches the default.
+ await testTransition({
+ initialDefaultValue: false,
+ initialUserValue: false,
+ expectedPostTransitionValue: true,
+ });
+
+ await testTransition({
+ initialDefaultValue: false,
+ initialUserValue: true,
+ expectedPostTransitionValue: true,
+ });
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/head_update.js b/toolkit/mozapps/update/tests/unit_aus_update/head_update.js
new file mode 100644
index 0000000000..3cfde06015
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/head_update.js
@@ -0,0 +1,8 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const IS_SERVICE_TEST = false;
+
+/* import-globals-from ../data/xpcshellUtilsAUS.js */
+load("xpcshellUtilsAUS.js");
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/languagePackUpdates.js b/toolkit/mozapps/update/tests/unit_aus_update/languagePackUpdates.js
new file mode 100644
index 0000000000..5c67c59fe0
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/languagePackUpdates.js
@@ -0,0 +1,291 @@
+const { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+const { getAppInfo } = ChromeUtils.importESModule(
+ "resource://testing-common/AppInfo.sys.mjs"
+);
+const { XPIInstall } = ChromeUtils.import(
+ "resource://gre/modules/addons/XPIInstall.jsm"
+);
+const { PromiseUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PromiseUtils.sys.mjs"
+);
+const { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+AddonTestUtils.init(this);
+setupTestCommon();
+AddonTestUtils.appInfo = getAppInfo();
+start_httpserver();
+setUpdateURL(gURLData + gHTTPHandlerPath);
+setUpdateChannel("test_channel");
+Services.prefs.setBoolPref(PREF_APP_UPDATE_LANGPACK_ENABLED, true);
+
+/**
+ * Checks for updates and waits for the update to download.
+ */
+async function downloadUpdate() {
+ let patches = getRemotePatchString({});
+ let updateString = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updateString);
+
+ let { updates } = await waitForUpdateCheck(true);
+
+ initMockIncrementalDownload();
+ gIncrementalDownloadErrorType = 3;
+
+ await waitForUpdateDownload(updates, Cr.NS_OK);
+}
+
+/**
+ * Returns a promise that will resolve when the add-ons manager attempts to
+ * stage langpack updates. The returned object contains the appVersion and
+ * platformVersion parameters as well as resolve and reject functions to
+ * complete the mocked langpack update.
+ */
+function mockLangpackUpdate() {
+ let stagingCall = PromiseUtils.defer();
+ XPIInstall.stageLangpacksForAppUpdate = (appVersion, platformVersion) => {
+ let result = PromiseUtils.defer();
+ stagingCall.resolve({
+ appVersion,
+ platformVersion,
+ resolve: result.resolve,
+ reject: result.reject,
+ });
+
+ return result.promise;
+ };
+
+ return stagingCall.promise;
+}
+
+add_setup(async function () {
+ // Thunderbird doesn't have one or more of the probes used in this test.
+ // Ensure the data is collected anyway.
+ Services.prefs.setBoolPref(
+ "toolkit.telemetry.testing.overrideProductsCheck",
+ true
+ );
+
+ await AddonTestUtils.promiseStartupManager();
+});
+
+add_task(async function testLangpackUpdateSuccess() {
+ let histogram = TelemetryTestUtils.getAndClearHistogram(
+ "UPDATE_LANGPACK_OVERTIME"
+ );
+
+ let updateDownloadNotified = false;
+ let notified = waitForEvent("update-downloaded").then(
+ () => (updateDownloadNotified = true)
+ );
+
+ let stagingCall = mockLangpackUpdate();
+
+ await downloadUpdate();
+
+ // We have to wait for UpdateService's onStopRequest to run far enough that
+ // the notification will have been sent if the language pack update completed.
+ await TestUtils.waitForCondition(() => readStatusFile() == "pending");
+
+ Assert.ok(
+ !updateDownloadNotified,
+ "Should not have seen the notification yet."
+ );
+
+ let { appVersion, platformVersion, resolve } = await stagingCall;
+ Assert.equal(
+ appVersion,
+ DEFAULT_UPDATE_VERSION,
+ "Should see the right app version"
+ );
+ Assert.equal(
+ platformVersion,
+ DEFAULT_UPDATE_VERSION,
+ "Should see the right platform version"
+ );
+
+ resolve();
+
+ await notified;
+
+ // Because we resolved the lang pack call after the download completed a value
+ // should have been recorded in telemetry.
+ let snapshot = histogram.snapshot();
+ Assert.ok(
+ !Object.values(snapshot.values).every(val => val == 0),
+ "Should have recorded a time"
+ );
+
+ // Reload the update manager so that we can download the same update again
+ reloadUpdateManagerData(true);
+});
+
+add_task(async function testLangpackUpdateFails() {
+ let updateDownloadNotified = false;
+ let notified = waitForEvent("update-downloaded").then(
+ () => (updateDownloadNotified = true)
+ );
+
+ let stagingCall = mockLangpackUpdate();
+
+ await downloadUpdate();
+
+ // We have to wait for UpdateService's onStopRequest to run far enough that
+ // the notification will have been sent if the language pack update completed.
+ await TestUtils.waitForCondition(() => readStatusFile() == "pending");
+
+ Assert.ok(
+ !updateDownloadNotified,
+ "Should not have seen the notification yet."
+ );
+
+ let { appVersion, platformVersion, reject } = await stagingCall;
+ Assert.equal(
+ appVersion,
+ DEFAULT_UPDATE_VERSION,
+ "Should see the right app version"
+ );
+ Assert.equal(
+ platformVersion,
+ DEFAULT_UPDATE_VERSION,
+ "Should see the right platform version"
+ );
+
+ reject();
+
+ await notified;
+
+ // Reload the update manager so that we can download the same update again
+ reloadUpdateManagerData(true);
+});
+
+add_task(async function testLangpackStaged() {
+ let updateStagedNotified = false;
+ let notified = waitForEvent("update-staged").then(
+ () => (updateStagedNotified = true)
+ );
+
+ let stagingCall = mockLangpackUpdate();
+
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, true);
+ copyTestUpdaterToBinDir();
+
+ let greDir = getGREDir();
+ let updateSettingsIni = greDir.clone();
+ updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI);
+ writeFile(updateSettingsIni, UPDATE_SETTINGS_CONTENTS);
+
+ await downloadUpdate();
+
+ // We have to wait for the update to be applied and then check that the
+ // notification hasn't been sent.
+ await TestUtils.waitForCondition(() => readStatusFile() == "applied");
+
+ Assert.ok(
+ !updateStagedNotified,
+ "Should not have seen the notification yet."
+ );
+
+ let { appVersion, platformVersion, resolve } = await stagingCall;
+ Assert.equal(
+ appVersion,
+ DEFAULT_UPDATE_VERSION,
+ "Should see the right app version"
+ );
+ Assert.equal(
+ platformVersion,
+ DEFAULT_UPDATE_VERSION,
+ "Should see the right platform version"
+ );
+
+ resolve();
+
+ await notified;
+
+ // Reload the update manager so that we can download the same update again
+ reloadUpdateManagerData(true);
+});
+
+add_task(async function testRedownload() {
+ // When the download of a partial mar fails the same downloader is re-used to
+ // download the complete mar. We should only call the add-ons manager to stage
+ // language packs once in this case.
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
+ let histogram = TelemetryTestUtils.getAndClearHistogram(
+ "UPDATE_LANGPACK_OVERTIME"
+ );
+
+ let partialPatch = getRemotePatchString({
+ type: "partial",
+ url: gURLData + "missing.mar",
+ size: 28,
+ });
+ let completePatch = getRemotePatchString({});
+ let updateString = getRemoteUpdateString({}, partialPatch + completePatch);
+ gResponseBody = getRemoteUpdatesXMLString(updateString);
+
+ let { updates } = await waitForUpdateCheck(true);
+
+ initMockIncrementalDownload();
+ gIncrementalDownloadErrorType = 3;
+
+ let stageCount = 0;
+ XPIInstall.stageLangpacksForAppUpdate = () => {
+ stageCount++;
+ return Promise.resolve();
+ };
+
+ let downloadCount = 0;
+ let listener = {
+ onStartRequest: aRequest => {},
+ onProgress: (aRequest, aContext, aProgress, aMaxProgress) => {},
+ onStatus: (aRequest, aStatus, aStatusText) => {},
+ onStopRequest: (request, status) => {
+ Assert.equal(
+ status,
+ downloadCount ? 0 : Cr.NS_ERROR_CORRUPTED_CONTENT,
+ "Should have seen the right status."
+ );
+ downloadCount++;
+
+ // Keep the same status.
+ gIncrementalDownloadErrorType = 3;
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIRequestObserver",
+ "nsIProgressEventSink",
+ ]),
+ };
+ gAUS.addDownloadListener(listener);
+
+ let bestUpdate = gAUS.selectUpdate(updates);
+ await gAUS.downloadUpdate(bestUpdate, false);
+
+ await waitForEvent("update-downloaded");
+
+ gAUS.removeDownloadListener(listener);
+
+ Assert.equal(downloadCount, 2, "Should have seen two downloads");
+ Assert.equal(stageCount, 1, "Should have only tried to stage langpacks once");
+
+ // Because we resolved the lang pack call before the download completed a value
+ // should not have been recorded in telemetry.
+ let snapshot = histogram.snapshot();
+ Assert.ok(
+ Object.values(snapshot.values).every(val => val == 0),
+ "Should have recorded a time"
+ );
+
+ // Reload the update manager so that we can download the same update again
+ reloadUpdateManagerData(true);
+});
+
+add_task(async function finish() {
+ stop_httpserver(doTestFinish);
+});
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js b/toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js
new file mode 100644
index 0000000000..73a262e527
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js
@@ -0,0 +1,396 @@
+/* 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/.
+ */
+
+/**
+ * This tests the multiple update downloads per Firefox session feature.
+ *
+ * This test does some unusual things, compared to the other files in this
+ * directory. We want to start the updates with aus.checkForBackgroundUpdates()
+ * to ensure that we test the whole flow. Other tests start update with things
+ * like aus.downloadUpdate(), but that bypasses some of the exact checks that we
+ * are trying to test as part of the multiupdate flow.
+ *
+ * In order to accomplish all this, we will be using app_update.sjs to serve
+ * updates XMLs and MARs. Outside of this test, this is really only done
+ * by browser-chrome mochitests (in ../browser). So we have to do some weird
+ * things to make it work properly in an xpcshell test. Things like
+ * defining URL_HTTP_UPDATE_SJS in testConstants.js so that it can be read by
+ * app_update.sjs in order to provide the correct download URL for MARs, but
+ * not reading that file here, because URL_HTTP_UPDATE_SJS is already defined
+ * (as something else) in xpcshellUtilsAUS.js.
+ */
+
+let { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+// These are from testConstants.js, which cannot be loaded by this file, because
+// some values are already defined at this point. However, we need these some
+// other values to be defined because continueFileHandler in shared.js expects
+// them to be.
+const REL_PATH_DATA = "";
+// This should be URL_HOST, but that conflicts with an existing constant.
+const APP_UPDATE_SJS_HOST = "http://127.0.0.1:8888";
+const URL_PATH_UPDATE_XML = "/" + REL_PATH_DATA + "app_update.sjs";
+// This should be URL_HTTP_UPDATE_SJS, but that conflicts with an existing
+// constant.
+const APP_UPDATE_SJS_URL = APP_UPDATE_SJS_HOST + URL_PATH_UPDATE_XML;
+const CONTINUE_CHECK = "continueCheck";
+const CONTINUE_DOWNLOAD = "continueDownload";
+const CONTINUE_STAGING = "continueStaging";
+
+const FIRST_UPDATE_VERSION = "999998.0";
+const SECOND_UPDATE_VERSION = "999999.0";
+
+/**
+ * Downloads an update via aus.checkForBackgroundUpdates()
+ * Function returns only after the update has been downloaded.
+ *
+ * The provided callback will be invoked once during the update download,
+ * specifically when onStartRequest is fired.
+ *
+ * If automatic update downloads are turned off (appUpdateAuto is false), then
+ * we listen for the update-available notification and then roughly simulate
+ * accepting the prompt by calling:
+ * AppUpdateService.downloadUpdate(update, true);
+ * This is what is normally called when the user accepts the update-available
+ * prompt.
+ */
+async function downloadUpdate(appUpdateAuto, onDownloadStartCallback) {
+ let downloadFinishedPromise = waitForEvent("update-downloaded");
+ let updateAvailablePromise;
+ if (!appUpdateAuto) {
+ updateAvailablePromise = new Promise(resolve => {
+ let observer = (subject, topic, status) => {
+ Services.obs.removeObserver(observer, "update-available");
+ subject.QueryInterface(Ci.nsIUpdate);
+ resolve({ update: subject, status });
+ };
+ Services.obs.addObserver(observer, "update-available");
+ });
+ }
+ let waitToStartPromise = new Promise(resolve => {
+ let listener = {
+ onStartRequest: aRequest => {
+ gAUS.removeDownloadListener(listener);
+ onDownloadStartCallback();
+ resolve();
+ },
+ onProgress: (aRequest, aContext, aProgress, aMaxProgress) => {},
+ onStatus: (aRequest, aStatus, aStatusText) => {},
+ onStopRequest(request, status) {},
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIRequestObserver",
+ "nsIProgressEventSink",
+ ]),
+ };
+ gAUS.addDownloadListener(listener);
+ });
+
+ let updateCheckStarted = gAUS.checkForBackgroundUpdates();
+ Assert.ok(updateCheckStarted, "Update check should have started");
+
+ if (!appUpdateAuto) {
+ let { update, status } = await updateAvailablePromise;
+ Assert.equal(
+ status,
+ "show-prompt",
+ "Should attempt to show the update-available prompt"
+ );
+ // Simulate accepting the update-available prompt
+ await gAUS.downloadUpdate(update, true);
+ }
+
+ await continueFileHandler(CONTINUE_DOWNLOAD);
+ await waitToStartPromise;
+ await downloadFinishedPromise;
+ // Wait an extra tick after the download has finished. If we try to check for
+ // another update exactly when "update-downloaded" fires,
+ // Downloader:onStopRequest won't have finished yet, which it normally would
+ // have.
+ await TestUtils.waitForTick();
+}
+
+/**
+ * This is like downloadUpdate. The difference is that downloadUpdate assumes
+ * that an update actually will be downloaded. This function instead verifies
+ * that we the update wasn't downloaded.
+ *
+ * downloadUpdate(), above, uses aus.checkForBackgroundUpdates() to download
+ * updates to verify that background updates actually will check for subsequent
+ * updates, but this function will use some slightly different mechanisms. We
+ * can call aus.downloadUpdate() and check its return value to see if it started
+ * a download. But this doesn't properly check that the update-available
+ * notification isn't shown. So we will do an additional check where we follow
+ * the normal flow a bit more closely by forwarding the results that we got from
+ * checkForUpdates() to aus.onCheckComplete() and make sure that the update
+ * prompt isn't shown.
+ */
+async function testUpdateDoesNotDownload() {
+ let check = gUpdateChecker.checkForUpdates(gUpdateChecker.BACKGROUND_CHECK);
+ let result = await check.result;
+ Assert.ok(result.checksAllowed, "Should be able to check for updates");
+ Assert.ok(result.succeeded, "Update check should have succeeded");
+
+ Assert.equal(
+ result.updates.length,
+ 1,
+ "Should have gotten 1 update in update check"
+ );
+ let update = result.updates[0];
+
+ let downloadStarted = await gAUS.downloadUpdate(update, true);
+ Assert.equal(
+ downloadStarted,
+ false,
+ "Expected that we would not start downloading an update"
+ );
+
+ let updateAvailableObserved = false;
+ let observer = (subject, topic, status) => {
+ updateAvailableObserved = true;
+ };
+ Services.obs.addObserver(observer, "update-available");
+ await gAUS.onCheckComplete(result);
+ Services.obs.removeObserver(observer, "update-available");
+ Assert.equal(
+ updateAvailableObserved,
+ false,
+ "update-available notification should not fire if we aren't going to " +
+ "download the update."
+ );
+}
+
+function testUpdateCheckDoesNotStart() {
+ let updateCheckStarted = gAUS.checkForBackgroundUpdates();
+ Assert.equal(
+ updateCheckStarted,
+ false,
+ "Update check should not have started"
+ );
+}
+
+function prepareToDownloadVersion(version, onlyCompleteMar = false) {
+ let updateUrl = `${APP_UPDATE_SJS_URL}?useSlowDownloadMar=1&appVersion=${version}`;
+ if (onlyCompleteMar) {
+ updateUrl += "&completePatchOnly=1";
+ }
+ setUpdateURL(updateUrl);
+}
+
+function startUpdateServer() {
+ let httpServer = new HttpServer();
+ httpServer.registerContentType("sjs", "sjs");
+ httpServer.registerDirectory("/", do_get_cwd());
+ httpServer.start(8888);
+ registerCleanupFunction(async function cleanup_httpServer() {
+ await new Promise(resolve => {
+ httpServer.stop(resolve);
+ });
+ });
+}
+
+async function multi_update_test(appUpdateAuto) {
+ await UpdateUtils.setAppUpdateAutoEnabled(appUpdateAuto);
+
+ prepareToDownloadVersion(FIRST_UPDATE_VERSION);
+
+ await downloadUpdate(appUpdateAuto, () => {
+ Assert.ok(
+ !gUpdateManager.readyUpdate,
+ "There should not be a ready update yet"
+ );
+ Assert.ok(
+ !!gUpdateManager.downloadingUpdate,
+ "First update download should be in downloadingUpdate"
+ );
+ Assert.equal(
+ gUpdateManager.downloadingUpdate.state,
+ STATE_DOWNLOADING,
+ "downloadingUpdate should be downloading"
+ );
+ Assert.equal(
+ readStatusFile(),
+ STATE_DOWNLOADING,
+ "Updater state should be downloading"
+ );
+ });
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "First update download should no longer be in downloadingUpdate"
+ );
+ Assert.ok(
+ !!gUpdateManager.readyUpdate,
+ "First update download should be in readyUpdate"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.state,
+ STATE_PENDING,
+ "readyUpdate should be pending"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.appVersion,
+ FIRST_UPDATE_VERSION,
+ "readyUpdate version should be match the version of the first update"
+ );
+ Assert.equal(
+ readStatusFile(),
+ STATE_PENDING,
+ "Updater state should be pending"
+ );
+
+ let existingUpdate = gUpdateManager.readyUpdate;
+ await testUpdateDoesNotDownload();
+
+ Assert.equal(
+ gUpdateManager.readyUpdate,
+ existingUpdate,
+ "readyUpdate should not have changed when no newer update is available"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.state,
+ STATE_PENDING,
+ "readyUpdate should still be pending"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.appVersion,
+ FIRST_UPDATE_VERSION,
+ "readyUpdate version should be match the version of the first update"
+ );
+ Assert.equal(
+ readStatusFile(),
+ STATE_PENDING,
+ "Updater state should still be pending"
+ );
+
+ // With only a complete update available, we should not download the newer
+ // update when we already have an update ready.
+ prepareToDownloadVersion(SECOND_UPDATE_VERSION, true);
+ await testUpdateDoesNotDownload();
+
+ Assert.equal(
+ gUpdateManager.readyUpdate,
+ existingUpdate,
+ "readyUpdate should not have changed when no newer partial update is available"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.state,
+ STATE_PENDING,
+ "readyUpdate should still be pending"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.appVersion,
+ FIRST_UPDATE_VERSION,
+ "readyUpdate version should be match the version of the first update"
+ );
+ Assert.equal(
+ readStatusFile(),
+ STATE_PENDING,
+ "Updater state should still be pending"
+ );
+
+ prepareToDownloadVersion(SECOND_UPDATE_VERSION);
+
+ await downloadUpdate(appUpdateAuto, () => {
+ Assert.ok(
+ !!gUpdateManager.downloadingUpdate,
+ "Second update download should be in downloadingUpdate"
+ );
+ Assert.equal(
+ gUpdateManager.downloadingUpdate.state,
+ STATE_DOWNLOADING,
+ "downloadingUpdate should be downloading"
+ );
+ Assert.ok(
+ !!gUpdateManager.readyUpdate,
+ "First update download should still be in readyUpdate"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.state,
+ STATE_PENDING,
+ "readyUpdate should still be pending"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.appVersion,
+ FIRST_UPDATE_VERSION,
+ "readyUpdate version should be match the version of the first update"
+ );
+ Assert.equal(
+ readStatusFile(),
+ STATE_PENDING,
+ "Updater state should match the readyUpdate's state"
+ );
+ });
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "Second update download should no longer be in downloadingUpdate"
+ );
+ Assert.ok(
+ !!gUpdateManager.readyUpdate,
+ "Second update download should be in readyUpdate"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.state,
+ STATE_PENDING,
+ "readyUpdate should be pending"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.appVersion,
+ SECOND_UPDATE_VERSION,
+ "readyUpdate version should be match the version of the second update"
+ );
+ Assert.equal(
+ readStatusFile(),
+ STATE_PENDING,
+ "Updater state should be pending"
+ );
+
+ // Reset the updater to its initial state to test that the complete/partial
+ // MAR behavior is correct
+ reloadUpdateManagerData(true);
+
+ // Second parameter forces a complete MAR download.
+ prepareToDownloadVersion(FIRST_UPDATE_VERSION, true);
+
+ await downloadUpdate(appUpdateAuto, () => {
+ Assert.equal(
+ gUpdateManager.downloadingUpdate.selectedPatch.type,
+ "complete",
+ "First update download should be a complete patch"
+ );
+ });
+
+ Assert.equal(
+ gUpdateManager.readyUpdate.selectedPatch.type,
+ "complete",
+ "First update download should be a complete patch"
+ );
+
+ // Even a newer partial update should not be downloaded at this point.
+ prepareToDownloadVersion(SECOND_UPDATE_VERSION);
+ testUpdateCheckDoesNotStart();
+}
+
+add_task(async function all_multi_update_tests() {
+ setupTestCommon(true);
+ startUpdateServer();
+
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_DISABLEDFORTESTING, false);
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
+
+ let origAppUpdateAutoVal = await UpdateUtils.getAppUpdateAutoEnabled();
+ registerCleanupFunction(async () => {
+ await UpdateUtils.setAppUpdateAutoEnabled(origAppUpdateAutoVal);
+ });
+
+ await multi_update_test(true);
+
+ // Reset the update system so we can start again from scratch.
+ reloadUpdateManagerData(true);
+
+ await multi_update_test(false);
+
+ doTestFinish();
+});
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/onlyDownloadUpdatesThisSession.js b/toolkit/mozapps/update/tests/unit_aus_update/onlyDownloadUpdatesThisSession.js
new file mode 100644
index 0000000000..ab08ac854f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/onlyDownloadUpdatesThisSession.js
@@ -0,0 +1,69 @@
+/* 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";
+
+function setup() {
+ setupTestCommon();
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ setUpdateChannel("test_channel");
+}
+setup();
+
+/**
+ * Checks for updates and makes sure that the update process does not proceed
+ * beyond the downloading stage.
+ */
+async function downloadUpdate() {
+ let patches = getRemotePatchString({});
+ let updateString = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updateString);
+
+ let { updates } = await waitForUpdateCheck(true);
+
+ initMockIncrementalDownload();
+ gIncrementalDownloadErrorType = 3;
+
+ let downloadRestrictionHitPromise = new Promise(resolve => {
+ let downloadRestrictionHitListener = (subject, topic) => {
+ Services.obs.removeObserver(downloadRestrictionHitListener, topic);
+ resolve();
+ };
+ Services.obs.addObserver(
+ downloadRestrictionHitListener,
+ "update-download-restriction-hit"
+ );
+ });
+
+ let bestUpdate = gAUS.selectUpdate(updates);
+ let success = await gAUS.downloadUpdate(bestUpdate, false);
+ Assert.ok(success, "Update download should have started");
+ return downloadRestrictionHitPromise;
+}
+
+add_task(async function onlyDownloadUpdatesThisSession() {
+ gAUS.onlyDownloadUpdatesThisSession = true;
+
+ await downloadUpdate();
+
+ Assert.ok(
+ !gUpdateManager.readyUpdate,
+ "There should not be a ready update. The update should still be downloading"
+ );
+ Assert.ok(
+ !!gUpdateManager.downloadingUpdate,
+ "A downloading update should exist"
+ );
+ Assert.equal(
+ gUpdateManager.downloadingUpdate.state,
+ STATE_DOWNLOADING,
+ "The downloading update should still be in the downloading state"
+ );
+});
+
+add_task(async function finish() {
+ stop_httpserver(doTestFinish);
+});
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/perInstallationPrefs.js b/toolkit/mozapps/update/tests/unit_aus_update/perInstallationPrefs.js
new file mode 100644
index 0000000000..4a4a8ff2ae
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/perInstallationPrefs.js
@@ -0,0 +1,238 @@
+/* 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/.
+ */
+
+let gPolicyFunctionResult;
+
+async function testSetup() {
+ // The setup functions we use for update testing typically allow for update.
+ // But we are just testing preferences here. We don't want anything to
+ // actually attempt to update. Also, because we are messing with the pref
+ // system itself in this test, we want to make sure to use a pref outside of
+ // that system to disable update.
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_DISABLEDFORTESTING, true);
+
+ // We run these tests whether per-installation prefs are supported or not,
+ // because this API needs to work in both cases.
+ logTestInfo(
+ "PER_INSTALLATION_PREFS_SUPPORTED = " +
+ UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED.toString()
+ );
+
+ // Save the original state so we can restore it after the test.
+ const originalPerInstallationPrefs = UpdateUtils.PER_INSTALLATION_PREFS;
+ let configFile = getUpdateDirFile(FILE_UPDATE_CONFIG_JSON);
+ try {
+ configFile.moveTo(null, FILE_BACKUP_UPDATE_CONFIG_JSON);
+ } catch (e) {}
+
+ // Currently, features exist for per-installation prefs that are not used by
+ // any actual pref. We added them because we intend to use these features in
+ // the future. Thus, we will override the normally defined per-installation
+ // prefs and provide our own, for the purposes of testing.
+ UpdateUtils.PER_INSTALLATION_PREFS = {
+ "test.pref.boolean": {
+ type: UpdateUtils.PER_INSTALLATION_PREF_TYPE_BOOL,
+ defaultValue: true,
+ observerTopic: "test-pref-change-observer-boolean",
+ },
+ "test.pref.integer": {
+ type: UpdateUtils.PER_INSTALLATION_PREF_TYPE_INT,
+ defaultValue: 1234,
+ observerTopic: "test-pref-change-observer-integer",
+ },
+ "test.pref.string": {
+ type: UpdateUtils.PER_INSTALLATION_PREF_TYPE_ASCII_STRING,
+ defaultValue: "<default>",
+ observerTopic: "test-pref-change-observer-string",
+ },
+ "test.pref.policy": {
+ type: UpdateUtils.PER_INSTALLATION_PREF_TYPE_INT,
+ defaultValue: 1234,
+ observerTopic: "test-pref-change-observer-policy",
+ policyFn: () => gPolicyFunctionResult,
+ },
+ };
+ // We need to re-initialize the pref system with these new prefs
+ UpdateUtils.initPerInstallPrefs();
+
+ registerCleanupFunction(() => {
+ if (UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED) {
+ let testConfigFile = getUpdateDirFile(FILE_UPDATE_CONFIG_JSON);
+ let backupConfigFile = getUpdateDirFile(FILE_BACKUP_UPDATE_CONFIG_JSON);
+ try {
+ testConfigFile.remove(false);
+ backupConfigFile.moveTo(null, FILE_UPDATE_CONFIG_JSON);
+ } catch (ex) {}
+ } else {
+ for (const prefName in UpdateUtils.PER_INSTALLATION_PREFS) {
+ Services.prefs.clearUserPref(prefName);
+ }
+ }
+
+ UpdateUtils.PER_INSTALLATION_PREFS = originalPerInstallationPrefs;
+ UpdateUtils.initPerInstallPrefs();
+ });
+}
+
+let gObserverSeenCount = 0;
+let gExpectedObserverData;
+function observerCallback(subject, topic, data) {
+ gObserverSeenCount += 1;
+ Assert.equal(
+ data,
+ gExpectedObserverData,
+ `Expected observer to have data: "${gExpectedObserverData}". ` +
+ `It actually has data: "${data}"`
+ );
+}
+
+async function changeAndVerifyPref(
+ prefName,
+ expectedInitialValue,
+ newValue,
+ setterShouldThrow = false
+) {
+ let initialValue = await UpdateUtils.readUpdateConfigSetting(prefName);
+ Assert.strictEqual(
+ initialValue,
+ expectedInitialValue,
+ `Expected pref '${prefName}' to have an initial value of ` +
+ `${JSON.stringify(expectedInitialValue)}. Its actual initial value is ` +
+ `${JSON.stringify(initialValue)}`
+ );
+
+ let expectedObserverCount = 1;
+ if (initialValue == newValue || setterShouldThrow) {
+ expectedObserverCount = 0;
+ }
+
+ let observerTopic =
+ UpdateUtils.PER_INSTALLATION_PREFS[prefName].observerTopic;
+ gObserverSeenCount = 0;
+ gExpectedObserverData = newValue.toString();
+ Services.obs.addObserver(observerCallback, observerTopic);
+
+ let returned;
+ let exceptionThrown;
+ try {
+ returned = await UpdateUtils.writeUpdateConfigSetting(prefName, newValue);
+ } catch (e) {
+ exceptionThrown = e;
+ }
+ if (setterShouldThrow) {
+ Assert.ok(!!exceptionThrown, "Expected an exception to be thrown");
+ } else {
+ Assert.ok(
+ !exceptionThrown,
+ `Unexpected exception thrown by writeUpdateConfigSetting: ` +
+ `${exceptionThrown}`
+ );
+ }
+
+ if (!exceptionThrown) {
+ Assert.strictEqual(
+ returned,
+ newValue,
+ `Expected writeUpdateConfigSetting to return ` +
+ `${JSON.stringify(newValue)}. It actually returned ` +
+ `${JSON.stringify(returned)}`
+ );
+ }
+
+ let readValue = await UpdateUtils.readUpdateConfigSetting(prefName);
+ let expectedReadValue = exceptionThrown ? expectedInitialValue : newValue;
+ Assert.strictEqual(
+ readValue,
+ expectedReadValue,
+ `Expected pref '${prefName}' to be ${JSON.stringify(expectedReadValue)}.` +
+ ` It was actually ${JSON.stringify(readValue)}.`
+ );
+
+ Assert.equal(
+ gObserverSeenCount,
+ expectedObserverCount,
+ `Expected to see observer fire ${expectedObserverCount} times. It ` +
+ `actually fired ${gObserverSeenCount} times.`
+ );
+ Services.obs.removeObserver(observerCallback, observerTopic);
+}
+
+async function run_test() {
+ setupTestCommon(null);
+ standardInit();
+ await testSetup();
+
+ logTestInfo("Testing boolean pref and its observer");
+ let pref = "test.pref.boolean";
+ let defaultValue = UpdateUtils.PER_INSTALLATION_PREFS[pref].defaultValue;
+ await changeAndVerifyPref(pref, defaultValue, defaultValue);
+ await changeAndVerifyPref(pref, defaultValue, !defaultValue);
+ await changeAndVerifyPref(pref, !defaultValue, !defaultValue);
+ await changeAndVerifyPref(pref, !defaultValue, defaultValue);
+ await changeAndVerifyPref(pref, defaultValue, defaultValue);
+ await changeAndVerifyPref(pref, defaultValue, "true", true);
+ await changeAndVerifyPref(pref, defaultValue, 1, true);
+
+ logTestInfo("Testing string pref and its observer");
+ pref = "test.pref.string";
+ defaultValue = UpdateUtils.PER_INSTALLATION_PREFS[pref].defaultValue;
+ await changeAndVerifyPref(pref, defaultValue, defaultValue);
+ await changeAndVerifyPref(pref, defaultValue, defaultValue + "1");
+ await changeAndVerifyPref(pref, defaultValue + "1", "");
+ await changeAndVerifyPref(pref, "", 1, true);
+ await changeAndVerifyPref(pref, "", true, true);
+
+ logTestInfo("Testing integer pref and its observer");
+ pref = "test.pref.integer";
+ defaultValue = UpdateUtils.PER_INSTALLATION_PREFS[pref].defaultValue;
+ await changeAndVerifyPref(pref, defaultValue, defaultValue);
+ await changeAndVerifyPref(pref, defaultValue, defaultValue + 1);
+ await changeAndVerifyPref(pref, defaultValue + 1, 0);
+ await changeAndVerifyPref(pref, 0, "1", true);
+ await changeAndVerifyPref(pref, 0, true, true);
+
+ // Testing that the default pref branch works the same way that the default
+ // branch works for our per-profile prefs.
+ logTestInfo("Testing default branch behavior");
+ pref = "test.pref.integer";
+ let originalDefault = UpdateUtils.PER_INSTALLATION_PREFS[pref].defaultValue;
+ // Make sure the value is the default value, then change the default value
+ // and check that the effective value changes.
+ await UpdateUtils.writeUpdateConfigSetting(pref, originalDefault, {
+ setDefaultOnly: true,
+ });
+ await UpdateUtils.writeUpdateConfigSetting(pref, originalDefault);
+ await UpdateUtils.writeUpdateConfigSetting(pref, originalDefault + 1, {
+ setDefaultOnly: true,
+ });
+ Assert.strictEqual(
+ await UpdateUtils.readUpdateConfigSetting(pref),
+ originalDefault + 1,
+ `Expected that changing the default of a pref with no user value should ` +
+ `change the effective value`
+ );
+ // Now make the user value different from the default value and ensure that
+ // changing the default value does not affect the effective value
+ await UpdateUtils.writeUpdateConfigSetting(pref, originalDefault + 10);
+ await UpdateUtils.writeUpdateConfigSetting(pref, originalDefault + 20, {
+ setDefaultOnly: true,
+ });
+ Assert.strictEqual(
+ await UpdateUtils.readUpdateConfigSetting(pref),
+ originalDefault + 10,
+ `Expected that changing the default of a pref with a user value should ` +
+ `NOT change the effective value`
+ );
+
+ logTestInfo("Testing policy behavior");
+ pref = "test.pref.policy";
+ defaultValue = UpdateUtils.PER_INSTALLATION_PREFS[pref].defaultValue;
+ gPolicyFunctionResult = null;
+ await changeAndVerifyPref(pref, defaultValue, defaultValue + 1);
+ gPolicyFunctionResult = defaultValue + 10;
+ await changeAndVerifyPref(pref, gPolicyFunctionResult, 0, true);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js b/toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js
new file mode 100644
index 0000000000..94081a01a5
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js
@@ -0,0 +1,327 @@
+/* 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/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+ debugDump("testing remote update xml attributes");
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ setUpdateChannel("test_channel");
+
+ debugDump("testing update xml not available");
+ await waitForUpdateCheck(false).then(aArgs => {
+ Assert.equal(
+ aArgs.updates[0].errorCode,
+ 1500,
+ "the update errorCode" + MSG_SHOULD_EQUAL
+ );
+ });
+
+ debugDump(
+ "testing one update available, the update's property values and " +
+ "the property values of the update's patches"
+ );
+ let patchProps = {
+ type: "complete",
+ url: "http://complete/",
+ size: "9856459",
+ };
+ let patches = getRemotePatchString(patchProps);
+ patchProps = { type: "partial", url: "http://partial/", size: "1316138" };
+ patches += getRemotePatchString(patchProps);
+ let updateProps = {
+ type: "minor",
+ name: "Minor Test",
+ displayVersion: "version 2.1a1pre",
+ appVersion: "2.1a1pre",
+ buildID: "20080811053724",
+ detailsURL: "http://details/",
+ promptWaitTime: "345600",
+ custom1: 'custom1_attr="custom1 value"',
+ custom2: 'custom2_attr="custom2 value"',
+ };
+ let updates = getRemoteUpdateString(updateProps, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 1 }).then(aArgs => {
+ // XXXrstrong - not specifying a detailsURL will cause a leak due to
+ // bug 470244 and until this is fixed this will not test the value for
+ // detailsURL when it isn't specified in the update xml.
+
+ let bestUpdate = gAUS
+ .selectUpdate(aArgs.updates)
+ .QueryInterface(Ci.nsIWritablePropertyBag);
+ Assert.equal(
+ bestUpdate.type,
+ "minor",
+ "the update type attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.name,
+ "Minor Test",
+ "the update name attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.displayVersion,
+ "version 2.1a1pre",
+ "the update displayVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.appVersion,
+ "2.1a1pre",
+ "the update appVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.buildID,
+ "20080811053724",
+ "the update buildID attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.detailsURL,
+ "http://details/",
+ "the update detailsURL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.promptWaitTime,
+ "345600",
+ "the update promptWaitTime attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.serviceURL,
+ gURLData + gHTTPHandlerPath + "?force=1",
+ "the update serviceURL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.channel,
+ "test_channel",
+ "the update channel attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ !bestUpdate.isCompleteUpdate,
+ "the update isCompleteUpdate attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ !bestUpdate.isSecurityUpdate,
+ "the update isSecurityUpdate attribute" + MSG_SHOULD_EQUAL
+ );
+ // Check that installDate is within 10 seconds of the current date.
+ Assert.ok(
+ Date.now() - bestUpdate.installDate < 10000,
+ "the update installDate attribute should be within 10 seconds " +
+ "of the current time"
+ );
+ Assert.ok(
+ !bestUpdate.statusText,
+ "the update statusText attribute" + MSG_SHOULD_EQUAL
+ );
+ // nsIUpdate:state returns an empty string when no action has been performed
+ // on an available update
+ Assert.equal(
+ bestUpdate.state,
+ "",
+ "the update state attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.errorCode,
+ 0,
+ "the update errorCode attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.patchCount,
+ 2,
+ "the update patchCount attribute" + MSG_SHOULD_EQUAL
+ );
+ // XXX TODO - test nsIUpdate:serialize
+
+ Assert.equal(
+ bestUpdate.getProperty("custom1_attr"),
+ "custom1 value",
+ "the update custom1_attr property" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.getProperty("custom2_attr"),
+ "custom2 value",
+ "the update custom2_attr property" + MSG_SHOULD_EQUAL
+ );
+
+ let patch = bestUpdate.getPatchAt(0);
+ Assert.equal(
+ patch.type,
+ "complete",
+ "the update patch type attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.URL,
+ "http://complete/",
+ "the update patch URL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.size,
+ 9856459,
+ "the update patch size attribute" + MSG_SHOULD_EQUAL
+ );
+ // The value for patch.state can be the string 'null' as a valid value. This
+ // is confusing if it returns null which is an invalid value since the test
+ // failure output will show a failure for null == null. To lessen the
+ // confusion first check that the typeof for patch.state is string.
+ Assert.equal(
+ typeof patch.state,
+ "string",
+ "the update patch state typeof value should equal |string|"
+ );
+ Assert.equal(
+ patch.state,
+ STATE_NONE,
+ "the update patch state attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ !patch.selected,
+ "the update patch selected attribute" + MSG_SHOULD_EQUAL
+ );
+ // XXX TODO - test nsIUpdatePatch:serialize
+
+ patch = bestUpdate.getPatchAt(1);
+ Assert.equal(
+ patch.type,
+ "partial",
+ "the update patch type attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.URL,
+ "http://partial/",
+ "the update patch URL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.size,
+ 1316138,
+ "the update patch size attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.state,
+ STATE_NONE,
+ "the update patch state attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ !patch.selected,
+ "the update patch selected attribute" + MSG_SHOULD_EQUAL
+ );
+ // XXX TODO - test nsIUpdatePatch:serialize
+ });
+
+ debugDump(
+ "testing an empty update xml that returns a root node name of " +
+ "parsererror"
+ );
+ gResponseBody = "<parsererror/>";
+ await waitForUpdateCheck(false).then(aArgs => {
+ Assert.equal(
+ aArgs.updates[0].errorCode,
+ 1200,
+ "the update errorCode" + MSG_SHOULD_EQUAL
+ );
+ });
+
+ debugDump("testing no updates available");
+ gResponseBody = getRemoteUpdatesXMLString("");
+ await waitForUpdateCheck(true, { updateCount: 0 });
+
+ debugDump("testing one update available with two patches");
+ patches = getRemotePatchString({});
+ patchProps = { type: "partial" };
+ patches += getRemotePatchString(patchProps);
+ updates = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 1 });
+
+ debugDump("testing three updates available each with two patches");
+ patches = getRemotePatchString({});
+ patchProps = { type: "partial" };
+ patches += getRemotePatchString(patchProps);
+ updates = getRemoteUpdateString({}, patches);
+ updates += updates + updates;
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 3 });
+
+ debugDump(
+ "testing one update with complete and partial patches with size " +
+ "0 specified in the update xml"
+ );
+ patchProps = { size: "0" };
+ patches = getRemotePatchString(patchProps);
+ patchProps = { type: "partial", size: "0" };
+ patches += getRemotePatchString(patchProps);
+ updates = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true).then(aArgs => {
+ Assert.equal(
+ aArgs.updates.length,
+ 0,
+ "the update count" + MSG_SHOULD_EQUAL
+ );
+ });
+
+ debugDump(
+ "testing one update with complete patch with size 0 specified in " +
+ "the update xml"
+ );
+ patchProps = { size: "0" };
+ patches = getRemotePatchString(patchProps);
+ updates = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 0 });
+
+ debugDump(
+ "testing one update with partial patch with size 0 specified in " +
+ "the update xml"
+ );
+ patchProps = { type: "partial", size: "0" };
+ patches = getRemotePatchString(patchProps);
+ updates = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 0 });
+
+ debugDump(
+ "testing that updates for older versions of the application " +
+ "aren't selected"
+ );
+ patches = getRemotePatchString({});
+ patchProps = { type: "partial" };
+ patches += getRemotePatchString(patchProps);
+ updateProps = { appVersion: "1.0pre" };
+ updates = getRemoteUpdateString(updateProps, patches);
+ updateProps = { appVersion: "1.0a" };
+ updates += getRemoteUpdateString(updateProps, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 2 }).then(aArgs => {
+ let bestUpdate = gAUS.selectUpdate(aArgs.updates);
+ Assert.ok(!bestUpdate, "there shouldn't be an update available");
+ });
+
+ debugDump(
+ "testing that updates for the current version of the application " +
+ "are selected"
+ );
+ patches = getRemotePatchString({});
+ patchProps = { type: "partial" };
+ patches += getRemotePatchString(patchProps);
+ updateProps = { appVersion: "1.0" };
+ updates = getRemoteUpdateString(updateProps, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 1 }).then(aArgs => {
+ let bestUpdate = gAUS.selectUpdate(aArgs.updates);
+ Assert.ok(!!bestUpdate, "there should be one update available");
+ Assert.equal(
+ bestUpdate.appVersion,
+ "1.0",
+ "the update appVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.displayVersion,
+ "1.0",
+ "the update displayVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ });
+
+ stop_httpserver(doTestFinish);
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/testConstants.js b/toolkit/mozapps/update/tests/unit_aus_update/testConstants.js
new file mode 100644
index 0000000000..ace6134a38
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/testConstants.js
@@ -0,0 +1,8 @@
+/* eslint-disable no-unused-vars */
+const REL_PATH_DATA = "";
+const URL_HOST = "http://127.0.0.1:8888";
+const URL_PATH_UPDATE_XML = "/" + REL_PATH_DATA + "app_update.sjs";
+const URL_HTTP_UPDATE_SJS = URL_HOST + URL_PATH_UPDATE_XML;
+const CONTINUE_CHECK = "continueCheck";
+const CONTINUE_DOWNLOAD = "continueDownload";
+const CONTINUE_STAGING = "continueStaging";
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/updateAutoPrefMigrate.js b/toolkit/mozapps/update/tests/unit_aus_update/updateAutoPrefMigrate.js
new file mode 100644
index 0000000000..8f62b9458b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/updateAutoPrefMigrate.js
@@ -0,0 +1,74 @@
+/* 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/.
+ */
+
+async function verifyPref(expectedValue) {
+ let configValue = await UpdateUtils.getAppUpdateAutoEnabled();
+ Assert.equal(
+ configValue,
+ expectedValue,
+ "Value returned by getAppUpdateAutoEnabled should have " +
+ "matched the expected value"
+ );
+}
+
+async function run_test() {
+ setupTestCommon(null);
+ standardInit();
+
+ let configFile = getUpdateDirFile(FILE_UPDATE_CONFIG_JSON);
+
+ // Test that, if there is no value to migrate, the default value is set.
+ Services.prefs.setBoolPref("app.update.auto.migrated", false);
+ Services.prefs.clearUserPref("app.update.auto");
+ Assert.ok(!configFile.exists(), "Config file should not exist yet");
+ await verifyPref(
+ UpdateUtils.PER_INSTALLATION_PREFS["app.update.auto"].defaultValue
+ );
+
+ debugDump("about to remove config file");
+ configFile.remove(false);
+
+ // Test migration of a |false| value
+ Services.prefs.setBoolPref("app.update.auto.migrated", false);
+ Services.prefs.setBoolPref("app.update.auto", false);
+ Assert.ok(!configFile.exists(), "Config file should have been removed");
+ await verifyPref(false);
+
+ // Test that migration doesn't happen twice
+ Services.prefs.setBoolPref("app.update.auto", true);
+ await verifyPref(false);
+
+ // If the file is deleted after migration, the default value should be
+ // returned, regardless of the pref value.
+ debugDump("about to remove config file");
+ configFile.remove(false);
+ Assert.ok(!configFile.exists(), "Config file should have been removed");
+ let configValue = await UpdateUtils.getAppUpdateAutoEnabled();
+ Assert.equal(
+ configValue,
+ true,
+ "getAppUpdateAutoEnabled should have returned the default value (true)"
+ );
+
+ // Setting a new value should cause the value to get written out again
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+ await verifyPref(false);
+
+ // Test migration of a |true| value
+ Services.prefs.setBoolPref("app.update.auto.migrated", false);
+ Services.prefs.setBoolPref("app.update.auto", true);
+ configFile.remove(false);
+ Assert.ok(
+ !configFile.exists(),
+ "App update config file should have been removed"
+ );
+ await verifyPref(true);
+
+ // Test that setting app.update.auto without migrating also works
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+ await verifyPref(false);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/updateCheckCombine.js b/toolkit/mozapps/update/tests/unit_aus_update/updateCheckCombine.js
new file mode 100644
index 0000000000..1b2c9ef78c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/updateCheckCombine.js
@@ -0,0 +1,38 @@
+/* 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";
+
+/**
+ * This test checks that multiple foreground update checks are combined into
+ * a single web request.
+ */
+
+add_task(async function setup() {
+ setupTestCommon();
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ setUpdateChannel("test_channel");
+
+ let patches = getRemotePatchString({});
+ let updateString = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updateString);
+});
+
+add_task(async function testUpdateCheckCombine() {
+ gUpdateCheckCount = 0;
+ let check1 = gUpdateChecker.checkForUpdates(gUpdateChecker.FOREGROUND_CHECK);
+ let check2 = gUpdateChecker.checkForUpdates(gUpdateChecker.FOREGROUND_CHECK);
+
+ let result1 = await check1.result;
+ let result2 = await check2.result;
+ Assert.ok(result1.succeeded, "Check 1 should have succeeded");
+ Assert.ok(result2.succeeded, "Check 2 should have succeeded");
+ Assert.equal(gUpdateCheckCount, 1, "Should only have made a single request");
+});
+
+add_task(async function finish() {
+ stop_httpserver(doTestFinish);
+});
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/updateDirectoryMigrate.js b/toolkit/mozapps/update/tests/unit_aus_update/updateDirectoryMigrate.js
new file mode 100644
index 0000000000..6c566d76fc
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/updateDirectoryMigrate.js
@@ -0,0 +1,246 @@
+/* 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/.
+ */
+
+/**
+ * Gets the root directory for the old (unmigrated) updates directory.
+ *
+ * @return nsIFile for the updates root directory.
+ */
+function getOldUpdatesRootDir() {
+ return Services.dirsvc.get(XRE_OLD_UPDATE_ROOT_DIR, Ci.nsIFile);
+}
+
+/**
+ * Gets the old (unmigrated) updates directory.
+ *
+ * @return nsIFile for the updates directory.
+ */
+function getOldUpdatesDir() {
+ let dir = getOldUpdatesRootDir();
+ dir.append(DIR_UPDATES);
+ return dir;
+}
+
+/**
+ * Gets the directory for update patches in the old (unmigrated) updates
+ * directory.
+ *
+ * @return nsIFile for the updates directory.
+ */
+function getOldUpdatesPatchDir() {
+ let dir = getOldUpdatesDir();
+ dir.append(DIR_PATCH);
+ return dir;
+}
+
+/**
+ * Returns either the active or regular update database XML file in the old
+ * (unmigrated) updates directory
+ *
+ * @param isActiveUpdate
+ * If true this will return the active-update.xml otherwise it will
+ * return the updates.xml file.
+ */
+function getOldUpdatesXMLFile(aIsActiveUpdate) {
+ let file = getOldUpdatesRootDir();
+ file.append(aIsActiveUpdate ? FILE_ACTIVE_UPDATE_XML : FILE_UPDATES_XML);
+ return file;
+}
+
+/**
+ * Writes the updates specified to either the active-update.xml or the
+ * updates.xml in the old (unmigrated) update directory
+ *
+ * @param aContent
+ * The updates represented as a string to write to the XML file.
+ * @param isActiveUpdate
+ * If true this will write to the active-update.xml otherwise it will
+ * write to the updates.xml file.
+ */
+function writeUpdatesToOldXMLFile(aContent, aIsActiveUpdate) {
+ writeFile(getOldUpdatesXMLFile(aIsActiveUpdate), aContent);
+}
+
+/**
+ * Writes the given update operation/state to a file in the old (unmigrated)
+ * patch directory, indicating to the patching system what operations need
+ * to be performed.
+ *
+ * @param aStatus
+ * The status value to write.
+ */
+function writeOldStatusFile(aStatus) {
+ let file = getOldUpdatesPatchDir();
+ file.append(FILE_UPDATE_STATUS);
+ writeFile(file, aStatus + "\n");
+}
+
+/**
+ * Writes the given data to the config file in the old (unmigrated)
+ * patch directory.
+ *
+ * @param aData
+ * The config data to write.
+ */
+function writeOldConfigFile(aData) {
+ let file = getOldUpdatesRootDir();
+ file.append(FILE_UPDATE_CONFIG_JSON);
+ writeFile(file, aData);
+}
+
+/**
+ * Gets the specified update log from the old (unmigrated) update directory
+ *
+ * @param aLogLeafName
+ * The leaf name of the log to get.
+ * @return nsIFile for the update log.
+ */
+function getOldUpdateLog(aLogLeafName) {
+ let updateLog = getOldUpdatesDir();
+ if (aLogLeafName == FILE_UPDATE_LOG) {
+ updateLog.append(DIR_PATCH);
+ }
+ updateLog.append(aLogLeafName);
+ return updateLog;
+}
+
+async function run_test() {
+ setupTestCommon(null);
+
+ debugDump(
+ "testing that the update directory is migrated after a successful update"
+ );
+
+ Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS, 5);
+
+ let patchProps = { state: STATE_PENDING };
+ let patches = getLocalPatchString(patchProps);
+ let updates = getLocalUpdateString({}, patches);
+ writeUpdatesToOldXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeOldStatusFile(STATE_SUCCEEDED);
+ writeOldConfigFile('{"app.update.auto":false}');
+
+ let log = getOldUpdateLog(FILE_UPDATE_LOG);
+ writeFile(log, "Last Update Log");
+
+ let oldUninstallPingFile = getOldUpdatesRootDir();
+ const hash = oldUninstallPingFile.leafName;
+ const uninstallPingFilename = `uninstall_ping_${hash}_98537294-d37b-4b8b-a4e9-ab417a5d7a87.json`;
+ oldUninstallPingFile = oldUninstallPingFile.parent.parent;
+ oldUninstallPingFile.append(uninstallPingFilename);
+ const uninstallPingContents = "arbitrary uninstall ping file contents";
+ writeFile(oldUninstallPingFile, uninstallPingContents);
+
+ let oldBackgroundUpdateLog1File = getOldUpdatesRootDir();
+ const oldBackgroundUpdateLog1Filename = "backgroundupdate.moz_log";
+ oldBackgroundUpdateLog1File.append(oldBackgroundUpdateLog1Filename);
+ const oldBackgroundUpdateLog1Contents = "arbitrary log 1 contents";
+ writeFile(oldBackgroundUpdateLog1File, oldBackgroundUpdateLog1Contents);
+
+ let oldBackgroundUpdateLog2File = getOldUpdatesRootDir();
+ const oldBackgroundUpdateLog2Filename = "backgroundupdate.child-1.moz_log";
+ oldBackgroundUpdateLog2File.append(oldBackgroundUpdateLog2Filename);
+ const oldBackgroundUpdateLog2Contents = "arbitrary log 2 contents";
+ writeFile(oldBackgroundUpdateLog2File, oldBackgroundUpdateLog2Contents);
+
+ const pendingPingRelativePath =
+ "backgroundupdate\\datareporting\\glean\\pending_pings\\" +
+ "01234567-89ab-cdef-fedc-0123456789ab";
+ let oldPendingPingFile = getOldUpdatesRootDir();
+ oldPendingPingFile.appendRelativePath(pendingPingRelativePath);
+ const pendingPingContents = "arbitrary pending ping file contents";
+ writeFile(oldPendingPingFile, pendingPingContents);
+
+ standardInit();
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 1,
+ "the update manager update count" + MSG_SHOULD_EQUAL
+ );
+ await waitForUpdateXMLFiles();
+
+ let cancelations = Services.prefs.getIntPref(PREF_APP_UPDATE_CANCELATIONS, 0);
+ Assert.equal(
+ cancelations,
+ 0,
+ "the " + PREF_APP_UPDATE_CANCELATIONS + " preference " + MSG_SHOULD_EQUAL
+ );
+
+ let oldDir = getOldUpdatesRootDir();
+ let newDir = getUpdateDirFile();
+ if (oldDir.path != newDir.path) {
+ Assert.ok(
+ !oldDir.exists(),
+ "Old update directory should have been deleted after migration"
+ );
+ }
+
+ log = getUpdateDirFile(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST);
+
+ log = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(
+ readFile(log),
+ "Last Update Log",
+ "the last update log contents" + MSG_SHOULD_EQUAL
+ );
+
+ log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST);
+
+ let dir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ Assert.equal(
+ await UpdateUtils.getAppUpdateAutoEnabled(),
+ false,
+ "Automatic update download setting should have been migrated."
+ );
+
+ let newUninstallPing = newDir.parent.parent;
+ newUninstallPing.append(uninstallPingFilename);
+ Assert.ok(newUninstallPing.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(
+ readFile(newUninstallPing),
+ uninstallPingContents,
+ "the uninstall ping contents" + MSG_SHOULD_EQUAL
+ );
+
+ let newBackgroundUpdateLog1File = newDir.clone();
+ newBackgroundUpdateLog1File.append(oldBackgroundUpdateLog1Filename);
+ Assert.ok(newBackgroundUpdateLog1File.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(
+ readFile(newBackgroundUpdateLog1File),
+ oldBackgroundUpdateLog1Contents,
+ "background log file 1 contents" + MSG_SHOULD_EQUAL
+ );
+
+ let newBackgroundUpdateLog2File = newDir.clone();
+ newBackgroundUpdateLog2File.append(oldBackgroundUpdateLog2Filename);
+ Assert.ok(newBackgroundUpdateLog2File.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(
+ readFile(newBackgroundUpdateLog2File),
+ oldBackgroundUpdateLog2Contents,
+ "background log file 2 contents" + MSG_SHOULD_EQUAL
+ );
+
+ let newPendingPing = newDir.clone();
+ newPendingPing.appendRelativePath(pendingPingRelativePath);
+ Assert.ok(newPendingPing.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(
+ readFile(newPendingPing),
+ pendingPingContents,
+ "the pending ping contents" + MSG_SHOULD_EQUAL
+ );
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js b/toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js
new file mode 100644
index 0000000000..f15e51827e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js
@@ -0,0 +1,593 @@
+/* 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/.
+ */
+
+function run_test() {
+ setupTestCommon();
+
+ debugDump(
+ "testing addition of a successful update to " +
+ FILE_UPDATES_XML +
+ " and verification of update properties including the format " +
+ "prior to bug 530872"
+ );
+
+ setUpdateChannel("test_channel");
+
+ let patchProps = {
+ type: "partial",
+ url: "http://partial/",
+ size: "86",
+ selected: "true",
+ state: STATE_PENDING,
+ custom1: 'custom1_attr="custom1 patch value"',
+ custom2: 'custom2_attr="custom2 patch value"',
+ };
+ let patches = getLocalPatchString(patchProps);
+ let updateProps = {
+ type: "major",
+ name: "New",
+ displayVersion: "version 4",
+ appVersion: "4.0",
+ buildID: "20070811053724",
+ detailsURL: "http://details1/",
+ serviceURL: "http://service1/",
+ installDate: "1238441300314",
+ statusText: "test status text",
+ isCompleteUpdate: "false",
+ channel: "test_channel",
+ foregroundDownload: "true",
+ promptWaitTime: "345600",
+ previousAppVersion: "3.0",
+ custom1: 'custom1_attr="custom1 value"',
+ custom2: 'custom2_attr="custom2 value"',
+ };
+ let updates = getLocalUpdateString(updateProps, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_SUCCEEDED);
+
+ patchProps = {
+ type: "complete",
+ url: "http://complete/",
+ size: "75",
+ selected: "true",
+ state: STATE_FAILED,
+ custom1: 'custom3_attr="custom3 patch value"',
+ custom2: 'custom4_attr="custom4 patch value"',
+ };
+ patches = getLocalPatchString(patchProps);
+ updateProps = {
+ type: "minor",
+ name: "Existing",
+ appVersion: "3.0",
+ detailsURL: "http://details2/",
+ serviceURL: "http://service2/",
+ statusText: getString("patchApplyFailure"),
+ isCompleteUpdate: "true",
+ channel: "test_channel",
+ foregroundDownload: "false",
+ promptWaitTime: "691200",
+ custom1: 'custom3_attr="custom3 value"',
+ custom2: 'custom4_attr="custom4 value"',
+ };
+ updates = getLocalUpdateString(updateProps, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), false);
+
+ standardInit();
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 2,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL
+ );
+
+ debugDump("checking the first update properties");
+ let update = gUpdateManager
+ .getUpdateAt(0)
+ .QueryInterface(Ci.nsIWritablePropertyBag);
+ Assert.equal(
+ update.state,
+ STATE_SUCCEEDED,
+ "the update state attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.type,
+ "major",
+ "the update type attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.name,
+ "New",
+ "the update name attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.displayVersion,
+ "version 4",
+ "the update displayVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.appVersion,
+ "4.0",
+ "the update appVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.buildID,
+ "20070811053724",
+ "the update buildID attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.detailsURL,
+ "http://details1/",
+ "the update detailsURL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.serviceURL,
+ "http://service1/",
+ "the update serviceURL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.installDate,
+ "1238441300314",
+ "the update installDate attribute" + MSG_SHOULD_EQUAL
+ );
+ // statusText is updated
+ Assert.equal(
+ update.statusText,
+ getString("installSuccess"),
+ "the update statusText attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ !update.isCompleteUpdate,
+ "the update isCompleteUpdate attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.channel,
+ "test_channel",
+ "the update channel attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.promptWaitTime,
+ "345600",
+ "the update promptWaitTime attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.previousAppVersion,
+ "3.0",
+ "the update previousAppVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ // Custom attributes
+ Assert.equal(
+ update.getProperty("custom1_attr"),
+ "custom1 value",
+ "the update custom1_attr property" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.getProperty("custom2_attr"),
+ "custom2 value",
+ "the update custom2_attr property" + MSG_SHOULD_EQUAL
+ );
+ // nsIPropertyBag enumerator
+ debugDump("checking the first update enumerator");
+ Assert.ok(
+ update.enumerator instanceof Ci.nsISimpleEnumerator,
+ "update enumerator should be an instance of nsISimpleEnumerator"
+ );
+ let results = Array.from(update.enumerator);
+ Assert.equal(
+ results.length,
+ 3,
+ "the length of the array created from the update enumerator" +
+ MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ results.every(prop => prop instanceof Ci.nsIProperty),
+ "the objects in the array created from the update enumerator " +
+ "should all be an instance of nsIProperty"
+ );
+ Assert.equal(
+ results[0].name,
+ "custom1_attr",
+ "the first property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[0].value,
+ "custom1 value",
+ "the first property value" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[1].name,
+ "custom2_attr",
+ "the second property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[1].value,
+ "custom2 value",
+ "the second property value" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[2].name,
+ "foregroundDownload",
+ "the second property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[2].value,
+ "true",
+ "the third property value" + MSG_SHOULD_EQUAL
+ );
+
+ debugDump("checking the first update patch properties");
+ let patch = update.selectedPatch.QueryInterface(Ci.nsIWritablePropertyBag);
+ Assert.equal(
+ patch.type,
+ "partial",
+ "the update patch type attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.URL,
+ "http://partial/",
+ "the update patch URL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.size,
+ "86",
+ "the update patch size attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ !!patch.selected,
+ "the update patch selected attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.state,
+ STATE_SUCCEEDED,
+ "the update patch state attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.getProperty("custom1_attr"),
+ "custom1 patch value",
+ "the update patch custom1_attr property" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.getProperty("custom2_attr"),
+ "custom2 patch value",
+ "the update patch custom2_attr property" + MSG_SHOULD_EQUAL
+ );
+ // nsIPropertyBag enumerator
+ debugDump("checking the first update patch enumerator");
+ Assert.ok(
+ patch.enumerator instanceof Ci.nsISimpleEnumerator,
+ "patch enumerator should be an instance of nsISimpleEnumerator"
+ );
+ results = Array.from(patch.enumerator);
+ Assert.equal(
+ results.length,
+ 2,
+ "the length of the array created from the patch enumerator" +
+ MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ results.every(prop => prop instanceof Ci.nsIProperty),
+ "the objects in the array created from the patch enumerator " +
+ "should all be an instance of nsIProperty"
+ );
+ Assert.equal(
+ results[0].name,
+ "custom1_attr",
+ "the first property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[0].value,
+ "custom1 patch value",
+ "the first property value" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[1].name,
+ "custom2_attr",
+ "the second property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[1].value,
+ "custom2 patch value",
+ "the second property value" + MSG_SHOULD_EQUAL
+ );
+
+ debugDump("checking the second update properties");
+ update = gUpdateManager
+ .getUpdateAt(1)
+ .QueryInterface(Ci.nsIWritablePropertyBag);
+ Assert.equal(
+ update.state,
+ STATE_FAILED,
+ "the update state attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.name,
+ "Existing",
+ "the update name attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.type,
+ "minor",
+ "the update type attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.displayVersion,
+ "3.0",
+ "the update displayVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.appVersion,
+ "3.0",
+ "the update appVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.detailsURL,
+ "http://details2/",
+ "the update detailsURL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.serviceURL,
+ "http://service2/",
+ "the update serviceURL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.installDate,
+ "1238441400314",
+ "the update installDate attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.statusText,
+ getString("patchApplyFailure"),
+ "the update statusText attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.buildID,
+ "20080811053724",
+ "the update buildID attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ !!update.isCompleteUpdate,
+ "the update isCompleteUpdate attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.channel,
+ "test_channel",
+ "the update channel attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.promptWaitTime,
+ "691200",
+ "the update promptWaitTime attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.previousAppVersion,
+ "1.0",
+ "the update previousAppVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ // Custom attributes
+ Assert.equal(
+ update.getProperty("custom3_attr"),
+ "custom3 value",
+ "the update custom3_attr property" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.getProperty("custom4_attr"),
+ "custom4 value",
+ "the update custom4_attr property" + MSG_SHOULD_EQUAL
+ );
+ // nsIPropertyBag enumerator
+ debugDump("checking the second update enumerator");
+ Assert.ok(
+ update.enumerator instanceof Ci.nsISimpleEnumerator,
+ "update enumerator should be an instance of nsISimpleEnumerator"
+ );
+ results = Array.from(update.enumerator);
+ Assert.equal(
+ results.length,
+ 3,
+ "the length of the array created from the update enumerator" +
+ MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ results.every(prop => prop instanceof Ci.nsIProperty),
+ "the objects in the array created from the update enumerator " +
+ "should all be an instance of nsIProperty"
+ );
+ Assert.equal(
+ results[0].name,
+ "custom3_attr",
+ "the first property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[0].value,
+ "custom3 value",
+ "the first property value" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[1].name,
+ "custom4_attr",
+ "the second property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[1].value,
+ "custom4 value",
+ "the second property value" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[2].name,
+ "foregroundDownload",
+ "the third property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[2].value,
+ "false",
+ "the third property value" + MSG_SHOULD_EQUAL
+ );
+
+ debugDump("checking the second update patch properties");
+ patch = update.selectedPatch.QueryInterface(Ci.nsIWritablePropertyBag);
+ Assert.equal(
+ patch.type,
+ "complete",
+ "the update patch type attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.URL,
+ "http://complete/",
+ "the update patch URL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.size,
+ "75",
+ "the update patch size attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ !!patch.selected,
+ "the update patch selected attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.state,
+ STATE_FAILED,
+ "the update patch state attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.getProperty("custom3_attr"),
+ "custom3 patch value",
+ "the update patch custom3_attr property" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.getProperty("custom4_attr"),
+ "custom4 patch value",
+ "the update patch custom4_attr property" + MSG_SHOULD_EQUAL
+ );
+ // nsIPropertyBag enumerator
+ debugDump("checking the second update patch enumerator");
+ Assert.ok(
+ patch.enumerator instanceof Ci.nsISimpleEnumerator,
+ "patch enumerator should be an instance of nsISimpleEnumerator"
+ );
+ results = Array.from(patch.enumerator);
+ Assert.equal(
+ results.length,
+ 2,
+ "the length of the array created from the patch enumerator" +
+ MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ results.every(prop => prop instanceof Ci.nsIProperty),
+ "the objects in the array created from the patch enumerator " +
+ "should all be an instance of nsIProperty"
+ );
+ Assert.equal(
+ results[0].name,
+ "custom3_attr",
+ "the first property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[0].value,
+ "custom3 patch value",
+ "the first property value" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[1].name,
+ "custom4_attr",
+ "the second property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[1].value,
+ "custom4 patch value",
+ "the second property value" + MSG_SHOULD_EQUAL
+ );
+
+ let attrNames = [
+ "appVersion",
+ "buildID",
+ "channel",
+ "detailsURL",
+ "displayVersion",
+ "elevationFailure",
+ "errorCode",
+ "installDate",
+ "isCompleteUpdate",
+ "name",
+ "previousAppVersion",
+ "promptWaitTime",
+ "serviceURL",
+ "state",
+ "statusText",
+ "type",
+ "unsupported",
+ ];
+ checkIllegalProperties(update, attrNames);
+
+ attrNames = [
+ "errorCode",
+ "finalURL",
+ "selected",
+ "size",
+ "state",
+ "type",
+ "URL",
+ ];
+ checkIllegalProperties(patch, attrNames);
+
+ executeSoon(doTestFinish);
+}
+
+function checkIllegalProperties(object, propertyNames) {
+ let objectName =
+ object instanceof Ci.nsIUpdate ? "nsIUpdate" : "nsIUpdatePatch";
+ propertyNames.forEach(function (name) {
+ // Check that calling getProperty, setProperty, and deleteProperty on an
+ // nsIUpdate attribute throws NS_ERROR_ILLEGAL_VALUE
+ let result = 0;
+ try {
+ object.getProperty(name);
+ } catch (e) {
+ result = e.result;
+ }
+ Assert.equal(
+ result,
+ Cr.NS_ERROR_ILLEGAL_VALUE,
+ "calling getProperty using an " +
+ objectName +
+ " attribute " +
+ "name should throw NS_ERROR_ILLEGAL_VALUE"
+ );
+
+ result = 0;
+ try {
+ object.setProperty(name, "value");
+ } catch (e) {
+ result = e.result;
+ }
+ Assert.equal(
+ result,
+ Cr.NS_ERROR_ILLEGAL_VALUE,
+ "calling setProperty using an " +
+ objectName +
+ " attribute " +
+ "name should throw NS_ERROR_ILLEGAL_VALUE"
+ );
+
+ result = 0;
+ try {
+ object.deleteProperty(name);
+ } catch (e) {
+ result = e.result;
+ }
+ Assert.equal(
+ result,
+ Cr.NS_ERROR_ILLEGAL_VALUE,
+ "calling deleteProperty using an " +
+ objectName +
+ " attribute " +
+ "name should throw NS_ERROR_ILLEGAL_VALUE"
+ );
+ });
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/updateSyncManager.js b/toolkit/mozapps/update/tests/unit_aus_update/updateSyncManager.js
new file mode 100644
index 0000000000..9123f9e1e3
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/updateSyncManager.js
@@ -0,0 +1,105 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This test verifies that the update sync manager is working correctly by
+// a) making sure we're the only one that's opened it to begin with, and then
+// b) starting a second copy of the same binary and making sure we can tell we
+// are no longer the only one that's opened it.
+
+const { Subprocess } = ChromeUtils.importESModule(
+ "resource://gre/modules/Subprocess.sys.mjs"
+);
+
+// Save off the real GRE directory and binary path before we register our
+// mock directory service which overrides them both.
+const thisBinary = Services.dirsvc.get("XREExeF", Ci.nsIFile);
+const greDir = Services.dirsvc.get("GreD", Ci.nsIFile);
+
+add_task(async function () {
+ setupTestCommon();
+
+ // First check that we believe we exclusively hold the lock.
+ let syncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
+ Ci.nsIUpdateSyncManager
+ );
+ Assert.ok(
+ !syncManager.isOtherInstanceRunning(),
+ "no other instance is running yet"
+ );
+
+ // Now start a second copy of this xpcshell binary so that something else
+ // takes the same lock. First we'll define its command line.
+ // Most of the child's code is in a separate script file, so all the command
+ // line has to do is set up a few required path strings we need to pass
+ // through to the child, and then include the script file.
+ const args = [
+ "-g",
+ greDir.path,
+ "-e",
+ `
+ const customGreDirPath = "${getApplyDirFile(
+ DIR_RESOURCES
+ ).path.replaceAll("\\", "\\\\")}";
+ const customGreBinDirPath = "${getApplyDirFile(DIR_MACOS).path.replaceAll(
+ "\\",
+ "\\\\"
+ )}";
+ const customExePath = "${getApplyDirFile(
+ DIR_MACOS + FILE_APP_BIN
+ ).path.replaceAll("\\", "\\\\")}";
+ const customUpdDirPath = "${getMockUpdRootD().path.replaceAll(
+ "\\",
+ "\\\\"
+ )}";
+ const customOldUpdDirPath = "${getMockUpdRootD(true).path.replaceAll(
+ "\\",
+ "\\\\"
+ )}";
+ `,
+ "-f",
+ getTestDirFile("syncManagerTestChild.js").path,
+ ];
+
+ // Run the second copy two times, to show the lock is usable after having
+ // been closed.
+ for (let runs = 0; runs < 2; runs++) {
+ // Now we can actually invoke the process.
+ debugDump(
+ `launching child process at ${thisBinary.path} with args ${args}`
+ );
+ Subprocess.call({
+ command: thisBinary.path,
+ arguments: args,
+ stderr: "stdout",
+ });
+
+ // It will take the new xpcshell a little time to start up, but we should see
+ // the effect on the lock within at most a few seconds.
+ await TestUtils.waitForCondition(
+ () => syncManager.isOtherInstanceRunning(),
+ "waiting for child process to take the lock"
+ ).catch(e => {
+ // Rather than throwing out of waitForCondition(), catch and log the failure
+ // manually so that we get output that's a bit more readable.
+ Assert.ok(
+ syncManager.isOtherInstanceRunning(),
+ "child process has the lock"
+ );
+ });
+
+ // The lock should have been closed when the process exited, but we'll allow
+ // a little time for the OS to clean up the handle.
+ await TestUtils.waitForCondition(
+ () => !syncManager.isOtherInstanceRunning(),
+ "waiting for child process to release the lock"
+ ).catch(e => {
+ Assert.ok(
+ !syncManager.isOtherInstanceRunning(),
+ "child process has released the lock"
+ );
+ });
+ }
+
+ doTestFinish();
+});
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js b/toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js
new file mode 100644
index 0000000000..6fd1cf8a73
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js
@@ -0,0 +1,26 @@
+/* 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/.
+ */
+
+/* Application Update URL Construction Tests */
+
+/**
+ * Tests for the majority of values are located in
+ * toolkit/modules/tests/xpcshell/test_UpdateUtils_updatechannel.js
+ * toolkit/modules/tests/xpcshell/test_UpdateUtils_url.js
+ */
+
+async function run_test() {
+ const URL_PREFIX = URL_HOST + "/";
+ setupTestCommon();
+ let url = URL_PREFIX;
+ debugDump("testing url force param is present: " + url);
+ setUpdateURL(url);
+ await waitForUpdateCheck(false, { url: url + "?force=1" });
+ url = URL_PREFIX + "?testparam=1";
+ debugDump("testing url force param when there is already a param: " + url);
+ setUpdateURL(url);
+ await waitForUpdateCheck(false, { url: url + "&force=1" });
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/verifyChannelPrefsFile.js b/toolkit/mozapps/update/tests/unit_aus_update/verifyChannelPrefsFile.js
new file mode 100644
index 0000000000..4cca77c73d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/verifyChannelPrefsFile.js
@@ -0,0 +1,38 @@
+/* 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/.
+ */
+
+/**
+ * This test exists solely to ensure that channel-prefs.js is not changed.
+ * If it does get changed, it will cause a variation of Bug 1431342.
+ * To summarize, our updater doesn't update that file. But, on macOS, it is
+ * still used to compute the application's signature. This means that if Firefox
+ * updates and that file has been changed, the signature no will no longer
+ * validate.
+ */
+
+const expectedChannelPrefsContents = `/* 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/. */
+//
+// This pref is in its own file for complex reasons. See the comment in
+// browser/app/Makefile.in, bug 756325, and bug 1431342 for details. Do not add
+// other prefs to this file.
+
+pref("app.update.channel", "${UpdateUtils.UpdateChannel}");
+`;
+
+async function run_test() {
+ let channelPrefsFile = Services.dirsvc.get("GreD", Ci.nsIFile);
+ channelPrefsFile.append("defaults");
+ channelPrefsFile.append("pref");
+ channelPrefsFile.append("channel-prefs.js");
+
+ const contents = await IOUtils.readUTF8(channelPrefsFile.path);
+ Assert.equal(
+ contents,
+ expectedChannelPrefsContents,
+ "Channel Prefs file should should not change"
+ );
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini b/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini
new file mode 100644
index 0000000000..bb0a575d72
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini
@@ -0,0 +1,76 @@
+# 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/.
+
+[DEFAULT]
+tags = appupdate
+head = head_update.js
+skip-if =
+ (os == 'win' && (ccov || msix)) # Our updater is disabled in MSIX builds
+prefs =
+ app.update.staging.enabled=false
+support-files =
+ ../data/shared.js
+ ../data/sharedUpdateXML.js
+ ../data/xpcshellUtilsAUS.js
+ ../data/app_update.sjs
+ testConstants.js
+ ../data/simple.mar
+
+[ausReadStrings.js]
+[canCheckForAndCanApplyUpdates.js]
+[urlConstruction.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[updateManagerXML.js]
+[remoteUpdateXML.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[cleanupDownloadingForOlderAppVersion.js]
+[cleanupDownloadingForDifferentChannel.js]
+[cleanupDownloadingForSameVersionAndBuildID.js]
+[cleanupDownloadingIncorrectStatus.js]
+[cleanupPendingVersionFileIncorrectStatus.js]
+[cleanupSuccessLogMove.js]
+[cleanupSuccessLogsFIFO.js]
+[downloadInterruptedOffline.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[downloadInterruptedNoRecovery.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[downloadInterruptedRecovery.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[downloadResumeForSameAppVersion.js]
+[languagePackUpdates.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[updateSyncManager.js]
+[updateAutoPrefMigrate.js]
+skip-if = os != 'win'
+reason = Update pref migration is currently Windows only
+[updateDirectoryMigrate.js]
+skip-if = os != 'win'
+reason = Update directory migration is currently Windows only
+[multiUpdate.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[perInstallationPrefs.js]
+[onlyDownloadUpdatesThisSession.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[disableBackgroundUpdatesBackgroundTask.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[disableBackgroundUpdatesNonBackgroundTask.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[ensureExperimentToRolloutTransitionPerformed.js]
+run-if = os == 'win' && appname == 'firefox'
+reason = Feature is Firefox-specific and Windows-specific.
+[verifyChannelPrefsFile.js]
+run-if = appname == 'firefox'
+reason = File being verified is Firefox-specific.
+[backgroundUpdateTaskInternalUpdater.js]
+[updateCheckCombine.js]