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