/* 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.
* "!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";
const EXIT_CODE_BASE = ChromeUtils.importESModule(
"resource://gre/modules/BackgroundTasksManager.sys.mjs"
).EXIT_CODE;
const { AppConstants } = ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
const { Subprocess } = ChromeUtils.importESModule(
"resource://gre/modules/Subprocess.sys.mjs"
);
const { TestUtils } = ChromeUtils.importESModule(
"resource://testing-common/TestUtils.sys.mjs"
);
ChromeUtils.defineESModuleGetters(
this, {
MockRegistrar:
"resource://testing-common/MockRegistrar.sys.mjs",
updateAppInfo:
"resource://testing-common/AppInfo.sys.mjs",
});
const Cm = Components.manager;
/* global MOZ_APP_VENDOR, MOZ_APP_BASENAME */
/* global MOZ_VERIFY_MAR_SIGNATURE, IS_AUTHENTICODE_CHECK_ENABLED */
load(
"../data/xpcshellConstantsPP.js");
// Note: DIR_CONTENTS, DIR_MACOS and DIR_RESOURCES only differ on macOS. They
// default to "" on all other platforms.
const DIR_CONTENTS = AppConstants.platform ==
"macosx" ?
"Contents/" :
"";
const DIR_MACOS =
AppConstants.platform ==
"macosx" ? DIR_CONTENTS +
"MacOS/" :
"";
const DIR_RESOURCES =
AppConstants.platform ==
"macosx" ? DIR_CONTENTS +
"Resources/" :
"";
const TEST_FILE_SUFFIX = AppConstants.platform ==
"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 =
"_" + mozinfo.os;
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 MAC_APP_XATTR_KEY =
"com.apple.application-instance";
const MAC_APP_XATTR_VALUE =
"dlsource%3Dmozillaci";
const USE_EXECV = AppConstants.platform ==
"linux";
const URL_HOST =
"http://localhost";
const APP_INFO_NAME =
"XPCShell";
const APP_INFO_VENDOR =
"Mozilla";
const APP_BIN_SUFFIX =
AppConstants.platform ==
"linux" ?
"-bin" : mozinfo.bin_suffix;
const FILE_APP_BIN = AppConstants.MOZ_APP_NAME + APP_BIN_SUFFIX;
const FILE_COMPLETE_EXE =
"complete.exe";
const FILE_HELPER_BIN =
AppConstants.platform ==
"macosx"
?
"callback_app.app/Contents/MacOS/TestAUSHelper"
:
"TestAUSHelper" + mozinfo.bin_suffix;
const FILE_HELPER_APP =
AppConstants.platform ==
"macosx" ?
"callback_app.app" : FILE_HELPER_BIN;
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" + (AppConstants.platform ==
"macosx" ?
".app" : mozinfo.bin_suffix);
const PERFORMING_STAGED_UPDATE =
"Performing a staged update";
const CALL_QUIT =
"calling QuitProgressUI";
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 ERR_PARENT_PID_PERSISTS =
"The parent process didn't exit! Continuing with update.";
const ERR_BGTASK_EXCLUSIVE =
"failed to exclusively open executable file from background task: ";
const LOG_SVC_SUCCESSFUL_LAUNCH =
"Process was started... waiting on result.";
const LOG_SVC_UNSUCCESSFUL_LAUNCH =
"The install directory path is not valid for this application.";
// 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";
const CONTINUE_CHECK =
"continueCheck";
const CONTINUE_DOWNLOAD =
"continueDownload";
const CONTINUE_STAGING =
"continueStaging";
// 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;
// How many of do_timeout calls using FILE_IN_USE_TIMEOUT_MS to wait before the
// test is aborted.
const FILE_IN_USE_TIMEOUT_MS = 1000;
const PIPE_TO_NULL =
AppConstants.platform ==
"win" ?
">nul" :
"> /dev/null 2>&1";
const LOG_FUNCTION = info;
const gHTTPHandlerPath =
"updates.xml";
var gIsServiceTest;
var gTestID;
// This default value will be overridden when using the http server.
var gURLData = URL_HOST +
"/";
var gTestserver;
var gUpdateCheckCount = 0;
const REL_PATH_DATA =
"";
const APP_UPDATE_SJS_HOST =
"http://127.0.0.1";
const APP_UPDATE_SJS_PATH =
"/" + REL_PATH_DATA +
"app_update.sjs";
var gIncrementalDownloadErrorType;
var gResponseBody;
var gProcess;
var gAppTimer;
var gHandle;
var gGREDirOrig;
var gGREBinDirOrig;
var gPIDPersistProcess;
// Variables are used instead of contants so tests can override these values if
// necessary.
var gCallbackArgs = [
"./",
"callback.log",
"Test Arg 2",
"Test Arg 3"];
var gCallbackApp = (() => {
if (AppConstants.platform ==
"macosx") {
return "callback_app.app";
}
return "callback_app" + mozinfo.bin_suffix;
})();
var gCallbackBinFile = (() => {
if (AppConstants.platform ==
"macosx") {
return FILE_HELPER_BIN;
}
return "callback_app" + mozinfo.bin_suffix;
})();
var gPostUpdateBinFile =
"postup_app" + mozinfo.bin_suffix;
var gTimeoutRuns = 0;
// Environment related globals
var gShouldResetEnv = undefined;
var gAddedEnvXRENoWindowsCrashDialog =
false;
var gCrashReporterDisabled;
var gEnvXPCOMDebugBreak;
var gEnvXPCOMMemLeakLog;
var gEnvForceServiceFallback =
false;
const URL_HTTP_UPDATE_SJS =
"http://test_details/";
const DATA_URI_SPEC = Services.io.newFileURI(do_get_file(
"",
false)).spec;
/* import-globals-from shared.js */
load(
"shared.js");
// Set to true to log additional information for debugging. To log additional
// information for individual tests set gDebugTest to false here and to true in
// the test's onload function.
gDebugTest =
true;
// Setting gDebugTestLog to true will create log files for the tests in
// <objdir>/_tests/xpcshell/toolkit/mozapps/update/tests/<testdir>/ except for
// the service tests since they run sequentially. This can help when debugging
// failures for the tests that intermittently fail when they run in parallel.
// Never set gDebugTestLog to true except when running tests locally.
var gDebugTestLog =
false;
// An empty array for gTestsToLog will log most of the output of all of the
// update tests except for the service tests. To only log specific tests add the
// test file name without the file extension to the array below.
var gTestsToLog = [];
var gRealDump;
var gFOS;
var gUpdateBin;
var gTestFiles = [];
var gTestDirs = [];
// Common files for both successful and failed updates.
var gTestFilesCommon = [
{
description:
"Should never change",
fileName: FILE_CHANNEL_PREFS,
relPathDir:
AppConstants.platform ==
"macosx"
?
"Contents/Frameworks/ChannelPrefs.framework/"
: DIR_RESOURCES +
"defaults/pref/",
originalContents:
"ShouldNotBeReplaced\n",
compareContents:
"ShouldNotBeReplaced\n",
originalFile:
null,
compareFile:
null,
originalPerms: 0o767,
comparePerms: 0o767,
},
];
var gTestFilesCommonNonMac = [
{
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,
},
];
if (AppConstants.platform !=
"macosx") {
gTestFilesCommon = gTestFilesCommon.concat(gTestFilesCommonNonMac);
}
var gTestFilesCommonMac = [
{
description:
"Should never change",
fileName: FILE_UPDATE_SETTINGS_FRAMEWORK,
relPathDir:
DIR_MACOS +
"updater.app/Contents/Frameworks/UpdateSettings.framework/",
originalContents:
null,
compareContents:
null,
originalFile:
null,
compareFile:
null,
originalPerms:
null,
comparePerms:
null,
existingFile:
true,
},
{
description:
"Should never change",
fileName: FILE_INFO_PLIST,
relPathDir: DIR_CONTENTS,
originalContents: DIR_APP_INFO_PLIST_FILE_CONTENTS,
compareContents: DIR_APP_INFO_PLIST_FILE_CONTENTS,
originalFile:
null,
compareFile:
null,
originalPerms:
null,
comparePerms:
null,
existingFile:
true,
},
{
description:
"Should never change",
fileName: FILE_INFO_PLIST,
relPathDir: DIR_MACOS +
"updater.app/Contents/",
originalContents:
null,
compareContents:
null,
originalFile:
null,
compareFile:
null,
originalPerms:
null,
comparePerms:
null,
existingFile:
true,
},
{
description:
"Should never change",
fileName: FILE_INFO_PLIST,
relPathDir: DIR_MACOS +
"callback_app.app/Contents/",
originalContents:
null,
compareContents:
null,
originalFile:
null,
compareFile:
null,
originalPerms:
null,
comparePerms:
null,
existingFile:
true,
},
];
if (AppConstants.platform ==
"macosx") {
gTestFilesCommon = gTestFilesCommon.concat(gTestFilesCommonMac);
}
// 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);
/**
* Searches `gTestFiles` for the file with the given filename. This is currently
* not very efficient (it searches the whole array every time).
*
* @param filename
* The name of the file to search for (i.e. the `fileName` attribute).
* @returns
* The object in `gTestFiles` that describes the requested file.
* Or `null`, if the file is not in `gTestFiles`.
*/
function getTestFileByName(filename) {
return gTestFiles.find(f => f.fileName == filename) ??
null;
}
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,
},
{
description:
"Silences 'WARNING: Failed to resolve XUL App Dir.' in debug builds",
relPathDir: DIR_RESOURCES +
"browser",
dirRemoved:
false,
},
];
// 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);
/**
* Helper function for setting up the test environment.
*
* @param aAppUpdateAutoEnabled
* See setAppUpdateAutoSync in shared.js for details.
* @param aAllowBits
* If true, allow update downloads via the Windows BITS service.
* If false, this download mechanism will not be used.
*/
function setupTestCommon(aAppUpdateAutoEnabled =
false, aAllowBits =
false) {
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];
if (gDebugTestLog && !gIsServiceTest) {
if (!gTestsToLog.length || gTestsToLog.includes(gTestID)) {
let logFile = do_get_file(gTestID +
".log",
true);
if (!logFile.exists()) {
logFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
}
gFOS = Cc[
"@mozilla.org/network/file-output-stream;1"].createInstance(
Ci.nsIFileOutputStream
);
gFOS.init(logFile, MODE_WRONLY | MODE_APPEND, PERMS_FILE, 0);
gRealDump = dump;
dump = dumpOverride;
}
}
createAppInfo(
"xpcshell@tests.mozilla.org", APP_INFO_NAME,
"1.0",
"2.0");
if (gIsServiceTest && !shouldRunServiceTest()) {
return false;
}
do_test_pending();
setDefaultPrefs();
gGREDirOrig = getGREDir();
gGREBinDirOrig = getGREBinDir();
let applyDir = getApplyDirFile().parent;
// Try to remove the directory used to apply updates and the updates directory
// on platforms other than Windows. This is non-fatal for the test since if
// this fails a different directory will be used.
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 (AppConstants.platform ==
"win") {
Services.prefs.setBoolPref(
PREF_APP_UPDATE_SERVICE_ENABLED,
!!gIsServiceTest
);
}
if (gIsServiceTest) {
let exts = [
"id",
"log",
"status"];
for (let i = 0; i < exts.length; ++i) {
let file = getSecureOutputFile(exts[i]);
if (file.exists()) {
try {
file.remove(
false);
}
catch (e) {}
}
}
}
adjustGeneralPaths();
createWorldWritableAppUpdateDir();
// 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.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
}
// The name of the update lock needs to be changed to match the path
// overridden in adjustGeneralPaths() above. Wait until now to reset
// because the GRE dir now exists, which may cause the "install
// path" to be normalized differently now that it can be resolved.
debugDump(
"resetting update lock");
resetSyncManagerLock();
// Remove the updates directory on Windows and macOS which is located
// outside of the application directory after the call to adjustGeneralPaths
// has set it up. Since the test hasn't run yet, the directory shouldn't
// exist and failure to remove the directory should be non-fatal for the test.
if (AppConstants.platform ==
"win" || AppConstants.platform ==
"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
);
}
}
}
setAppUpdateAutoSync(aAppUpdateAutoEnabled);
Services.prefs.setBoolPref(PREF_APP_UPDATE_BITS_ENABLED, aAllowBits);
debugDump(
"finish - general test setup");
return true;
}
/**
* Cleans up all the files we may have created by simulating an update.
*/
function cleanupUpdateFiles() {
if (AppConstants.platform ==
"macosx" || AppConstants.platform ==
"linux") {
// This will delete the launch script if it exists.
getLaunchScript();
}
if (gIsServiceTest) {
let exts = [
"id",
"log",
"status"];
for (let i = 0; i < exts.length; ++i) {
let file = getSecureOutputFile(exts[i]);
if (file.exists()) {
try {
file.remove(
false);
}
catch (e) {}
}
}
}
// The updates directory is located outside of the application directory and
// needs to be removed on Windows and Mac OS X.
if (AppConstants.platform ==
"win" || AppConstants.platform ==
"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 (AppConstants.platform ==
"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().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
);
}
}
// We just deleted this.
gUpdateBin =
null;
}
/**
* 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");
if (gChannel) {
gPrefRoot.removeObserver(PREF_APP_UPDATE_CHANNEL, observer);
}
gTestserver =
null;
if (AppConstants.platform ==
"win" && MOZ_APP_BASENAME) {
let appDir = getApplyDirFile();
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) {}
}
cleanupUpdateFiles();
resetEnvironment();
Services.prefs.clearUserPref(PREF_APP_UPDATE_BITS_ENABLED);
debugDump(
"finish - general test cleanup");
if (gRealDump) {
dump = gRealDump;
gRealDump =
null;
}
if (gFOS) {
gFOS.close();
}
}
/**
* Helper function to store the log output of calls to dump in a variable so the
* values can be written to a file for a parallel run of a test and printed to
* the log file when the test runs synchronously.
*/
function dumpOverride(aText) {
gFOS.write(aText, aText.length);
gRealDump(aText);
}
/**
* 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.
*/
async
function doTestFinish() {
if (gDebugTest) {
// 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);
}
await reloadUpdateManagerData(
true);
// Call app update's observe method passing quit-application 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,
"quit-application",
"");
executeSoon(do_test_finished);
}
/**
* Sets the most commonly used preferences used by tests
*/
function setDefaultPrefs() {
Services.prefs.setBoolPref(PREF_APP_UPDATE_DISABLEDFORTESTING,
false);
if (gDebugTest) {
// 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);
}
}
/**
* 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.includes(
"distribution/");
});
gTestDirs = gTestDirs.filter(
function (aTestDir) {
return !aTestDir.relPathDir.includes(
"distribution/");
});
}
/**
* 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 (AppConstants.platform !=
"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 (AppConstants.platform !=
"macosx") {
return;
}
// 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;
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"
);
}
/**
* Performs Update Manager checks to verify that the update metadata is correct
* and that it is the same after the update xml files are reloaded.
*
* @param aStatusFileState
* The expected state of the status file.
* @param aHasActiveUpdate
* Should there be an active update.
* @param aUpdateStatusState
* The expected update's status state.
* @param aUpdateErrCode
* The expected update's error code.
* @param aUpdateCount
* The update history's update count.
*/
async
function checkUpdateManager(
aStatusFileState,
aHasActiveUpdate,
aUpdateStatusState,
aUpdateErrCode,
aUpdateCount
) {
let activeUpdate = await (aUpdateStatusState == STATE_DOWNLOADING
? gUpdateManager.getDownloadingUpdate()
: gUpdateManager.getReadyUpdate());
Assert.equal(
readStatusState(),
aStatusFileState,
"the status file state" + MSG_SHOULD_EQUAL
);
let msgTags = [
" after startup ",
" after a file reload "];
for (let i = 0; i < msgTags.length; ++i) {
logTestInfo(
"checking Update Manager updates" + msgTags[i] +
"is performed"
);
if (aHasActiveUpdate) {
Assert.ok(
!!activeUpdate,
msgTags[i] +
"the active update should be defined"
);
}
else {
Assert.ok(
!activeUpdate,
msgTags[i] +
"the active update should not be defined"
);
}
const history = await gUpdateManager.getHistory();
Assert.equal(
history.length,
aUpdateCount,
msgTags[i] +
"the update manager updateCount attribute" + MSG_SHOULD_EQUAL
);
if (aUpdateCount > 0) {
let update = history[0];
Assert.equal(
update.state,
aUpdateStatusState,
msgTags[i] +
"the first update state" + MSG_SHOULD_EQUAL
);
Assert.equal(
update.errorCode,
aUpdateErrCode,
msgTags[i] +
"the first update errorCode" + MSG_SHOULD_EQUAL
);
}
if (i != msgTags.length - 1) {
reloadUpdateManagerData();
}
}
}
/**
* Waits until the update files exist or not based on the parameters specified
* when calling this function or the default values if the parameters are not
* specified. This is necessary due to the update xml files being written
* asynchronously by nsIUpdateManager.
*
* @param aActiveUpdateExists (optional)
* Whether the active-update.xml file should exist (default is false).
* @param aUpdatesExists (optional)
* Whether the updates.xml file should exist (default is true).
*/
async
function waitForUpdateXMLFiles(
aActiveUpdateExists =
false,
aUpdatesExists =
true
) {
function areFilesStabilized() {
let file = getUpdateDirFile(FILE_ACTIVE_UPDATE_XML_TMP);
if (file.exists()) {
debugDump(
"file exists, Path: " + file.path);
return false;
}
file = getUpdateDirFile(FILE_UPDATES_XML_TMP);
if (file.exists()) {
debugDump(
"file exists, Path: " + file.path);
return false;
}
file = getUpdateDirFile(FILE_ACTIVE_UPDATE_XML);
if (file.exists() != aActiveUpdateExists) {
debugDump(
"file exists should equal: " +
aActiveUpdateExists +
", Path: " +
file.path
);
return false;
}
file = getUpdateDirFile(FILE_UPDATES_XML);
if (file.exists() != aUpdatesExists) {
debugDump(
"file exists should equal: " +
aActiveUpdateExists +
", Path: " +
file.path
);
return false;
}
return true;
}
await TestUtils.waitForCondition(
() => areFilesStabilized(),
"Waiting for update xml files to stabilize"
);
}
/**
* 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 (AppConstants.platform ==
"linux") {
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.
*/
async
function standardInit() {
// Initialize the update service stub component
await 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 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.
* Note: For service tests with IS_AUTHENTICODE_CHECK_ENABLED we use an absolute
* path inside Program Files because the service itself will refuse to
* update an installation not located in Program Files.
*
* @return The path to the directory where application binary is located.
*/
function getApplyDirPath() {
if (gIsServiceTest && IS_AUTHENTICODE_CHECK_ENABLED) {
let dir = getMaintSvcDir();
dir.append(gTestID);
dir.append(
"dir.app");
return dir.path;
}
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 macOS 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.
* @return The nsIFile for the file in the directory where the update will be
* applied.
*/
function getApplyDirFile(aRelPath) {
// do_get_file only supports relative paths, but under these conditions we
// need to use an absolute path in Program Files instead.
if (gIsServiceTest && IS_AUTHENTICODE_CHECK_ENABLED) {
let file = Cc[
"@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.initWithPath(getApplyDirPath());
if (aRelPath) {
if (aRelPath ==
"..") {
file = file.parent;
}
else {
aRelPath = aRelPath.replace(/\
//g, "\\");
file.appendRelativePath(aRelPath);
}
}
return file;
}
let relpath = getApplyDirPath() + (aRelPath ? aRelPath :
"");
return do_get_file(relpath,
true);
}
/**
* 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.
* @throws If called from a platform other than Windows.
*/
function getMaintSvcDir() {
if (AppConstants.platform !=
"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;
}
/**
* Reads the current update operation/state in the status file in the secure
* update log directory.
*
* @return The status value.
*/
function readSecureStatusFile() {
let file = getSecureOutputFile(
"status");
if (!file.exists()) {
debugDump(
"update status file does not exist, path: " + file.path);
return STATE_NONE;
}
return readFile(file).split(
"\n")[0];
}
/**
* Get an nsIFile for a file in the secure update log directory. The file name
* is always the value of gTestID and the file extension is specified by the
* aFileExt parameter.
*
* @param aFileExt
* The file extension.
* @return The nsIFile of the secure update file.
*/
function getSecureOutputFile(aFileExt) {
let file = getMaintSvcDir();
file.append(
"UpdateLogs");
file.append(gTestID +
"." + aFileExt);
return file;
}
/**
* Get the nsIFile for a Windows special folder determined by the CSIDL
* passed.
*
* @param aCSIDL
* The CSIDL for the Windows special folder.
* @return The nsIFile for the Windows special folder.
* @throws If called from a platform other than Windows.
*/
function getSpecialFolderDir(aCSIDL) {
if (AppConstants.platform !=
"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);
if (!rv) {
do_throw(
"SHGetSpecialFolderPath failed to retrieve " +
aCSIDL +
" with Win32 error " +
ctypes.winLastError
);
}
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.nsIFile);
dir.initWithPath(path);
return dir;
}
ChromeUtils.defineLazyGetter(
this,
"gInstallDirPathHash",
function test_gIDPH() {
if (AppConstants.platform !=
"win") {
do_throw(
"Windows only function called by a different platform!");
}
if (!MOZ_APP_BASENAME) {
return null;
}
let vendor = MOZ_APP_VENDOR ? MOZ_APP_VENDOR :
"Mozilla";
let appDir = getApplyDirFile();
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 value. Registry Path: " +
REG_PATH +
", Value Name: " +
appDir.path +
", Value Data: " +
gTestID +
", Exception " +
e
);
do_throw(
"Unable to write HKLM or HKCU TaskBarIDs registry value, key path: " +
REG_PATH
);
}
return null;
}
);
ChromeUtils.defineLazyGetter(
this,
"gLocalAppDataDir",
function test_gLADD() {
if (AppConstants.platform !=
"win") {
do_throw(
"Windows only function called by a different platform!");
}
const CSIDL_LOCAL_APPDATA = 0x1c;
return getSpecialFolderDir(CSIDL_LOCAL_APPDATA);
});
ChromeUtils.defineLazyGetter(
this,
"gCommonAppDataDir",
function test_gCDD() {
if (AppConstants.platform !=
"win") {
do_throw(
"Windows only function called by a different platform!");
}
const CSIDL_COMMON_APPDATA = 0x0023;
return getSpecialFolderDir(CSIDL_COMMON_APPDATA);
});
ChromeUtils.defineLazyGetter(
this,
"gProgFilesDir",
function test_gPFD() {
if (AppConstants.platform !=
"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.
*
* The aGetOldLocation argument performs the same function that the argument
* with the same name in nsXREDirProvider::GetUpdateRootDir performs. If true,
* the old (pre-migration) update directory is returned.
*/
function getMockUpdRootD(aGetOldLocation =
false) {
if (AppConstants.platform ==
"win") {
return getMockUpdRootDWin(aGetOldLocation);
}
if (AppConstants.platform ==
"macosx") {
return getMockUpdRootDMac();
}
return getApplyDirFile(DIR_MACOS);
}
/**
* 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.
*
* @throws If called from a platform other than Windows.
*/
function getMockUpdRootDWin(aGetOldLocation) {
if (AppConstants.platform !=
"win") {
do_throw(
"Windows only function called by a different platform!");
}
let relPathUpdates =
"";
let dataDirectory = gCommonAppDataDir.clone();
if (aGetOldLocation) {
relPathUpdates +=
"Mozilla";
}
else {
relPathUpdates +=
"Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38";
}
relPathUpdates +=
"\\" + DIR_UPDATES +
"\\" + gInstallDirPathHash;
let updatesDir = Cc[
"@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
updatesDir.initWithPath(dataDirectory.path +
"\\" + relPathUpdates);
return updatesDir;
}
function createWorldWritableAppUpdateDir() {
// This function is only necessary in Windows
if (AppConstants.platform ==
"win") {
let installDir = Services.dirsvc.get(
XRE_EXECUTABLE_FILE,
Ci.nsIFile
).parent;
let exitValue = runTestHelperSync([
"create-update-dir", installDir.path]);
Assert.equal(exitValue, 0,
"The helper process exit value should be 0");
}
}
ChromeUtils.defineLazyGetter(
this,
"gUpdatesRootDir",
function test_gURD() {
if (AppConstants.platform !=
"macosx") {
do_throw(
"Mac OS X only function called by a different platform!");
}
let dir = Services.dirsvc.get(
"ULibDir", Ci.nsIFile);
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 (AppConstants.platform !=
"macosx") {
do_throw(
"Mac OS X only function called by a different platform!");
}
let exeFile = Cc[
"@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
exeFile.initWithPath(gCustomGeneralPaths[XRE_EXECUTABLE_FILE]);
let appDir = exeFile.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.nsIFile);
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.
* @throws If called from a platform other than Windows.
*/
function createUpdateInProgressLockFile(aDir) {
if (AppConstants.platform !=
"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.readOnly =
true;
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.
* @throws If called from a platform other than Windows.
*/
function removeUpdateInProgressLockFile(aDir) {
if (AppConstants.platform !=
"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.readOnly =
false;
file.remove(
false);
Assert.ok(!file.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(file.path));
}
function stripQuarantineBitFromPath(aPath) {
if (AppConstants.platform !=
"macosx") {
do_throw(
"macOS-only function called by a different platform!");
}
let args = [
"-dr",
"com.apple.quarantine", aPath];
let stripQuarantineBitProcess = Cc[
"@mozilla.org/process/util;1"
].createInstance(Ci.nsIProcess);
let xattrBin = Cc[
"@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
xattrBin.initWithPath(
"/usr/bin/xattr");
stripQuarantineBitProcess.init(xattrBin);
stripQuarantineBitProcess.run(
true, args, args.length);
}
/**
* 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 = getTestDirFile(FILE_UPDATER_BIN);
let updater = getGREBinDir();
updater.append(FILE_UPDATER_BIN);
if (!updater.exists()) {
testUpdater.copyToFollowingLinks(updater.parent, FILE_UPDATER_BIN);
}
if (AppConstants.platform ==
"macosx") {
stripQuarantineBitFromPath(updater.path);
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 = getUpdateDirFile(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 = removeTimeStamps(updateLogContents);
updateLogContents = replaceLogPaths(updateLogContents);
let aryLogContents = updateLogContents.split(
"\n");
logTestInfo(
"contents of " + updateLog.path +
":");
aryLogContents.forEach(
function LU_ULC_FE(aLine) {
logTestInfo(aLine);
});
}
else {
logTestInfo(
"update log doesn't exist, path: " + updateLog.path);
}
if (gIsServiceTest) {
let secureStatus = readSecureStatusFile();
logTestInfo(
"secure update status: " + secureStatus);
updateLog = getSecureOutputFile(
"log");
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 = removeTimeStamps(updateLogContents);
updateLogContents = replaceLogPaths(updateLogContents);
let aryLogContents = updateLogContents.split(
"\n");
logTestInfo(
"contents of " + updateLog.path +
":");
aryLogContents.forEach(
function LU_SULC_FE(aLine) {
logTestInfo(aLine);
});
}
else {
logTestInfo(
"secure update log doesn't exist, path: " + updateLog.path);
}
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 LU_MSLC_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
--> --------------------
--> maximum size reached
--> --------------------