Bug 642175 - Part 2: Allow mochitests to clean up plugin and IPC process crash dumps. r=ted

This commit is contained in:
Cameron McCormack 2011-06-21 12:11:50 +12:00
parent 74bf1bc1f0
commit 9d4035280b
5 changed files with 292 additions and 23 deletions

View File

@ -114,6 +114,7 @@ def checkForCrashes(dumpDir, symbolsPath, testName=None):
dumps = glob.glob(os.path.join(dumpDir, '*.dmp'))
for d in dumps:
log.info("PROCESS-CRASH | %s | application crashed (minidump found)", testName)
print "Crash dump filename: " + d
if symbolsPath and stackwalkPath and os.path.exists(stackwalkPath):
p = subprocess.Popen([stackwalkPath, d, symbolsPath],
stdout=subprocess.PIPE,

View File

@ -64,7 +64,11 @@ SpecialPowersException.prototype.toString = function() {
};
/* XPCOM gunk */
function SpecialPowersObserver() {}
function SpecialPowersObserver() {
this._isFrameScriptLoaded = false;
this._messageManager = Cc["@mozilla.org/globalmessagemanager;1"].
getService(Ci.nsIChromeFrameMessageManager);
}
SpecialPowersObserver.prototype = {
classDescription: "Special powers Observer for use in testing.",
@ -72,24 +76,49 @@ SpecialPowersObserver.prototype = {
contractID: "@mozilla.org/special-powers-observer;1",
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIObserver]),
_xpcom_categories: [{category: "profile-after-change", service: true }],
isFrameScriptLoaded: false,
observe: function(aSubject, aTopic, aData)
{
if (aTopic == "profile-after-change") {
this.init();
} else if (!this.isFrameScriptLoaded &&
aTopic == "chrome-document-global-created") {
var messageManager = Cc["@mozilla.org/globalmessagemanager;1"].
getService(Ci.nsIChromeFrameMessageManager);
// Register for any messages our API needs us to handle
messageManager.addMessageListener("SPPrefService", this);
switch (aTopic) {
case "profile-after-change":
this.init();
break;
messageManager.loadFrameScript(CHILD_SCRIPT, true);
this.isFrameScriptLoaded = true;
} else if (aTopic == "xpcom-shutdown") {
this.uninit();
case "chrome-document-global-created":
if (!this._isFrameScriptLoaded) {
// Register for any messages our API needs us to handle
this._messageManager.addMessageListener("SPPrefService", this);
this._messageManager.addMessageListener("SPProcessCrashService", this);
this._messageManager.addMessageListener("SPPingService", this);
this._messageManager.loadFrameScript(CHILD_SCRIPT, true);
this._isFrameScriptLoaded = true;
}
break;
case "xpcom-shutdown":
this.uninit();
break;
case "plugin-crashed":
case "ipc:content-shutdown":
function addDumpIDToMessage(propertyName) {
var id = aSubject.getPropertyAsAString(propertyName);
if (id) {
message.dumpIDs.push(id);
}
}
var message = { type: "crash-observed", dumpIDs: [] };
aSubject = aSubject.QueryInterface(Ci.nsIPropertyBag2);
if (aTopic == "plugin-crashed") {
addDumpIDToMessage("pluginDumpID");
addDumpIDToMessage("browserDumpID");
} else { // ipc:content-shutdown
addDumpIDToMessage("dumpID");
}
this._messageManager.sendAsyncMessage("SPProcessCrashService", message);
break;
}
},
@ -104,8 +133,76 @@ SpecialPowersObserver.prototype = {
{
var obs = Services.obs;
obs.removeObserver(this, "chrome-document-global-created", false);
this.removeProcessCrashObservers();
},
addProcessCrashObservers: function() {
if (this._processCrashObserversRegistered) {
return;
}
Services.obs.addObserver(this, "plugin-crashed", false);
Services.obs.addObserver(this, "ipc:content-shutdown", false);
this._processCrashObserversRegistered = true;
},
removeProcessCrashObservers: function() {
if (!this._processCrashObserversRegistered) {
return;
}
Services.obs.removeObserver(this, "plugin-crashed");
Services.obs.removeObserver(this, "ipc:content-shutdown");
this._processCrashObserversRegistered = false;
},
getCrashDumpDir: function() {
if (!this._crashDumpDir) {
var directoryService = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties);
this._crashDumpDir = directoryService.get("ProfD", Ci.nsIFile);
this._crashDumpDir.append("minidumps");
}
return this._crashDumpDir;
},
deleteCrashDumpFiles: function(aFilenames) {
var crashDumpDir = this.getCrashDumpDir();
if (!crashDumpDir.exists()) {
return false;
}
var success = aFilenames.length != 0;
aFilenames.forEach(function(crashFilename) {
var file = crashDumpDir.clone();
file.append(crashFilename);
if (file.exists()) {
file.remove(false);
} else {
success = false;
}
});
return success;
},
findCrashDumpFiles: function(aToIgnore) {
var crashDumpDir = this.getCrashDumpDir();
var entries = crashDumpDir.exists() && crashDumpDir.directoryEntries;
if (!entries) {
return [];
}
var crashDumpFiles = [];
while (entries.hasMoreElements()) {
var file = entries.getNext().QueryInterface(Ci.nsIFile);
var path = String(file.path);
if (path.match(/\.(dmp|extra)$/) && !aToIgnore[path]) {
crashDumpFiles.push(path);
}
}
return crashDumpFiles.concat();
},
/**
* messageManager callback function
* This will get requests from our API in the window and process them in chrome for it
@ -159,6 +256,34 @@ SpecialPowersObserver.prototype = {
}
}
break;
case "SPProcessCrashService":
switch (aMessage.json.op) {
case "register-observer":
this.addProcessCrashObservers();
break;
case "unregister-observer":
this.removeProcessCrashObservers();
break;
case "delete-crash-dump-files":
return this.deleteCrashDumpFiles(aMessage.json.filenames);
case "find-crash-dump-files":
return this.findCrashDumpFiles(aMessage.json.crashDumpFilesToIgnore);
default:
throw new SpecialPowersException("Invalid operation for SPProcessCrashService");
}
break;
case "SPPingService":
if (aMessage.json.op == "ping") {
aMessage.target
.QueryInterface(Ci.nsIFrameLoaderOwner)
.frameLoader
.messageManager
.sendAsyncMessage("SPPingService", { op: "pong" });
}
break;
default:
throw new SpecialPowersException("Unrecognized Special Powers API");
}

View File

@ -44,6 +44,12 @@ var Cc = Components.classes;
function SpecialPowers(window) {
this.window = window;
bindDOMWindowUtils(this, window);
this._encounteredCrashDumpFiles = [];
this._unexpectedCrashDumpFiles = { };
this._crashDumpDir = null;
this._pongHandlers = [];
this._messageListener = this._messageReceived.bind(this);
addMessageListener("SPPingService", this._messageListener);
}
function bindDOMWindowUtils(sp, window) {
@ -214,6 +220,77 @@ SpecialPowers.prototype = {
return true;
}
},
registerProcessCrashObservers: function() {
addMessageListener("SPProcessCrashService", this._messageListener);
sendSyncMessage("SPProcessCrashService", { op: "register-observer" });
},
_messageReceived: function(aMessage) {
switch (aMessage.name) {
case "SPProcessCrashService":
if (aMessage.json.type == "crash-observed") {
var self = this;
aMessage.json.dumpIDs.forEach(function(id) {
self._encounteredCrashDumpFiles.push(id + ".dmp");
self._encounteredCrashDumpFiles.push(id + ".extra");
});
}
break;
case "SPPingService":
if (aMessage.json.op == "pong") {
var handler = this._pongHandlers.shift();
if (handler) {
handler();
}
}
break;
}
return true;
},
removeExpectedCrashDumpFiles: function(aExpectingProcessCrash) {
var success = true;
if (aExpectingProcessCrash) {
var message = {
op: "delete-crash-dump-files",
filenames: this._encounteredCrashDumpFiles
};
if (!sendSyncMessage("SPProcessCrashService", message)[0]) {
success = false;
}
}
this._encounteredCrashDumpFiles.length = 0;
return success;
},
findUnexpectedCrashDumpFiles: function() {
var self = this;
var message = {
op: "find-crash-dump-files",
crashDumpFilesToIgnore: this._unexpectedCrashDumpFiles
};
var crashDumpFiles = sendSyncMessage("SPProcessCrashService", message)[0];
crashDumpFiles.forEach(function(aFilename) {
self._unexpectedCrashDumpFiles[aFilename] = true;
});
return crashDumpFiles;
},
executeAfterFlushingMessageQueue: function(aCallback) {
this._pongHandlers.push(aCallback);
sendAsyncMessage("SPPingService", { op: "ping" });
},
executeSoon: function(aFunc) {
var tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
tm.mainThread.dispatch({
run: function() {
aFunc();
}
}, Ci.nsIThread.DISPATCH_NORMAL);
}
};
// Expose everything but internal APIs (starting with underscores) to

View File

@ -129,6 +129,10 @@ SimpleTest._logResult = function(test, passString, failString) {
}
};
SimpleTest._logInfo = function(name, message) {
this._logResult({result:true, name:name, diag:message}, "TEST-INFO");
};
/**
* Copies of is and isnot with the call to ok replaced by a call to todo.
**/
@ -330,7 +334,7 @@ SimpleTest.waitForFocus = function (callback, targetWindow, expectBlankPage) {
childTargetWindow = childTargetWindow.value;
function info(msg) {
SimpleTest._logResult({result: true, name: msg}, "TEST-INFO");
SimpleTest._logInfo("", msg);
}
function debugFocusLog(prefix) {
@ -522,6 +526,8 @@ SimpleTest.waitForClipboard = function(aExpectedStringOrValidatorFn, aSetupFn,
* working (or finish).
*/
SimpleTest.executeSoon = function(aFunc) {
// Once SpecialPowers is available in chrome mochitests, we can replace the
// body of this function with a call to SpecialPowers.executeSoon().
if ("Components" in window && "classes" in window.Components) {
try {
netscape.security.PrivilegeManager
@ -556,6 +562,17 @@ SimpleTest.finish = function () {
}
};
/**
* Indicates to the test framework that the current test expects one or
* more crashes (from plugins or IPC documents), and that the minidumps from
* those crashes should be removed.
*/
SimpleTest.expectChildProcessCrash = function () {
if (parentRunner) {
parentRunner.expectChildProcessCrash();
}
};
addLoadEvent(function() {
if (SimpleTest._stopOnLoad) {

View File

@ -49,6 +49,8 @@ if (typeof SpecialPowers != 'undefined') {
TestRunner.ipcMode = false;
}
TestRunner._expectingProcessCrash = false;
/**
* Make sure the tests don't hang indefinitely.
**/
@ -168,6 +170,10 @@ TestRunner._makeIframe = function (url, retry) {
TestRunner.runTests = function (/*url...*/) {
TestRunner.log("SimpleTest START");
if (typeof SpecialPowers != "undefined") {
SpecialPowers.registerProcessCrashObservers();
}
TestRunner._urls = flattenArguments(arguments);
$('testframe').src="";
TestRunner._checkForHangs();
@ -226,18 +232,61 @@ TestRunner.runNextTest = function() {
}
};
TestRunner.expectChildProcessCrash = function() {
if (typeof SpecialPowers == "undefined") {
throw "TestRunner.expectChildProcessCrash must only be called from plain mochitests.";
}
TestRunner._expectingProcessCrash = true;
};
/**
* This stub is called by SimpleTest when a test is finished.
**/
TestRunner.testFinished = function(tests) {
var runtime = new Date().valueOf() - TestRunner._currentTestStartTime;
TestRunner.log("TEST-END | " +
TestRunner._urls[TestRunner._currentTest] +
" | finished in " + runtime + "ms");
function cleanUpCrashDumpFiles() {
if (!SpecialPowers.removeExpectedCrashDumpFiles(TestRunner._expectingProcessCrash)) {
TestRunner.error("TEST-UNEXPECTED-FAIL | " +
TestRunner.currentTestURL +
" | This test did not leave any crash dumps behind, but we were expecting some!");
tests.push({ result: false });
}
var unexpectedCrashDumpFiles =
SpecialPowers.findUnexpectedCrashDumpFiles();
TestRunner._expectingProcessCrash = false;
if (unexpectedCrashDumpFiles.length) {
TestRunner.error("TEST-UNEXPECTED-FAIL | " +
TestRunner.currentTestURL +
" | This test left crash dumps behind, but we " +
"weren't expecting it to!");
tests.push({ result: false });
unexpectedCrashDumpFiles.sort().forEach(function(aFilename) {
TestRunner.log("TEST-INFO | Found unexpected crash dump file " +
aFilename + ".");
});
}
}
TestRunner.updateUI(tests);
TestRunner._currentTest++;
TestRunner.runNextTest();
function runNextTest() {
var runtime = new Date().valueOf() - TestRunner._currentTestStartTime;
TestRunner.log("TEST-END | " +
TestRunner._urls[TestRunner._currentTest] +
" | finished in " + runtime + "ms");
TestRunner.log("TEST-INFO | Time is " + Math.floor(Date.now() / 1000));
TestRunner.updateUI(tests);
TestRunner._currentTest++;
TestRunner.runNextTest();
}
if (typeof SpecialPowers != 'undefined') {
SpecialPowers.executeAfterFlushingMessageQueue(function() {
cleanUpCrashDumpFiles();
runNextTest();
});
} else {
runNextTest();
}
};
/**