From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../update/tests/unit_aus_update/multiUpdate.js | 396 +++++++++++++++++++++ 1 file changed, 396 insertions(+) create mode 100644 toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js (limited to 'toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js') diff --git a/toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js b/toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js new file mode 100644 index 0000000000..73a262e527 --- /dev/null +++ b/toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js @@ -0,0 +1,396 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/** + * This tests the multiple update downloads per Firefox session feature. + * + * This test does some unusual things, compared to the other files in this + * directory. We want to start the updates with aus.checkForBackgroundUpdates() + * to ensure that we test the whole flow. Other tests start update with things + * like aus.downloadUpdate(), but that bypasses some of the exact checks that we + * are trying to test as part of the multiupdate flow. + * + * In order to accomplish all this, we will be using app_update.sjs to serve + * updates XMLs and MARs. Outside of this test, this is really only done + * by browser-chrome mochitests (in ../browser). So we have to do some weird + * things to make it work properly in an xpcshell test. Things like + * defining URL_HTTP_UPDATE_SJS in testConstants.js so that it can be read by + * app_update.sjs in order to provide the correct download URL for MARs, but + * not reading that file here, because URL_HTTP_UPDATE_SJS is already defined + * (as something else) in xpcshellUtilsAUS.js. + */ + +let { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js"); + +// These are from testConstants.js, which cannot be loaded by this file, because +// some values are already defined at this point. However, we need these some +// other values to be defined because continueFileHandler in shared.js expects +// them to be. +const REL_PATH_DATA = ""; +// This should be URL_HOST, but that conflicts with an existing constant. +const APP_UPDATE_SJS_HOST = "http://127.0.0.1:8888"; +const URL_PATH_UPDATE_XML = "/" + REL_PATH_DATA + "app_update.sjs"; +// This should be URL_HTTP_UPDATE_SJS, but that conflicts with an existing +// constant. +const APP_UPDATE_SJS_URL = APP_UPDATE_SJS_HOST + URL_PATH_UPDATE_XML; +const CONTINUE_CHECK = "continueCheck"; +const CONTINUE_DOWNLOAD = "continueDownload"; +const CONTINUE_STAGING = "continueStaging"; + +const FIRST_UPDATE_VERSION = "999998.0"; +const SECOND_UPDATE_VERSION = "999999.0"; + +/** + * Downloads an update via aus.checkForBackgroundUpdates() + * Function returns only after the update has been downloaded. + * + * The provided callback will be invoked once during the update download, + * specifically when onStartRequest is fired. + * + * If automatic update downloads are turned off (appUpdateAuto is false), then + * we listen for the update-available notification and then roughly simulate + * accepting the prompt by calling: + * AppUpdateService.downloadUpdate(update, true); + * This is what is normally called when the user accepts the update-available + * prompt. + */ +async function downloadUpdate(appUpdateAuto, onDownloadStartCallback) { + let downloadFinishedPromise = waitForEvent("update-downloaded"); + let updateAvailablePromise; + if (!appUpdateAuto) { + updateAvailablePromise = new Promise(resolve => { + let observer = (subject, topic, status) => { + Services.obs.removeObserver(observer, "update-available"); + subject.QueryInterface(Ci.nsIUpdate); + resolve({ update: subject, status }); + }; + Services.obs.addObserver(observer, "update-available"); + }); + } + let waitToStartPromise = new Promise(resolve => { + let listener = { + onStartRequest: aRequest => { + gAUS.removeDownloadListener(listener); + onDownloadStartCallback(); + resolve(); + }, + onProgress: (aRequest, aContext, aProgress, aMaxProgress) => {}, + onStatus: (aRequest, aStatus, aStatusText) => {}, + onStopRequest(request, status) {}, + QueryInterface: ChromeUtils.generateQI([ + "nsIRequestObserver", + "nsIProgressEventSink", + ]), + }; + gAUS.addDownloadListener(listener); + }); + + let updateCheckStarted = gAUS.checkForBackgroundUpdates(); + Assert.ok(updateCheckStarted, "Update check should have started"); + + if (!appUpdateAuto) { + let { update, status } = await updateAvailablePromise; + Assert.equal( + status, + "show-prompt", + "Should attempt to show the update-available prompt" + ); + // Simulate accepting the update-available prompt + await gAUS.downloadUpdate(update, true); + } + + await continueFileHandler(CONTINUE_DOWNLOAD); + await waitToStartPromise; + await downloadFinishedPromise; + // Wait an extra tick after the download has finished. If we try to check for + // another update exactly when "update-downloaded" fires, + // Downloader:onStopRequest won't have finished yet, which it normally would + // have. + await TestUtils.waitForTick(); +} + +/** + * This is like downloadUpdate. The difference is that downloadUpdate assumes + * that an update actually will be downloaded. This function instead verifies + * that we the update wasn't downloaded. + * + * downloadUpdate(), above, uses aus.checkForBackgroundUpdates() to download + * updates to verify that background updates actually will check for subsequent + * updates, but this function will use some slightly different mechanisms. We + * can call aus.downloadUpdate() and check its return value to see if it started + * a download. But this doesn't properly check that the update-available + * notification isn't shown. So we will do an additional check where we follow + * the normal flow a bit more closely by forwarding the results that we got from + * checkForUpdates() to aus.onCheckComplete() and make sure that the update + * prompt isn't shown. + */ +async function testUpdateDoesNotDownload() { + let check = gUpdateChecker.checkForUpdates(gUpdateChecker.BACKGROUND_CHECK); + let result = await check.result; + Assert.ok(result.checksAllowed, "Should be able to check for updates"); + Assert.ok(result.succeeded, "Update check should have succeeded"); + + Assert.equal( + result.updates.length, + 1, + "Should have gotten 1 update in update check" + ); + let update = result.updates[0]; + + let downloadStarted = await gAUS.downloadUpdate(update, true); + Assert.equal( + downloadStarted, + false, + "Expected that we would not start downloading an update" + ); + + let updateAvailableObserved = false; + let observer = (subject, topic, status) => { + updateAvailableObserved = true; + }; + Services.obs.addObserver(observer, "update-available"); + await gAUS.onCheckComplete(result); + Services.obs.removeObserver(observer, "update-available"); + Assert.equal( + updateAvailableObserved, + false, + "update-available notification should not fire if we aren't going to " + + "download the update." + ); +} + +function testUpdateCheckDoesNotStart() { + let updateCheckStarted = gAUS.checkForBackgroundUpdates(); + Assert.equal( + updateCheckStarted, + false, + "Update check should not have started" + ); +} + +function prepareToDownloadVersion(version, onlyCompleteMar = false) { + let updateUrl = `${APP_UPDATE_SJS_URL}?useSlowDownloadMar=1&appVersion=${version}`; + if (onlyCompleteMar) { + updateUrl += "&completePatchOnly=1"; + } + setUpdateURL(updateUrl); +} + +function startUpdateServer() { + let httpServer = new HttpServer(); + httpServer.registerContentType("sjs", "sjs"); + httpServer.registerDirectory("/", do_get_cwd()); + httpServer.start(8888); + registerCleanupFunction(async function cleanup_httpServer() { + await new Promise(resolve => { + httpServer.stop(resolve); + }); + }); +} + +async function multi_update_test(appUpdateAuto) { + await UpdateUtils.setAppUpdateAutoEnabled(appUpdateAuto); + + prepareToDownloadVersion(FIRST_UPDATE_VERSION); + + await downloadUpdate(appUpdateAuto, () => { + Assert.ok( + !gUpdateManager.readyUpdate, + "There should not be a ready update yet" + ); + Assert.ok( + !!gUpdateManager.downloadingUpdate, + "First update download should be in downloadingUpdate" + ); + Assert.equal( + gUpdateManager.downloadingUpdate.state, + STATE_DOWNLOADING, + "downloadingUpdate should be downloading" + ); + Assert.equal( + readStatusFile(), + STATE_DOWNLOADING, + "Updater state should be downloading" + ); + }); + + Assert.ok( + !gUpdateManager.downloadingUpdate, + "First update download should no longer be in downloadingUpdate" + ); + Assert.ok( + !!gUpdateManager.readyUpdate, + "First update download should be in readyUpdate" + ); + Assert.equal( + gUpdateManager.readyUpdate.state, + STATE_PENDING, + "readyUpdate should be pending" + ); + Assert.equal( + gUpdateManager.readyUpdate.appVersion, + FIRST_UPDATE_VERSION, + "readyUpdate version should be match the version of the first update" + ); + Assert.equal( + readStatusFile(), + STATE_PENDING, + "Updater state should be pending" + ); + + let existingUpdate = gUpdateManager.readyUpdate; + await testUpdateDoesNotDownload(); + + Assert.equal( + gUpdateManager.readyUpdate, + existingUpdate, + "readyUpdate should not have changed when no newer update is available" + ); + Assert.equal( + gUpdateManager.readyUpdate.state, + STATE_PENDING, + "readyUpdate should still be pending" + ); + Assert.equal( + gUpdateManager.readyUpdate.appVersion, + FIRST_UPDATE_VERSION, + "readyUpdate version should be match the version of the first update" + ); + Assert.equal( + readStatusFile(), + STATE_PENDING, + "Updater state should still be pending" + ); + + // With only a complete update available, we should not download the newer + // update when we already have an update ready. + prepareToDownloadVersion(SECOND_UPDATE_VERSION, true); + await testUpdateDoesNotDownload(); + + Assert.equal( + gUpdateManager.readyUpdate, + existingUpdate, + "readyUpdate should not have changed when no newer partial update is available" + ); + Assert.equal( + gUpdateManager.readyUpdate.state, + STATE_PENDING, + "readyUpdate should still be pending" + ); + Assert.equal( + gUpdateManager.readyUpdate.appVersion, + FIRST_UPDATE_VERSION, + "readyUpdate version should be match the version of the first update" + ); + Assert.equal( + readStatusFile(), + STATE_PENDING, + "Updater state should still be pending" + ); + + prepareToDownloadVersion(SECOND_UPDATE_VERSION); + + await downloadUpdate(appUpdateAuto, () => { + Assert.ok( + !!gUpdateManager.downloadingUpdate, + "Second update download should be in downloadingUpdate" + ); + Assert.equal( + gUpdateManager.downloadingUpdate.state, + STATE_DOWNLOADING, + "downloadingUpdate should be downloading" + ); + Assert.ok( + !!gUpdateManager.readyUpdate, + "First update download should still be in readyUpdate" + ); + Assert.equal( + gUpdateManager.readyUpdate.state, + STATE_PENDING, + "readyUpdate should still be pending" + ); + Assert.equal( + gUpdateManager.readyUpdate.appVersion, + FIRST_UPDATE_VERSION, + "readyUpdate version should be match the version of the first update" + ); + Assert.equal( + readStatusFile(), + STATE_PENDING, + "Updater state should match the readyUpdate's state" + ); + }); + + Assert.ok( + !gUpdateManager.downloadingUpdate, + "Second update download should no longer be in downloadingUpdate" + ); + Assert.ok( + !!gUpdateManager.readyUpdate, + "Second update download should be in readyUpdate" + ); + Assert.equal( + gUpdateManager.readyUpdate.state, + STATE_PENDING, + "readyUpdate should be pending" + ); + Assert.equal( + gUpdateManager.readyUpdate.appVersion, + SECOND_UPDATE_VERSION, + "readyUpdate version should be match the version of the second update" + ); + Assert.equal( + readStatusFile(), + STATE_PENDING, + "Updater state should be pending" + ); + + // Reset the updater to its initial state to test that the complete/partial + // MAR behavior is correct + reloadUpdateManagerData(true); + + // Second parameter forces a complete MAR download. + prepareToDownloadVersion(FIRST_UPDATE_VERSION, true); + + await downloadUpdate(appUpdateAuto, () => { + Assert.equal( + gUpdateManager.downloadingUpdate.selectedPatch.type, + "complete", + "First update download should be a complete patch" + ); + }); + + Assert.equal( + gUpdateManager.readyUpdate.selectedPatch.type, + "complete", + "First update download should be a complete patch" + ); + + // Even a newer partial update should not be downloaded at this point. + prepareToDownloadVersion(SECOND_UPDATE_VERSION); + testUpdateCheckDoesNotStart(); +} + +add_task(async function all_multi_update_tests() { + setupTestCommon(true); + startUpdateServer(); + + Services.prefs.setBoolPref(PREF_APP_UPDATE_DISABLEDFORTESTING, false); + Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false); + + let origAppUpdateAutoVal = await UpdateUtils.getAppUpdateAutoEnabled(); + registerCleanupFunction(async () => { + await UpdateUtils.setAppUpdateAutoEnabled(origAppUpdateAutoVal); + }); + + await multi_update_test(true); + + // Reset the update system so we can start again from scratch. + reloadUpdateManagerData(true); + + await multi_update_test(false); + + doTestFinish(); +}); -- cgit v1.2.3