mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 06:11:37 +00:00
Bug 728294 - Part 3 - Analyze cycle collection logs on testsuite shutdown to detected leaked windows; r=ted,smaug
This commit is contained in:
parent
75ba6077cc
commit
9a670c04a3
@ -63,6 +63,7 @@ _SERV_FILES = \
|
||||
harness.xul \
|
||||
browser-test-overlay.xul \
|
||||
browser-test.js \
|
||||
cc-analyzer.js \
|
||||
chrome-harness.js \
|
||||
browser-harness.xul \
|
||||
redirect.html \
|
||||
|
@ -8,4 +8,5 @@
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"/>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/browser-test.js"/>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/cc-analyzer.js"/>
|
||||
</overlay>
|
||||
|
@ -7,6 +7,12 @@ if (Cc === undefined) {
|
||||
var Ci = Components.interfaces;
|
||||
var Cu = Components.utils;
|
||||
}
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
|
||||
window.addEventListener("load", testOnLoad, false);
|
||||
|
||||
function testOnLoad() {
|
||||
@ -15,21 +21,18 @@ function testOnLoad() {
|
||||
gConfig = readConfig();
|
||||
if (gConfig.testRoot == "browser" || gConfig.testRoot == "webapprtChrome") {
|
||||
// Make sure to launch the test harness for the first opened window only
|
||||
var prefs = Cc["@mozilla.org/preferences-service;1"].
|
||||
getService(Ci.nsIPrefBranch);
|
||||
var prefs = Services.prefs;
|
||||
if (prefs.prefHasUserValue("testing.browserTestHarness.running"))
|
||||
return;
|
||||
|
||||
prefs.setBoolPref("testing.browserTestHarness.running", true);
|
||||
|
||||
var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
|
||||
getService(Ci.nsIWindowWatcher);
|
||||
var sstring = Cc["@mozilla.org/supports-string;1"].
|
||||
createInstance(Ci.nsISupportsString);
|
||||
sstring.data = location.search;
|
||||
|
||||
ww.openWindow(window, "chrome://mochikit/content/browser-harness.xul", "browserTest",
|
||||
"chrome,centerscreen,dialog=no,resizable,titlebar,toolbar=no,width=800,height=600", sstring);
|
||||
Services.ww.openWindow(window, "chrome://mochikit/content/browser-harness.xul", "browserTest",
|
||||
"chrome,centerscreen,dialog=no,resizable,titlebar,toolbar=no,width=800,height=600", sstring);
|
||||
} else {
|
||||
// This code allows us to redirect without requiring specialpowers for chrome and a11y tests.
|
||||
function messageHandler(m) {
|
||||
@ -53,15 +56,10 @@ function Tester(aTests, aDumper, aCallback) {
|
||||
this.dumper = aDumper;
|
||||
this.tests = aTests;
|
||||
this.callback = aCallback;
|
||||
this._cs = Cc["@mozilla.org/consoleservice;1"].
|
||||
getService(Ci.nsIConsoleService);
|
||||
this._wm = Cc["@mozilla.org/appshell/window-mediator;1"].
|
||||
getService(Ci.nsIWindowMediator);
|
||||
this._fm = Cc["@mozilla.org/focus-manager;1"].
|
||||
getService(Ci.nsIFocusManager);
|
||||
this.openedWindows = {};
|
||||
this.openedURLs = {};
|
||||
|
||||
this._scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
|
||||
getService(Ci.mozIJSSubScriptLoader);
|
||||
this._scriptLoader = Services.scriptloader;
|
||||
this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", this.EventUtils);
|
||||
var simpleTestScope = {};
|
||||
this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js", simpleTestScope);
|
||||
@ -79,6 +77,8 @@ Tester.prototype = {
|
||||
checker: null,
|
||||
currentTestIndex: -1,
|
||||
lastStartTime: null,
|
||||
openedWindows: null,
|
||||
|
||||
get currentTest() {
|
||||
return this.tests[this.currentTestIndex];
|
||||
},
|
||||
@ -92,7 +92,9 @@ Tester.prototype = {
|
||||
gConfig = readConfig();
|
||||
this.repeat = gConfig.repeat;
|
||||
this.dumper.dump("*** Start BrowserChrome Test Results ***\n");
|
||||
this._cs.registerListener(this);
|
||||
Services.console.registerListener(this);
|
||||
Services.obs.addObserver(this, "chrome-document-global-created", false);
|
||||
Services.obs.addObserver(this, "content-document-global-created", false);
|
||||
this._globalProperties = Object.keys(window);
|
||||
this._globalPropertyWhitelist = ["navigator", "constructor", "Application",
|
||||
"__SS_tabsToRestore", "__SSi", "webConsoleCommandController",
|
||||
@ -124,7 +126,7 @@ Tester.prototype = {
|
||||
}
|
||||
|
||||
this.dumper.dump("TEST-INFO | checking window state\n");
|
||||
let windowsEnum = this._wm.getEnumerator(null);
|
||||
let windowsEnum = Services.wm.getEnumerator(null);
|
||||
while (windowsEnum.hasMoreElements()) {
|
||||
let win = windowsEnum.getNext();
|
||||
if (win != window && !win.closed &&
|
||||
@ -159,7 +161,9 @@ Tester.prototype = {
|
||||
this.nextTest();
|
||||
}
|
||||
else{
|
||||
this._cs.unregisterListener(this);
|
||||
Services.console.unregisterListener(this);
|
||||
Services.obs.removeObserver(this, "chrome-document-global-created");
|
||||
Services.obs.removeObserver(this, "content-document-global-created");
|
||||
|
||||
this.dumper.dump("\nINFO TEST-START | Shutdown\n");
|
||||
if (this.tests.length) {
|
||||
@ -186,10 +190,34 @@ Tester.prototype = {
|
||||
this.callback(this.tests);
|
||||
this.callback = null;
|
||||
this.tests = null;
|
||||
this.openedWindows = null;
|
||||
}
|
||||
},
|
||||
|
||||
observe: function Tester_observe(aConsoleMessage) {
|
||||
observe: function Tester_observe(aSubject, aTopic, aData) {
|
||||
if (!aTopic) {
|
||||
this.onConsoleMessage(aSubject);
|
||||
} else if (this.currentTest) {
|
||||
this.onDocumentCreated(aSubject);
|
||||
}
|
||||
},
|
||||
|
||||
onDocumentCreated: function Tester_onDocumentCreated(aWindow) {
|
||||
let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
let outerID = utils.outerWindowID;
|
||||
let innerID = utils.currentInnerWindowID;
|
||||
|
||||
if (!(outerID in this.openedWindows)) {
|
||||
this.openedWindows[outerID] = this.currentTest;
|
||||
}
|
||||
this.openedWindows[innerID] = this.currentTest;
|
||||
|
||||
let url = aWindow.location.href || "about:blank";
|
||||
this.openedURLs[outerID] = this.openedURLs[innerID] = url;
|
||||
},
|
||||
|
||||
onConsoleMessage: function Tester_onConsoleMessage(aConsoleMessage) {
|
||||
// Ignore empty messages.
|
||||
if (!aConsoleMessage.message)
|
||||
return;
|
||||
@ -265,12 +293,20 @@ Tester.prototype = {
|
||||
// Schedule GC and CC runs before finishing in order to detect
|
||||
// DOM windows leaked by our tests or the tested code.
|
||||
Cu.schedulePreciseGC((function () {
|
||||
let winutils = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
winutils.garbageCollect();
|
||||
winutils.garbageCollect();
|
||||
winutils.garbageCollect();
|
||||
this.finish();
|
||||
let analyzer = new CCAnalyzer();
|
||||
analyzer.run(function () {
|
||||
for (let obj of analyzer.find("nsGlobalWindow ")) {
|
||||
let m = obj.name.match(/^nsGlobalWindow #(\d+)/);
|
||||
if (m && m[1] in this.openedWindows) {
|
||||
let test = this.openedWindows[m[1]];
|
||||
let msg = "leaked until shutdown [" + obj.name +
|
||||
" " + (this.openedURLs[m[1]] || "NULL") + "]";
|
||||
test.addResult(new testResult(false, msg, "", false));
|
||||
}
|
||||
}
|
||||
|
||||
this.finish();
|
||||
}.bind(this));
|
||||
}).bind(this));
|
||||
return;
|
||||
}
|
||||
@ -462,9 +498,7 @@ function testScope(aTester, aTest) {
|
||||
};
|
||||
|
||||
this.executeSoon = function test_executeSoon(func) {
|
||||
let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
|
||||
|
||||
tm.mainThread.dispatch({
|
||||
Services.tm.mainThread.dispatch({
|
||||
run: function() {
|
||||
func();
|
||||
}
|
||||
|
126
testing/mochitest/cc-analyzer.js
Normal file
126
testing/mochitest/cc-analyzer.js
Normal file
@ -0,0 +1,126 @@
|
||||
/* 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/. */
|
||||
|
||||
function CCAnalyzer() {
|
||||
}
|
||||
|
||||
CCAnalyzer.prototype = {
|
||||
clear: function () {
|
||||
this.callback = null;
|
||||
this.processingCount = 0;
|
||||
this.graph = {};
|
||||
this.roots = [];
|
||||
this.garbage = [];
|
||||
this.edges = [];
|
||||
this.listener = null;
|
||||
},
|
||||
|
||||
run: function (aCallback) {
|
||||
this.clear();
|
||||
this.callback = aCallback;
|
||||
|
||||
this.listener = Cc["@mozilla.org/cycle-collector-logger;1"].
|
||||
createInstance(Ci.nsICycleCollectorListener);
|
||||
|
||||
this.listener.disableLog = true;
|
||||
this.listener.wantAfterProcessing = true;
|
||||
|
||||
this.runCC(3);
|
||||
},
|
||||
|
||||
runCC: function (aCounter) {
|
||||
let utils = window.QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIDOMWindowUtils);
|
||||
|
||||
if (aCounter > 1) {
|
||||
utils.garbageCollect();
|
||||
setTimeout(this.runCC.bind(this, aCounter - 1), 0);
|
||||
} else {
|
||||
utils.garbageCollect(this.listener);
|
||||
this.processLog();
|
||||
}
|
||||
},
|
||||
|
||||
processLog: function () {
|
||||
// Process entire heap step by step in 5K chunks
|
||||
for (let i = 0; i < 5000; i++) {
|
||||
if (!this.listener.processNext(this)) {
|
||||
this.callback();
|
||||
this.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Next chunk on timeout.
|
||||
setTimeout(this.processLog.bind(this), 0);
|
||||
},
|
||||
|
||||
noteRefCountedObject: function (aAddress, aRefCount, aObjectDescription) {
|
||||
let o = this.ensureObject(aAddress);
|
||||
o.address = aAddress;
|
||||
o.refcount = aRefCount;
|
||||
o.name = aObjectDescription;
|
||||
},
|
||||
|
||||
noteGCedObject: function (aAddress, aMarked, aObjectDescription) {
|
||||
let o = this.ensureObject(aAddress);
|
||||
o.address = aAddress;
|
||||
o.gcmarked = aMarked;
|
||||
o.name = aObjectDescription;
|
||||
},
|
||||
|
||||
noteEdge: function (aFromAddress, aToAddress, aEdgeName) {
|
||||
let fromObject = this.ensureObject(aFromAddress);
|
||||
let toObject = this.ensureObject(aToAddress);
|
||||
fromObject.edges.push({name: aEdgeName, to: toObject});
|
||||
toObject.owners.push({name: aEdgeName, from: fromObject});
|
||||
|
||||
this.edges.push({
|
||||
name: aEdgeName,
|
||||
from: fromObject,
|
||||
to: toObject
|
||||
});
|
||||
},
|
||||
|
||||
describeRoot: function (aAddress, aKnownEdges) {
|
||||
let o = this.ensureObject(aAddress);
|
||||
o.root = true;
|
||||
o.knownEdges = aKnownEdges;
|
||||
this.roots.push(o);
|
||||
},
|
||||
|
||||
describeGarbage: function (aAddress) {
|
||||
let o = this.ensureObject(aAddress);
|
||||
o.garbage = true;
|
||||
this.garbage.push(o);
|
||||
},
|
||||
|
||||
ensureObject: function (aAddress) {
|
||||
if (!this.graph[aAddress])
|
||||
this.graph[aAddress] = new CCObject();
|
||||
|
||||
return this.graph[aAddress];
|
||||
},
|
||||
|
||||
find: function (aText) {
|
||||
let result = [];
|
||||
for each (let o in this.graph) {
|
||||
if (!o.garbage && o.name.indexOf(aText) >= 0)
|
||||
result.push(o);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
function CCObject() {
|
||||
this.name = "";
|
||||
this.address = null;
|
||||
this.refcount = 0;
|
||||
this.gcmarked = false;
|
||||
this.root = false;
|
||||
this.garbage = false;
|
||||
this.knownEdges = 0;
|
||||
this.edges = [];
|
||||
this.owners = [];
|
||||
}
|
@ -3,6 +3,7 @@ mochikit.jar:
|
||||
content/browser-harness.xul (browser-harness.xul)
|
||||
content/browser-test.js (browser-test.js)
|
||||
content/browser-test-overlay.xul (browser-test-overlay.xul)
|
||||
content/cc-analyzer.js (cc-analyzer.js)
|
||||
content/chrome-harness.js (chrome-harness.js)
|
||||
content/harness-overlay.xul (harness-overlay.xul)
|
||||
content/harness.xul (harness.xul)
|
||||
|
Loading…
Reference in New Issue
Block a user