summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/update/tests/marionette/test_no_window_update_restart.py
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/update/tests/marionette/test_no_window_update_restart.py')
-rw-r--r--toolkit/mozapps/update/tests/marionette/test_no_window_update_restart.py255
1 files changed, 255 insertions, 0 deletions
diff --git a/toolkit/mozapps/update/tests/marionette/test_no_window_update_restart.py b/toolkit/mozapps/update/tests/marionette/test_no_window_update_restart.py
new file mode 100644
index 0000000000..245efc774d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/marionette/test_no_window_update_restart.py
@@ -0,0 +1,255 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+# This test is not written in a very modular way because update tests are generally not done with
+# Marionette, so this is sort of a one-off. We can't successfully just load all the actual setup
+# code that the normal update tests use, so this test basically just copies the bits that it needs.
+# The reason that this is a Marionette test at all is that, even if we stub out the quit/restart
+# call, we need no windows to be open to test the relevant functionality, but xpcshell doesn't do
+# windows at all and mochitest has a test runner window that Firefox recognizes, but mustn't close
+# during testing.
+
+from marionette_driver import Wait, errors
+from marionette_harness import MarionetteTestCase
+
+
+class TestNoWindowUpdateRestart(MarionetteTestCase):
+ def setUp(self):
+ super(TestNoWindowUpdateRestart, self).setUp()
+
+ self.marionette.delete_session()
+ self.marionette.start_session({"moz:windowless": True})
+ # See Bug 1777956
+ window = self.marionette.window_handles[0]
+ self.marionette.switch_to_window(window)
+
+ # Every part of this test ought to run in the chrome context.
+ self.marionette.set_context(self.marionette.CONTEXT_CHROME)
+ self.setUpBrowser()
+ self.origDisabledForTesting = self.marionette.get_pref(
+ "app.update.disabledForTesting"
+ )
+ self.resetUpdate()
+
+ def setUpBrowser(self):
+ self.origAppUpdateAuto = self.marionette.execute_async_script(
+ """
+ let [resolve] = arguments;
+
+ (async () => {
+ Services.prefs.setIntPref("app.update.download.attempts", 0);
+ Services.prefs.setIntPref("app.update.download.maxAttempts", 0);
+ Services.prefs.setBoolPref("app.update.staging.enabled", false);
+ Services.prefs.setBoolPref("app.update.noWindowAutoRestart.enabled", true);
+ Services.prefs.setIntPref("app.update.noWindowAutoRestart.delayMs", 1000);
+ Services.prefs.clearUserPref("testing.no_window_update_restart.silent_restart_env");
+
+ let { UpdateUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/UpdateUtils.sys.mjs"
+ );
+ let origAppUpdateAuto = await UpdateUtils.getAppUpdateAutoEnabled();
+ await UpdateUtils.setAppUpdateAutoEnabled(true);
+
+ // Prevent the update sync manager from thinking there are two instances running
+ let exePath = Services.dirsvc.get("XREExeF", Ci.nsIFile);
+ let dirProvider = {
+ getFile: function AGP_DP_getFile(aProp, aPersistent) {
+ aPersistent.value = false;
+ switch (aProp) {
+ case "XREExeF":
+ exePath.append("browser-test");
+ return exePath;
+ }
+ return null;
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIDirectoryServiceProvider"]),
+ };
+ let ds = Services.dirsvc.QueryInterface(Ci.nsIDirectoryService);
+ ds.QueryInterface(Ci.nsIProperties).undefine("XREExeF");
+ ds.registerProvider(dirProvider);
+ let gSyncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
+ Ci.nsIUpdateSyncManager
+ );
+ gSyncManager.resetLock();
+ ds.unregisterProvider(dirProvider);
+
+ return origAppUpdateAuto;
+ })().then(resolve);
+ """
+ )
+
+ def tearDown(self):
+ self.tearDownBrowser()
+ self.resetUpdate()
+
+ # Reset context to the default.
+ self.marionette.set_context(self.marionette.CONTEXT_CONTENT)
+
+ super(TestNoWindowUpdateRestart, self).tearDown()
+
+ def tearDownBrowser(self):
+ self.marionette.execute_async_script(
+ """
+ let [origAppUpdateAuto, origDisabledForTesting, resolve] = arguments;
+ (async () => {
+ Services.prefs.setBoolPref("app.update.disabledForTesting", origDisabledForTesting);
+ Services.prefs.clearUserPref("app.update.download.attempts");
+ Services.prefs.clearUserPref("app.update.download.maxAttempts");
+ Services.prefs.clearUserPref("app.update.staging.enabled");
+ Services.prefs.clearUserPref("app.update.noWindowAutoRestart.enabled");
+ Services.prefs.clearUserPref("app.update.noWindowAutoRestart.delayMs");
+ Services.prefs.clearUserPref("testing.no_window_update_restart.silent_restart_env");
+
+ let { UpdateUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/UpdateUtils.sys.mjs"
+ );
+ await UpdateUtils.setAppUpdateAutoEnabled(origAppUpdateAuto);
+ })().then(resolve);
+ """,
+ script_args=(self.origAppUpdateAuto, self.origDisabledForTesting),
+ )
+
+ def test_update_on_last_window_close(self):
+ # By preparing an update and closing all windows, we activate the No
+ # Window Update Restart feature (see Bug 1720742) which causes Firefox
+ # to restart to install updates.
+ self.marionette.restart(
+ callback=self.prepare_update_and_close_all_windows, in_app=True
+ )
+
+ # Firefox should come back without any windows (i.e. silently).
+ with self.assertRaises(errors.TimeoutException):
+ wait = Wait(
+ self.marionette,
+ ignored_exceptions=errors.NoSuchWindowException,
+ timeout=5,
+ )
+ wait.until(lambda _: self.marionette.window_handles)
+
+ # Reset the browser and active WebDriver session
+ self.marionette.restart(in_app=True)
+ self.marionette.delete_session()
+ self.marionette.start_session()
+ self.marionette.set_context(self.marionette.CONTEXT_CHROME)
+
+ quit_flags_correct = self.marionette.get_pref(
+ "testing.no_window_update_restart.silent_restart_env"
+ )
+ self.assertTrue(quit_flags_correct)
+
+ # Normally, the update status file would have been removed at this point by Post Update
+ # Processing. But restarting resets app.update.disabledForTesting, which causes that to be
+ # skipped, allowing us to look at the update status file directly.
+ update_status_path = self.marionette.execute_script(
+ """
+ let statusFile = FileUtils.getDir("UpdRootD", ["updates", "0"], true);
+ statusFile.append("update.status");
+ return statusFile.path;
+ """
+ )
+ with open(update_status_path, "r") as f:
+ # If Firefox was built with "--enable-unverified-updates" (or presumably if we tested
+ # with an actual, signed update), the update should succeed. Otherwise, it will fail
+ # with CERT_VERIFY_ERROR (error code 19). Unfortunately, there is no good way to tell
+ # which of those situations we are in. Luckily, it doesn't matter, because we aren't
+ # trying to test whether the update applied successfully, just whether the
+ # "No Window Update Restart" feature works.
+ self.assertIn(f.read().strip(), ["succeeded", "failed: 19"])
+
+ def resetUpdate(self):
+ self.marionette.execute_script(
+ """
+ let UM = Cc["@mozilla.org/updates/update-manager;1"].getService(Ci.nsIUpdateManager);
+ UM.QueryInterface(Ci.nsIObserver).observe(null, "um-reload-update-data", "skip-files");
+
+ let { UpdateListener } = ChromeUtils.import("resource://gre/modules/UpdateListener.jsm");
+ UpdateListener.reset();
+
+ let { AppMenuNotifications } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppMenuNotifications.sys.mjs"
+ );
+ AppMenuNotifications.removeNotification(/.*/);
+
+ // Remove old update files so that they don't interfere with tests.
+ let rootUpdateDir = Services.dirsvc.get("UpdRootD", Ci.nsIFile);
+ let updateDir = rootUpdateDir.clone();
+ updateDir.append("updates");
+ let patchDir = updateDir.clone();
+ patchDir.append("0");
+
+ let filesToRemove = [];
+ let addFileToRemove = (dir, filename) => {
+ let file = dir.clone();
+ file.append(filename);
+ filesToRemove.push(file);
+ };
+
+ addFileToRemove(rootUpdateDir, "active-update.xml");
+ addFileToRemove(rootUpdateDir, "updates.xml");
+ addFileToRemove(patchDir, "bt.result");
+ addFileToRemove(patchDir, "update.status");
+ addFileToRemove(patchDir, "update.version");
+ addFileToRemove(patchDir, "update.mar");
+ addFileToRemove(patchDir, "updater.ini");
+ addFileToRemove(updateDir, "backup-update.log");
+ addFileToRemove(updateDir, "last-update.log");
+ addFileToRemove(patchDir, "update.log");
+
+ for (const file of filesToRemove) {
+ try {
+ if (file.exists()) {
+ file.remove(false);
+ }
+ } catch (e) {
+ console.warn("Unable to remove file. Path: '" + file.path + "', Exception: " + e);
+ }
+ }
+ """
+ )
+
+ def prepare_update_and_close_all_windows(self):
+ self.marionette.execute_async_script(
+ """
+ let [updateURLString, resolve] = arguments;
+
+ (async () => {
+ let updateDownloadedPromise = new Promise(innerResolve => {
+ Services.obs.addObserver(function callback() {
+ Services.obs.removeObserver(callback, "update-downloaded");
+ innerResolve();
+ }, "update-downloaded");
+ });
+
+ // Set the update URL to the one that was passed in.
+ let mockAppInfo = Object.create(Services.appinfo, {
+ updateURL: {
+ configurable: true,
+ enumerable: true,
+ writable: false,
+ value: updateURLString,
+ },
+ });
+ Services.appinfo = mockAppInfo;
+
+ // We aren't going to flip this until after the URL is set because the test fails
+ // if we hit the real update server.
+ Services.prefs.setBoolPref("app.update.disabledForTesting", false);
+
+ let aus = Cc["@mozilla.org/updates/update-service;1"]
+ .getService(Ci.nsIApplicationUpdateService);
+ aus.checkForBackgroundUpdates();
+
+ await updateDownloadedPromise;
+
+ Services.obs.addObserver((aSubject, aTopic, aData) => {
+ let silent_restart = Services.env.get("MOZ_APP_SILENT_START") == 1 && Services.env.get("MOZ_APP_RESTART") == 1;
+ Services.prefs.setBoolPref("testing.no_window_update_restart.silent_restart_env", silent_restart);
+ }, "quit-application-granted");
+
+ for (const win of Services.wm.getEnumerator("navigator:browser")) {
+ win.close();
+ }
+ })().then(resolve);
+ """,
+ script_args=(self.marionette.absolute_url("update.xml"),),
+ )