Bug 1110511 - Move tab-crashing test helper function to BrowserTestUtils.jsm r=felipe

This commit is contained in:
Mike Conley 2015-09-29 16:44:50 -04:00
parent 4859e86f4f
commit 1a329623c1
5 changed files with 155 additions and 184 deletions

View File

@ -69,7 +69,7 @@ add_task(function* test_crash() {
// the content process. The "crash" message makes it first so that we don't
// get a chance to process the flush. The TabStateFlusher however should be
// notified so that the flush still completes.
let promise1 = crashBrowser(browser);
let promise1 = BrowserTestUtils.crashBrowser(browser);
let promise2 = TabStateFlusher.flush(browser);
yield Promise.all([promise1, promise2]);

View File

@ -117,7 +117,7 @@ add_task(function test_crash_page_not_in_history() {
yield TabStateFlusher.flush(browser);
// Crash the tab
yield crashBrowser(browser);
yield BrowserTestUtils.crashBrowser(browser);
// Check the tab state and make sure the tab crashed page isn't
// mentioned.
@ -146,7 +146,7 @@ add_task(function test_revived_history_from_remote() {
yield TabStateFlusher.flush(browser);
// Crash the tab
yield crashBrowser(browser);
yield BrowserTestUtils.crashBrowser(browser);
// Browse to a new site that will cause the browser to
// become remote again.
@ -185,7 +185,7 @@ add_task(function test_revived_history_from_non_remote() {
yield TabStateFlusher.flush(browser);
// Crash the tab
yield crashBrowser(browser);
yield BrowserTestUtils.crashBrowser(browser);
// Browse to a new site that will not cause the browser to
// become remote again.
@ -235,7 +235,7 @@ add_task(function test_revive_tab_from_session_store() {
yield TabStateFlusher.flush(browser);
// Crash the tab
yield crashBrowser(browser);
yield BrowserTestUtils.crashBrowser(browser);
is(newTab2.getAttribute("crashed"), "true", "Second tab should be crashed too.");
// Use SessionStore to revive the tab
@ -286,7 +286,7 @@ add_task(function test_revive_all_tabs_from_session_store() {
yield TabStateFlusher.flush(browser2);
// Crash the tab
yield crashBrowser(browser);
yield BrowserTestUtils.crashBrowser(browser);
is(newTab2.getAttribute("crashed"), "true", "Second tab should be crashed too.");
// Use SessionStore to revive all the tabs
@ -331,7 +331,7 @@ add_task(function test_close_tab_after_crash() {
yield TabStateFlusher.flush(browser);
// Crash the tab
yield crashBrowser(browser);
yield BrowserTestUtils.crashBrowser(browser);
let promise = promiseEvent(gBrowser.tabContainer, "TabClose");
@ -359,7 +359,7 @@ add_task(function* test_hide_restore_all_button() {
yield TabStateFlusher.flush(browser);
// Crash the tab
yield crashBrowser(browser);
yield BrowserTestUtils.crashBrowser(browser);
let doc = browser.contentDocument;
let restoreAllButton = doc.getElementById("restoreAll");
@ -375,7 +375,7 @@ add_task(function* test_hide_restore_all_button() {
yield promiseBrowserLoaded(browser);
// Crash the tab
yield crashBrowser(browser);
yield BrowserTestUtils.crashBrowser(browser);
doc = browser.contentDocument;
restoreAllButton = doc.getElementById("restoreAll");
@ -405,7 +405,7 @@ add_task(function* test_aboutcrashedtabzoom() {
yield TabStateFlusher.flush(browser);
// Crash the tab
yield crashBrowser(browser);
yield BrowserTestUtils.crashBrowser(browser);
ok(ZoomManager.getZoomForBrowser(browser) === 1, "zoom should have reset on crash");

View File

@ -538,112 +538,6 @@ function promiseRemoveTab(tab) {
return BrowserTestUtils.removeTab(tab);
}
/**
* Returns a Promise that resolves once a remote <xul:browser> has experienced
* a crash. Also does the job of cleaning up the minidump of the crash.
*
* @param browser
* The <xul:browser> that will crash
* @return Promise
*/
function crashBrowser(browser) {
/**
* Returns the directory where crash dumps are stored.
*
* @return nsIFile
*/
function getMinidumpDirectory() {
let dir = Services.dirsvc.get('ProfD', Ci.nsIFile);
dir.append("minidumps");
return dir;
}
/**
* Removes a file from a directory. This is a no-op if the file does not
* exist.
*
* @param directory
* The nsIFile representing the directory to remove from.
* @param filename
* A string for the file to remove from the directory.
*/
function removeFile(directory, filename) {
let file = directory.clone();
file.append(filename);
if (file.exists()) {
file.remove(false);
}
}
// This frame script is injected into the remote browser, and used to
// intentionally crash the tab. We crash by using js-ctypes and dereferencing
// a bad pointer. The crash should happen immediately upon loading this
// frame script.
let frame_script = () => {
const Cu = Components.utils;
Cu.import("resource://gre/modules/ctypes.jsm");
let dies = function() {
privateNoteIntentionalCrash();
let zero = new ctypes.intptr_t(8);
let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
badptr.contents
};
dump("Et tu, Brute?");
dies();
}
let crashCleanupPromise = new Promise((resolve, reject) => {
let observer = (subject, topic, data) => {
is(topic, 'ipc:content-shutdown', 'Received correct observer topic.');
ok(subject instanceof Ci.nsIPropertyBag2,
'Subject implements nsIPropertyBag2.');
// we might see this called as the process terminates due to previous tests.
// We are only looking for "abnormal" exits...
if (!subject.hasKey("abnormal")) {
info("This is a normal termination and isn't the one we are looking for...");
return;
}
let dumpID;
if ('nsICrashReporter' in Ci) {
dumpID = subject.getPropertyAsAString('dumpID');
ok(dumpID, "dumpID is present and not an empty string");
}
if (dumpID) {
let minidumpDirectory = getMinidumpDirectory();
removeFile(minidumpDirectory, dumpID + '.dmp');
removeFile(minidumpDirectory, dumpID + '.extra');
}
Services.obs.removeObserver(observer, 'ipc:content-shutdown');
info("Crash cleaned up");
resolve();
};
Services.obs.addObserver(observer, 'ipc:content-shutdown');
});
let aboutTabCrashedLoadPromise = new Promise((resolve, reject) => {
browser.addEventListener("AboutTabCrashedLoad", function onCrash() {
browser.removeEventListener("AboutTabCrashedLoad", onCrash, false);
info("about:tabcrashed loaded");
resolve();
}, false, true);
});
// This frame script will crash the remote browser as soon as it is
// evaluated.
let mm = browser.messageManager;
mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", false);
return Promise.all([crashCleanupPromise, aboutTabCrashedLoadPromise]).then(() => {
let tab = gBrowser.getTabForBrowser(browser);
is(tab.getAttribute("crashed"), "true", "Tab should be marked as crashed");
});
}
// Write DOMSessionStorage data to the given browser.
function modifySessionStorage(browser, data, options = {}) {
return ContentTask.spawn(browser, [data, options], function* ([data, options]) {

View File

@ -17,6 +17,7 @@ this.EXPORTED_SYMBOLS = [
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
@ -458,5 +459,147 @@ this.BrowserTestUtils = {
tab.ownerDocument.defaultView.gBrowser.removeTab(tab);
}
});
}
},
/**
* Crashes a remote browser tab and cleans up the generated minidumps.
* Resolves with the data from the .extra file (the crash annotations).
*
* @param (Browser) browser
* A remote <xul:browser> element. Must not be null.
*
* @returns (Promise)
* @resolves An Object with key-value pairs representing the data from the
* crash report's extra file (if applicable).
*/
crashBrowser: Task.async(function*(browser) {
let extra = {};
let KeyValueParser = {};
if (AppConstants.MOZ_CRASHREPORTER) {
Cu.import("resource://gre/modules/KeyValueParser.jsm", KeyValueParser);
}
if (!browser.isRemoteBrowser) {
throw new Error("<xul:browser> needs to be remote in order to crash");
}
/**
* Returns the directory where crash dumps are stored.
*
* @return nsIFile
*/
function getMinidumpDirectory() {
let dir = Services.dirsvc.get('ProfD', Ci.nsIFile);
dir.append("minidumps");
return dir;
}
/**
* Removes a file from a directory. This is a no-op if the file does not
* exist.
*
* @param directory
* The nsIFile representing the directory to remove from.
* @param filename
* A string for the file to remove from the directory.
*/
function removeFile(directory, filename) {
let file = directory.clone();
file.append(filename);
if (file.exists()) {
file.remove(false);
}
}
// This frame script is injected into the remote browser, and used to
// intentionally crash the tab. We crash by using js-ctypes and dereferencing
// a bad pointer. The crash should happen immediately upon loading this
// frame script.
let frame_script = () => {
const Cu = Components.utils;
Cu.import("resource://gre/modules/ctypes.jsm");
let dies = function() {
privateNoteIntentionalCrash();
let zero = new ctypes.intptr_t(8);
let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
badptr.contents
};
dump("\nEt tu, Brute?\n");
dies();
}
let crashCleanupPromise = new Promise((resolve, reject) => {
let observer = (subject, topic, data) => {
if (topic != "ipc:content-shutdown") {
return reject("Received incorrect observer topic: " + topic);
}
if (!(subject instanceof Ci.nsIPropertyBag2)) {
return reject("Subject did not implement nsIPropertyBag2");
}
// we might see this called as the process terminates due to previous tests.
// We are only looking for "abnormal" exits...
if (!subject.hasKey("abnormal")) {
dump("\nThis is a normal termination and isn't the one we are looking for...\n");
return;
}
let dumpID;
if ('nsICrashReporter' in Ci) {
dumpID = subject.getPropertyAsAString('dumpID');
if (!dumpID) {
return reject("dumpID was not present despite crash reporting " +
"being enabled");
}
}
if (dumpID) {
let minidumpDirectory = getMinidumpDirectory();
let extrafile = minidumpDirectory.clone();
extrafile.append(dumpID + '.extra');
if (extrafile.exists()) {
dump(`\nNo .extra file for dumpID: ${dumpID}\n`);
if (AppConstants.MOZ_CRASHREPORTER) {
extra = KeyValueParser.parseKeyValuePairsFromFile(extrafile);
} else {
dump('\nCrashReporter not enabled - will not return any extra data\n');
}
}
removeFile(minidumpDirectory, dumpID + '.dmp');
removeFile(minidumpDirectory, dumpID + '.extra');
}
Services.obs.removeObserver(observer, 'ipc:content-shutdown');
dump("\nCrash cleaned up\n");
resolve();
};
Services.obs.addObserver(observer, 'ipc:content-shutdown', false);
});
let aboutTabCrashedLoadPromise = new Promise((resolve, reject) => {
browser.addEventListener("AboutTabCrashedLoad", function onCrash() {
browser.removeEventListener("AboutTabCrashedLoad", onCrash, false);
dump("\nabout:tabcrashed loaded\n");
resolve();
}, false, true);
});
// This frame script will crash the remote browser as soon as it is
// evaluated.
let mm = browser.messageManager;
mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", false);
yield Promise.all([crashCleanupPromise, aboutTabCrashedLoadPromise]);
let gBrowser = browser.ownerDocument.defaultView.gBrowser;
let tab = gBrowser.getTabForBrowser(browser);
if (tab.getAttribute("crashed") != "true") {
throw new Error("Tab should be marked as crashed");
}
return extra;
}),
};

View File

@ -9,72 +9,6 @@
// Running this test in ASAN is slow.
requestLongerTimeout(2);
/**
* Returns a Promise that resolves once a remote <xul:browser> has experienced
* a crash. Resolves with the data from the .extra file (the crash annotations).
*
* @param browser
* The <xul:browser> that will crash
* @return Promise
*/
function crashBrowser(browser) {
let kv = {};
Cu.import("resource://gre/modules/KeyValueParser.jsm", kv);
// This frame script is injected into the remote browser, and used to
// intentionally crash the tab. We crash by using js-ctypes and dereferencing
// a bad pointer. The crash should happen immediately upon loading this
// frame script.
let frame_script = () => {
const Cu = Components.utils;
Cu.import("resource://gre/modules/ctypes.jsm");
let dies = function() {
privateNoteIntentionalCrash();
let zero = new ctypes.intptr_t(8);
let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
let crash = badptr.contents;
};
dump("Et tu, Brute?");
dies();
};
function checkSubject(subject, data) {
return subject instanceof Ci.nsIPropertyBag2 &&
subject.hasKey("abnormal");
};
let crashPromise = TestUtils.topicObserved('ipc:content-shutdown',
checkSubject);
let crashDataPromise = crashPromise.then(([subject, data]) => {
ok(subject instanceof Ci.nsIPropertyBag2);
let dumpID;
if ('nsICrashReporter' in Ci) {
dumpID = subject.getPropertyAsAString('dumpID');
ok(dumpID, "dumpID is present and not an empty string");
}
let extra = null;
if (dumpID) {
let minidumpDirectory = getMinidumpDirectory();
let extrafile = minidumpDirectory.clone();
extrafile.append(dumpID + '.extra');
ok(extrafile.exists(), 'found .extra file');
extra = kv.parseKeyValuePairsFromFile(extrafile);
removeFile(minidumpDirectory, dumpID + '.dmp');
removeFile(minidumpDirectory, dumpID + '.extra');
}
return extra;
});
// This frame script will crash the remote browser as soon as it is
// evaluated.
let mm = browser.messageManager;
mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", false);
return crashDataPromise;
}
/**
* Removes a file from a directory. This is a no-op if the file does not
* exist.
@ -130,7 +64,7 @@ add_task(function* test_content_url_annotation() {
yield promise;
// Crash the tab
let annotations = yield crashBrowser(browser);
let annotations = yield BrowserTestUtils.crashBrowser(browser);
ok("URL" in annotations, "annotated a URL");
is(annotations.URL, redirect_url,