testing/mochitest/tests/SimpleTest/SimpleTest.js

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:09a9a67d2a8c
1 /* -*- js-indent-level: 4; tab-width: 4; indent-tabs-mode: nil -*- */
2 /* vim:set ts=4 sw=4 sts=4 et: */
3 /**
4 * SimpleTest, a partial Test.Simple/Test.More API compatible test library.
5 *
6 * Why?
7 *
8 * Test.Simple doesn't work on IE < 6.
9 * TODO:
10 * * Support the Test.Simple API used by MochiKit, to be able to test MochiKit
11 * itself against IE 5.5
12 *
13 * NOTE: Pay attention to cross-browser compatibility in this file. For
14 * instance, do not use const or JS > 1.5 features which are not yet
15 * implemented everywhere.
16 *
17 **/
18
19 var SimpleTest = { };
20 var parentRunner = null;
21
22 // In normal test runs, the window that has a TestRunner in its parent is
23 // the primary window. In single test runs, if there is no parent and there
24 // is no opener then it is the primary window.
25 var isSingleTestRun = (parent == window && !opener)
26 var isPrimaryTestWindow = !!parent.TestRunner || isSingleTestRun;
27
28 // Finds the TestRunner for this test run and the SpecialPowers object (in
29 // case it is not defined) from a parent/opener window.
30 //
31 // Finding the SpecialPowers object is needed when we have ChromePowers in
32 // harness.xul and we need SpecialPowers in the iframe, and also for tests
33 // like test_focus.xul where we open a window which opens another window which
34 // includes SimpleTest.js.
35 (function() {
36 function ancestor(w) {
37 return w.parent != w ? w.parent : w.opener;
38 }
39
40 var w = ancestor(window);
41 while (w && (!parentRunner || !window.SpecialPowers)) {
42 if (!parentRunner) {
43 parentRunner = w.TestRunner;
44 if (!parentRunner && w.wrappedJSObject) {
45 parentRunner = w.wrappedJSObject.TestRunner;
46 }
47 }
48 if (!window.SpecialPowers) {
49 window.SpecialPowers = w.SpecialPowers;
50 }
51 w = ancestor(w);
52 }
53 })();
54
55 /* Helper functions pulled out of various MochiKit modules */
56 if (typeof(repr) == 'undefined') {
57 this.repr = function(o) {
58 if (typeof(o) == "undefined") {
59 return "undefined";
60 } else if (o === null) {
61 return "null";
62 }
63 try {
64 if (typeof(o.__repr__) == 'function') {
65 return o.__repr__();
66 } else if (typeof(o.repr) == 'function' && o.repr != arguments.callee) {
67 return o.repr();
68 }
69 } catch (e) {
70 }
71 try {
72 if (typeof(o.NAME) == 'string' && (
73 o.toString == Function.prototype.toString ||
74 o.toString == Object.prototype.toString
75 )) {
76 return o.NAME;
77 }
78 } catch (e) {
79 }
80 try {
81 var ostring = (o + "");
82 } catch (e) {
83 return "[" + typeof(o) + "]";
84 }
85 if (typeof(o) == "function") {
86 o = ostring.replace(/^\s+/, "");
87 var idx = o.indexOf("{");
88 if (idx != -1) {
89 o = o.substr(0, idx) + "{...}";
90 }
91 }
92 return ostring;
93 };
94 }
95
96 /* This returns a function that applies the previously given parameters.
97 * This is used by SimpleTest.showReport
98 */
99 if (typeof(partial) == 'undefined') {
100 this.partial = function(func) {
101 var args = [];
102 for (var i = 1; i < arguments.length; i++) {
103 args.push(arguments[i]);
104 }
105 return function() {
106 if (arguments.length > 0) {
107 for (var i = 1; i < arguments.length; i++) {
108 args.push(arguments[i]);
109 }
110 }
111 func(args);
112 };
113 };
114 }
115
116 if (typeof(getElement) == 'undefined') {
117 this.getElement = function(id) {
118 return ((typeof(id) == "string") ?
119 document.getElementById(id) : id);
120 };
121 this.$ = this.getElement;
122 }
123
124 SimpleTest._newCallStack = function(path) {
125 var rval = function () {
126 var callStack = arguments.callee.callStack;
127 for (var i = 0; i < callStack.length; i++) {
128 if (callStack[i].apply(this, arguments) === false) {
129 break;
130 }
131 }
132 try {
133 this[path] = null;
134 } catch (e) {
135 // pass
136 }
137 };
138 rval.callStack = [];
139 return rval;
140 };
141
142 if (typeof(addLoadEvent) == 'undefined') {
143 this.addLoadEvent = function(func) {
144 var existing = window["onload"];
145 var regfunc = existing;
146 if (!(typeof(existing) == 'function'
147 && typeof(existing.callStack) == "object"
148 && existing.callStack !== null)) {
149 regfunc = SimpleTest._newCallStack("onload");
150 if (typeof(existing) == 'function') {
151 regfunc.callStack.push(existing);
152 }
153 window["onload"] = regfunc;
154 }
155 regfunc.callStack.push(func);
156 };
157 }
158
159 function createEl(type, attrs, html) {
160 //use createElementNS so the xul/xhtml tests have no issues
161 var el;
162 if (!document.body) {
163 el = document.createElementNS("http://www.w3.org/1999/xhtml", type);
164 }
165 else {
166 el = document.createElement(type);
167 }
168 if (attrs !== null && attrs !== undefined) {
169 for (var k in attrs) {
170 el.setAttribute(k, attrs[k]);
171 }
172 }
173 if (html !== null && html !== undefined) {
174 el.appendChild(document.createTextNode(html));
175 }
176 return el;
177 }
178
179 /* lots of tests use this as a helper to get css properties */
180 if (typeof(computedStyle) == 'undefined') {
181 this.computedStyle = function(elem, cssProperty) {
182 elem = getElement(elem);
183 if (elem.currentStyle) {
184 return elem.currentStyle[cssProperty];
185 }
186 if (typeof(document.defaultView) == 'undefined' || document === null) {
187 return undefined;
188 }
189 var style = document.defaultView.getComputedStyle(elem, null);
190 if (typeof(style) == 'undefined' || style === null) {
191 return undefined;
192 }
193
194 var selectorCase = cssProperty.replace(/([A-Z])/g, '-$1'
195 ).toLowerCase();
196
197 return style.getPropertyValue(selectorCase);
198 };
199 }
200
201 /**
202 * Check for OOP test plugin
203 **/
204 SimpleTest.testPluginIsOOP = function () {
205 var testPluginIsOOP = false;
206 if (navigator.platform.indexOf("Mac") == 0) {
207 if (SpecialPowers.XPCOMABI.match(/x86-/)) {
208 try {
209 testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.i386.test.plugin");
210 } catch (e) {
211 testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.i386");
212 }
213 }
214 else if (SpecialPowers.XPCOMABI.match(/x86_64-/)) {
215 try {
216 testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.x86_64.test.plugin");
217 } catch (e) {
218 testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled.x86_64");
219 }
220 }
221 }
222 else {
223 testPluginIsOOP = SpecialPowers.getBoolPref("dom.ipc.plugins.enabled");
224 }
225
226 return testPluginIsOOP;
227 };
228
229 SimpleTest._tests = [];
230 SimpleTest._stopOnLoad = true;
231 SimpleTest._cleanupFunctions = [];
232
233 /**
234 * Something like assert.
235 **/
236 SimpleTest.ok = function (condition, name, diag) {
237 var test = {'result': !!condition, 'name': name, 'diag': diag};
238 SimpleTest._logResult(test, "TEST-PASS", "TEST-UNEXPECTED-FAIL");
239 SimpleTest._tests.push(test);
240 };
241
242 /**
243 * Roughly equivalent to ok(a==b, name)
244 **/
245 SimpleTest.is = function (a, b, name) {
246 var pass = (a == b);
247 var diag = pass ? "" : "got " + repr(a) + ", expected " + repr(b)
248 SimpleTest.ok(pass, name, diag);
249 };
250
251 SimpleTest.isfuzzy = function (a, b, epsilon, name) {
252 var pass = (a > b - epsilon) && (a < b + epsilon);
253 var diag = pass ? "" : "got " + repr(a) + ", expected " + repr(b) + " epsilon: +/- " + repr(epsilon)
254 SimpleTest.ok(pass, name, diag);
255 };
256
257 SimpleTest.isnot = function (a, b, name) {
258 var pass = (a != b);
259 var diag = pass ? "" : "didn't expect " + repr(a) + ", but got it";
260 SimpleTest.ok(pass, name, diag);
261 };
262
263 /**
264 * Roughly equivalent to ok(a===b, name)
265 **/
266 SimpleTest.ise = function (a, b, name) {
267 var pass = (a === b);
268 var diag = pass ? "" : "got " + repr(a) + ", strictly expected " + repr(b)
269 SimpleTest.ok(pass, name, diag);
270 };
271
272 /**
273 * Check that the function call throws an exception.
274 */
275 SimpleTest.doesThrow = function(fn, name) {
276 var gotException = false;
277 try {
278 fn();
279 } catch (ex) { gotException = true; }
280 ok(gotException, name);
281 };
282
283 // --------------- Test.Builder/Test.More todo() -----------------
284
285 SimpleTest.todo = function(condition, name, diag) {
286 var test = {'result': !!condition, 'name': name, 'diag': diag, todo: true};
287 SimpleTest._logResult(test, "TEST-UNEXPECTED-PASS", "TEST-KNOWN-FAIL");
288 SimpleTest._tests.push(test);
289 };
290
291 /*
292 * Returns the absolute URL to a test data file from where tests
293 * are served. i.e. the file doesn't necessarely exists where tests
294 * are executed.
295 * (For b2g and android, mochitest are executed on the device, while
296 * all mochitest html (and others) files are served from the test runner
297 * slave)
298 */
299 SimpleTest.getTestFileURL = function(path) {
300 var lastSlashIdx = path.lastIndexOf("/") + 1;
301 var filename = path.substr(lastSlashIdx);
302 var location = window.location;
303 // Remove mochitest html file name from the path
304 var remotePath = location.pathname.replace(/\/[^\/]+?$/,"");
305 var url = location.origin +
306 remotePath + "/" + path;
307 return url;
308 };
309
310 SimpleTest._getCurrentTestURL = function() {
311 return parentRunner && parentRunner.currentTestURL ||
312 typeof gTestPath == "string" && gTestPath ||
313 "unknown test url";
314 };
315
316 SimpleTest._forceLogMessageOutput = parentRunner && !parentRunner.quiet;
317
318 /**
319 * Force all test messages to be displayed. Only applies for the current test.
320 */
321 SimpleTest.requestCompleteLog = function() {
322 if (SimpleTest._forceLogMessageOutput)
323 return;
324
325 SimpleTest._forceLogMessageOutput = true;
326 SimpleTest.registerCleanupFunction(function() {
327 SimpleTest._forceLogMessageOutput = false;
328 });
329 };
330
331 /**
332 * A circular buffer, managed by _logResult. We explicitly manage the
333 * circularness of the buffer, rather than resorting to .shift()/.push()
334 * because explicit management is much faster.
335 */
336 SimpleTest._bufferedMessages = [];
337 SimpleTest._logResult = (function () {
338 var bufferingThreshold = 100;
339 var outputIndex = 0;
340
341 function logResult(test, passString, failString) {
342 var url = SimpleTest._getCurrentTestURL();
343 var resultString = test.result ? passString : failString;
344 var diagnostic = test.name + (test.diag ? " - " + test.diag : "");
345 var msg = [resultString, url, diagnostic].join(" | ");
346 var isError = !test.result == !test.todo;
347
348 // Due to JavaScript's name lookup rules, it is important that
349 // the second parameter here be named identically to the isError
350 // variable declared above.
351 function dumpMessage(msg, isError) {
352 if (parentRunner) {
353 if (isError) {
354 parentRunner.addFailedTest(url);
355 parentRunner.error(msg);
356 } else {
357 parentRunner.log(msg);
358 }
359 } else if (typeof dump === "function") {
360 dump(msg + "\n");
361 } else {
362 // Non-Mozilla browser? Just do nothing.
363 }
364 }
365
366 // Detect when SimpleTest.reset() has been called, so we can
367 // reset outputIndex. We store outputIndex as local state to
368 // avoid adding even more state to SimpleTest.
369 if (SimpleTest._bufferedMessages.length == 0) {
370 outputIndex = 0;
371 }
372
373 // We want to eliminate mundane TEST-PASS/TEST-KNOWN-FAIL
374 // output, since some tests produce tens of thousands of of such
375 // messages. These messages can consume a lot of memory to
376 // generate and take a significant amount of time to output.
377 // However, the reality is that TEST-PASS messages can also be
378 // used as a form of logging via constructs like:
379 //
380 // SimpleTest.ok(true, "some informative message");
381 //
382 // And eliding the logging can be very confusing when trying to
383 // debug test failures.
384 //
385 // Hence the compromise adopted here: We buffer messages up to
386 // some limit and dump the buffer when a test failure happens.
387 // This behavior ought to provide enough context for developers
388 // looking to understand where in the test things failed.
389 if (isError) {
390 // Display this message and all the messages we have buffered.
391 if (SimpleTest._bufferedMessages.length > 0) {
392 dumpMessage("TEST-INFO | dumping last " + SimpleTest._bufferedMessages.length + " message(s)");
393 dumpMessage("TEST-INFO | if you need more context, please use SimpleTest.requestCompleteLog() in your test");
394
395 function dumpBufferedMessage(m) {
396 dumpMessage(m, false);
397 }
398 // The latest message is just before outputIndex.
399 // The earliest message is located at outputIndex.
400 var earliest = SimpleTest._bufferedMessages.slice(outputIndex);
401 var latest = SimpleTest._bufferedMessages.slice(0, outputIndex);
402 earliest.map(dumpBufferedMessage);
403 latest.map(dumpBufferedMessage);
404
405 SimpleTest._bufferedMessages = [];
406 }
407
408 dumpMessage(msg);
409 return;
410 }
411
412 var runningSingleTest = ((parentRunner &&
413 parentRunner._urls.length == 1) ||
414 isSingleTestRun);
415 var shouldLogImmediately = (runningSingleTest ||
416 SimpleTest._forceLogMessageOutput);
417
418 if (!shouldLogImmediately) {
419 // Buffer the message for possible later output.
420 if (SimpleTest._bufferedMessages.length >= bufferingThreshold) {
421 if (outputIndex >= bufferingThreshold) {
422 outputIndex = 0;
423 }
424 SimpleTest._bufferedMessages[outputIndex] = msg;
425 outputIndex++;
426 } else {
427 SimpleTest._bufferedMessages.push(msg);
428 }
429 return;
430 }
431
432 dumpMessage(msg);
433 }
434
435 return logResult;
436 })();
437
438 SimpleTest.info = function(name, message) {
439 SimpleTest._logResult({result:true, name:name, diag:message}, "TEST-INFO");
440 };
441
442 /**
443 * Copies of is and isnot with the call to ok replaced by a call to todo.
444 **/
445
446 SimpleTest.todo_is = function (a, b, name) {
447 var pass = (a == b);
448 var diag = pass ? repr(a) + " should equal " + repr(b)
449 : "got " + repr(a) + ", expected " + repr(b);
450 SimpleTest.todo(pass, name, diag);
451 };
452
453 SimpleTest.todo_isnot = function (a, b, name) {
454 var pass = (a != b);
455 var diag = pass ? repr(a) + " should not equal " + repr(b)
456 : "didn't expect " + repr(a) + ", but got it";
457 SimpleTest.todo(pass, name, diag);
458 };
459
460
461 /**
462 * Makes a test report, returns it as a DIV element.
463 **/
464 SimpleTest.report = function () {
465 var passed = 0;
466 var failed = 0;
467 var todo = 0;
468
469 var tallyAndCreateDiv = function (test) {
470 var cls, msg, div;
471 var diag = test.diag ? " - " + test.diag : "";
472 if (test.todo && !test.result) {
473 todo++;
474 cls = "test_todo";
475 msg = "todo | " + test.name + diag;
476 } else if (test.result && !test.todo) {
477 passed++;
478 cls = "test_ok";
479 msg = "passed | " + test.name + diag;
480 } else {
481 failed++;
482 cls = "test_not_ok";
483 msg = "failed | " + test.name + diag;
484 }
485 div = createEl('div', {'class': cls}, msg);
486 return div;
487 };
488 var results = [];
489 for (var d=0; d<SimpleTest._tests.length; d++) {
490 results.push(tallyAndCreateDiv(SimpleTest._tests[d]));
491 }
492
493 var summary_class = failed != 0 ? 'some_fail' :
494 passed == 0 ? 'todo_only' : 'all_pass';
495
496 var div1 = createEl('div', {'class': 'tests_report'});
497 var div2 = createEl('div', {'class': 'tests_summary ' + summary_class});
498 var div3 = createEl('div', {'class': 'tests_passed'}, 'Passed: ' + passed);
499 var div4 = createEl('div', {'class': 'tests_failed'}, 'Failed: ' + failed);
500 var div5 = createEl('div', {'class': 'tests_todo'}, 'Todo: ' + todo);
501 div2.appendChild(div3);
502 div2.appendChild(div4);
503 div2.appendChild(div5);
504 div1.appendChild(div2);
505 for (var t=0; t<results.length; t++) {
506 //iterate in order
507 div1.appendChild(results[t]);
508 }
509 return div1;
510 };
511
512 /**
513 * Toggle element visibility
514 **/
515 SimpleTest.toggle = function(el) {
516 if (computedStyle(el, 'display') == 'block') {
517 el.style.display = 'none';
518 } else {
519 el.style.display = 'block';
520 }
521 };
522
523 /**
524 * Toggle visibility for divs with a specific class.
525 **/
526 SimpleTest.toggleByClass = function (cls, evt) {
527 var children = document.getElementsByTagName('div');
528 var elements = [];
529 for (var i=0; i<children.length; i++) {
530 var child = children[i];
531 var clsName = child.className;
532 if (!clsName) {
533 continue;
534 }
535 var classNames = clsName.split(' ');
536 for (var j = 0; j < classNames.length; j++) {
537 if (classNames[j] == cls) {
538 elements.push(child);
539 break;
540 }
541 }
542 }
543 for (var t=0; t<elements.length; t++) {
544 //TODO: again, for-in loop over elems seems to break this
545 SimpleTest.toggle(elements[t]);
546 }
547 if (evt)
548 evt.preventDefault();
549 };
550
551 /**
552 * Shows the report in the browser
553 **/
554 SimpleTest.showReport = function() {
555 var togglePassed = createEl('a', {'href': '#'}, "Toggle passed checks");
556 var toggleFailed = createEl('a', {'href': '#'}, "Toggle failed checks");
557 var toggleTodo = createEl('a',{'href': '#'}, "Toggle todo checks");
558 togglePassed.onclick = partial(SimpleTest.toggleByClass, 'test_ok');
559 toggleFailed.onclick = partial(SimpleTest.toggleByClass, 'test_not_ok');
560 toggleTodo.onclick = partial(SimpleTest.toggleByClass, 'test_todo');
561 var body = document.body; // Handles HTML documents
562 if (!body) {
563 // Do the XML thing.
564 body = document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml",
565 "body")[0];
566 }
567 var firstChild = body.childNodes[0];
568 var addNode;
569 if (firstChild) {
570 addNode = function (el) {
571 body.insertBefore(el, firstChild);
572 };
573 } else {
574 addNode = function (el) {
575 body.appendChild(el)
576 };
577 }
578 addNode(togglePassed);
579 addNode(createEl('span', null, " "));
580 addNode(toggleFailed);
581 addNode(createEl('span', null, " "));
582 addNode(toggleTodo);
583 addNode(SimpleTest.report());
584 // Add a separator from the test content.
585 addNode(createEl('hr'));
586 };
587
588 /**
589 * Tells SimpleTest to don't finish the test when the document is loaded,
590 * useful for asynchronous tests.
591 *
592 * When SimpleTest.waitForExplicitFinish is called,
593 * explicit SimpleTest.finish() is required.
594 **/
595 SimpleTest.waitForExplicitFinish = function () {
596 SimpleTest._stopOnLoad = false;
597 };
598
599 /**
600 * Multiply the timeout the parent runner uses for this test by the
601 * given factor.
602 *
603 * For example, in a test that may take a long time to complete, using
604 * "SimpleTest.requestLongerTimeout(5)" will give it 5 times as long to
605 * finish.
606 */
607 SimpleTest.requestLongerTimeout = function (factor) {
608 if (parentRunner) {
609 parentRunner.requestLongerTimeout(factor);
610 }
611 }
612
613 /**
614 * Note that the given range of assertions is to be expected. When
615 * this function is not called, 0 assertions are expected. When only
616 * one argument is given, that number of assertions are expected.
617 *
618 * A test where we expect to have assertions (which should largely be a
619 * transitional mechanism to get assertion counts down from our current
620 * situation) can call the SimpleTest.expectAssertions() function, with
621 * either one or two arguments: one argument gives an exact number
622 * expected, and two arguments give a range. For example, a test might do
623 * one of the following:
624 *
625 * // Currently triggers two assertions (bug NNNNNN).
626 * SimpleTest.expectAssertions(2);
627 *
628 * // Currently triggers one assertion on Mac (bug NNNNNN).
629 * if (navigator.platform.indexOf("Mac") == 0) {
630 * SimpleTest.expectAssertions(1);
631 * }
632 *
633 * // Currently triggers two assertions on all platforms (bug NNNNNN),
634 * // but intermittently triggers two additional assertions (bug NNNNNN)
635 * // on Windows.
636 * if (navigator.platform.indexOf("Win") == 0) {
637 * SimpleTest.expectAssertions(2, 4);
638 * } else {
639 * SimpleTest.expectAssertions(2);
640 * }
641 *
642 * // Intermittently triggers up to three assertions (bug NNNNNN).
643 * SimpleTest.expectAssertions(0, 3);
644 */
645 SimpleTest.expectAssertions = function(min, max) {
646 if (parentRunner) {
647 parentRunner.expectAssertions(min, max);
648 }
649 }
650
651 SimpleTest.waitForFocus_started = false;
652 SimpleTest.waitForFocus_loaded = false;
653 SimpleTest.waitForFocus_focused = false;
654 SimpleTest._pendingWaitForFocusCount = 0;
655
656 /**
657 * If the page is not yet loaded, waits for the load event. In addition, if
658 * the page is not yet focused, focuses and waits for the window to be
659 * focused. Calls the callback when completed. If the current page is
660 * 'about:blank', then the page is assumed to not yet be loaded. Pass true for
661 * expectBlankPage to not make this assumption if you expect a blank page to
662 * be present.
663 *
664 * targetWindow should be specified if it is different than 'window'. The actual
665 * focused window may be a descendant of targetWindow.
666 *
667 * @param callback
668 * function called when load and focus are complete
669 * @param targetWindow
670 * optional window to be loaded and focused, defaults to 'window'
671 * @param expectBlankPage
672 * true if targetWindow.location is 'about:blank'. Defaults to false
673 */
674 SimpleTest.waitForFocus = function (callback, targetWindow, expectBlankPage) {
675 SimpleTest._pendingWaitForFocusCount++;
676 if (!targetWindow)
677 targetWindow = window;
678
679 SimpleTest.waitForFocus_started = false;
680 expectBlankPage = !!expectBlankPage;
681
682 var childTargetWindow = {};
683 SpecialPowers.getFocusedElementForWindow(targetWindow, true, childTargetWindow);
684 childTargetWindow = childTargetWindow.value;
685
686 function info(msg) {
687 SimpleTest.info(msg);
688 }
689 function getHref(aWindow) {
690 return SpecialPowers.getPrivilegedProps(aWindow, 'location.href');
691 }
692
693 function maybeRunTests() {
694 if (SimpleTest.waitForFocus_loaded &&
695 SimpleTest.waitForFocus_focused &&
696 !SimpleTest.waitForFocus_started) {
697 SimpleTest._pendingWaitForFocusCount--;
698 SimpleTest.waitForFocus_started = true;
699 setTimeout(callback, 0, targetWindow);
700 }
701 }
702
703 function waitForEvent(event) {
704 try {
705 // Check to make sure that this isn't a load event for a blank or
706 // non-blank page that wasn't desired.
707 if (event.type == "load" && (expectBlankPage != (event.target.location == "about:blank")))
708 return;
709
710 SimpleTest["waitForFocus_" + event.type + "ed"] = true;
711 var win = (event.type == "load") ? targetWindow : childTargetWindow;
712 win.removeEventListener(event.type, waitForEvent, true);
713 maybeRunTests();
714 } catch (e) {
715 SimpleTest.ok(false, "Exception caught in waitForEvent: " + e.message +
716 ", at: " + e.fileName + " (" + e.lineNumber + ")");
717 }
718 }
719
720 // If the current document is about:blank and we are not expecting a blank
721 // page (or vice versa), and the document has not yet loaded, wait for the
722 // page to load. A common situation is to wait for a newly opened window
723 // to load its content, and we want to skip over any intermediate blank
724 // pages that load. This issue is described in bug 554873.
725 SimpleTest.waitForFocus_loaded =
726 expectBlankPage ?
727 getHref(targetWindow) == "about:blank" :
728 getHref(targetWindow) != "about:blank" && targetWindow.document.readyState == "complete";
729 if (!SimpleTest.waitForFocus_loaded) {
730 info("must wait for load");
731 targetWindow.addEventListener("load", waitForEvent, true);
732 }
733
734 // Check if the desired window is already focused.
735 var focusedChildWindow = { };
736 if (SpecialPowers.activeWindow()) {
737 SpecialPowers.getFocusedElementForWindow(SpecialPowers.activeWindow(), true, focusedChildWindow);
738 focusedChildWindow = focusedChildWindow.value;
739 }
740
741 // If this is a child frame, ensure that the frame is focused.
742 SimpleTest.waitForFocus_focused = (focusedChildWindow == childTargetWindow);
743 if (SimpleTest.waitForFocus_focused) {
744 // If the frame is already focused and loaded, call the callback directly.
745 maybeRunTests();
746 }
747 else {
748 info("must wait for focus");
749 childTargetWindow.addEventListener("focus", waitForEvent, true);
750 SpecialPowers.focus(childTargetWindow);
751 }
752 };
753
754 SimpleTest.waitForClipboard_polls = 0;
755
756 /*
757 * Polls the clipboard waiting for the expected value. A known value different than
758 * the expected value is put on the clipboard first (and also polled for) so we
759 * can be sure the value we get isn't just the expected value because it was already
760 * on the clipboard. This only uses the global clipboard and only for text/unicode
761 * values.
762 *
763 * @param aExpectedStringOrValidatorFn
764 * The string value that is expected to be on the clipboard or a
765 * validator function getting cripboard data and returning a bool.
766 * @param aSetupFn
767 * A function responsible for setting the clipboard to the expected value,
768 * called after the known value setting succeeds.
769 * @param aSuccessFn
770 * A function called when the expected value is found on the clipboard.
771 * @param aFailureFn
772 * A function called if the expected value isn't found on the clipboard
773 * within 5s. It can also be called if the known value can't be found.
774 * @param aFlavor [optional] The flavor to look for. Defaults to "text/unicode".
775 */
776 SimpleTest.__waitForClipboardMonotonicCounter = 0;
777 SimpleTest.__defineGetter__("_waitForClipboardMonotonicCounter", function () {
778 return SimpleTest.__waitForClipboardMonotonicCounter++;
779 });
780 SimpleTest.waitForClipboard = function(aExpectedStringOrValidatorFn, aSetupFn,
781 aSuccessFn, aFailureFn, aFlavor) {
782 var requestedFlavor = aFlavor || "text/unicode";
783
784 // Build a default validator function for common string input.
785 var inputValidatorFn = typeof(aExpectedStringOrValidatorFn) == "string"
786 ? function(aData) { return aData == aExpectedStringOrValidatorFn; }
787 : aExpectedStringOrValidatorFn;
788
789 // reset for the next use
790 function reset() {
791 SimpleTest.waitForClipboard_polls = 0;
792 }
793
794 function wait(validatorFn, successFn, failureFn, flavor) {
795 if (++SimpleTest.waitForClipboard_polls > 50) {
796 // Log the failure.
797 SimpleTest.ok(false, "Timed out while polling clipboard for pasted data.");
798 reset();
799 failureFn();
800 return;
801 }
802
803 var data = SpecialPowers.getClipboardData(flavor);
804
805 if (validatorFn(data)) {
806 // Don't show the success message when waiting for preExpectedVal
807 if (preExpectedVal)
808 preExpectedVal = null;
809 else
810 SimpleTest.ok(true, "Clipboard has the correct value");
811 reset();
812 successFn();
813 } else {
814 setTimeout(function() { return wait(validatorFn, successFn, failureFn, flavor); }, 100);
815 }
816 }
817
818 // First we wait for a known value different from the expected one.
819 var preExpectedVal = SimpleTest._waitForClipboardMonotonicCounter +
820 "-waitForClipboard-known-value";
821 SpecialPowers.clipboardCopyString(preExpectedVal);
822 wait(function(aData) { return aData == preExpectedVal; },
823 function() {
824 // Call the original setup fn
825 aSetupFn();
826 wait(inputValidatorFn, aSuccessFn, aFailureFn, requestedFlavor);
827 }, aFailureFn, "text/unicode");
828 }
829
830 /**
831 * Executes a function shortly after the call, but lets the caller continue
832 * working (or finish).
833 */
834 SimpleTest.executeSoon = function(aFunc) {
835 if ("SpecialPowers" in window) {
836 return SpecialPowers.executeSoon(aFunc, window);
837 }
838 setTimeout(aFunc, 0);
839 return null; // Avoid warning.
840 };
841
842 SimpleTest.registerCleanupFunction = function(aFunc) {
843 SimpleTest._cleanupFunctions.push(aFunc);
844 };
845
846 /**
847 * Finishes the tests. This is automatically called, except when
848 * SimpleTest.waitForExplicitFinish() has been invoked.
849 **/
850 SimpleTest.finish = function() {
851 var Task = SpecialPowers.Cu.import("resource://gre/modules/Task.jsm").Task;
852
853 if (SimpleTest._alreadyFinished) {
854 SimpleTest.ok(false, "[SimpleTest.finish()] this test already called finish!");
855 }
856
857 SimpleTest._alreadyFinished = true;
858
859 Task.spawn(function*() {
860 // Execute all of our cleanup functions.
861 var func;
862 while ((func = SimpleTest._cleanupFunctions.pop())) {
863 try {
864 yield func();
865 }
866 catch (ex) {
867 SimpleTest.ok(false, "Cleanup function threw exception: " + ex);
868 }
869 }
870
871 if (SpecialPowers.DOMWindowUtils.isTestControllingRefreshes) {
872 SimpleTest.ok(false, "test left refresh driver under test control");
873 SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
874 }
875 if (SimpleTest._expectingUncaughtException) {
876 SimpleTest.ok(false, "expectUncaughtException was called but no uncaught exception was detected!");
877 }
878 if (SimpleTest._pendingWaitForFocusCount != 0) {
879 SimpleTest.is(SimpleTest._pendingWaitForFocusCount, 0,
880 "[SimpleTest.finish()] waitForFocus() was called a "
881 + "different number of times from the number of "
882 + "callbacks run. Maybe the test terminated "
883 + "prematurely -- be sure to use "
884 + "SimpleTest.waitForExplicitFinish().");
885 }
886 if (SimpleTest._tests.length == 0) {
887 SimpleTest.ok(false, "[SimpleTest.finish()] No checks actually run. "
888 + "(You need to call ok(), is(), or similar "
889 + "functions at least once. Make sure you use "
890 + "SimpleTest.waitForExplicitFinish() if you need "
891 + "it.)");
892 }
893
894 if (parentRunner) {
895 /* We're running in an iframe, and the parent has a TestRunner */
896 parentRunner.testFinished(SimpleTest._tests);
897 } else {
898 SpecialPowers.flushAllAppsLaunchable();
899 SpecialPowers.flushPermissions(function () {
900 SpecialPowers.flushPrefEnv(function() {
901 SimpleTest.showReport();
902 });
903 });
904 }
905 });
906 };
907
908 /**
909 * Monitor console output from now until endMonitorConsole is called.
910 *
911 * Expect to receive all console messages described by the elements of
912 * |msgs|, an array, in the order listed in |msgs|; each element is an
913 * object which may have any number of the following properties:
914 * message, errorMessage, sourceName, sourceLine, category:
915 * string or regexp
916 * lineNumber, columnNumber: number
917 * isScriptError, isWarning, isException, isStrict: boolean
918 * Strings, numbers, and booleans must compare equal to the named
919 * property of the Nth console message. Regexps must match. Any
920 * fields present in the message but not in the pattern object are ignored.
921 *
922 * In addition to the above properties, elements in |msgs| may have a |forbid|
923 * boolean property. When |forbid| is true, a failure is logged each time a
924 * matching message is received.
925 *
926 * If |forbidUnexpectedMsgs| is true, then the messages received in the console
927 * must exactly match the non-forbidden messages in |msgs|; for each received
928 * message not described by the next element in |msgs|, a failure is logged. If
929 * false, then other non-forbidden messages are ignored, but all expected
930 * messages must still be received.
931 *
932 * After endMonitorConsole is called, |continuation| will be called
933 * asynchronously. (Normally, you will want to pass |SimpleTest.finish| here.)
934 *
935 * It is incorrect to use this function in a test which has not called
936 * SimpleTest.waitForExplicitFinish.
937 */
938 SimpleTest.monitorConsole = function (continuation, msgs, forbidUnexpectedMsgs) {
939 if (SimpleTest._stopOnLoad) {
940 ok(false, "Console monitoring requires use of waitForExplicitFinish.");
941 }
942
943 function msgMatches(msg, pat) {
944 for (k in pat) {
945 if (!(k in msg)) {
946 return false;
947 }
948 if (pat[k] instanceof RegExp && typeof(msg[k]) === 'string') {
949 if (!pat[k].test(msg[k])) {
950 return false;
951 }
952 } else if (msg[k] !== pat[k]) {
953 return false;
954 }
955 }
956 return true;
957 }
958
959 var forbiddenMsgs = [];
960 var i = 0;
961 while (i < msgs.length) {
962 var pat = msgs[i];
963 if ("forbid" in pat) {
964 var forbid = pat.forbid;
965 delete pat.forbid;
966 if (forbid) {
967 forbiddenMsgs.push(pat);
968 msgs.splice(i, 1);
969 continue;
970 }
971 }
972 i++;
973 }
974
975 var counter = 0;
976 function listener(msg) {
977 if (msg.message === "SENTINEL" && !msg.isScriptError) {
978 is(counter, msgs.length, "monitorConsole | number of messages");
979 SimpleTest.executeSoon(continuation);
980 return;
981 }
982 for (var pat of forbiddenMsgs) {
983 if (msgMatches(msg, pat)) {
984 ok(false, "monitorConsole | observed forbidden message " +
985 JSON.stringify(msg));
986 return;
987 }
988 }
989 if (counter >= msgs.length) {
990 var str = "monitorConsole | extra message | " + JSON.stringify(msg);
991 if (forbidUnexpectedMsgs) {
992 ok(false, str);
993 } else {
994 info(str);
995 }
996 return;
997 }
998 var matches = msgMatches(msg, msgs[counter]);
999 if (forbidUnexpectedMsgs) {
1000 ok(matches, "monitorConsole | [" + counter + "] must match " +
1001 JSON.stringify(msg));
1002 } else {
1003 info("monitorConsole | [" + counter + "] " +
1004 (matches ? "matched " : "did not match ") + JSON.stringify(msg));
1005 }
1006 if (matches)
1007 counter++;
1008 }
1009 SpecialPowers.registerConsoleListener(listener);
1010 };
1011
1012 /**
1013 * Stop monitoring console output.
1014 */
1015 SimpleTest.endMonitorConsole = function () {
1016 SpecialPowers.postConsoleSentinel();
1017 };
1018
1019 /**
1020 * Run |testfn| synchronously, and monitor its console output.
1021 *
1022 * |msgs| is handled as described above for monitorConsole.
1023 *
1024 * After |testfn| returns, console monitoring will stop, and
1025 * |continuation| will be called asynchronously.
1026 */
1027 SimpleTest.expectConsoleMessages = function (testfn, msgs, continuation) {
1028 SimpleTest.monitorConsole(continuation, msgs);
1029 testfn();
1030 SimpleTest.executeSoon(SimpleTest.endMonitorConsole);
1031 };
1032
1033 /**
1034 * Wrapper around |expectConsoleMessages| for the case where the test has
1035 * only one |testfn| to run.
1036 */
1037 SimpleTest.runTestExpectingConsoleMessages = function(testfn, msgs) {
1038 SimpleTest.waitForExplicitFinish();
1039 SimpleTest.expectConsoleMessages(testfn, msgs, SimpleTest.finish);
1040 };
1041
1042 /**
1043 * Indicates to the test framework that the current test expects one or
1044 * more crashes (from plugins or IPC documents), and that the minidumps from
1045 * those crashes should be removed.
1046 */
1047 SimpleTest.expectChildProcessCrash = function () {
1048 if (parentRunner) {
1049 parentRunner.expectChildProcessCrash();
1050 }
1051 };
1052
1053 /**
1054 * Indicates to the test framework that the next uncaught exception during
1055 * the test is expected, and should not cause a test failure.
1056 */
1057 SimpleTest.expectUncaughtException = function (aExpecting) {
1058 SimpleTest._expectingUncaughtException = aExpecting === void 0 || !!aExpecting;
1059 };
1060
1061 /**
1062 * Returns whether the test has indicated that it expects an uncaught exception
1063 * to occur.
1064 */
1065 SimpleTest.isExpectingUncaughtException = function () {
1066 return SimpleTest._expectingUncaughtException;
1067 };
1068
1069 /**
1070 * Indicates to the test framework that all of the uncaught exceptions
1071 * during the test are known problems that should be fixed in the future,
1072 * but which should not cause the test to fail currently.
1073 */
1074 SimpleTest.ignoreAllUncaughtExceptions = function (aIgnoring) {
1075 SimpleTest._ignoringAllUncaughtExceptions = aIgnoring === void 0 || !!aIgnoring;
1076 };
1077
1078 /**
1079 * Returns whether the test has indicated that all uncaught exceptions should be
1080 * ignored.
1081 */
1082 SimpleTest.isIgnoringAllUncaughtExceptions = function () {
1083 return SimpleTest._ignoringAllUncaughtExceptions;
1084 };
1085
1086 /**
1087 * Resets any state this SimpleTest object has. This is important for
1088 * browser chrome mochitests, which reuse the same SimpleTest object
1089 * across a run.
1090 */
1091 SimpleTest.reset = function () {
1092 SimpleTest._ignoringAllUncaughtExceptions = false;
1093 SimpleTest._expectingUncaughtException = false;
1094 SimpleTest._bufferedMessages = [];
1095 };
1096
1097 if (isPrimaryTestWindow) {
1098 addLoadEvent(function() {
1099 if (SimpleTest._stopOnLoad) {
1100 SimpleTest.finish();
1101 }
1102 });
1103 }
1104
1105 // --------------- Test.Builder/Test.More isDeeply() -----------------
1106
1107
1108 SimpleTest.DNE = {dne: 'Does not exist'};
1109 SimpleTest.LF = "\r\n";
1110 SimpleTest._isRef = function (object) {
1111 var type = typeof(object);
1112 return type == 'object' || type == 'function';
1113 };
1114
1115
1116 SimpleTest._deepCheck = function (e1, e2, stack, seen) {
1117 var ok = false;
1118 // Either they're both references or both not.
1119 var sameRef = !(!SimpleTest._isRef(e1) ^ !SimpleTest._isRef(e2));
1120 if (e1 == null && e2 == null) {
1121 ok = true;
1122 } else if (e1 != null ^ e2 != null) {
1123 ok = false;
1124 } else if (e1 == SimpleTest.DNE ^ e2 == SimpleTest.DNE) {
1125 ok = false;
1126 } else if (sameRef && e1 == e2) {
1127 // Handles primitives and any variables that reference the same
1128 // object, including functions.
1129 ok = true;
1130 } else if (SimpleTest.isa(e1, 'Array') && SimpleTest.isa(e2, 'Array')) {
1131 ok = SimpleTest._eqArray(e1, e2, stack, seen);
1132 } else if (typeof e1 == "object" && typeof e2 == "object") {
1133 ok = SimpleTest._eqAssoc(e1, e2, stack, seen);
1134 } else if (typeof e1 == "number" && typeof e2 == "number"
1135 && isNaN(e1) && isNaN(e2)) {
1136 ok = true;
1137 } else {
1138 // If we get here, they're not the same (function references must
1139 // always simply reference the same function).
1140 stack.push({ vals: [e1, e2] });
1141 ok = false;
1142 }
1143 return ok;
1144 };
1145
1146 SimpleTest._eqArray = function (a1, a2, stack, seen) {
1147 // Return if they're the same object.
1148 if (a1 == a2) return true;
1149
1150 // JavaScript objects have no unique identifiers, so we have to store
1151 // references to them all in an array, and then compare the references
1152 // directly. It's slow, but probably won't be much of an issue in
1153 // practice. Start by making a local copy of the array to as to avoid
1154 // confusing a reference seen more than once (such as [a, a]) for a
1155 // circular reference.
1156 for (var j = 0; j < seen.length; j++) {
1157 if (seen[j][0] == a1) {
1158 return seen[j][1] == a2;
1159 }
1160 }
1161
1162 // If we get here, we haven't seen a1 before, so store it with reference
1163 // to a2.
1164 seen.push([ a1, a2 ]);
1165
1166 var ok = true;
1167 // Only examines enumerable attributes. Only works for numeric arrays!
1168 // Associative arrays return 0. So call _eqAssoc() for them, instead.
1169 var max = a1.length > a2.length ? a1.length : a2.length;
1170 if (max == 0) return SimpleTest._eqAssoc(a1, a2, stack, seen);
1171 for (var i = 0; i < max; i++) {
1172 var e1 = i > a1.length - 1 ? SimpleTest.DNE : a1[i];
1173 var e2 = i > a2.length - 1 ? SimpleTest.DNE : a2[i];
1174 stack.push({ type: 'Array', idx: i, vals: [e1, e2] });
1175 ok = SimpleTest._deepCheck(e1, e2, stack, seen);
1176 if (ok) {
1177 stack.pop();
1178 } else {
1179 break;
1180 }
1181 }
1182 return ok;
1183 };
1184
1185 SimpleTest._eqAssoc = function (o1, o2, stack, seen) {
1186 // Return if they're the same object.
1187 if (o1 == o2) return true;
1188
1189 // JavaScript objects have no unique identifiers, so we have to store
1190 // references to them all in an array, and then compare the references
1191 // directly. It's slow, but probably won't be much of an issue in
1192 // practice. Start by making a local copy of the array to as to avoid
1193 // confusing a reference seen more than once (such as [a, a]) for a
1194 // circular reference.
1195 seen = seen.slice(0);
1196 for (var j = 0; j < seen.length; j++) {
1197 if (seen[j][0] == o1) {
1198 return seen[j][1] == o2;
1199 }
1200 }
1201
1202 // If we get here, we haven't seen o1 before, so store it with reference
1203 // to o2.
1204 seen.push([ o1, o2 ]);
1205
1206 // They should be of the same class.
1207
1208 var ok = true;
1209 // Only examines enumerable attributes.
1210 var o1Size = 0; for (var i in o1) o1Size++;
1211 var o2Size = 0; for (var i in o2) o2Size++;
1212 var bigger = o1Size > o2Size ? o1 : o2;
1213 for (var i in bigger) {
1214 var e1 = o1[i] == undefined ? SimpleTest.DNE : o1[i];
1215 var e2 = o2[i] == undefined ? SimpleTest.DNE : o2[i];
1216 stack.push({ type: 'Object', idx: i, vals: [e1, e2] });
1217 ok = SimpleTest._deepCheck(e1, e2, stack, seen)
1218 if (ok) {
1219 stack.pop();
1220 } else {
1221 break;
1222 }
1223 }
1224 return ok;
1225 };
1226
1227 SimpleTest._formatStack = function (stack) {
1228 var variable = '$Foo';
1229 for (var i = 0; i < stack.length; i++) {
1230 var entry = stack[i];
1231 var type = entry['type'];
1232 var idx = entry['idx'];
1233 if (idx != null) {
1234 if (/^\d+$/.test(idx)) {
1235 // Numeric array index.
1236 variable += '[' + idx + ']';
1237 } else {
1238 // Associative array index.
1239 idx = idx.replace("'", "\\'");
1240 variable += "['" + idx + "']";
1241 }
1242 }
1243 }
1244
1245 var vals = stack[stack.length-1]['vals'].slice(0, 2);
1246 var vars = [
1247 variable.replace('$Foo', 'got'),
1248 variable.replace('$Foo', 'expected')
1249 ];
1250
1251 var out = "Structures begin differing at:" + SimpleTest.LF;
1252 for (var i = 0; i < vals.length; i++) {
1253 var val = vals[i];
1254 if (val == null) {
1255 val = 'undefined';
1256 } else {
1257 val == SimpleTest.DNE ? "Does not exist" : "'" + val + "'";
1258 }
1259 }
1260
1261 out += vars[0] + ' = ' + vals[0] + SimpleTest.LF;
1262 out += vars[1] + ' = ' + vals[1] + SimpleTest.LF;
1263
1264 return ' ' + out;
1265 };
1266
1267
1268 SimpleTest.isDeeply = function (it, as, name) {
1269 var ok;
1270 // ^ is the XOR operator.
1271 if (SimpleTest._isRef(it) ^ SimpleTest._isRef(as)) {
1272 // One's a reference, one isn't.
1273 ok = false;
1274 } else if (!SimpleTest._isRef(it) && !SimpleTest._isRef(as)) {
1275 // Neither is an object.
1276 ok = SimpleTest.is(it, as, name);
1277 } else {
1278 // We have two objects. Do a deep comparison.
1279 var stack = [], seen = [];
1280 if ( SimpleTest._deepCheck(it, as, stack, seen)) {
1281 ok = SimpleTest.ok(true, name);
1282 } else {
1283 ok = SimpleTest.ok(false, name, SimpleTest._formatStack(stack));
1284 }
1285 }
1286 return ok;
1287 };
1288
1289 SimpleTest.typeOf = function (object) {
1290 var c = Object.prototype.toString.apply(object);
1291 var name = c.substring(8, c.length - 1);
1292 if (name != 'Object') return name;
1293 // It may be a non-core class. Try to extract the class name from
1294 // the constructor function. This may not work in all implementations.
1295 if (/function ([^(\s]+)/.test(Function.toString.call(object.constructor))) {
1296 return RegExp.$1;
1297 }
1298 // No idea. :-(
1299 return name;
1300 };
1301
1302 SimpleTest.isa = function (object, clas) {
1303 return SimpleTest.typeOf(object) == clas;
1304 };
1305
1306 // Global symbols:
1307 var ok = SimpleTest.ok;
1308 var is = SimpleTest.is;
1309 var isfuzzy = SimpleTest.isfuzzy;
1310 var isnot = SimpleTest.isnot;
1311 var ise = SimpleTest.ise;
1312 var todo = SimpleTest.todo;
1313 var todo_is = SimpleTest.todo_is;
1314 var todo_isnot = SimpleTest.todo_isnot;
1315 var isDeeply = SimpleTest.isDeeply;
1316 var info = SimpleTest.info;
1317
1318 var gOldOnError = window.onerror;
1319 window.onerror = function simpletestOnerror(errorMsg, url, lineNumber) {
1320 // Log the message.
1321 // XXX Chrome mochitests sometimes trigger this window.onerror handler,
1322 // but there are a number of uncaught JS exceptions from those tests.
1323 // For now, for tests that self identify as having unintentional uncaught
1324 // exceptions, just dump it so that the error is visible but doesn't cause
1325 // a test failure. See bug 652494.
1326 var isExpected = !!SimpleTest._expectingUncaughtException;
1327 var message = (isExpected ? "expected " : "") + "uncaught exception";
1328 var error = errorMsg + " at " + url + ":" + lineNumber;
1329 if (!SimpleTest._ignoringAllUncaughtExceptions) {
1330 SimpleTest.ok(isExpected, message, error);
1331 SimpleTest._expectingUncaughtException = false;
1332 } else {
1333 SimpleTest.todo(false, message + ": " + error);
1334 }
1335 // There is no Components.stack.caller to log. (See bug 511888.)
1336
1337 // Call previous handler.
1338 if (gOldOnError) {
1339 try {
1340 // Ignore return value: always run default handler.
1341 gOldOnError(errorMsg, url, lineNumber);
1342 } catch (e) {
1343 // Log the error.
1344 SimpleTest.info("Exception thrown by gOldOnError(): " + e);
1345 // Log its stack.
1346 if (e.stack) {
1347 SimpleTest.info("JavaScript error stack:\n" + e.stack);
1348 }
1349 }
1350 }
1351
1352 if (!SimpleTest._stopOnLoad && !isExpected) {
1353 // Need to finish() manually here, yet let the test actually end first.
1354 SimpleTest.executeSoon(SimpleTest.finish);
1355 }
1356 };

mercurial