Bug 760577 - Part 4: Add full round-trip tests for keeping the mar file around and falling back to apply it at startup if replacing the application fails; r=rstrong

--HG--
rename : toolkit/mozapps/update/test/unit/test_0201_app_launch_apply_update.js => toolkit/mozapps/update/test/unit/test_0203_app_launch_apply_update.js
rename : toolkit/mozapps/update/test_svc/unit/test_0201_app_launch_apply_update_svc.js => toolkit/mozapps/update/test_svc/unit/test_0203_app_launch_apply_update_svc.js
This commit is contained in:
Ehsan Akhgari 2012-06-08 15:06:53 -04:00
parent 48639f87df
commit 87bbd427a3
5 changed files with 1148 additions and 0 deletions

View File

@ -0,0 +1,584 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/**
* Test applying an update by applying an update in the background and
* launching an application
*/
/**
* This test is identical to test_0201_app_launch_apply_update.js, except
* that it locks the application directory when the test is launched to
* make the updater fall back to apply the update regularly.
*/
/**
* The MAR file used for this test should not contain a version 2 update
* manifest file (e.g. updatev2.manifest).
*/
const TEST_ID = "0203";
// Backup the updater.ini and use a custom one to prevent the updater from
// launching a post update executable.
const FILE_UPDATER_INI_BAK = "updater.ini.bak";
// Number of milliseconds for each do_timeout call.
const CHECK_TIMEOUT_MILLI = 1000;
// Maximum number of milliseconds the process that is launched can run before
// the test will try to kill it.
const APP_TIMER_TIMEOUT = 15000;
Components.utils.import("resource://gre/modules/ctypes.jsm");
let gAppTimer;
let gProcess;
let gActiveUpdate;
// Override getUpdatesRootDir on Mac because we need to apply the update
// inside the bundle directory.
function symlinkUpdateFilesIntoBundleDirectory() {
if (!shouldAdjustPathsOnMac()) {
return;
}
// Symlink active-update.xml and updates/ inside the dist/bin directory
// to point to the bundle directory.
// This is necessary because in order to test the code which actually ships
// with Firefox, we need to perform the update inside the bundle directory,
// whereas xpcshell runs from dist/bin/, and the updater service code looks
// at the current process directory to find things like these two files.
Components.utils.import("resource://gre/modules/ctypes.jsm");
let libc = ctypes.open("/usr/lib/libc.dylib");
// We need these two low level APIs because their functionality is not
// provided in nsIFile APIs.
let symlink = libc.declare("symlink", ctypes.default_abi, ctypes.int,
ctypes.char.ptr, ctypes.char.ptr);
let unlink = libc.declare("unlink", ctypes.default_abi, ctypes.int,
ctypes.char.ptr);
// Symlink active-update.xml
let dest = getAppDir();
dest.append("active-update.xml");
if (!dest.exists()) {
dest.create(dest.NORMAL_FILE_TYPE, 0644);
}
do_check_true(dest.exists());
let source = getUpdatesRootDir();
source.append("active-update.xml");
unlink(source.path);
let ret = symlink(dest.path, source.path);
do_check_eq(ret, 0);
do_check_true(source.exists());
// Symlink updates/
let dest2 = getAppDir();
dest2.append("updates");
if (dest2.exists()) {
dest2.remove(true);
}
dest2.create(dest.DIRECTORY_TYPE, 0755);
do_check_true(dest2.exists());
let source2 = getUpdatesRootDir();
source2.append("updates");
if (source2.exists()) {
source2.remove(true);
}
ret = symlink(dest2.path, source2.path);
do_check_eq(ret, 0);
do_check_true(source2.exists());
// Cleanup the symlinks when the test is finished.
do_register_cleanup(function() {
let ret = unlink(source.path);
do_check_false(source.exists());
let ret = unlink(source2.path);
do_check_false(source2.exists());
});
// Now, make sure that getUpdatesRootDir returns the application bundle
// directory, to make the various stuff in the test framework to work
// correctly.
getUpdatesRootDir = getAppDir;
}
function run_test() {
do_test_pending();
do_register_cleanup(end_test);
removeUpdateDirsAndFiles();
symlinkUpdateFilesIntoBundleDirectory();
if (IS_WIN) {
adjustPathsOnWindows();
}
if (!gAppBinPath) {
do_throw("Main application binary not found... expected: " +
APP_BIN_NAME + APP_BIN_SUFFIX);
return;
}
// Don't attempt to show a prompt when the update is finished.
Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, true);
let channel = Services.prefs.getCharPref(PREF_APP_UPDATE_CHANNEL);
let patches = getLocalPatchString(null, null, null, null, null, "true",
STATE_PENDING);
let updates = getLocalUpdateString(patches, null, null, null, null, null,
null, null, null, null, null, null,
null, "true", channel);
writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
// Read the application.ini and use its application version
let processDir = getAppDir();
let file = processDir.clone();
file.append("application.ini");
let ini = AUS_Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
getService(AUS_Ci.nsIINIParserFactory).
createINIParser(file);
let version = ini.getString("App", "Version");
writeVersionFile(version);
writeStatusFile(STATE_PENDING);
// This is the directory where the update files will be located
let updateTestDir = getUpdateTestDir();
try {
removeDirRecursive(updateTestDir);
}
catch (e) {
logTestInfo("unable to remove directory - path: " + updateTestDir.path +
", exception: " + e);
}
let updatesPatchDir = getUpdatesDir();
updatesPatchDir.append("0");
let mar = do_get_file("data/simple.mar");
mar.copyTo(updatesPatchDir, FILE_UPDATE_ARCHIVE);
reloadUpdateManagerData();
gActiveUpdate = gUpdateManager.activeUpdate;
do_check_true(!!gActiveUpdate);
let updateSettingsIni = processDir.clone();
updateSettingsIni.append(UPDATE_SETTINGS_INI_FILE);
writeFile(updateSettingsIni, UPDATE_SETTINGS_CONTENTS);
// Initiate a background update.
AUS_Cc["@mozilla.org/updates/update-processor;1"].
createInstance(AUS_Ci.nsIUpdateProcessor).
processUpdate(gActiveUpdate);
checkUpdateApplied();
}
function switchApp() {
let launchBin = getLaunchBin();
let args = getProcessArgs();
logTestInfo("launching " + launchBin.path + " " + args.join(" "));
// Lock the installation directory
const LPCWSTR = ctypes.jschar.ptr;
const DWORD = ctypes.uint32_t;
const LPVOID = ctypes.voidptr_t;
const GENERIC_READ = 0x80000000;
const FILE_SHARE_READ = 1;
const FILE_SHARE_WRITE = 2;
const OPEN_EXISTING = 3;
const FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
const INVALID_HANDLE_VALUE = LPVOID(0xffffffff);
let kernel32 = ctypes.open("kernel32");
let CreateFile = kernel32.declare("CreateFileW", ctypes.default_abi,
LPVOID, LPCWSTR, DWORD, DWORD,
LPVOID, DWORD, DWORD, LPVOID);
logTestInfo(gWindowsBinDir.path);
let handle = CreateFile(gWindowsBinDir.path, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE, LPVOID(0),
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, LPVOID(0));
do_check_neq(handle.toString(), INVALID_HANDLE_VALUE.toString());
kernel32.close();
gProcess = AUS_Cc["@mozilla.org/process/util;1"].
createInstance(AUS_Ci.nsIProcess);
gProcess.init(launchBin);
gAppTimer = AUS_Cc["@mozilla.org/timer;1"].createInstance(AUS_Ci.nsITimer);
gAppTimer.initWithCallback(gTimerCallback, APP_TIMER_TIMEOUT,
AUS_Ci.nsITimer.TYPE_ONE_SHOT);
setEnvironment();
gProcess.runAsync(args, args.length, gProcessObserver);
resetEnvironment();
}
function end_test() {
if (gProcess.isRunning) {
logTestInfo("attempt to kill process");
gProcess.kill();
}
if (gAppTimer) {
logTestInfo("cancelling timer");
gAppTimer.cancel();
gAppTimer = null;
}
resetEnvironment();
// Remove the files added by the update.
let updateTestDir = getUpdateTestDir();
try {
logTestInfo("removing update test directory " + updateTestDir.path);
removeDirRecursive(updateTestDir);
}
catch (e) {
logTestInfo("unable to remove directory - path: " + updateTestDir.path +
", exception: " + e);
}
if (IS_UNIX) {
// This will delete the launch script if it exists.
getLaunchScript();
if (IS_MACOSX) {
// This will delete the version script and version file if they exist.
getVersionScriptAndFile();
}
}
cleanUp();
}
/**
* The observer for the call to nsIProcess:runAsync.
*/
let gProcessObserver = {
observe: function PO_observe(subject, topic, data) {
logTestInfo("topic " + topic + ", process exitValue " + gProcess.exitValue);
if (gAppTimer) {
gAppTimer.cancel();
gAppTimer = null;
}
if (topic != "process-finished" || gProcess.exitValue != 0) {
do_throw("Failed to launch application");
}
do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateFinished);
},
QueryInterface: XPCOMUtils.generateQI([AUS_Ci.nsIObserver])
};
/**
* The timer callback to kill the process if it takes too long.
*/
let gTimerCallback = {
notify: function TC_notify(aTimer) {
gAppTimer = null;
if (gProcess.isRunning) {
gProcess.kill();
}
do_throw("launch application timer expired");
},
QueryInterface: XPCOMUtils.generateQI([AUS_Ci.nsITimerCallback])
};
function shouldAdjustPathsOnMac() {
// When running xpcshell tests locally, xpcshell and firefox-bin do not live
// in the same directory.
let dir = getCurrentProcessDir();
return (IS_MACOSX && dir.leafName != "MacOS");
}
/**
* This function copies the entire process directory over to a new one which we
* can write to, so that we can test under Windows which holds locks on opened
* files.
*/
function adjustPathsOnWindows() {
// We copy the entire GRE directory into another location so that xpcshell
// running doesn't prevent the updater from moving stuff around.
let tmpDir = do_get_profile();
tmpDir.append("ExecutableDir.tmp");
tmpDir.createUnique(tmpDir.DIRECTORY_TYPE, 0755);
let procDir = getCurrentProcessDir();
procDir.copyTo(tmpDir, "bin");
let newDir = tmpDir.clone();
newDir.append("bin");
gWindowsBinDir = newDir;
logTestInfo("Using this new bin directory: " + gWindowsBinDir.path);
// Note that this directory will be deleted as part of the xpcshell teardown,
// so we don't need to remove it explicitly.
// We need to make NS_GRE_DIR point to the new bindir, since
// nsUpdateProcessor::ProcessUpdate uses NS_GRE_DIR to construct the
// destination path name which would be passed to updater.exe.
let dirProvider = {
getFile: function DP_getFile(prop, persistent) {
persistent.value = true;
if (prop == NS_GRE_DIR)
return getAppDir();
return null;
},
QueryInterface: function(iid) {
if (iid.equals(AUS_Ci.nsIDirectoryServiceProvider) ||
iid.equals(AUS_Ci.nsISupports))
return this;
throw AUS_Cr.NS_ERROR_NO_INTERFACE;
}
};
let ds = Services.dirsvc.QueryInterface(AUS_Ci.nsIDirectoryService);
ds.QueryInterface(AUS_Ci.nsIProperties).undefine(NS_GRE_DIR);
ds.registerProvider(dirProvider);
do_register_cleanup(function() {
ds.unregisterProvider(dirProvider);
});
}
/**
* Gets the directory where the update adds / removes the files contained in the
* update.
*
* @return nsIFile for the directory where the update adds / removes the files
* contained in the update mar.
*/
function getUpdateTestDir() {
let updateTestDir = getAppDir();
if (IS_MACOSX) {
updateTestDir = updateTestDir.parent.parent;
}
updateTestDir.append("update_test");
return updateTestDir;
}
/**
* Checks if the update has finished being applied in the background.
*/
function checkUpdateApplied() {
// Don't proceed until the update has been applied.
if (gUpdateManager.activeUpdate.state != STATE_APPLIED_PLATFORM) {
do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateApplied);
return;
}
let updatedDir = getAppDir();
if (IS_MACOSX) {
updatedDir = updatedDir.parent.parent;
}
updatedDir.append(UPDATED_DIR_SUFFIX.replace("/", ""));
logTestInfo("testing " + updatedDir.path + " should exist");
do_check_true(updatedDir.exists());
let log;
if (IS_WIN) {
log = getUpdatesDir();
} else {
log = updatedDir.clone();
if (IS_MACOSX) {
log.append("Contents");
log.append("MacOS");
}
log.append("updates");
}
log.append(FILE_LAST_LOG);
if (!log.exists()) {
do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateApplied);
return;
}
// Don't proceed until the update status is no longer pending or applying.
let status = readStatusFile();
do_check_eq(status, STATE_APPLIED_PLATFORM);
// On Windows, make sure not to use the maintenance service for switching
// the app.
if (IS_WIN) {
writeStatusFile(STATE_APPLIED);
status = readStatusFile();
do_check_eq(status, STATE_APPLIED);
}
// Log the contents of the update.log so it is simpler to diagnose a test
// failure.
let contents = readFile(log);
logTestInfo("contents of " + log.path + ":\n" +
contents.replace(/\r\n/g, "\n"));
let updateTestDir = getUpdateTestDir();
logTestInfo("testing " + updateTestDir.path + " shouldn't exist");
do_check_false(updateTestDir.exists());
updateTestDir = updatedDir.clone();
updateTestDir.append("update_test");
let file = updateTestDir.clone();
file.append("UpdateTestRemoveFile");
logTestInfo("testing " + file.path + " shouldn't exist");
do_check_false(file.exists());
file = updateTestDir.clone();
file.append("UpdateTestAddFile");
logTestInfo("testing " + file.path + " should exist");
do_check_true(file.exists());
do_check_eq(readFileBytes(file), "UpdateTestAddFile\n");
file = updateTestDir.clone();
file.append("removed-files");
logTestInfo("testing " + file.path + " should exist");
do_check_true(file.exists());
do_check_eq(readFileBytes(file), "update_test/UpdateTestRemoveFile\n");
let updatesDir = getUpdatesDir();
log = updatesDir.clone();
log.append("0");
log.append(FILE_UPDATE_LOG);
logTestInfo("testing " + log.path + " shouldn't exist");
do_check_false(log.exists());
log = updatesDir.clone();
log.append(FILE_LAST_LOG);
if (IS_WIN) {
// On Windows this file lives outside of the app directory, so it should exist.
logTestInfo("testing " + log.path + " should exist");
do_check_true(log.exists());
} else {
logTestInfo("testing " + log.path + " shouldn't exist");
do_check_false(log.exists());
}
log = updatesDir.clone();
log.append(FILE_BACKUP_LOG);
logTestInfo("testing " + log.path + " shouldn't exist");
do_check_false(log.exists());
updatesDir = updatedDir.clone();
if (IS_MACOSX) {
updatesDir.append("Contents");
updatesDir.append("MacOS");
}
updatesDir.append("updates");
log = updatesDir.clone();
log.append("0");
log.append(FILE_UPDATE_LOG);
logTestInfo("testing " + log.path + " shouldn't exist");
do_check_false(log.exists());
if (!IS_WIN) {
log = updatesDir.clone();
log.append(FILE_LAST_LOG);
logTestInfo("testing " + log.path + " should exist");
do_check_true(log.exists());
}
log = updatesDir.clone();
log.append(FILE_BACKUP_LOG);
logTestInfo("testing " + log.path + " shouldn't exist");
do_check_false(log.exists());
updatesDir.append("0");
logTestInfo("testing " + updatesDir.path + " shouldn't exist");
do_check_false(updatesDir.exists());
// Now, switch the updated version of the app
do_timeout(CHECK_TIMEOUT_MILLI, switchApp);
}
/**
* Checks if the update has finished and if it has finished performs checks for
* the test.
*/
function checkUpdateFinished() {
// Don't proceed until the update status is no longer applied.
try {
let status = readStatusFile();
if (status != STATE_SUCCEEDED) {
do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateFinished);
return;
}
} catch (e) {
// Ignore exceptions if the status file is not found
}
try {
// This will delete the app console log file if it exists.
getAppConsoleLogPath();
} catch (e) {
if (e.result == Components.results.NS_ERROR_FILE_IS_LOCKED) {
// This might happen on Windows in case the callback application has not
// finished its job yet. So, we'll wait some more.
do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateFinished);
return;
} else {
do_throw("getAppConsoleLogPath threw: " + e);
}
}
// At this point we need to see if the application was switched successfully.
let updatedDir = getAppDir();
if (IS_MACOSX) {
updatedDir = updatedDir.parent.parent;
}
updatedDir.append(UPDATED_DIR_SUFFIX.replace("/", ""));
logTestInfo("testing " + updatedDir.path + " shouldn't exist");
if (updatedDir.exists()) {
do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateFinished);
return;
}
let updateTestDir = getUpdateTestDir();
let file = updateTestDir.clone();
file.append("UpdateTestRemoveFile");
logTestInfo("testing " + file.path + " shouldn't exist");
do_check_false(file.exists());
file = updateTestDir.clone();
file.append("UpdateTestAddFile");
logTestInfo("testing " + file.path + " should exist");
do_check_true(file.exists());
do_check_eq(readFileBytes(file), "UpdateTestAddFile\n");
file = updateTestDir.clone();
file.append("removed-files");
logTestInfo("testing " + file.path + " should exist");
do_check_true(file.exists());
do_check_eq(readFileBytes(file), "update_test/UpdateTestRemoveFile\n");
let updatesDir = getUpdatesDir();
log = updatesDir.clone();
log.append("0");
log.append(FILE_UPDATE_LOG);
if (IS_WIN) {
// On Windows, this log file is written to the AppData directory, and will
// therefore exist.
logTestInfo("testing " + log.path + " should exist");
do_check_true(log.exists());
} else {
logTestInfo("testing " + log.path + " shouldn't exist");
do_check_false(log.exists());
}
log = updatesDir.clone();
log.append(FILE_LAST_LOG);
logTestInfo("testing " + log.path + " should exist");
do_check_true(log.exists());
log = updatesDir.clone();
log.append(FILE_BACKUP_LOG);
logTestInfo("testing " + log.path + " shouldn't exist");
do_check_false(log.exists());
updatesDir.append("0");
if (IS_WIN) {
// On Windows, this log file is written to the AppData directory, and will
// therefore exist.
logTestInfo("testing " + updatesDir.path + " should exist");
do_check_true(updatesDir.exists());
} else {
logTestInfo("testing " + updatesDir.path + " shouldn't exist");
do_check_false(updatesDir.exists());
}
removeCallbackCopy();
}

View File

@ -27,3 +27,4 @@
[test_0191_rmrfdirFileInUse_xp_win_partial.js]
[test_0202_app_launch_apply_update_dirlocked.js]
skip-if = true
[test_0203_app_launch_apply_update.js]

View File

@ -0,0 +1,561 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/**
* Test applying an update by applying an update in the background and
* launching an application
*/
/**
* This test is identical to test_0201_app_launch_apply_update_svc.js, except
* that it locks the application directory when the test is launched to
* make the updater fall back to apply the update regularly.
*/
/**
* The MAR file used for this test should not contain a version 2 update
* manifest file (e.g. updatev2.manifest).
*/
const TEST_ID = "0203_svc";
// Backup the updater.ini and use a custom one to prevent the updater from
// launching a post update executable.
const FILE_UPDATER_INI_BAK = "updater.ini.bak";
// Number of milliseconds for each do_timeout call.
const CHECK_TIMEOUT_MILLI = 1000;
// Maximum number of milliseconds the process that is launched can run before
// the test will try to kill it.
const APP_TIMER_TIMEOUT = 15000;
Components.utils.import("resource://gre/modules/ctypes.jsm");
let gAppTimer;
let gProcess;
let gActiveUpdate;
// Override getUpdatesRootDir on Mac because we need to apply the update
// inside the bundle directory.
function symlinkUpdateFilesIntoBundleDirectory() {
if (!shouldAdjustPathsOnMac()) {
return;
}
// Symlink active-update.xml and updates/ inside the dist/bin directory
// to point to the bundle directory.
// This is necessary because in order to test the code which actually ships
// with Firefox, we need to perform the update inside the bundle directory,
// whereas xpcshell runs from dist/bin/, and the updater service code looks
// at the current process directory to find things like these two files.
Components.utils.import("resource://gre/modules/ctypes.jsm");
let libc = ctypes.open("/usr/lib/libc.dylib");
// We need these two low level APIs because their functionality is not
// provided in nsIFile APIs.
let symlink = libc.declare("symlink", ctypes.default_abi, ctypes.int,
ctypes.char.ptr, ctypes.char.ptr);
let unlink = libc.declare("unlink", ctypes.default_abi, ctypes.int,
ctypes.char.ptr);
// Symlink active-update.xml
let dest = getAppDir();
dest.append("active-update.xml");
if (!dest.exists()) {
dest.create(dest.NORMAL_FILE_TYPE, 0644);
}
do_check_true(dest.exists());
let source = getUpdatesRootDir();
source.append("active-update.xml");
unlink(source.path);
let ret = symlink(dest.path, source.path);
do_check_eq(ret, 0);
do_check_true(source.exists());
// Symlink updates/
let dest2 = getAppDir();
dest2.append("updates");
if (dest2.exists()) {
dest2.remove(true);
}
dest2.create(dest.DIRECTORY_TYPE, 0755);
do_check_true(dest2.exists());
let source2 = getUpdatesRootDir();
source2.append("updates");
if (source2.exists()) {
source2.remove(true);
}
ret = symlink(dest2.path, source2.path);
do_check_eq(ret, 0);
do_check_true(source2.exists());
// Cleanup the symlinks when the test is finished.
do_register_cleanup(function() {
let ret = unlink(source.path);
do_check_false(source.exists());
let ret = unlink(source2.path);
do_check_false(source2.exists());
});
// Now, make sure that getUpdatesRootDir returns the application bundle
// directory, to make the various stuff in the test framework to work
// correctly.
getUpdatesRootDir = getAppDir;
}
function run_test() {
if (!shouldRunServiceTest()) {
return;
}
do_test_pending();
do_register_cleanup(end_test);
removeUpdateDirsAndFiles();
symlinkUpdateFilesIntoBundleDirectory();
if (IS_WIN) {
adjustPathsOnWindows();
}
if (!gAppBinPath) {
do_throw("Main application binary not found... expected: " +
APP_BIN_NAME + APP_BIN_SUFFIX);
return;
}
// Don't attempt to show a prompt when the update is finished.
Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, true);
let channel = Services.prefs.getCharPref(PREF_APP_UPDATE_CHANNEL);
let patches = getLocalPatchString(null, null, null, null, null, "true",
STATE_PENDING);
let updates = getLocalUpdateString(patches, null, null, null, null, null,
null, null, null, null, null, null,
null, "true", channel);
writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
// Read the application.ini and use its application version
let processDir = getAppDir();
let file = processDir.clone();
file.append("application.ini");
let ini = AUS_Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
getService(AUS_Ci.nsIINIParserFactory).
createINIParser(file);
let version = ini.getString("App", "Version");
writeVersionFile(version);
writeStatusFile(STATE_PENDING_SVC);
// This is the directory where the update files will be located
let updateTestDir = getUpdateTestDir();
try {
removeDirRecursive(updateTestDir);
}
catch (e) {
logTestInfo("unable to remove directory - path: " + updateTestDir.path +
", exception: " + e);
}
let updatesPatchDir = getUpdatesDir();
updatesPatchDir.append("0");
let mar = do_get_file("data/simple.mar");
mar.copyTo(updatesPatchDir, FILE_UPDATE_ARCHIVE);
reloadUpdateManagerData();
gActiveUpdate = gUpdateManager.activeUpdate;
do_check_true(!!gActiveUpdate);
setEnvironment();
let updateSettingsIni = processDir.clone();
updateSettingsIni.append(UPDATE_SETTINGS_INI_FILE);
writeFile(updateSettingsIni, UPDATE_SETTINGS_CONTENTS);
// Initiate a background update.
AUS_Cc["@mozilla.org/updates/update-processor;1"].
createInstance(AUS_Ci.nsIUpdateProcessor).
processUpdate(gActiveUpdate);
resetEnvironment();
checkUpdateApplied();
}
function switchApp() {
let launchBin = getLaunchBin();
let args = getProcessArgs();
logTestInfo("launching " + launchBin.path + " " + args.join(" "));
// Lock the installation directory
const LPCWSTR = ctypes.jschar.ptr;
const DWORD = ctypes.uint32_t;
const LPVOID = ctypes.voidptr_t;
const GENERIC_READ = 0x80000000;
const FILE_SHARE_READ = 1;
const FILE_SHARE_WRITE = 2;
const OPEN_EXISTING = 3;
const FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
const INVALID_HANDLE_VALUE = LPVOID(0xffffffff);
let kernel32 = ctypes.open("kernel32");
let CreateFile = kernel32.declare("CreateFileW", ctypes.default_abi,
LPVOID, LPCWSTR, DWORD, DWORD,
LPVOID, DWORD, DWORD, LPVOID);
logTestInfo(gWindowsBinDir.path);
let handle = CreateFile(gWindowsBinDir.path, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE, LPVOID(0),
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, LPVOID(0));
do_check_neq(handle.toString(), INVALID_HANDLE_VALUE.toString());
kernel32.close();
gProcess = AUS_Cc["@mozilla.org/process/util;1"].
createInstance(AUS_Ci.nsIProcess);
gProcess.init(launchBin);
gAppTimer = AUS_Cc["@mozilla.org/timer;1"].createInstance(AUS_Ci.nsITimer);
gAppTimer.initWithCallback(gTimerCallback, APP_TIMER_TIMEOUT,
AUS_Ci.nsITimer.TYPE_ONE_SHOT);
setEnvironment();
gProcess.runAsync(args, args.length, gProcessObserver);
resetEnvironment();
}
function end_test() {
if (gProcess.isRunning) {
logTestInfo("attempt to kill process");
gProcess.kill();
}
if (gAppTimer) {
logTestInfo("cancelling timer");
gAppTimer.cancel();
gAppTimer = null;
}
resetEnvironment();
// Remove the files added by the update.
let updateTestDir = getUpdateTestDir();
try {
logTestInfo("removing update test directory " + updateTestDir.path);
removeDirRecursive(updateTestDir);
}
catch (e) {
logTestInfo("unable to remove directory - path: " + updateTestDir.path +
", exception: " + e);
}
if (IS_UNIX) {
// This will delete the launch script if it exists.
getLaunchScript();
if (IS_MACOSX) {
// This will delete the version script and version file if they exist.
getVersionScriptAndFile();
}
}
cleanUp();
}
/**
* The observer for the call to nsIProcess:runAsync.
*/
let gProcessObserver = {
observe: function PO_observe(subject, topic, data) {
logTestInfo("topic " + topic + ", process exitValue " + gProcess.exitValue);
if (gAppTimer) {
gAppTimer.cancel();
gAppTimer = null;
}
if (topic != "process-finished" || gProcess.exitValue != 0) {
do_throw("Failed to launch application");
}
do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateFinished);
},
QueryInterface: XPCOMUtils.generateQI([AUS_Ci.nsIObserver])
};
/**
* The timer callback to kill the process if it takes too long.
*/
let gTimerCallback = {
notify: function TC_notify(aTimer) {
gAppTimer = null;
if (gProcess.isRunning) {
gProcess.kill();
}
do_throw("launch application timer expired");
},
QueryInterface: XPCOMUtils.generateQI([AUS_Ci.nsITimerCallback])
};
function shouldAdjustPathsOnMac() {
// When running xpcshell tests locally, xpcshell and firefox-bin do not live
// in the same directory.
let dir = getCurrentProcessDir();
return (IS_MACOSX && dir.leafName != "MacOS");
}
/**
* This function copies the entire process directory over to a new one which we
* can write to, so that we can test under Windows which holds locks on opened
* files.
*/
function adjustPathsOnWindows() {
// We copy the entire GRE directory into another location so that xpcshell
// running doesn't prevent the updater from moving stuff around.
let tmpDir = do_get_profile();
tmpDir.append("ExecutableDir.tmp");
tmpDir.createUnique(tmpDir.DIRECTORY_TYPE, 0755);
let procDir = getCurrentProcessDir();
procDir.copyTo(tmpDir, "bin");
let newDir = tmpDir.clone();
newDir.append("bin");
gWindowsBinDir = newDir;
logTestInfo("Using this new bin directory: " + gWindowsBinDir.path);
// Note that this directory will be deleted as part of the xpcshell teardown,
// so we don't need to remove it explicitly.
// We need to make NS_GRE_DIR point to the new bindir, since
// nsUpdateProcessor::ProcessUpdate uses NS_GRE_DIR to construct the
// destination path name which would be passed to updater.exe.
let dirProvider = {
getFile: function DP_getFile(prop, persistent) {
persistent.value = true;
if (prop == NS_GRE_DIR)
return getAppDir();
return null;
},
QueryInterface: function(iid) {
if (iid.equals(AUS_Ci.nsIDirectoryServiceProvider) ||
iid.equals(AUS_Ci.nsISupports))
return this;
throw AUS_Cr.NS_ERROR_NO_INTERFACE;
}
};
let ds = Services.dirsvc.QueryInterface(AUS_Ci.nsIDirectoryService);
ds.QueryInterface(AUS_Ci.nsIProperties).undefine(NS_GRE_DIR);
ds.registerProvider(dirProvider);
do_register_cleanup(function() {
ds.unregisterProvider(dirProvider);
});
}
/**
* Gets the directory where the update adds / removes the files contained in the
* update.
*
* @return nsIFile for the directory where the update adds / removes the files
* contained in the update mar.
*/
function getUpdateTestDir() {
let updateTestDir = getAppDir();
if (IS_MACOSX) {
updateTestDir = updateTestDir.parent.parent;
}
updateTestDir.append("update_test");
return updateTestDir;
}
/**
* Checks if the update has finished being applied in the background.
*/
function checkUpdateApplied() {
// Don't proceed until the update has been applied.
if (gUpdateManager.activeUpdate.state != STATE_APPLIED_PLATFORM) {
do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateApplied);
return;
}
let updatedDir = getAppDir();
if (IS_MACOSX) {
updatedDir = updatedDir.parent.parent;
}
updatedDir.append(UPDATED_DIR_SUFFIX.replace("/", ""));
logTestInfo("testing " + updatedDir.path + " should exist");
do_check_true(updatedDir.exists());
let log = getUpdatesDir();
log.append(FILE_LAST_LOG);
if (!log.exists()) {
do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateApplied);
return;
}
// Don't proceed until the update status is no longer pending or applying.
let status = readStatusFile();
do_check_eq(status, STATE_APPLIED_PLATFORM);
// On Windows, make sure not to use the maintenance service for switching
// the app.
if (IS_WIN) {
writeStatusFile(STATE_APPLIED);
status = readStatusFile();
do_check_eq(status, STATE_APPLIED);
}
// Log the contents of the update.log so it is simpler to diagnose a test
// failure.
let contents = readFile(log);
logTestInfo("contents of " + log.path + ":\n" +
contents.replace(/\r\n/g, "\n"));
let updateTestDir = getUpdateTestDir();
logTestInfo("testing " + updateTestDir.path + " shouldn't exist");
do_check_false(updateTestDir.exists());
updateTestDir = updatedDir.clone();
updateTestDir.append("update_test");
let file = updateTestDir.clone();
file.append("UpdateTestRemoveFile");
logTestInfo("testing " + file.path + " shouldn't exist");
do_check_false(file.exists());
file = updateTestDir.clone();
file.append("UpdateTestAddFile");
logTestInfo("testing " + file.path + " should exist");
do_check_true(file.exists());
do_check_eq(readFileBytes(file), "UpdateTestAddFile\n");
file = updateTestDir.clone();
file.append("removed-files");
logTestInfo("testing " + file.path + " should exist");
do_check_true(file.exists());
do_check_eq(readFileBytes(file), "update_test/UpdateTestRemoveFile\n");
let updatesDir = getUpdatesDir();
log = updatesDir.clone();
log.append("0");
log.append(FILE_UPDATE_LOG);
logTestInfo("testing " + log.path + " shouldn't exist");
do_check_false(log.exists());
log = updatesDir.clone();
log.append(FILE_LAST_LOG);
logTestInfo("testing " + log.path + " should exist");
do_check_true(log.exists());
log = updatesDir.clone();
log.append(FILE_BACKUP_LOG);
logTestInfo("testing " + log.path + " shouldn't exist");
do_check_false(log.exists());
updatesDir = updatedDir.clone();
if (IS_MACOSX) {
updatesDir.append("Contents");
updatesDir.append("MacOS");
}
updatesDir.append("updates");
log = updatesDir.clone();
log.append("0");
log.append(FILE_UPDATE_LOG);
logTestInfo("testing " + log.path + " shouldn't exist");
do_check_false(log.exists());
updatesDir.append("0");
logTestInfo("testing " + updatesDir.path + " shouldn't exist");
do_check_false(updatesDir.exists());
// Now, switch the updated version of the app
do_timeout(CHECK_TIMEOUT_MILLI, switchApp);
}
/**
* Checks if the update has finished and if it has finished performs checks for
* the test.
*/
function checkUpdateFinished() {
// Don't proceed until the update status is no longer applied.
try {
let status = readStatusFile();
if (status != STATE_SUCCEEDED) {
do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateFinished);
return;
}
} catch (e) {
// Ignore exceptions if the status file is not found
}
try {
// This will delete the app console log file if it exists.
getAppConsoleLogPath();
} catch (e) {
if (e.result == Components.results.NS_ERROR_FILE_IS_LOCKED) {
// This might happen on Windows in case the callback application has not
// finished its job yet. So, we'll wait some more.
do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateFinished);
return;
} else {
do_throw("getAppConsoleLogPath threw: " + e);
}
}
// At this point we need to see if the application was switched successfully.
let updatedDir = getAppDir();
if (IS_MACOSX) {
updatedDir = updatedDir.parent.parent;
}
updatedDir.append(UPDATED_DIR_SUFFIX.replace("/", ""));
logTestInfo("testing " + updatedDir.path + " shouldn't exist");
do_check_false(updatedDir.exists());
let updateTestDir = getUpdateTestDir();
let file = updateTestDir.clone();
file.append("UpdateTestRemoveFile");
logTestInfo("testing " + file.path + " shouldn't exist");
do_check_false(file.exists());
file = updateTestDir.clone();
file.append("UpdateTestAddFile");
logTestInfo("testing " + file.path + " should exist");
do_check_true(file.exists());
do_check_eq(readFileBytes(file), "UpdateTestAddFile\n");
file = updateTestDir.clone();
file.append("removed-files");
logTestInfo("testing " + file.path + " should exist");
do_check_true(file.exists());
do_check_eq(readFileBytes(file), "update_test/UpdateTestRemoveFile\n");
let updatesDir = getUpdatesDir();
log = updatesDir.clone();
log.append("0");
log.append(FILE_UPDATE_LOG);
if (IS_WIN) {
// On Windows, this log file is written to the AppData directory, and will
// therefore exist.
logTestInfo("testing " + log.path + " should exist");
do_check_true(log.exists());
} else {
logTestInfo("testing " + log.path + " shouldn't exist");
do_check_false(log.exists());
}
log = updatesDir.clone();
log.append(FILE_LAST_LOG);
logTestInfo("testing " + log.path + " should exist");
do_check_true(log.exists());
log = updatesDir.clone();
log.append(FILE_BACKUP_LOG);
logTestInfo("testing " + log.path + " shouldn't exist");
do_check_false(log.exists());
updatesDir.append("0");
if (IS_WIN) {
// On Windows, this log file is written to the AppData directory, and will
// therefore exist.
logTestInfo("testing " + updatesDir.path + " should exist");
do_check_true(updatesDir.exists());
} else {
logTestInfo("testing " + updatesDir.path + " shouldn't exist");
do_check_false(updatesDir.exists());
}
removeCallbackCopy();
}

View File

@ -41,3 +41,4 @@ tail =
[test_0200_app_launch_apply_update_svc.js]
[test_0201_app_launch_apply_update_svc.js]
[test_0202_app_launch_apply_update_dirlocked_svc.js]
[test_0203_app_launch_apply_update_svc.js]

View File

@ -2067,6 +2067,7 @@ UpdateThreadFunc(void *param)
ensure_remove_recursive(stageDir);
WriteStatusText(sUsingService ? "pending-service" : "pending");
putenv("MOZ_PROCESS_UPDATES="); // We need to use -process-updates again in the tests
reportRealResults = false; // pretend success
}
}