зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1360493 write a test asserting that Firefox launches without hanging; r=rstrong
MozReview-Commit-ID: D0axTNp4KCt --HG-- extra : rebase_source : b56359ba3797a62f51fbc421d404409f994df11f
This commit is contained in:
Родитель
fd474519c7
Коммит
2e80270b6e
|
@ -12,6 +12,7 @@ if CONFIG['OS_ARCH'] == 'WINNT':
|
|||
|
||||
MOCHITEST_MANIFESTS += ['test/mochitest.ini']
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsINativeAppSupport.idl',
|
||||
|
|
|
@ -3982,6 +3982,12 @@ XREMain::XRE_mainStartup(bool* aExitFlag)
|
|||
}
|
||||
#endif
|
||||
|
||||
// Support exiting early for testing startup sequence. Bug 1360493
|
||||
if (CheckArg("test-launch-without-hang")) {
|
||||
*aExitFlag = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_WIDGET_GONK)
|
||||
// Check for and process any available updates
|
||||
nsCOMPtr<nsIFile> updRoot;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
module.exports = {
|
||||
"extends": [
|
||||
"plugin:mozilla/mochitest-test",
|
||||
"plugin:mozilla/browser-test"
|
||||
"plugin:mozilla/browser-test",
|
||||
"plugin:mozilla/xpcshell-test"
|
||||
]
|
||||
};
|
||||
|
|
|
@ -0,0 +1,237 @@
|
|||
// 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/.
|
||||
|
||||
// bug 1360493
|
||||
// Launch the browser a number of times, testing startup hangs.
|
||||
|
||||
"use strict";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, manager: Cm, results: Cr, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm", this);
|
||||
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
|
||||
const APP_TIMER_TIMEOUT_MS = 1000 * 15;
|
||||
const TRY_COUNT = 50;
|
||||
|
||||
|
||||
// Sets a group of environment variables, returning the old values.
|
||||
// newVals AND return value is an array of { key: "", value: "" }
|
||||
function setEnvironmentVariables(newVals) {
|
||||
let oldVals = [];
|
||||
let env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
|
||||
for (let i = 0; i < newVals.length; ++i) {
|
||||
let key = newVals[i].key;
|
||||
let value = newVals[i].value;
|
||||
let oldObj = { key };
|
||||
if (env.exists(key)) {
|
||||
oldObj.value = env.get(key);
|
||||
} else {
|
||||
oldObj.value = null;
|
||||
}
|
||||
|
||||
env.set(key, value);
|
||||
oldVals.push(oldObj);
|
||||
}
|
||||
return oldVals;
|
||||
}
|
||||
|
||||
|
||||
function getFirefoxExecutableFilename() {
|
||||
if (AppConstants.platform === "win") {
|
||||
return AppConstants.MOZ_APP_NAME + ".exe";
|
||||
}
|
||||
return AppConstants.MOZ_APP_NAME;
|
||||
}
|
||||
|
||||
|
||||
// Returns a nsIFile to the firefox.exe executable file
|
||||
function getFirefoxExecutableFile() {
|
||||
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
file = Services.dirsvc.get("GreBinD", Ci.nsIFile);
|
||||
|
||||
file.append(getFirefoxExecutableFilename());
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
// Takes an executable and arguments, and wraps it in a call to the system shell.
|
||||
// Technique adapted from \toolkit\mozapps\update\tests\unit_service_updater\xpcshellUtilsAUS.js
|
||||
// to avoid child process console output polluting the xpcshell log.
|
||||
// returns { file: (nsIFile), args: [] }
|
||||
function wrapLaunchInShell(file, args) {
|
||||
let ret = { };
|
||||
|
||||
if (AppConstants.platform === "win") {
|
||||
ret.file = Services.dirsvc.get("WinD", Ci.nsILocalFile);
|
||||
ret.file.append("System32");
|
||||
ret.file.append("cmd.exe");
|
||||
ret.args = ["/D", "/Q", "/C", file.path].concat(args).concat([">nul"]);
|
||||
} else {
|
||||
ret.file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
||||
ret.file.initWithPath("/usr/bin/env");
|
||||
ret.args = [file.path].concat(args).concat(["> /dev/null"]);
|
||||
}
|
||||
|
||||
Assert.ok(ret.file.exists(), "Executable file should exist: " + ret.file.path);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// Needed because process.kill() kills the console, not its child process, firefox.
|
||||
function terminateFirefox(completion) {
|
||||
let executableName = getFirefoxExecutableFilename();
|
||||
let file;
|
||||
let args;
|
||||
|
||||
if (AppConstants.platform === "win") {
|
||||
file = Services.dirsvc.get("WinD", Ci.nsILocalFile);
|
||||
file.append("System32");
|
||||
file.append("taskkill.exe");
|
||||
args = ["/F", "/IM", executableName];
|
||||
} else {
|
||||
file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
||||
file.initWithPath("/usr/bin/killall");
|
||||
args = [executableName];
|
||||
}
|
||||
|
||||
do_print("launching application: " + file.path);
|
||||
do_print(" with args: " + args.join(" "));
|
||||
|
||||
let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
|
||||
process.init(file);
|
||||
|
||||
let processObserver = {
|
||||
observe: function PO_observe(aSubject, aTopic, aData) {
|
||||
do_print("topic: " + aTopic + ", process exitValue: " + process.exitValue);
|
||||
|
||||
Assert.equal(process.exitValue, 0,
|
||||
"Terminate firefox process exit value should be 0");
|
||||
Assert.equal(aTopic, "process-finished",
|
||||
"Terminate firefox observer topic should be " +
|
||||
"process-finished");
|
||||
|
||||
if (completion) {
|
||||
completion();
|
||||
}
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
|
||||
};
|
||||
|
||||
process.runAsync(args, args.length, processObserver);
|
||||
|
||||
do_print(" with pid: " + process.pid);
|
||||
}
|
||||
|
||||
|
||||
// Launches file with args asynchronously, failing if the process did not
|
||||
// exit within timeoutMS milliseconds. If a timeout occurs, handler()
|
||||
// is called.
|
||||
function launchProcess(file, args, env, timeoutMS, handler, attemptCount) {
|
||||
let state = { };
|
||||
|
||||
state.attempt = attemptCount;
|
||||
|
||||
state.processObserver = {
|
||||
observe: function PO_observe(aSubject, aTopic, aData) {
|
||||
if (!state.appTimer) {
|
||||
// the app timer has been canceled; this process has timed out already so don't process further.
|
||||
handler(false);
|
||||
return;
|
||||
}
|
||||
|
||||
do_print("topic: " + aTopic + ", process exitValue: " + state.process.exitValue);
|
||||
|
||||
do_print("Restoring environment variables");
|
||||
setEnvironmentVariables(state.oldEnv);
|
||||
|
||||
state.appTimer.cancel();
|
||||
state.appTimer = null;
|
||||
|
||||
Assert.equal(state.process.exitValue, 0,
|
||||
"the application process exit value should be 0");
|
||||
Assert.equal(aTopic, "process-finished",
|
||||
"the application process observer topic should be " +
|
||||
"process-finished");
|
||||
|
||||
handler(true);
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
|
||||
};
|
||||
|
||||
// The timer callback to kill the process if it takes too long.
|
||||
state.appTimerCallback = {
|
||||
notify: function TC_notify(aTimer) {
|
||||
state.appTimer = null;
|
||||
|
||||
do_print("Restoring environment variables");
|
||||
setEnvironmentVariables(state.oldEnv);
|
||||
|
||||
if (state.process.isRunning) {
|
||||
do_print("attempting to kill process");
|
||||
|
||||
// This will cause the shell process to exit as well, triggering our process observer.
|
||||
terminateFirefox(function terminateFirefoxCompletion() {
|
||||
Assert.ok(false, "Launch application timer expired");
|
||||
});
|
||||
}
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback])
|
||||
};
|
||||
|
||||
do_print("launching application: " + file.path);
|
||||
do_print(" with args: " + args.join(" "));
|
||||
do_print(" with environment: ");
|
||||
for (let i = 0; i < env.length; ++i) {
|
||||
do_print(" " + env[i].key + "=" + env[i].value);
|
||||
}
|
||||
|
||||
state.process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
|
||||
state.process.init(file);
|
||||
|
||||
state.appTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
state.appTimer.initWithCallback(state.appTimerCallback, timeoutMS, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
|
||||
state.oldEnv = setEnvironmentVariables(env);
|
||||
|
||||
state.process.runAsync(args, args.length, state.processObserver);
|
||||
|
||||
do_print(" with pid: " + state.process.pid);
|
||||
}
|
||||
|
||||
|
||||
function run_test() {
|
||||
do_test_pending();
|
||||
|
||||
let env = [
|
||||
{ key: "MOZ_CRASHREPORTER_DISABLE", value: null },
|
||||
{ key: "MOZ_CRASHREPORTER", value: "1" },
|
||||
{ key: "MOZ_CRASHREPORTER_NO_REPORT", value: "1" },
|
||||
{ key: "MOZ_CRASHREPORTER_SHUTDOWN", value: "1" },
|
||||
{ key: "XPCOM_DEBUG_BREAK", value: "stack-and-abort" }
|
||||
];
|
||||
|
||||
let triesStarted = 1;
|
||||
|
||||
let handler = function launchFirefoxHandler(okToContinue) {
|
||||
triesStarted++;
|
||||
if ((triesStarted <= TRY_COUNT) && okToContinue) {
|
||||
testTry();
|
||||
} else {
|
||||
do_test_finished();
|
||||
}
|
||||
};
|
||||
|
||||
let testTry = function testTry() {
|
||||
let shell = wrapLaunchInShell(getFirefoxExecutableFile(), ["-no-remote", "-test-launch-without-hang"]);
|
||||
do_print("Try attempt #" + triesStarted);
|
||||
launchProcess(shell.file, shell.args, env, APP_TIMER_TIMEOUT_MS, handler, triesStarted);
|
||||
};
|
||||
|
||||
testTry();
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# 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/.
|
||||
|
||||
[DEFAULT]
|
||||
tags = native
|
||||
|
||||
[test_launch_without_hang.js]
|
||||
run-sequentially = Has to launch application binary
|
||||
skip-if = toolkit == 'android'
|
||||
|
Загрузка…
Ссылка в новой задаче