summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/update/tests/unit_background_update
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/update/tests/unit_background_update')
-rw-r--r--toolkit/mozapps/update/tests/unit_background_update/head.js58
-rw-r--r--toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_exitcodes.js80
-rw-r--r--toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_glean.js224
-rw-r--r--toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason.js66
-rw-r--r--toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason_schedule.js136
-rw-r--r--toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason_update.js241
-rw-r--r--toolkit/mozapps/update/tests/unit_background_update/xpcshell.ini24
7 files changed, 829 insertions, 0 deletions
diff --git a/toolkit/mozapps/update/tests/unit_background_update/head.js b/toolkit/mozapps/update/tests/unit_background_update/head.js
new file mode 100644
index 0000000000..c7ed24a0a7
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_background_update/head.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* import-globals-from ../data/xpcshellUtilsAUS.js */
+load("xpcshellUtilsAUS.js");
+gIsServiceTest = false;
+
+const { BackgroundTasksTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/BackgroundTasksTestUtils.sys.mjs"
+);
+BackgroundTasksTestUtils.init(this);
+const do_backgroundtask = BackgroundTasksTestUtils.do_backgroundtask.bind(
+ BackgroundTasksTestUtils
+);
+const setupProfileService = BackgroundTasksTestUtils.setupProfileService.bind(
+ BackgroundTasksTestUtils
+);
+
+// Helper function to register a callback to catch a Glean ping before its
+// submission. The function returns all string_list items, but not as they
+// appear in the ping itself, but as full text representation, which is the
+// value of the corresponding field. This makes the test more unique, because
+// the values often contain chars, which are not allowed in glean metric labels
+//
+// @returns: an array which contains all glean metrics, but as full text
+// representation from the BackgroundUpdate.REASON object => its
+// values, see description for further details.
+//
+async function checkGleanPing() {
+ let retval = ["EMPTY"];
+ let ping_submitted = false;
+
+ const { maybeSubmitBackgroundUpdatePing } = ChromeUtils.importESModule(
+ "resource://gre/modules/backgroundtasks/BackgroundTask_backgroundupdate.sys.mjs"
+ );
+ const { BackgroundUpdate } = ChromeUtils.importESModule(
+ "resource://gre/modules/BackgroundUpdate.sys.mjs"
+ );
+
+ GleanPings.backgroundUpdate.testBeforeNextSubmit(_ => {
+ ping_submitted = true;
+ retval = Glean.backgroundUpdate.reasonsToNotUpdate.testGetValue().map(v => {
+ return BackgroundUpdate.REASON[v];
+ });
+ Assert.ok(Array.isArray(retval));
+ return retval;
+ });
+ await maybeSubmitBackgroundUpdatePing();
+ Assert.ok(ping_submitted, "Glean ping successfully submitted");
+
+ // The metric has `lifetime: application` set, but when testing we do not
+ // want to keep the results around and avoid, that one test can influence
+ // another. That is why we clear this string_list.
+ Glean.backgroundUpdate.reasonsToNotUpdate.set([]);
+
+ return retval;
+}
diff --git a/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_exitcodes.js b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_exitcodes.js
new file mode 100644
index 0000000000..f862815ab1
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_exitcodes.js
@@ -0,0 +1,80 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=4 ts=4 sts=4 et
+ * 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 exercises functionality and also ensures the exit codes,
+// which are a public API, do not change over time.
+const { EXIT_CODE } = ChromeUtils.importESModule(
+ "resource://gre/modules/BackgroundUpdate.sys.mjs"
+).BackgroundUpdate;
+
+setupProfileService();
+
+// Ensure launched background tasks don't see this xpcshell as a concurrent
+// instance.
+let syncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
+ Ci.nsIUpdateSyncManager
+);
+let lockFile = do_get_profile();
+lockFile.append("customExePath");
+lockFile.append("customExe");
+syncManager.resetLock(lockFile);
+
+add_task(async function test_default_profile_does_not_exist() {
+ // Pretend there's no default profile.
+ let exitCode = await do_backgroundtask("backgroundupdate", {
+ extraEnv: {
+ MOZ_BACKGROUNDTASKS_NO_DEFAULT_PROFILE: "1",
+ },
+ });
+ Assert.equal(EXIT_CODE.DEFAULT_PROFILE_DOES_NOT_EXIST, exitCode);
+ Assert.equal(11, exitCode);
+});
+
+add_task(async function test_default_profile_cannot_be_locked() {
+ // Now, lock the default profile.
+ let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].getService(
+ Ci.nsIToolkitProfileService
+ );
+
+ let file = do_get_profile();
+ file.append("profile_cannot_be_locked");
+
+ let profile = profileService.createUniqueProfile(
+ file,
+ "test_default_profile"
+ );
+ let lock = profile.lock({});
+
+ try {
+ let exitCode = await do_backgroundtask("backgroundupdate", {
+ extraEnv: {
+ MOZ_BACKGROUNDTASKS_DEFAULT_PROFILE_PATH: lock.directory.path,
+ },
+ });
+ Assert.equal(EXIT_CODE.DEFAULT_PROFILE_CANNOT_BE_LOCKED, exitCode);
+ Assert.equal(12, exitCode);
+ } finally {
+ lock.unlock();
+ }
+});
+
+add_task(async function test_default_profile_cannot_be_read() {
+ // Finally, provide an empty default profile, one without prefs.
+ let file = do_get_profile();
+ file.append("profile_cannot_be_read");
+
+ await IOUtils.makeDirectory(file.path);
+
+ let exitCode = await do_backgroundtask("backgroundupdate", {
+ extraEnv: {
+ MOZ_BACKGROUNDTASKS_DEFAULT_PROFILE_PATH: file.path,
+ },
+ });
+ Assert.equal(EXIT_CODE.DEFAULT_PROFILE_CANNOT_BE_READ, exitCode);
+ Assert.equal(13, exitCode);
+});
diff --git a/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_glean.js b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_glean.js
new file mode 100644
index 0000000000..c66c98ebc9
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_glean.js
@@ -0,0 +1,224 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=4 ts=4 sts=4 et
+ * 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";
+
+const { ASRouterTargeting } = ChromeUtils.import(
+ "resource://activity-stream/lib/ASRouterTargeting.jsm"
+);
+
+const { BackgroundUpdate } = ChromeUtils.importESModule(
+ "resource://gre/modules/BackgroundUpdate.sys.mjs"
+);
+
+const { maybeSubmitBackgroundUpdatePing } = ChromeUtils.importESModule(
+ "resource://gre/modules/backgroundtasks/BackgroundTask_backgroundupdate.sys.mjs"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "UpdateService",
+ "@mozilla.org/updates/update-service;1",
+ "nsIApplicationUpdateService"
+);
+
+add_setup(function test_setup() {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+
+ // We need to initialize it once, otherwise operations will be stuck in the pre-init queue.
+ Services.fog.initializeFOG();
+
+ setupProfileService();
+});
+
+add_task(async function test_record_update_environment() {
+ await BackgroundUpdate.recordUpdateEnvironment();
+
+ let pingSubmitted = false;
+ let appUpdateAutoEnabled = await UpdateUtils.getAppUpdateAutoEnabled();
+ let backgroundUpdateEnabled = await UpdateUtils.readUpdateConfigSetting(
+ "app.update.background.enabled"
+ );
+ GleanPings.backgroundUpdate.testBeforeNextSubmit(reason => {
+ Assert.equal(reason, "backgroundupdate_task");
+
+ pingSubmitted = true;
+ Assert.equal(
+ Services.prefs.getBoolPref("app.update.service.enabled", false),
+ Glean.update.serviceEnabled.testGetValue()
+ );
+
+ Assert.equal(
+ appUpdateAutoEnabled,
+ Glean.update.autoDownload.testGetValue()
+ );
+
+ Assert.equal(
+ backgroundUpdateEnabled,
+ Glean.update.backgroundUpdate.testGetValue()
+ );
+
+ Assert.equal(
+ UpdateUtils.UpdateChannel,
+ Glean.update.channel.testGetValue()
+ );
+ Assert.equal(
+ !Services.policies || Services.policies.isAllowed("appUpdate"),
+ Glean.update.enabled.testGetValue()
+ );
+
+ Assert.equal(
+ UpdateService.canUsuallyApplyUpdates,
+ Glean.update.canUsuallyApplyUpdates.testGetValue()
+ );
+ Assert.equal(
+ UpdateService.canUsuallyCheckForUpdates,
+ Glean.update.canUsuallyCheckForUpdates.testGetValue()
+ );
+ Assert.equal(
+ UpdateService.canUsuallyStageUpdates,
+ Glean.update.canUsuallyStageUpdates.testGetValue()
+ );
+ Assert.equal(
+ UpdateService.canUsuallyUseBits,
+ Glean.update.canUsuallyUseBits.testGetValue()
+ );
+ });
+
+ // There's nothing async in this function atm, but it's annotated async, so..
+ await maybeSubmitBackgroundUpdatePing();
+
+ ok(pingSubmitted, "'background-update' ping was submitted");
+});
+
+async function do_readTargeting(content, beforeNextSubmitCallback) {
+ let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].getService(
+ Ci.nsIToolkitProfileService
+ );
+
+ let file = do_get_profile();
+ file.append("profile_cannot_be_locked");
+
+ let profile = profileService.createUniqueProfile(
+ file,
+ "test_default_profile"
+ );
+
+ let targetingSnapshot = profile.rootDir.clone();
+ targetingSnapshot.append("targeting.snapshot.json");
+
+ if (content) {
+ await IOUtils.writeUTF8(targetingSnapshot.path, content);
+ }
+
+ let lock = profile.lock({});
+
+ Services.fog.testResetFOG();
+ try {
+ await BackgroundUpdate.readFirefoxMessagingSystemTargetingSnapshot(lock);
+ } finally {
+ lock.unlock();
+ }
+
+ let pingSubmitted = false;
+ GleanPings.backgroundUpdate.testBeforeNextSubmit(reason => {
+ pingSubmitted = true;
+ return beforeNextSubmitCallback(reason);
+ });
+
+ // There's nothing async in this function atm, but it's annotated async, so..
+ await maybeSubmitBackgroundUpdatePing();
+
+ ok(pingSubmitted, "'background-update' ping was submitted");
+}
+
+// Missing targeting is anticipated.
+add_task(async function test_targeting_missing() {
+ await do_readTargeting(null, reason => {
+ Assert.equal(false, Glean.backgroundUpdate.targetingExists.testGetValue());
+
+ Assert.equal(
+ false,
+ Glean.backgroundUpdate.targetingException.testGetValue()
+ );
+ });
+});
+
+// Malformed JSON yields an exception.
+add_task(async function test_targeting_exception() {
+ await do_readTargeting("{", reason => {
+ Assert.equal(false, Glean.backgroundUpdate.targetingExists.testGetValue());
+
+ Assert.equal(
+ true,
+ Glean.backgroundUpdate.targetingException.testGetValue()
+ );
+ });
+});
+
+// Well formed targeting values are reflected into the Glean telemetry.
+add_task(async function test_targeting_exists() {
+ // We can't take a full environment snapshot under `xpcshell`; these are just
+ // the items we need.
+ let target = {
+ currentDate: ASRouterTargeting.Environment.currentDate,
+ profileAgeCreated: ASRouterTargeting.Environment.profileAgeCreated,
+ firefoxVersion: ASRouterTargeting.Environment.firefoxVersion,
+ };
+ let targetSnapshot = await ASRouterTargeting.getEnvironmentSnapshot(target);
+
+ await do_readTargeting(JSON.stringify(targetSnapshot), reason => {
+ Assert.equal(true, Glean.backgroundUpdate.targetingExists.testGetValue());
+
+ Assert.equal(
+ false,
+ Glean.backgroundUpdate.targetingException.testGetValue()
+ );
+
+ // `environment.firefoxVersion` is a positive integer.
+ Assert.ok(
+ Glean.backgroundUpdate.targetingEnvFirefoxVersion.testGetValue() > 0
+ );
+
+ Assert.equal(
+ targetSnapshot.environment.firefoxVersion,
+ Glean.backgroundUpdate.targetingEnvFirefoxVersion.testGetValue()
+ );
+
+ let profileAge =
+ Glean.backgroundUpdate.targetingEnvProfileAge.testGetValue();
+
+ Assert.ok(profileAge instanceof Date);
+ Assert.ok(0 < profileAge.getTime());
+ Assert.ok(profileAge.getTime() < Date.now());
+
+ // `environment.profileAgeCreated` is an integer, milliseconds since the
+ // Unix epoch.
+ let targetProfileAge = new Date(
+ targetSnapshot.environment.profileAgeCreated
+ );
+ // Our `time_unit: day` has Glean round to the nearest day *in the local
+ // timezone*, so we must do the same.
+ targetProfileAge.setHours(0, 0, 0, 0);
+
+ Assert.equal(targetProfileAge.toISOString(), profileAge.toISOString());
+
+ let currentDate =
+ Glean.backgroundUpdate.targetingEnvCurrentDate.testGetValue();
+
+ Assert.ok(0 < currentDate.getTime());
+ Assert.ok(currentDate.getTime() < Date.now());
+
+ // `environment.currentDate` is in ISO string format.
+ let targetCurrentDate = new Date(targetSnapshot.environment.currentDate);
+ // Our `time_unit: day` has Glean round to the nearest day *in the local
+ // timezone*, so we must do the same.
+ targetCurrentDate.setHours(0, 0, 0, 0);
+
+ Assert.equal(targetCurrentDate.toISOString(), currentDate.toISOString());
+ });
+});
diff --git a/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason.js b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason.js
new file mode 100644
index 0000000000..71ce7f0361
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason.js
@@ -0,0 +1,66 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=2 sts=2 et
+ * 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";
+
+const { BackgroundUpdate } = ChromeUtils.importESModule(
+ "resource://gre/modules/BackgroundUpdate.sys.mjs"
+);
+
+// These tests use per-installation prefs, and those are a shared resource, so
+// they require some non-trivial setup.
+setupTestCommon(null);
+standardInit();
+
+add_setup(function test_setup() {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+
+ // We need to initialize it once, otherwise operations will be stuck in the
+ // pre-init queue.
+ Services.fog.initializeFOG();
+});
+
+// Because we want to use the keys from REASON as strings and send these with
+// Glean, we have to make sure, that they meet the requirements for `String
+// Lists` and are not too long.
+add_task(async function test_reasons_length() {
+ for (const key of Object.keys(BackgroundUpdate.REASON)) {
+ Glean.backgroundUpdate.reasonsToNotUpdate.add(key);
+ // No exception means success.
+ Assert.ok(
+ Array.isArray(Glean.backgroundUpdate.reasonsToNotUpdate.testGetValue()),
+ "Glean allows the name of the reason to be '" + key + "'"
+ );
+ }
+});
+
+// The string list in Glean can overflow and has a hard limit of 20 entries.
+// This test toggles a switch to reach this limit and fails if this causes an
+// exception, because we want to avoid that statistical data collection can have
+// an negative impact on the success rate of background updates.
+add_task(async function test_reasons_overflow() {
+ let prev = await UpdateUtils.getAppUpdateAutoEnabled();
+ try {
+ for (let i = 1; i <= 21; i++) {
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+ await BackgroundUpdate._reasonsToNotUpdateInstallation();
+ await UpdateUtils.setAppUpdateAutoEnabled(true);
+ await BackgroundUpdate._reasonsToNotUpdateInstallation();
+ Assert.ok(true, "Overflow test successful for run #" + i);
+ }
+ } finally {
+ ok(true, "resetting AppUpdateAutoEnabled to " + prev);
+ await UpdateUtils.setAppUpdateAutoEnabled(prev);
+ }
+});
+
+add_task(() => {
+ // `setupTestCommon()` calls `do_test_pending()`; this calls
+ // `do_test_finish()`. The `add_task` schedules this to run after all the
+ // other tests have completed.
+ doTestFinish();
+});
diff --git a/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason_schedule.js b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason_schedule.js
new file mode 100644
index 0000000000..277ea993ec
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason_schedule.js
@@ -0,0 +1,136 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=4 ts=4 sts=4 et
+ * 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";
+
+const { BackgroundUpdate } = ChromeUtils.importESModule(
+ "resource://gre/modules/BackgroundUpdate.sys.mjs"
+);
+let reasons = () => BackgroundUpdate._reasonsToNotScheduleUpdates();
+let REASON = BackgroundUpdate.REASON;
+
+const { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+const { ExtensionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ExtensionXPCShellUtils.sys.mjs"
+);
+
+setupProfileService();
+
+// Setup that allows to install a langpack.
+ExtensionTestUtils.init(this);
+
+AddonTestUtils.init(this);
+AddonTestUtils.overrideCertDB();
+
+AddonTestUtils.createAppInfo(
+ "xpcshell@tests.mozilla.org",
+ "XPCShell",
+ "42",
+ "42"
+);
+
+add_setup(function test_setup() {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+
+ // We need to initialize it once, otherwise operations will be stuck in the pre-init queue.
+ Services.fog.initializeFOG();
+
+ setupProfileService();
+});
+
+add_task(
+ {
+ skip_if: () => !AppConstants.MOZ_BACKGROUNDTASKS,
+ },
+ async function test_reasons_schedule_langpacks() {
+ await AddonTestUtils.promiseStartupManager();
+
+ Services.prefs.setBoolPref("app.update.langpack.enabled", true);
+
+ let result = await reasons();
+ Assert.ok(
+ !result.includes(REASON.LANGPACK_INSTALLED),
+ "Reasons does not include LANGPACK_INSTALLED"
+ );
+
+ // Install a langpack.
+ let langpack = {
+ "manifest.json": {
+ name: "test Language Pack",
+ version: "1.0",
+ manifest_version: 2,
+ browser_specific_settings: {
+ gecko: {
+ id: "@test-langpack",
+ strict_min_version: "42.0",
+ strict_max_version: "42.0",
+ },
+ },
+ langpack_id: "fr",
+ languages: {
+ fr: {
+ chrome_resources: {
+ global: "chrome/fr/locale/fr/global/",
+ },
+ version: "20171001190118",
+ },
+ },
+ sources: {
+ browser: {
+ base_path: "browser/",
+ },
+ },
+ },
+ };
+
+ await Promise.all([
+ TestUtils.topicObserved("webextension-langpack-startup"),
+ AddonTestUtils.promiseInstallXPI(langpack),
+ ]);
+
+ result = await reasons();
+ Assert.ok(
+ result.includes(REASON.LANGPACK_INSTALLED),
+ "Reasons include LANGPACK_INSTALLED"
+ );
+ result = await checkGleanPing();
+ Assert.ok(
+ result.includes(REASON.LANGPACK_INSTALLED),
+ "Recognizes a language pack is installed."
+ );
+
+ // Now turn off langpack updating.
+ Services.prefs.setBoolPref("app.update.langpack.enabled", false);
+
+ result = await reasons();
+ Assert.ok(
+ !result.includes(REASON.LANGPACK_INSTALLED),
+ "Reasons does not include LANGPACK_INSTALLED"
+ );
+ result = await checkGleanPing();
+ Assert.ok(
+ !result.includes(REASON.LANGPACK_INSTALLED),
+ "No Glean metric when no language pack is installed."
+ );
+ }
+);
+
+add_task(
+ {
+ skip_if: () => !AppConstants.MOZ_BACKGROUNDTASKS,
+ },
+ async function test_reasons_schedule_default_profile() {
+ // It's difficult to arrange a default profile in a testing environment, so
+ // this is not as thorough as we'd like.
+ let result = await reasons();
+
+ Assert.ok(result.includes(REASON.NO_DEFAULT_PROFILE_EXISTS));
+ Assert.ok(result.includes(REASON.NOT_DEFAULT_PROFILE));
+ }
+);
diff --git a/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason_update.js b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason_update.js
new file mode 100644
index 0000000000..9988b7206d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason_update.js
@@ -0,0 +1,241 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=4 ts=4 sts=4 et
+ * 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";
+
+const { BackgroundUpdate } = ChromeUtils.importESModule(
+ "resource://gre/modules/BackgroundUpdate.sys.mjs"
+);
+let reasons = () => BackgroundUpdate._reasonsToNotUpdateInstallation();
+let REASON = BackgroundUpdate.REASON;
+const { EnterprisePolicyTesting } = ChromeUtils.importESModule(
+ "resource://testing-common/EnterprisePolicyTesting.sys.mjs"
+);
+const { UpdateService } = ChromeUtils.importESModule(
+ "resource://gre/modules/UpdateService.sys.mjs"
+);
+
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+
+// We can't reasonably check NO_MOZ_BACKGROUNDTASKS, nor NO_OMNIJAR.
+
+// These tests use per-installation prefs, and those are a shared resource, so
+// they require some non-trivial setup.
+setupTestCommon(null);
+standardInit();
+
+function setup_enterprise_policy_testing() {
+ // This initializes the policy engine for xpcshell tests
+ let policies = Cc["@mozilla.org/enterprisepolicies;1"].getService(
+ Ci.nsIObserver
+ );
+ policies.observe(null, "policies-startup", null);
+}
+setup_enterprise_policy_testing();
+
+async function setupPolicyEngineWithJson(json, customSchema) {
+ if (typeof json != "object") {
+ let filePath = do_get_file(json ? json : "non-existing-file.json").path;
+ return EnterprisePolicyTesting.setupPolicyEngineWithJson(
+ filePath,
+ customSchema
+ );
+ }
+ return EnterprisePolicyTesting.setupPolicyEngineWithJson(json, customSchema);
+}
+
+add_setup(function test_setup() {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+
+ // We need to initialize it once, otherwise operations will be stuck in the pre-init queue.
+ Services.fog.initializeFOG();
+
+ setupProfileService();
+});
+
+add_task(async function test_reasons_update_no_app_update_auto() {
+ let prev = await UpdateUtils.getAppUpdateAutoEnabled();
+ try {
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+ let result = await reasons();
+ Assert.ok(result.includes(REASON.NO_APP_UPDATE_AUTO));
+ result = await checkGleanPing();
+ Assert.ok(result.includes(REASON.NO_APP_UPDATE_AUTO));
+
+ await UpdateUtils.setAppUpdateAutoEnabled(true);
+ result = await reasons();
+ Assert.ok(!result.includes(REASON.NO_APP_UPDATE_AUTO));
+
+ result = await checkGleanPing();
+ Assert.ok(!result.includes(REASON.NO_APP_UPDATE_AUTO));
+ } finally {
+ await UpdateUtils.setAppUpdateAutoEnabled(prev);
+ }
+});
+
+add_task(async function test_reasons_update_no_app_update_background_enabled() {
+ let prev = await UpdateUtils.readUpdateConfigSetting(
+ "app.update.background.enabled"
+ );
+ try {
+ await UpdateUtils.writeUpdateConfigSetting(
+ "app.update.background.enabled",
+ false
+ );
+ let result = await reasons();
+ Assert.ok(result.includes(REASON.NO_APP_UPDATE_BACKGROUND_ENABLED));
+ result = await checkGleanPing();
+ Assert.ok(result.includes(REASON.NO_APP_UPDATE_BACKGROUND_ENABLED));
+
+ await UpdateUtils.writeUpdateConfigSetting(
+ "app.update.background.enabled",
+ true
+ );
+ result = await reasons();
+ Assert.ok(!result.includes(REASON.NO_APP_UPDATE_BACKGROUND_ENABLED));
+ result = await checkGleanPing();
+ Assert.ok(!result.includes(REASON.NO_APP_UPDATE_BACKGROUND_ENABLED));
+ } finally {
+ await UpdateUtils.writeUpdateConfigSetting(
+ "app.update.background.enabled",
+ prev
+ );
+ }
+});
+
+add_task(async function test_reasons_update_cannot_usually_check() {
+ // It's difficult to arrange the conditions in a testing environment, so
+ // we'll use mocks to get a little assurance.
+ let result = await reasons();
+ Assert.ok(!result.includes(REASON.CANNOT_USUALLY_CHECK));
+
+ let sandbox = sinon.createSandbox();
+ try {
+ sandbox
+ .stub(UpdateService.prototype, "canUsuallyCheckForUpdates")
+ .get(() => false);
+ result = await reasons();
+ Assert.ok(result.includes(REASON.CANNOT_USUALLY_CHECK));
+ result = await checkGleanPing();
+ Assert.ok(result.includes(REASON.CANNOT_USUALLY_CHECK));
+ } finally {
+ sandbox.restore();
+ }
+});
+
+add_task(async function test_reasons_update_can_usually_stage_or_appl() {
+ // It's difficult to arrange the conditions in a testing environment, so
+ // we'll use mocks to get a little assurance.
+ let sandbox = sinon.createSandbox();
+ try {
+ sandbox
+ .stub(UpdateService.prototype, "canUsuallyStageUpdates")
+ .get(() => true);
+ sandbox
+ .stub(UpdateService.prototype, "canUsuallyApplyUpdates")
+ .get(() => true);
+ let result = await reasons();
+ Assert.ok(
+ !result.includes(REASON.CANNOT_USUALLY_STAGE_AND_CANNOT_USUALLY_APPLY)
+ );
+ result = await checkGleanPing();
+ Assert.ok(
+ !result.includes(REASON.CANNOT_USUALLY_STAGE_AND_CANNOT_USUALLY_APPLY)
+ );
+
+ sandbox
+ .stub(UpdateService.prototype, "canUsuallyStageUpdates")
+ .get(() => false);
+ sandbox
+ .stub(UpdateService.prototype, "canUsuallyApplyUpdates")
+ .get(() => false);
+ result = await reasons();
+ Assert.ok(
+ result.includes(REASON.CANNOT_USUALLY_STAGE_AND_CANNOT_USUALLY_APPLY)
+ );
+ result = await checkGleanPing();
+ Assert.ok(
+ result.includes(REASON.CANNOT_USUALLY_STAGE_AND_CANNOT_USUALLY_APPLY)
+ );
+ } finally {
+ sandbox.restore();
+ }
+});
+
+add_task(
+ {
+ skip_if: () =>
+ !AppConstants.MOZ_BITS_DOWNLOAD || AppConstants.platform != "win",
+ },
+ async function test_reasons_update_can_usually_use_bits() {
+ let prev = Services.prefs.getBoolPref("app.update.BITS.enabled");
+
+ // Here we use mocks to "get by" preconditions that are not
+ // satisfied in the testing environment.
+ let sandbox = sinon.createSandbox();
+ try {
+ sandbox
+ .stub(UpdateService.prototype, "canUsuallyStageUpdates")
+ .get(() => true);
+ sandbox
+ .stub(UpdateService.prototype, "canUsuallyApplyUpdates")
+ .get(() => true);
+
+ Services.prefs.setBoolPref("app.update.BITS.enabled", false);
+ let result = await reasons();
+ Assert.ok(result.includes(REASON.WINDOWS_CANNOT_USUALLY_USE_BITS));
+ result = await checkGleanPing();
+ Assert.ok(
+ result.includes(REASON.WINDOWS_CANNOT_USUALLY_USE_BITS),
+ "result : " + result.join("', '") + "']"
+ );
+
+ Services.prefs.setBoolPref("app.update.BITS.enabled", true);
+ result = await reasons();
+ Assert.ok(!result.includes(REASON.WINDOWS_CANNOT_USUALLY_USE_BITS));
+ result = await checkGleanPing();
+ Assert.ok(!result.includes(REASON.WINDOWS_CANNOT_USUALLY_USE_BITS));
+ } finally {
+ sandbox.restore();
+ Services.prefs.setBoolPref("app.update.BITS.enabled", prev);
+ }
+ }
+);
+
+add_task(async function test_reasons_update_manual_update_only() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ ManualAppUpdateOnly: true,
+ },
+ });
+ Assert.equal(
+ Services.policies.status,
+ Ci.nsIEnterprisePolicies.ACTIVE,
+ "Engine is active"
+ );
+
+ let result = await reasons();
+ Assert.ok(result.includes(REASON.MANUAL_UPDATE_ONLY));
+ result = await checkGleanPing();
+ Assert.ok(result.includes(REASON.MANUAL_UPDATE_ONLY));
+
+ await setupPolicyEngineWithJson({});
+
+ result = await reasons();
+ Assert.ok(!result.includes(REASON.MANUAL_UPDATE_ONLY));
+ result = await checkGleanPing();
+ Assert.ok(!result.includes(REASON.MANUAL_UPDATE_ONLY));
+});
+
+add_task(() => {
+ // `setupTestCommon()` calls `do_test_pending()`; this calls
+ // `do_test_finish()`. The `add_task` schedules this to run after all the
+ // other tests have completed.
+ doTestFinish();
+});
diff --git a/toolkit/mozapps/update/tests/unit_background_update/xpcshell.ini b/toolkit/mozapps/update/tests/unit_background_update/xpcshell.ini
new file mode 100644
index 0000000000..45016b42e2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_background_update/xpcshell.ini
@@ -0,0 +1,24 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+[DEFAULT]
+firefox-appdir = browser
+skip-if =
+ toolkit == 'android'
+ (os == 'win' && msix) # Our updater is disabled in MSIX builds
+head = head.js
+support-files =
+ ../data/shared.js
+ ../data/sharedUpdateXML.js
+ ../data/xpcshellUtilsAUS.js
+
+[test_backgroundupdate_exitcodes.js]
+run-sequentially = very high failure rate in parallel
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+
+[test_backgroundupdate_glean.js]
+[test_backgroundupdate_reason.js]
+[test_backgroundupdate_reason_update.js]
+[test_backgroundupdate_reason_schedule.js]