tor-browser

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

Assert.sys.mjs (21984B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 // Originally from narwhal.js (http://narwhaljs.org)
      6 // Copyright (c) 2009 Thomas Robinson <280north.com>
      7 // MIT license: http://opensource.org/licenses/MIT
      8 
      9 import { ObjectUtils } from "resource://gre/modules/ObjectUtils.sys.mjs";
     10 
     11 /**
     12 * This module is based on the
     13 * `CommonJS spec <https://wiki.commonjs.org/wiki/Unit_Testing/1.0>`_
     14 *
     15 * When you see a jsdoc comment that contains a number, it's a reference to a
     16 * specific section of the CommonJS spec.
     17 *
     18 * 1. The assert module provides functions that throw AssertionError's when
     19 * particular conditions are not met.
     20 *
     21 * To use the module you may instantiate it first.
     22 *
     23 * @param {reporterFunc} reporterFunc
     24 *        Allows consumers to override reporting for this instance.
     25 * @param {boolean} isDefault
     26 *        Used by test suites to set ``reporterFunc`` as the default
     27 *        used by the global instance, which is called for example
     28 *        by other test-only modules. This is false when the
     29 *        reporter is set by content scripts, because they may still
     30 *        run in the parent process.
     31 *
     32 * @class
     33 */
     34 export function Assert(reporterFunc, isDefault) {
     35  if (reporterFunc) {
     36    this.setReporter(reporterFunc);
     37  }
     38  if (isDefault) {
     39    Assert.setReporter(reporterFunc);
     40  }
     41 }
     42 
     43 // This allows using the Assert object as an additional global instance.
     44 Object.setPrototypeOf(Assert, Assert.prototype);
     45 
     46 function instanceOf(object, type) {
     47  return Object.prototype.toString.call(object) == "[object " + type + "]";
     48 }
     49 
     50 function replacer(key, value) {
     51  if (value === undefined) {
     52    return "" + value;
     53  }
     54  if (typeof value === "number" && (isNaN(value) || !isFinite(value))) {
     55    return value.toString();
     56  }
     57  if (typeof value === "function" || instanceOf(value, "RegExp")) {
     58    return value.toString();
     59  }
     60  if (
     61    typeof value === "object" &&
     62    value !== null &&
     63    "QueryInterface" in value
     64  ) {
     65    return value.toString();
     66  }
     67  return value;
     68 }
     69 
     70 const kTruncateLength = 128;
     71 
     72 function truncate(text, newLength = kTruncateLength) {
     73  if (typeof text == "string") {
     74    return text.length < newLength ? text : text.slice(0, newLength);
     75  }
     76  return text;
     77 }
     78 
     79 function getMessage(error, prefix = "") {
     80  let actual, expected;
     81  // Wrap calls to JSON.stringify in try...catch blocks, as they may throw. If
     82  // so, fall back to toString().
     83  try {
     84    actual = JSON.stringify(error.actual, replacer);
     85  } catch (ex) {
     86    actual = Object.prototype.toString.call(error.actual);
     87  }
     88  try {
     89    expected = JSON.stringify(error.expected, replacer);
     90  } catch (ex) {
     91    expected = Object.prototype.toString.call(error.expected);
     92  }
     93  let message = prefix;
     94  if (error.operator) {
     95    let truncateLength = error.truncate ? kTruncateLength : Infinity;
     96    message +=
     97      (prefix ? " - " : "") +
     98      truncate(actual, truncateLength) +
     99      " " +
    100      error.operator +
    101      " " +
    102      truncate(expected, truncateLength);
    103  }
    104  return message;
    105 }
    106 
    107 /**
    108 * 2. The AssertionError is defined in assert.
    109 *
    110 * At present only the four keys mentioned below are used and
    111 * understood by the spec. Implementations or sub modules can pass
    112 * other keys to the AssertionError's constructor - they will be
    113 * ignored.
    114 *
    115 * @example
    116 *
    117 * new assert.AssertionError({
    118 *   message: message,
    119 *   actual: actual,
    120 *   expected: expected,
    121 *   operator: operator,
    122 *   truncate: truncate,
    123 *   stack: stack, // Optional, defaults to the current stack.
    124 * });
    125 */
    126 Assert.AssertionError = function (options) {
    127  this.name = "AssertionError";
    128  this.actual = options.actual;
    129  this.expected = options.expected;
    130  this.operator = options.operator;
    131  this.message = getMessage(this, options.message, options.truncate);
    132  // The part of the stack that comes from this module is not interesting.
    133  let stack = options.stack || Components.stack;
    134  do {
    135    stack = stack.asyncCaller || stack.caller;
    136  } while (
    137    stack &&
    138    stack.filename &&
    139    stack.filename.includes("Assert.sys.mjs")
    140  );
    141  this.stack = stack;
    142 };
    143 
    144 // assert.AssertionError instanceof Error
    145 Assert.AssertionError.prototype = Object.create(Error.prototype, {
    146  constructor: {
    147    value: Assert.AssertionError,
    148    enumerable: false,
    149    writable: true,
    150    configurable: true,
    151  },
    152 });
    153 
    154 Assert.prototype._reporter = null;
    155 
    156 /**
    157 * This callback type is used for custom assertion report handling.
    158 *
    159 * @callback reporterFunc
    160 * @param {AssertionError|null} err
    161 *        An error object when the assertion failed, or null when it passed.
    162 * @param {string} message
    163 *        Message describing the assertion.
    164 * @param {Stack} stack
    165 *        Stack trace of the assertion function.
    166 */
    167 
    168 /**
    169 * Set a custom assertion report handler function.
    170 *
    171 * @example
    172 *
    173 * Assert.setReporter(function customReporter(err, message, stack) {
    174 *   if (err) {
    175 *     do_report_result(false, err.message, err.stack);
    176 *   } else {
    177 *     do_report_result(true, message, stack);
    178 *   }
    179 * });
    180 *
    181 * @param {reporterFunc} reporterFunc
    182 *        Report handler function.
    183 */
    184 Assert.prototype.setReporter = function (reporterFunc) {
    185  this._reporter = reporterFunc;
    186 };
    187 
    188 /**
    189 * 3. All of the following functions must throw an AssertionError when a
    190 * corresponding condition is not met, with a message that may be undefined if
    191 * not provided.  All assertion methods provide both the actual and expected
    192 * values to the assertion error for display purposes.
    193 *
    194 * This report method only throws errors on assertion failures, as per spec,
    195 * but consumers of this module (think: xpcshell-test, mochitest) may want to
    196 * override this default implementation.
    197 *
    198 * @example
    199 *
    200 * // The following will report an assertion failure.
    201 * this.report(1 != 2, 1, 2, "testing JS number math!", "==");
    202 *
    203 * @param {boolean} failed
    204 *        Indicates if the assertion failed or not.
    205 * @param {*} actual
    206 *        The result of evaluating the assertion.
    207 * @param {*} [expected]
    208 *        Expected result from the test author.
    209 * @param {string} [message]
    210 *        Short explanation of the expected result.
    211 * @param {string} [operator]
    212 *        Operation qualifier used by the assertion method (ex: '==').
    213 * @param {boolean} [truncate=true]
    214 *        Whether or not ``actual`` and ``expected`` should be truncated when printing.
    215 * @param {nsIStackFrame} [stack]
    216 *        The stack trace including the caller of the assertion method,
    217 *        if this cannot be inferred automatically (e.g. due to async callbacks).
    218 */
    219 Assert.prototype.report = function (
    220  failed,
    221  actual,
    222  expected,
    223  message,
    224  operator,
    225  truncate = true,
    226  stack = null // Defaults to Components.stack in AssertionError.
    227 ) {
    228  // Although not ideal, we allow a "null" message due to the way some of the extension tests
    229  // work.
    230  if (message !== undefined && message !== null && typeof message != "string") {
    231    this.ok(
    232      false,
    233      `Expected a string or undefined for the error message to Assert.*, got ${typeof message}`
    234    );
    235  }
    236  let err = new Assert.AssertionError({
    237    message,
    238    actual,
    239    expected,
    240    operator,
    241    truncate,
    242    stack,
    243  });
    244  if (!this._reporter) {
    245    // If no custom reporter is set, throw the error.
    246    if (failed) {
    247      throw err;
    248    }
    249  } else {
    250    this._reporter(failed ? err : null, err.message, err.stack);
    251  }
    252 };
    253 
    254 /**
    255 * 4. Pure assertion tests whether a value is truthy, as determined by !!guard.
    256 * ``assert.ok(guard, message_opt);``
    257 * This statement is equivalent to ``assert.equal(true, !!guard, message_opt);``.
    258 * To test strictly for the value true, use ``assert.strictEqual(true, guard,
    259 * message_opt);``.
    260 *
    261 * @param {*} value
    262 *        Test subject to be evaluated as truthy.
    263 * @param {string} [message]
    264 *        Short explanation of the expected result.
    265 */
    266 Assert.prototype.ok = function (value, message) {
    267  if (arguments.length > 2) {
    268    this.report(
    269      true,
    270      false,
    271      true,
    272      "Too many arguments passed to `Assert.ok()`",
    273      "=="
    274    );
    275  } else {
    276    this.report(!value, value, true, message, "==");
    277  }
    278 };
    279 
    280 /**
    281 * 5. The equality assertion tests shallow, coercive equality with ==.
    282 * ``assert.equal(actual, expected, message_opt);``
    283 *
    284 * @param {*} actual
    285 *        Test subject to be evaluated as equivalent to ``expected``.
    286 * @param {*} expected
    287 *        Test reference to evaluate against ``actual``.
    288 * @param {string} [message]
    289 *        Short explanation of the expected result.
    290 */
    291 Assert.prototype.equal = function equal(actual, expected, message) {
    292  this.report(actual != expected, actual, expected, message, "==");
    293 };
    294 
    295 /**
    296 * 6. The non-equality assertion tests for whether two objects are not equal
    297 * with ``!=``
    298 *
    299 * @example
    300 * assert.notEqual(actual, expected, message_opt);
    301 *
    302 * @param {*} actual
    303 *        Test subject to be evaluated as NOT equivalent to ``expected``.
    304 * @param {*} expected
    305 *        Test reference to evaluate against ``actual``.
    306 * @param {string} [message]
    307 *        Short explanation of the expected result.
    308 */
    309 Assert.prototype.notEqual = function notEqual(actual, expected, message) {
    310  this.report(actual == expected, actual, expected, message, "!=");
    311 };
    312 
    313 /**
    314 * 7. The equivalence assertion tests a deep equality relation.
    315 * assert.deepEqual(actual, expected, message_opt);
    316 *
    317 * We check using the most exact approximation of equality between two objects
    318 * to keep the chance of false positives to a minimum.
    319 * `JSON.stringify` is not designed to be used for this purpose; objects may
    320 * have ambiguous `toJSON()` implementations that would influence the test.
    321 *
    322 * @param {*} actual
    323 *        Test subject to be evaluated as equivalent to ``expected``, including nested properties.
    324 * @param {*} expected
    325 *        Test reference to evaluate against ``actual``.
    326 * @param {string} [message]
    327 *        Short explanation of the expected result.
    328 */
    329 Assert.prototype.deepEqual = function deepEqual(actual, expected, message) {
    330  this.report(
    331    !ObjectUtils.deepEqual(actual, expected),
    332    actual,
    333    expected,
    334    message,
    335    "deepEqual",
    336    false
    337  );
    338 };
    339 
    340 /**
    341 * 8. The non-equivalence assertion tests for any deep inequality.
    342 * assert.notDeepEqual(actual, expected, message_opt);
    343 *
    344 * @param {*} actual
    345 *        Test subject to be evaluated as NOT equivalent to ``expected``, including nested
    346 *        properties.
    347 * @param {*} expected
    348 *        Test reference to evaluate against ``actual``.
    349 * @param {string} [message]
    350 *        Short explanation of the expected result.
    351 */
    352 Assert.prototype.notDeepEqual = function notDeepEqual(
    353  actual,
    354  expected,
    355  message
    356 ) {
    357  this.report(
    358    ObjectUtils.deepEqual(actual, expected),
    359    actual,
    360    expected,
    361    message,
    362    "notDeepEqual",
    363    false
    364  );
    365 };
    366 
    367 /**
    368 * 9. The strict equality assertion tests strict equality, as determined by ===.
    369 * ``assert.strictEqual(actual, expected, message_opt);``
    370 *
    371 * @param {*} actual
    372 *        Test subject to be evaluated as strictly equivalent to ``expected``.
    373 * @param {*} expected
    374 *        Test reference to evaluate against ``actual``.
    375 * @param {string} [message]
    376 *        Short explanation of the expected result.
    377 */
    378 Assert.prototype.strictEqual = function strictEqual(actual, expected, message) {
    379  this.report(actual !== expected, actual, expected, message, "===");
    380 };
    381 
    382 /**
    383 * 10. The strict non-equality assertion tests for strict inequality, as
    384 * determined by !==. ``assert.notStrictEqual(actual, expected, message_opt);``
    385 *
    386 * @param {*} actual
    387 *        Test subject to be evaluated as NOT strictly equivalent to ``expected``.
    388 * @param {*} expected
    389 *        Test reference to evaluate against ``actual``.
    390 * @param {string} [message]
    391 *        Short explanation of the expected result.
    392 */
    393 Assert.prototype.notStrictEqual = function notStrictEqual(
    394  actual,
    395  expected,
    396  message
    397 ) {
    398  this.report(actual === expected, actual, expected, message, "!==");
    399 };
    400 
    401 function checkExpectedArgument(instance, funcName, expected) {
    402  if (!expected) {
    403    instance.ok(
    404      false,
    405      `Error: The 'expected' argument was not supplied to Assert.${funcName}()`
    406    );
    407  }
    408 
    409  if (
    410    !instanceOf(expected, "RegExp") &&
    411    typeof expected !== "function" &&
    412    typeof expected !== "object"
    413  ) {
    414    instance.ok(
    415      false,
    416      `Error: The 'expected' argument to Assert.${funcName}() must be a RegExp, function or an object`
    417    );
    418  }
    419 }
    420 
    421 function expectedException(actual, expected) {
    422  if (!actual || !expected) {
    423    return false;
    424  }
    425 
    426  if (instanceOf(expected, "RegExp")) {
    427    return expected.test(actual);
    428    // We need to guard against the right hand parameter of "instanceof" lacking
    429    // the "prototype" property, which is true of arrow functions in particular.
    430  } else if (
    431    !(typeof expected === "function" && !expected.prototype) &&
    432    actual instanceof expected
    433  ) {
    434    return true;
    435  } else if (expected.call({}, actual) === true) {
    436    return true;
    437  }
    438 
    439  return false;
    440 }
    441 
    442 /**
    443 * 11. Expected to throw an error:
    444 * assert.throws(block, Error_opt, message_opt);
    445 *
    446 * Example:
    447 * ```js
    448 * // The following will verify that an error of type TypeError was thrown:
    449 * Assert.throws(() => testBody(), TypeError);
    450 * // The following will verify that an error was thrown with an error message matching "hello":
    451 * Assert.throws(() => testBody(), /hello/);
    452 * ```
    453 *
    454 * @param {Function} block
    455 *        Function to evaluate and catch eventual thrown errors.
    456 * @param {RegExp|Function} expected
    457 *        This parameter can be either a RegExp or a function. The function is
    458 *        either the error type's constructor, or it's a method that returns
    459 *        a boolean that describes the test outcome.
    460 * @param {string} [message]
    461 *        Short explanation of the expected result.
    462 */
    463 Assert.prototype.throws = function (block, expected, message) {
    464  checkExpectedArgument(this, "throws", expected);
    465 
    466  // `true` if we realize that we have added an
    467  // error to `ChromeUtils.recentJSDevError` and
    468  // that we probably need to clean it up.
    469  let cleanupRecentJSDevError = false;
    470  if ("recentJSDevError" in ChromeUtils) {
    471    // Check that we're in a build of Firefox that supports
    472    // the `recentJSDevError` mechanism (i.e. Nightly build).
    473    if (ChromeUtils.recentJSDevError === undefined) {
    474      // There was no previous error, so if we throw
    475      // an error here, we may need to clean it up.
    476      cleanupRecentJSDevError = true;
    477    }
    478  }
    479 
    480  let actual;
    481 
    482  try {
    483    block();
    484  } catch (e) {
    485    actual = e;
    486  }
    487 
    488  message =
    489    (expected.name ? " (" + expected.name + ")." : ".") +
    490    (message ? " " + message : ".");
    491 
    492  if (!actual) {
    493    this.report(true, actual, expected, "Missing expected exception" + message);
    494  }
    495 
    496  if (actual && !expectedException(actual, expected)) {
    497    throw actual;
    498  }
    499 
    500  this.report(false, expected, expected, message);
    501 
    502  // Make sure that we don't cause failures for JS Dev Errors that
    503  // were expected, typically for tests that attempt to check
    504  // that we react properly to TypeError, ReferenceError, SyntaxError.
    505  if (cleanupRecentJSDevError) {
    506    let recentJSDevError = ChromeUtils.recentJSDevError;
    507    if (recentJSDevError) {
    508      if (expectedException(recentJSDevError)) {
    509        ChromeUtils.clearRecentJSDevError();
    510      }
    511    }
    512  }
    513 };
    514 
    515 /**
    516 * A promise that is expected to reject:
    517 * assert.rejects(promise, expected, message);
    518 *
    519 * @param {Promise} promise
    520 *        A promise that is expected to reject.
    521 * @param {?} [expected]
    522 *        Test reference to evaluate against the rejection result.
    523 * @param {string} [message]
    524 *        Short explanation of the expected result.
    525 */
    526 Assert.prototype.rejects = function (promise, expected, message) {
    527  checkExpectedArgument(this, "rejects", expected);
    528  const operator = undefined; // Should we use "rejects" here?
    529  const stack = Components.stack;
    530  return new Promise((resolve, reject) => {
    531    return promise
    532      .then(
    533        () => {
    534          this.report(
    535            true,
    536            null,
    537            expected,
    538            "Missing expected exception " + message,
    539            operator,
    540            true,
    541            stack
    542          );
    543          // this.report() above should raise an AssertionError. If _reporter
    544          // has been overridden and doesn't throw an error, just resolve.
    545          // Otherwise we'll have a never-resolving promise that got stuck.
    546          resolve();
    547        },
    548        err => {
    549          if (!expectedException(err, expected)) {
    550            // TODO bug 1480075: Should report error instead of rejecting.
    551            reject(err);
    552            return;
    553          }
    554          this.report(false, err, expected, message, operator, truncate, stack);
    555          resolve();
    556        }
    557      )
    558      .catch(reject);
    559  });
    560 };
    561 
    562 function compareNumbers(expression, lhs, rhs, message, operator) {
    563  let lhsIsNumber = typeof lhs == "number" && !Number.isNaN(lhs);
    564  let rhsIsNumber = typeof rhs == "number" && !Number.isNaN(rhs);
    565 
    566  if (lhsIsNumber && rhsIsNumber) {
    567    this.report(expression, lhs, rhs, message, operator);
    568    return;
    569  }
    570  let lhsIsDate =
    571    typeof lhs == "object" && lhs.constructor.name == "Date" && !isNaN(lhs);
    572  let rhsIsDate =
    573    typeof rhs == "object" && rhs.constructor.name == "Date" && !isNaN(rhs);
    574  if (lhsIsDate && rhsIsDate) {
    575    this.report(expression, lhs, rhs, message, operator);
    576    return;
    577  }
    578 
    579  let errorMessage;
    580  if (!lhsIsNumber && !rhsIsNumber && !lhsIsDate && !rhsIsDate) {
    581    errorMessage = `Neither '${lhs}' nor '${rhs}' are numbers or dates.`;
    582  } else if ((lhsIsNumber && rhsIsDate) || (lhsIsDate && rhsIsNumber)) {
    583    errorMessage = `'${lhsIsNumber ? lhs : rhs}' is a number and '${
    584      rhsIsDate ? rhs : lhs
    585    }' is a date.`;
    586  } else {
    587    errorMessage = `'${
    588      lhsIsNumber || lhsIsDate ? rhs : lhs
    589    }' is not a number or date.`;
    590  }
    591  this.report(true, lhs, rhs, errorMessage);
    592 }
    593 
    594 /**
    595 * The lhs must be greater than the rhs.
    596 * assert.greater(lhs, rhs, message_opt);
    597 *
    598 * @param {number} lhs
    599 *        The left-hand side value.
    600 * @param {number} rhs
    601 *        The right-hand side value.
    602 * @param {string} [message]
    603 *        Short explanation of the comparison result.
    604 */
    605 Assert.prototype.greater = function greater(lhs, rhs, message) {
    606  compareNumbers.call(this, lhs <= rhs, lhs, rhs, message, ">");
    607 };
    608 
    609 /**
    610 * The lhs must be greater than or equal to the rhs.
    611 * assert.greaterOrEqual(lhs, rhs, message_opt);
    612 *
    613 * @param {number} [lhs]
    614 *        The left-hand side value.
    615 * @param {number} [rhs]
    616 *        The right-hand side value.
    617 * @param {string} [message]
    618 *        Short explanation of the comparison result.
    619 */
    620 Assert.prototype.greaterOrEqual = function greaterOrEqual(lhs, rhs, message) {
    621  compareNumbers.call(this, lhs < rhs, lhs, rhs, message, ">=");
    622 };
    623 
    624 /**
    625 * The lhs must be less than the rhs.
    626 * assert.less(lhs, rhs, message_opt);
    627 *
    628 * @param {number} [lhs]
    629 *        The left-hand side value.
    630 * @param {number} [rhs]
    631 *        The right-hand side value.
    632 * @param {string} [message]
    633 *        Short explanation of the comparison result.
    634 */
    635 Assert.prototype.less = function less(lhs, rhs, message) {
    636  compareNumbers.call(this, lhs >= rhs, lhs, rhs, message, "<");
    637 };
    638 
    639 /**
    640 * The lhs must be less than or equal to the rhs.
    641 * assert.lessOrEqual(lhs, rhs, message_opt);
    642 *
    643 * @param {number} [lhs]
    644 *        The left-hand side value.
    645 * @param {number} [rhs]
    646 *        The right-hand side value.
    647 * @param {string} [message]
    648 *        Short explanation of the comparison result.
    649 */
    650 Assert.prototype.lessOrEqual = function lessOrEqual(lhs, rhs, message) {
    651  compareNumbers.call(this, lhs > rhs, lhs, rhs, message, "<=");
    652 };
    653 
    654 /**
    655 * The lhs must be a string that matches the rhs regular expression.
    656 * rhs can be specified either as a string or a RegExp object. If specified as a
    657 * string it will be interpreted as a regular expression so take care to escape
    658 * special characters such as "?" or "(" if you need the actual characters.
    659 *
    660 * @param {string} lhs
    661 *        The string to be tested.
    662 * @param {string | RegExp} rhs
    663 *        The regular expression that the string will be tested with.
    664 *        Note that if passed as a string, this will be interpreted.
    665 *        as a regular expression.
    666 * @param {string} [message]
    667 *        Short explanation of the comparison result.
    668 */
    669 Assert.prototype.stringMatches = function stringMatches(lhs, rhs, message) {
    670  if (typeof rhs != "string" && !instanceOf(rhs, "RegExp")) {
    671    this.report(
    672      true,
    673      lhs,
    674      String(rhs),
    675      `Expected a string or a RegExp for rhs, but "${rhs}" isn't a string or a RegExp object.`
    676    );
    677    return;
    678  }
    679 
    680  if (typeof lhs != "string") {
    681    this.report(
    682      true,
    683      lhs,
    684      String(rhs),
    685      `Expected a string for lhs, but "${lhs}" isn't a string.`
    686    );
    687    return;
    688  }
    689 
    690  if (typeof rhs == "string") {
    691    try {
    692      rhs = new RegExp(rhs);
    693    } catch {
    694      this.report(
    695        true,
    696        lhs,
    697        rhs,
    698        `Expected a valid regular expression for rhs, but "${rhs}" isn't one.`
    699      );
    700      return;
    701    }
    702  }
    703 
    704  const isCorrect = rhs.test(lhs);
    705  this.report(!isCorrect, lhs, rhs.toString(), message, "matches");
    706 };
    707 
    708 /**
    709 * The lhs must be a string that contains the rhs string.
    710 *
    711 * @param {string} lhs
    712 *        The string to be tested (haystack).
    713 * @param {string} rhs
    714 *        The string to be found (needle).
    715 * @param {string} [message]
    716 *        Short explanation of the expected result.
    717 */
    718 Assert.prototype.stringContains = function stringContains(lhs, rhs, message) {
    719  if (typeof lhs != "string" || typeof rhs != "string") {
    720    this.report(
    721      true,
    722      lhs,
    723      rhs,
    724      `Expected a string for both lhs and rhs, but either "${lhs}" or "${rhs}" is not a string.`
    725    );
    726  }
    727 
    728  const isCorrect = lhs.includes(rhs);
    729  this.report(!isCorrect, lhs, rhs, message, "includes");
    730 };