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