mirror of
https://github.com/ManchildProductions/UXP-Fixed.git
synced 2026-06-11 22:18:59 +00:00
4048 lines
140 KiB
JavaScript
4048 lines
140 KiB
JavaScript
/* 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/. */
|
|
|
|
/**
|
|
* Test log warnings that happen before the test has started
|
|
* "Couldn't get the user appdata directory. Crash events may not be produced."
|
|
* in nsExceptionHandler.cpp (possibly bug 619104)
|
|
*
|
|
* Test log warnings that happen after the test has finished
|
|
* "OOPDeinit() without successful OOPInit()" in nsExceptionHandler.cpp
|
|
* (bug 619104)
|
|
* "XPCOM objects created/destroyed from static ctor/dtor" in nsTraceRefcnt.cpp
|
|
* (possibly bug 457479)
|
|
*
|
|
* Other warnings printed to the test logs
|
|
* "site security information will not be persisted" in
|
|
* nsSiteSecurityService.cpp and the error in nsSystemInfo.cpp preceding this
|
|
* error are due to not having a profile when running some of the xpcshell
|
|
* tests. Since most xpcshell tests also log these errors these tests don't
|
|
* call do_get_profile unless necessary for the test.
|
|
* The "This method is lossy. Use GetCanonicalPath !" warning on Windows in
|
|
* nsLocalFileWin.cpp is from the call to GetNSSProfilePath in
|
|
* nsNSSComponent.cpp due to it using GetNativeCanonicalPath.
|
|
* "!mMainThread" in nsThreadManager.cpp are due to using timers and it might be
|
|
* possible to fix some or all of these in the test itself.
|
|
* "NS_FAILED(rv)" in nsThreadUtils.cpp are due to using timers and it might be
|
|
* possible to fix some or all of these in the test itself.
|
|
*/
|
|
|
|
'use strict';
|
|
/* eslint-disable no-undef */
|
|
|
|
const { classes: Cc, interfaces: Ci, manager: Cm, results: Cr,
|
|
utils: Cu } = Components;
|
|
|
|
/* global INSTALL_LOCALE, MOZ_APP_NAME, BIN_SUFFIX, MOZ_APP_VENDOR */
|
|
/* global MOZ_APP_BASENAME, APP_BIN_SUFFIX, APP_INFO_NAME, APP_INFO_VENDOR */
|
|
/* global IS_WIN, IS_MACOSX, IS_UNIX, MOZ_VERIFY_MAR_SIGNATURE */
|
|
/* global IS_AUTHENTICODE_CHECK_ENABLED */
|
|
load("../data/xpcshellConstantsPP.js");
|
|
|
|
function getLogSuffix() {
|
|
if (IS_WIN) {
|
|
return "_win";
|
|
}
|
|
if (IS_MACOSX) {
|
|
return "_mac";
|
|
}
|
|
return "_linux";
|
|
}
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm", this);
|
|
Cu.import("resource://gre/modules/ctypes.jsm", this);
|
|
|
|
const DIR_MACOS = IS_MACOSX ? "Contents/MacOS/" : "";
|
|
const DIR_RESOURCES = IS_MACOSX ? "Contents/Resources/" : "";
|
|
const TEST_FILE_SUFFIX = IS_MACOSX ? "_mac" : "";
|
|
const FILE_COMPLETE_MAR = "complete" + TEST_FILE_SUFFIX + ".mar";
|
|
const FILE_PARTIAL_MAR = "partial" + TEST_FILE_SUFFIX + ".mar";
|
|
const FILE_COMPLETE_PRECOMPLETE = "complete_precomplete" + TEST_FILE_SUFFIX;
|
|
const FILE_PARTIAL_PRECOMPLETE = "partial_precomplete" + TEST_FILE_SUFFIX;
|
|
const FILE_COMPLETE_REMOVEDFILES = "complete_removed-files" + TEST_FILE_SUFFIX;
|
|
const FILE_PARTIAL_REMOVEDFILES = "partial_removed-files" + TEST_FILE_SUFFIX;
|
|
const FILE_UPDATE_IN_PROGRESS_LOCK = "updated.update_in_progress.lock";
|
|
const COMPARE_LOG_SUFFIX = getLogSuffix();
|
|
const LOG_COMPLETE_SUCCESS = "complete_log_success" + COMPARE_LOG_SUFFIX;
|
|
const LOG_PARTIAL_SUCCESS = "partial_log_success" + COMPARE_LOG_SUFFIX;
|
|
const LOG_PARTIAL_FAILURE = "partial_log_failure" + COMPARE_LOG_SUFFIX;
|
|
const LOG_REPLACE_SUCCESS = "replace_log_success";
|
|
|
|
const USE_EXECV = IS_UNIX && !IS_MACOSX;
|
|
|
|
const URL_HOST = "http://localhost";
|
|
|
|
const FILE_APP_BIN = MOZ_APP_NAME + APP_BIN_SUFFIX;
|
|
const FILE_COMPLETE_EXE = "complete.exe";
|
|
const FILE_HELPER_BIN = "TestAUSHelper" + BIN_SUFFIX;
|
|
const FILE_MAINTENANCE_SERVICE_BIN = "maintenanceservice.exe";
|
|
const FILE_MAINTENANCE_SERVICE_INSTALLER_BIN = "maintenanceservice_installer.exe";
|
|
const FILE_OLD_VERSION_MAR = "old_version.mar";
|
|
const FILE_PARTIAL_EXE = "partial.exe";
|
|
const FILE_UPDATER_BIN = "updater" + BIN_SUFFIX;
|
|
const FILE_WRONG_CHANNEL_MAR = "wrong_product_channel.mar";
|
|
|
|
const PERFORMING_STAGED_UPDATE = "Performing a staged update";
|
|
const CALL_QUIT = "calling QuitProgressUI";
|
|
const REMOVE_OLD_DIST_DIR = "removing old distribution directory";
|
|
const MOVE_OLD_DIST_DIR = "Moving old distribution directory to new location";
|
|
const ERR_UPDATE_IN_PROGRESS = "Update already in progress! Exiting";
|
|
const ERR_RENAME_FILE = "rename_file: failed to rename file";
|
|
const ERR_ENSURE_COPY = "ensure_copy: failed to copy the file";
|
|
const ERR_UNABLE_OPEN_DEST = "unable to open destination file";
|
|
const ERR_BACKUP_DISCARD = "backup_discard: unable to remove";
|
|
const ERR_MOVE_DESTDIR_7 = "Moving destDir to tmpDir failed, err: 7";
|
|
const ERR_BACKUP_CREATE_7 = "backup_create failed: 7";
|
|
const ERR_LOADSOURCEFILE_FAILED = "LoadSourceFile failed";
|
|
|
|
const LOG_SVC_SUCCESSFUL_LAUNCH = "Process was started... waiting on result.";
|
|
|
|
// Typical end of a message when calling assert
|
|
const MSG_SHOULD_EQUAL = " should equal the expected value";
|
|
const MSG_SHOULD_EXIST = "the file or directory should exist";
|
|
const MSG_SHOULD_NOT_EXIST = "the file or directory should not exist";
|
|
|
|
// All we care about is that the last modified time has changed so that Mac OS
|
|
// X Launch Services invalidates its cache so the test allows up to one minute
|
|
// difference in the last modified time.
|
|
const MAC_MAX_TIME_DIFFERENCE = 60000;
|
|
|
|
// How many of do_execute_soon calls to wait before the test is aborted.
|
|
const MAX_TIMEOUT_RUNS = 20000;
|
|
|
|
// Time in seconds the helper application should sleep before exiting. The
|
|
// helper can also be made to exit by writing |finish| to its input file.
|
|
const HELPER_SLEEP_TIMEOUT = 180;
|
|
|
|
// Maximum number of milliseconds the process that is launched can run before
|
|
// the test will try to kill it.
|
|
const APP_TIMER_TIMEOUT = 120000;
|
|
|
|
// How many of do_timeout calls using FILE_IN_USE_TIMEOUT_MS to wait before the
|
|
// test is aborted.
|
|
const FILE_IN_USE_MAX_TIMEOUT_RUNS = 60;
|
|
const FILE_IN_USE_TIMEOUT_MS = 1000;
|
|
|
|
const PIPE_TO_NULL = IS_WIN ? ">nul" : "> /dev/null 2>&1";
|
|
|
|
const LOG_FUNCTION = do_print;
|
|
|
|
const gHTTPHandlerPath = "updates.xml";
|
|
|
|
// This default value will be overridden when using the http server.
|
|
var gURLData = URL_HOST + "/";
|
|
|
|
var gTestID;
|
|
|
|
var gTestserver;
|
|
|
|
var gRegisteredServiceCleanup;
|
|
|
|
var gCheckFunc;
|
|
var gResponseBody;
|
|
var gResponseStatusCode = 200;
|
|
var gRequestURL;
|
|
var gUpdateCount;
|
|
var gUpdates;
|
|
var gStatusCode;
|
|
var gStatusText;
|
|
var gStatusResult;
|
|
|
|
var gProcess;
|
|
var gAppTimer;
|
|
var gHandle;
|
|
|
|
var gGREDirOrig;
|
|
var gGREBinDirOrig;
|
|
var gAppDirOrig;
|
|
|
|
// Variables are used instead of contants so tests can override these values if
|
|
// necessary.
|
|
var gCallbackBinFile = "callback_app" + BIN_SUFFIX;
|
|
var gCallbackArgs = ["./", "callback.log", "Test Arg 2", "Test Arg 3"];
|
|
var gPostUpdateBinFile = "postup_app" + BIN_SUFFIX;
|
|
var gSvcOriginalLogContents;
|
|
var gUseTestAppDir = true;
|
|
// Some update staging failures can remove the update. This allows tests to
|
|
// specify that the status file and the active update should not be checked
|
|
// after an update is staged.
|
|
var gStagingRemovedUpdate = false;
|
|
|
|
var gTimeoutRuns = 0;
|
|
var gFileInUseTimeoutRuns = 0;
|
|
|
|
// Environment related globals
|
|
var gShouldResetEnv = undefined;
|
|
var gAddedEnvXRENoWindowsCrashDialog = false;
|
|
var gEnvXPCOMDebugBreak;
|
|
var gEnvXPCOMMemLeakLog;
|
|
var gEnvDyldLibraryPath;
|
|
var gEnvLdLibraryPath;
|
|
var gASanOptions;
|
|
|
|
// Set to true to log additional information for debugging. To log additional
|
|
// information for an individual test set DEBUG_AUS_TEST to true in the test's
|
|
// run_test function.
|
|
var DEBUG_AUS_TEST = true;
|
|
|
|
const DATA_URI_SPEC = Services.io.newFileURI(do_get_file("../data", false)).spec;
|
|
/* import-globals-from ../data/shared.js */
|
|
Services.scriptloader.loadSubScript(DATA_URI_SPEC + "shared.js", this);
|
|
|
|
var gTestFiles = [];
|
|
var gTestDirs = [];
|
|
|
|
// Common files for both successful and failed updates.
|
|
var gTestFilesCommon = [
|
|
{
|
|
description: "Should never change",
|
|
fileName: FILE_UPDATE_SETTINGS_INI,
|
|
relPathDir: DIR_RESOURCES,
|
|
originalContents: UPDATE_SETTINGS_CONTENTS,
|
|
compareContents: UPDATE_SETTINGS_CONTENTS,
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: 0o767,
|
|
comparePerms: 0o767
|
|
}, {
|
|
description: "Should never change",
|
|
fileName: "channel-prefs.js",
|
|
relPathDir: DIR_RESOURCES + "defaults/pref/",
|
|
originalContents: "ShouldNotBeReplaced\n",
|
|
compareContents: "ShouldNotBeReplaced\n",
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: 0o767,
|
|
comparePerms: 0o767
|
|
}];
|
|
|
|
// Files for a complete successful update. This can be used for a complete
|
|
// failed update by calling setTestFilesAndDirsForFailure.
|
|
var gTestFilesCompleteSuccess = [
|
|
{
|
|
description: "Added by update.manifest (add)",
|
|
fileName: "precomplete",
|
|
relPathDir: DIR_RESOURCES,
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: FILE_PARTIAL_PRECOMPLETE,
|
|
compareFile: FILE_COMPLETE_PRECOMPLETE,
|
|
originalPerms: 0o666,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Added by update.manifest (add)",
|
|
fileName: "searchpluginstext0",
|
|
relPathDir: DIR_RESOURCES + "searchplugins/",
|
|
originalContents: "ToBeReplacedWithFromComplete\n",
|
|
compareContents: "FromComplete\n",
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: 0o775,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Added by update.manifest (add)",
|
|
fileName: "searchpluginspng1.png",
|
|
relPathDir: DIR_RESOURCES + "searchplugins/",
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: null,
|
|
compareFile: "complete.png",
|
|
originalPerms: null,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Added by update.manifest (add)",
|
|
fileName: "searchpluginspng0.png",
|
|
relPathDir: DIR_RESOURCES + "searchplugins/",
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: "partial.png",
|
|
compareFile: "complete.png",
|
|
originalPerms: 0o666,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Added by update.manifest (add)",
|
|
fileName: "removed-files",
|
|
relPathDir: DIR_RESOURCES,
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: FILE_PARTIAL_REMOVEDFILES,
|
|
compareFile: FILE_COMPLETE_REMOVEDFILES,
|
|
originalPerms: 0o666,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Added by update.manifest if the parent directory exists (add-if)",
|
|
fileName: "extensions1text0",
|
|
relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
|
|
originalContents: null,
|
|
compareContents: "FromComplete\n",
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: null,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Added by update.manifest if the parent directory exists (add-if)",
|
|
fileName: "extensions1png1.png",
|
|
relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: "partial.png",
|
|
compareFile: "complete.png",
|
|
originalPerms: 0o666,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Added by update.manifest if the parent directory exists (add-if)",
|
|
fileName: "extensions1png0.png",
|
|
relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: null,
|
|
compareFile: "complete.png",
|
|
originalPerms: null,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Added by update.manifest if the parent directory exists (add-if)",
|
|
fileName: "extensions0text0",
|
|
relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
|
|
originalContents: "ToBeReplacedWithFromComplete\n",
|
|
compareContents: "FromComplete\n",
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: null,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Added by update.manifest if the parent directory exists (add-if)",
|
|
fileName: "extensions0png1.png",
|
|
relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: null,
|
|
compareFile: "complete.png",
|
|
originalPerms: null,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Added by update.manifest if the parent directory exists (add-if)",
|
|
fileName: "extensions0png0.png",
|
|
relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: null,
|
|
compareFile: "complete.png",
|
|
originalPerms: null,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Added by update.manifest (add)",
|
|
fileName: "exe0.exe",
|
|
relPathDir: DIR_MACOS,
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: FILE_HELPER_BIN,
|
|
compareFile: FILE_COMPLETE_EXE,
|
|
originalPerms: 0o777,
|
|
comparePerms: 0o755
|
|
}, {
|
|
description: "Added by update.manifest (add)",
|
|
fileName: "10text0",
|
|
relPathDir: DIR_RESOURCES + "1/10/",
|
|
originalContents: "ToBeReplacedWithFromComplete\n",
|
|
compareContents: "FromComplete\n",
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: 0o767,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Added by update.manifest (add)",
|
|
fileName: "0exe0.exe",
|
|
relPathDir: DIR_RESOURCES + "0/",
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: FILE_HELPER_BIN,
|
|
compareFile: FILE_COMPLETE_EXE,
|
|
originalPerms: 0o777,
|
|
comparePerms: 0o755
|
|
}, {
|
|
description: "Added by update.manifest (add)",
|
|
fileName: "00text1",
|
|
relPathDir: DIR_RESOURCES + "0/00/",
|
|
originalContents: "ToBeReplacedWithFromComplete\n",
|
|
compareContents: "FromComplete\n",
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: 0o677,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Added by update.manifest (add)",
|
|
fileName: "00text0",
|
|
relPathDir: DIR_RESOURCES + "0/00/",
|
|
originalContents: "ToBeReplacedWithFromComplete\n",
|
|
compareContents: "FromComplete\n",
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: 0o775,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Added by update.manifest (add)",
|
|
fileName: "00png0.png",
|
|
relPathDir: DIR_RESOURCES + "0/00/",
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: null,
|
|
compareFile: "complete.png",
|
|
originalPerms: 0o776,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Removed by precomplete (remove)",
|
|
fileName: "20text0",
|
|
relPathDir: DIR_RESOURCES + "2/20/",
|
|
originalContents: "ToBeDeleted\n",
|
|
compareContents: null,
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: null,
|
|
comparePerms: null
|
|
}, {
|
|
description: "Removed by precomplete (remove)",
|
|
fileName: "20png0.png",
|
|
relPathDir: DIR_RESOURCES + "2/20/",
|
|
originalContents: "ToBeDeleted\n",
|
|
compareContents: null,
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: null,
|
|
comparePerms: null
|
|
}];
|
|
|
|
// Concatenate the common files to the end of the array.
|
|
gTestFilesCompleteSuccess = gTestFilesCompleteSuccess.concat(gTestFilesCommon);
|
|
|
|
// Files for a partial successful update. This can be used for a partial failed
|
|
// update by calling setTestFilesAndDirsForFailure.
|
|
var gTestFilesPartialSuccess = [
|
|
{
|
|
description: "Added by update.manifest (add)",
|
|
fileName: "precomplete",
|
|
relPathDir: DIR_RESOURCES,
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: FILE_COMPLETE_PRECOMPLETE,
|
|
compareFile: FILE_PARTIAL_PRECOMPLETE,
|
|
originalPerms: 0o666,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Added by update.manifest (add)",
|
|
fileName: "searchpluginstext0",
|
|
relPathDir: DIR_RESOURCES + "searchplugins/",
|
|
originalContents: "ToBeReplacedWithFromPartial\n",
|
|
compareContents: "FromPartial\n",
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: 0o775,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Patched by update.manifest if the file exists (patch-if)",
|
|
fileName: "searchpluginspng1.png",
|
|
relPathDir: DIR_RESOURCES + "searchplugins/",
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: "complete.png",
|
|
compareFile: "partial.png",
|
|
originalPerms: 0o666,
|
|
comparePerms: 0o666
|
|
}, {
|
|
description: "Patched by update.manifest if the file exists (patch-if)",
|
|
fileName: "searchpluginspng0.png",
|
|
relPathDir: DIR_RESOURCES + "searchplugins/",
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: "complete.png",
|
|
compareFile: "partial.png",
|
|
originalPerms: 0o666,
|
|
comparePerms: 0o666
|
|
}, {
|
|
description: "Added by update.manifest if the parent directory exists (add-if)",
|
|
fileName: "extensions1text0",
|
|
relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
|
|
originalContents: null,
|
|
compareContents: "FromPartial\n",
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: null,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Patched by update.manifest if the parent directory exists (patch-if)",
|
|
fileName: "extensions1png1.png",
|
|
relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: "complete.png",
|
|
compareFile: "partial.png",
|
|
originalPerms: 0o666,
|
|
comparePerms: 0o666
|
|
}, {
|
|
description: "Patched by update.manifest if the parent directory exists (patch-if)",
|
|
fileName: "extensions1png0.png",
|
|
relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: "complete.png",
|
|
compareFile: "partial.png",
|
|
originalPerms: 0o666,
|
|
comparePerms: 0o666
|
|
}, {
|
|
description: "Added by update.manifest if the parent directory exists (add-if)",
|
|
fileName: "extensions0text0",
|
|
relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
|
|
originalContents: "ToBeReplacedWithFromPartial\n",
|
|
compareContents: "FromPartial\n",
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: 0o644,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Patched by update.manifest if the parent directory exists (patch-if)",
|
|
fileName: "extensions0png1.png",
|
|
relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: "complete.png",
|
|
compareFile: "partial.png",
|
|
originalPerms: 0o644,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Patched by update.manifest if the parent directory exists (patch-if)",
|
|
fileName: "extensions0png0.png",
|
|
relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: "complete.png",
|
|
compareFile: "partial.png",
|
|
originalPerms: 0o644,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Patched by update.manifest (patch)",
|
|
fileName: "exe0.exe",
|
|
relPathDir: DIR_MACOS,
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: FILE_COMPLETE_EXE,
|
|
compareFile: FILE_PARTIAL_EXE,
|
|
originalPerms: 0o755,
|
|
comparePerms: 0o755
|
|
}, {
|
|
description: "Patched by update.manifest (patch)",
|
|
fileName: "0exe0.exe",
|
|
relPathDir: DIR_RESOURCES + "0/",
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: FILE_COMPLETE_EXE,
|
|
compareFile: FILE_PARTIAL_EXE,
|
|
originalPerms: 0o755,
|
|
comparePerms: 0o755
|
|
}, {
|
|
description: "Added by update.manifest (add)",
|
|
fileName: "00text0",
|
|
relPathDir: DIR_RESOURCES + "0/00/",
|
|
originalContents: "ToBeReplacedWithFromPartial\n",
|
|
compareContents: "FromPartial\n",
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: 0o644,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Patched by update.manifest (patch)",
|
|
fileName: "00png0.png",
|
|
relPathDir: DIR_RESOURCES + "0/00/",
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: "complete.png",
|
|
compareFile: "partial.png",
|
|
originalPerms: 0o666,
|
|
comparePerms: 0o666
|
|
}, {
|
|
description: "Added by update.manifest (add)",
|
|
fileName: "20text0",
|
|
relPathDir: DIR_RESOURCES + "2/20/",
|
|
originalContents: null,
|
|
compareContents: "FromPartial\n",
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: null,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Added by update.manifest (add)",
|
|
fileName: "20png0.png",
|
|
relPathDir: DIR_RESOURCES + "2/20/",
|
|
originalContents: null,
|
|
compareContents: null,
|
|
originalFile: null,
|
|
compareFile: "partial.png",
|
|
originalPerms: null,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Added by update.manifest (add)",
|
|
fileName: "00text2",
|
|
relPathDir: DIR_RESOURCES + "0/00/",
|
|
originalContents: null,
|
|
compareContents: "FromPartial\n",
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: null,
|
|
comparePerms: 0o644
|
|
}, {
|
|
description: "Removed by update.manifest (remove)",
|
|
fileName: "10text0",
|
|
relPathDir: DIR_RESOURCES + "1/10/",
|
|
originalContents: "ToBeDeleted\n",
|
|
compareContents: null,
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: null,
|
|
comparePerms: null
|
|
}, {
|
|
description: "Removed by update.manifest (remove)",
|
|
fileName: "00text1",
|
|
relPathDir: DIR_RESOURCES + "0/00/",
|
|
originalContents: "ToBeDeleted\n",
|
|
compareContents: null,
|
|
originalFile: null,
|
|
compareFile: null,
|
|
originalPerms: null,
|
|
comparePerms: null
|
|
}];
|
|
|
|
// Concatenate the common files to the end of the array.
|
|
gTestFilesPartialSuccess = gTestFilesPartialSuccess.concat(gTestFilesCommon);
|
|
|
|
var gTestDirsCommon = [
|
|
{
|
|
relPathDir: DIR_RESOURCES + "3/",
|
|
dirRemoved: false,
|
|
files: ["3text0", "3text1"],
|
|
filesRemoved: true
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "4/",
|
|
dirRemoved: true,
|
|
files: ["4text0", "4text1"],
|
|
filesRemoved: true
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "5/",
|
|
dirRemoved: true,
|
|
files: ["5test.exe", "5text0", "5text1"],
|
|
filesRemoved: true
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "6/",
|
|
dirRemoved: true
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "7/",
|
|
dirRemoved: true,
|
|
files: ["7text0", "7text1"],
|
|
subDirs: ["70/", "71/"],
|
|
subDirFiles: ["7xtest.exe", "7xtext0", "7xtext1"]
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "8/",
|
|
dirRemoved: false
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "8/80/",
|
|
dirRemoved: true
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "8/81/",
|
|
dirRemoved: false,
|
|
files: ["81text0", "81text1"]
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "8/82/",
|
|
dirRemoved: false,
|
|
subDirs: ["820/", "821/"]
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "8/83/",
|
|
dirRemoved: true
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "8/84/",
|
|
dirRemoved: true
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "8/85/",
|
|
dirRemoved: true
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "8/86/",
|
|
dirRemoved: true,
|
|
files: ["86text0", "86text1"]
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "8/87/",
|
|
dirRemoved: true,
|
|
subDirs: ["870/", "871/"],
|
|
subDirFiles: ["87xtext0", "87xtext1"]
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "8/88/",
|
|
dirRemoved: true
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "8/89/",
|
|
dirRemoved: true
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "9/90/",
|
|
dirRemoved: true
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "9/91/",
|
|
dirRemoved: false,
|
|
files: ["91text0", "91text1"]
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "9/92/",
|
|
dirRemoved: false,
|
|
subDirs: ["920/", "921/"]
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "9/93/",
|
|
dirRemoved: true
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "9/94/",
|
|
dirRemoved: true
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "9/95/",
|
|
dirRemoved: true
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "9/96/",
|
|
dirRemoved: true,
|
|
files: ["96text0", "96text1"]
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "9/97/",
|
|
dirRemoved: true,
|
|
subDirs: ["970/", "971/"],
|
|
subDirFiles: ["97xtext0", "97xtext1"]
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "9/98/",
|
|
dirRemoved: true
|
|
}, {
|
|
relPathDir: DIR_RESOURCES + "9/99/",
|
|
dirRemoved: true
|
|
}];
|
|
|
|
// Directories for a complete successful update. This array can be used for a
|
|
// complete failed update by calling setTestFilesAndDirsForFailure.
|
|
var gTestDirsCompleteSuccess = [
|
|
{
|
|
description: "Removed by precomplete (rmdir)",
|
|
relPathDir: DIR_RESOURCES + "2/20/",
|
|
dirRemoved: true
|
|
}, {
|
|
description: "Removed by precomplete (rmdir)",
|
|
relPathDir: DIR_RESOURCES + "2/",
|
|
dirRemoved: true
|
|
}];
|
|
|
|
// Concatenate the common files to the beginning of the array.
|
|
gTestDirsCompleteSuccess = gTestDirsCommon.concat(gTestDirsCompleteSuccess);
|
|
|
|
// Directories for a partial successful update. This array can be used for a
|
|
// partial failed update by calling setTestFilesAndDirsForFailure.
|
|
var gTestDirsPartialSuccess = [
|
|
{
|
|
description: "Removed by update.manifest (rmdir)",
|
|
relPathDir: DIR_RESOURCES + "1/10/",
|
|
dirRemoved: true
|
|
}, {
|
|
description: "Removed by update.manifest (rmdir)",
|
|
relPathDir: DIR_RESOURCES + "1/",
|
|
dirRemoved: true
|
|
}];
|
|
|
|
// Concatenate the common files to the beginning of the array.
|
|
gTestDirsPartialSuccess = gTestDirsCommon.concat(gTestDirsPartialSuccess);
|
|
|
|
// This makes it possible to run most tests on xulrunner where the update
|
|
// channel default preference is not set.
|
|
if (MOZ_APP_NAME == "xulrunner") {
|
|
try {
|
|
gDefaultPrefBranch.getCharPref(PREF_APP_UPDATE_CHANNEL);
|
|
} catch (e) {
|
|
setUpdateChannel("test_channel");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function for setting up the test environment.
|
|
*/
|
|
function setupTestCommon() {
|
|
debugDump("start - general test setup");
|
|
|
|
Assert.strictEqual(gTestID, undefined,
|
|
"gTestID should be 'undefined' (setupTestCommon should " +
|
|
"only be called once)");
|
|
|
|
let caller = Components.stack.caller;
|
|
gTestID = caller.filename.toString().split("/").pop().split(".")[0];
|
|
|
|
// Tests that don't work with XULRunner.
|
|
const XUL_RUNNER_INCOMPATIBLE = ["marAppApplyUpdateAppBinInUseStageSuccess_win",
|
|
"marAppApplyUpdateStageSuccess",
|
|
"marAppApplyUpdateSuccess",
|
|
"marAppApplyUpdateAppBinInUseStageSuccessSvc_win",
|
|
"marAppApplyUpdateStageSuccessSvc",
|
|
"marAppApplyUpdateSuccessSvc"];
|
|
// Replace with Array.prototype.includes when it has stabilized.
|
|
if (MOZ_APP_NAME == "xulrunner" &&
|
|
XUL_RUNNER_INCOMPATIBLE.indexOf(gTestID) != -1) {
|
|
logTestInfo("Unable to run this test on xulrunner");
|
|
return false;
|
|
}
|
|
|
|
if (IS_SERVICE_TEST && !shouldRunServiceTest()) {
|
|
return false;
|
|
}
|
|
|
|
do_test_pending();
|
|
|
|
setDefaultPrefs();
|
|
|
|
// Don't attempt to show a prompt when an update finishes.
|
|
Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, true);
|
|
|
|
gGREDirOrig = getGREDir();
|
|
gGREBinDirOrig = getGREBinDir();
|
|
gAppDirOrig = getAppBaseDir();
|
|
|
|
let applyDir = getApplyDirFile(null, true).parent;
|
|
|
|
// Try to remove the directory used to apply updates and the updates directory
|
|
// on platforms other than Windows. Since the test hasn't ran yet and the
|
|
// directory shouldn't exist finished this is non-fatal for the test.
|
|
if (applyDir.exists()) {
|
|
debugDump("attempting to remove directory. Path: " + applyDir.path);
|
|
try {
|
|
removeDirRecursive(applyDir);
|
|
} catch (e) {
|
|
logTestInfo("non-fatal error removing directory. Path: " +
|
|
applyDir.path + ", Exception: " + e);
|
|
// When the application doesn't exit properly it can cause the test to
|
|
// fail again on the second run with an NS_ERROR_FILE_ACCESS_DENIED error
|
|
// along with no useful information in the test log. To prevent this use
|
|
// a different directory for the test when it isn't possible to remove the
|
|
// existing test directory (bug 1294196).
|
|
gTestID += "_new";
|
|
logTestInfo("using a new directory for the test by changing gTestID " +
|
|
"since there is an existing test directory that can't be " +
|
|
"removed, gTestID: " + gTestID);
|
|
}
|
|
}
|
|
|
|
if (IS_WIN) {
|
|
Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED,
|
|
IS_SERVICE_TEST ? true : false);
|
|
}
|
|
|
|
// adjustGeneralPaths registers a cleanup function that calls end_test when
|
|
// it is defined as a function.
|
|
adjustGeneralPaths();
|
|
// Logged once here instead of in the mock directory provider to lessen test
|
|
// log spam.
|
|
debugDump("Updates Directory (UpdRootD) Path: " + getMockUpdRootD().path);
|
|
|
|
// This prevents a warning about not being able to find the greprefs.js file
|
|
// from being logged.
|
|
let grePrefsFile = getGREDir();
|
|
if (!grePrefsFile.exists()) {
|
|
grePrefsFile.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
|
|
}
|
|
grePrefsFile.append("greprefs.js");
|
|
if (!grePrefsFile.exists()) {
|
|
grePrefsFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
|
|
}
|
|
|
|
// Remove the updates directory on Windows and Mac OS X which is located
|
|
// outside of the application directory after the call to adjustGeneralPaths
|
|
// has set it up. Since the test hasn't ran yet and the directory shouldn't
|
|
// exist this is non-fatal for the test.
|
|
if (IS_WIN || IS_MACOSX) {
|
|
let updatesDir = getMockUpdRootD();
|
|
if (updatesDir.exists()) {
|
|
debugDump("attempting to remove directory. Path: " + updatesDir.path);
|
|
try {
|
|
removeDirRecursive(updatesDir);
|
|
} catch (e) {
|
|
logTestInfo("non-fatal error removing directory. Path: " +
|
|
updatesDir.path + ", Exception: " + e);
|
|
}
|
|
}
|
|
}
|
|
|
|
debugDump("finish - general test setup");
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Nulls out the most commonly used global vars used by tests to prevent leaks
|
|
* as needed and attempts to restore the system to its original state.
|
|
*/
|
|
function cleanupTestCommon() {
|
|
debugDump("start - general test cleanup");
|
|
|
|
// Force the update manager to reload the update data to prevent it from
|
|
// writing the old data to the files that have just been removed.
|
|
reloadUpdateManagerData();
|
|
|
|
if (gChannel) {
|
|
gPrefRoot.removeObserver(PREF_APP_UPDATE_CHANNEL, observer);
|
|
}
|
|
|
|
// Call app update's observe method passing xpcom-shutdown to test that the
|
|
// shutdown of app update runs without throwing or leaking. The observer
|
|
// method is used directly instead of calling notifyObservers so components
|
|
// outside of the scope of this test don't assert and thereby cause app update
|
|
// tests to fail.
|
|
gAUS.observe(null, "xpcom-shutdown", "");
|
|
|
|
gTestserver = null;
|
|
|
|
if (IS_UNIX) {
|
|
// This will delete the launch script if it exists.
|
|
getLaunchScript();
|
|
}
|
|
|
|
if (IS_WIN && MOZ_APP_BASENAME) {
|
|
let appDir = getApplyDirFile(null, true);
|
|
let vendor = MOZ_APP_VENDOR ? MOZ_APP_VENDOR : "Mozilla";
|
|
const REG_PATH = "SOFTWARE\\" + vendor + "\\" + MOZ_APP_BASENAME +
|
|
"\\TaskBarIDs";
|
|
let key = Cc["@mozilla.org/windows-registry-key;1"].
|
|
createInstance(Ci.nsIWindowsRegKey);
|
|
try {
|
|
key.open(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, REG_PATH,
|
|
Ci.nsIWindowsRegKey.ACCESS_ALL);
|
|
if (key.hasValue(appDir.path)) {
|
|
key.removeValue(appDir.path);
|
|
}
|
|
} catch (e) {
|
|
}
|
|
try {
|
|
key.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, REG_PATH,
|
|
Ci.nsIWindowsRegKey.ACCESS_ALL);
|
|
if (key.hasValue(appDir.path)) {
|
|
key.removeValue(appDir.path);
|
|
}
|
|
} catch (e) {
|
|
}
|
|
}
|
|
|
|
// The updates directory is located outside of the application directory and
|
|
// needs to be removed on Windows and Mac OS X.
|
|
if (IS_WIN || IS_MACOSX) {
|
|
let updatesDir = getMockUpdRootD();
|
|
// Try to remove the directory used to apply updates. Since the test has
|
|
// already finished this is non-fatal for the test.
|
|
if (updatesDir.exists()) {
|
|
debugDump("attempting to remove directory. Path: " + updatesDir.path);
|
|
try {
|
|
removeDirRecursive(updatesDir);
|
|
} catch (e) {
|
|
logTestInfo("non-fatal error removing directory. Path: " +
|
|
updatesDir.path + ", Exception: " + e);
|
|
}
|
|
if (IS_MACOSX) {
|
|
let updatesRootDir = gUpdatesRootDir.clone();
|
|
while (updatesRootDir.path != updatesDir.path) {
|
|
if (updatesDir.exists()) {
|
|
debugDump("attempting to remove directory. Path: " +
|
|
updatesDir.path);
|
|
try {
|
|
// Try to remove the directory without the recursive flag set
|
|
// since the top level directory has already had its contents
|
|
// removed and the parent directory might still be used by a
|
|
// different test.
|
|
updatesDir.remove(false);
|
|
} catch (e) {
|
|
logTestInfo("non-fatal error removing directory. Path: " +
|
|
updatesDir.path + ", Exception: " + e);
|
|
if (e == Cr.NS_ERROR_FILE_DIR_NOT_EMPTY) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
updatesDir = updatesDir.parent;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let applyDir = getApplyDirFile(null, true).parent;
|
|
|
|
// Try to remove the directory used to apply updates. Since the test has
|
|
// already finished this is non-fatal for the test.
|
|
if (applyDir.exists()) {
|
|
debugDump("attempting to remove directory. Path: " + applyDir.path);
|
|
try {
|
|
removeDirRecursive(applyDir);
|
|
} catch (e) {
|
|
logTestInfo("non-fatal error removing directory. Path: " +
|
|
applyDir.path + ", Exception: " + e);
|
|
}
|
|
}
|
|
|
|
resetEnvironment();
|
|
|
|
debugDump("finish - general test cleanup");
|
|
}
|
|
|
|
/**
|
|
* Helper function that calls do_test_finished that tracks whether a parallel
|
|
* run of a test passed when it runs synchronously so the log output can be
|
|
* inspected.
|
|
*/
|
|
function doTestFinish() {
|
|
if (DEBUG_AUS_TEST) {
|
|
// This prevents do_print errors from being printed by the xpcshell test
|
|
// harness due to nsUpdateService.js logging to the console when the
|
|
// app.update.log preference is true.
|
|
Services.prefs.setBoolPref(PREF_APP_UPDATE_LOG, false);
|
|
gAUS.observe(null, "nsPref:changed", PREF_APP_UPDATE_LOG);
|
|
}
|
|
do_execute_soon(do_test_finished);
|
|
}
|
|
|
|
/**
|
|
* Sets the most commonly used preferences used by tests
|
|
*/
|
|
function setDefaultPrefs() {
|
|
Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, true);
|
|
if (DEBUG_AUS_TEST) {
|
|
// Enable Update logging
|
|
Services.prefs.setBoolPref(PREF_APP_UPDATE_LOG, true);
|
|
} else {
|
|
// Some apps set this preference to true by default
|
|
Services.prefs.setBoolPref(PREF_APP_UPDATE_LOG, false);
|
|
}
|
|
// In case telemetry is enabled for xpcshell tests.
|
|
Services.prefs.setBoolPref(PREF_TOOLKIT_TELEMETRY_ENABLED, false);
|
|
}
|
|
|
|
/**
|
|
* Helper function for updater binary tests that sets the appropriate values
|
|
* to check for update failures.
|
|
*/
|
|
function setTestFilesAndDirsForFailure() {
|
|
gTestFiles.forEach(function STFADFF_Files(aTestFile) {
|
|
aTestFile.compareContents = aTestFile.originalContents;
|
|
aTestFile.compareFile = aTestFile.originalFile;
|
|
aTestFile.comparePerms = aTestFile.originalPerms;
|
|
});
|
|
|
|
gTestDirs.forEach(function STFADFF_Dirs(aTestDir) {
|
|
aTestDir.dirRemoved = false;
|
|
if (aTestDir.filesRemoved) {
|
|
aTestDir.filesRemoved = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper function for updater binary tests that prevents the distribution
|
|
* directory files from being created.
|
|
*/
|
|
function preventDistributionFiles() {
|
|
gTestFiles = gTestFiles.filter(function(aTestFile) {
|
|
return aTestFile.relPathDir.indexOf("distribution/") == -1;
|
|
});
|
|
|
|
gTestDirs = gTestDirs.filter(function(aTestDir) {
|
|
return aTestDir.relPathDir.indexOf("distribution/") == -1;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* On Mac OS X this sets the last modified time for the app bundle directory to
|
|
* a date in the past to test that the last modified time is updated when an
|
|
* update has been successfully applied (bug 600098).
|
|
*/
|
|
function setAppBundleModTime() {
|
|
if (!IS_MACOSX) {
|
|
return;
|
|
}
|
|
let now = Date.now();
|
|
let yesterday = now - (1000 * 60 * 60 * 24);
|
|
let applyToDir = getApplyDirFile();
|
|
applyToDir.lastModifiedTime = yesterday;
|
|
}
|
|
|
|
/**
|
|
* On Mac OS X this checks that the last modified time for the app bundle
|
|
* directory has been updated when an update has been successfully applied
|
|
* (bug 600098).
|
|
*/
|
|
function checkAppBundleModTime() {
|
|
if (!IS_MACOSX) {
|
|
return;
|
|
}
|
|
let now = Date.now();
|
|
let applyToDir = getApplyDirFile();
|
|
let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
|
|
Assert.ok(timeDiff < MAC_MAX_TIME_DIFFERENCE,
|
|
"the last modified time on the apply to directory should " +
|
|
"change after a successful update");
|
|
}
|
|
|
|
/**
|
|
* On Mac OS X and Windows this checks if the post update '.running' file exists
|
|
* to determine if the post update binary was launched.
|
|
*
|
|
* @param aShouldExist
|
|
* Whether the post update '.running' file should exist.
|
|
*/
|
|
function checkPostUpdateRunningFile(aShouldExist) {
|
|
if (!IS_WIN && !IS_MACOSX) {
|
|
return;
|
|
}
|
|
let postUpdateRunningFile = getPostUpdateFile(".running");
|
|
if (aShouldExist) {
|
|
Assert.ok(postUpdateRunningFile.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(postUpdateRunningFile.path));
|
|
} else {
|
|
Assert.ok(!postUpdateRunningFile.exists(),
|
|
MSG_SHOULD_NOT_EXIST + getMsgPath(postUpdateRunningFile.path));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initializes the most commonly used settings and creates an instance of the
|
|
* update service stub.
|
|
*/
|
|
function standardInit() {
|
|
createAppInfo("xpcshell@tests.mozilla.org", APP_INFO_NAME, "1.0", "2.0");
|
|
// Initialize the update service stub component
|
|
initUpdateServiceStub();
|
|
}
|
|
|
|
/**
|
|
* Helper function for getting the application version from the application.ini
|
|
* file. This will look in both the GRE and the application directories for the
|
|
* application.ini file.
|
|
*
|
|
* @return The version string from the application.ini file.
|
|
*/
|
|
function getAppVersion() {
|
|
// Read the application.ini and use its application version.
|
|
let iniFile = gGREDirOrig.clone();
|
|
iniFile.append(FILE_APPLICATION_INI);
|
|
if (!iniFile.exists()) {
|
|
iniFile = gGREBinDirOrig.clone();
|
|
iniFile.append(FILE_APPLICATION_INI);
|
|
}
|
|
Assert.ok(iniFile.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(iniFile.path));
|
|
let iniParser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
|
|
getService(Ci.nsIINIParserFactory).
|
|
createINIParser(iniFile);
|
|
return iniParser.getString("App", "Version");
|
|
}
|
|
|
|
/**
|
|
* Helper function for getting the relative path to the directory where the
|
|
* application binary is located (e.g. <test_file_leafname>/dir.app/).
|
|
*
|
|
* Note: The dir.app subdirectory under <test_file_leafname> is needed for
|
|
* platforms other than Mac OS X so the tests can run in parallel due to
|
|
* update staging creating a lock file named moz_update_in_progress.lock in
|
|
* the parent directory of the installation directory.
|
|
*
|
|
* @return The relative path to the directory where application binary is
|
|
* located.
|
|
*/
|
|
function getApplyDirPath() {
|
|
return gTestID + "/dir.app/";
|
|
}
|
|
|
|
/**
|
|
* Helper function for getting the nsIFile for a file in the directory where the
|
|
* update will be applied.
|
|
*
|
|
* The files for the update are located two directories below the apply to
|
|
* directory since Mac OS X sets the last modified time for the root directory
|
|
* to the current time and if the update changes any files in the root directory
|
|
* then it wouldn't be possible to test (bug 600098).
|
|
*
|
|
* @param aRelPath (optional)
|
|
* The relative path to the file or directory to get from the root of
|
|
* the test's directory. If not specified the test's directory will be
|
|
* returned.
|
|
* @param aAllowNonexistent (optional)
|
|
* Whether the file must exist. If false or not specified the file must
|
|
* exist or the function will throw.
|
|
* @return The nsIFile for the file in the directory where the update will be
|
|
* applied.
|
|
* @throws If aAllowNonexistent is not specified or is false and the file or
|
|
* directory does not exist.
|
|
*/
|
|
function getApplyDirFile(aRelPath, aAllowNonexistent) {
|
|
let relpath = getApplyDirPath() + (aRelPath ? aRelPath : "");
|
|
return do_get_file(relpath, aAllowNonexistent);
|
|
}
|
|
|
|
/**
|
|
* Helper function for getting the nsIFile for a file in the directory where the
|
|
* update will be staged.
|
|
*
|
|
* The files for the update are located two directories below the stage
|
|
* directory since Mac OS X sets the last modified time for the root directory
|
|
* to the current time and if the update changes any files in the root directory
|
|
* then it wouldn't be possible to test (bug 600098).
|
|
*
|
|
* @param aRelPath (optional)
|
|
* The relative path to the file or directory to get from the root of
|
|
* the stage directory. If not specified the stage directory will be
|
|
* returned.
|
|
* @param aAllowNonexistent (optional)
|
|
* Whether the file must exist. If false or not specified the file must
|
|
* exist or the function will throw.
|
|
* @return The nsIFile for the file in the directory where the update will be
|
|
* staged.
|
|
* @throws If aAllowNonexistent is not specified or is false and the file or
|
|
* directory does not exist.
|
|
*/
|
|
function getStageDirFile(aRelPath, aAllowNonexistent) {
|
|
if (IS_MACOSX) {
|
|
let file = getMockUpdRootD();
|
|
file.append(DIR_UPDATES);
|
|
file.append(DIR_PATCH);
|
|
file.append(DIR_UPDATED);
|
|
if (aRelPath) {
|
|
let pathParts = aRelPath.split("/");
|
|
for (let i = 0; i < pathParts.length; i++) {
|
|
if (pathParts[i]) {
|
|
file.append(pathParts[i]);
|
|
}
|
|
}
|
|
}
|
|
if (!aAllowNonexistent) {
|
|
Assert.ok(file.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(file.path));
|
|
}
|
|
return file;
|
|
}
|
|
|
|
let relpath = getApplyDirPath() + DIR_UPDATED + "/" + (aRelPath ? aRelPath : "");
|
|
return do_get_file(relpath, aAllowNonexistent);
|
|
}
|
|
|
|
/**
|
|
* Helper function for getting the relative path to the directory where the
|
|
* test data files are located.
|
|
*
|
|
* @return The relative path to the directory where the test data files are
|
|
* located.
|
|
*/
|
|
function getTestDirPath() {
|
|
return "../data/";
|
|
}
|
|
|
|
/**
|
|
* Helper function for getting the nsIFile for a file in the test data
|
|
* directory.
|
|
*
|
|
* @param aRelPath (optional)
|
|
* The relative path to the file or directory to get from the root of
|
|
* the test's data directory. If not specified the test's data
|
|
* directory will be returned.
|
|
* @param aAllowNonExists (optional)
|
|
* Whether or not to throw an error if the path exists.
|
|
* If not specified, then false is used.
|
|
* @return The nsIFile for the file in the test data directory.
|
|
* @throws If the file or directory does not exist.
|
|
*/
|
|
function getTestDirFile(aRelPath, aAllowNonExists) {
|
|
let relpath = getTestDirPath() + (aRelPath ? aRelPath : "");
|
|
return do_get_file(relpath, !!aAllowNonExists);
|
|
}
|
|
|
|
/**
|
|
* Helper function for getting the nsIFile for the maintenance service
|
|
* directory on Windows.
|
|
*
|
|
* @return The nsIFile for the maintenance service directory.
|
|
*/
|
|
function getMaintSvcDir() {
|
|
if (!IS_WIN) {
|
|
do_throw("Windows only function called by a different platform!");
|
|
}
|
|
|
|
const CSIDL_PROGRAM_FILES = 0x26;
|
|
const CSIDL_PROGRAM_FILESX86 = 0x2A;
|
|
// This will return an empty string on our Win XP build systems.
|
|
let maintSvcDir = getSpecialFolderDir(CSIDL_PROGRAM_FILESX86);
|
|
if (maintSvcDir) {
|
|
maintSvcDir.append("Mozilla Maintenance Service");
|
|
debugDump("using CSIDL_PROGRAM_FILESX86 - maintenance service install " +
|
|
"directory path: " + maintSvcDir.path);
|
|
}
|
|
if (!maintSvcDir || !maintSvcDir.exists()) {
|
|
maintSvcDir = getSpecialFolderDir(CSIDL_PROGRAM_FILES);
|
|
if (maintSvcDir) {
|
|
maintSvcDir.append("Mozilla Maintenance Service");
|
|
debugDump("using CSIDL_PROGRAM_FILES - maintenance service install " +
|
|
"directory path: " + maintSvcDir.path);
|
|
}
|
|
}
|
|
if (!maintSvcDir) {
|
|
do_throw("Unable to find the maintenance service install directory");
|
|
}
|
|
|
|
return maintSvcDir;
|
|
}
|
|
|
|
/**
|
|
* Get the nsILocalFile for a Windows special folder determined by the CSIDL
|
|
* passed.
|
|
*
|
|
* @param aCSIDL
|
|
* The CSIDL for the Windows special folder.
|
|
* @return The nsILocalFile for the Windows special folder.
|
|
* @throws If called from a platform other than Windows.
|
|
*/
|
|
function getSpecialFolderDir(aCSIDL) {
|
|
if (!IS_WIN) {
|
|
do_throw("Windows only function called by a different platform!");
|
|
}
|
|
|
|
let lib = ctypes.open("shell32");
|
|
let SHGetSpecialFolderPath = lib.declare("SHGetSpecialFolderPathW",
|
|
ctypes.winapi_abi,
|
|
ctypes.bool, /* bool(return) */
|
|
ctypes.int32_t, /* HWND hwndOwner */
|
|
ctypes.char16_t.ptr, /* LPTSTR lpszPath */
|
|
ctypes.int32_t, /* int csidl */
|
|
ctypes.bool /* BOOL fCreate */);
|
|
|
|
let aryPath = ctypes.char16_t.array()(260);
|
|
let rv = SHGetSpecialFolderPath(0, aryPath, aCSIDL, false);
|
|
lib.close();
|
|
|
|
let path = aryPath.readString(); // Convert the c-string to js-string
|
|
if (!path) {
|
|
return null;
|
|
}
|
|
debugDump("SHGetSpecialFolderPath returned path: " + path);
|
|
let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
|
dir.initWithPath(path);
|
|
return dir;
|
|
}
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "gInstallDirPathHash", function test_gIDPH() {
|
|
if (!IS_WIN) {
|
|
do_throw("Windows only function called by a different platform!");
|
|
}
|
|
|
|
// Figure out where we should check for a cached hash value
|
|
if (!MOZ_APP_BASENAME) {
|
|
return null;
|
|
}
|
|
|
|
let vendor = MOZ_APP_VENDOR ? MOZ_APP_VENDOR : "Mozilla";
|
|
let appDir = getApplyDirFile(null, true);
|
|
|
|
const REG_PATH = "SOFTWARE\\" + vendor + "\\" + MOZ_APP_BASENAME +
|
|
"\\TaskBarIDs";
|
|
let regKey = Cc["@mozilla.org/windows-registry-key;1"].
|
|
createInstance(Ci.nsIWindowsRegKey);
|
|
try {
|
|
regKey.open(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, REG_PATH,
|
|
Ci.nsIWindowsRegKey.ACCESS_ALL);
|
|
regKey.writeStringValue(appDir.path, gTestID);
|
|
return gTestID;
|
|
} catch (e) {
|
|
}
|
|
|
|
try {
|
|
regKey.create(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, REG_PATH,
|
|
Ci.nsIWindowsRegKey.ACCESS_ALL);
|
|
regKey.writeStringValue(appDir.path, gTestID);
|
|
return gTestID;
|
|
} catch (e) {
|
|
logTestInfo("failed to create registry key. Registry Path: " + REG_PATH +
|
|
", Key Name: " + appDir.path + ", Key Value: " + gTestID +
|
|
", Exception " + e);
|
|
}
|
|
return null;
|
|
});
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "gLocalAppDataDir", function test_gLADD() {
|
|
if (!IS_WIN) {
|
|
do_throw("Windows only function called by a different platform!");
|
|
}
|
|
|
|
const CSIDL_LOCAL_APPDATA = 0x1c;
|
|
return getSpecialFolderDir(CSIDL_LOCAL_APPDATA);
|
|
});
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "gProgFilesDir", function test_gPFD() {
|
|
if (!IS_WIN) {
|
|
do_throw("Windows only function called by a different platform!");
|
|
}
|
|
|
|
const CSIDL_PROGRAM_FILES = 0x26;
|
|
return getSpecialFolderDir(CSIDL_PROGRAM_FILES);
|
|
});
|
|
|
|
/**
|
|
* Helper function for getting the update root directory used by the tests. This
|
|
* returns the same directory as returned by nsXREDirProvider::GetUpdateRootDir
|
|
* in nsXREDirProvider.cpp so an application will be able to find the update
|
|
* when running a test that launches the application.
|
|
*/
|
|
function getMockUpdRootD() {
|
|
if (IS_WIN) {
|
|
return getMockUpdRootDWin();
|
|
}
|
|
|
|
if (IS_MACOSX) {
|
|
return getMockUpdRootDMac();
|
|
}
|
|
|
|
return getApplyDirFile(DIR_MACOS, true);
|
|
}
|
|
|
|
/**
|
|
* Helper function for getting the update root directory used by the tests. This
|
|
* returns the same directory as returned by nsXREDirProvider::GetUpdateRootDir
|
|
* in nsXREDirProvider.cpp so an application will be able to find the update
|
|
* when running a test that launches the application.
|
|
*/
|
|
function getMockUpdRootDWin() {
|
|
if (!IS_WIN) {
|
|
do_throw("Windows only function called by a different platform!");
|
|
}
|
|
|
|
let localAppDataDir = gLocalAppDataDir.clone();
|
|
let progFilesDir = gProgFilesDir.clone();
|
|
let appDir = Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsIFile).parent;
|
|
|
|
let appDirPath = appDir.path;
|
|
let relPathUpdates = "";
|
|
if (gInstallDirPathHash && (MOZ_APP_VENDOR || MOZ_APP_BASENAME)) {
|
|
relPathUpdates += (MOZ_APP_VENDOR ? MOZ_APP_VENDOR : MOZ_APP_BASENAME) +
|
|
"\\" + DIR_UPDATES + "\\" + gInstallDirPathHash;
|
|
}
|
|
|
|
if (!relPathUpdates && progFilesDir) {
|
|
if (appDirPath.length > progFilesDir.path.length) {
|
|
if (appDirPath.substr(0, progFilesDir.path.length) == progFilesDir.path) {
|
|
if (MOZ_APP_VENDOR && MOZ_APP_BASENAME) {
|
|
relPathUpdates += MOZ_APP_VENDOR + "\\" + MOZ_APP_BASENAME;
|
|
} else {
|
|
relPathUpdates += MOZ_APP_BASENAME;
|
|
}
|
|
relPathUpdates += appDirPath.substr(progFilesDir.path.length);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!relPathUpdates) {
|
|
if (MOZ_APP_VENDOR && MOZ_APP_BASENAME) {
|
|
relPathUpdates += MOZ_APP_VENDOR + "\\" + MOZ_APP_BASENAME;
|
|
} else {
|
|
relPathUpdates += MOZ_APP_BASENAME;
|
|
}
|
|
relPathUpdates += "\\" + MOZ_APP_NAME;
|
|
}
|
|
|
|
let updatesDir = Cc["@mozilla.org/file/local;1"].
|
|
createInstance(Ci.nsILocalFile);
|
|
updatesDir.initWithPath(localAppDataDir.path + "\\" + relPathUpdates);
|
|
return updatesDir;
|
|
}
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "gUpdatesRootDir", function test_gURD() {
|
|
if (!IS_MACOSX) {
|
|
do_throw("Mac OS X only function called by a different platform!");
|
|
}
|
|
|
|
let dir = Services.dirsvc.get("ULibDir", Ci.nsILocalFile);
|
|
dir.append("Caches");
|
|
if (MOZ_APP_VENDOR || MOZ_APP_BASENAME) {
|
|
dir.append(MOZ_APP_VENDOR ? MOZ_APP_VENDOR : MOZ_APP_BASENAME);
|
|
} else {
|
|
dir.append("Mozilla");
|
|
}
|
|
dir.append(DIR_UPDATES);
|
|
return dir;
|
|
});
|
|
|
|
/**
|
|
* Helper function for getting the update root directory used by the tests. This
|
|
* returns the same directory as returned by nsXREDirProvider::GetUpdateRootDir
|
|
* in nsXREDirProvider.cpp so an application will be able to find the update
|
|
* when running a test that launches the application.
|
|
*/
|
|
function getMockUpdRootDMac() {
|
|
if (!IS_MACOSX) {
|
|
do_throw("Mac OS X only function called by a different platform!");
|
|
}
|
|
|
|
let appDir = Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsIFile).
|
|
parent.parent.parent;
|
|
let appDirPath = appDir.path;
|
|
appDirPath = appDirPath.substr(0, appDirPath.length - 4);
|
|
|
|
let pathUpdates = gUpdatesRootDir.path + appDirPath;
|
|
let updatesDir = Cc["@mozilla.org/file/local;1"].
|
|
createInstance(Ci.nsILocalFile);
|
|
updatesDir.initWithPath(pathUpdates);
|
|
return updatesDir;
|
|
}
|
|
|
|
/**
|
|
* Creates an update in progress lock file in the specified directory on
|
|
* Windows.
|
|
*
|
|
* @param aDir
|
|
* The nsIFile for the directory where the lock file should be created.
|
|
*/
|
|
function createUpdateInProgressLockFile(aDir) {
|
|
if (!IS_WIN) {
|
|
do_throw("Windows only function called by a different platform!");
|
|
}
|
|
|
|
let file = aDir.clone();
|
|
file.append(FILE_UPDATE_IN_PROGRESS_LOCK);
|
|
file.create(file.NORMAL_FILE_TYPE, 0o444);
|
|
file.QueryInterface(Ci.nsILocalFileWin);
|
|
file.fileAttributesWin |= file.WFA_READONLY;
|
|
file.fileAttributesWin &= ~file.WFA_READWRITE;
|
|
Assert.ok(file.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(file.path));
|
|
Assert.ok(!file.isWritable(),
|
|
"the lock file should not be writeable");
|
|
}
|
|
|
|
/**
|
|
* Removes an update in progress lock file in the specified directory on
|
|
* Windows.
|
|
*
|
|
* @param aDir
|
|
* The nsIFile for the directory where the lock file is located.
|
|
*/
|
|
function removeUpdateInProgressLockFile(aDir) {
|
|
if (!IS_WIN) {
|
|
do_throw("Windows only function called by a different platform!");
|
|
}
|
|
|
|
let file = aDir.clone();
|
|
file.append(FILE_UPDATE_IN_PROGRESS_LOCK);
|
|
file.QueryInterface(Ci.nsILocalFileWin);
|
|
file.fileAttributesWin |= file.WFA_READWRITE;
|
|
file.fileAttributesWin &= ~file.WFA_READONLY;
|
|
file.remove(false);
|
|
Assert.ok(!file.exists(),
|
|
MSG_SHOULD_NOT_EXIST + getMsgPath(file.path));
|
|
}
|
|
|
|
/**
|
|
* Gets the test updater from the test data direcory.
|
|
*
|
|
* @return nsIFIle for the test updater.
|
|
*/
|
|
function getTestUpdater() {
|
|
let updater = getTestDirFile("updater.app", true);
|
|
if (!updater.exists()) {
|
|
updater = getTestDirFile(FILE_UPDATER_BIN);
|
|
if (!updater.exists()) {
|
|
do_throw("Unable to find the updater binary!");
|
|
}
|
|
}
|
|
Assert.ok(updater.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(updater.path));
|
|
return updater;
|
|
}
|
|
|
|
/**
|
|
* Copies the test updater to the GRE binary directory and returns the nsIFile
|
|
* for the copied test updater.
|
|
*
|
|
* @return nsIFIle for the copied test updater.
|
|
*/
|
|
function copyTestUpdaterToBinDir() {
|
|
let testUpdater = getTestUpdater();
|
|
let updater = getGREBinDir();
|
|
updater.append(testUpdater.leafName);
|
|
if (!updater.exists()) {
|
|
testUpdater.copyToFollowingLinks(updater.parent, updater.leafName);
|
|
}
|
|
return updater;
|
|
}
|
|
|
|
/**
|
|
* Copies the test updater to the location where it will be launched to apply an
|
|
* update and returns the nsIFile for the copied test updater.
|
|
*
|
|
* @return nsIFIle for the copied test updater.
|
|
*/
|
|
function copyTestUpdaterForRunUsingUpdater() {
|
|
if (IS_WIN) {
|
|
return copyTestUpdaterToBinDir();
|
|
}
|
|
|
|
let testUpdater = getTestUpdater();
|
|
let updater = getUpdatesPatchDir();
|
|
updater.append(testUpdater.leafName);
|
|
if (!updater.exists()) {
|
|
testUpdater.copyToFollowingLinks(updater.parent, updater.leafName);
|
|
}
|
|
|
|
if (IS_MACOSX) {
|
|
updater.append("Contents");
|
|
updater.append("MacOS");
|
|
updater.append("org.mozilla.updater");
|
|
}
|
|
return updater;
|
|
}
|
|
|
|
/**
|
|
* Logs the contents of an update log and for maintenance service tests this
|
|
* will log the contents of the latest maintenanceservice.log.
|
|
*
|
|
* @param aLogLeafName
|
|
* The leaf name of the update log.
|
|
*/
|
|
function logUpdateLog(aLogLeafName) {
|
|
let updateLog = getUpdateLog(aLogLeafName);
|
|
if (updateLog.exists()) {
|
|
// xpcshell tests won't display the entire contents so log each line.
|
|
let updateLogContents = readFileBytes(updateLog).replace(/\r\n/g, "\n");
|
|
updateLogContents = replaceLogPaths(updateLogContents);
|
|
let aryLogContents = updateLogContents.split("\n");
|
|
logTestInfo("contents of " + updateLog.path + ":");
|
|
aryLogContents.forEach(function RU_LC_FE(aLine) {
|
|
logTestInfo(aLine);
|
|
});
|
|
} else {
|
|
logTestInfo("update log doesn't exist, path: " + updateLog.path);
|
|
}
|
|
|
|
if (IS_SERVICE_TEST) {
|
|
let serviceLog = getMaintSvcDir();
|
|
serviceLog.append("logs");
|
|
serviceLog.append("maintenanceservice.log");
|
|
if (serviceLog.exists()) {
|
|
// xpcshell tests won't display the entire contents so log each line.
|
|
let serviceLogContents = readFileBytes(serviceLog).replace(/\r\n/g, "\n");
|
|
serviceLogContents = replaceLogPaths(serviceLogContents);
|
|
let aryLogContents = serviceLogContents.split("\n");
|
|
logTestInfo("contents of " + serviceLog.path + ":");
|
|
aryLogContents.forEach(function RU_LC_FE(aLine) {
|
|
logTestInfo(aLine);
|
|
});
|
|
} else {
|
|
logTestInfo("maintenance service log doesn't exist, path: " +
|
|
serviceLog.path);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the maintenance service log contents.
|
|
*/
|
|
function readServiceLogFile() {
|
|
let file = getMaintSvcDir();
|
|
file.append("logs");
|
|
file.append("maintenanceservice.log");
|
|
return readFile(file);
|
|
}
|
|
|
|
/**
|
|
* Launches the updater binary to apply an update for updater tests.
|
|
*
|
|
* @param aExpectedStatus
|
|
* The expected value of update.status when the test finishes. For
|
|
* service tests passing STATE_PENDING or STATE_APPLIED will change the
|
|
* value to STATE_PENDING_SVC and STATE_APPLIED_SVC respectively.
|
|
* @param aSwitchApp
|
|
* If true the update should switch the application with an updated
|
|
* staged application and if false the update should be applied to the
|
|
* installed application.
|
|
* @param aExpectedExitValue
|
|
* The expected exit value from the updater binary for non-service
|
|
* tests.
|
|
* @param aCheckSvcLog
|
|
* Whether the service log should be checked for service tests.
|
|
* @param aPatchDirPath (optional)
|
|
* When specified the patch directory path to use for invalid argument
|
|
* tests otherwise the normal path will be used.
|
|
* @param aInstallDirPath (optional)
|
|
* When specified the install directory path to use for invalid
|
|
* argument tests otherwise the normal path will be used.
|
|
* @param aApplyToDirPath (optional)
|
|
* When specified the apply to / working directory path to use for
|
|
* invalid argument tests otherwise the normal path will be used.
|
|
* @param aCallbackPath (optional)
|
|
* When specified the callback path to use for invalid argument tests
|
|
* otherwise the normal path will be used.
|
|
*/
|
|
function runUpdate(aExpectedStatus, aSwitchApp, aExpectedExitValue, aCheckSvcLog,
|
|
aPatchDirPath, aInstallDirPath, aApplyToDirPath,
|
|
aCallbackPath) {
|
|
let isInvalidArgTest = !!aPatchDirPath || !!aInstallDirPath ||
|
|
!!aApplyToDirPath || aCallbackPath;
|
|
|
|
let svcOriginalLog;
|
|
if (IS_SERVICE_TEST) {
|
|
copyFileToTestAppDir(FILE_MAINTENANCE_SERVICE_BIN, false);
|
|
copyFileToTestAppDir(FILE_MAINTENANCE_SERVICE_INSTALLER_BIN, false);
|
|
if (aCheckSvcLog) {
|
|
svcOriginalLog = readServiceLogFile();
|
|
}
|
|
}
|
|
|
|
// Copy the updater binary to the directory where it will apply updates.
|
|
let updateBin = copyTestUpdaterForRunUsingUpdater();
|
|
Assert.ok(updateBin.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(updateBin.path));
|
|
|
|
let updatesDirPath = aPatchDirPath || getUpdatesPatchDir().path;
|
|
let installDirPath = aInstallDirPath || getApplyDirFile(null, true).path;
|
|
let applyToDirPath = aApplyToDirPath || getApplyDirFile(null, true).path;
|
|
let stageDirPath = aApplyToDirPath || getStageDirFile(null, true).path;
|
|
|
|
let callbackApp = getApplyDirFile(DIR_RESOURCES + gCallbackBinFile);
|
|
callbackApp.permissions = PERMS_DIRECTORY;
|
|
|
|
setAppBundleModTime();
|
|
|
|
let args = [updatesDirPath, installDirPath];
|
|
if (aSwitchApp) {
|
|
args[2] = stageDirPath;
|
|
args[3] = "0/replace";
|
|
} else {
|
|
args[2] = applyToDirPath;
|
|
args[3] = "0";
|
|
}
|
|
|
|
let launchBin = IS_SERVICE_TEST && isInvalidArgTest ? callbackApp : updateBin;
|
|
|
|
if (!isInvalidArgTest) {
|
|
args = args.concat([callbackApp.parent.path, callbackApp.path]);
|
|
args = args.concat(gCallbackArgs);
|
|
} else if (IS_SERVICE_TEST) {
|
|
args = ["launch-service", updateBin.path].concat(args);
|
|
} else if (aCallbackPath) {
|
|
args = args.concat([callbackApp.parent.path, aCallbackPath]);
|
|
}
|
|
|
|
debugDump("launching the program: " + launchBin.path + " " + args.join(" "));
|
|
|
|
if (aSwitchApp && !isInvalidArgTest) {
|
|
// We want to set the env vars again
|
|
gShouldResetEnv = undefined;
|
|
}
|
|
|
|
setEnvironment();
|
|
|
|
let process = Cc["@mozilla.org/process/util;1"].
|
|
createInstance(Ci.nsIProcess);
|
|
process.init(launchBin);
|
|
process.run(true, args, args.length);
|
|
|
|
resetEnvironment();
|
|
|
|
let status = readStatusFile();
|
|
if ((!IS_SERVICE_TEST && process.exitValue != aExpectedExitValue) ||
|
|
status != aExpectedStatus) {
|
|
if (process.exitValue != aExpectedExitValue) {
|
|
logTestInfo("updater exited with unexpected value! Got: " +
|
|
process.exitValue + ", Expected: " + aExpectedExitValue);
|
|
}
|
|
if (status != aExpectedStatus) {
|
|
logTestInfo("update status is not the expected status! Got: " + status +
|
|
", Expected: " + aExpectedStatus);
|
|
}
|
|
logUpdateLog(FILE_LAST_UPDATE_LOG);
|
|
}
|
|
|
|
if (!IS_SERVICE_TEST) {
|
|
Assert.equal(process.exitValue, aExpectedExitValue,
|
|
"the process exit value" + MSG_SHOULD_EQUAL);
|
|
}
|
|
Assert.equal(status, aExpectedStatus,
|
|
"the update status" + MSG_SHOULD_EQUAL);
|
|
|
|
if (IS_SERVICE_TEST && aCheckSvcLog) {
|
|
let contents = readServiceLogFile();
|
|
Assert.notEqual(contents, svcOriginalLog,
|
|
"the contents of the maintenanceservice.log should not " +
|
|
"be the same as the original contents");
|
|
if (!isInvalidArgTest) {
|
|
Assert.notEqual(contents.indexOf(LOG_SVC_SUCCESSFUL_LAUNCH), -1,
|
|
"the contents of the maintenanceservice.log should " +
|
|
"contain the successful launch string");
|
|
}
|
|
}
|
|
|
|
do_execute_soon(runUpdateFinished);
|
|
}
|
|
|
|
/**
|
|
* Launches the helper binary synchronously with the specified arguments for
|
|
* updater tests.
|
|
*
|
|
* @param aArgs
|
|
* The arguments to pass to the helper binary.
|
|
* @return the process exit value returned by the helper binary.
|
|
*/
|
|
function runTestHelperSync(aArgs) {
|
|
let helperBin = getTestDirFile(FILE_HELPER_BIN);
|
|
let process = Cc["@mozilla.org/process/util;1"].
|
|
createInstance(Ci.nsIProcess);
|
|
process.init(helperBin);
|
|
debugDump("Running " + helperBin.path + " " + aArgs.join(" "));
|
|
process.run(true, aArgs, aArgs.length);
|
|
return process.exitValue;
|
|
}
|
|
|
|
/**
|
|
* Creates a symlink for updater tests.
|
|
*/
|
|
function createSymlink() {
|
|
let args = ["setup-symlink", "moz-foo", "moz-bar", "target",
|
|
getApplyDirFile().path + "/" + DIR_RESOURCES + "link"];
|
|
let exitValue = runTestHelperSync(args);
|
|
Assert.equal(exitValue, 0,
|
|
"the helper process exit value should be 0");
|
|
getApplyDirFile(DIR_RESOURCES + "link", false).permissions = 0o666;
|
|
args = ["setup-symlink", "moz-foo2", "moz-bar2", "target2",
|
|
getApplyDirFile().path + "/" + DIR_RESOURCES + "link2", "change-perm"];
|
|
exitValue = runTestHelperSync(args);
|
|
Assert.equal(exitValue, 0,
|
|
"the helper process exit value should be 0");
|
|
}
|
|
|
|
/**
|
|
* Removes a symlink for updater tests.
|
|
*/
|
|
function removeSymlink() {
|
|
let args = ["remove-symlink", "moz-foo", "moz-bar", "target",
|
|
getApplyDirFile().path + "/" + DIR_RESOURCES + "link"];
|
|
let exitValue = runTestHelperSync(args);
|
|
Assert.equal(exitValue, 0,
|
|
"the helper process exit value should be 0");
|
|
args = ["remove-symlink", "moz-foo2", "moz-bar2", "target2",
|
|
getApplyDirFile().path + "/" + DIR_RESOURCES + "link2"];
|
|
exitValue = runTestHelperSync(args);
|
|
Assert.equal(exitValue, 0,
|
|
"the helper process exit value should be 0");
|
|
}
|
|
|
|
/**
|
|
* Checks a symlink for updater tests.
|
|
*/
|
|
function checkSymlink() {
|
|
let args = ["check-symlink",
|
|
getApplyDirFile().path + "/" + DIR_RESOURCES + "link"];
|
|
let exitValue = runTestHelperSync(args);
|
|
Assert.equal(exitValue, 0,
|
|
"the helper process exit value should be 0");
|
|
}
|
|
|
|
/**
|
|
* Sets the active update and related information for updater tests.
|
|
*/
|
|
function setupActiveUpdate() {
|
|
let state = IS_SERVICE_TEST ? STATE_PENDING_SVC : STATE_PENDING;
|
|
let channel = gDefaultPrefBranch.getCharPref(PREF_APP_UPDATE_CHANNEL);
|
|
let patches = getLocalPatchString(null, null, null, null, null, "true",
|
|
state);
|
|
let updates = getLocalUpdateString(patches, null, null, null, null, null,
|
|
null, null, null, null, "true", channel);
|
|
writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
|
|
writeVersionFile(DEFAULT_UPDATE_VERSION);
|
|
writeStatusFile(state);
|
|
reloadUpdateManagerData();
|
|
Assert.ok(!!gUpdateManager.activeUpdate,
|
|
"the active update should be defined");
|
|
}
|
|
|
|
/**
|
|
* Gets the specified update log.
|
|
*
|
|
* @param aLogLeafName
|
|
* The leaf name of the log to get.
|
|
* @return nsIFile for the update log.
|
|
*/
|
|
function getUpdateLog(aLogLeafName) {
|
|
let updateLog = getUpdatesDir();
|
|
if (aLogLeafName == FILE_UPDATE_LOG) {
|
|
updateLog.append(DIR_PATCH);
|
|
}
|
|
updateLog.append(aLogLeafName);
|
|
return updateLog;
|
|
}
|
|
|
|
/**
|
|
* The update-staged observer for the call to nsIUpdateProcessor:processUpdate.
|
|
*/
|
|
const gUpdateStagedObserver = {
|
|
observe: function(aSubject, aTopic, aData) {
|
|
debugDump("observe called with topic: " + aTopic + ", data: " + aData);
|
|
if (aTopic == "update-staged") {
|
|
Services.obs.removeObserver(gUpdateStagedObserver, "update-staged");
|
|
// The environment is reset after the update-staged observer topic because
|
|
// processUpdate in nsIUpdateProcessor uses a new thread and clearing the
|
|
// environment immediately after calling processUpdate can clear the
|
|
// environment before the updater is launched.
|
|
resetEnvironment();
|
|
// Use do_execute_soon to prevent any failures from propagating to the
|
|
// update service.
|
|
do_execute_soon(checkUpdateStagedState.bind(null, aData));
|
|
}
|
|
},
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
|
|
};
|
|
|
|
/**
|
|
* Stages an update using nsIUpdateProcessor:processUpdate for updater tests.
|
|
*
|
|
* @param aCheckSvcLog
|
|
* Whether the service log should be checked for service tests.
|
|
*/
|
|
function stageUpdate(aCheckSvcLog) {
|
|
debugDump("start - attempting to stage update");
|
|
|
|
if (IS_SERVICE_TEST && aCheckSvcLog) {
|
|
gSvcOriginalLogContents = readServiceLogFile();
|
|
}
|
|
|
|
Services.obs.addObserver(gUpdateStagedObserver, "update-staged", false);
|
|
|
|
setAppBundleModTime();
|
|
setEnvironment();
|
|
// Stage the update.
|
|
Cc["@mozilla.org/updates/update-processor;1"].
|
|
createInstance(Ci.nsIUpdateProcessor).
|
|
processUpdate(gUpdateManager.activeUpdate);
|
|
|
|
// The environment is not reset here because processUpdate in
|
|
// nsIUpdateProcessor uses a new thread and clearing the environment
|
|
// immediately after calling processUpdate can clear the environment before
|
|
// the updater is launched. Instead it is reset after the update-staged
|
|
// observer topic.
|
|
|
|
debugDump("finish - attempting to stage update");
|
|
}
|
|
|
|
/**
|
|
* Checks that the update state is correct as well as the expected files are
|
|
* present after staging and update for updater tests and then calls
|
|
* stageUpdateFinished.
|
|
*
|
|
* @param aUpdateState
|
|
* The update state received by the observer notification.
|
|
*/
|
|
function checkUpdateStagedState(aUpdateState) {
|
|
if (IS_WIN) {
|
|
if (IS_SERVICE_TEST) {
|
|
waitForServiceStop(false);
|
|
} else {
|
|
let updater = getApplyDirFile(FILE_UPDATER_BIN, true);
|
|
if (isFileInUse(updater)) {
|
|
do_timeout(FILE_IN_USE_TIMEOUT_MS,
|
|
checkUpdateStagedState.bind(null, aUpdateState));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
Assert.equal(aUpdateState, STATE_AFTER_STAGE,
|
|
"the notified state" + MSG_SHOULD_EQUAL);
|
|
|
|
if (!gStagingRemovedUpdate) {
|
|
Assert.equal(readStatusState(), STATE_AFTER_STAGE,
|
|
"the status file state" + MSG_SHOULD_EQUAL);
|
|
|
|
Assert.equal(gUpdateManager.activeUpdate.state, STATE_AFTER_STAGE,
|
|
"the update state" + MSG_SHOULD_EQUAL);
|
|
}
|
|
|
|
Assert.equal(gUpdateManager.updateCount, 1,
|
|
"the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
|
|
Assert.equal(gUpdateManager.getUpdateAt(0).state, STATE_AFTER_STAGE,
|
|
"the update state" + MSG_SHOULD_EQUAL);
|
|
|
|
let log = getUpdateLog(FILE_LAST_UPDATE_LOG);
|
|
Assert.ok(log.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(log.path));
|
|
|
|
log = getUpdateLog(FILE_UPDATE_LOG);
|
|
Assert.ok(!log.exists(),
|
|
MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
|
|
|
|
log = getUpdateLog(FILE_BACKUP_UPDATE_LOG);
|
|
Assert.ok(!log.exists(),
|
|
MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
|
|
|
|
let stageDir = getStageDirFile(null, true);
|
|
if (STATE_AFTER_STAGE == STATE_APPLIED ||
|
|
STATE_AFTER_STAGE == STATE_APPLIED_SVC) {
|
|
Assert.ok(stageDir.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(stageDir.path));
|
|
} else {
|
|
Assert.ok(!stageDir.exists(),
|
|
MSG_SHOULD_NOT_EXIST + getMsgPath(stageDir.path));
|
|
}
|
|
|
|
if (IS_SERVICE_TEST && gSvcOriginalLogContents !== undefined) {
|
|
let contents = readServiceLogFile();
|
|
Assert.notEqual(contents, gSvcOriginalLogContents,
|
|
"the contents of the maintenanceservice.log should not " +
|
|
"be the same as the original contents");
|
|
Assert.notEqual(contents.indexOf(LOG_SVC_SUCCESSFUL_LAUNCH), -1,
|
|
"the contents of the maintenanceservice.log should " +
|
|
"contain the successful launch string");
|
|
}
|
|
|
|
do_execute_soon(stageUpdateFinished);
|
|
}
|
|
|
|
/**
|
|
* Helper function to check whether the maintenance service updater tests should
|
|
* run. See bug 711660 for more details.
|
|
*
|
|
* @return true if the test should run and false if it shouldn't.
|
|
*/
|
|
function shouldRunServiceTest() {
|
|
if (!IS_WIN) {
|
|
do_throw("Windows only function called by a different platform!");
|
|
}
|
|
|
|
let binDir = getGREBinDir();
|
|
let updaterBin = binDir.clone();
|
|
updaterBin.append(FILE_UPDATER_BIN);
|
|
Assert.ok(updaterBin.exists(),
|
|
MSG_SHOULD_EXIST + ", leafName: " + updaterBin.leafName);
|
|
|
|
let updaterBinPath = updaterBin.path;
|
|
if (/ /.test(updaterBinPath)) {
|
|
updaterBinPath = '"' + updaterBinPath + '"';
|
|
}
|
|
|
|
let isBinSigned = isBinarySigned(updaterBinPath);
|
|
|
|
const REG_PATH = "SOFTWARE\\Mozilla\\MaintenanceService\\" +
|
|
"3932ecacee736d366d6436db0f55bce4";
|
|
let key = Cc["@mozilla.org/windows-registry-key;1"].
|
|
createInstance(Ci.nsIWindowsRegKey);
|
|
try {
|
|
key.open(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, REG_PATH,
|
|
Ci.nsIWindowsRegKey.ACCESS_READ | key.WOW64_64);
|
|
} catch (e) {
|
|
// The build system could sign the files and not have the test registry key
|
|
// in which case we should fail the test if the updater binary is signed so
|
|
// the build system can be fixed by adding the registry key.
|
|
if (IS_AUTHENTICODE_CHECK_ENABLED) {
|
|
Assert.ok(!isBinSigned,
|
|
"the updater.exe binary should not be signed when the test " +
|
|
"registry key doesn't exist (if it is, build system " +
|
|
"configuration bug?)");
|
|
}
|
|
|
|
logTestInfo("this test can only run on the buildbot build system at this " +
|
|
"time");
|
|
return false;
|
|
}
|
|
|
|
// Check to make sure the service is installed
|
|
let args = ["wait-for-service-stop", "MozillaMaintenance", "10"];
|
|
let exitValue = runTestHelperSync(args);
|
|
Assert.notEqual(exitValue, 0xEE, "the maintenance service should be " +
|
|
"installed (if not, build system configuration bug?)");
|
|
|
|
if (IS_AUTHENTICODE_CHECK_ENABLED) {
|
|
// The test registry key exists and IS_AUTHENTICODE_CHECK_ENABLED is true
|
|
// so the binaries should be signed. To run the test locally
|
|
// DISABLE_UPDATER_AUTHENTICODE_CHECK can be defined.
|
|
Assert.ok(isBinSigned,
|
|
"the updater.exe binary should be signed (if not, build system " +
|
|
"configuration bug?)");
|
|
}
|
|
|
|
// In case the machine is running an old maintenance service or if it
|
|
// is not installed, and permissions exist to install it. Then install
|
|
// the newer bin that we have since all of the other checks passed.
|
|
return attemptServiceInstall();
|
|
}
|
|
|
|
/**
|
|
* Helper function to check whether the a binary is signed.
|
|
*
|
|
* @param aBinPath
|
|
* The path to the file to check if it is signed.
|
|
* @return true if the file is signed and false if it isn't.
|
|
*/
|
|
function isBinarySigned(aBinPath) {
|
|
let args = ["check-signature", aBinPath];
|
|
let exitValue = runTestHelperSync(args);
|
|
if (exitValue != 0) {
|
|
logTestInfo("binary is not signed. " + FILE_HELPER_BIN + " returned " +
|
|
exitValue + " for file " + aBinPath);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Helper function for asynchronously setting up the application files required
|
|
* to launch the application for the updater tests by either copying or creating
|
|
* symlinks for the files. This is needed for Windows debug builds which can
|
|
* lock a file that is being copied so that the tests can run in parallel. After
|
|
* the files have been copied the setupUpdaterTestFinished function will be
|
|
* called.
|
|
*/
|
|
function setupAppFilesAsync() {
|
|
gTimeoutRuns++;
|
|
try {
|
|
setupAppFiles();
|
|
} catch (e) {
|
|
if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
|
|
do_throw("Exceeded MAX_TIMEOUT_RUNS while trying to setup application " +
|
|
"files! Exception: " + e);
|
|
}
|
|
do_execute_soon(setupAppFilesAsync);
|
|
return;
|
|
}
|
|
|
|
do_execute_soon(setupUpdaterTestFinished);
|
|
}
|
|
|
|
/**
|
|
* Helper function for setting up the application files required to launch the
|
|
* application for the updater tests by either copying or creating symlinks to
|
|
* the files.
|
|
*/
|
|
function setupAppFiles() {
|
|
debugDump("start - copying or creating symlinks to application files " +
|
|
"for the test");
|
|
|
|
let destDir = getApplyDirFile(null, true);
|
|
if (!destDir.exists()) {
|
|
try {
|
|
destDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
|
|
} catch (e) {
|
|
logTestInfo("unable to create directory! Path: " + destDir.path +
|
|
", Exception: " + e);
|
|
do_throw(e);
|
|
}
|
|
}
|
|
|
|
// Required files for the application or the test that aren't listed in the
|
|
// dependentlibs.list file.
|
|
let appFiles = [{relPath: FILE_APP_BIN,
|
|
inGreDir: false},
|
|
{relPath: FILE_APPLICATION_INI,
|
|
inGreDir: true},
|
|
{relPath: "dependentlibs.list",
|
|
inGreDir: true}];
|
|
|
|
// On Linux the updater.png must also be copied
|
|
if (IS_UNIX && !IS_MACOSX) {
|
|
appFiles.push({relPath: "icons/updater.png",
|
|
inGreDir: true});
|
|
}
|
|
|
|
// Read the dependent libs file leafnames from the dependentlibs.list file
|
|
// into the array.
|
|
let deplibsFile = gGREDirOrig.clone();
|
|
deplibsFile.append("dependentlibs.list");
|
|
let fis = Cc["@mozilla.org/network/file-input-stream;1"].
|
|
createInstance(Ci.nsIFileInputStream);
|
|
fis.init(deplibsFile, 0x01, 0o444, Ci.nsIFileInputStream.CLOSE_ON_EOF);
|
|
fis.QueryInterface(Ci.nsILineInputStream);
|
|
|
|
let hasMore;
|
|
let line = {};
|
|
do {
|
|
hasMore = fis.readLine(line);
|
|
appFiles.push({relPath: line.value,
|
|
inGreDir: false});
|
|
} while (hasMore);
|
|
|
|
fis.close();
|
|
|
|
appFiles.forEach(function CMAF_FLN_FE(aAppFile) {
|
|
copyFileToTestAppDir(aAppFile.relPath, aAppFile.inGreDir);
|
|
});
|
|
|
|
copyTestUpdaterToBinDir();
|
|
|
|
debugDump("finish - copying or creating symlinks to application files " +
|
|
"for the test");
|
|
}
|
|
|
|
/**
|
|
* Copies the specified files from the dist/bin directory into the test's
|
|
* application directory.
|
|
*
|
|
* @param aFileRelPath
|
|
* The relative path to the source and the destination of the file to
|
|
* copy.
|
|
* @param aInGreDir
|
|
* Whether the file is located in the GRE directory which is
|
|
* <bundle>/Contents/Resources on Mac OS X and is the installation
|
|
* directory on all other platforms. If false the file must be in the
|
|
* GRE Binary directory which is <bundle>/Contents/MacOS on Mac OS X
|
|
* and is the installation directory on on all other platforms.
|
|
*/
|
|
function copyFileToTestAppDir(aFileRelPath, aInGreDir) {
|
|
// gGREDirOrig and gGREBinDirOrig must always be cloned when changing its
|
|
// properties
|
|
let srcFile = aInGreDir ? gGREDirOrig.clone() : gGREBinDirOrig.clone();
|
|
let destFile = aInGreDir ? getGREDir() : getGREBinDir();
|
|
let fileRelPath = aFileRelPath;
|
|
let pathParts = fileRelPath.split("/");
|
|
for (let i = 0; i < pathParts.length; i++) {
|
|
if (pathParts[i]) {
|
|
srcFile.append(pathParts[i]);
|
|
destFile.append(pathParts[i]);
|
|
}
|
|
}
|
|
|
|
if (IS_MACOSX && !srcFile.exists()) {
|
|
debugDump("unable to copy file since it doesn't exist! Checking if " +
|
|
fileRelPath + ".app exists. Path: " + srcFile.path);
|
|
// gGREDirOrig and gGREBinDirOrig must always be cloned when changing its
|
|
// properties
|
|
srcFile = aInGreDir ? gGREDirOrig.clone() : gGREBinDirOrig.clone();
|
|
destFile = aInGreDir ? getGREDir() : getGREBinDir();
|
|
for (let i = 0; i < pathParts.length; i++) {
|
|
if (pathParts[i]) {
|
|
srcFile.append(pathParts[i] + (pathParts.length - 1 == i ? ".app" : ""));
|
|
destFile.append(pathParts[i] + (pathParts.length - 1 == i ? ".app" : ""));
|
|
}
|
|
}
|
|
fileRelPath = fileRelPath + ".app";
|
|
}
|
|
Assert.ok(srcFile.exists(),
|
|
MSG_SHOULD_EXIST + ", leafName: " + srcFile.leafName);
|
|
|
|
// Symlink libraries. Note that the XUL library on Mac OS X doesn't have a
|
|
// file extension and shouldSymlink will always be false on Windows.
|
|
let shouldSymlink = (pathParts[pathParts.length - 1] == "XUL" ||
|
|
fileRelPath.substr(fileRelPath.length - 3) == ".so" ||
|
|
fileRelPath.substr(fileRelPath.length - 6) == ".dylib");
|
|
if (!shouldSymlink) {
|
|
if (!destFile.exists()) {
|
|
try {
|
|
srcFile.copyToFollowingLinks(destFile.parent, destFile.leafName);
|
|
} catch (e) {
|
|
// Just in case it is partially copied
|
|
if (destFile.exists()) {
|
|
try {
|
|
destFile.remove(true);
|
|
} catch (ex) {
|
|
logTestInfo("unable to remove file that failed to copy! Path: " +
|
|
destFile.path);
|
|
}
|
|
}
|
|
do_throw("Unable to copy file! Path: " + srcFile.path +
|
|
", Exception: " + ex);
|
|
}
|
|
}
|
|
} else {
|
|
try {
|
|
if (destFile.exists()) {
|
|
destFile.remove(false);
|
|
}
|
|
let ln = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
|
ln.initWithPath("/bin/ln");
|
|
let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
|
|
process.init(ln);
|
|
let args = ["-s", srcFile.path, destFile.path];
|
|
process.run(true, args, args.length);
|
|
Assert.ok(destFile.isSymlink(),
|
|
destFile.leafName + " should be a symlink");
|
|
} catch (e) {
|
|
do_throw("Unable to create symlink for file! Path: " + srcFile.path +
|
|
", Exception: " + e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attempts to upgrade the maintenance service if permissions are allowed.
|
|
* This is useful for XP where we have permission to upgrade in case an
|
|
* older service installer exists. Also if the user manually installed into
|
|
* a unprivileged location.
|
|
*
|
|
* @return true if the installed service is from this build. If the installed
|
|
* service is not from this build the test will fail instead of
|
|
* returning false.
|
|
*/
|
|
function attemptServiceInstall() {
|
|
if (!IS_WIN) {
|
|
do_throw("Windows only function called by a different platform!");
|
|
}
|
|
|
|
let maintSvcDir = getMaintSvcDir();
|
|
Assert.ok(maintSvcDir.exists(),
|
|
MSG_SHOULD_EXIST + ", leafName: " + maintSvcDir.leafName);
|
|
let oldMaintSvcBin = maintSvcDir.clone();
|
|
oldMaintSvcBin.append(FILE_MAINTENANCE_SERVICE_BIN);
|
|
Assert.ok(oldMaintSvcBin.exists(),
|
|
MSG_SHOULD_EXIST + ", leafName: " + oldMaintSvcBin.leafName);
|
|
let buildMaintSvcBin = getGREBinDir();
|
|
buildMaintSvcBin.append(FILE_MAINTENANCE_SERVICE_BIN);
|
|
if (readFileBytes(oldMaintSvcBin) == readFileBytes(buildMaintSvcBin)) {
|
|
debugDump("installed maintenance service binary is the same as the " +
|
|
"build's maintenance service binary");
|
|
return true;
|
|
}
|
|
let backupMaintSvcBin = maintSvcDir.clone();
|
|
backupMaintSvcBin.append(FILE_MAINTENANCE_SERVICE_BIN + ".backup");
|
|
try {
|
|
if (backupMaintSvcBin.exists()) {
|
|
backupMaintSvcBin.remove(false);
|
|
}
|
|
oldMaintSvcBin.moveTo(maintSvcDir, FILE_MAINTENANCE_SERVICE_BIN + ".backup");
|
|
buildMaintSvcBin.copyTo(maintSvcDir, FILE_MAINTENANCE_SERVICE_BIN);
|
|
backupMaintSvcBin.remove(false);
|
|
} catch (e) {
|
|
// Restore the original file in case the moveTo was successful.
|
|
if (backupMaintSvcBin.exists()) {
|
|
oldMaintSvcBin = maintSvcDir.clone();
|
|
oldMaintSvcBin.append(FILE_MAINTENANCE_SERVICE_BIN);
|
|
if (!oldMaintSvcBin.exists()) {
|
|
backupMaintSvcBin.moveTo(maintSvcDir, FILE_MAINTENANCE_SERVICE_BIN);
|
|
}
|
|
}
|
|
Assert.ok(false, "should be able copy the test maintenance service to " +
|
|
"the maintenance service directory (if not, build system " +
|
|
"configuration bug?), path: " + maintSvcDir.path);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Waits for the applications that are launched by the maintenance service to
|
|
* stop.
|
|
*/
|
|
function waitServiceApps() {
|
|
if (!IS_WIN) {
|
|
do_throw("Windows only function called by a different platform!");
|
|
}
|
|
|
|
// maintenanceservice_installer.exe is started async during updates.
|
|
waitForApplicationStop("maintenanceservice_installer.exe");
|
|
// maintenanceservice_tmp.exe is started async from the service installer.
|
|
waitForApplicationStop("maintenanceservice_tmp.exe");
|
|
// In case the SCM thinks the service is stopped, but process still exists.
|
|
waitForApplicationStop("maintenanceservice.exe");
|
|
}
|
|
|
|
/**
|
|
* Waits for the maintenance service to stop.
|
|
*/
|
|
function waitForServiceStop(aFailTest) {
|
|
if (!IS_WIN) {
|
|
do_throw("Windows only function called by a different platform!");
|
|
}
|
|
|
|
waitServiceApps();
|
|
debugDump("waiting for the maintenance service to stop if necessary");
|
|
// Use the helper bin to ensure the service is stopped. If not stopped, then
|
|
// wait for the service to stop (at most 120 seconds).
|
|
let args = ["wait-for-service-stop", "MozillaMaintenance", "120"];
|
|
let exitValue = runTestHelperSync(args);
|
|
Assert.notEqual(exitValue, 0xEE,
|
|
"the maintenance service should exist");
|
|
if (exitValue != 0) {
|
|
if (aFailTest) {
|
|
Assert.ok(false, "the maintenance service should stop, process exit " +
|
|
"value: " + exitValue);
|
|
}
|
|
logTestInfo("maintenance service did not stop which may cause test " +
|
|
"failures later, process exit value: " + exitValue);
|
|
} else {
|
|
debugDump("service stopped");
|
|
}
|
|
waitServiceApps();
|
|
}
|
|
|
|
/**
|
|
* Waits for the specified application to stop.
|
|
*
|
|
* @param aApplication
|
|
* The application binary name to wait until it has stopped.
|
|
*/
|
|
function waitForApplicationStop(aApplication) {
|
|
if (!IS_WIN) {
|
|
do_throw("Windows only function called by a different platform!");
|
|
}
|
|
|
|
debugDump("waiting for " + aApplication + " to stop if necessary");
|
|
// Use the helper bin to ensure the application is stopped. If not stopped,
|
|
// then wait for it to stop (at most 120 seconds).
|
|
let args = ["wait-for-application-exit", aApplication, "120"];
|
|
let exitValue = runTestHelperSync(args);
|
|
Assert.equal(exitValue, 0,
|
|
"the process should have stopped, process name: " +
|
|
aApplication);
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets the platform specific shell binary that is launched using nsIProcess and
|
|
* in turn launches a binary used for the test (e.g. application, updater,
|
|
* etc.). A shell is used so debug console output can be redirected to a file so
|
|
* it doesn't end up in the test log.
|
|
*
|
|
* @return nsIFile for the shell binary to launch using nsIProcess.
|
|
*/
|
|
function getLaunchBin() {
|
|
let launchBin;
|
|
if (IS_WIN) {
|
|
launchBin = Services.dirsvc.get("WinD", Ci.nsIFile);
|
|
launchBin.append("System32");
|
|
launchBin.append("cmd.exe");
|
|
} else {
|
|
launchBin = Cc["@mozilla.org/file/local;1"].
|
|
createInstance(Ci.nsILocalFile);
|
|
launchBin.initWithPath("/bin/sh");
|
|
}
|
|
Assert.ok(launchBin.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(launchBin.path));
|
|
|
|
return launchBin;
|
|
}
|
|
|
|
|
|
/**
|
|
* Locks a Windows directory.
|
|
*
|
|
* @param aDirPath
|
|
* The test file object that describes the file to make in use.
|
|
*/
|
|
function lockDirectory(aDirPath) {
|
|
if (!IS_WIN) {
|
|
do_throw("Windows only function called by a different platform!");
|
|
}
|
|
|
|
debugDump("start - locking installation directory");
|
|
const LPCWSTR = ctypes.char16_t.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);
|
|
gHandle = CreateFile(aDirPath, GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE, LPVOID(0),
|
|
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, LPVOID(0));
|
|
Assert.notEqual(gHandle.toString(), INVALID_HANDLE_VALUE.toString(),
|
|
"the handle should not equal INVALID_HANDLE_VALUE");
|
|
kernel32.close();
|
|
debugDump("finish - locking installation directory");
|
|
}
|
|
|
|
/**
|
|
* Launches the test helper binary to make it in use for updater tests and then
|
|
* calls waitForHelperSleep.
|
|
*
|
|
* @param aTestFile
|
|
* The test file object that describes the file to make in use.
|
|
*/
|
|
function runHelperFileInUse(aRelPath, aCopyTestHelper) {
|
|
logTestInfo("aRelPath: " + aRelPath);
|
|
// Launch an existing file so it is in use during the update.
|
|
let helperBin = getTestDirFile(FILE_HELPER_BIN);
|
|
let fileInUseBin = getApplyDirFile(aRelPath);
|
|
if (aCopyTestHelper) {
|
|
fileInUseBin.remove(false);
|
|
helperBin.copyTo(fileInUseBin.parent, fileInUseBin.leafName);
|
|
}
|
|
fileInUseBin.permissions = PERMS_DIRECTORY;
|
|
let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
|
|
HELPER_SLEEP_TIMEOUT];
|
|
let fileInUseProcess = Cc["@mozilla.org/process/util;1"].
|
|
createInstance(Ci.nsIProcess);
|
|
fileInUseProcess.init(fileInUseBin);
|
|
fileInUseProcess.run(false, args, args.length);
|
|
|
|
do_execute_soon(waitForHelperSleep);
|
|
}
|
|
|
|
/**
|
|
* Launches the test helper binary and locks a file specified on the command
|
|
* line for updater tests and then calls waitForHelperSleep.
|
|
*
|
|
* @param aTestFile
|
|
* The test file object that describes the file to lock.
|
|
*/
|
|
function runHelperLockFile(aTestFile) {
|
|
// Exclusively lock an existing file so it is in use during the update.
|
|
let helperBin = getTestDirFile(FILE_HELPER_BIN);
|
|
let helperDestDir = getApplyDirFile(DIR_RESOURCES);
|
|
helperBin.copyTo(helperDestDir, FILE_HELPER_BIN);
|
|
helperBin = getApplyDirFile(DIR_RESOURCES + FILE_HELPER_BIN);
|
|
// Strip off the first two directories so the path has to be from the helper's
|
|
// working directory.
|
|
let lockFileRelPath = aTestFile.relPathDir.split("/");
|
|
if (IS_MACOSX) {
|
|
lockFileRelPath = lockFileRelPath.slice(2);
|
|
}
|
|
lockFileRelPath = lockFileRelPath.join("/") + "/" + aTestFile.fileName;
|
|
let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
|
|
HELPER_SLEEP_TIMEOUT, lockFileRelPath];
|
|
let helperProcess = Cc["@mozilla.org/process/util;1"].
|
|
createInstance(Ci.nsIProcess);
|
|
helperProcess.init(helperBin);
|
|
helperProcess.run(false, args, args.length);
|
|
|
|
do_execute_soon(waitForHelperSleep);
|
|
}
|
|
|
|
/**
|
|
* Helper function that waits until the helper has completed its operations and
|
|
* calls waitForHelperSleepFinished when it is finished.
|
|
*/
|
|
function waitForHelperSleep() {
|
|
gTimeoutRuns++;
|
|
// Give the lock file process time to lock the file before updating otherwise
|
|
// this test can fail intermittently on Windows debug builds.
|
|
let output = getApplyDirFile(DIR_RESOURCES + "output", true);
|
|
if (readFile(output) != "sleeping\n") {
|
|
if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
|
|
do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the helper to " +
|
|
"finish its operation. Path: " + output.path);
|
|
}
|
|
// Uses do_timeout instead of do_execute_soon to lessen log spew.
|
|
do_timeout(FILE_IN_USE_TIMEOUT_MS, waitForHelperSleep);
|
|
return;
|
|
}
|
|
try {
|
|
output.remove(false);
|
|
} catch (e) {
|
|
if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
|
|
do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the helper " +
|
|
"message file to no longer be in use. Path: " + output.path);
|
|
}
|
|
debugDump("failed to remove file. Path: " + output.path);
|
|
// Uses do_timeout instead of do_execute_soon to lessen log spew.
|
|
do_timeout(FILE_IN_USE_TIMEOUT_MS, waitForHelperSleep);
|
|
return;
|
|
}
|
|
waitForHelperSleepFinished();
|
|
}
|
|
|
|
/**
|
|
* Helper function that waits until the helper has finished its operations
|
|
* before calling waitForHelperFinishFileUnlock to verify that the helper's
|
|
* input and output directories are no longer in use.
|
|
*/
|
|
function waitForHelperFinished() {
|
|
// Give the lock file process time to lock the file before updating otherwise
|
|
// this test can fail intermittently on Windows debug builds.
|
|
let output = getApplyDirFile(DIR_RESOURCES + "output", true);
|
|
if (readFile(output) != "finished\n") {
|
|
// Uses do_timeout instead of do_execute_soon to lessen log spew.
|
|
do_timeout(FILE_IN_USE_TIMEOUT_MS, waitForHelperFinished);
|
|
return;
|
|
}
|
|
// Give the lock file process time to unlock the file before deleting the
|
|
// input and output files.
|
|
waitForHelperFinishFileUnlock();
|
|
}
|
|
|
|
/**
|
|
* Helper function that waits until the helper's input and output files are no
|
|
* longer in use before calling waitForHelperExitFinished.
|
|
*/
|
|
function waitForHelperFinishFileUnlock() {
|
|
try {
|
|
let output = getApplyDirFile(DIR_RESOURCES + "output", true);
|
|
if (output.exists()) {
|
|
output.remove(false);
|
|
}
|
|
let input = getApplyDirFile(DIR_RESOURCES + "input", true);
|
|
if (input.exists()) {
|
|
input.remove(false);
|
|
}
|
|
} catch (e) {
|
|
// Give the lock file process time to unlock the file before deleting the
|
|
// input and output files.
|
|
do_execute_soon(waitForHelperFinishFileUnlock);
|
|
return;
|
|
}
|
|
do_execute_soon(waitForHelperExitFinished);
|
|
}
|
|
|
|
/**
|
|
* Helper function to tell the helper to finish and exit its sleep state.
|
|
*/
|
|
function waitForHelperExit() {
|
|
let input = getApplyDirFile(DIR_RESOURCES + "input", true);
|
|
writeFile(input, "finish\n");
|
|
waitForHelperFinished();
|
|
}
|
|
|
|
/**
|
|
* Helper function for updater binary tests that creates the files and
|
|
* directories used by the test.
|
|
*
|
|
* @param aMarFile
|
|
* The mar file for the update test.
|
|
* @param aPostUpdateAsync
|
|
* When null the updater.ini is not created otherwise this parameter
|
|
* is passed to createUpdaterINI.
|
|
* @param aPostUpdateExeRelPathPrefix
|
|
* When aPostUpdateAsync null this value is ignored otherwise it is
|
|
* passed to createUpdaterINI.
|
|
*/
|
|
function setupUpdaterTest(aMarFile, aPostUpdateAsync,
|
|
aPostUpdateExeRelPathPrefix = "") {
|
|
let updatesPatchDir = getUpdatesPatchDir();
|
|
if (!updatesPatchDir.exists()) {
|
|
updatesPatchDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
|
|
}
|
|
// Copy the mar that will be applied
|
|
let mar = getTestDirFile(aMarFile);
|
|
mar.copyToFollowingLinks(updatesPatchDir, FILE_UPDATE_MAR);
|
|
|
|
let helperBin = getTestDirFile(FILE_HELPER_BIN);
|
|
helperBin.permissions = PERMS_DIRECTORY;
|
|
let afterApplyBinDir = getApplyDirFile(DIR_RESOURCES, true);
|
|
helperBin.copyToFollowingLinks(afterApplyBinDir, gCallbackBinFile);
|
|
helperBin.copyToFollowingLinks(afterApplyBinDir, gPostUpdateBinFile);
|
|
|
|
gTestFiles.forEach(function SUT_TF_FE(aTestFile) {
|
|
if (aTestFile.originalFile || aTestFile.originalContents) {
|
|
let testDir = getApplyDirFile(aTestFile.relPathDir, true);
|
|
if (!testDir.exists()) {
|
|
testDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
|
|
}
|
|
|
|
let testFile;
|
|
if (aTestFile.originalFile) {
|
|
testFile = getTestDirFile(aTestFile.originalFile);
|
|
testFile.copyToFollowingLinks(testDir, aTestFile.fileName);
|
|
testFile = getApplyDirFile(aTestFile.relPathDir + aTestFile.fileName);
|
|
} else {
|
|
testFile = getApplyDirFile(aTestFile.relPathDir + aTestFile.fileName,
|
|
true);
|
|
writeFile(testFile, aTestFile.originalContents);
|
|
}
|
|
|
|
// Skip these tests on Windows since chmod doesn't really set permissions
|
|
// on Windows.
|
|
if (!IS_WIN && aTestFile.originalPerms) {
|
|
testFile.permissions = aTestFile.originalPerms;
|
|
// Store the actual permissions on the file for reference later after
|
|
// setting the permissions.
|
|
if (!aTestFile.comparePerms) {
|
|
aTestFile.comparePerms = testFile.permissions;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Add the test directory that will be updated for a successful update or left
|
|
// in the initial state for a failed update.
|
|
gTestDirs.forEach(function SUT_TD_FE(aTestDir) {
|
|
let testDir = getApplyDirFile(aTestDir.relPathDir, true);
|
|
if (!testDir.exists()) {
|
|
testDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
|
|
}
|
|
|
|
if (aTestDir.files) {
|
|
aTestDir.files.forEach(function SUT_TD_F_FE(aTestFile) {
|
|
let testFile = getApplyDirFile(aTestDir.relPathDir + aTestFile, true);
|
|
if (!testFile.exists()) {
|
|
testFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (aTestDir.subDirs) {
|
|
aTestDir.subDirs.forEach(function SUT_TD_SD_FE(aSubDir) {
|
|
let testSubDir = getApplyDirFile(aTestDir.relPathDir + aSubDir, true);
|
|
if (!testSubDir.exists()) {
|
|
testSubDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
|
|
}
|
|
|
|
if (aTestDir.subDirFiles) {
|
|
aTestDir.subDirFiles.forEach(function SUT_TD_SDF_FE(aTestFile) {
|
|
let testFile = getApplyDirFile(aTestDir.relPathDir + aSubDir + aTestFile, true);
|
|
if (!testFile.exists()) {
|
|
testFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
setupActiveUpdate();
|
|
|
|
if (aPostUpdateAsync !== null) {
|
|
createUpdaterINI(aPostUpdateAsync, aPostUpdateExeRelPathPrefix);
|
|
}
|
|
|
|
setupAppFilesAsync();
|
|
}
|
|
|
|
/**
|
|
* Helper function for updater binary tests that creates the update-settings.ini
|
|
* file.
|
|
*/
|
|
function createUpdateSettingsINI() {
|
|
let ini = getApplyDirFile(DIR_RESOURCES + FILE_UPDATE_SETTINGS_INI, true);
|
|
writeFile(ini, UPDATE_SETTINGS_CONTENTS);
|
|
}
|
|
|
|
/**
|
|
* Helper function for updater binary tests that creates the updater.ini
|
|
* file.
|
|
*
|
|
* @param aIsExeAsync
|
|
* True or undefined if the post update process should be async. If
|
|
* undefined ExeAsync will not be added to the updater.ini file in
|
|
* order to test the default launch behavior which is async.
|
|
* @param aExeRelPathPrefix
|
|
* A string to prefix the ExeRelPath values in the updater.ini.
|
|
*/
|
|
function createUpdaterINI(aIsExeAsync, aExeRelPathPrefix) {
|
|
let exeArg = "ExeArg=post-update-async\n";
|
|
let exeAsync = "";
|
|
if (aIsExeAsync !== undefined) {
|
|
if (aIsExeAsync) {
|
|
exeAsync = "ExeAsync=true\n";
|
|
} else {
|
|
exeArg = "ExeArg=post-update-sync\n";
|
|
exeAsync = "ExeAsync=false\n";
|
|
}
|
|
}
|
|
|
|
if (aExeRelPathPrefix && IS_WIN) {
|
|
aExeRelPathPrefix = aExeRelPathPrefix.replace("/", "\\");
|
|
}
|
|
|
|
let exeRelPathMac = "ExeRelPath=" + aExeRelPathPrefix + DIR_RESOURCES +
|
|
gPostUpdateBinFile + "\n";
|
|
let exeRelPathWin = "ExeRelPath=" + aExeRelPathPrefix + gPostUpdateBinFile + "\n";
|
|
let updaterIniContents = "[Strings]\n" +
|
|
"Title=Update Test\n" +
|
|
"Info=Running update test " + gTestID + "\n\n" +
|
|
"[PostUpdateMac]\n" +
|
|
exeRelPathMac +
|
|
exeArg +
|
|
exeAsync +
|
|
"\n" +
|
|
"[PostUpdateWin]\n" +
|
|
exeRelPathWin +
|
|
exeArg +
|
|
exeAsync;
|
|
let updaterIni = getApplyDirFile(DIR_RESOURCES + FILE_UPDATER_INI, true);
|
|
writeFile(updaterIni, updaterIniContents);
|
|
}
|
|
|
|
/**
|
|
* Gets the message log path used for assert checks to lessen the length printed
|
|
* to the log file.
|
|
*
|
|
* @param aPath
|
|
* The path to shorten for the log file.
|
|
* @return the message including the shortened path for the log file.
|
|
*/
|
|
function getMsgPath(aPath) {
|
|
return ", path: " + replaceLogPaths(aPath);
|
|
}
|
|
|
|
/**
|
|
* Helper function that replaces the common part of paths in the update log's
|
|
* contents with <test_dir_path> for paths to the the test directory and
|
|
* <update_dir_path> for paths to the update directory. This is needed since
|
|
* Assert.equal will truncate what it prints to the xpcshell log file.
|
|
*
|
|
* @param aLogContents
|
|
* The update log file's contents.
|
|
* @return the log contents with the paths replaced.
|
|
*/
|
|
function replaceLogPaths(aLogContents) {
|
|
let logContents = aLogContents;
|
|
// Remove the majority of the path up to the test directory. This is needed
|
|
// since Assert.equal won't print long strings to the test logs.
|
|
let testDirPath = do_get_file(gTestID, false).path;
|
|
if (IS_WIN) {
|
|
// Replace \\ with \\\\ so the regexp works.
|
|
testDirPath = testDirPath.replace(/\\/g, "\\\\");
|
|
}
|
|
logContents = logContents.replace(new RegExp(testDirPath, "g"),
|
|
"<test_dir_path>/" + gTestID);
|
|
let updatesDirPath = getMockUpdRootD().path;
|
|
if (IS_WIN) {
|
|
// Replace \\ with \\\\ so the regexp works.
|
|
updatesDirPath = updatesDirPath.replace(/\\/g, "\\\\");
|
|
}
|
|
logContents = logContents.replace(new RegExp(updatesDirPath, "g"),
|
|
"<update_dir_path>/" + gTestID);
|
|
if (IS_WIN) {
|
|
// Replace \ with /
|
|
logContents = logContents.replace(/\\/g, "/");
|
|
}
|
|
return logContents;
|
|
}
|
|
|
|
/**
|
|
* Helper function for updater binary tests for verifying the contents of the
|
|
* update log after a successful update.
|
|
*
|
|
* @param aCompareLogFile
|
|
* The log file to compare the update log with.
|
|
* @param aStaged
|
|
* If the update log file is for a staged update.
|
|
* @param aReplace
|
|
* If the update log file is for a replace update.
|
|
* @param aExcludeDistDir
|
|
* Removes lines containing the distribution directory from the log
|
|
* file to compare the update log with.
|
|
*/
|
|
function checkUpdateLogContents(aCompareLogFile, aStaged = false,
|
|
aReplace = false, aExcludeDistDir = false) {
|
|
if (IS_UNIX && !IS_MACOSX) {
|
|
// The order that files are returned when enumerating the file system on
|
|
// Linux is not deterministic so skip checking the logs.
|
|
return;
|
|
}
|
|
|
|
let updateLog = getUpdateLog(FILE_LAST_UPDATE_LOG);
|
|
let updateLogContents = readFileBytes(updateLog);
|
|
|
|
// The channel-prefs.js is defined in gTestFilesCommon which will always be
|
|
// located to the end of gTestFiles when it is present.
|
|
if (gTestFiles.length > 1 &&
|
|
gTestFiles[gTestFiles.length - 1].fileName == "channel-prefs.js" &&
|
|
!gTestFiles[gTestFiles.length - 1].originalContents) {
|
|
updateLogContents = updateLogContents.replace(/.*defaults\/.*/g, "");
|
|
}
|
|
|
|
if (gTestFiles.length > 2 &&
|
|
gTestFiles[gTestFiles.length - 2].fileName == FILE_UPDATE_SETTINGS_INI &&
|
|
!gTestFiles[gTestFiles.length - 2].originalContents) {
|
|
updateLogContents = updateLogContents.replace(/.*update-settings.ini.*/g, "");
|
|
}
|
|
|
|
// Skip the source/destination lines since they contain absolute paths.
|
|
// These could be changed to relative paths using <test_dir_path> and
|
|
// <update_dir_path>
|
|
updateLogContents = updateLogContents.replace(/PATCH DIRECTORY.*/g, "");
|
|
updateLogContents = updateLogContents.replace(/INSTALLATION DIRECTORY.*/g, "");
|
|
updateLogContents = updateLogContents.replace(/WORKING DIRECTORY.*/g, "");
|
|
// Skip lines that log failed attempts to open the callback executable.
|
|
updateLogContents = updateLogContents.replace(/NS_main: callback app file .*/g, "");
|
|
|
|
if (IS_MACOSX) {
|
|
// Skip lines that log moving the distribution directory for Mac v2 signing.
|
|
updateLogContents = updateLogContents.replace(/Moving old [^\n]*\nrename_file: .*/g, "");
|
|
updateLogContents = updateLogContents.replace(/New distribution directory .*/g, "");
|
|
}
|
|
|
|
if (IS_WIN) {
|
|
// The FindFile results when enumerating the filesystem on Windows is not
|
|
// determistic so the results matching the following need to be fixed.
|
|
let re = new RegExp("([^\n]* 7\/7text1[^\n]*)\n" +
|
|
"([^\n]* 7\/7text0[^\n]*)\n", "g");
|
|
updateLogContents = updateLogContents.replace(re, "$2\n$1\n");
|
|
}
|
|
|
|
if (aReplace) {
|
|
// Remove the lines which contain absolute paths
|
|
updateLogContents = updateLogContents.replace(/^Begin moving.*$/mg, "");
|
|
updateLogContents = updateLogContents.replace(/^ensure_remove: failed to remove file: .*$/mg, "");
|
|
updateLogContents = updateLogContents.replace(/^ensure_remove_recursive: unable to remove directory: .*$/mg, "");
|
|
updateLogContents = updateLogContents.replace(/^Removing tmpDir failed, err: -1$/mg, "");
|
|
updateLogContents = updateLogContents.replace(/^remove_recursive_on_reboot: .*$/mg, "");
|
|
}
|
|
|
|
// Remove carriage returns.
|
|
updateLogContents = updateLogContents.replace(/\r/g, "");
|
|
// Replace error codes since they are different on each platform.
|
|
updateLogContents = updateLogContents.replace(/, err:.*\n/g, "\n");
|
|
// Replace to make the log parsing happy.
|
|
updateLogContents = updateLogContents.replace(/non-fatal error /g, "");
|
|
// Remove consecutive newlines
|
|
updateLogContents = updateLogContents.replace(/\n+/g, "\n");
|
|
// Remove leading and trailing newlines
|
|
updateLogContents = updateLogContents.replace(/^\n|\n$/g, "");
|
|
// Replace the log paths with <test_dir_path> and <update_dir_path>
|
|
updateLogContents = replaceLogPaths(updateLogContents);
|
|
|
|
let compareLogContents = "";
|
|
if (aCompareLogFile) {
|
|
compareLogContents = readFileBytes(getTestDirFile(aCompareLogFile));
|
|
}
|
|
|
|
if (aStaged) {
|
|
compareLogContents = PERFORMING_STAGED_UPDATE + "\n" + compareLogContents;
|
|
}
|
|
|
|
// The channel-prefs.js is defined in gTestFilesCommon which will always be
|
|
// located to the end of gTestFiles.
|
|
if (gTestFiles.length > 1 &&
|
|
gTestFiles[gTestFiles.length - 1].fileName == "channel-prefs.js" &&
|
|
!gTestFiles[gTestFiles.length - 1].originalContents) {
|
|
compareLogContents = compareLogContents.replace(/.*defaults\/.*/g, "");
|
|
}
|
|
|
|
if (gTestFiles.length > 2 &&
|
|
gTestFiles[gTestFiles.length - 2].fileName == FILE_UPDATE_SETTINGS_INI &&
|
|
!gTestFiles[gTestFiles.length - 2].originalContents) {
|
|
compareLogContents = compareLogContents.replace(/.*update-settings.ini.*/g, "");
|
|
}
|
|
|
|
if (aExcludeDistDir) {
|
|
compareLogContents = compareLogContents.replace(/.*distribution\/.*/g, "");
|
|
}
|
|
|
|
// Remove leading and trailing newlines
|
|
compareLogContents = compareLogContents.replace(/\n+/g, "\n");
|
|
// Remove leading and trailing newlines
|
|
compareLogContents = compareLogContents.replace(/^\n|\n$/g, "");
|
|
|
|
// Don't write the contents of the file to the log to reduce log spam
|
|
// unless there is a failure.
|
|
if (compareLogContents == updateLogContents) {
|
|
Assert.ok(true, "the update log contents" + MSG_SHOULD_EQUAL);
|
|
} else {
|
|
logTestInfo("the update log contents are not correct");
|
|
logUpdateLog(FILE_LAST_UPDATE_LOG);
|
|
let aryLog = updateLogContents.split("\n");
|
|
let aryCompare = compareLogContents.split("\n");
|
|
// Pushing an empty string to both arrays makes it so either array's length
|
|
// can be used in the for loop below without going out of bounds.
|
|
aryLog.push("");
|
|
aryCompare.push("");
|
|
// xpcshell tests won't display the entire contents so log the first
|
|
// incorrect line.
|
|
for (let i = 0; i < aryLog.length; ++i) {
|
|
if (aryLog[i] != aryCompare[i]) {
|
|
logTestInfo("the first incorrect line in the update log is: " +
|
|
aryLog[i]);
|
|
Assert.equal(aryLog[i], aryCompare[i],
|
|
"the update log contents" + MSG_SHOULD_EQUAL);
|
|
}
|
|
}
|
|
// This should never happen!
|
|
do_throw("Unable to find incorrect update log contents!");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function to check if the update log contains a string.
|
|
*
|
|
* @param aCheckString
|
|
* The string to check if the update log contains.
|
|
*/
|
|
function checkUpdateLogContains(aCheckString) {
|
|
let updateLog = getUpdateLog(FILE_LAST_UPDATE_LOG);
|
|
let updateLogContents = readFileBytes(updateLog).replace(/\r\n/g, "\n");
|
|
updateLogContents = replaceLogPaths(updateLogContents);
|
|
Assert.notEqual(updateLogContents.indexOf(aCheckString), -1,
|
|
"the update log contents should contain value: " +
|
|
aCheckString);
|
|
}
|
|
|
|
/**
|
|
* Helper function for updater binary tests for verifying the state of files and
|
|
* directories after a successful update.
|
|
*
|
|
* @param aGetFileFunc
|
|
* The function used to get the files in the directory to be checked.
|
|
* @param aStageDirExists
|
|
* If true the staging directory will be tested for existence and if
|
|
* false the staging directory will be tested for non-existence.
|
|
* @param aToBeDeletedDirExists
|
|
* On Windows, if true the tobedeleted directory will be tested for
|
|
* existence and if false the tobedeleted directory will be tested for
|
|
* non-existence. On all othere platforms it will be tested for
|
|
* non-existence.
|
|
*/
|
|
function checkFilesAfterUpdateSuccess(aGetFileFunc, aStageDirExists = false,
|
|
aToBeDeletedDirExists = false) {
|
|
debugDump("testing contents of files after a successful update");
|
|
gTestFiles.forEach(function CFAUS_TF_FE(aTestFile) {
|
|
let testFile = aGetFileFunc(aTestFile.relPathDir + aTestFile.fileName, true);
|
|
debugDump("testing file: " + testFile.path);
|
|
if (aTestFile.compareFile || aTestFile.compareContents) {
|
|
Assert.ok(testFile.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(testFile.path));
|
|
|
|
// Skip these tests on Windows since chmod doesn't really set permissions
|
|
// on Windows.
|
|
if (!IS_WIN && aTestFile.comparePerms) {
|
|
// Check if the permssions as set in the complete mar file are correct.
|
|
Assert.equal(testFile.permissions & 0xfff,
|
|
aTestFile.comparePerms & 0xfff,
|
|
"the file permissions" + MSG_SHOULD_EQUAL);
|
|
}
|
|
|
|
let fileContents1 = readFileBytes(testFile);
|
|
let fileContents2 = aTestFile.compareFile ?
|
|
readFileBytes(getTestDirFile(aTestFile.compareFile)) :
|
|
aTestFile.compareContents;
|
|
// Don't write the contents of the file to the log to reduce log spam
|
|
// unless there is a failure.
|
|
if (fileContents1 == fileContents2) {
|
|
Assert.ok(true, "the file contents" + MSG_SHOULD_EQUAL);
|
|
} else {
|
|
Assert.equal(fileContents1, fileContents2,
|
|
"the file contents" + MSG_SHOULD_EQUAL);
|
|
}
|
|
} else {
|
|
Assert.ok(!testFile.exists(),
|
|
MSG_SHOULD_NOT_EXIST + getMsgPath(testFile.path));
|
|
}
|
|
});
|
|
|
|
debugDump("testing operations specified in removed-files were performed " +
|
|
"after a successful update");
|
|
gTestDirs.forEach(function CFAUS_TD_FE(aTestDir) {
|
|
let testDir = aGetFileFunc(aTestDir.relPathDir, true);
|
|
debugDump("testing directory: " + testDir.path);
|
|
if (aTestDir.dirRemoved) {
|
|
Assert.ok(!testDir.exists(),
|
|
MSG_SHOULD_NOT_EXIST + getMsgPath(testDir.path));
|
|
} else {
|
|
Assert.ok(testDir.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(testDir.path));
|
|
|
|
if (aTestDir.files) {
|
|
aTestDir.files.forEach(function CFAUS_TD_F_FE(aTestFile) {
|
|
let testFile = aGetFileFunc(aTestDir.relPathDir + aTestFile, true);
|
|
if (aTestDir.filesRemoved) {
|
|
Assert.ok(!testFile.exists(),
|
|
MSG_SHOULD_NOT_EXIST + getMsgPath(testFile.path));
|
|
} else {
|
|
Assert.ok(testFile.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(testFile.path));
|
|
}
|
|
});
|
|
}
|
|
|
|
if (aTestDir.subDirs) {
|
|
aTestDir.subDirs.forEach(function CFAUS_TD_SD_FE(aSubDir) {
|
|
let testSubDir = aGetFileFunc(aTestDir.relPathDir + aSubDir, true);
|
|
Assert.ok(testSubDir.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(testSubDir.path));
|
|
if (aTestDir.subDirFiles) {
|
|
aTestDir.subDirFiles.forEach(function CFAUS_TD_SDF_FE(aTestFile) {
|
|
let testFile = aGetFileFunc(aTestDir.relPathDir +
|
|
aSubDir + aTestFile, true);
|
|
Assert.ok(testFile.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(testFile.path));
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
checkFilesAfterUpdateCommon(aGetFileFunc, aStageDirExists,
|
|
aToBeDeletedDirExists);
|
|
}
|
|
|
|
/**
|
|
* Helper function for updater binary tests for verifying the state of files and
|
|
* directories after a failed update.
|
|
*
|
|
* @param aGetFileFunc
|
|
* The function used to get the files in the directory to be checked.
|
|
* @param aStageDirExists
|
|
* If true the staging directory will be tested for existence and if
|
|
* false the staging directory will be tested for non-existence.
|
|
* @param aToBeDeletedDirExists
|
|
* On Windows, if true the tobedeleted directory will be tested for
|
|
* existence and if false the tobedeleted directory will be tested for
|
|
* non-existence. On all othere platforms it will be tested for
|
|
* non-existence.
|
|
*/
|
|
function checkFilesAfterUpdateFailure(aGetFileFunc, aStageDirExists = false,
|
|
aToBeDeletedDirExists = false) {
|
|
debugDump("testing contents of files after a failed update");
|
|
gTestFiles.forEach(function CFAUF_TF_FE(aTestFile) {
|
|
let testFile = aGetFileFunc(aTestFile.relPathDir + aTestFile.fileName, true);
|
|
debugDump("testing file: " + testFile.path);
|
|
if (aTestFile.compareFile || aTestFile.compareContents) {
|
|
Assert.ok(testFile.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(testFile.path));
|
|
|
|
// Skip these tests on Windows since chmod doesn't really set permissions
|
|
// on Windows.
|
|
if (!IS_WIN && aTestFile.comparePerms) {
|
|
// Check the original permssions are retained on the file.
|
|
Assert.equal(testFile.permissions & 0xfff,
|
|
aTestFile.comparePerms & 0xfff,
|
|
"the file permissions" + MSG_SHOULD_EQUAL);
|
|
}
|
|
|
|
let fileContents1 = readFileBytes(testFile);
|
|
let fileContents2 = aTestFile.compareFile ?
|
|
readFileBytes(getTestDirFile(aTestFile.compareFile)) :
|
|
aTestFile.compareContents;
|
|
// Don't write the contents of the file to the log to reduce log spam
|
|
// unless there is a failure.
|
|
if (fileContents1 == fileContents2) {
|
|
Assert.ok(true, "the file contents" + MSG_SHOULD_EQUAL);
|
|
} else {
|
|
Assert.equal(fileContents1, fileContents2,
|
|
"the file contents" + MSG_SHOULD_EQUAL);
|
|
}
|
|
} else {
|
|
Assert.ok(!testFile.exists(),
|
|
MSG_SHOULD_NOT_EXIST + getMsgPath(testFile.path));
|
|
}
|
|
});
|
|
|
|
debugDump("testing operations specified in removed-files were not " +
|
|
"performed after a failed update");
|
|
gTestDirs.forEach(function CFAUF_TD_FE(aTestDir) {
|
|
let testDir = aGetFileFunc(aTestDir.relPathDir, true);
|
|
Assert.ok(testDir.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(testDir.path));
|
|
|
|
if (aTestDir.files) {
|
|
aTestDir.files.forEach(function CFAUS_TD_F_FE(aTestFile) {
|
|
let testFile = aGetFileFunc(aTestDir.relPathDir + aTestFile, true);
|
|
Assert.ok(testFile.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(testFile.path));
|
|
});
|
|
}
|
|
|
|
if (aTestDir.subDirs) {
|
|
aTestDir.subDirs.forEach(function CFAUS_TD_SD_FE(aSubDir) {
|
|
let testSubDir = aGetFileFunc(aTestDir.relPathDir + aSubDir, true);
|
|
Assert.ok(testSubDir.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(testSubDir.path));
|
|
if (aTestDir.subDirFiles) {
|
|
aTestDir.subDirFiles.forEach(function CFAUS_TD_SDF_FE(aTestFile) {
|
|
let testFile = aGetFileFunc(aTestDir.relPathDir +
|
|
aSubDir + aTestFile, true);
|
|
Assert.ok(testFile.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(testFile.path));
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
checkFilesAfterUpdateCommon(aGetFileFunc, aStageDirExists,
|
|
aToBeDeletedDirExists);
|
|
}
|
|
|
|
/**
|
|
* Helper function for updater binary tests for verifying the state of common
|
|
* files and directories after a successful or failed update.
|
|
*
|
|
* @param aGetFileFunc
|
|
* the function used to get the files in the directory to be checked.
|
|
* @param aStageDirExists
|
|
* If true the staging directory will be tested for existence and if
|
|
* false the staging directory will be tested for non-existence.
|
|
* @param aToBeDeletedDirExists
|
|
* On Windows, if true the tobedeleted directory will be tested for
|
|
* existence and if false the tobedeleted directory will be tested for
|
|
* non-existence. On all othere platforms it will be tested for
|
|
* non-existence.
|
|
*/
|
|
function checkFilesAfterUpdateCommon(aGetFileFunc, aStageDirExists,
|
|
aToBeDeletedDirExists) {
|
|
debugDump("testing extra directories");
|
|
let stageDir = getStageDirFile(null, true);
|
|
if (aStageDirExists) {
|
|
Assert.ok(stageDir.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(stageDir.path));
|
|
} else {
|
|
Assert.ok(!stageDir.exists(),
|
|
MSG_SHOULD_NOT_EXIST + getMsgPath(stageDir.path));
|
|
}
|
|
|
|
let toBeDeletedDirExists = IS_WIN ? aToBeDeletedDirExists : false;
|
|
let toBeDeletedDir = getApplyDirFile(DIR_TOBEDELETED, true);
|
|
if (toBeDeletedDirExists) {
|
|
Assert.ok(toBeDeletedDir.exists(),
|
|
MSG_SHOULD_EXIST + getMsgPath(toBeDeletedDir.path));
|
|
} else {
|
|
Assert.ok(!toBeDeletedDir.exists(),
|
|
MSG_SHOULD_NOT_EXIST + getMsgPath(toBeDeletedDir.path));
|
|
}
|
|
|
|
let updatingDir = getApplyDirFile("updating", true);
|
|
Assert.ok(!updatingDir.exists(),
|
|
MSG_SHOULD_NOT_EXIST + getMsgPath(updatingDir.path));
|
|
|
|
if (stageDir.exists()) {
|
|
updatingDir = stageDir.clone();
|
|
updatingDir.append("updating");
|
|
Assert.ok(!updatingDir.exists(),
|
|
MSG_SHOULD_NOT_EXIST + getMsgPath(updatingDir.path));
|
|
}
|
|
|
|
debugDump("testing backup files should not be left behind in the " +
|
|
"application directory");
|
|
let applyToDir = getApplyDirFile(null, true);
|
|
checkFilesInDirRecursive(applyToDir, checkForBackupFiles);
|
|
|
|
if (stageDir.exists()) {
|
|
debugDump("testing backup files should not be left behind in the " +
|
|
"staging directory");
|
|
applyToDir = getApplyDirFile(null, true);
|
|
checkFilesInDirRecursive(stageDir, checkForBackupFiles);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function for updater binary tests for verifying the contents of the
|
|
* updater callback application log which should contain the arguments passed to
|
|
* the callback application.
|
|
*/
|
|
function checkCallbackLog() {
|
|
let appLaunchLog = getApplyDirFile(DIR_RESOURCES + gCallbackArgs[1], true);
|
|
if (!appLaunchLog.exists()) {
|
|
// Uses do_timeout instead of do_execute_soon to lessen log spew.
|
|
do_timeout(FILE_IN_USE_TIMEOUT_MS, checkCallbackLog);
|
|
return;
|
|
}
|
|
|
|
let expectedLogContents = gCallbackArgs.join("\n") + "\n";
|
|
let logContents = readFile(appLaunchLog);
|
|
// It is possible for the log file contents check to occur before the log file
|
|
// contents are completely written so wait until the contents are the expected
|
|
// value. If the contents are never the expected value then the test will
|
|
// fail by timing out after gTimeoutRuns is greater than MAX_TIMEOUT_RUNS or
|
|
// the test harness times out the test.
|
|
if (logContents != expectedLogContents) {
|
|
gTimeoutRuns++;
|
|
if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
|
|
logTestInfo("callback log contents are not correct");
|
|
// This file doesn't contain full paths so there is no need to call
|
|
// replaceLogPaths.
|
|
let aryLog = logContents.split("\n");
|
|
let aryCompare = expectedLogContents.split("\n");
|
|
// Pushing an empty string to both arrays makes it so either array's length
|
|
// can be used in the for loop below without going out of bounds.
|
|
aryLog.push("");
|
|
aryCompare.push("");
|
|
// xpcshell tests won't display the entire contents so log the incorrect
|
|
// line.
|
|
for (let i = 0; i < aryLog.length; ++i) {
|
|
if (aryLog[i] != aryCompare[i]) {
|
|
logTestInfo("the first incorrect line in the callback log is: " +
|
|
aryLog[i]);
|
|
Assert.equal(aryLog[i], aryCompare[i],
|
|
"the callback log contents" + MSG_SHOULD_EQUAL);
|
|
}
|
|
}
|
|
// This should never happen!
|
|
do_throw("Unable to find incorrect callback log contents!");
|
|
}
|
|
// Uses do_timeout instead of do_execute_soon to lessen log spew.
|
|
do_timeout(FILE_IN_USE_TIMEOUT_MS, checkCallbackLog);
|
|
return;
|
|
}
|
|
Assert.ok(true, "the callback log contents" + MSG_SHOULD_EQUAL);
|
|
|
|
waitForFilesInUse();
|
|
}
|
|
|
|
/**
|
|
* Helper function for updater binary tests for getting the log and running
|
|
* files created by the test helper binary file when called with the post-update
|
|
* command line argument.
|
|
*
|
|
* @param aSuffix
|
|
* The string to append to the post update test helper binary path.
|
|
*/
|
|
function getPostUpdateFile(aSuffix) {
|
|
return getApplyDirFile(DIR_RESOURCES + gPostUpdateBinFile + aSuffix, true);
|
|
}
|
|
|
|
/**
|
|
* Checks the contents of the updater post update binary log. When completed
|
|
* checkPostUpdateAppLogFinished will be called.
|
|
*/
|
|
function checkPostUpdateAppLog() {
|
|
// Only Mac OS X and Windows support post update.
|
|
if (IS_MACOSX || IS_WIN) {
|
|
gTimeoutRuns++;
|
|
let postUpdateLog = getPostUpdateFile(".log");
|
|
if (!postUpdateLog.exists()) {
|
|
debugDump("postUpdateLog does not exist. Path: " + postUpdateLog.path);
|
|
if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
|
|
do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the post update " +
|
|
"process to create the post update log. Path: " +
|
|
postUpdateLog.path);
|
|
}
|
|
do_execute_soon(checkPostUpdateAppLog);
|
|
return;
|
|
}
|
|
|
|
let logContents = readFile(postUpdateLog);
|
|
// It is possible for the log file contents check to occur before the log file
|
|
// contents are completely written so wait until the contents are the expected
|
|
// value. If the contents are never the expected value then the test will
|
|
// fail by timing out after gTimeoutRuns is greater than MAX_TIMEOUT_RUNS or
|
|
// the test harness times out the test.
|
|
if (logContents != "post-update\n") {
|
|
if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
|
|
do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the post update " +
|
|
"process to create the expected contents in the post update log. Path: " +
|
|
postUpdateLog.path);
|
|
}
|
|
do_execute_soon(checkPostUpdateAppLog);
|
|
return;
|
|
}
|
|
Assert.ok(true, "the post update log contents" + MSG_SHOULD_EQUAL);
|
|
}
|
|
|
|
do_execute_soon(checkPostUpdateAppLogFinished);
|
|
}
|
|
|
|
/**
|
|
* Helper function to check if a file is in use on Windows by making a copy of
|
|
* a file and attempting to delete the original file. If the deletion is
|
|
* successful the copy of the original file is renamed to the original file's
|
|
* name and if the deletion is not successful the copy of the original file is
|
|
* deleted.
|
|
*
|
|
* @param aFile
|
|
* An nsIFile for the file to be checked if it is in use.
|
|
* @return true if the file can't be deleted and false otherwise.
|
|
*/
|
|
function isFileInUse(aFile) {
|
|
if (!IS_WIN) {
|
|
do_throw("Windows only function called by a different platform!");
|
|
}
|
|
|
|
if (!aFile.exists()) {
|
|
debugDump("file does not exist, path: " + aFile.path);
|
|
return false;
|
|
}
|
|
|
|
let fileBak = aFile.parent;
|
|
fileBak.append(aFile.leafName + ".bak");
|
|
try {
|
|
if (fileBak.exists()) {
|
|
fileBak.remove(false);
|
|
}
|
|
aFile.copyTo(aFile.parent, fileBak.leafName);
|
|
aFile.remove(false);
|
|
fileBak.moveTo(aFile.parent, aFile.leafName);
|
|
debugDump("file is not in use, path: " + aFile.path);
|
|
return false;
|
|
} catch (e) {
|
|
debugDump("file in use, path: " + aFile.path + ", exception: " + e);
|
|
try {
|
|
if (fileBak.exists()) {
|
|
fileBak.remove(false);
|
|
}
|
|
} catch (ex) {
|
|
logTestInfo("unable to remove backup file, path: " +
|
|
fileBak.path + ", exception: " + ex);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Waits until files that are in use that break tests are no longer in use and
|
|
* then calls doTestFinish to end the test.
|
|
*/
|
|
function waitForFilesInUse() {
|
|
if (IS_WIN) {
|
|
let fileNames = [FILE_APP_BIN, FILE_UPDATER_BIN,
|
|
FILE_MAINTENANCE_SERVICE_INSTALLER_BIN];
|
|
for (let i = 0; i < fileNames.length; ++i) {
|
|
let file = getApplyDirFile(fileNames[i], true);
|
|
if (isFileInUse(file)) {
|
|
do_timeout(FILE_IN_USE_TIMEOUT_MS, waitForFilesInUse);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
debugDump("calling doTestFinish");
|
|
doTestFinish();
|
|
}
|
|
|
|
/**
|
|
* Helper function for updater binary tests for verifying there are no update
|
|
* backup files left behind after an update.
|
|
*
|
|
* @param aFile
|
|
* An nsIFile to check if it has moz-backup for its extension.
|
|
*/
|
|
function checkForBackupFiles(aFile) {
|
|
Assert.notEqual(getFileExtension(aFile), "moz-backup",
|
|
"the file's extension should not equal moz-backup" +
|
|
getMsgPath(aFile.path));
|
|
}
|
|
|
|
/**
|
|
* Helper function for updater binary tests for recursively enumerating a
|
|
* directory and calling a callback function with the file as a parameter for
|
|
* each file found.
|
|
*
|
|
* @param aDir
|
|
* A nsIFile for the directory to be deleted
|
|
* @param aCallback
|
|
* A callback function that will be called with the file as a
|
|
* parameter for each file found.
|
|
*/
|
|
function checkFilesInDirRecursive(aDir, aCallback) {
|
|
if (!aDir.exists()) {
|
|
do_throw("Directory must exist!");
|
|
}
|
|
|
|
let dirEntries = aDir.directoryEntries;
|
|
while (dirEntries.hasMoreElements()) {
|
|
let entry = dirEntries.getNext().QueryInterface(Ci.nsIFile);
|
|
|
|
if (entry.exists()) {
|
|
if (entry.isDirectory()) {
|
|
checkFilesInDirRecursive(entry, aCallback);
|
|
} else {
|
|
aCallback(entry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Helper function to override the update prompt component to verify whether it
|
|
* is called or not.
|
|
*
|
|
* @param aCallback
|
|
* The callback to call if the update prompt component is called.
|
|
*/
|
|
function overrideUpdatePrompt(aCallback) {
|
|
Cu.import("resource://testing-common/MockRegistrar.jsm");
|
|
MockRegistrar.register("@mozilla.org/updates/update-prompt;1", UpdatePrompt, [aCallback]);
|
|
}
|
|
|
|
function UpdatePrompt(aCallback) {
|
|
this._callback = aCallback;
|
|
|
|
let fns = ["checkForUpdates", "showUpdateAvailable", "showUpdateDownloaded",
|
|
"showUpdateError", "showUpdateHistory", "showUpdateInstalled"];
|
|
|
|
fns.forEach(function UP_fns(aPromptFn) {
|
|
UpdatePrompt.prototype[aPromptFn] = function() {
|
|
if (!this._callback) {
|
|
return;
|
|
}
|
|
|
|
let callback = this._callback[aPromptFn];
|
|
if (!callback) {
|
|
return;
|
|
}
|
|
|
|
callback.apply(this._callback,
|
|
Array.prototype.slice.call(arguments));
|
|
};
|
|
});
|
|
}
|
|
|
|
UpdatePrompt.prototype = {
|
|
flags: Ci.nsIClassInfo.SINGLETON,
|
|
getScriptableHelper: () => null,
|
|
getInterfaces: function(aCount) {
|
|
let interfaces = [Ci.nsISupports, Ci.nsIUpdatePrompt];
|
|
aCount.value = interfaces.length;
|
|
return interfaces;
|
|
},
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIClassInfo, Ci.nsIUpdatePrompt])
|
|
};
|
|
|
|
/* Update check listener */
|
|
const updateCheckListener = {
|
|
onProgress: function UCL_onProgress(aRequest, aPosition, aTotalSize) {
|
|
},
|
|
|
|
onCheckComplete: function UCL_onCheckComplete(aRequest, aUpdates, aUpdateCount) {
|
|
gRequestURL = aRequest.channel.originalURI.spec;
|
|
gUpdateCount = aUpdateCount;
|
|
gUpdates = aUpdates;
|
|
debugDump("url = " + gRequestURL + ", " +
|
|
"request.status = " + aRequest.status + ", " +
|
|
"updateCount = " + aUpdateCount);
|
|
// Use a timeout to allow the XHR to complete
|
|
do_execute_soon(gCheckFunc);
|
|
},
|
|
|
|
onError: function UCL_onError(aRequest, aUpdate) {
|
|
gRequestURL = aRequest.channel.originalURI.spec;
|
|
gStatusCode = aRequest.status;
|
|
if (gStatusCode == 0) {
|
|
gStatusCode = aRequest.channel.QueryInterface(Ci.nsIRequest).status;
|
|
}
|
|
gStatusText = aUpdate.statusText ? aUpdate.statusText : null;
|
|
debugDump("url = " + gRequestURL + ", " +
|
|
"request.status = " + gStatusCode + ", " +
|
|
"update.statusText = " + gStatusText);
|
|
// Use a timeout to allow the XHR to complete
|
|
do_execute_soon(gCheckFunc.bind(null, aRequest, aUpdate));
|
|
},
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateCheckListener])
|
|
};
|
|
|
|
/* Update download listener - nsIRequestObserver */
|
|
const downloadListener = {
|
|
onStartRequest: function DL_onStartRequest(aRequest, aContext) {
|
|
},
|
|
|
|
onProgress: function DL_onProgress(aRequest, aContext, aProgress, aMaxProgress) {
|
|
},
|
|
|
|
onStatus: function DL_onStatus(aRequest, aContext, aStatus, aStatusText) {
|
|
},
|
|
|
|
onStopRequest: function DL_onStopRequest(aRequest, aContext, aStatus) {
|
|
gStatusResult = aStatus;
|
|
// Use a timeout to allow the request to complete
|
|
do_execute_soon(gCheckFunc);
|
|
},
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
|
|
Ci.nsIProgressEventSink])
|
|
};
|
|
|
|
/**
|
|
* Helper for starting the http server used by the tests
|
|
*/
|
|
function start_httpserver() {
|
|
let dir = getTestDirFile();
|
|
debugDump("http server directory path: " + dir.path);
|
|
|
|
if (!dir.isDirectory()) {
|
|
do_throw("A file instead of a directory was specified for HttpServer " +
|
|
"registerDirectory! Path: " + dir.path);
|
|
}
|
|
|
|
let { HttpServer } = Cu.import("resource://testing-common/httpd.js", {});
|
|
gTestserver = new HttpServer();
|
|
gTestserver.registerDirectory("/", dir);
|
|
gTestserver.registerPathHandler("/" + gHTTPHandlerPath, pathHandler);
|
|
gTestserver.start(-1);
|
|
let testserverPort = gTestserver.identity.primaryPort;
|
|
gURLData = URL_HOST + ":" + testserverPort + "/";
|
|
debugDump("http server port = " + testserverPort);
|
|
}
|
|
|
|
/**
|
|
* Custom path handler for the http server
|
|
*
|
|
* @param aMetadata
|
|
* The http metadata for the request.
|
|
* @param aResponse
|
|
* The http response for the request.
|
|
*/
|
|
function pathHandler(aMetadata, aResponse) {
|
|
aResponse.setHeader("Content-Type", "text/xml", false);
|
|
aResponse.setStatusLine(aMetadata.httpVersion, gResponseStatusCode, "OK");
|
|
aResponse.bodyOutputStream.write(gResponseBody, gResponseBody.length);
|
|
}
|
|
|
|
/**
|
|
* Helper for stopping the http server used by the tests
|
|
*
|
|
* @param aCallback
|
|
* The callback to call after stopping the http server.
|
|
*/
|
|
function stop_httpserver(aCallback) {
|
|
Assert.ok(!!aCallback, "the aCallback parameter should be defined");
|
|
gTestserver.stop(aCallback);
|
|
}
|
|
|
|
/**
|
|
* Creates an nsIXULAppInfo
|
|
*
|
|
* @param aID
|
|
* The ID of the test application
|
|
* @param aName
|
|
* A name for the test application
|
|
* @param aVersion
|
|
* The version of the application
|
|
* @param aPlatformVersion
|
|
* The gecko version of the application
|
|
*/
|
|
function createAppInfo(aID, aName, aVersion, aPlatformVersion) {
|
|
const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1";
|
|
const XULAPPINFO_CID = Components.ID("{c763b610-9d49-455a-bbd2-ede71682a1ac}");
|
|
let ifaces = [Ci.nsIXULAppInfo, Ci.nsIXULRuntime];
|
|
if (IS_WIN) {
|
|
ifaces.push(Ci.nsIWinAppHelper);
|
|
}
|
|
const XULAppInfo = {
|
|
vendor: APP_INFO_VENDOR,
|
|
name: aName,
|
|
ID: aID,
|
|
version: aVersion,
|
|
appBuildID: "2007010101",
|
|
platformVersion: aPlatformVersion,
|
|
platformBuildID: "2007010101",
|
|
inSafeMode: false,
|
|
logConsoleErrors: true,
|
|
OS: "XPCShell",
|
|
XPCOMABI: "noarch-spidermonkey",
|
|
|
|
QueryInterface: XPCOMUtils.generateQI(ifaces)
|
|
};
|
|
|
|
const XULAppInfoFactory = {
|
|
createInstance: function(aOuter, aIID) {
|
|
if (aOuter == null) {
|
|
return XULAppInfo.QueryInterface(aIID);
|
|
}
|
|
throw Cr.NS_ERROR_NO_AGGREGATION;
|
|
}
|
|
};
|
|
|
|
let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
|
|
registrar.registerFactory(XULAPPINFO_CID, "XULAppInfo",
|
|
XULAPPINFO_CONTRACTID, XULAppInfoFactory);
|
|
}
|
|
|
|
/**
|
|
* Returns the platform specific arguments used by nsIProcess when launching
|
|
* the application.
|
|
*
|
|
* @param aExtraArgs (optional)
|
|
* An array of extra arguments to append to the default arguments.
|
|
* @return an array of arguments to be passed to nsIProcess.
|
|
*
|
|
* Note: a shell is necessary to pipe the application's console output which
|
|
* would otherwise pollute the xpcshell log.
|
|
*
|
|
* Command line arguments used when launching the application:
|
|
* -no-remote prevents shell integration from being affected by an existing
|
|
* application process.
|
|
* -test-process-updates makes the application exit after being relaunched by
|
|
* the updater.
|
|
* the platform specific string defined by PIPE_TO_NULL to output both stdout
|
|
* and stderr to null. This is needed to prevent output from the application
|
|
* from ending up in the xpchsell log.
|
|
*/
|
|
function getProcessArgs(aExtraArgs) {
|
|
if (!aExtraArgs) {
|
|
aExtraArgs = [];
|
|
}
|
|
|
|
let appBinPath = getApplyDirFile(DIR_MACOS + FILE_APP_BIN, false).path;
|
|
if (/ /.test(appBinPath)) {
|
|
appBinPath = '"' + appBinPath + '"';
|
|
}
|
|
|
|
let args;
|
|
if (IS_UNIX) {
|
|
let launchScript = getLaunchScript();
|
|
// Precreate the script with executable permissions
|
|
launchScript.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_DIRECTORY);
|
|
|
|
let scriptContents = "#! /bin/sh\n";
|
|
scriptContents += appBinPath + " -no-remote -test-process-updates " +
|
|
aExtraArgs.join(" ") + " " + PIPE_TO_NULL;
|
|
writeFile(launchScript, scriptContents);
|
|
debugDump("created " + launchScript.path + " containing:\n" +
|
|
scriptContents);
|
|
args = [launchScript.path];
|
|
} else {
|
|
args = ["/D", "/Q", "/C", appBinPath, "-no-remote", "-test-process-updates"].
|
|
concat(aExtraArgs).concat([PIPE_TO_NULL]);
|
|
}
|
|
return args;
|
|
}
|
|
|
|
/**
|
|
* Gets a file path for the application to dump its arguments into. This is used
|
|
* to verify that a callback application is launched.
|
|
*
|
|
* @return the file for the application to dump its arguments into.
|
|
*/
|
|
function getAppArgsLogPath() {
|
|
let appArgsLog = do_get_file("/" + gTestID + "_app_args_log", true);
|
|
if (appArgsLog.exists()) {
|
|
appArgsLog.remove(false);
|
|
}
|
|
let appArgsLogPath = appArgsLog.path;
|
|
if (/ /.test(appArgsLogPath)) {
|
|
appArgsLogPath = '"' + appArgsLogPath + '"';
|
|
}
|
|
return appArgsLogPath;
|
|
}
|
|
|
|
/**
|
|
* Gets the nsIFile reference for the shell script to launch the application. If
|
|
* the file exists it will be removed by this function.
|
|
*
|
|
* @return the nsIFile for the shell script to launch the application.
|
|
*/
|
|
function getLaunchScript() {
|
|
let launchScript = do_get_file("/" + gTestID + "_launch.sh", true);
|
|
if (launchScript.exists()) {
|
|
launchScript.remove(false);
|
|
}
|
|
return launchScript;
|
|
}
|
|
|
|
/**
|
|
* Makes GreD, XREExeF, and UpdRootD point to unique file system locations so
|
|
* xpcshell tests can run in parallel and to keep the environment clean.
|
|
*/
|
|
function adjustGeneralPaths() {
|
|
let dirProvider = {
|
|
getFile: function AGP_DP_getFile(aProp, aPersistent) {
|
|
aPersistent.value = true;
|
|
switch (aProp) {
|
|
case NS_GRE_DIR:
|
|
if (gUseTestAppDir) {
|
|
return getApplyDirFile(DIR_RESOURCES, true);
|
|
}
|
|
break;
|
|
case NS_GRE_BIN_DIR:
|
|
if (gUseTestAppDir) {
|
|
return getApplyDirFile(DIR_MACOS, true);
|
|
}
|
|
break;
|
|
case XRE_EXECUTABLE_FILE:
|
|
if (gUseTestAppDir) {
|
|
return getApplyDirFile(DIR_MACOS + FILE_APP_BIN, true);
|
|
}
|
|
break;
|
|
case XRE_UPDATE_ROOT_DIR:
|
|
return getMockUpdRootD();
|
|
}
|
|
return null;
|
|
},
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider])
|
|
};
|
|
let ds = Services.dirsvc.QueryInterface(Ci.nsIDirectoryService);
|
|
ds.QueryInterface(Ci.nsIProperties).undefine(NS_GRE_DIR);
|
|
ds.QueryInterface(Ci.nsIProperties).undefine(NS_GRE_BIN_DIR);
|
|
ds.QueryInterface(Ci.nsIProperties).undefine(XRE_EXECUTABLE_FILE);
|
|
ds.registerProvider(dirProvider);
|
|
do_register_cleanup(function AGP_cleanup() {
|
|
debugDump("start - unregistering directory provider");
|
|
|
|
if (gAppTimer) {
|
|
debugDump("start - cancel app timer");
|
|
gAppTimer.cancel();
|
|
gAppTimer = null;
|
|
debugDump("finish - cancel app timer");
|
|
}
|
|
|
|
if (gProcess && gProcess.isRunning) {
|
|
debugDump("start - kill process");
|
|
try {
|
|
gProcess.kill();
|
|
} catch (e) {
|
|
debugDump("kill process failed. Exception: " + e);
|
|
}
|
|
gProcess = null;
|
|
debugDump("finish - kill process");
|
|
}
|
|
|
|
if (gHandle) {
|
|
try {
|
|
debugDump("start - closing handle");
|
|
let kernel32 = ctypes.open("kernel32");
|
|
let CloseHandle = kernel32.declare("CloseHandle", ctypes.default_abi,
|
|
ctypes.bool, /* return*/
|
|
ctypes.voidptr_t /* handle*/);
|
|
if (!CloseHandle(gHandle)) {
|
|
debugDump("call to CloseHandle failed");
|
|
}
|
|
kernel32.close();
|
|
gHandle = null;
|
|
debugDump("finish - closing handle");
|
|
} catch (e) {
|
|
debugDump("call to CloseHandle failed. Exception: " + e);
|
|
}
|
|
}
|
|
|
|
// Call end_test first before the directory provider is unregistered
|
|
if (typeof end_test == typeof Function) {
|
|
debugDump("calling end_test");
|
|
end_test();
|
|
}
|
|
|
|
ds.unregisterProvider(dirProvider);
|
|
cleanupTestCommon();
|
|
|
|
debugDump("finish - unregistering directory provider");
|
|
});
|
|
}
|
|
|
|
/**
|
|
* The timer callback to kill the process if it takes too long.
|
|
*/
|
|
const gAppTimerCallback = {
|
|
notify: function TC_notify(aTimer) {
|
|
gAppTimer = null;
|
|
if (gProcess.isRunning) {
|
|
logTestInfo("attempting to kill process");
|
|
gProcess.kill();
|
|
}
|
|
Assert.ok(false, "launch application timer expired");
|
|
},
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback])
|
|
};
|
|
|
|
/**
|
|
* Launches an application to apply an update.
|
|
*/
|
|
function runUpdateUsingApp(aExpectedStatus) {
|
|
/**
|
|
* The observer for the call to nsIProcess:runAsync. When completed
|
|
* runUpdateFinished will be called.
|
|
*/
|
|
const processObserver = {
|
|
observe: function PO_observe(aSubject, aTopic, aData) {
|
|
debugDump("topic: " + aTopic + ", process exitValue: " +
|
|
gProcess.exitValue);
|
|
resetEnvironment();
|
|
if (gAppTimer) {
|
|
gAppTimer.cancel();
|
|
gAppTimer = null;
|
|
}
|
|
Assert.equal(gProcess.exitValue, 0,
|
|
"the application process exit value should be 0");
|
|
Assert.equal(aTopic, "process-finished",
|
|
"the application process observer topic should be " +
|
|
"process-finished");
|
|
|
|
if (IS_SERVICE_TEST) {
|
|
waitForServiceStop(false);
|
|
}
|
|
|
|
do_execute_soon(afterAppExits);
|
|
},
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
|
|
};
|
|
|
|
function afterAppExits() {
|
|
gTimeoutRuns++;
|
|
|
|
if (IS_WIN) {
|
|
waitForApplicationStop(FILE_UPDATER_BIN);
|
|
}
|
|
|
|
let status;
|
|
try {
|
|
status = readStatusFile();
|
|
} catch (e) {
|
|
logTestInfo("error reading status file, exception: " + e);
|
|
}
|
|
// Don't proceed until the update's status is the expected value.
|
|
if (status != aExpectedStatus) {
|
|
if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
|
|
logUpdateLog(FILE_UPDATE_LOG);
|
|
do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the update " +
|
|
"status to equal: " +
|
|
aExpectedStatus +
|
|
", current status: " + status);
|
|
} else {
|
|
do_timeout(FILE_IN_USE_TIMEOUT_MS, afterAppExits);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Don't check for an update log when the code in nsUpdateDriver.cpp skips
|
|
// updating.
|
|
if (aExpectedStatus != STATE_PENDING &&
|
|
aExpectedStatus != STATE_PENDING_SVC &&
|
|
aExpectedStatus != STATE_APPLIED &&
|
|
aExpectedStatus != STATE_APPLIED_SVC) {
|
|
// Don't proceed until the update log has been created.
|
|
let log = getUpdateLog(FILE_UPDATE_LOG);
|
|
if (!log.exists()) {
|
|
if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
|
|
do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the update " +
|
|
"log to be created. Path: " + log.path);
|
|
}
|
|
do_timeout(FILE_IN_USE_TIMEOUT_MS, afterAppExits);
|
|
return;
|
|
}
|
|
}
|
|
|
|
do_execute_soon(runUpdateFinished);
|
|
}
|
|
|
|
debugDump("start - launching application to apply update");
|
|
|
|
let appBin = getApplyDirFile(DIR_MACOS + FILE_APP_BIN, false);
|
|
|
|
let launchBin = getLaunchBin();
|
|
let args = getProcessArgs();
|
|
debugDump("launching " + launchBin.path + " " + args.join(" "));
|
|
|
|
gProcess = Cc["@mozilla.org/process/util;1"].
|
|
createInstance(Ci.nsIProcess);
|
|
gProcess.init(launchBin);
|
|
|
|
gAppTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
|
gAppTimer.initWithCallback(gAppTimerCallback, APP_TIMER_TIMEOUT,
|
|
Ci.nsITimer.TYPE_ONE_SHOT);
|
|
|
|
setEnvironment();
|
|
debugDump("launching application");
|
|
gProcess.runAsync(args, args.length, processObserver);
|
|
|
|
debugDump("finish - launching application to apply update");
|
|
}
|
|
|
|
/**
|
|
* Sets the environment that will be used by the application process when it is
|
|
* launched.
|
|
*/
|
|
function setEnvironment() {
|
|
// Prevent setting the environment more than once.
|
|
if (gShouldResetEnv !== undefined) {
|
|
return;
|
|
}
|
|
|
|
gShouldResetEnv = true;
|
|
|
|
// See bug 1279108.
|
|
if (gEnv.exists("ASAN_OPTIONS")) {
|
|
gASanOptions = gEnv.get("ASAN_OPTIONS");
|
|
gEnv.set("ASAN_OPTIONS", gASanOptions + ":detect_leaks=0");
|
|
} else {
|
|
gEnv.set("ASAN_OPTIONS", "detect_leaks=0");
|
|
}
|
|
|
|
if (IS_WIN && !gEnv.exists("XRE_NO_WINDOWS_CRASH_DIALOG")) {
|
|
gAddedEnvXRENoWindowsCrashDialog = true;
|
|
debugDump("setting the XRE_NO_WINDOWS_CRASH_DIALOG environment " +
|
|
"variable to 1... previously it didn't exist");
|
|
gEnv.set("XRE_NO_WINDOWS_CRASH_DIALOG", "1");
|
|
}
|
|
|
|
if (IS_UNIX) {
|
|
let appGreBinDir = gGREBinDirOrig.clone();
|
|
let envGreBinDir = Cc["@mozilla.org/file/local;1"].
|
|
createInstance(Ci.nsILocalFile);
|
|
let shouldSetEnv = true;
|
|
if (IS_MACOSX) {
|
|
if (gEnv.exists("DYLD_LIBRARY_PATH")) {
|
|
gEnvDyldLibraryPath = gEnv.get("DYLD_LIBRARY_PATH");
|
|
envGreBinDir.initWithPath(gEnvDyldLibraryPath);
|
|
if (envGreBinDir.path == appGreBinDir.path) {
|
|
gEnvDyldLibraryPath = null;
|
|
shouldSetEnv = false;
|
|
}
|
|
}
|
|
|
|
if (shouldSetEnv) {
|
|
debugDump("setting DYLD_LIBRARY_PATH environment variable value to " +
|
|
appGreBinDir.path);
|
|
gEnv.set("DYLD_LIBRARY_PATH", appGreBinDir.path);
|
|
}
|
|
} else {
|
|
if (gEnv.exists("LD_LIBRARY_PATH")) {
|
|
gEnvLdLibraryPath = gEnv.get("LD_LIBRARY_PATH");
|
|
envGreBinDir.initWithPath(gEnvLdLibraryPath);
|
|
if (envGreBinDir.path == appGreBinDir.path) {
|
|
gEnvLdLibraryPath = null;
|
|
shouldSetEnv = false;
|
|
}
|
|
}
|
|
|
|
if (shouldSetEnv) {
|
|
debugDump("setting LD_LIBRARY_PATH environment variable value to " +
|
|
appGreBinDir.path);
|
|
gEnv.set("LD_LIBRARY_PATH", appGreBinDir.path);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gEnv.exists("XPCOM_MEM_LEAK_LOG")) {
|
|
gEnvXPCOMMemLeakLog = gEnv.get("XPCOM_MEM_LEAK_LOG");
|
|
debugDump("removing the XPCOM_MEM_LEAK_LOG environment variable... " +
|
|
"previous value " + gEnvXPCOMMemLeakLog);
|
|
gEnv.set("XPCOM_MEM_LEAK_LOG", "");
|
|
}
|
|
|
|
if (gEnv.exists("XPCOM_DEBUG_BREAK")) {
|
|
gEnvXPCOMDebugBreak = gEnv.get("XPCOM_DEBUG_BREAK");
|
|
debugDump("setting the XPCOM_DEBUG_BREAK environment variable to " +
|
|
"warn... previous value " + gEnvXPCOMDebugBreak);
|
|
} else {
|
|
debugDump("setting the XPCOM_DEBUG_BREAK environment variable to " +
|
|
"warn... previously it didn't exist");
|
|
}
|
|
|
|
gEnv.set("XPCOM_DEBUG_BREAK", "warn");
|
|
|
|
if (IS_SERVICE_TEST) {
|
|
debugDump("setting MOZ_NO_SERVICE_FALLBACK environment variable to 1");
|
|
gEnv.set("MOZ_NO_SERVICE_FALLBACK", "1");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the environment back to the original values after launching the
|
|
* application.
|
|
*/
|
|
function resetEnvironment() {
|
|
// Prevent resetting the environment more than once.
|
|
if (gShouldResetEnv !== true) {
|
|
return;
|
|
}
|
|
|
|
gShouldResetEnv = false;
|
|
|
|
// Restore previous ASAN_OPTIONS if there were any.
|
|
gEnv.set("ASAN_OPTIONS", gASanOptions ? gASanOptions : "");
|
|
|
|
if (gEnvXPCOMMemLeakLog) {
|
|
debugDump("setting the XPCOM_MEM_LEAK_LOG environment variable back to " +
|
|
gEnvXPCOMMemLeakLog);
|
|
gEnv.set("XPCOM_MEM_LEAK_LOG", gEnvXPCOMMemLeakLog);
|
|
}
|
|
|
|
if (gEnvXPCOMDebugBreak) {
|
|
debugDump("setting the XPCOM_DEBUG_BREAK environment variable back to " +
|
|
gEnvXPCOMDebugBreak);
|
|
gEnv.set("XPCOM_DEBUG_BREAK", gEnvXPCOMDebugBreak);
|
|
} else if (gEnv.exists("XPCOM_DEBUG_BREAK")) {
|
|
debugDump("clearing the XPCOM_DEBUG_BREAK environment variable");
|
|
gEnv.set("XPCOM_DEBUG_BREAK", "");
|
|
}
|
|
|
|
if (IS_UNIX) {
|
|
if (IS_MACOSX) {
|
|
if (gEnvDyldLibraryPath) {
|
|
debugDump("setting DYLD_LIBRARY_PATH environment variable value " +
|
|
"back to " + gEnvDyldLibraryPath);
|
|
gEnv.set("DYLD_LIBRARY_PATH", gEnvDyldLibraryPath);
|
|
} else if (gEnvDyldLibraryPath !== null) {
|
|
debugDump("removing DYLD_LIBRARY_PATH environment variable");
|
|
gEnv.set("DYLD_LIBRARY_PATH", "");
|
|
}
|
|
} else if (gEnvLdLibraryPath) {
|
|
debugDump("setting LD_LIBRARY_PATH environment variable value back " +
|
|
"to " + gEnvLdLibraryPath);
|
|
gEnv.set("LD_LIBRARY_PATH", gEnvLdLibraryPath);
|
|
} else if (gEnvLdLibraryPath !== null) {
|
|
debugDump("removing LD_LIBRARY_PATH environment variable");
|
|
gEnv.set("LD_LIBRARY_PATH", "");
|
|
}
|
|
}
|
|
|
|
if (IS_WIN && gAddedEnvXRENoWindowsCrashDialog) {
|
|
debugDump("removing the XRE_NO_WINDOWS_CRASH_DIALOG environment " +
|
|
"variable");
|
|
gEnv.set("XRE_NO_WINDOWS_CRASH_DIALOG", "");
|
|
}
|
|
|
|
if (IS_SERVICE_TEST) {
|
|
debugDump("removing MOZ_NO_SERVICE_FALLBACK environment variable");
|
|
gEnv.set("MOZ_NO_SERVICE_FALLBACK", "");
|
|
}
|
|
}
|