Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 "use strict";
6 module.metadata = {
7 "stability": "experimental"
8 };
10 const { Cc, Ci, Cu } = require("chrome");
11 const { Loader } = require('./loader');
12 const { serializeStack, parseStack } = require("toolkit/loader");
13 const { setTimeout } = require('../timers');
14 const { PlainTextConsole } = require("../console/plain-text");
15 const { when: unload } = require("../system/unload");
16 const { format, fromException } = require("../console/traceback");
17 const system = require("../system");
18 const memory = require('../deprecated/memory');
19 const { gc: gcPromise } = require('./memory');
20 const { defer } = require('../core/promise');
22 // Trick manifest builder to make it think we need these modules ?
23 const unit = require("../deprecated/unit-test");
24 const test = require("../../test");
25 const url = require("../url");
27 function emptyPromise() {
28 let { promise, resolve } = defer();
29 resolve();
30 return promise;
31 }
33 var cService = Cc['@mozilla.org/consoleservice;1'].getService()
34 .QueryInterface(Ci.nsIConsoleService);
36 // The console used to log messages
37 var testConsole;
39 // Cuddlefish loader in which we load and execute tests.
40 var loader;
42 // Function to call when we're done running tests.
43 var onDone;
45 // Function to print text to a console, w/o CR at the end.
46 var print;
48 // How many more times to run all tests.
49 var iterationsLeft;
51 // Whether to report memory profiling information.
52 var profileMemory;
54 // Whether we should stop as soon as a test reports a failure.
55 var stopOnError;
57 // Function to call to retrieve a list of tests to execute
58 var findAndRunTests;
60 // Combined information from all test runs.
61 var results = {
62 passed: 0,
63 failed: 0,
64 testRuns: []
65 };
67 // A list of the compartments and windows loaded after startup
68 var startLeaks;
70 // JSON serialization of last memory usage stats; we keep it stringified
71 // so we don't actually change the memory usage stats (in terms of objects)
72 // of the JSRuntime we're profiling.
73 var lastMemoryUsage;
75 function analyzeRawProfilingData(data) {
76 var graph = data.graph;
77 var shapes = {};
79 // Convert keys in the graph from strings to ints.
80 // TODO: Can we get rid of this ridiculousness?
81 var newGraph = {};
82 for (id in graph) {
83 newGraph[parseInt(id)] = graph[id];
84 }
85 graph = newGraph;
87 var modules = 0;
88 var moduleIds = [];
89 var moduleObjs = {UNKNOWN: 0};
90 for (let name in data.namedObjects) {
91 moduleObjs[name] = 0;
92 moduleIds[data.namedObjects[name]] = name;
93 modules++;
94 }
96 var count = 0;
97 for (id in graph) {
98 var parent = graph[id].parent;
99 while (parent) {
100 if (parent in moduleIds) {
101 var name = moduleIds[parent];
102 moduleObjs[name]++;
103 break;
104 }
105 if (!(parent in graph)) {
106 moduleObjs.UNKNOWN++;
107 break;
108 }
109 parent = graph[parent].parent;
110 }
111 count++;
112 }
114 print("\nobject count is " + count + " in " + modules + " modules" +
115 " (" + data.totalObjectCount + " across entire JS runtime)\n");
116 if (lastMemoryUsage) {
117 var last = JSON.parse(lastMemoryUsage);
118 var diff = {
119 moduleObjs: dictDiff(last.moduleObjs, moduleObjs),
120 totalObjectClasses: dictDiff(last.totalObjectClasses,
121 data.totalObjectClasses)
122 };
124 for (let name in diff.moduleObjs)
125 print(" " + diff.moduleObjs[name] + " in " + name + "\n");
126 for (let name in diff.totalObjectClasses)
127 print(" " + diff.totalObjectClasses[name] + " instances of " +
128 name + "\n");
129 }
130 lastMemoryUsage = JSON.stringify(
131 {moduleObjs: moduleObjs,
132 totalObjectClasses: data.totalObjectClasses}
133 );
134 }
136 function dictDiff(last, curr) {
137 var diff = {};
139 for (let name in last) {
140 var result = (curr[name] || 0) - last[name];
141 if (result)
142 diff[name] = (result > 0 ? "+" : "") + result;
143 }
144 for (let name in curr) {
145 var result = curr[name] - (last[name] || 0);
146 if (result)
147 diff[name] = (result > 0 ? "+" : "") + result;
148 }
149 return diff;
150 }
152 function reportMemoryUsage() {
153 if (!profileMemory) {
154 return emptyPromise();
155 }
157 return gcPromise().then((function () {
158 var mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
159 .getService(Ci.nsIMemoryReporterManager);
160 let count = 0;
161 function logReporter(process, path, kind, units, amount, description) {
162 print(((++count == 1) ? "\n" : "") + description + ": " + amount + "\n");
163 }
164 mgr.getReportsForThisProcess(logReporter, null);
166 var weakrefs = [info.weakref.get()
167 for each (info in memory.getObjects())];
168 weakrefs = [weakref for each (weakref in weakrefs) if (weakref)];
169 print("Tracked memory objects in testing sandbox: " + weakrefs.length + "\n");
170 }));
171 }
173 var gWeakrefInfo;
175 function checkMemory() {
176 return gcPromise().then(_ => {
177 let leaks = getPotentialLeaks();
179 let compartmentURLs = Object.keys(leaks.compartments).filter(function(url) {
180 return !(url in startLeaks.compartments);
181 });
183 let windowURLs = Object.keys(leaks.windows).filter(function(url) {
184 return !(url in startLeaks.windows);
185 });
187 for (let url of compartmentURLs)
188 console.warn("LEAKED", leaks.compartments[url]);
190 for (let url of windowURLs)
191 console.warn("LEAKED", leaks.windows[url]);
192 }).then(showResults);
193 }
195 function showResults() {
196 let { promise, resolve } = defer();
198 if (gWeakrefInfo) {
199 gWeakrefInfo.forEach(
200 function(info) {
201 var ref = info.weakref.get();
202 if (ref !== null) {
203 var data = ref.__url__ ? ref.__url__ : ref;
204 var warning = data == "[object Object]"
205 ? "[object " + data.constructor.name + "(" +
206 [p for (p in data)].join(", ") + ")]"
207 : data;
208 console.warn("LEAK", warning, info.bin);
209 }
210 }
211 );
212 }
214 onDone(results);
216 resolve();
217 return promise;
218 }
220 function cleanup() {
221 let coverObject = {};
222 try {
223 for (let name in loader.modules)
224 memory.track(loader.modules[name],
225 "module global scope: " + name);
226 memory.track(loader, "Cuddlefish Loader");
228 if (profileMemory) {
229 gWeakrefInfo = [{ weakref: info.weakref, bin: info.bin }
230 for each (info in memory.getObjects())];
231 }
233 loader.unload();
235 if (loader.globals.console.errorsLogged && !results.failed) {
236 results.failed++;
237 console.error("warnings and/or errors were logged.");
238 }
240 if (consoleListener.errorsLogged && !results.failed) {
241 console.warn(consoleListener.errorsLogged + " " +
242 "warnings or errors were logged to the " +
243 "platform's nsIConsoleService, which could " +
244 "be of no consequence; however, they could also " +
245 "be indicative of aberrant behavior.");
246 }
248 // read the code coverage object, if it exists, from CoverJS-moz
249 if (typeof loader.globals.global == "object") {
250 coverObject = loader.globals.global['__$coverObject'] || {};
251 }
253 consoleListener.errorsLogged = 0;
254 loader = null;
256 memory.gc();
257 }
258 catch (e) {
259 results.failed++;
260 console.error("unload.send() threw an exception.");
261 console.exception(e);
262 };
264 setTimeout(require('@test/options').checkMemory ? checkMemory : showResults, 1);
266 // dump the coverobject
267 if (Object.keys(coverObject).length){
268 const self = require('sdk/self');
269 const {pathFor} = require("sdk/system");
270 let file = require('sdk/io/file');
271 const {env} = require('sdk/system/environment');
272 console.log("CWD:", env.PWD);
273 let out = file.join(env.PWD,'coverstats-'+self.id+'.json');
274 console.log('coverstats:', out);
275 let outfh = file.open(out,'w');
276 outfh.write(JSON.stringify(coverObject,null,2));
277 outfh.flush();
278 outfh.close();
279 }
280 }
282 function getPotentialLeaks() {
283 memory.gc();
285 // Things we can assume are part of the platform and so aren't leaks
286 let WHITELIST_BASE_URLS = [
287 "chrome://",
288 "resource:///",
289 "resource://app/",
290 "resource://gre/",
291 "resource://gre-resources/",
292 "resource://pdf.js/",
293 "resource://pdf.js.components/",
294 "resource://services-common/",
295 "resource://services-crypto/",
296 "resource://services-sync/"
297 ];
299 let ioService = Cc["@mozilla.org/network/io-service;1"].
300 getService(Ci.nsIIOService);
301 let uri = ioService.newURI("chrome://global/content/", "UTF-8", null);
302 let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
303 getService(Ci.nsIChromeRegistry);
304 uri = chromeReg.convertChromeURL(uri);
305 let spec = uri.spec;
306 let pos = spec.indexOf("!/");
307 WHITELIST_BASE_URLS.push(spec.substring(0, pos + 2));
309 let zoneRegExp = new RegExp("^explicit/js-non-window/zones/zone[^/]+/compartment\\((.+)\\)");
310 let compartmentRegexp = new RegExp("^explicit/js-non-window/compartments/non-window-global/compartment\\((.+)\\)/");
311 let compartmentDetails = new RegExp("^([^,]+)(?:, (.+?))?(?: \\(from: (.*)\\))?$");
312 let windowRegexp = new RegExp("^explicit/window-objects/top\\((.*)\\)/active");
313 let windowDetails = new RegExp("^(.*), id=.*$");
315 function isPossibleLeak(item) {
316 if (!item.location)
317 return false;
319 for (let whitelist of WHITELIST_BASE_URLS) {
320 if (item.location.substring(0, whitelist.length) == whitelist)
321 return false;
322 }
324 return true;
325 }
327 let compartments = {};
328 let windows = {};
329 function logReporter(process, path, kind, units, amount, description) {
330 let matches;
332 if ((matches = compartmentRegexp.exec(path)) || (matches = zoneRegExp.exec(path))) {
333 if (matches[1] in compartments)
334 return;
336 let details = compartmentDetails.exec(matches[1]);
337 if (!details) {
338 console.error("Unable to parse compartment detail " + matches[1]);
339 return;
340 }
342 let item = {
343 path: matches[1],
344 principal: details[1],
345 location: details[2] ? details[2].replace("\\", "/", "g") : undefined,
346 source: details[3] ? details[3].split(" -> ").reverse() : undefined,
347 toString: function() this.location
348 };
350 if (!isPossibleLeak(item))
351 return;
353 compartments[matches[1]] = item;
354 return;
355 }
357 if (matches = windowRegexp.exec(path)) {
358 if (matches[1] in windows)
359 return;
361 let details = windowDetails.exec(matches[1]);
362 if (!details) {
363 console.error("Unable to parse window detail " + matches[1]);
364 return;
365 }
367 let item = {
368 path: matches[1],
369 location: details[1].replace("\\", "/", "g"),
370 source: [details[1].replace("\\", "/", "g")],
371 toString: function() this.location
372 };
374 if (!isPossibleLeak(item))
375 return;
377 windows[matches[1]] = item;
378 }
379 }
381 Cc["@mozilla.org/memory-reporter-manager;1"]
382 .getService(Ci.nsIMemoryReporterManager)
383 .getReportsForThisProcess(logReporter, null);
385 return { compartments: compartments, windows: windows };
386 }
388 function nextIteration(tests) {
389 if (tests) {
390 results.passed += tests.passed;
391 results.failed += tests.failed;
393 reportMemoryUsage().then(_ => {
394 let testRun = [];
395 for each (let test in tests.testRunSummary) {
396 let testCopy = {};
397 for (let info in test) {
398 testCopy[info] = test[info];
399 }
400 testRun.push(testCopy);
401 }
403 results.testRuns.push(testRun);
404 iterationsLeft--;
406 checkForEnd();
407 })
408 }
409 else {
410 checkForEnd();
411 }
412 }
414 function checkForEnd() {
415 if (iterationsLeft && (!stopOnError || results.failed == 0)) {
416 // Pass the loader which has a hooked console that doesn't dispatch
417 // errors to the JS console and avoid firing false alarm in our
418 // console listener
419 findAndRunTests(loader, nextIteration);
420 }
421 else {
422 setTimeout(cleanup, 0);
423 }
424 }
426 var POINTLESS_ERRORS = [
427 'Invalid chrome URI:',
428 'OpenGL LayerManager Initialized Succesfully.',
429 '[JavaScript Error: "TelemetryStopwatch:',
430 'reference to undefined property',
431 '[JavaScript Error: "The character encoding of the HTML document was ' +
432 'not declared.',
433 '[Javascript Warning: "Error: Failed to preserve wrapper of wrapped ' +
434 'native weak map key',
435 '[JavaScript Warning: "Duplicate resource declaration for',
436 'file: "chrome://browser/content/',
437 'file: "chrome://global/content/',
438 '[JavaScript Warning: "The character encoding of a framed document was ' +
439 'not declared.'
440 ];
442 var consoleListener = {
443 errorsLogged: 0,
444 observe: function(object) {
445 if (!(object instanceof Ci.nsIScriptError))
446 return;
447 this.errorsLogged++;
448 var message = object.QueryInterface(Ci.nsIConsoleMessage).message;
449 var pointless = [err for each (err in POINTLESS_ERRORS)
450 if (message.indexOf(err) >= 0)];
451 if (pointless.length == 0 && message)
452 testConsole.log(message);
453 }
454 };
456 function TestRunnerConsole(base, options) {
457 this.__proto__ = {
458 errorsLogged: 0,
459 warn: function warn() {
460 this.errorsLogged++;
461 base.warn.apply(base, arguments);
462 },
463 error: function error() {
464 this.errorsLogged++;
465 base.error.apply(base, arguments);
466 },
467 info: function info(first) {
468 if (options.verbose)
469 base.info.apply(base, arguments);
470 else
471 if (first == "pass:")
472 print(".");
473 },
474 __proto__: base
475 };
476 }
478 function stringify(arg) {
479 try {
480 return String(arg);
481 }
482 catch(ex) {
483 return "<toString() error>";
484 }
485 }
487 function stringifyArgs(args) {
488 return Array.map(args, stringify).join(" ");
489 }
491 function TestRunnerTinderboxConsole(base, options) {
492 this.base = base;
493 this.print = options.print;
494 this.verbose = options.verbose;
495 this.errorsLogged = 0;
497 // Binding all the public methods to an instance so that they can be used
498 // as callback / listener functions straightaway.
499 this.log = this.log.bind(this);
500 this.info = this.info.bind(this);
501 this.warn = this.warn.bind(this);
502 this.error = this.error.bind(this);
503 this.debug = this.debug.bind(this);
504 this.exception = this.exception.bind(this);
505 this.trace = this.trace.bind(this);
506 };
508 TestRunnerTinderboxConsole.prototype = {
509 testMessage: function testMessage(pass, expected, test, message) {
510 let type = "TEST-";
511 if (expected) {
512 if (pass)
513 type += "PASS";
514 else
515 type += "KNOWN-FAIL";
516 }
517 else {
518 this.errorsLogged++;
519 if (pass)
520 type += "UNEXPECTED-PASS";
521 else
522 type += "UNEXPECTED-FAIL";
523 }
525 this.print(type + " | " + test + " | " + message + "\n");
526 if (!expected)
527 this.trace();
528 },
530 log: function log() {
531 this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n");
532 },
534 info: function info(first) {
535 this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n");
536 },
538 warn: function warn() {
539 this.errorsLogged++;
540 this.print("TEST-UNEXPECTED-FAIL | " + stringifyArgs(arguments) + "\n");
541 },
543 error: function error() {
544 this.errorsLogged++;
545 this.print("TEST-UNEXPECTED-FAIL | " + stringifyArgs(arguments) + "\n");
546 this.base.error.apply(this.base, arguments);
547 },
549 debug: function debug() {
550 this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n");
551 },
553 exception: function exception(e) {
554 this.print("An exception occurred.\n" +
555 require("../console/traceback").format(e) + "\n" + e + "\n");
556 },
558 trace: function trace() {
559 var traceback = require("../console/traceback");
560 var stack = traceback.get();
561 stack.splice(-1, 1);
562 this.print("TEST-INFO | " + stringify(traceback.format(stack)) + "\n");
563 }
564 };
566 var runTests = exports.runTests = function runTests(options) {
567 iterationsLeft = options.iterations;
568 profileMemory = options.profileMemory;
569 stopOnError = options.stopOnError;
570 onDone = options.onDone;
571 print = options.print;
572 findAndRunTests = options.findAndRunTests;
574 try {
575 cService.registerListener(consoleListener);
576 print("Running tests on " + system.name + " " + system.version +
577 "/Gecko " + system.platformVersion + " (" +
578 system.id + ") under " +
579 system.platform + "/" + system.architecture + ".\n");
581 if (options.parseable)
582 testConsole = new TestRunnerTinderboxConsole(new PlainTextConsole(), options);
583 else
584 testConsole = new TestRunnerConsole(new PlainTextConsole(), options);
586 loader = Loader(module, {
587 console: testConsole,
588 global: {} // useful for storing things like coverage testing.
589 });
591 // Load these before getting initial leak stats as they will still be in
592 // memory when we check later
593 require("../deprecated/unit-test");
594 require("../deprecated/unit-test-finder");
595 startLeaks = getPotentialLeaks();
597 nextIteration();
598 } catch (e) {
599 let frames = fromException(e).reverse().reduce(function(frames, frame) {
600 if (frame.fileName.split("/").pop() === "unit-test-finder.js")
601 frames.done = true
602 if (!frames.done) frames.push(frame)
604 return frames
605 }, [])
607 let prototype = typeof(e) === "object" ? e.constructor.prototype :
608 Error.prototype;
609 let stack = serializeStack(frames.reverse());
611 let error = Object.create(prototype, {
612 message: { value: e.message, writable: true, configurable: true },
613 fileName: { value: e.fileName, writable: true, configurable: true },
614 lineNumber: { value: e.lineNumber, writable: true, configurable: true },
615 stack: { value: stack, writable: true, configurable: true },
616 toString: { value: function() String(e), writable: true, configurable: true },
617 });
619 print("Error: " + error + " \n " + format(error));
620 onDone({passed: 0, failed: 1});
621 }
622 };
624 unload(function() {
625 cService.unregisterListener(consoleListener);
626 });