assert.js 8.83 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// Some methods are taken from v8/test/mjsunit/mjsunit.js

30 31 32 33 34 35 36 37

function classOf(object) {
   // Argument must not be null or undefined.
   var string = Object.prototype.toString.call(object);
   // String has format [object <ClassName>].
   return string.substring(8, string.length - 1);
}

38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
/**
 * Compares two objects for key/value equality.
 * Returns true if they are equal, false otherwise.
 */
function deepObjectEquals(a, b) {
  var aProps = Object.keys(a);
  aProps.sort();
  var bProps = Object.keys(b);
  bProps.sort();
  if (!deepEquals(aProps, bProps)) {
    return false;
  }
  for (var i = 0; i < aProps.length; i++) {
    if (!deepEquals(a[aProps[i]], b[aProps[i]])) {
      return false;
    }
  }
  return true;
}


/**
 * Compares two JavaScript values for type and value equality.
 * It checks internals of arrays and objects.
 */
function deepEquals(a, b) {
  if (a === b) {
    // Check for -0.
    if (a === 0) return (1 / a) === (1 / b);
    return true;
  }
  if (typeof a != typeof b) return false;
  if (typeof a == 'number') return isNaN(a) && isNaN(b);
  if (typeof a !== 'object' && typeof a !== 'function') return false;
  // Neither a nor b is primitive.
  var objectClass = classOf(a);
  if (objectClass !== classOf(b)) return false;
  if (objectClass === 'RegExp') {
    // For RegExp, just compare pattern and flags using its toString.
    return (a.toString() === b.toString());
  }
  // Functions are only identical to themselves.
  if (objectClass === 'Function') return false;
  if (objectClass === 'Array') {
    var elementCount = 0;
    if (a.length != b.length) {
      return false;
    }
    for (var i = 0; i < a.length; i++) {
      if (!deepEquals(a[i], b[i])) return false;
    }
    return true;
  }
  if (objectClass == 'String' || objectClass == 'Number' ||
      objectClass == 'Boolean' || objectClass == 'Date') {
    if (a.valueOf() !== b.valueOf()) return false;
  }
  return deepObjectEquals(a, b);
}

/**
99
 * Throws an exception containing the user_message (if any) and the values.
100
 */
101
function fail(expected, found, user_message = '') {
102
  // TODO(cira): Replace String with PrettyPrint for objects and arrays.
103 104
  var message = 'Failure' + (user_message ? ' (' + user_message + ')' : '') +
      ': expected <' + String(expected) + '>, found <' + String(found) + '>.';
105 106 107 108 109 110 111
  throw new Error(message);
}


/**
 * Throws if two variables have different types or values.
 */
112
function assertEquals(expected, found, user_message = '') {
113
  if (!deepEquals(expected, found)) {
114
    fail(expected, found, user_message);
115 116 117 118 119 120 121
  }
}


/**
 * Throws if value is false.
 */
122 123
function assertTrue(value, user_message = '') {
  assertEquals(true, value, user_message);
124 125 126 127 128 129
}


/**
 * Throws if value is true.
 */
130 131
function assertFalse(value, user_message = '') {
  assertEquals(false, value, user_message);
132 133 134
}


135 136 137 138 139 140 141 142 143 144
/**
 * Throws if value is null.
 */
function assertNotNull(value, user_message = '') {
  if (value === null) {
    fail("not null", value, user_message);
  }
}


145
/**
146
 * Runs code() and asserts that it throws the specified exception.
147 148 149 150 151 152 153 154 155 156 157 158 159
 */
function assertThrows(code, type_opt, cause_opt) {
  try {
    if (typeof code == 'function') {
      code();
    } else {
      eval(code);
    }
  } catch (e) {
    if (typeof type_opt == 'function') {
      assertInstanceof(e, type_opt);
    }
    if (arguments.length >= 3) {
160
      assertEquals(cause_opt, e.message, 'thrown exception type mismatch');
161 162 163 164
    }
    // Success.
    return;
  }
165 166 167
  var expected = arguments.length >= 3 ? cause_opt :
      typeof type_opt == 'function' ? type_opt : 'any exception';
  fail(expected, 'no exception', 'expected thrown exception');
168 169 170 171
}


/**
172
 * Runs code() and asserts that it does now throw any exception.
173
 */
174
function assertDoesNotThrow(code, user_message = '') {
175 176 177 178 179 180 181
  try {
    if (typeof code == 'function') {
      code();
    } else {
      eval(code);
    }
  } catch (e) {
182
    fail("no expection", "exception: " + String(e), user_message);
183 184 185 186 187 188 189 190 191 192
  }
}


/**
 * Throws if obj is not of given type.
 */
function assertInstanceof(obj, type) {
  if (!(obj instanceof type)) {
    var actualTypeName = null;
193
    var actualConstructor = Object.getPrototypeOf(obj).constructor;
194 195 196 197
    if (typeof actualConstructor == "function") {
      actualTypeName = actualConstructor.name || String(actualConstructor);
    }
    throw new Error('Object <' + obj + '> is not an instance of <' +
198 199
                    (type.name || type) + '>' +
                    (actualTypeName ? ' but of < ' + actualTypeName + '>' : ''));
200 201
  }
}
202 203 204 205 206

/**
 * Split a BCP 47 language tag into locale and extension.
 */
function splitLanguageTag(tag) {
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
  // Search for the beginning of one or more extension tags, each of which
  // contains a singleton tag followed by one or more subtags. The equivalent
  // regexp is: /(-[0-9A-Za-z](-[0-9A-Za-z]{2,8})+)+$/. For example, in
  // 'de-DE-u-co-phonebk' the matched extension tags are '-u-co-phonebk'.
  //
  // The below is a mini-parser that reads backwards from the end of the string.

  function charCode(char) { return char.charCodeAt(0); }
  function isAlphaNumeric(code) {
    return (charCode("0") <= code && code <= charCode("9")) ||
           (charCode("A") <= code && code <= charCode("Z")) ||
           (charCode("a") <= code && code <= charCode("z"));
  }

  const MATCH_SUBTAG = 0;
  const MATCH_SINGLETON_OR_SUBTAG = 1;
  let state = MATCH_SUBTAG;

  const MINIMUM_TAG_LENGTH = 2;
  const MAXIMUM_TAG_LENGTH = 8;
  let currentTagLength = 0;

  // -1 signifies failure, a non-negative integer is the start index of the
  // extension tag.
  let extensionTagStartIndex = -1;

  for (let i = tag.length - 1; i >= 0; i--) {
    const currentCharCode = tag.charCodeAt(i);
    if (currentCharCode == charCode("-")) {
      if (state == MATCH_SINGLETON_OR_SUBTAG && currentTagLength == 1) {
        // Found the singleton tag, the match succeeded.
        // Save the matched index, and reset the state. After this point, we
        // definitely have a match, but we may still find another extension tag
        // sequence.
        extensionTagStartIndex = i;
        state = MATCH_SUBTAG;
        currentTagLength = 0;
      } else if (MINIMUM_TAG_LENGTH <= currentTagLength &&
                currentTagLength <= MAXIMUM_TAG_LENGTH) {
        // Found a valid subtag.
        state = MATCH_SINGLETON_OR_SUBTAG;
        currentTagLength = 0;
      } else {
        // Invalid subtag (too short or too long).
        break;
      }
    } else if (isAlphaNumeric(currentCharCode)) {
      // An alphanumeric character is potentially part of a tag.
      currentTagLength++;
    } else {
      // Any other character is invalid.
      break;
    }
  }

  if (extensionTagStartIndex != -1) {
    return { locale: tag.substring(0, extensionTagStartIndex),
             extension: tag.substring(extensionTagStartIndex) };
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
  }

  return { locale: tag, extension: '' };
}


/**
 * Throw if |parent| is not a more general language tag of |child|, nor |child|
 * itself, per BCP 47 rules.
 */
function assertLanguageTag(child, parent) {
  var childSplit = splitLanguageTag(child);
  var parentSplit = splitLanguageTag(parent);

  // Do not compare extensions at this moment, as %GetDefaultICULocale()
  // doesn't always output something we support.
  if (childSplit.locale !== parentSplit.locale &&
      !childSplit.locale.startsWith(parentSplit.locale + '-')) {
    fail(child, parent, 'language tag comparison');
  }
}