From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../update/tests/unit_aus_update/ausReadStrings.js | 33 ++ .../backgroundUpdateTaskInternalUpdater.js | 85 +++ .../canCheckForAndCanApplyUpdates.js | 62 +++ .../cleanupDownloadingForDifferentChannel.js | 60 +++ .../cleanupDownloadingForOlderAppVersion.js | 58 ++ .../cleanupDownloadingForSameVersionAndBuildID.js | 59 ++ .../cleanupDownloadingIncorrectStatus.js | 57 ++ .../cleanupPendingVersionFileIncorrectStatus.js | 57 ++ .../tests/unit_aus_update/cleanupSuccessLogMove.js | 77 +++ .../unit_aus_update/cleanupSuccessLogsFIFO.js | 226 ++++++++ .../disableBackgroundUpdatesBackgroundTask.js | 48 ++ .../disableBackgroundUpdatesNonBackgroundTask.js | 41 ++ .../downloadInterruptedNoRecovery.js | 23 + .../unit_aus_update/downloadInterruptedOffline.js | 21 + .../unit_aus_update/downloadInterruptedRecovery.js | 26 + .../downloadResumeForSameAppVersion.js | 38 ++ ...ensureExperimentToRolloutTransitionPerformed.js | 111 ++++ .../update/tests/unit_aus_update/head_update.js | 8 + .../tests/unit_aus_update/languagePackUpdates.js | 291 ++++++++++ .../update/tests/unit_aus_update/multiUpdate.js | 398 ++++++++++++++ .../onlyDownloadUpdatesThisSession.js | 69 +++ .../tests/unit_aus_update/perInstallationPrefs.js | 238 +++++++++ .../tests/unit_aus_update/remoteUpdateXML.js | 327 ++++++++++++ .../update/tests/unit_aus_update/testConstants.js | 8 + .../tests/unit_aus_update/updateAutoPrefMigrate.js | 74 +++ .../tests/unit_aus_update/updateCheckCombine.js | 38 ++ .../unit_aus_update/updateDirectoryMigrate.js | 246 +++++++++ .../tests/unit_aus_update/updateManagerXML.js | 593 +++++++++++++++++++++ .../tests/unit_aus_update/updateSyncManager.js | 105 ++++ .../tests/unit_aus_update/urlConstruction.js | 26 + .../unit_aus_update/verifyChannelPrefsFile.js | 38 ++ .../update/tests/unit_aus_update/xpcshell.toml | 89 ++++ 32 files changed, 3630 insertions(+) create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/ausReadStrings.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/backgroundUpdateTaskInternalUpdater.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/canCheckForAndCanApplyUpdates.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForDifferentChannel.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForOlderAppVersion.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForSameVersionAndBuildID.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingIncorrectStatus.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/cleanupPendingVersionFileIncorrectStatus.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesBackgroundTask.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesNonBackgroundTask.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedNoRecovery.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedOffline.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/ensureExperimentToRolloutTransitionPerformed.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/head_update.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/languagePackUpdates.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/onlyDownloadUpdatesThisSession.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/perInstallationPrefs.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/testConstants.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/updateAutoPrefMigrate.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/updateCheckCombine.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/updateDirectoryMigrate.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/updateSyncManager.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/verifyChannelPrefsFile.js create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/xpcshell.toml (limited to 'toolkit/mozapps/update/tests/unit_aus_update') 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..c05b4bfd73 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js @@ -0,0 +1,77 @@ +/* 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"); + log = getUpdateDirFile(FILE_UPDATE_ELEVATED_LOG); + writeFile(log, "Last Update Elevated 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_UPDATE_ELEVATED_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_LAST_UPDATE_ELEVATED_LOG); + Assert.ok(log.exists(), MSG_SHOULD_EXIST); + Assert.equal( + readFile(log), + "Last Update Elevated Log", + "the last update elevated log contents" + MSG_SHOULD_EQUAL + ); + + log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG); + Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST); + + log = getUpdateDirFile(FILE_BACKUP_UPDATE_ELEVATED_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..c93644bc0e --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js @@ -0,0 +1,226 @@ +/* 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/. + */ + +/** + * Creates the specified log files and makes sure that they are rotated properly + * when we successfully update and cleanup the old update files. + * + * We test various starting combinations of logs because we want to make sure + * that, for example, when "update.log" exists but "update-elevated.log" does + * not, that we still move "last-update-elevated.log" to + * "backup-update-elevated.log". Allowing the files to be mismatched makes them + * much less useful. + * + * When running this, either `createUpdateLog` or `createUpdateElevatedLog` + * should be `true` because otherwise it doesn't make sense that an update just + * ran successfully. + * + * @param createUpdateLog + * If `true`, "update.log" will be created at the start of the test. + * @param createLastUpdateLog + * If `true`, "last-update.log" will be created at the start of the test. + * @param createBackupUpdateLog + * If `true`, "backup-update.log" will be created at the start of the + * test. + * @param createUpdateElevatedLog + * If `true`, "update-elevated.log" will be created at the start of the + * test. + * @param createLastUpdateElevatedLog + * If `true`, "last-update-elevated.log" will be created at the start of + * the test. + * @param createBackupUpdateElevatedLog + * If `true`, "backup-update-elevated.log" will be created at the start + * of the test. + */ +async function testCleanupSuccessLogsFIFO( + createUpdateLog, + createLastUpdateLog, + createBackupUpdateLog, + createUpdateElevatedLog, + createLastUpdateElevatedLog, + createBackupUpdateElevatedLog +) { + logTestInfo( + `createUpdateLog=${createUpdateLog} ` + + `createLastUpdateLog=${createLastUpdateLog} ` + + `createBackupUpdateLog=${createBackupUpdateLog} ` + + `createUpdateElevatedLog=${createUpdateElevatedLog} ` + + `createLastUpdateElevatedLog=${createLastUpdateElevatedLog} ` + + `createBackupUpdateElevatedLog=${createBackupUpdateElevatedLog}` + ); + + let patchProps = { state: STATE_PENDING }; + let patches = getLocalPatchString(patchProps); + let updates = getLocalUpdateString({}, patches); + writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true); + writeStatusFile(STATE_SUCCEEDED); + + const createOrDeleteFile = (shouldCreate, filename, contents) => { + let log = getUpdateDirFile(filename); + if (shouldCreate) { + writeFile(log, contents); + } else { + try { + log.remove(false); + } catch (ex) { + if (ex.result != Cr.NS_ERROR_FILE_NOT_FOUND) { + throw ex; + } + } + } + }; + + createOrDeleteFile( + createLastUpdateLog, + FILE_LAST_UPDATE_LOG, + "Backup Update Log" + ); + createOrDeleteFile( + createBackupUpdateLog, + FILE_BACKUP_UPDATE_LOG, + "To Be Deleted Backup Update Log" + ); + createOrDeleteFile(createUpdateLog, FILE_UPDATE_LOG, "Last Update Log"); + createOrDeleteFile( + createLastUpdateElevatedLog, + FILE_LAST_UPDATE_ELEVATED_LOG, + "Backup Update Elevated Log" + ); + createOrDeleteFile( + createBackupUpdateElevatedLog, + FILE_BACKUP_UPDATE_ELEVATED_LOG, + "To Be Deleted Backup Update Elevated Log" + ); + createOrDeleteFile( + createUpdateElevatedLog, + FILE_UPDATE_ELEVATED_LOG, + "Last Update Elevated 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 log = getUpdateDirFile(FILE_UPDATE_LOG); + Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST); + + log = getUpdateDirFile(FILE_UPDATE_ELEVATED_LOG); + Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST); + + log = getUpdateDirFile(FILE_LAST_UPDATE_LOG); + if (createUpdateLog) { + Assert.ok(log.exists(), MSG_SHOULD_EXIST); + Assert.equal( + readFile(log), + "Last Update Log", + "the last update log contents" + MSG_SHOULD_EQUAL + ); + } else { + Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST); + } + + log = getUpdateDirFile(FILE_LAST_UPDATE_ELEVATED_LOG); + if (createUpdateElevatedLog) { + Assert.ok(log.exists(), MSG_SHOULD_EXIST); + Assert.equal( + readFile(log), + "Last Update Elevated Log", + "the last update log contents" + MSG_SHOULD_EQUAL + ); + } else { + Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST); + } + + log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG); + if (createLastUpdateLog) { + Assert.ok(log.exists(), MSG_SHOULD_EXIST); + Assert.equal( + readFile(log), + "Backup Update Log", + "the backup update log contents" + MSG_SHOULD_EQUAL + ); + } else if (!createLastUpdateElevatedLog && createBackupUpdateLog) { + // This isn't really a conventional FIFO. We don't shift the old backup logs + // out if, for some reason the last pair of logs didn't exist. + Assert.ok(log.exists(), MSG_SHOULD_EXIST); + Assert.equal( + readFile(log), + "To Be Deleted Backup Update Log", + "the backup update log contents" + MSG_SHOULD_EQUAL + ); + } else { + Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST); + } + + log = getUpdateDirFile(FILE_BACKUP_UPDATE_ELEVATED_LOG); + if (createLastUpdateElevatedLog) { + Assert.ok(log.exists(), MSG_SHOULD_EXIST); + Assert.equal( + readFile(log), + "Backup Update Elevated Log", + "the backup update log contents" + MSG_SHOULD_EQUAL + ); + } else if (!createLastUpdateLog && createBackupUpdateElevatedLog) { + // This isn't really a conventional FIFO. We don't shift the old backup logs + // out if, for some reason the last pair of logs didn't exist. + Assert.ok(log.exists(), MSG_SHOULD_EXIST); + Assert.equal( + readFile(log), + "To Be Deleted Backup Update Elevated Log", + "the backup update log contents" + MSG_SHOULD_EQUAL + ); + } else { + Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST); + } + + let dir = getUpdateDirFile(DIR_PATCH); + Assert.ok(dir.exists(), MSG_SHOULD_EXIST); + + // Clean up so this function can run again. + reloadUpdateManagerData(true); +} + +async function run_test() { + debugDump("testing update logs are first in first out deleted"); + setupTestCommon(); + + // This runs a bunch of tests (I think 48 of them: 2^6 - 2^4), but each test + // is fairly simple and doesn't do a lot of waiting. So it seems unlikely that + // it will exceed its timeout. + for (const createUpdateLog of [true, false]) { + for (const createUpdateElevatedLog of [true, false]) { + if (!createUpdateLog && !createUpdateElevatedLog) { + continue; + } + for (const createLastUpdateLog of [true, false]) { + for (const createLastUpdateElevatedLog of [true, false]) { + for (const createBackupUpdateLog of [true, false]) { + for (const createBackupUpdateElevatedLog of [true, false]) { + await testCleanupSuccessLogsFIFO( + createUpdateLog, + createLastUpdateLog, + createBackupUpdateLog, + createUpdateElevatedLog, + createLastUpdateElevatedLog, + createBackupUpdateElevatedLog + ); + } + } + } + } + } + } + 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..0ae35ef77a --- /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 { XPIExports } = ChromeUtils.importESModule( + "resource://gre/modules/addons/XPIExports.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 = Promise.withResolvers(); + XPIExports.XPIInstall.stageLangpacksForAppUpdate = ( + appVersion, + platformVersion + ) => { + let result = Promise.withResolvers(); + 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; + XPIExports.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..3767a44feb --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js @@ -0,0 +1,398 @@ +/* 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.importESModule( + "resource://testing-common/httpd.sys.mjs" +); + +// 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: "", + 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 = ""; + 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.toml b/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.toml new file mode 100644 index 0000000000..74790016e4 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.toml @@ -0,0 +1,89 @@ +[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"] + +["backgroundUpdateTaskInternalUpdater.js"] + +["canCheckForAndCanApplyUpdates.js"] + +["cleanupDownloadingForDifferentChannel.js"] + +["cleanupDownloadingForOlderAppVersion.js"] + +["cleanupDownloadingForSameVersionAndBuildID.js"] + +["cleanupDownloadingIncorrectStatus.js"] + +["cleanupPendingVersionFileIncorrectStatus.js"] + +["cleanupSuccessLogMove.js"] + +["cleanupSuccessLogsFIFO.js"] + +["disableBackgroundUpdatesBackgroundTask.js"] +skip-if = ["socketprocess_networking"] # Bug 1759035 + +["disableBackgroundUpdatesNonBackgroundTask.js"] +skip-if = ["socketprocess_networking"] # Bug 1759035 + +["downloadInterruptedNoRecovery.js"] +skip-if = ["socketprocess_networking"] # Bug 1759035 + +["downloadInterruptedOffline.js"] +skip-if = ["socketprocess_networking"] # Bug 1759035 + +["downloadInterruptedRecovery.js"] +skip-if = ["socketprocess_networking"] # Bug 1759035 + +["downloadResumeForSameAppVersion.js"] + +["ensureExperimentToRolloutTransitionPerformed.js"] +run-if = ["os == 'win' && appname == 'firefox'"] +reason = "Feature is Firefox-specific and Windows-specific." + +["languagePackUpdates.js"] +skip-if = ["socketprocess_networking"] # Bug 1759035 + +["multiUpdate.js"] +skip-if = ["socketprocess_networking"] # Bug 1759035 + +["onlyDownloadUpdatesThisSession.js"] +skip-if = ["socketprocess_networking"] # Bug 1759035 + +["perInstallationPrefs.js"] + +["remoteUpdateXML.js"] +skip-if = ["socketprocess_networking"] # Bug 1759035 + +["updateAutoPrefMigrate.js"] +run-if = ["os == 'win'"] +reason = "Update pref migration is currently Windows only" + +["updateCheckCombine.js"] + +["updateDirectoryMigrate.js"] +run-if = ["os == 'win'"] +reason = "Update directory migration is currently Windows only" + +["updateManagerXML.js"] + +["updateSyncManager.js"] + +["urlConstruction.js"] +skip-if = ["socketprocess_networking"] # Bug 1759035 + +["verifyChannelPrefsFile.js"] +run-if = ["appname == 'firefox'"] +reason = "File being verified is Firefox-specific." -- cgit v1.2.3