Bug 599233 - add about dialog app update tests. r=mhowell

Adds disabledForTesting checks to the about dialog app update code
Makes a copy of update.sjs for browser-chrome tests so the changes don't break chrome tests
Prepares for the removal of the chrome tests and the old app update UI
Adds 14 tests for app update in the about dialog

--HG--
rename : toolkit/mozapps/update/tests/data/update.sjs => toolkit/mozapps/update/tests/browser/app_update.sjs
rename : toolkit/mozapps/update/tests/data/update.sjs => toolkit/mozapps/update/tests/chrome/update.sjs
This commit is contained in:
Robert Strong 2019-01-16 13:21:55 -08:00
parent 3dc0a3b83a
commit fa80d81a6d
25 changed files with 977 additions and 62 deletions

View File

@ -13,8 +13,9 @@ ChromeUtils.import("resource://gre/modules/DownloadUtils.jsm");
ChromeUtils.defineModuleGetter(this, "UpdateUtils",
"resource://gre/modules/UpdateUtils.jsm");
const PREF_APP_UPDATE_CANCELATIONS_OSX = "app.update.cancelations.osx";
const PREF_APP_UPDATE_ELEVATE_NEVER = "app.update.elevate.never";
const PREF_APP_UPDATE_CANCELATIONS_OSX = "app.update.cancelations.osx";
const PREF_APP_UPDATE_ELEVATE_NEVER = "app.update.elevate.never";
const PREF_APP_UPDATE_DISABLEDFORTESTING = "app.update.disabledForTesting";
var gAppUpdater;
@ -131,7 +132,20 @@ appUpdater.prototype =
// true when updating has been disabled by enterprise policy
get updateDisabledByPolicy() {
return Services.policies && !Services.policies.isAllowed("appUpdate");
return Services.policies && !Services.policies.isAllowed("appUpdate") ||
this.disabledForTesting;
},
get disabledForTesting() {
let marionetteRunning = false;
if ("nsIMarionette" in Ci) {
marionetteRunning = Cc["@mozilla.org/remote/marionette;1"].
createInstance(Ci.nsIMarionette).running;
}
return (Cu.isInAutomation || marionetteRunning) &&
Services.prefs.getBoolPref(PREF_APP_UPDATE_DISABLEDFORTESTING, false);
},
// true when updating in background is enabled.

View File

@ -0,0 +1,241 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/**
* Server side http server script for application update tests.
*/
function getTestDataFile(aFilename) {
let file = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties).get("CurWorkD", Ci.nsIFile);
let pathParts = REL_PATH_DATA.split("/");
for (let i = 0; i < pathParts.length; ++i) {
file.append(pathParts[i]);
}
if (aFilename) {
file.append(aFilename);
}
return file;
}
function loadHelperScript(aScriptFile) {
let io = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
let scriptSpec = io.newFileURI(aScriptFile).spec;
let scriptloader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
getService(Ci.mozIJSSubScriptLoader);
scriptloader.loadSubScript(scriptSpec, this);
}
var scriptFile = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);
scriptFile.initWithPath(getState("__LOCATION__"));
scriptFile = scriptFile.parent;
scriptFile.append("testConstants.js");
loadHelperScript(scriptFile);
scriptFile = getTestDataFile("sharedUpdateXML.js");
loadHelperScript(scriptFile);
const SERVICE_URL = URL_HOST + "/" + REL_PATH_DATA + FILE_SIMPLE_MAR;
const BAD_SERVICE_URL = URL_HOST + "/" + REL_PATH_DATA + "not_here.mar";
const SLOW_RESPONSE_INTERVAL = 10;
var gSlowDownloadTimer;
var gSlowCheckTimer;
function handleRequest(aRequest, aResponse) {
let params = { };
if (aRequest.queryString) {
params = parseQueryString(aRequest.queryString);
}
let statusCode = params.statusCode ? parseInt(params.statusCode) : 200;
let statusReason = params.statusReason ? params.statusReason : "OK";
aResponse.setStatusLine(aRequest.httpVersion, statusCode, statusReason);
aResponse.setHeader("Cache-Control", "no-cache", false);
// When a mar download is started by the update service it can finish
// downloading before the ui has loaded. By specifying a serviceURL for the
// update patch that points to this file and has a slowDownloadMar param the
// mar will be downloaded asynchronously which will allow the ui to load
// before the download completes.
if (params.slowDownloadMar) {
aResponse.processAsync();
aResponse.setHeader("Content-Type", "binary/octet-stream");
aResponse.setHeader("Content-Length", SIZE_SIMPLE_MAR);
var continueFile = getTestDataFile(CONTINUE_DOWNLOAD);
var contents = readFileBytes(getTestDataFile(FILE_SIMPLE_MAR));
gSlowDownloadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
gSlowDownloadTimer.initWithCallback(function(aTimer) {
if (continueFile.exists()) {
try {
// If the continue file is in use try again the next time the timer
// fires.
continueFile.remove(false);
gSlowDownloadTimer.cancel();
aResponse.write(contents);
aResponse.finish();
} catch (e) {
}
}
}, SLOW_RESPONSE_INTERVAL, Ci.nsITimer.TYPE_REPEATING_SLACK);
return;
}
if (params.uiURL) {
aResponse.write("<html><head><meta http-equiv=\"content-type\" content=" +
"\"text/html; charset=utf-8\"></head><body>" +
params.uiURL + "<br><br>this is a test mar that will not " +
"affect your build.</body></html>");
return;
}
if (params.xmlMalformed) {
respond(aResponse, params, "xml error");
return;
}
if (params.noUpdates) {
respond(aResponse, params, getRemoteUpdatesXMLString(""));
return;
}
if (params.unsupported) {
let detailsURL = params.detailsURL ? params.detailsURL : URL_HOST;
let unsupportedXML = getRemoteUpdatesXMLString(" <update type=\"major\" " +
"unsupported=\"true\" " +
"detailsURL=\"" + detailsURL +
"\"></update>\n");
respond(aResponse, params, unsupportedXML);
return;
}
let size;
let patches = "";
let url = "";
if (params.useSlowDownloadMar) {
url = URL_HTTP_UPDATE_SJS + "?slowDownloadMar=1"
} else {
url = params.badURL ? BAD_SERVICE_URL : SERVICE_URL
}
if (!params.partialPatchOnly) {
size = SIZE_SIMPLE_MAR + (params.invalidCompleteSize ? "1" : "");
let patchProps = {type: "complete",
url: url,
size: size};
patches += getRemotePatchString(patchProps);
}
if (!params.completePatchOnly) {
size = SIZE_SIMPLE_MAR + (params.invalidPartialSize ? "1" : "");
let patchProps = {type: "partial",
url: url,
size: size};
patches += getRemotePatchString(patchProps);
}
let updateProps = {};
if (params.type) {
updateProps.type = params.type;
}
if (params.name) {
updateProps.name = params.name;
}
if (params.appVersion) {
updateProps.appVersion = params.appVersion;
}
if (params.displayVersion) {
updateProps.displayVersion = params.displayVersion;
}
if (params.buildID) {
updateProps.buildID = params.buildID;
}
if (params.promptWaitTime) {
updateProps.promptWaitTime = params.promptWaitTime;
}
let updates = getRemoteUpdateString(updateProps, patches);
let xml = getRemoteUpdatesXMLString(updates);
respond(aResponse, params, xml);
}
function respond(aResponse, aParams, aResponseString) {
if (aParams.slowUpdateCheck) {
aResponse.processAsync();
var continueFile = getTestDataFile(CONTINUE_CHECK);
gSlowCheckTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
gSlowCheckTimer.initWithCallback(function(aTimer) {
if (continueFile.exists()) {
try {
// If the continue file is in use try again the next time the timer
// fires.
continueFile.remove(false);
gSlowCheckTimer.cancel();
aResponse.write(aResponseString);
aResponse.finish();
} catch (e) {
}
}
}, SLOW_RESPONSE_INTERVAL, Ci.nsITimer.TYPE_REPEATING_SLACK);
} else {
aResponse.write(aResponseString);
}
}
/**
* Helper function to create a JS object representing the url parameters from
* the request's queryString.
*
* @param aQueryString
* The request's query string.
* @return A JS object representing the url parameters from the request's
* queryString.
*/
function parseQueryString(aQueryString) {
let paramArray = aQueryString.split("&");
let regex = /^([^=]+)=(.*)$/;
let params = {};
for (let i = 0, sz = paramArray.length; i < sz; i++) {
let match = regex.exec(paramArray[i]);
if (!match) {
throw "Bad parameter in queryString! '" + paramArray[i] + "'";
}
params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
}
return params;
}
/**
* Reads the binary contents of a file and returns it as a string.
*
* @param aFile
* The file to read from.
* @return The contents of the file as a string.
*/
function readFileBytes(aFile) {
let fis = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
fis.init(aFile, -1, -1, false);
let bis = Cc["@mozilla.org/binaryinputstream;1"].
createInstance(Ci.nsIBinaryInputStream);
bis.setInputStream(fis);
let data = [];
let count = fis.available();
while (count > 0) {
let bytes = bis.readByteArray(Math.min(65535, count));
data.push(String.fromCharCode.apply(null, bytes));
count -= bytes.length;
if (bytes.length == 0) {
throw "Nothing read from input stream!";
}
}
data.join('');
fis.close();
return data.toString();
}

View File

@ -4,7 +4,30 @@ support-files =
head.js
downloadPage.html
testConstants.js
app_update.sjs
[browser_about_bc_downloaded.js]
[browser_about_bc_downloaded_staged.js]
skip-if = asan
reason = Bug 1168003
[browser_about_fc_check_cantApply.js]
skip-if = os != 'win'
reason = test must be able to prevent file deletion.
[browser_about_fc_check_malformedXML.js]
[browser_about_fc_check_noUpdate.js]
[browser_about_fc_check_unsupported.js]
[browser_about_fc_downloadAuto.js]
[browser_about_fc_downloadAuto_staging.js]
skip-if = asan
reason = Bug 1168003
[browser_about_fc_downloadOptIn.js]
[browser_about_fc_downloadOptIn_staging.js]
skip-if = asan
reason = Bug 1168003
[browser_about_fc_patch_completeBadSize.js]
[browser_about_fc_patch_partialBadSize.js]
[browser_about_fc_patch_partialBadSize_complete.js]
[browser_about_fc_patch_partialBadSize_completeBadSize.js]
[browser_TelemetryUpdatePing.js]
[browser_updateAutoPrefUI.js]
skip-if = os != 'win'
@ -17,6 +40,7 @@ reason = Bug 1168003
[browser_updatesBasicPromptNoStaging.js]
[browser_updatesCantApply.js]
skip-if = os != 'win'
reason = test must be able to prevent file deletion.
[browser_updatesCompleteAndPartialPatchesWithBadCompleteSize.js]
[browser_updatesCompleteAndPartialPatchesWithBadPartialSize.js]
[browser_updatesCompleteAndPartialPatchesWithBadSizes.js]

View File

@ -0,0 +1,17 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for About Dialog background check for updates
// with the update downloaded.
add_task(async function aboutDialog_backgroundCheck_downloaded() {
let updateParams = "";
await runAboutDialogUpdateTest(updateParams, true, [
{
panelId: "apply",
checkActiveUpdate: {state: STATE_PENDING},
continueFile: null,
},
]);
});

View File

@ -0,0 +1,25 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for About Dialog background check for updates
// with the update downloaded and staged.
add_task(async function aboutDialog_backgroundCheck_downloaded_staged() {
await SpecialPowers.pushPrefEnv({
set: [
[PREF_APP_UPDATE_STAGING_ENABLED, true],
],
});
// Since the partial should be successful specify an invalid size for the
// complete update.
let updateParams = "&invalidCompleteSize=1";
await runAboutDialogUpdateTest(updateParams, true, [
{
panelId: "apply",
checkActiveUpdate: {state: STATE_APPLIED},
continueFile: null,
},
]);
});

View File

@ -0,0 +1,24 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for About Dialog foreground check for updates
// without the ability to apply updates.
add_task(async function aboutDialog_foregroundCheck_cantApply() {
lockWriteTestFile();
let updateParams = "";
await runAboutDialogUpdateTest(updateParams, false, [
{
panelId: "checkingForUpdates",
checkActiveUpdate: null,
continueFile: CONTINUE_CHECK,
},
{
panelId: "manualUpdate",
checkActiveUpdate: null,
continueFile: null,
},
]);
});

View File

@ -0,0 +1,22 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for About Dialog foreground check for updates
// with a malformed update XML file.
add_task(async function aboutDialog_foregroundCheck_malformedXML() {
let updateParams = "&xmlMalformed=1";
await runAboutDialogUpdateTest(updateParams, false, [
{
panelId: "checkingForUpdates",
checkActiveUpdate: null,
continueFile: CONTINUE_CHECK,
},
{
panelId: "noUpdatesFound",
checkActiveUpdate: null,
continueFile: null,
},
]);
});

View File

@ -0,0 +1,22 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for About Dialog foreground check for updates
// with no update available.
add_task(async function aboutDialog_foregroundCheck_noUpdate() {
let updateParams = "&noUpdates=1";
await runAboutDialogUpdateTest(updateParams, false, [
{
panelId: "checkingForUpdates",
checkActiveUpdate: null,
continueFile: CONTINUE_CHECK,
},
{
panelId: "noUpdatesFound",
checkActiveUpdate: null,
continueFile: null,
},
]);
});

View File

@ -0,0 +1,22 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for About Dialog foreground check for updates
// with an unsupported update.
add_task(async function aboutDialog_foregroundCheck_unsupported() {
let updateParams = "&unsupported=1";
await runAboutDialogUpdateTest(updateParams, false, [
{
panelId: "checkingForUpdates",
checkActiveUpdate: null,
continueFile: CONTINUE_CHECK,
},
{
panelId: "unsupportedSystem",
checkActiveUpdate: null,
continueFile: null,
},
]);
});

View File

@ -0,0 +1,29 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for About Dialog foreground check for updates
// with an automatic download.
add_task(async function aboutDialog_foregroundCheck_downloadAuto() {
// Since the partial should be successful specify an invalid size for the
// complete update.
let updateParams = "&invalidCompleteSize=1";
await runAboutDialogUpdateTest(updateParams, false, [
{
panelId: "checkingForUpdates",
checkActiveUpdate: null,
continueFile: CONTINUE_CHECK,
},
{
panelId: "downloading",
checkActiveUpdate: {state: STATE_DOWNLOADING},
continueFile: CONTINUE_DOWNLOAD,
},
{
panelId: "apply",
checkActiveUpdate: {state: STATE_PENDING},
continueFile: null,
},
]);
});

View File

@ -0,0 +1,40 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for About Dialog foreground check for updates
// with an automatic download and update staging.
add_task(async function aboutDialog_foregroundCheck_downloadAuto_staging() {
await SpecialPowers.pushPrefEnv({
set: [
[PREF_APP_UPDATE_STAGING_ENABLED, true],
],
});
// Since the partial should be successful specify an invalid size for the
// complete update.
let updateParams = "&invalidCompleteSize=1";
await runAboutDialogUpdateTest(updateParams, false, [
{
panelId: "checkingForUpdates",
checkActiveUpdate: null,
continueFile: CONTINUE_CHECK,
},
{
panelId: "downloading",
checkActiveUpdate: {state: STATE_DOWNLOADING},
continueFile: CONTINUE_DOWNLOAD,
},
{
panelId: "applying",
checkActiveUpdate: {state: STATE_PENDING},
continueFile: CONTINUE_STAGING,
},
{
panelId: "apply",
checkActiveUpdate: {state: STATE_APPLIED},
continueFile: null,
},
]);
});

View File

@ -0,0 +1,36 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for About Dialog foreground check for updates
// with a manual download.
add_task(async function aboutDialog_foregroundCheck_downloadOptIn() {
await UpdateUtils.setAppUpdateAutoEnabled(false);
// Since the partial should be successful specify an invalid size for the
// complete update.
let updateParams = "&invalidCompleteSize=1";
await runAboutDialogUpdateTest(updateParams, false, [
{
panelId: "checkingForUpdates",
checkActiveUpdate: null,
continueFile: CONTINUE_CHECK,
},
{
panelId: "downloadAndInstall",
checkActiveUpdate: null,
continueFile: null,
},
{
panelId: "downloading",
checkActiveUpdate: {state: STATE_DOWNLOADING},
continueFile: CONTINUE_DOWNLOAD,
},
{
panelId: "apply",
checkActiveUpdate: {state: STATE_PENDING},
continueFile: null,
},
]);
});

View File

@ -0,0 +1,46 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for About Dialog foreground check for updates
// with a manual download and update staging.
add_task(async function aboutDialog_foregroundCheck_downloadOptIn_staging() {
await SpecialPowers.pushPrefEnv({
set: [
[PREF_APP_UPDATE_STAGING_ENABLED, true],
],
});
await UpdateUtils.setAppUpdateAutoEnabled(false);
// Since the partial should be successful specify an invalid size for the
// complete update.
let updateParams = "&invalidCompleteSize=1";
await runAboutDialogUpdateTest(updateParams, false, [
{
panelId: "checkingForUpdates",
checkActiveUpdate: null,
continueFile: CONTINUE_CHECK,
},
{
panelId: "downloadAndInstall",
checkActiveUpdate: null,
continueFile: null,
},
{
panelId: "downloading",
checkActiveUpdate: {state: STATE_DOWNLOADING},
continueFile: CONTINUE_DOWNLOAD,
},
{
panelId: "applying",
checkActiveUpdate: {state: STATE_PENDING},
continueFile: CONTINUE_STAGING,
},
{
panelId: "apply",
checkActiveUpdate: {state: STATE_APPLIED},
continueFile: null,
},
]);
});

View File

@ -0,0 +1,27 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for About Dialog foreground check for updates
// with a complete bad size patch.
add_task(async function aboutDialog_foregroundCheck_completeBadSize() {
let updateParams = "&completePatchOnly=1&invalidCompleteSize=1";
await runAboutDialogUpdateTest(updateParams, false, [
{
panelId: "checkingForUpdates",
checkActiveUpdate: null,
continueFile: CONTINUE_CHECK,
},
{
panelId: "downloading",
checkActiveUpdate: {state: STATE_DOWNLOADING},
continueFile: CONTINUE_DOWNLOAD,
},
{
panelId: "downloadFailed",
checkActiveUpdate: null,
continueFile: null,
},
]);
});

View File

@ -0,0 +1,27 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for About Dialog foreground check for updates
// with a partial bad size patch.
add_task(async function aboutDialog_foregroundCheck_partialBadSize() {
let updateParams = "&partialPatchOnly=1&invalidPartialSize=1";
await runAboutDialogUpdateTest(updateParams, false, [
{
panelId: "checkingForUpdates",
checkActiveUpdate: null,
continueFile: CONTINUE_CHECK,
},
{
panelId: "downloading",
checkActiveUpdate: {state: STATE_DOWNLOADING},
continueFile: CONTINUE_DOWNLOAD,
},
{
panelId: "downloadFailed",
checkActiveUpdate: null,
continueFile: null,
},
]);
});

View File

@ -0,0 +1,32 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for About Dialog foreground check for updates
// with a partial bad size patch and a complete patch.
add_task(async function aboutDialog_foregroundCheck_partialBadSize_complete() {
let updateParams = "&invalidPartialSize=1";
await runAboutDialogUpdateTest(updateParams, false, [
{
panelId: "checkingForUpdates",
checkActiveUpdate: null,
continueFile: CONTINUE_CHECK,
},
{
panelId: "downloading",
checkActiveUpdate: {state: STATE_DOWNLOADING},
continueFile: CONTINUE_DOWNLOAD,
},
{
panelId: "downloading",
checkActiveUpdate: {state: STATE_DOWNLOADING},
continueFile: CONTINUE_DOWNLOAD,
},
{
panelId: "apply",
checkActiveUpdate: {state: STATE_PENDING},
continueFile: null,
},
]);
});

View File

@ -0,0 +1,32 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for About Dialog foreground check for updates
// with a partial bad size patch and a complete bad size patch.
add_task(async function aboutDialog_foregroundCheck_partialBadSize_completeBadSize() {
let updateParams = "&invalidPartialSize=1&invalidCompleteSize=1";
await runAboutDialogUpdateTest(updateParams, false, [
{
panelId: "checkingForUpdates",
checkActiveUpdate: null,
continueFile: CONTINUE_CHECK,
},
{
panelId: "downloading",
checkActiveUpdate: {state: STATE_DOWNLOADING},
continueFile: CONTINUE_DOWNLOAD,
},
{
panelId: "downloading",
checkActiveUpdate: {state: STATE_DOWNLOADING},
continueFile: CONTINUE_DOWNLOAD,
},
{
panelId: "downloadFailed",
checkActiveUpdate: null,
continueFile: null,
},
]);
});

View File

@ -5,7 +5,6 @@ add_task(async function testBasicPrompt() {
await UpdateUtils.setAppUpdateAutoEnabled(false);
let updateParams = "promptWaitTime=0";
gUseTestUpdater = true;
await runUpdateTest(updateParams, 1, [
{

View File

@ -1,13 +1,9 @@
add_task(async function testBasicPrompt() {
SpecialPowers.pushPrefEnv({set: [[PREF_APP_UPDATE_SERVICE_ENABLED, false]]});
lockWriteTestFile();
let updateParams = "promptWaitTime=0";
let file = getWriteTestFile();
file.create(file.NORMAL_FILE_TYPE, 0o444);
file.fileAttributesWin |= file.WFA_READONLY;
file.fileAttributesWin &= ~file.WFA_READWRITE;
await runUpdateTest(updateParams, 1, [
{
notificationId: "update-manual",
@ -17,20 +13,7 @@ add_task(async function testBasicPrompt() {
is(gBrowser.selectedBrowser.currentURI.spec,
URL_MANUAL_UPDATE, "Landed on manual update page.");
gBrowser.removeTab(gBrowser.selectedTab);
getWriteTestFile();
},
},
]);
});
function getWriteTestFile() {
let file = getUpdatesRootDir();
file.append(FILE_UPDATE_TEST);
file.QueryInterface(Ci.nsILocalFileWin);
if (file.exists()) {
file.fileAttributesWin |= file.WFA_READWRITE;
file.fileAttributesWin &= ~file.WFA_READONLY;
file.remove(true);
}
return file;
}

View File

@ -16,12 +16,9 @@ const FILE_UPDATER_BIN_BAK = FILE_UPDATER_BIN + ".bak";
const PREF_APP_UPDATE_INTERVAL = "app.update.interval";
const PREF_APP_UPDATE_LASTUPDATETIME = "app.update.lastUpdateTime.background-update-timer";
let gRembemberedPrefs = [];
const DATA_URI_SPEC = "chrome://mochitests/content/browser/toolkit/mozapps/update/tests/browser/";
var DEBUG_AUS_TEST = true;
var gUseTestUpdater = false;
const LOG_FUNCTION = info;
@ -38,33 +35,105 @@ const URL_MANUAL_UPDATE = gURLData + "downloadPage.html";
const gEnv = Cc["@mozilla.org/process/environment;1"].
getService(Ci.nsIEnvironment);
const NOTIFICATIONS = [
"update-available",
"update-manual",
"update-restart",
];
let gOriginalUpdateAutoValue = null;
/**
* Delay for a very short period. Useful for moving the code after this
* to the back of the event loop.
* Creates the continue file used to signal that update staging or the mock http
* server should continue. The delay this creates allows the tests to verify the
* user interfaces before they auto advance to phases of an update. The continue
* file for staging will be deleted by the test updater and the continue file
* for update check and update download requests will be deleted by the test
* http server handler implemented in app_update.sjs. The test returns a promise
* so the test can wait on the deletion of the continue file when necessary.
*
* @return A promise which will resolve after a very short period.
* @param leafName
* The leafName of the file to create. This should be one of the
* folowing constants that are defined in testConstants.js:
* CONTINUE_CHECK
* CONTINUE_DOWNLOAD
* CONTINUE_STAGING
* @return Promise
* Resolves when the file is deleted.
* Rejects if timeout is exceeded or condition ever throws.
* @throws If the file already exists.
*/
function delay() {
return new Promise(resolve => executeSoon(resolve));
async function continueFileHandler(leafName) {
// The default number of retries of 50 in TestUtils.waitForCondition is
// sufficient for test http server requests. The total time to wait with the
// default interval of 100 is approximately 5 seconds.
let retries = undefined;
let continueFile;
if (leafName == CONTINUE_STAGING) {
debugDump("creating " + leafName + " file for slow update staging");
// Use 100 retries for staging requests to lessen the likelihood of tests
// intermittently failing on debug builds due to launching the updater. The
// total time to wait with the default interval of 100 is approximately 10
// seconds. The test updater uses the same values.
retries = 100;
continueFile = getUpdatesPatchDir();
continueFile.append(leafName);
} else {
debugDump("creating " + leafName + " file for slow http server requests");
continueFile = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
let continuePath = REL_PATH_DATA + leafName;
let continuePathParts = continuePath.split("/");
for (let i = 0; i < continuePathParts.length; ++i) {
continueFile.append(continuePathParts[i]);
}
}
if (continueFile.exists()) {
throw new Error("The continue file should not exist, path: " +
continueFile.path);
}
continueFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
return BrowserTestUtils.waitForCondition(() =>
(!continueFile.exists()),
"Waiting for file to be deleted, path: " + continueFile.path,
undefined, retries);
}
/**
* Creates and locks the app update write test file so it is possible to test
* when the user doesn't have write access to update. Since this is only
* possible on Windows the function throws when it is called on other platforms.
* This uses registerCleanupFunction to remove the lock and the file when the
* test completes.
*
* @throws If the function is called on a platform other than Windows.
*/
function lockWriteTestFile() {
if (AppConstants.platform != "win") {
throw new Error("Windows only test function called");
}
let file = getUpdatesRootDir();
file.append(FILE_UPDATE_TEST);
file.QueryInterface(Ci.nsILocalFileWin);
// Remove the file if it exists just in case.
if (file.exists()) {
file.fileAttributesWin |= file.WFA_READWRITE;
file.fileAttributesWin &= ~file.WFA_READONLY;
file.remove(false);
}
file.create(file.NORMAL_FILE_TYPE, 0o444);
file.fileAttributesWin |= file.WFA_READONLY;
file.fileAttributesWin &= ~file.WFA_READWRITE;
registerCleanupFunction(() => {
file.fileAttributesWin |= file.WFA_READWRITE;
file.fileAttributesWin &= ~file.WFA_READONLY;
file.remove(false);
});
}
/**
* Gets the update version info for the update url parameters to send to
* update.sjs.
* app_update.sjs.
*
* @param aAppVersion (optional)
* The application version for the update snippet. If not specified the
* current application version will be used.
* @return The url parameters for the application and platform version to send
* to update.sjs.
* to app_update.sjs.
*/
function getVersionParams(aAppVersion) {
let appInfo = Services.appinfo;
@ -124,7 +193,7 @@ add_task(async function setDefaults() {
* everything is cleaned up afterwards.
*
* @param updateParams
* URL-encoded params which will be sent to update.sjs.
* Params which will be sent to app_update.sjs.
* @param checkAttempts
* How many times to check for updates. Useful for testing the UI
* for check failures.
@ -318,21 +387,20 @@ function checkWhatsNewLink(win, id, url) {
}
/**
* For tests that use the test updater restores the backed up real updater if
* it exists and tries again on failure since Windows debug builds at times
* leave the file in use. After success moveRealUpdater is called to continue
* the setup of the test updater. For tests that don't use the test updater
* runTest will be called.
* For staging tests the test updater must be used and this restores the backed
* up real updater if it exists and tries again on failure since Windows debug
* builds at times leave the file in use. After success moveRealUpdater is
* called to continue the setup of the test updater.
*/
function setupTestUpdater() {
return (async function() {
if (gUseTestUpdater) {
if (Services.prefs.getBoolPref(PREF_APP_UPDATE_STAGING_ENABLED)) {
try {
restoreUpdaterBackup();
} catch (e) {
logTestInfo("Attempt to restore the backed up updater failed... " +
"will try again, Exception: " + e);
await delay();
await TestUtils.waitForTick();
await setupTestUpdater();
return;
}
@ -357,7 +425,7 @@ function moveRealUpdater() {
} catch (e) {
logTestInfo("Attempt to move the real updater out of the way failed... " +
"will try again, Exception: " + e);
await delay();
await TestUtils.waitForTick();
await moveRealUpdater();
return;
}
@ -367,9 +435,8 @@ function moveRealUpdater() {
}
/**
* Copies the test updater so it can be used by tests and tries again on failure
* since Windows debug builds at times leave the file in use. After success it
* will call runTest to continue the test.
* Copies the test updater and tries again on failure since Windows debug builds
* at times leave the file in use.
*/
function copyTestUpdater(attempt = 0) {
return (async function() {
@ -391,7 +458,7 @@ function copyTestUpdater(attempt = 0) {
if (attempt < MAX_UPDATE_COPY_ATTEMPTS) {
logTestInfo("Attempt to copy the test updater failed... " +
"will try again, Exception: " + e);
await delay();
await TestUtils.waitForTick();
await copyTestUpdater(attempt + 1);
}
}
@ -401,9 +468,7 @@ function copyTestUpdater(attempt = 0) {
/**
* Restores the updater that was backed up. This is called in setupTestUpdater
* before the backup of the real updater is done in case the previous test
* failed to restore the updater, in finishTestDefaultWaitForWindowClosed when
* the test has finished, and in test_9999_cleanup.xul after all tests have
* finished.
* failed to restore the updater when the test has finished.
*/
function restoreUpdaterBackup() {
let baseAppDir = getAppBaseDir();
@ -420,13 +485,12 @@ function restoreUpdaterBackup() {
}
/**
* When a test finishes this will repeatedly attempt to restore the real updater
* for tests that use the test updater and then call
* finishTestDefaultWaitForWindowClosed after the restore is successful.
* When a staging test finishes this will repeatedly attempt to restore the real
* updater.
*/
function finishTestRestoreUpdaterBackup() {
return (async function() {
if (gUseTestUpdater) {
if (Services.prefs.getBoolPref(PREF_APP_UPDATE_STAGING_ENABLED)) {
try {
// Windows debug builds keep the updater file in use for a short period of
// time after the updater process exits.
@ -435,9 +499,169 @@ function finishTestRestoreUpdaterBackup() {
logTestInfo("Attempt to restore the backed up updater failed... " +
"will try again, Exception: " + e);
await delay();
await TestUtils.waitForTick();
await finishTestRestoreUpdaterBackup();
}
}
})();
}
/**
* Waits for the About Dialog to load.
*
* @return A promise that returns the domWindow for the About Dialog and
* resolves when the About Dialog loads.
*/
function waitForAboutDialog() {
return new Promise(resolve => {
var listener = {
onOpenWindow: aXULWindow => {
debugDump("About dialog shown...");
Services.wm.removeListener(listener);
async function aboutDialogOnLoad() {
domwindow.removeEventListener("load", aboutDialogOnLoad, true);
let chromeURI = "chrome://browser/content/aboutDialog.xul";
is(domwindow.document.location.href, chromeURI, "About dialog appeared");
resolve(domwindow);
}
var domwindow = aXULWindow.docShell.domWindow;
domwindow.addEventListener("load", aboutDialogOnLoad, true);
},
onCloseWindow: aXULWindow => {},
};
Services.wm.addListener(listener);
openAboutDialog();
});
}
/**
* Runs an About Dialog update test. This will set various common prefs for
* updating and runs the provided list of steps.
*
* @param updateParams
* Params which will be sent to app_update.sjs.
* @param backgroundUpdate
* If true a background check will be performed before opening the About
* Dialog.
* @param steps
* An array of test steps to perform. A step will either be an object
* containing expected conditions and actions or a function to call.
* @return A promise which will resolve once all of the steps have been run.
*/
function runAboutDialogUpdateTest(updateParams, backgroundUpdate, steps) {
let aboutDialog;
function processAboutDialogStep(step) {
if (typeof(step) == "function") {
return step();
}
// Helper function to get the selected panel.
function getSelectedPanel() {
return aboutDialog.document.getElementById("updateDeck").selectedPanel;
}
// Helper function to get the selected panel's button.
function getSelectedPanelButton() {
return getSelectedPanel().querySelector("button");
}
// Helper function to get the selected panel's label with a class of
// text-link.
function getSelectedLabelLink() {
return getSelectedPanel().querySelector("label.text-link");
}
const {panelId, checkActiveUpdate, continueFile} = step;
return (async function() {
await BrowserTestUtils.waitForCondition(() =>
(getSelectedPanel() && getSelectedPanel().id == panelId),
"Waiting for expected panel ID - expected \"" + panelId + "\"");
// Skip when checkActiveUpdate evaluates to false.
if (checkActiveUpdate) {
ok(!!gUpdateManager.activeUpdate, "There should be an active update");
is(gUpdateManager.activeUpdate.state, checkActiveUpdate.state,
"The active update state should equal " + checkActiveUpdate.state);
} else {
ok(!gUpdateManager.activeUpdate,
"There should not be an active update");
}
if (continueFile) {
await continueFileHandler(continueFile);
}
let linkPanels = ["downloadFailed", "manualUpdate", "unsupportedSystem"];
if (linkPanels.includes(panelId)) {
// The unsupportedSystem panel uses the update's detailsURL and the
// downloadFailed and manualUpdate panels use the app.update.url.manual
// preference.
let labelLink = getSelectedLabelLink();
is(labelLink.href, URL_HOST,
"The panel's link href should equal the expected value");
}
let buttonPanels = ["downloadAndInstall", "apply"];
if (buttonPanels.includes(panelId)) {
let buttonEl = getSelectedPanelButton();
await BrowserTestUtils.waitForCondition(() =>
(aboutDialog.document.activeElement == buttonEl),
"The button should receive focus");
ok(!buttonEl.disabled, "The button should be enabled");
// Don't click the button on the apply panel since this will restart the
// application.
if (panelId != "apply") {
buttonEl.click();
}
}
})();
}
return (async function() {
await SpecialPowers.pushPrefEnv({
set: [
[PREF_APP_UPDATE_SERVICE_ENABLED, false],
[PREF_APP_UPDATE_DISABLEDFORTESTING, false],
[PREF_APP_UPDATE_URL_MANUAL, URL_HOST],
],
});
registerCleanupFunction(() => {
gEnv.set("MOZ_TEST_SLOW_SKIP_UPDATE_STAGE", "");
UpdateListener.reset();
cleanUpUpdates();
});
gEnv.set("MOZ_TEST_SLOW_SKIP_UPDATE_STAGE", "1");
setUpdateTimerPrefs();
removeUpdateDirsAndFiles();
await setupTestUpdater();
let url = URL_HTTP_UPDATE_SJS + "?detailsURL=" + URL_HOST +
updateParams + getVersionParams();
if (backgroundUpdate) {
setUpdateURL(url);
if (Services.prefs.getBoolPref(PREF_APP_UPDATE_STAGING_ENABLED)) {
// Don't wait on the deletion of the continueStaging file
continueFileHandler(CONTINUE_STAGING);
}
gAUS.checkForBackgroundUpdates();
await waitForEvent("update-downloaded");
} else {
url += "&slowUpdateCheck=1&useSlowDownloadMar=1";
setUpdateURL(url);
}
aboutDialog = await waitForAboutDialog();
for (let step of steps) {
await processAboutDialogStep(step);
}
aboutDialog.close();
await finishTestRestoreUpdaterBackup();
})();
}

View File

@ -1,4 +1,7 @@
const REL_PATH_DATA = "browser/toolkit/mozapps/update/tests/browser/";
const URL_HOST = "http://example.com";
const URL_PATH_UPDATE_XML = "/" + REL_PATH_DATA + "update.sjs";
const URL_PATH_UPDATE_XML = "/" + REL_PATH_DATA + "app_update.sjs";
const URL_HTTP_UPDATE_SJS = URL_HOST + URL_PATH_UPDATE_XML;
const CONTINUE_CHECK = "continueCheck";
const CONTINUE_DOWNLOAD = "continueDownload";
const CONTINUE_STAGING = "continueStaging";

View File

@ -6,6 +6,7 @@
tags = appupdate
support-files =
testConstants.js
update.sjs
utils.js
# mochitest-chrome tests must start with "test_" and are executed in sorted

View File

@ -65,14 +65,12 @@ TEST_HARNESS_FILES.testing.mochitest.browser.toolkit.mozapps.update.tests.browse
'data/shared.js',
'data/sharedUpdateXML.js',
'data/simple.mar',
'data/update.sjs',
]
TEST_HARNESS_FILES.testing.mochitest.chrome.toolkit.mozapps.update.tests.chrome += [
'data/shared.js',
'data/sharedUpdateXML.js',
'data/simple.mar',
'data/update.sjs',
]
FINAL_TARGET_FILES += [

View File

@ -2459,6 +2459,33 @@ static void UpdateThreadFunc(void *param) {
// this can cause tests to timeout.
if (EnvHasValue("MOZ_TEST_SKIP_UPDATE_STAGE")) {
rv = OK;
} else if (EnvHasValue("MOZ_TEST_SLOW_SKIP_UPDATE_STAGE")) {
// The following is to simulate staging so the UI tests have time to
// show that the update is being staged.
NS_tchar continueFilePath[MAXPATHLEN] = {NS_T('\0')};
NS_tsnprintf(continueFilePath,
sizeof(continueFilePath) / sizeof(continueFilePath[0]),
NS_T("%s/continueStaging"), gPatchDirPath);
// Use 100 retries for staging requests to lessen the likelihood of
// tests intermittently failing on debug builds due to launching the
// updater. The total time to wait with the default interval of 100 ms
// is approximately 10 seconds. The tests use the same values.
const int max_retries = 100;
int retries = 1;
while (retries++ < max_retries) {
#ifdef XP_WIN
Sleep(100);
#else
usleep(100000);
#endif
// Continue after the continue file exists and it is successfully
// removed.
if (!NS_taccess(continueFilePath, F_OK) &&
!NS_tremove(continueFilePath)) {
break;
}
}
rv = OK;
} else {
rv = CopyInstallDirToDestDir();
}