Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* -*- 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/. */
/**
* Unit tests that verify that the state machine controlling throttling (not
* actually updating after not running the browser for a long period) and
* debouncing (not taking any action at all within a short window) is working as
* expected.
*
* Also unit tests that `runActions` invokes appropriate action methods.
*/
"use strict";
const { Actions, runActions } = ChromeUtils.importESModule(
"resource://gre/modules/backgroundtasks/BackgroundTask_backgroundupdate.sys.mjs"
);
const { AppUpdater } = ChromeUtils.importESModule(
"resource://gre/modules/AppUpdater.sys.mjs"
);
const { BackgroundUpdate } = ChromeUtils.importESModule(
"resource://gre/modules/BackgroundUpdate.sys.mjs"
);
const { sinon } = ChromeUtils.importESModule(
);
const ACTION = BackgroundUpdate.ACTION;
let ACTIONS_ALL = new Set(Object.values(ACTION));
let ACTIONS_THROTTLED = new Set([ACTION.EXPERIMENTER, ACTION.SUBMIT_PING]);
let ACTIONS_DEBOUNCED = new Set([]);
const HOUR_IN_SECONDS = 60 * 60;
const DAY_IN_SECONDS = 24 * HOUR_IN_SECONDS;
function getLastTaskRunSeconds() {
return Services.prefs.getIntPref(
"app.update.background.lastTaskRunSecondsAfterEpoch",
-1
);
}
add_setup(async 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();
// We're in an xpcshell context, not an actual background task context, so we
// need to set some background task-specific prefs here for testing.
Services.prefs.setBoolPref(
"app.update.background.checkPolicy.throttleEnabled",
true
);
Services.prefs.setIntPref(
"app.update.background.checkPolicy.throttleAfterDays",
14
);
Services.prefs.setIntPref(
"app.update.background.checkPolicy.throttleDebouncePeriodInHours",
24
);
// For convenience.
Services.prefs.setCharPref("app.update.background.loglevel", "debug");
});
async function testWithActionsToPerform(
{
nowSeconds = Math.floor(Date.now() / 1000),
lastBrowsedDays = 0,
lastTaskRunHours = 0,
lastTaskRunUnset = false,
},
expectedActions
) {
info(
`nowSeconds=${nowSeconds} lastBrowsedDays=${lastBrowsedDays} lastTaskRunHours=${lastTaskRunHours} lastTaskRunUnset=${lastTaskRunUnset}`
);
// This should really be `Services.fog.testResetFOG()`. But this works
// well enough and, currently, Bug 1954203 prevents us from successfully using
// `testResetFOG`.
GleanPings.backgroundUpdate.submit();
let environment = {
currentDate: 1000 * (nowSeconds - lastBrowsedDays * DAY_IN_SECONDS),
};
let prevTaskRunSeconds = getLastTaskRunSeconds();
try {
let startingTaskRunSeconds =
nowSeconds - lastTaskRunHours * HOUR_IN_SECONDS;
if (!lastTaskRunUnset) {
Services.prefs.setIntPref(
"app.update.background.lastTaskRunSecondsAfterEpoch",
startingTaskRunSeconds
);
} else {
Services.prefs.clearUserPref(
"app.update.background.lastTaskRunSecondsAfterEpoch"
);
}
let beforeTaskRunSeconds;
let actions = await BackgroundUpdate.withActionsToPerform(
{ defaultProfileTargetingSnapshot: { environment }, nowSeconds },
actionSet => {
beforeTaskRunSeconds = getLastTaskRunSeconds();
return actionSet;
}
);
Assert.deepEqual(
Array.from(actions).toSorted(),
Array.from(expectedActions).toSorted()
);
let afterTaskRunSeconds = getLastTaskRunSeconds();
if (expectedActions.size) {
Assert.equal(
afterTaskRunSeconds,
nowSeconds,
"Some expected actions, so last task run timestamp changed after task complete callback invoked"
);
} else {
Assert.equal(
afterTaskRunSeconds,
beforeTaskRunSeconds,
"No expected actions, so last task run timestamp not changed when task is debounced"
);
}
Assert.equal(
Glean.backgroundUpdate.daysSinceLastBrowsed.testGetValue(),
lastBrowsedDays,
"`daysSinceLastBrowsed` instrumentation is correct"
);
Assert.equal(
Glean.backgroundUpdate.throttled.testGetValue(),
!actions.has(ACTION.UPDATE),
"`throttled` instrumentation is correct"
);
Assert.equal(
Glean.backgroundUpdate.debounced.testGetValue(),
actions.size ? null : 1,
"`debounced` instrumentation is correct"
);
} finally {
Services.prefs.setIntPref(
"app.update.background.lastTaskRunSecondsAfterEpoch",
prevTaskRunSeconds
);
}
}
add_task(async function test_disabled() {
// Setting in this test is generally `true`, so undo the change after we're done.
let prev = Services.prefs.getBoolPref(
"app.update.background.checkPolicy.throttleEnabled",
false
);
try {
Services.prefs.setBoolPref(
"app.update.background.checkPolicy.throttleEnabled",
false
);
await testWithActionsToPerform(
{
lastBrowsedDays: 100,
lastTaskRunHours: 0,
},
ACTIONS_ALL
);
// Test with the task never having run before.
await testWithActionsToPerform(
{
lastBrowsedDays: 100,
lastTaskRunUnset: true,
},
ACTIONS_ALL
);
} finally {
Services.prefs.setBoolPref(
"app.update.background.checkPolicy.throttleEnabled",
prev
);
}
});
add_task(async function test_all() {
// Last task run days don't matter, since not throttled.
await testWithActionsToPerform(
{
lastBrowsedDays: 10,
lastTaskRunHours: 0,
},
ACTIONS_ALL
);
await testWithActionsToPerform(
{
lastBrowsedDays: 10,
lastTaskRunHours: 23,
},
ACTIONS_ALL
);
await testWithActionsToPerform(
{
lastBrowsedDays: 10,
lastTaskRunHours: 48,
},
ACTIONS_ALL
);
// Test the boundary condition.
await testWithActionsToPerform(
{
lastBrowsedDays: 10,
lastTaskRunHours: 0,
},
ACTIONS_ALL
);
await testWithActionsToPerform(
{
lastBrowsedDays: 10,
lastTaskRunHours: 23,
},
ACTIONS_ALL
);
await testWithActionsToPerform(
{
lastBrowsedDays: 10,
lastTaskRunHours: 48,
},
ACTIONS_ALL
);
// Test with the task never having run before.
await testWithActionsToPerform(
{
lastBrowsedDays: 10,
lastTaskRunUnset: true,
},
ACTIONS_ALL
);
// Test the boundary condition.
await testWithActionsToPerform(
{
lastBrowsedDays: 14,
lastTaskRunHours: 48,
},
ACTIONS_ALL
);
});
add_task(async function test_throttled() {
// Test the boundary condition.
await testWithActionsToPerform(
{
lastBrowsedDays: 15,
lastTaskRunHours: 24,
},
ACTIONS_THROTTLED
);
// More obvious cases.
await testWithActionsToPerform(
{
lastBrowsedDays: 100,
lastTaskRunHours: 48,
},
ACTIONS_THROTTLED
);
await testWithActionsToPerform(
{
lastBrowsedDays: 100,
lastTaskRunHours: 48,
},
ACTIONS_THROTTLED
);
});
add_task(async function test_debounced() {
await testWithActionsToPerform(
{
lastBrowsedDays: 91,
lastTaskRunHours: 0,
},
ACTIONS_DEBOUNCED
);
await testWithActionsToPerform(
{
lastBrowsedDays: 100,
lastTaskRunHours: 0,
},
ACTIONS_DEBOUNCED
);
// Verify to the hour.
await testWithActionsToPerform(
{
lastBrowsedDays: 100,
lastTaskRunHours: 0,
},
ACTIONS_DEBOUNCED
);
await testWithActionsToPerform(
{
lastBrowsedDays: 100,
lastTaskRunHours: 12,
},
ACTIONS_DEBOUNCED
);
await testWithActionsToPerform(
{
lastBrowsedDays: 100,
lastTaskRunHours: 23,
},
ACTIONS_DEBOUNCED
);
// Boundary condition.
await testWithActionsToPerform(
{
lastBrowsedDays: 100,
lastTaskRunHours: 24,
},
ACTIONS_THROTTLED
);
await testWithActionsToPerform(
{
lastBrowsedDays: 100,
lastTaskRunHours: 25,
},
ACTIONS_THROTTLED
);
// Test with the task never having run before.
await testWithActionsToPerform(
{
lastBrowsedDays: 100,
lastTaskRunUnset: true,
},
ACTIONS_THROTTLED
);
});
async function testRunActions(actions) {
let cmdLine = Cu.createCommandLine(
[],
null,
Ci.nsICommandLine.STATE_INITIAL_LAUNCH
);
let sandbox = sinon.createSandbox();
let stubs = {
[ACTION.EXPERIMENTER]: sandbox
.stub(Actions, "enableNimbusAndFirefoxMessagingSystem")
.resolves(null),
[ACTION.UPDATE]: sandbox
.stub(Actions, "attemptBackgroundUpdate")
.resolves(AppUpdater.STATUS.NO_UPDATES_FOUND),
[ACTION.SUBMIT_PING]: sandbox
.stub(Actions, "maybeSubmitBackgroundUpdatePing")
.returns(null),
};
try {
await runActions(cmdLine, {}, actions);
for (let action of ACTIONS_ALL) {
if (actions.has(action)) {
Assert.ok(stubs[action].calledOnce, `${action} invoked exactly once`);
} else {
Assert.ok(!stubs[action].calledOnce, `${action} not invoked`);
}
}
} finally {
sandbox.restore();
}
}
add_task(async function test_runActionsAll() {
await testRunActions(ACTIONS_ALL);
});
add_task(async function test_runActionsThrottled() {
await testRunActions(ACTIONS_THROTTLED);
});
add_task(async function test_runActionsDebounced() {
await testRunActions(ACTIONS_DEBOUNCED);
});