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.
michael@0 | 1 | /* Any copyright is dedicated to the Public Domain. |
michael@0 | 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ |
michael@0 | 3 | |
michael@0 | 4 | const {utils: Cu} = Components; |
michael@0 | 5 | |
michael@0 | 6 | Cu.import("resource://gre/modules/NetUtil.jsm"); |
michael@0 | 7 | Cu.import("resource://gre/modules/osfile.jsm"); |
michael@0 | 8 | |
michael@0 | 9 | Cu.import("resource://gre/modules/Log.jsm"); |
michael@0 | 10 | |
michael@0 | 11 | let testFormatter = { |
michael@0 | 12 | format: function format(message) { |
michael@0 | 13 | return message.loggerName + "\t" + |
michael@0 | 14 | message.levelDesc + "\t" + |
michael@0 | 15 | message.message; |
michael@0 | 16 | } |
michael@0 | 17 | }; |
michael@0 | 18 | |
michael@0 | 19 | function MockAppender(formatter) { |
michael@0 | 20 | Log.Appender.call(this, formatter); |
michael@0 | 21 | this.messages = []; |
michael@0 | 22 | } |
michael@0 | 23 | MockAppender.prototype = { |
michael@0 | 24 | __proto__: Log.Appender.prototype, |
michael@0 | 25 | |
michael@0 | 26 | doAppend: function DApp_doAppend(message) { |
michael@0 | 27 | this.messages.push(message); |
michael@0 | 28 | } |
michael@0 | 29 | }; |
michael@0 | 30 | |
michael@0 | 31 | function run_test() { |
michael@0 | 32 | run_next_test(); |
michael@0 | 33 | } |
michael@0 | 34 | |
michael@0 | 35 | add_task(function test_Logger() { |
michael@0 | 36 | let log = Log.repository.getLogger("test.logger"); |
michael@0 | 37 | let appender = new MockAppender(new Log.BasicFormatter()); |
michael@0 | 38 | |
michael@0 | 39 | log.level = Log.Level.Debug; |
michael@0 | 40 | appender.level = Log.Level.Info; |
michael@0 | 41 | log.addAppender(appender); |
michael@0 | 42 | log.info("info test"); |
michael@0 | 43 | log.debug("this should be logged but not appended."); |
michael@0 | 44 | |
michael@0 | 45 | do_check_eq(appender.messages.length, 1); |
michael@0 | 46 | |
michael@0 | 47 | let msgRe = /\d+\ttest.logger\t\INFO\tinfo test/; |
michael@0 | 48 | do_check_true(msgRe.test(appender.messages[0])); |
michael@0 | 49 | }); |
michael@0 | 50 | |
michael@0 | 51 | add_task(function test_Logger_parent() { |
michael@0 | 52 | // Check whether parenting is correct |
michael@0 | 53 | let grandparentLog = Log.repository.getLogger("grandparent"); |
michael@0 | 54 | let childLog = Log.repository.getLogger("grandparent.parent.child"); |
michael@0 | 55 | do_check_eq(childLog.parent.name, "grandparent"); |
michael@0 | 56 | |
michael@0 | 57 | let parentLog = Log.repository.getLogger("grandparent.parent"); |
michael@0 | 58 | do_check_eq(childLog.parent.name, "grandparent.parent"); |
michael@0 | 59 | |
michael@0 | 60 | // Check that appends are exactly in scope |
michael@0 | 61 | let gpAppender = new MockAppender(new Log.BasicFormatter()); |
michael@0 | 62 | gpAppender.level = Log.Level.Info; |
michael@0 | 63 | grandparentLog.addAppender(gpAppender); |
michael@0 | 64 | childLog.info("child info test"); |
michael@0 | 65 | Log.repository.rootLogger.info("this shouldn't show up in gpAppender"); |
michael@0 | 66 | |
michael@0 | 67 | do_check_eq(gpAppender.messages.length, 1); |
michael@0 | 68 | do_check_true(gpAppender.messages[0].indexOf("child info test") > 0); |
michael@0 | 69 | }); |
michael@0 | 70 | |
michael@0 | 71 | add_test(function test_LoggerWithMessagePrefix() { |
michael@0 | 72 | let log = Log.repository.getLogger("test.logger.prefix"); |
michael@0 | 73 | let appender = new MockAppender(new Log.MessageOnlyFormatter()); |
michael@0 | 74 | log.addAppender(appender); |
michael@0 | 75 | |
michael@0 | 76 | let prefixed = Log.repository.getLoggerWithMessagePrefix( |
michael@0 | 77 | "test.logger.prefix", "prefix: "); |
michael@0 | 78 | |
michael@0 | 79 | log.warn("no prefix"); |
michael@0 | 80 | prefixed.warn("with prefix"); |
michael@0 | 81 | |
michael@0 | 82 | Assert.equal(appender.messages.length, 2, "2 messages were logged."); |
michael@0 | 83 | Assert.deepEqual(appender.messages, [ |
michael@0 | 84 | "no prefix", |
michael@0 | 85 | "prefix: with prefix", |
michael@0 | 86 | ], "Prefix logger works."); |
michael@0 | 87 | |
michael@0 | 88 | run_next_test(); |
michael@0 | 89 | }); |
michael@0 | 90 | |
michael@0 | 91 | /* |
michael@0 | 92 | * A utility method for checking object equivalence. |
michael@0 | 93 | * Fields with a reqular expression value in expected will be tested |
michael@0 | 94 | * against the corresponding value in actual. Otherwise objects |
michael@0 | 95 | * are expected to have the same keys and equal values. |
michael@0 | 96 | */ |
michael@0 | 97 | function checkObjects(expected, actual) { |
michael@0 | 98 | do_check_true(expected instanceof Object); |
michael@0 | 99 | do_check_true(actual instanceof Object); |
michael@0 | 100 | for (let key in expected) { |
michael@0 | 101 | do_check_neq(actual[key], undefined); |
michael@0 | 102 | if (expected[key] instanceof RegExp) { |
michael@0 | 103 | do_check_true(expected[key].test(actual[key].toString())); |
michael@0 | 104 | } else { |
michael@0 | 105 | if (expected[key] instanceof Object) { |
michael@0 | 106 | checkObjects(expected[key], actual[key]); |
michael@0 | 107 | } else { |
michael@0 | 108 | do_check_eq(expected[key], actual[key]); |
michael@0 | 109 | } |
michael@0 | 110 | } |
michael@0 | 111 | } |
michael@0 | 112 | |
michael@0 | 113 | for (let key in actual) { |
michael@0 | 114 | do_check_neq(expected[key], undefined); |
michael@0 | 115 | } |
michael@0 | 116 | } |
michael@0 | 117 | |
michael@0 | 118 | add_task(function test_StructuredLogCommands() { |
michael@0 | 119 | let appender = new MockAppender(new Log.StructuredFormatter()); |
michael@0 | 120 | let logger = Log.repository.getLogger("test.StructuredOutput"); |
michael@0 | 121 | logger.addAppender(appender); |
michael@0 | 122 | logger.level = Log.Level.Info; |
michael@0 | 123 | |
michael@0 | 124 | logger.logStructured("test_message", {_message: "message string one"}); |
michael@0 | 125 | logger.logStructured("test_message", {_message: "message string two", |
michael@0 | 126 | _level: "ERROR", |
michael@0 | 127 | source_file: "test_Log.js"}); |
michael@0 | 128 | logger.logStructured("test_message"); |
michael@0 | 129 | logger.logStructured("test_message", {source_file: "test_Log.js", |
michael@0 | 130 | message_position: 4}); |
michael@0 | 131 | |
michael@0 | 132 | let messageOne = {"_time": /\d+/, |
michael@0 | 133 | "_namespace": "test.StructuredOutput", |
michael@0 | 134 | "_level": "INFO", |
michael@0 | 135 | "_message": "message string one", |
michael@0 | 136 | "action": "test_message"}; |
michael@0 | 137 | |
michael@0 | 138 | let messageTwo = {"_time": /\d+/, |
michael@0 | 139 | "_namespace": "test.StructuredOutput", |
michael@0 | 140 | "_level": "ERROR", |
michael@0 | 141 | "_message": "message string two", |
michael@0 | 142 | "action": "test_message", |
michael@0 | 143 | "source_file": "test_Log.js"}; |
michael@0 | 144 | |
michael@0 | 145 | let messageThree = {"_time": /\d+/, |
michael@0 | 146 | "_namespace": "test.StructuredOutput", |
michael@0 | 147 | "_level": "INFO", |
michael@0 | 148 | "action": "test_message"}; |
michael@0 | 149 | |
michael@0 | 150 | let messageFour = {"_time": /\d+/, |
michael@0 | 151 | "_namespace": "test.StructuredOutput", |
michael@0 | 152 | "_level": "INFO", |
michael@0 | 153 | "action": "test_message", |
michael@0 | 154 | "source_file": "test_Log.js", |
michael@0 | 155 | "message_position": 4}; |
michael@0 | 156 | |
michael@0 | 157 | checkObjects(messageOne, JSON.parse(appender.messages[0])); |
michael@0 | 158 | checkObjects(messageTwo, JSON.parse(appender.messages[1])); |
michael@0 | 159 | checkObjects(messageThree, JSON.parse(appender.messages[2])); |
michael@0 | 160 | checkObjects(messageFour, JSON.parse(appender.messages[3])); |
michael@0 | 161 | |
michael@0 | 162 | let errored = false; |
michael@0 | 163 | try { |
michael@0 | 164 | logger.logStructured("", {_message: "invalid message"}); |
michael@0 | 165 | } catch (e) { |
michael@0 | 166 | errored = true; |
michael@0 | 167 | do_check_eq(e, "An action is required when logging a structured message."); |
michael@0 | 168 | } finally { |
michael@0 | 169 | do_check_true(errored); |
michael@0 | 170 | } |
michael@0 | 171 | |
michael@0 | 172 | let errored = false; |
michael@0 | 173 | try { |
michael@0 | 174 | logger.logStructured("message_action", "invalid params"); |
michael@0 | 175 | } catch (e) { |
michael@0 | 176 | errored = true; |
michael@0 | 177 | do_check_eq(e, "The params argument is required to be an object."); |
michael@0 | 178 | } finally { |
michael@0 | 179 | do_check_true(errored); |
michael@0 | 180 | } |
michael@0 | 181 | |
michael@0 | 182 | // Logging with unstructured interface should produce the same messages |
michael@0 | 183 | // as the structured interface for these cases. |
michael@0 | 184 | let appender = new MockAppender(new Log.StructuredFormatter()); |
michael@0 | 185 | let logger = Log.repository.getLogger("test.StructuredOutput1"); |
michael@0 | 186 | messageOne._namespace = "test.StructuredOutput1"; |
michael@0 | 187 | messageTwo._namespace = "test.StructuredOutput1"; |
michael@0 | 188 | logger.addAppender(appender); |
michael@0 | 189 | logger.level = Log.Level.All; |
michael@0 | 190 | logger.info("message string one", {action: "test_message"}); |
michael@0 | 191 | logger.error("message string two", {action: "test_message", |
michael@0 | 192 | source_file: "test_Log.js"}); |
michael@0 | 193 | |
michael@0 | 194 | checkObjects(messageOne, JSON.parse(appender.messages[0])); |
michael@0 | 195 | checkObjects(messageTwo, JSON.parse(appender.messages[1])); |
michael@0 | 196 | }); |
michael@0 | 197 | |
michael@0 | 198 | add_task(function test_StorageStreamAppender() { |
michael@0 | 199 | let appender = new Log.StorageStreamAppender(testFormatter); |
michael@0 | 200 | do_check_eq(appender.getInputStream(), null); |
michael@0 | 201 | |
michael@0 | 202 | // Log to the storage stream and verify the log was written and can be |
michael@0 | 203 | // read back. |
michael@0 | 204 | let logger = Log.repository.getLogger("test.StorageStreamAppender"); |
michael@0 | 205 | logger.addAppender(appender); |
michael@0 | 206 | logger.info("OHAI"); |
michael@0 | 207 | let inputStream = appender.getInputStream(); |
michael@0 | 208 | let data = NetUtil.readInputStreamToString(inputStream, |
michael@0 | 209 | inputStream.available()); |
michael@0 | 210 | do_check_eq(data, "test.StorageStreamAppender\tINFO\tOHAI\n"); |
michael@0 | 211 | |
michael@0 | 212 | // We can read it again even. |
michael@0 | 213 | let sndInputStream = appender.getInputStream(); |
michael@0 | 214 | let sameData = NetUtil.readInputStreamToString(sndInputStream, |
michael@0 | 215 | sndInputStream.available()); |
michael@0 | 216 | do_check_eq(data, sameData); |
michael@0 | 217 | |
michael@0 | 218 | // Reset the appender and log some more. |
michael@0 | 219 | appender.reset(); |
michael@0 | 220 | do_check_eq(appender.getInputStream(), null); |
michael@0 | 221 | logger.debug("wut?!?"); |
michael@0 | 222 | inputStream = appender.getInputStream(); |
michael@0 | 223 | data = NetUtil.readInputStreamToString(inputStream, |
michael@0 | 224 | inputStream.available()); |
michael@0 | 225 | do_check_eq(data, "test.StorageStreamAppender\tDEBUG\twut?!?\n"); |
michael@0 | 226 | }); |
michael@0 | 227 | |
michael@0 | 228 | function fileContents(path) { |
michael@0 | 229 | let decoder = new TextDecoder(); |
michael@0 | 230 | return OS.File.read(path).then(array => { |
michael@0 | 231 | return decoder.decode(array); |
michael@0 | 232 | }); |
michael@0 | 233 | } |
michael@0 | 234 | |
michael@0 | 235 | add_task(function test_FileAppender() { |
michael@0 | 236 | // This directory does not exist yet |
michael@0 | 237 | let dir = OS.Path.join(do_get_profile().path, "test_Log"); |
michael@0 | 238 | do_check_false(yield OS.File.exists(dir)); |
michael@0 | 239 | let path = OS.Path.join(dir, "test_FileAppender"); |
michael@0 | 240 | let appender = new Log.FileAppender(path, testFormatter); |
michael@0 | 241 | let logger = Log.repository.getLogger("test.FileAppender"); |
michael@0 | 242 | logger.addAppender(appender); |
michael@0 | 243 | |
michael@0 | 244 | // Logging to a file that can't be created won't do harm. |
michael@0 | 245 | do_check_false(yield OS.File.exists(path)); |
michael@0 | 246 | logger.info("OHAI!"); |
michael@0 | 247 | |
michael@0 | 248 | yield OS.File.makeDir(dir); |
michael@0 | 249 | logger.info("OHAI"); |
michael@0 | 250 | yield appender._lastWritePromise; |
michael@0 | 251 | |
michael@0 | 252 | do_check_eq((yield fileContents(path)), |
michael@0 | 253 | "test.FileAppender\tINFO\tOHAI\n"); |
michael@0 | 254 | |
michael@0 | 255 | logger.info("OHAI"); |
michael@0 | 256 | yield appender._lastWritePromise; |
michael@0 | 257 | |
michael@0 | 258 | do_check_eq((yield fileContents(path)), |
michael@0 | 259 | "test.FileAppender\tINFO\tOHAI\n" + |
michael@0 | 260 | "test.FileAppender\tINFO\tOHAI\n"); |
michael@0 | 261 | |
michael@0 | 262 | // Reset the appender and log some more. |
michael@0 | 263 | yield appender.reset(); |
michael@0 | 264 | do_check_false(yield OS.File.exists(path)); |
michael@0 | 265 | |
michael@0 | 266 | logger.debug("O RLY?!?"); |
michael@0 | 267 | yield appender._lastWritePromise; |
michael@0 | 268 | do_check_eq((yield fileContents(path)), |
michael@0 | 269 | "test.FileAppender\tDEBUG\tO RLY?!?\n"); |
michael@0 | 270 | |
michael@0 | 271 | yield appender.reset(); |
michael@0 | 272 | logger.debug("1"); |
michael@0 | 273 | logger.info("2"); |
michael@0 | 274 | logger.info("3"); |
michael@0 | 275 | logger.info("4"); |
michael@0 | 276 | logger.info("5"); |
michael@0 | 277 | // Waiting on only the last promise should account for all of these. |
michael@0 | 278 | yield appender._lastWritePromise; |
michael@0 | 279 | |
michael@0 | 280 | // Messages ought to be logged in order. |
michael@0 | 281 | do_check_eq((yield fileContents(path)), |
michael@0 | 282 | "test.FileAppender\tDEBUG\t1\n" + |
michael@0 | 283 | "test.FileAppender\tINFO\t2\n" + |
michael@0 | 284 | "test.FileAppender\tINFO\t3\n" + |
michael@0 | 285 | "test.FileAppender\tINFO\t4\n" + |
michael@0 | 286 | "test.FileAppender\tINFO\t5\n"); |
michael@0 | 287 | }); |
michael@0 | 288 | |
michael@0 | 289 | add_task(function test_BoundedFileAppender() { |
michael@0 | 290 | let dir = OS.Path.join(do_get_profile().path, "test_Log"); |
michael@0 | 291 | let path = OS.Path.join(dir, "test_BoundedFileAppender"); |
michael@0 | 292 | // This appender will hold about two lines at a time. |
michael@0 | 293 | let appender = new Log.BoundedFileAppender(path, testFormatter, 40); |
michael@0 | 294 | let logger = Log.repository.getLogger("test.BoundedFileAppender"); |
michael@0 | 295 | logger.addAppender(appender); |
michael@0 | 296 | |
michael@0 | 297 | logger.info("ONE"); |
michael@0 | 298 | logger.info("TWO"); |
michael@0 | 299 | yield appender._lastWritePromise; |
michael@0 | 300 | |
michael@0 | 301 | do_check_eq((yield fileContents(path)), |
michael@0 | 302 | "test.BoundedFileAppender\tINFO\tONE\n" + |
michael@0 | 303 | "test.BoundedFileAppender\tINFO\tTWO\n"); |
michael@0 | 304 | |
michael@0 | 305 | logger.info("THREE"); |
michael@0 | 306 | logger.info("FOUR"); |
michael@0 | 307 | |
michael@0 | 308 | do_check_neq(appender._removeFilePromise, undefined); |
michael@0 | 309 | yield appender._removeFilePromise; |
michael@0 | 310 | yield appender._lastWritePromise; |
michael@0 | 311 | |
michael@0 | 312 | do_check_eq((yield fileContents(path)), |
michael@0 | 313 | "test.BoundedFileAppender\tINFO\tTHREE\n" + |
michael@0 | 314 | "test.BoundedFileAppender\tINFO\tFOUR\n"); |
michael@0 | 315 | |
michael@0 | 316 | yield appender.reset(); |
michael@0 | 317 | logger.info("ONE"); |
michael@0 | 318 | logger.info("TWO"); |
michael@0 | 319 | logger.info("THREE"); |
michael@0 | 320 | logger.info("FOUR"); |
michael@0 | 321 | |
michael@0 | 322 | do_check_neq(appender._removeFilePromise, undefined); |
michael@0 | 323 | yield appender._removeFilePromise; |
michael@0 | 324 | yield appender._lastWritePromise; |
michael@0 | 325 | |
michael@0 | 326 | do_check_eq((yield fileContents(path)), |
michael@0 | 327 | "test.BoundedFileAppender\tINFO\tTHREE\n" + |
michael@0 | 328 | "test.BoundedFileAppender\tINFO\tFOUR\n"); |
michael@0 | 329 | |
michael@0 | 330 | }); |
michael@0 | 331 | |
michael@0 | 332 | /* |
michael@0 | 333 | * Test parameter formatting. |
michael@0 | 334 | */ |
michael@0 | 335 | add_task(function log_message_with_params() { |
michael@0 | 336 | let formatter = new Log.BasicFormatter(); |
michael@0 | 337 | |
michael@0 | 338 | function formatMessage(text, params) { |
michael@0 | 339 | let full = formatter.format(new Log.LogMessage("test.logger", Log.Level.Warn, text, params)); |
michael@0 | 340 | return full.split("\t")[3]; |
michael@0 | 341 | } |
michael@0 | 342 | |
michael@0 | 343 | // Strings are substituted directly. |
michael@0 | 344 | do_check_eq(formatMessage("String is ${foo}", {foo: "bar"}), |
michael@0 | 345 | "String is bar"); |
michael@0 | 346 | |
michael@0 | 347 | // Numbers are substituted. |
michael@0 | 348 | do_check_eq(formatMessage("Number is ${number}", {number: 47}), |
michael@0 | 349 | "Number is 47") |
michael@0 | 350 | |
michael@0 | 351 | // The entire params object is JSON-formatted and substituted. |
michael@0 | 352 | do_check_eq(formatMessage("Object is ${}", {foo: "bar"}), |
michael@0 | 353 | 'Object is {"foo":"bar"}'); |
michael@0 | 354 | |
michael@0 | 355 | // An object nested inside params is JSON-formatted and substituted. |
michael@0 | 356 | do_check_eq(formatMessage("Sub object is ${sub}", {sub: {foo: "bar"}}), |
michael@0 | 357 | 'Sub object is {"foo":"bar"}'); |
michael@0 | 358 | |
michael@0 | 359 | // The substitution field is missing from params. Leave the placeholder behind |
michael@0 | 360 | // to make the mistake obvious. |
michael@0 | 361 | do_check_eq(formatMessage("Missing object is ${missing}", {}), |
michael@0 | 362 | 'Missing object is ${missing}'); |
michael@0 | 363 | |
michael@0 | 364 | // Make sure we don't treat the parameter name 'false' as a falsey value. |
michael@0 | 365 | do_check_eq(formatMessage("False is ${false}", {false: true}), |
michael@0 | 366 | 'False is true'); |
michael@0 | 367 | |
michael@0 | 368 | // If an object has a .toJSON method, the formatter uses it. |
michael@0 | 369 | let ob = function() {}; |
michael@0 | 370 | ob.toJSON = function() {return {sneaky: "value"}}; |
michael@0 | 371 | do_check_eq(formatMessage("JSON is ${sub}", {sub: ob}), |
michael@0 | 372 | 'JSON is {"sneaky":"value"}'); |
michael@0 | 373 | |
michael@0 | 374 | // Fall back to .toSource() if JSON.stringify() fails on an object. |
michael@0 | 375 | let ob = function() {}; |
michael@0 | 376 | ob.toJSON = function() {throw "oh noes JSON"}; |
michael@0 | 377 | do_check_eq(formatMessage("Fail is ${sub}", {sub: ob}), |
michael@0 | 378 | 'Fail is (function () {})'); |
michael@0 | 379 | |
michael@0 | 380 | // Fall back to .toString if both .toJSON and .toSource fail. |
michael@0 | 381 | ob.toSource = function() {throw "oh noes SOURCE"}; |
michael@0 | 382 | do_check_eq(formatMessage("Fail is ${sub}", {sub: ob}), |
michael@0 | 383 | 'Fail is function () {}'); |
michael@0 | 384 | |
michael@0 | 385 | // Fall back to '[object]' if .toJSON, .toSource and .toString fail. |
michael@0 | 386 | ob.toString = function() {throw "oh noes STRING"}; |
michael@0 | 387 | do_check_eq(formatMessage("Fail is ${sub}", {sub: ob}), |
michael@0 | 388 | 'Fail is [object]'); |
michael@0 | 389 | |
michael@0 | 390 | // If params are passed but there are no substitution in the text |
michael@0 | 391 | // we JSON format and append the entire parameters object. |
michael@0 | 392 | do_check_eq(formatMessage("Text with no subs", {a: "b", c: "d"}), |
michael@0 | 393 | 'Text with no subs: {"a":"b","c":"d"}'); |
michael@0 | 394 | |
michael@0 | 395 | // If we substitute one parameter but not the other, |
michael@0 | 396 | // we ignore any params that aren't substituted. |
michael@0 | 397 | do_check_eq(formatMessage("Text with partial sub ${a}", {a: "b", c: "d"}), |
michael@0 | 398 | 'Text with partial sub b'); |
michael@0 | 399 | |
michael@0 | 400 | // We don't format internal fields stored in params. |
michael@0 | 401 | do_check_eq(formatMessage("Params with _ ${}", {a: "b", _c: "d", _level:20, _message:"froo", |
michael@0 | 402 | _time:123456, _namespace:"here.there"}), |
michael@0 | 403 | 'Params with _ {"a":"b","_c":"d"}'); |
michael@0 | 404 | |
michael@0 | 405 | // Don't print an empty params holder if all params are internal. |
michael@0 | 406 | do_check_eq(formatMessage("All params internal", {_level:20, _message:"froo", |
michael@0 | 407 | _time:123456, _namespace:"here.there"}), |
michael@0 | 408 | 'All params internal'); |
michael@0 | 409 | |
michael@0 | 410 | // Format params with null and undefined values. |
michael@0 | 411 | do_check_eq(formatMessage("Null ${n} undefined ${u}", {n: null, u: undefined}), |
michael@0 | 412 | 'Null null undefined undefined'); |
michael@0 | 413 | |
michael@0 | 414 | // Format params with number, bool, and Object/String type. |
michael@0 | 415 | do_check_eq(formatMessage("number ${n} boolean ${b} boxed Boolean ${bx} String ${s}", |
michael@0 | 416 | {n: 45, b: false, bx: new Boolean(true), s: new String("whatevs")}), |
michael@0 | 417 | 'number 45 boolean false boxed Boolean true String whatevs'); |
michael@0 | 418 | |
michael@0 | 419 | /* |
michael@0 | 420 | * Check that errors get special formatting if they're formatted directly as |
michael@0 | 421 | * a named param or they're the only param, but not if they're a field in a |
michael@0 | 422 | * larger structure. |
michael@0 | 423 | */ |
michael@0 | 424 | let err = Components.Exception("test exception", Components.results.NS_ERROR_FAILURE); |
michael@0 | 425 | let str = formatMessage("Exception is ${}", err); |
michael@0 | 426 | do_check_true(str.contains('Exception is [Exception... "test exception"')); |
michael@0 | 427 | do_check_true(str.contains("(NS_ERROR_FAILURE)")); |
michael@0 | 428 | let str = formatMessage("Exception is", err); |
michael@0 | 429 | do_check_true(str.contains('Exception is: [Exception... "test exception"')); |
michael@0 | 430 | let str = formatMessage("Exception is ${error}", {error: err}); |
michael@0 | 431 | do_check_true(str.contains('Exception is [Exception... "test exception"')); |
michael@0 | 432 | let str = formatMessage("Exception is", {_error: err}); |
michael@0 | 433 | do_print(str); |
michael@0 | 434 | // Exceptions buried inside objects are formatted badly. |
michael@0 | 435 | do_check_true(str.contains('Exception is: {"_error":{}')); |
michael@0 | 436 | // If the message text is null, the message contains only the formatted params object. |
michael@0 | 437 | let str = formatMessage(null, err); |
michael@0 | 438 | do_check_true(str.startsWith('[Exception... "test exception"')); |
michael@0 | 439 | // If the text is null and 'params' is a String object, the message is exactly that string. |
michael@0 | 440 | let str = formatMessage(null, new String("String in place of params")); |
michael@0 | 441 | do_check_eq(str, "String in place of params"); |
michael@0 | 442 | |
michael@0 | 443 | // We use object.valueOf() internally; make sure a broken valueOf() method |
michael@0 | 444 | // doesn't cause the logger to fail. |
michael@0 | 445 | let vOf = {a: 1, valueOf: function() {throw "oh noes valueOf"}}; |
michael@0 | 446 | do_check_eq(formatMessage("Broken valueOf ${}", vOf), |
michael@0 | 447 | 'Broken valueOf ({a:1, valueOf:(function () {throw "oh noes valueOf"})})'); |
michael@0 | 448 | |
michael@0 | 449 | // Test edge cases of bad data to formatter: |
michael@0 | 450 | // If 'params' is not an object, format it as a basic type. |
michael@0 | 451 | do_check_eq(formatMessage("non-object no subst", 1), |
michael@0 | 452 | 'non-object no subst: 1'); |
michael@0 | 453 | do_check_eq(formatMessage("non-object all subst ${}", 2), |
michael@0 | 454 | 'non-object all subst 2'); |
michael@0 | 455 | // If 'params' is not an object, no named substitutions can succeed; |
michael@0 | 456 | // therefore we leave the placeholder and append the formatted params. |
michael@0 | 457 | do_check_eq(formatMessage("non-object named subst ${junk} space", 3), |
michael@0 | 458 | 'non-object named subst ${junk} space: 3'); |
michael@0 | 459 | // If there are no params, we leave behind the placeholders in the text. |
michael@0 | 460 | do_check_eq(formatMessage("no params ${missing}", undefined), |
michael@0 | 461 | 'no params ${missing}'); |
michael@0 | 462 | // If params doesn't contain any of the tags requested in the text, |
michael@0 | 463 | // we leave them all behind and append the formatted params. |
michael@0 | 464 | do_check_eq(formatMessage("object missing tag ${missing} space", {mising: "not here"}), |
michael@0 | 465 | 'object missing tag ${missing} space: {"mising":"not here"}'); |
michael@0 | 466 | // If we are given null text and no params, the resulting formatted message is empty. |
michael@0 | 467 | do_check_eq(formatMessage(null), ''); |
michael@0 | 468 | }); |
michael@0 | 469 | |
michael@0 | 470 | /* |
michael@0 | 471 | * If we call a log function with a non-string object in place of the text |
michael@0 | 472 | * argument, and no parameters, treat that the same as logging empty text |
michael@0 | 473 | * with the object argument as parameters. This makes the log useful when the |
michael@0 | 474 | * caller does "catch(err) {logger.error(err)}" |
michael@0 | 475 | */ |
michael@0 | 476 | add_task(function test_log_err_only() { |
michael@0 | 477 | let log = Log.repository.getLogger("error.only"); |
michael@0 | 478 | let testFormatter = { format: msg => msg }; |
michael@0 | 479 | let appender = new MockAppender(testFormatter); |
michael@0 | 480 | log.addAppender(appender); |
michael@0 | 481 | |
michael@0 | 482 | /* |
michael@0 | 483 | * Check that log.error(err) is treated the same as |
michael@0 | 484 | * log.error(null, err) by the logMessage constructor; the formatMessage() |
michael@0 | 485 | * tests above ensure that the combination of null text and an error object |
michael@0 | 486 | * is formatted correctly. |
michael@0 | 487 | */ |
michael@0 | 488 | try { |
michael@0 | 489 | eval("javascript syntax error"); |
michael@0 | 490 | } |
michael@0 | 491 | catch (e) { |
michael@0 | 492 | log.error(e); |
michael@0 | 493 | msg = appender.messages.pop(); |
michael@0 | 494 | do_check_eq(msg.message, null); |
michael@0 | 495 | do_check_eq(msg.params, e); |
michael@0 | 496 | } |
michael@0 | 497 | }); |
michael@0 | 498 | |
michael@0 | 499 | /* |
michael@0 | 500 | * Test logStructured() messages through basic formatter. |
michael@0 | 501 | */ |
michael@0 | 502 | add_task(function test_structured_basic() { |
michael@0 | 503 | let log = Log.repository.getLogger("test.logger"); |
michael@0 | 504 | let appender = new MockAppender(new Log.BasicFormatter()); |
michael@0 | 505 | |
michael@0 | 506 | log.level = Log.Level.Info; |
michael@0 | 507 | appender.level = Log.Level.Info; |
michael@0 | 508 | log.addAppender(appender); |
michael@0 | 509 | |
michael@0 | 510 | // A structured entry with no _message is treated the same as log./level/(null, params) |
michael@0 | 511 | // except the 'action' field is added to the object. |
michael@0 | 512 | log.logStructured("action", {data: "structure"}); |
michael@0 | 513 | do_check_eq(appender.messages.length, 1); |
michael@0 | 514 | do_check_true(appender.messages[0].contains('{"data":"structure","action":"action"}')); |
michael@0 | 515 | |
michael@0 | 516 | // A structured entry with _message and substitution is treated the same as |
michael@0 | 517 | // log./level/(null, params). |
michael@0 | 518 | log.logStructured("action", {_message: "Structured sub ${data}", data: "structure"}); |
michael@0 | 519 | do_check_eq(appender.messages.length, 2); |
michael@0 | 520 | do_print(appender.messages[1]); |
michael@0 | 521 | do_check_true(appender.messages[1].contains('Structured sub structure')); |
michael@0 | 522 | }); |
michael@0 | 523 | |
michael@0 | 524 | /* |
michael@0 | 525 | * Test that all the basic logger methods pass the message and params through to the appender. |
michael@0 | 526 | */ |
michael@0 | 527 | add_task(function log_message_with_params() { |
michael@0 | 528 | let log = Log.repository.getLogger("error.logger"); |
michael@0 | 529 | let testFormatter = { format: msg => msg }; |
michael@0 | 530 | let appender = new MockAppender(testFormatter); |
michael@0 | 531 | log.addAppender(appender); |
michael@0 | 532 | |
michael@0 | 533 | let testParams = {a:1, b:2}; |
michael@0 | 534 | log.fatal("Test fatal", testParams); |
michael@0 | 535 | log.error("Test error", testParams); |
michael@0 | 536 | log.warn("Test warn", testParams); |
michael@0 | 537 | log.info("Test info", testParams); |
michael@0 | 538 | log.config("Test config", testParams); |
michael@0 | 539 | log.debug("Test debug", testParams); |
michael@0 | 540 | log.trace("Test trace", testParams); |
michael@0 | 541 | do_check_eq(appender.messages.length, 7); |
michael@0 | 542 | for (let msg of appender.messages) { |
michael@0 | 543 | do_check_true(msg.params === testParams); |
michael@0 | 544 | do_check_true(msg.message.startsWith("Test ")); |
michael@0 | 545 | } |
michael@0 | 546 | }); |
michael@0 | 547 | |
michael@0 | 548 | /* |
michael@0 | 549 | * Check that we format JS Errors reasonably. |
michael@0 | 550 | */ |
michael@0 | 551 | add_task(function format_errors() { |
michael@0 | 552 | let pFormat = new Log.ParameterFormatter(); |
michael@0 | 553 | |
michael@0 | 554 | // Test that subclasses of Error are recognized as errors. |
michael@0 | 555 | err = new ReferenceError("Ref Error", "ERROR_FILE", 28); |
michael@0 | 556 | str = pFormat.format(err); |
michael@0 | 557 | do_check_true(str.contains("ReferenceError")); |
michael@0 | 558 | do_check_true(str.contains("ERROR_FILE:28")); |
michael@0 | 559 | do_check_true(str.contains("Ref Error")); |
michael@0 | 560 | |
michael@0 | 561 | // Test that JS-generated Errors are recognized and formatted. |
michael@0 | 562 | try { |
michael@0 | 563 | eval("javascript syntax error"); |
michael@0 | 564 | } |
michael@0 | 565 | catch (e) { |
michael@0 | 566 | str = pFormat.format(e); |
michael@0 | 567 | do_check_true(str.contains("SyntaxError: missing ;")); |
michael@0 | 568 | // Make sure we identified it as an Error and formatted the error location as |
michael@0 | 569 | // lineNumber:columnNumber. |
michael@0 | 570 | do_check_true(str.contains(":1:11)")); |
michael@0 | 571 | } |
michael@0 | 572 | }); |