summaryrefslogtreecommitdiffstats
path: root/toolkit/xre/test/test_launch_without_hang.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/xre/test/test_launch_without_hang.js')
-rw-r--r--toolkit/xre/test/test_launch_without_hang.js256
1 files changed, 256 insertions, 0 deletions
diff --git a/toolkit/xre/test/test_launch_without_hang.js b/toolkit/xre/test/test_launch_without_hang.js
new file mode 100644
index 0000000000..feacae36d4
--- /dev/null
+++ b/toolkit/xre/test/test_launch_without_hang.js
@@ -0,0 +1,256 @@
+// 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 { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+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 = [];
+ for (let i = 0; i < newVals.length; ++i) {
+ let key = newVals[i].key;
+ let value = newVals[i].value;
+ let oldObj = { key };
+ if (Services.env.exists(key)) {
+ oldObj.value = Services.env.get(key);
+ } else {
+ oldObj.value = null;
+ }
+
+ Services.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.nsIFile);
+ 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.nsIFile);
+ 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.nsIFile);
+ file.append("System32");
+ file.append("taskkill.exe");
+ args = ["/F", "/IM", executableName];
+ } else {
+ file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath("/usr/bin/killall");
+ args = [executableName];
+ }
+
+ info("launching application: " + file.path);
+ info(" 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) {
+ info("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: ChromeUtils.generateQI(["nsIObserver"]),
+ };
+
+ process.runAsync(args, args.length, processObserver);
+
+ info(" 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;
+ }
+
+ info(
+ "topic: " + aTopic + ", process exitValue: " + state.process.exitValue
+ );
+
+ info("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: ChromeUtils.generateQI(["nsIObserver"]),
+ };
+
+ // The timer callback to kill the process if it takes too long.
+ state.appTimerCallback = {
+ notify: function TC_notify(aTimer) {
+ state.appTimer = null;
+
+ info("Restoring environment variables");
+ setEnvironmentVariables(state.oldEnv);
+
+ if (state.process.isRunning) {
+ info("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: ChromeUtils.generateQI(["nsITimerCallback"]),
+ };
+
+ info("launching application: " + file.path);
+ info(" with args: " + args.join(" "));
+ info(" with environment: ");
+ for (let i = 0; i < env.length; ++i) {
+ info(" " + 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);
+
+ info(" 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",
+ ]);
+ info("Try attempt #" + triesStarted);
+ launchProcess(
+ shell.file,
+ shell.args,
+ env,
+ APP_TIMER_TIMEOUT_MS,
+ handler,
+ triesStarted
+ );
+ };
+
+ testTry();
+}