Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / js / src / tests / shell.js
blobfbc7dc102c732b88448385fb691f9d17977210d3
1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 // NOTE: If you're adding new test harness functionality -- first, should you
7 //       at all?  Most stuff is better in specific tests, or in nested shell.js
8 //       or browser.js.  Second, supposing you should, please add it to this
9 //       IIFE for better modularity/resilience against tests that must do
10 //       particularly bizarre things that might break the harness.
12 (function(global) {
13   "use strict";
15   /**********************************************************************
16    * CACHED PRIMORDIAL FUNCTIONALITY (before a test might overwrite it) *
17    **********************************************************************/
19   var undefined; // sigh
21   var Error = global.Error;
22   var Function = global.Function;
23   var Number = global.Number;
24   var RegExp = global.RegExp;
25   var String = global.String;
26   var Symbol = global.Symbol;
27   var TypeError = global.TypeError;
29   var ArrayIsArray = global.Array.isArray;
30   var MathAbs = global.Math.abs;
31   var ObjectCreate = global.Object.create;
32   var ObjectDefineProperty = global.Object.defineProperty;
33   var ReflectApply = global.Reflect.apply;
34   var RegExpPrototypeExec = global.RegExp.prototype.exec;
35   var StringPrototypeCharCodeAt = global.String.prototype.charCodeAt;
36   var StringPrototypeIndexOf = global.String.prototype.indexOf;
37   var StringPrototypeSubstring = global.String.prototype.substring;
39   var runningInBrowser = typeof global.window !== "undefined";
40   if (runningInBrowser) {
41     // Certain cached functionality only exists (and is only needed) when
42     // running in the browser.  Segregate that caching here.
44     var SpecialPowersSetGCZeal =
45       global.SpecialPowers ? global.SpecialPowers.setGCZeal : undefined;
46   }
48   var evaluate = global.evaluate;
49   var options = global.options;
51   /****************************
52    * GENERAL HELPER FUNCTIONS *
53    ****************************/
55   // We *cannot* use Array.prototype.push for this, because that function sets
56   // the new trailing element, which could invoke a setter (left by a test) on
57   // Array.prototype or Object.prototype.
58   function ArrayPush(arr, val) {
59     assertEq(ArrayIsArray(arr), true,
60              "ArrayPush must only be used on actual arrays");
62     var desc = ObjectCreate(null);
63     desc.value = val;
64     desc.enumerable = true;
65     desc.configurable = true;
66     desc.writable = true;
67     ObjectDefineProperty(arr, arr.length, desc);
68   }
70   function StringCharCodeAt(str, index) {
71     return ReflectApply(StringPrototypeCharCodeAt, str, [index]);
72   }
74   function StringSplit(str, delimiter) {
75     assertEq(typeof str === "string" && typeof delimiter === "string", true,
76              "StringSplit must be called with two string arguments");
77     assertEq(delimiter.length > 0, true,
78              "StringSplit doesn't support an empty delimiter string");
80     var parts = [];
81     var last = 0;
82     while (true) {
83       var i = ReflectApply(StringPrototypeIndexOf, str, [delimiter, last]);
84       if (i < 0) {
85         if (last < str.length)
86           ArrayPush(parts, ReflectApply(StringPrototypeSubstring, str, [last]));
87         return parts;
88       }
90       ArrayPush(parts, ReflectApply(StringPrototypeSubstring, str, [last, i]));
91       last = i + delimiter.length;
92     }
93   }
95   function shellOptionsClear() {
96     assertEq(runningInBrowser, false, "Only called when running in the shell.");
98     // Return early if no options are set.
99     var currentOptions = options ? options() : "";
100     if (currentOptions === "")
101       return;
103     // Turn off current settings.
104     var optionNames = StringSplit(currentOptions, ",");
105     for (var i = 0; i < optionNames.length; i++) {
106       options(optionNames[i]);
107     }
108   }
110   /****************************
111    * TESTING FUNCTION EXPORTS *
112    ****************************/
114   function SameValue(v1, v2) {
115     // We could |return Object.is(v1, v2);|, but that's less portable.
116     if (v1 === 0 && v2 === 0)
117       return 1 / v1 === 1 / v2;
118     if (v1 !== v1 && v2 !== v2)
119       return true;
120     return v1 === v2;
121   }
123   var assertEq = global.assertEq;
124   if (typeof assertEq !== "function") {
125     assertEq = function assertEq(actual, expected, message) {
126       if (!SameValue(actual, expected)) {
127         throw new TypeError(`Assertion failed: got "${actual}", expected "${expected}"` +
128                             (message ? ": " + message : ""));
129       }
130     };
131     global.assertEq = assertEq;
132   }
134   function assertEqArray(actual, expected) {
135     var len = actual.length;
136     assertEq(len, expected.length, "mismatching array lengths");
138     var i = 0;
139     try {
140       for (; i < len; i++)
141         assertEq(actual[i], expected[i], "mismatch at element " + i);
142     } catch (e) {
143       throw new Error(`Exception thrown at index ${i}: ${e}`);
144     }
145   }
146   global.assertEqArray = assertEqArray;
148   function assertThrows(f) {
149     if (arguments.length != 1) {
150       throw new Error("Too many arguments to assertThrows; maybe you meant assertThrowsInstanceOf?");
151     }
152     var ok = false;
153     try {
154       f();
155     } catch (exc) {
156       ok = true;
157     }
158     if (!ok)
159       throw new Error(`Assertion failed: ${f} did not throw as expected`);
160   }
161   global.assertThrows = assertThrows;
163   function assertThrowsInstanceOf(f, ctor, msg) {
164     var fullmsg;
165     try {
166       f();
167     } catch (exc) {
168       if (exc instanceof ctor)
169         return;
170       fullmsg = `Assertion failed: expected exception ${ctor.name}, got ${exc}`;
171     }
173     if (fullmsg === undefined)
174       fullmsg = `Assertion failed: expected exception ${ctor.name}, no exception thrown`;
175     if (msg !== undefined)
176       fullmsg += " - " + msg;
178     throw new Error(fullmsg);
179   }
180   global.assertThrowsInstanceOf = assertThrowsInstanceOf;
182   /****************************
183    * UTILITY FUNCTION EXPORTS *
184    ****************************/
186   var dump = global.dump;
187   if (typeof global.dump === "function") {
188     // A presumptively-functional |dump| exists, so no need to do anything.
189   } else {
190     // We don't have |dump|.  Try to simulate the desired effect another way.
191     if (runningInBrowser) {
192       // We can't actually print to the console: |global.print| invokes browser
193       // printing functionality here (it's overwritten just below), and
194       // |global.dump| isn't a function that'll dump to the console (presumably
195       // because the preference to enable |dump| wasn't set).  Just make it a
196       // no-op.
197       dump = function() {};
198     } else {
199       // |print| prints to stdout: make |dump| do likewise.
200       dump = global.print;
201     }
202     global.dump = dump;
203   }
205   var print;
206   if (runningInBrowser) {
207     // We're executing in a browser.  Using |global.print| would invoke browser
208     // printing functionality: not what tests want!  Instead, use a print
209     // function that syncs up with the test harness and console.
210     print = function print() {
211       var s = "TEST-INFO | ";
212       for (var i = 0; i < arguments.length; i++)
213         s += String(arguments[i]) + " ";
215       // Dump the string to the console for developers and the harness.
216       dump(s + "\n");
218       // AddPrintOutput doesn't require HTML special characters be escaped.
219       global.AddPrintOutput(s);
220     };
222     global.print = print;
223   } else {
224     // We're executing in a shell, and |global.print| is the desired function.
225     print = global.print;
226   }
228   var gczeal = global.gczeal;
229   if (typeof gczeal !== "function") {
230     if (typeof SpecialPowersSetGCZeal === "function") {
231       gczeal = function gczeal(z) {
232         SpecialPowersSetGCZeal(z);
233       };
234     } else {
235       gczeal = function() {}; // no-op if not available
236     }
238     global.gczeal = gczeal;
239   }
241   // Evaluates the given source code as global script code. browser.js provides
242   // a different implementation for this function.
243   var evaluateScript = global.evaluateScript;
244   if (typeof evaluate === "function" && typeof evaluateScript !== "function") {
245     evaluateScript = function evaluateScript(code) {
246       evaluate(String(code));
247     };
249     global.evaluateScript = evaluateScript;
250   }
252   function toPrinted(value) {
253     value = String(value);
255     var digits = "0123456789ABCDEF";
256     var result = "";
257     for (var i = 0; i < value.length; i++) {
258       var ch = StringCharCodeAt(value, i);
259       if (ch === 0x5C && i + 1 < value.length) {
260         var d = value[i + 1];
261         if (d === "n") {
262           result += "NL";
263           i++;
264         } else if (d === "r") {
265           result += "CR";
266           i++;
267         } else {
268           result += "\\";
269         }
270       } else if (ch === 0x0A) {
271         result += "NL";
272       } else if (ch < 0x20 || ch > 0x7E) {
273         var a = digits[ch & 0xf];
274         ch >>= 4;
275         var b = digits[ch & 0xf];
276         ch >>= 4;
278         if (ch) {
279           var c = digits[ch & 0xf];
280           ch >>= 4;
281           var d = digits[ch & 0xf];
283           result += "\\u" + d + c + b + a;
284         } else {
285           result += "\\x" + b + a;
286         }
287       } else {
288         result += value[i];
289       }
290     }
292     return result;
293   }
295   /*
296    * An xorshift pseudo-random number generator see:
297    * https://en.wikipedia.org/wiki/Xorshift#xorshift.2A
298    * This generator will always produce a value, n, where
299    * 0 <= n <= 255
300    */
301   function *XorShiftGenerator(seed, size) {
302       let x = seed;
303       for (let i = 0; i < size; i++) {
304           x ^= x >> 12;
305           x ^= x << 25;
306           x ^= x >> 27;
307           yield x % 256;
308       }
309   }
310   global.XorShiftGenerator = XorShiftGenerator;
312   /*************************************************************************
313    * HARNESS-CENTRIC EXPORTS (we should generally work to eliminate these) *
314    *************************************************************************/
316   var PASSED = " PASSED! ";
317   var FAILED = " FAILED! ";
319   /*
320    * Same as `new TestCase(description, expect, actual)`, except it doesn't
321    * return the newly created test case object.
322    */
323   function AddTestCase(description, expect, actual) {
324     new TestCase(description, expect, actual);
325   }
326   global.AddTestCase = AddTestCase;
328   var testCasesArray = [];
330   function TestCase(d, e, a, r) {
331     this.description = d;
332     this.expect = e;
333     this.actual = a;
334     this.passed = getTestCaseResult(e, a);
335     this.reason = typeof r !== 'undefined' ? String(r) : '';
337     ArrayPush(testCasesArray, this);
338   }
339   global.TestCase = TestCase;
341   TestCase.prototype = ObjectCreate(null);
342   TestCase.prototype.testPassed = (function TestCase_testPassed() { return this.passed; });
343   TestCase.prototype.testFailed = (function TestCase_testFailed() { return !this.passed; });
344   TestCase.prototype.testDescription = (function TestCase_testDescription() { return this.description + ' ' + this.reason; });
346   function getTestCaseResult(expected, actual) {
347     if (typeof expected !== typeof actual)
348       return false;
349     if (typeof expected !== 'number')
350       // Note that many tests depend on the use of '==' here, not '==='.
351       return actual == expected;
353     // Distinguish NaN from other values.  Using x !== x comparisons here
354     // works even if tests redefine isNaN.
355     if (actual !== actual)
356       return expected !== expected;
357     if (expected !== expected)
358       return false;
360     // Tolerate a certain degree of error.
361     if (actual !== expected)
362       return MathAbs(actual - expected) <= 1E-10;
364     // Here would be a good place to distinguish 0 and -0, if we wanted
365     // to.  However, doing so would introduce a number of failures in
366     // areas where they don't seem important.  For example, the WeekDay
367     // function in ECMA-262 returns -0 for Sundays before the epoch, but
368     // the Date functions in SpiderMonkey specified in terms of WeekDay
369     // often don't.  This seems unimportant.
370     return true;
371   }
373   function reportTestCaseResult(description, expected, actual, output) {
374     var testcase = new TestCase(description, expected, actual, output);
376     // if running under reftest, let it handle result reporting.
377     if (!runningInBrowser) {
378       if (testcase.passed) {
379         print(PASSED + description);
380       } else {
381         reportFailure(description + " : " + output);
382       }
383     }
384   }
386   function getTestCases() {
387     return testCasesArray;
388   }
389   global.getTestCases = getTestCases;
391   /*
392    * The test driver searches for such a phrase in the test output.
393    * If such phrase exists, it will set n as the expected exit code.
394    */
395   function expectExitCode(n) {
396     print('--- NOTE: IN THIS TESTCASE, WE EXPECT EXIT CODE ' + n + ' ---');
397   }
398   global.expectExitCode = expectExitCode;
400   /*
401    * Statuses current section of a test
402    */
403   function inSection(x) {
404     return "Section " + x + " of test - ";
405   }
406   global.inSection = inSection;
408   /*
409    * Report a failure in the 'accepted' manner
410    */
411   function reportFailure(msg) {
412     msg = String(msg);
413     var lines = StringSplit(msg, "\n");
415     for (var i = 0; i < lines.length; i++)
416       print(FAILED + " " + lines[i]);
417   }
418   global.reportFailure = reportFailure;
420   /*
421    * Print a non-failure message.
422    */
423   function printStatus(msg) {
424     msg = String(msg);
425     var lines = StringSplit(msg, "\n");
427     for (var i = 0; i < lines.length; i++)
428       print("STATUS: " + lines[i]);
429   }
430   global.printStatus = printStatus;
432   /*
433   * Print a bugnumber message.
434   */
435   function printBugNumber(num) {
436     print('BUGNUMBER: ' + num);
437   }
438   global.printBugNumber = printBugNumber;
440   /*
441    * Compare expected result to actual result, if they differ (in value and/or
442    * type) report a failure.  If description is provided, include it in the
443    * failure report.
444    */
445   function reportCompare(expected, actual, description) {
446     var expected_t = typeof expected;
447     var actual_t = typeof actual;
448     var output = "";
450     if (typeof description === "undefined")
451       description = "";
453     if (expected_t !== actual_t)
454       output += `Type mismatch, expected type ${expected_t}, actual type ${actual_t} `;
456     if (expected != actual)
457       output += `Expected value '${toPrinted(expected)}', Actual value '${toPrinted(actual)}' `;
459     reportTestCaseResult(description, expected, actual, output);
460   }
461   global.reportCompare = reportCompare;
463   /*
464    * Attempt to match a regular expression describing the result to
465    * the actual result, if they differ (in value and/or
466    * type) report a failure.  If description is provided, include it in the
467    * failure report.
468    */
469   function reportMatch(expectedRegExp, actual, description) {
470     var expected_t = "string";
471     var actual_t = typeof actual;
472     var output = "";
474     if (typeof description === "undefined")
475       description = "";
477     if (expected_t !== actual_t)
478       output += `Type mismatch, expected type ${expected_t}, actual type ${actual_t} `;
480     var matches = ReflectApply(RegExpPrototypeExec, expectedRegExp, [actual]) !== null;
481     if (!matches) {
482       output +=
483         `Expected match to '${toPrinted(expectedRegExp)}', Actual value '${toPrinted(actual)}' `;
484     }
486     reportTestCaseResult(description, true, matches, output);
487   }
488   global.reportMatch = reportMatch;
490   function compareSource(expect, actual, summary) {
491     // compare source
492     var expectP = String(expect);
493     var actualP = String(actual);
495     print('expect:\n' + expectP);
496     print('actual:\n' + actualP);
498     reportCompare(expectP, actualP, summary);
500     // actual must be compilable if expect is?
501     try {
502       var expectCompile = 'No Error';
503       var actualCompile;
505       Function(expect);
506       try {
507         Function(actual);
508         actualCompile = 'No Error';
509       } catch(ex1) {
510         actualCompile = ex1 + '';
511       }
512       reportCompare(expectCompile, actualCompile,
513                     summary + ': compile actual');
514     } catch(ex) {
515     }
516   }
517   global.compareSource = compareSource;
519   function test() {
520     var testCases = getTestCases();
521     for (var i = 0; i < testCases.length; i++) {
522       var testCase = testCases[i];
523       testCase.reason += testCase.passed ? "" : "wrong value ";
525       // if running under reftest, let it handle result reporting.
526       if (!runningInBrowser) {
527         var message = `${testCase.description} = ${testCase.actual} expected: ${testCase.expect}`;
528         print((testCase.passed ? PASSED : FAILED) + message);
529       }
530     }
531   }
532   global.test = test;
534   // This function uses the shell's print function. When running tests in the
535   // browser, browser.js overrides this function to write to the page.
536   function writeHeaderToLog(string) {
537     print(string);
538   }
539   global.writeHeaderToLog = writeHeaderToLog;
541   /************************************
542    * PROMISE TESTING FUNCTION EXPORTS *
543    ************************************/
545   function getPromiseResult(promise) {
546     var result, error, caught = false;
547     promise.then(r => { result = r; },
548                  e => { caught = true; error = e; });
549     drainJobQueue();
550     if (caught)
551       throw error;
552     return result;
553   }
554   global.getPromiseResult = getPromiseResult;
556   function assertEventuallyEq(promise, expected) {
557     assertEq(getPromiseResult(promise), expected);
558   }
559   global.assertEventuallyEq = assertEventuallyEq;
561   function assertEventuallyThrows(promise, expectedErrorType) {
562     assertThrowsInstanceOf(() => getPromiseResult(promise), expectedErrorType);
563   };
564   global.assertEventuallyThrows = assertEventuallyThrows;
566   function assertEventuallyDeepEq(promise, expected) {
567     assertDeepEq(getPromiseResult(promise), expected);
568   };
569   global.assertEventuallyDeepEq = assertEventuallyDeepEq;
571   /*******************************************
572    * RUN ONCE CODE TO SETUP ADDITIONAL STATE *
573    *******************************************/
575   // Clear all options before running any tests. browser.js performs this
576   // set-up as part of its jsTestDriverBrowserInit function.
577   if (!runningInBrowser) {
578     shellOptionsClear();
579   }
581   if (!runningInBrowser) {
582     // Set the minimum heap size for parallel marking to zero for testing
583     // purposes. We don't have access to gcparam in the browser.
584     gcparam('parallelMarkingThresholdMB', 0);
585   }
586 })(this);
588 var DESCRIPTION;