Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'win' && ccov OR msix
- Manifest: toolkit/mozapps/update/tests/unit_aus_update/xpcshell.toml
/* 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
*/
/**
* This test ensures that if we start up in a good state, we use that state. But
* if we start out in a bad/inconsistent state, we toss that state out and start
* fresh.
*/
let gServerPort;
// enum of possible states we might want to put the update object into when
// testing.
const UpdateObjectState = {
nonexistent: "UpdateObjectState::nonexistent",
hasWrongState: "UpdateObjectState::hasWrongState",
expected: "UpdateObjectState::expected",
};
async function testInitialStateValidation({
updateState,
downloadingUpdateState,
readyUpdateState,
readyUpdateMarExists,
}) {
const url =
APP_UPDATE_SJS_HOST +
":" +
gServerPort +
"/" +
REL_PATH_DATA +
"app_update.sjs?slowDownloadMar=1";
const statusFile = getUpdateDirFile(FILE_UPDATE_STATUS);
const updateXmlFile = getUpdateDirFile(FILE_ACTIVE_UPDATE_XML);
const readyMarFile = getUpdateDirFile(FILE_UPDATE_MAR, DIR_PATCH);
const downloadingMarFile = getUpdateDirFile(FILE_UPDATE_MAR, DIR_DOWNLOADING);
// Step 1: Write the requested state to disk.
let statusFileState = updateState;
if (statusFileState == STATE_FAILED) {
// If the state is failed, the status file is expected to have some sort of
// error code, but which one specifically isn't especially important for
// this test.
statusFileState += STATE_FAILED_DELIMETER;
statusFileState += READ_ERROR;
}
writeStatusFile(statusFileState);
let updates = "";
let readyUpdateStatus;
if (readyUpdateState != UpdateObjectState.nonexistent) {
readyUpdateStatus = updateState;
if (downloadingUpdateState == UpdateObjectState.hasWrongState) {
readyUpdateStatus = STATE_NONE;
}
const patches = getLocalPatchString({ state: readyUpdateStatus, url });
updates += getLocalUpdateString({ appVersion: "2" }, patches);
}
let downloadingUpdateStatus;
if (downloadingUpdateState != UpdateObjectState.nonexistent) {
downloadingUpdateStatus = STATE_DOWNLOADING;
if (downloadingUpdateState == UpdateObjectState.hasWrongState) {
downloadingUpdateStatus = STATE_NONE;
}
const patches = getLocalPatchString({
state: downloadingUpdateStatus,
url,
});
updates += getLocalUpdateString({ appVersion: "3" }, patches);
}
if (updates.length) {
writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
} else {
ensureRemoved(updateXmlFile);
}
if (readyUpdateMarExists) {
writeFile(readyMarFile, "test mar contents");
} else {
ensureRemoved(readyMarFile);
}
// Step 2: Reload the update state from the disk.
await reloadUpdateManagerData();
await reInitUpdateService();
// Step 3: Figure out what the current state _ought_ to be.
let expectedAusState;
switch (updateState) {
case STATE_DOWNLOADING:
if (
downloadingUpdateState == UpdateObjectState.nonexistent ||
readyUpdateState != UpdateObjectState.nonexistent
) {
expectedAusState = Ci.nsIApplicationUpdateService.STATE_IDLE;
} else {
expectedAusState = Ci.nsIApplicationUpdateService.STATE_DOWNLOADING;
}
break;
case STATE_PENDING:
case STATE_PENDING_SVC:
case STATE_APPLIED:
case STATE_APPLIED_SVC:
if (
readyUpdateState == UpdateObjectState.nonexistent ||
!readyUpdateMarExists
) {
expectedAusState = Ci.nsIApplicationUpdateService.STATE_IDLE;
} else {
expectedAusState = Ci.nsIApplicationUpdateService.STATE_PENDING;
readyUpdateStatus = updateState;
}
break;
case STATE_APPLYING:
// We don't have a testcase where the status file has state applying and
// the ready update state is pending or pending-service, which is the
// only case where we actually want to keep that update around rather
// than cleaning it up. But we do want to revert to the downloading state
// if there is a download in-progress.
if (
downloadingUpdateState == UpdateObjectState.expected &&
readyUpdateMarExists
) {
expectedAusState = Ci.nsIApplicationUpdateService.STATE_DOWNLOADING;
} else {
expectedAusState = Ci.nsIApplicationUpdateService.STATE_IDLE;
}
break;
default:
expectedAusState = Ci.nsIApplicationUpdateService.STATE_IDLE;
break;
}
let expectReadyUpdatePresent;
let expectDownloadingUpdatePresent;
switch (expectedAusState) {
case Ci.nsIApplicationUpdateService.STATE_IDLE:
expectReadyUpdatePresent = false;
expectDownloadingUpdatePresent = false;
break;
case Ci.nsIApplicationUpdateService.STATE_DOWNLOADING:
expectReadyUpdatePresent = false;
expectDownloadingUpdatePresent =
downloadingUpdateState == UpdateObjectState.expected;
break;
case Ci.nsIApplicationUpdateService.STATE_PENDING:
expectReadyUpdatePresent = true;
expectDownloadingUpdatePresent =
downloadingUpdateState == UpdateObjectState.expected;
break;
default:
// We always assign one of three states above. It should be impossible to
// hit this branch.
Assert.ok(
false,
`Unexpected value of expectedAusState=${expectedAusState}`
);
break;
}
// Step 3: Verify that the actual state matches the expected state.
Assert.equal(
gAUS.currentState,
expectedAusState,
`AUS state - actual=${gAUS.getStateName(gAUS.currentState)} expected=${gAUS.getStateName(expectedAusState)}`
);
const readyUpdate = await gUpdateManager.getReadyUpdate();
const downloadingUpdate = await gUpdateManager.getDownloadingUpdate();
Assert.equal(
!!readyUpdate,
expectReadyUpdatePresent,
`readyUpdate should${expectReadyUpdatePresent ? "" : " not"} be present: ${readyUpdate}`
);
Assert.equal(
!!downloadingUpdate,
expectDownloadingUpdatePresent,
`downloadingUpdate should${expectDownloadingUpdatePresent ? "" : " not"} be present`
);
if (expectReadyUpdatePresent) {
Assert.equal(
readyUpdate.state,
readyUpdateStatus,
"readyUpdate should have the expected state"
);
}
if (expectDownloadingUpdatePresent) {
Assert.equal(
downloadingUpdate.state,
downloadingUpdateStatus,
"downloadingUpdate should have the expected state"
);
}
// Step 4: Clean up.
if (expectDownloadingUpdatePresent) {
// It's a bit of a problem to clean up an in-progress download in a reliable
// way. Let's just let it finish downloading before we try to move on.
let downloadFinished = gAUS.stateTransition;
await continueFileHandler(CONTINUE_DOWNLOAD);
await downloadFinished;
// We could have just transitioned from downloading to pending or from
// pending to swap. If we are in swap state, wait for that to finish too.
if (gAUS.currentState == Ci.nsIApplicationUpdateService.STATE_SWAP) {
await gAUS.stateTransition;
}
}
ensureRemoved(statusFile);
ensureRemoved(updateXmlFile);
ensureRemoved(readyMarFile);
ensureRemoved(downloadingMarFile);
}
add_task(async function initialStateValidation() {
setupTestCommon(true);
const server = startSjsServer();
gServerPort = server.identity.primaryPort;
setUpdateChannel("test_channel");
Services.prefs.setBoolPref(PREF_APP_UPDATE_DISABLEDFORTESTING, false);
Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
await parameterizedTest(
testInitialStateValidation,
{
updateState: [
STATE_NONE,
STATE_DOWNLOADING,
STATE_PENDING,
STATE_PENDING_SVC,
STATE_APPLYING,
STATE_APPLIED,
STATE_APPLIED_SVC,
STATE_SUCCEEDED,
STATE_DOWNLOAD_FAILED,
STATE_FAILED,
"bad-state",
],
downloadingUpdateState: Object.values(UpdateObjectState),
readyUpdateState: Object.values(UpdateObjectState),
readyUpdateMarExists: [true, false],
},
{
skipFn: ({ updateState, downloadingUpdateState, readyUpdateState }) => {
// We don't actually store our updates in a way where, if there is only a
// single update object, it is inherently clear which one it is meant to be.
// We figure this out by inspecting the state. So a single update in a
// non-downloading state will always be interpreted as the ready update.
// We are going to skip these test cases because it is expected that they
// would fail.
return (
(readyUpdateState == UpdateObjectState.nonexistent &&
(downloadingUpdateState == UpdateObjectState.hasWrongState ||
updateState != STATE_DOWNLOADING)) ||
// Similar to the above situation, the ready update will be misconstrued as
// the downloading update in this situation.
(updateState == STATE_DOWNLOADING &&
readyUpdateState != UpdateObjectState.nonexistent &&
downloadingUpdateState == UpdateObjectState.nonexistent)
);
},
}
);
await doTestFinish();
});