diff options
Diffstat (limited to '')
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] |