tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

shell.js (18295B)


      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/. */
      5 
      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.
     11 
     12 (function(global) {
     13  "use strict";
     14 
     15  /**********************************************************************
     16   * CACHED PRIMORDIAL FUNCTIONALITY (before a test might overwrite it) *
     17   **********************************************************************/
     18 
     19  var undefined; // sigh
     20 
     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;
     28 
     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;
     38 
     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.
     43 
     44    var SpecialPowersSetGCZeal =
     45      global.SpecialPowers ? global.SpecialPowers.setGCZeal : undefined;
     46  }
     47 
     48  var evaluate = global.evaluate;
     49  var options = global.options;
     50 
     51  /****************************
     52   * GENERAL HELPER FUNCTIONS *
     53   ****************************/
     54 
     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");
     61 
     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  }
     69 
     70  function StringCharCodeAt(str, index) {
     71    return ReflectApply(StringPrototypeCharCodeAt, str, [index]);
     72  }
     73 
     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");
     79 
     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      }
     89 
     90      ArrayPush(parts, ReflectApply(StringPrototypeSubstring, str, [last, i]));
     91      last = i + delimiter.length;
     92    }
     93  }
     94 
     95  function shellOptionsClear() {
     96    assertEq(runningInBrowser, false, "Only called when running in the shell.");
     97 
     98    // Return early if no options are set.
     99    var currentOptions = options ? options() : "";
    100    if (currentOptions === "")
    101      return;
    102 
    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  }
    109 
    110  /****************************
    111   * TESTING FUNCTION EXPORTS *
    112   ****************************/
    113 
    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  }
    122 
    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  }
    133 
    134  function assertEqArray(actual, expected) {
    135    var len = actual.length;
    136    assertEq(len, expected.length, "mismatching array lengths");
    137 
    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;
    147 
    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;
    162 
    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    }
    172 
    173    if (fullmsg === undefined)
    174      fullmsg = `Assertion failed: expected exception ${ctor.name}, no exception thrown`;
    175    if (msg !== undefined)
    176      fullmsg += " - " + msg;
    177 
    178    throw new Error(fullmsg);
    179  }
    180  global.assertThrowsInstanceOf = assertThrowsInstanceOf;
    181 
    182  /****************************
    183   * UTILITY FUNCTION EXPORTS *
    184   ****************************/
    185 
    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  }
    204 
    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]) + " ";
    214 
    215      // Dump the string to the console for developers and the harness.
    216      dump(s + "\n");
    217 
    218      // AddPrintOutput doesn't require HTML special characters be escaped.
    219      global.AddPrintOutput(s);
    220    };
    221 
    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  }
    227 
    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    }
    237 
    238    global.gczeal = gczeal;
    239  }
    240 
    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    };
    248 
    249    global.evaluateScript = evaluateScript;
    250  }
    251 
    252  function toPrinted(value) {
    253    value = String(value);
    254 
    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;
    277 
    278        if (ch) {
    279          var c = digits[ch & 0xf];
    280          ch >>= 4;
    281          var d = digits[ch & 0xf];
    282 
    283          result += "\\u" + d + c + b + a;
    284        } else {
    285          result += "\\x" + b + a;
    286        }
    287      } else {
    288        result += value[i];
    289      }
    290    }
    291 
    292    return result;
    293  }
    294 
    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;
    311 
    312  /*************************************************************************
    313   * HARNESS-CENTRIC EXPORTS (we should generally work to eliminate these) *
    314   *************************************************************************/
    315 
    316  var PASSED = " PASSED! ";
    317  var FAILED = " FAILED! ";
    318 
    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;
    327 
    328  var testCasesArray = [];
    329 
    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) : '';
    336 
    337    ArrayPush(testCasesArray, this);
    338  }
    339  global.TestCase = TestCase;
    340 
    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; });
    345 
    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;
    352 
    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;
    359 
    360    // Tolerate a certain degree of error.
    361    if (actual !== expected)
    362      return MathAbs(actual - expected) <= 1E-10;
    363 
    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  }
    372 
    373  function reportTestCaseResult(description, expected, actual, output) {
    374    var testcase = new TestCase(description, expected, actual, output);
    375 
    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  }
    385 
    386  function getTestCases() {
    387    return testCasesArray;
    388  }
    389  global.getTestCases = getTestCases;
    390 
    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;
    399 
    400  /*
    401   * Statuses current section of a test
    402   */
    403  function inSection(x) {
    404    return "Section " + x + " of test - ";
    405  }
    406  global.inSection = inSection;
    407 
    408  /*
    409   * Report a failure in the 'accepted' manner
    410   */
    411  function reportFailure(msg) {
    412    msg = String(msg);
    413    var lines = StringSplit(msg, "\n");
    414 
    415    for (var i = 0; i < lines.length; i++)
    416      print(FAILED + " " + lines[i]);
    417  }
    418  global.reportFailure = reportFailure;
    419 
    420  /*
    421   * Print a non-failure message.
    422   */
    423  function printStatus(msg) {
    424    msg = String(msg);
    425    var lines = StringSplit(msg, "\n");
    426 
    427    for (var i = 0; i < lines.length; i++)
    428      print("STATUS: " + lines[i]);
    429  }
    430  global.printStatus = printStatus;
    431 
    432  /*
    433  * Print a bugnumber message.
    434  */
    435  function printBugNumber(num) {
    436    print('BUGNUMBER: ' + num);
    437  }
    438  global.printBugNumber = printBugNumber;
    439 
    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 = "";
    449 
    450    if (typeof description === "undefined")
    451      description = "";
    452 
    453    if (expected_t !== actual_t)
    454      output += `Type mismatch, expected type ${expected_t}, actual type ${actual_t} `;
    455 
    456    if (expected != actual)
    457      output += `Expected value '${toPrinted(expected)}', Actual value '${toPrinted(actual)}' `;
    458 
    459    reportTestCaseResult(description, expected, actual, output);
    460  }
    461  global.reportCompare = reportCompare;
    462 
    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 = "";
    473 
    474    if (typeof description === "undefined")
    475      description = "";
    476 
    477    if (expected_t !== actual_t)
    478      output += `Type mismatch, expected type ${expected_t}, actual type ${actual_t} `;
    479 
    480    var matches = ReflectApply(RegExpPrototypeExec, expectedRegExp, [actual]) !== null;
    481    if (!matches) {
    482      output +=
    483        `Expected match to '${toPrinted(expectedRegExp)}', Actual value '${toPrinted(actual)}' `;
    484    }
    485 
    486    reportTestCaseResult(description, true, matches, output);
    487  }
    488  global.reportMatch = reportMatch;
    489 
    490  function compareSource(expect, actual, summary) {
    491    // compare source
    492    var expectP = String(expect);
    493    var actualP = String(actual);
    494 
    495    print('expect:\n' + expectP);
    496    print('actual:\n' + actualP);
    497 
    498    reportCompare(expectP, actualP, summary);
    499 
    500    // actual must be compilable if expect is?
    501    try {
    502      var expectCompile = 'No Error';
    503      var actualCompile;
    504 
    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;
    518 
    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 ";
    524 
    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;
    533 
    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;
    540 
    541  /************************************
    542   * PROMISE TESTING FUNCTION EXPORTS *
    543   ************************************/
    544 
    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;
    555 
    556  function assertEventuallyEq(promise, expected) {
    557    assertEq(getPromiseResult(promise), expected);
    558  }
    559  global.assertEventuallyEq = assertEventuallyEq;
    560 
    561  function assertEventuallyThrows(promise, expectedErrorType) {
    562    assertThrowsInstanceOf(() => getPromiseResult(promise), expectedErrorType);
    563  };
    564  global.assertEventuallyThrows = assertEventuallyThrows;
    565 
    566  function assertEventuallyDeepEq(promise, expected) {
    567    assertDeepEq(getPromiseResult(promise), expected);
    568  };
    569  global.assertEventuallyDeepEq = assertEventuallyDeepEq;
    570 
    571  /*******************************************
    572   * RUN ONCE CODE TO SETUP ADDITIONAL STATE *
    573   *******************************************/
    574 
    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  }
    580 
    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);
    587 
    588 var DESCRIPTION;