| OLD | NEW |
| (Empty) |
| 1 /* global self */ | |
| 2 | |
| 3 // testharness.js has the higher priority. | |
| 4 var TESTHARNESS = true; | |
| 5 var JSTEST = false; | |
| 6 | |
| 7 (function () { | |
| 8 // Selected properies from testharness.js | |
| 9 var testharnessProperties = [ | |
| 10 'test', 'async_test', 'promise_test', 'promise_rejects', | |
| 11 'generate_tests', 'setup', 'done', 'assert_true', 'assert_false' | |
| 12 ]; | |
| 13 | |
| 14 // Selected properties from js-test.js | |
| 15 var jsTestProperties = [ | |
| 16 'isJsTest', 'testPassed', 'testFailed', 'gc', 'finishJSTest' | |
| 17 ]; | |
| 18 | |
| 19 // Check if testharness.js is properly loaded and set up a flag for it. | |
| 20 for (var name in testharnessProperties) { | |
| 21 if (!self.hasOwnProperty(testharnessProperties[name])) { | |
| 22 TESTHARNESS = false; | |
| 23 break; | |
| 24 } | |
| 25 } | |
| 26 | |
| 27 // Immediately return here because testharness.js is ready. | |
| 28 if (TESTHARNESS) | |
| 29 return; | |
| 30 | |
| 31 // Because testharness.js is not loaded, let us assume that js-test.js might | |
| 32 // be in use. Check if js-test.js is properly loaded and set up a flag for | |
| 33 // it. | |
| 34 JSTEST = true; | |
| 35 for (var name in jsTestProperties) { | |
| 36 if (!self.hasOwnProperty(jsTestProperties[name])) { | |
| 37 JSTEST = false; | |
| 38 break; | |
| 39 } | |
| 40 } | |
| 41 | |
| 42 // If both are not loaded at all, throw here. | |
| 43 if (!JSTEST) | |
| 44 throw new Error('Cannot proceed. No test infrastructure is loaded.'); | |
| 45 })(); | |
| 46 | |
| 47 | |
| 48 // |Audit| is a task runner for web audio test. It makes asynchronous web audio | |
| 49 // testing simple and manageable. | |
| 50 // | |
| 51 // EXAMPLE: | |
| 52 // | |
| 53 // var audit = Audit.createTaskRunner(); | |
| 54 // // Define test routine. Make sure to call done() when reached at the end. | |
| 55 // audit.defineTask('foo', function (done) { | |
| 56 // var context = new AudioContext(); | |
| 57 // // do things | |
| 58 // context.oncomplete = function () { | |
| 59 // // verification here | |
| 60 // done(); | |
| 61 // }; | |
| 62 // }); | |
| 63 // | |
| 64 // audit.defineTask('bar', function (done) { | |
| 65 // // your code here | |
| 66 // done(); | |
| 67 // }); | |
| 68 // | |
| 69 // // Queue tasks by readable task names. | |
| 70 // audit.runTasks('foo', 'bar'); | |
| 71 // | |
| 72 // // Alternatively, if you want to run all of the defined tasks in the order
in which they were | |
| 73 // // defined, use no arguments: | |
| 74 // audit.runTasks(); | |
| 75 var Audit = (function () { | |
| 76 | |
| 77 'use strict'; | |
| 78 | |
| 79 function Tasks() { | |
| 80 this.tasks = {}; | |
| 81 this.queue = []; | |
| 82 this.currentTask = 0; | |
| 83 } | |
| 84 | |
| 85 // This is to prime the task runner for the testharness.js async operation. | |
| 86 Tasks.prototype._initialize = function () { | |
| 87 if (TESTHARNESS) { | |
| 88 setup(new Function(), { | |
| 89 explicit_done: true | |
| 90 }); | |
| 91 } | |
| 92 }; | |
| 93 | |
| 94 // Finalize the task runner by notifying testharness and testRunner that | |
| 95 // all the task is completed. | |
| 96 Tasks.prototype._finalize = function () { | |
| 97 if (TESTHARNESS) { | |
| 98 // From testharness.js | |
| 99 done(); | |
| 100 } | |
| 101 }; | |
| 102 | |
| 103 Tasks.prototype.defineTask = function (taskName, taskFunc) { | |
| 104 // Check if there is a task defined with the same name. If found, do | |
| 105 // not add the task to the roster. | |
| 106 if (this.tasks.hasOwnProperty(taskName)) { | |
| 107 debug('>> Audit.defineTask:: Duplicate task definition. ("' + taskNa
me + '")'); | |
| 108 return; | |
| 109 } | |
| 110 | |
| 111 this.tasks[taskName] = taskFunc; | |
| 112 | |
| 113 // Push the task name in the order of definition. | |
| 114 this.queue.push(taskName); | |
| 115 }; | |
| 116 | |
| 117 // If arguments are given, only run the requested tasks. Check for any | |
| 118 // undefined or duplicate task in the requested task arguments. If there | |
| 119 // is no argument, run all the defined tasks. | |
| 120 Tasks.prototype.runTasks = function () { | |
| 121 | |
| 122 this._initialize(); | |
| 123 | |
| 124 if (arguments.length > 0) { | |
| 125 | |
| 126 // Reset task queue and refill it with the with the given arguments, | |
| 127 // in argument order. | |
| 128 this.queue = []; | |
| 129 | |
| 130 for (var i = 0; i < arguments.length; i++) { | |
| 131 if (!this.tasks.hasOwnProperty(arguments[i])) | |
| 132 debug('>> Audit.runTasks:: Ignoring undefined task. ("' + ar
guments[i] + '")'); | |
| 133 else if (this.queue.indexOf(arguments[i]) > -1) | |
| 134 debug('>> Audit.runTasks:: Ignoring duplicate task request.
("' + arguments[i] + '")'); | |
| 135 else | |
| 136 this.queue.push(arguments[i]); | |
| 137 } | |
| 138 } | |
| 139 | |
| 140 if (this.queue.length === 0) { | |
| 141 debug('>> Audit.runTasks:: No task to run.'); | |
| 142 return; | |
| 143 } | |
| 144 | |
| 145 // taskDone() callback from each task. Increase the task index and call | |
| 146 // the next task. Note that explicit signaling by taskDone() in each | |
| 147 // task is needed because some of tests run asynchronously. | |
| 148 var taskDone = function () { | |
| 149 if (this.currentTask !== this.queue.length - 1) { | |
| 150 ++this.currentTask; | |
| 151 // debug('>> Audit.runTasks: ' + this.queue[this.currentTask]); | |
| 152 this.tasks[this.queue[this.currentTask]](taskDone); | |
| 153 } else { | |
| 154 this._finalize(); | |
| 155 } | |
| 156 return; | |
| 157 }.bind(this); | |
| 158 | |
| 159 // Start the first task. | |
| 160 // debug('>> Audit.runTasks: ' + this.queue[this.currentTask]); | |
| 161 this.tasks[this.queue[this.currentTask]](taskDone); | |
| 162 }; | |
| 163 | |
| 164 return { | |
| 165 createTaskRunner: function () { | |
| 166 return new Tasks(); | |
| 167 } | |
| 168 }; | |
| 169 | |
| 170 })(); | |
| 171 | |
| 172 | |
| 173 // |Should| JS layout test utility. | |
| 174 // Dependency: ../resources/js-test.js | |
| 175 var Should = (function () { | |
| 176 | |
| 177 'use strict'; | |
| 178 | |
| 179 // ShouldModel internal class. For the exposed (factory) method, it is the | |
| 180 // return value of this closure. | |
| 181 function ShouldModel(desc, target, opts) { | |
| 182 this.desc = desc; | |
| 183 this.target = target; | |
| 184 | |
| 185 // Check if the target contains any NaN value. | |
| 186 this._checkNaN(this.target, 'ACTUAL'); | |
| 187 | |
| 188 // |_testPassed| and |_testFailed| set this appropriately. | |
| 189 this._success = false; | |
| 190 | |
| 191 // If the number of errors is greater than this, the rest of error | |
| 192 // messages are suppressed. the value is fairly arbitrary, but shouldn't | |
| 193 // be too small or too large. | |
| 194 this.NUM_ERRORS_LOG = opts.numberOfErrorLog; | |
| 195 | |
| 196 // If the number of array elements is greater than this, the rest of | |
| 197 // elements will be omitted. | |
| 198 this.NUM_ARRAY_LOG = opts.numberOfArrayLog; | |
| 199 | |
| 200 // If true, verbose output for the failure case is printed, for methods
where this makes | |
| 201 // sense. | |
| 202 this.verbose = !opts.brief; | |
| 203 | |
| 204 // If set, this is the precision with which numbers will be printed. | |
| 205 this.PRINT_PRECISION = opts.precision; | |
| 206 } | |
| 207 | |
| 208 // Internal methods starting with a underscore. | |
| 209 ShouldModel.prototype._testPassed = function (msg, addNewline) { | |
| 210 this._success = true; | |
| 211 var newLine = addNewline ? '\n' : ''; | |
| 212 if (TESTHARNESS) { | |
| 213 // Using testharness.js | |
| 214 test(function () { | |
| 215 assert_true(true); | |
| 216 }, this.desc + ' ' + msg + '.' + newLine); | |
| 217 } else { | |
| 218 // Using js-test.js | |
| 219 testPassed(this.desc + ' ' + msg + '.' + newLine); | |
| 220 } | |
| 221 }; | |
| 222 | |
| 223 ShouldModel.prototype._testFailed = function (msg, addNewline) { | |
| 224 this._success = false; | |
| 225 var that = this; | |
| 226 var newLine = addNewline ? '\n' : ''; | |
| 227 if (TESTHARNESS) { | |
| 228 test(function () { | |
| 229 assert_true(false, that.desc + ' ' + msg + '.' + newLine); | |
| 230 }, this.desc); | |
| 231 } else { | |
| 232 testFailed(this.desc + ' ' + msg + '.' + newLine); | |
| 233 } | |
| 234 }; | |
| 235 | |
| 236 ShouldModel.prototype._isArray = function (arg) { | |
| 237 return arg instanceof Array || arg instanceof Float32Array || arg instance
of Uint8Array || | |
| 238 arg instanceof Uint16Array || arg instanceof Uint32Array || arg instance
of Int8Array || | |
| 239 arg instanceof Int16Array || arg instanceof Int32Array || arg instanceof
Uint8ClampedArray || | |
| 240 arg instanceof Float64Array; | |
| 241 }; | |
| 242 | |
| 243 ShouldModel.prototype._assert = function (expression, reason, value) { | |
| 244 if (expression) | |
| 245 return; | |
| 246 | |
| 247 var failureMessage = 'Assertion failed: ' + reason + ' ' + this.desc +'.
'; | |
| 248 if (arguments.length >= 3) | |
| 249 failureMessage += ": " + value; | |
| 250 | |
| 251 if (TESTHARNESS) { | |
| 252 test(function () { | |
| 253 assert_true(false, reason + ' (' + value + ')'); | |
| 254 }, this.desc) | |
| 255 } else { | |
| 256 testFailed(failureMessage); | |
| 257 } | |
| 258 | |
| 259 throw failureMessage; | |
| 260 }; | |
| 261 | |
| 262 // Check the expected value if it is a NaN (Number) or has NaN(s) in | |
| 263 // its content (Array or Float32Array). Returns a string depends on the | |
| 264 // result of check. | |
| 265 ShouldModel.prototype._checkNaN = function (value, label) { | |
| 266 var failureMessage = 'NaN found in ' + label + ' while testing "' + | |
| 267 this.desc + '"'; | |
| 268 | |
| 269 // Checking a single variable first. | |
| 270 if (Number.isNaN(value)) { | |
| 271 if (TESTHARNESS) { | |
| 272 test(function () { | |
| 273 assert_true(false, failureMessage); | |
| 274 }, this.desc) | |
| 275 } else { | |
| 276 testFailed(failureMessage); | |
| 277 } | |
| 278 | |
| 279 throw failureMessage; | |
| 280 } | |
| 281 | |
| 282 // If the value is not a NaN nor array, we can assume it is safe. | |
| 283 if (!this._isArray(value)) | |
| 284 return; | |
| 285 | |
| 286 // Otherwise, check the array array. | |
| 287 var indices = []; | |
| 288 for (var i = 0; i < value.length; i++) { | |
| 289 if (Number.isNaN(value[i])) | |
| 290 indices.push(i); | |
| 291 } | |
| 292 | |
| 293 if (indices.length === 0) | |
| 294 return; | |
| 295 | |
| 296 var failureDetail = ' (' + indices.length + ' instances total)\n'; | |
| 297 for (var n = 0; n < indices.length; n++) { | |
| 298 failureDetail += ' >> [' + indices[n] + '] = NaN\n'; | |
| 299 if (n >= this.NUM_ERRORS_LOG) { | |
| 300 failureDetail += ' and ' + (indices.length - n) + | |
| 301 ' more NaNs...'; | |
| 302 break; | |
| 303 } | |
| 304 } | |
| 305 | |
| 306 if (TESTHARNESS) { | |
| 307 test(function () { | |
| 308 assert_true(false, failureMessage + failureDetail); | |
| 309 }, this.desc) | |
| 310 } else { | |
| 311 testFailed(failureMessage + failureDetail); | |
| 312 } | |
| 313 | |
| 314 throw failureMessage; | |
| 315 }; | |
| 316 | |
| 317 // Check if |target| exists. | |
| 318 // | |
| 319 // Example: | |
| 320 // Should('Object', {}).exist(); | |
| 321 // Result: | |
| 322 // "PASS Object exists." | |
| 323 ShouldModel.prototype.exist = function () { | |
| 324 if (this.target !== null && this.target !== undefined) { | |
| 325 this._testPassed('exists'); | |
| 326 } else { | |
| 327 this._testFailed('does not exist'); | |
| 328 } | |
| 329 | |
| 330 return this._success; | |
| 331 }; | |
| 332 | |
| 333 // Check if |target| is equal to |value|. | |
| 334 // | |
| 335 // Example: | |
| 336 // Should('Zero', 0).beEqualTo(0); | |
| 337 // Result: | |
| 338 // "PASS Zero is equal to 0." | |
| 339 ShouldModel.prototype.beEqualTo = function (value) { | |
| 340 if (value != null) { | |
| 341 var type = typeof value; | |
| 342 this._assert(type === 'number' || type === 'string' || type === 'boo
lean', | |
| 343 'value should be number, string, or boolean for', value
); | |
| 344 } | |
| 345 | |
| 346 this._checkNaN(value, 'EXPECTED'); | |
| 347 | |
| 348 var outputValue = value; | |
| 349 if (type === 'string') | |
| 350 outputValue = '"' + outputValue + '"'; | |
| 351 if (this.target === value) { | |
| 352 var outputValue = (type === 'string') ? '"' + value + '"' : value; | |
| 353 this._testPassed('is equal to ' + outputValue); | |
| 354 } else { | |
| 355 var targetValue = this.target; | |
| 356 if (typeof this.target === 'string') | |
| 357 targetValue = '"' + targetValue + '"'; | |
| 358 this._testFailed('was ' + targetValue + ' instead of ' + outputValue
); | |
| 359 } | |
| 360 return this._success; | |
| 361 }; | |
| 362 | |
| 363 // Check if |target| is not equal to |value|. | |
| 364 // | |
| 365 // Example: | |
| 366 // Should('One', one).notBeEqualTo(0); | |
| 367 // Result: | |
| 368 // "PASS One is not equal to 0." | |
| 369 ShouldModel.prototype.notBeEqualTo = function (value) { | |
| 370 var type = typeof value; | |
| 371 this._assert(type === 'number' || type === 'string' || type === "boolean
", | |
| 372 'value should be number, string, or boolean for', value); | |
| 373 | |
| 374 this._checkNaN(value, 'EXPECTED'); | |
| 375 | |
| 376 if (this.target === value) | |
| 377 this._testFailed('should not be equal to ' + value); | |
| 378 else | |
| 379 this._testPassed('is not equal to ' + value); | |
| 380 return this._success; | |
| 381 }; | |
| 382 | |
| 383 // Check if |target| is greater than or equal to |value|. | |
| 384 // | |
| 385 // Example: | |
| 386 // Should("SNR", snr).beGreaterThanOrEqualTo(100); | |
| 387 // Result: | |
| 388 // "PASS SNR exceeds 100" | |
| 389 // "FAIL SNR (n) is not greater than or equal to 100" | |
| 390 ShouldModel.prototype.beGreaterThanOrEqualTo = function (value) { | |
| 391 var type = typeof value; | |
| 392 this._assert(type === 'number' || type === 'string', | |
| 393 'value should be number or string for', value); | |
| 394 | |
| 395 this._checkNaN(value, 'EXPECTED'); | |
| 396 | |
| 397 var prefix = '(' + this.target + ') '; | |
| 398 | |
| 399 if (this.target >= value) { | |
| 400 if (!this.verbose) | |
| 401 prefix = ''; | |
| 402 this._testPassed(prefix + "is greater than or equal to " + value); | |
| 403 } else { | |
| 404 this._testFailed(prefix + "is not greater than or equal to " + value
); | |
| 405 } | |
| 406 return this._success; | |
| 407 } | |
| 408 | |
| 409 // Check if |target| is greater than |value|. | |
| 410 // | |
| 411 // Example: | |
| 412 // Should("SNR", snr).beGreaterThan(100); | |
| 413 // Result: | |
| 414 // "PASS SNR is greater than 100" | |
| 415 // "FAIL SNR (n) is not greater than 100" | |
| 416 ShouldModel.prototype.beGreaterThan = function (value) { | |
| 417 var type = typeof value; | |
| 418 this._assert(type === 'number' || type === 'string', | |
| 419 'value should be number or string for', value); | |
| 420 | |
| 421 this._checkNaN(value, 'EXPECTED'); | |
| 422 | |
| 423 if (this.target > value) | |
| 424 this._testPassed("is greater than " + value); | |
| 425 else | |
| 426 this._testFailed("(" + this.target + ") is not greater than " + valu
e); | |
| 427 return this._success; | |
| 428 } | |
| 429 | |
| 430 // Check if |target| is lest than or equal to |value|. | |
| 431 // | |
| 432 // Example: | |
| 433 // maxError = 1e-6; | |
| 434 // Should("max error", maxError).beLessThanOrEqualTo(1e-5); | |
| 435 // Should("max error", maxError).beLessThanOrEqualTo(-1); | |
| 436 // Result: | |
| 437 // "PASS max error is less than or equal to 1e-5" | |
| 438 // "FAIL max error (1e-6) is not less than or equal to -1" | |
| 439 ShouldModel.prototype.beLessThanOrEqualTo = function (value) { | |
| 440 var type = typeof value; | |
| 441 this._assert(type === 'number', 'value should be number or string for',
value); | |
| 442 | |
| 443 this._checkNaN(value, 'EXPECTED'); | |
| 444 | |
| 445 var prefix = '(' + this.target + ') '; | |
| 446 | |
| 447 if (this.target <= value) { | |
| 448 if (!this.verbose) | |
| 449 prefix = ''; | |
| 450 this._testPassed(prefix + "is less than or equal to " + value); | |
| 451 } else { | |
| 452 this._testFailed(prefix + "is not less than or equal to " + value); | |
| 453 } | |
| 454 return this._success; | |
| 455 } | |
| 456 | |
| 457 // Check if |target| is close to |value| using the given relative error |thr
eshold|. |value| | |
| 458 // should not be zero, but no check is made for that. The |target| value is
printed to | |
| 459 // precision |precision|, with |precision| defaulting to 7. | |
| 460 // | |
| 461 // If |value| is 0, however, |threshold| is treated as an absolute threshold
. | |
| 462 // | |
| 463 // Example: | |
| 464 // Should("One", 1.001).beCloseTo(1, .1); | |
| 465 // Should("One", 2).beCloseTo(1, .1); | |
| 466 // Result: | |
| 467 // "PASS One is 1 within a relative error of 0.1." | |
| 468 // "FAIL One is not 1 within a relative error of 0.1: 2" | |
| 469 ShouldModel.prototype.beCloseTo = function (value, errorThreshold) { | |
| 470 var type = typeof value; | |
| 471 this._assert(type === 'number', 'value should be number for', value); | |
| 472 | |
| 473 this._checkNaN(value, 'EXPECTED'); | |
| 474 | |
| 475 if (value) { | |
| 476 var relativeError = Math.abs(this.target - value) / Math.abs(value); | |
| 477 if (relativeError <= errorThreshold) { | |
| 478 this._testPassed("is " + value.toPrecision(this.PRINT_PRECISION)
+ | |
| 479 " within a relative error of " + errorThreshold); | |
| 480 } else { | |
| 481 // Include actual relative error so the failed test case can be
updated with the actual | |
| 482 // relative error, if appropriate. | |
| 483 this._testFailed("is not " + value.toPrecision(this.PRINT_PRECIS
ION) + | |
| 484 " within a relative error of " + errorThreshold + | |
| 485 ": " + this.target + " with relative error " + relativeError | |
| 486 ); | |
| 487 } | |
| 488 } else { | |
| 489 var absoluteError = Math.abs(this.target - value); | |
| 490 if (absoluteError <= errorThreshold) { | |
| 491 this._testPassed("is " + value.toPrecision(this.PRINT_PRECISION)
+ | |
| 492 " within an absolute error of " + errorThreshold); | |
| 493 } else { | |
| 494 // Include actual absolute error so the failed test case can be
updated with the | |
| 495 // actual error, if appropriate. | |
| 496 this._testFailed("is not " + value.toPrecision(this.PRINT_PRECIS
ION) + | |
| 497 " within an absolute error of " + errorThreshold + | |
| 498 ": " + this.target + " with absolute error " + absoluteError | |
| 499 ); | |
| 500 } | |
| 501 } | |
| 502 return this._success; | |
| 503 } | |
| 504 | |
| 505 // Check if |func| throws an exception with a certain |errorType| correctly. | |
| 506 // |errorType| is optional. | |
| 507 // | |
| 508 // Example: | |
| 509 // Should('A bad code', function () { var a = b; }).throw(); | |
| 510 // Result: | |
| 511 // "PASS A bad code threw an exception." | |
| 512 // Example: | |
| 513 // Should('var c = d', function () { var c = d; }).throw('ReferenceError'); | |
| 514 // "PASS var c = d threw ReferenceError." | |
| 515 ShouldModel.prototype.throw = function (errorType) { | |
| 516 if (typeof this.target !== 'function') { | |
| 517 console.log('target is not a function. test halted.'); | |
| 518 return; | |
| 519 } | |
| 520 | |
| 521 try { | |
| 522 this.target(); | |
| 523 this._testFailed('did not throw an exception'); | |
| 524 } catch (error) { | |
| 525 if (errorType === undefined) | |
| 526 this._testPassed('threw an exception of type ' + error.name); | |
| 527 else if (error.name === errorType) | |
| 528 this._testPassed('threw ' + errorType + ': ' + error.message); | |
| 529 else if (self.hasOwnProperty(errorType) && error instanceof self[err
orType]) | |
| 530 this._testPassed('threw ' + errorType + ': ' + error.message); | |
| 531 else | |
| 532 this._testFailed('threw ' + error.name + ' instead of ' + errorT
ype); | |
| 533 } | |
| 534 return this._success; | |
| 535 }; | |
| 536 | |
| 537 // Check if |func| does not throw an exception. | |
| 538 // | |
| 539 // Example: | |
| 540 // Should('var foo = "bar"', function () { var foo = 'bar'; }).notThrow(); | |
| 541 // Result: | |
| 542 // "PASS var foo = "bar" did not throw an exception." | |
| 543 ShouldModel.prototype.notThrow = function () { | |
| 544 try { | |
| 545 this.target(); | |
| 546 this._testPassed('did not throw an exception'); | |
| 547 } catch (error) { | |
| 548 this._testFailed('threw ' + error.name + ': ' + error.message); | |
| 549 } | |
| 550 return this._success; | |
| 551 }; | |
| 552 | |
| 553 // Check if |target| array is filled with constant values. | |
| 554 // | |
| 555 // Example: | |
| 556 // Should('[2, 2, 2]', [2, 2, 2]).beConstantValueOf(2); | |
| 557 // Result: | |
| 558 // "PASS [2, 2, 2] has constant values of 2." | |
| 559 ShouldModel.prototype.beConstantValueOf = function (value) { | |
| 560 this._checkNaN(value, 'EXPECTED'); | |
| 561 | |
| 562 var mismatches = {}; | |
| 563 for (var i = 0; i < this.target.length; i++) { | |
| 564 if (this.target[i] !== value) | |
| 565 mismatches[i] = this.target[i]; | |
| 566 } | |
| 567 | |
| 568 var numberOfmismatches = Object.keys(mismatches).length; | |
| 569 | |
| 570 if (numberOfmismatches === 0) { | |
| 571 this._testPassed('contains only the constant ' + value); | |
| 572 } else { | |
| 573 var counter = 0; | |
| 574 var failureMessage = 'contains ' + numberOfmismatches + | |
| 575 ' values that are NOT equal to ' + value + ':'; | |
| 576 for (var index in mismatches) { | |
| 577 failureMessage += '\n[' + index + '] : ' + mismatches[index]; | |
| 578 if (++counter >= this.NUM_ERRORS_LOG) { | |
| 579 failureMessage += '\nand ' + (numberOfmismatches - counter)
+ | |
| 580 ' more differences...'; | |
| 581 break; | |
| 582 } | |
| 583 } | |
| 584 this._testFailed(failureMessage); | |
| 585 } | |
| 586 return this._success; | |
| 587 }; | |
| 588 | |
| 589 // Check if |target| array is identical to |expected| array element-wise. | |
| 590 // | |
| 591 // Example: | |
| 592 // Should('[1, 2, 3]', [1, 2, 3]).beEqualToArray([1, 2, 3]); | |
| 593 // Result: | |
| 594 // "PASS [1, 2, 3] is identical to the array [1,2,3]." | |
| 595 ShouldModel.prototype.beEqualToArray = function (array) { | |
| 596 this._assert(this._isArray(array) && this.target.length === array.length
, | |
| 597 'Invalid array or the length does not match.', array); | |
| 598 | |
| 599 this._checkNaN(array, 'EXPECTED'); | |
| 600 | |
| 601 var mismatches = {}; | |
| 602 for (var i = 0; i < this.target.length; i++) { | |
| 603 if (this.target[i] !== array[i]) | |
| 604 mismatches[i] = this.target[i]; | |
| 605 } | |
| 606 | |
| 607 var numberOfmismatches = Object.keys(mismatches).length; | |
| 608 var arrSlice = array.slice(0, this.NUM_ARRAY_LOG); | |
| 609 var arrStr = arrSlice[0].toPrecision(this.PRINT_PRECISION); | |
| 610 for (var k = 1; k < arrSlice.length; ++k) | |
| 611 arrStr += ',' + arrSlice[k].toPrecision(this.PRINT_PRECISION); | |
| 612 if (array.length > this.NUM_ARRAY_LOG) | |
| 613 arrStr += ',...'; | |
| 614 | |
| 615 if (numberOfmismatches === 0) { | |
| 616 this._testPassed('is identical to the array [' + arrStr + ']'); | |
| 617 } else { | |
| 618 var counter = 0; | |
| 619 var failureMessage = 'is not equal to the array [' + arrStr + ']'; | |
| 620 if (this.verbose) | |
| 621 failureMessage += '\nindex\tActual\t\tExpected'; | |
| 622 for (var index in mismatches) { | |
| 623 failureMessage += '\n[' + index + '] : ' + mismatches[index]; | |
| 624 if (this.verbose) | |
| 625 failureMessage += '\t' + array[index]; | |
| 626 if (++counter >= this.NUM_ERRORS_LOG) { | |
| 627 failureMessage += '\nand ' + (numberOfmismatches - counter)
+ | |
| 628 ' more differences...'; | |
| 629 break; | |
| 630 } | |
| 631 } | |
| 632 | |
| 633 this._testFailed(failureMessage); | |
| 634 } | |
| 635 return this._success; | |
| 636 }; | |
| 637 | |
| 638 // Check if |target| array is close to |expected| array element-wise within | |
| 639 // an certain error bound given by |absoluteThresholdOrOptions|. | |
| 640 // | |
| 641 // The error criterion is: | |
| 642 // | |
| 643 // Math.abs(target[k] - expected[k]) < Math.max(abserr, relerr * Math.abs(
expected)) | |
| 644 // | |
| 645 // If |absoluteThresholdOrOptions| is a number, t, then abserr = t and reler
r = 0. That is the | |
| 646 // max difference is bounded by t. | |
| 647 // | |
| 648 // If |absoluteThresholdOrOptions| is a property bag, then abserr is the val
ue of the | |
| 649 // absoluteThreshold property and relerr is the value of the relativeThresho
ld property. If | |
| 650 // nothing is given, then abserr = relerr = 0. If abserr = 0, then the erro
r criterion is a | |
| 651 // relative error. A non-zero abserr value produces a mix intended to handl
e the case where the | |
| 652 // expected value is 0, allowing the target value to differ by abserr from t
he expected. | |
| 653 // | |
| 654 // Example: | |
| 655 // Should('My array', [0.11, 0.19]).beCloseToArray([0.1, 0.2], 0.02); | |
| 656 // Result: | |
| 657 // "PASS My array equals [0.1,0.2] within an element-wise tolerance of 0.02.
" | |
| 658 ShouldModel.prototype.beCloseToArray = function (expected, absoluteThreshold
OrOptions) { | |
| 659 // For the comparison, the target length must be bigger than the expecte
d. | |
| 660 this._assert(this.target.length >= expected.length, | |
| 661 'The target array length must be longer than ' + expected.length + | |
| 662 ' but got ' + this.target.length + '.'); | |
| 663 | |
| 664 this._checkNaN(expected, 'EXPECTED'); | |
| 665 | |
| 666 var absoluteErrorThreshold = 0; | |
| 667 var relativeErrorThreshold = 0; | |
| 668 | |
| 669 // A collection of all of the values that satisfy the error criterion.
This holds the | |
| 670 // absolute difference between the target element and the expected eleme
nt. | |
| 671 var mismatches = {}; | |
| 672 | |
| 673 // Keep track of the max absolute error found | |
| 674 var maxAbsError = -Infinity; | |
| 675 var maxAbsErrorIndex = -1; | |
| 676 // Keep trac of the max relative error found, ignoring cases where the r
elative error is | |
| 677 // Infinity because the expected value is 0. | |
| 678 var maxRelError = -Infinity; | |
| 679 var maxRelErrorIndex = -1; | |
| 680 | |
| 681 // A number or string for printing out the actual thresholds used for th
e error criterion. | |
| 682 var maxAllowedError; | |
| 683 | |
| 684 // Set up the thresholds based on |absoluteThresholdOrOptions|. | |
| 685 if (typeof(absoluteThresholdOrOptions) === 'number') { | |
| 686 absoluteErrorThreshold = absoluteThresholdOrOptions; | |
| 687 maxAllowedError = absoluteErrorThreshold; | |
| 688 } else { | |
| 689 var opts = absoluteThresholdOrOptions; | |
| 690 if (opts.hasOwnProperty('absoluteThreshold')) | |
| 691 absoluteErrorThreshold = opts.absoluteThreshold; | |
| 692 if (opts.hasOwnProperty('relativeThreshold')) | |
| 693 relativeErrorThreshold = opts.relativeThreshold; | |
| 694 maxAllowedError = '{absoluteThreshold: ' + absoluteErrorThreshold | |
| 695 + ', relativeThreshold: ' + relativeErrorThreshold | |
| 696 + '}'; | |
| 697 } | |
| 698 | |
| 699 for (var i = 0; i < expected.length; i++) { | |
| 700 var diff = Math.abs(this.target[i] - expected[i]); | |
| 701 if (diff > Math.max(absoluteErrorThreshold, relativeErrorThreshold *
Math.abs(expected[i]))) { | |
| 702 mismatches[i] = diff; | |
| 703 // Keep track of the location of the absolute max difference. | |
| 704 if (diff > maxAbsError) { | |
| 705 maxAbsErrorIndex = i; | |
| 706 maxAbsError = diff; | |
| 707 } | |
| 708 // Keep track of the location of the max relative error, ignorin
g cases where the | |
| 709 // relative error is NaN. | |
| 710 var relError = diff / Math.abs(expected[i]); | |
| 711 if (!isNaN(relError) && relError > maxRelError) { | |
| 712 maxRelErrorIndex = i; | |
| 713 maxRelError = relError; | |
| 714 } | |
| 715 } | |
| 716 } | |
| 717 | |
| 718 var numberOfmismatches = Object.keys(mismatches).length; | |
| 719 var arrSlice = expected.slice(0, Math.min(expected.length, this.NUM_ARRA
Y_LOG)); | |
| 720 var arrStr; | |
| 721 | |
| 722 arrStr = arrSlice[0].toPrecision(this.PRINT_PRECISION); | |
| 723 for (var k = 1; k < arrSlice.length; ++k) | |
| 724 arrStr += ',' + arrSlice[k].toPrecision(this.PRINT_PRECISION); | |
| 725 | |
| 726 if (expected.length > this.NUM_ARRAY_LOG) | |
| 727 arrStr += ',...'; | |
| 728 if (numberOfmismatches === 0) { | |
| 729 this._testPassed('equals [' + arrStr + | |
| 730 '] with an element-wise tolerance of ' + maxAllowedError); | |
| 731 } else { | |
| 732 var counter = 0; | |
| 733 var failureMessage = 'does not equal [' + arrStr + | |
| 734 '] with an element-wise tolerance of ' + maxAllowedError; | |
| 735 | |
| 736 // Print a nice header for the table to follow. | |
| 737 if (this.verbose) | |
| 738 failureMessage += "\nIndex Actual Expected
Diff Relative"; | |
| 739 else | |
| 740 failureMessage += "\nDifference between expected and actual:"; | |
| 741 | |
| 742 for (var index in mismatches) { | |
| 743 failureMessage += '\n[' + index + ']: '; | |
| 744 if (this.verbose) { | |
| 745 // When verbose, print out actual, expected, absolute error,
and relative error. | |
| 746 // TODO: print these out in nice columns to make it easier t
o read. | |
| 747 var relError = Math.abs(this.target[index] - expected[index]
) / Math.abs(expected[index]); | |
| 748 failureMessage += this.target[index].toExponential(16) + '
' | |
| 749 + expected[index].toExponential(16) + ' ' | |
| 750 + mismatches[index].toExponential(16) + ' ' | |
| 751 + relError.toExponential(16) + ' ' | |
| 752 + Math.max(absoluteErrorThreshold, | |
| 753 relativeErrorThreshold * Math.abs(expecte
d[index])); | |
| 754 } else { | |
| 755 // Otherwise, just the print the absolute error. | |
| 756 failureMessage += mismatches[index]; | |
| 757 } | |
| 758 if (++counter >= this.NUM_ERRORS_LOG) { | |
| 759 failureMessage += '\nand ' + (numberOfmismatches - counter)
+ | |
| 760 ' more differences, with max absolute error'; | |
| 761 if (this.verbose) { | |
| 762 // When verbose, print out the location of both the max
absolute error and | |
| 763 // the max relative error so we can adjust thresholds ap
propriately in the | |
| 764 // test. | |
| 765 var relError = Math.abs(this.target[maxAbsErrorIndex] -
expected[maxAbsErrorIndex]) | |
| 766 / Math.abs(expected[maxAbsErrorIndex]); | |
| 767 failureMessage += ' at index ' + maxAbsErrorIndex + ':'; | |
| 768 failureMessage += '\n[' + maxAbsErrorIndex + ']: '; | |
| 769 failureMessage += this.target[maxAbsErrorIndex].toExpone
ntial(16) + ' ' | |
| 770 + expected[maxAbsErrorIndex].toExponential(16) +
' ' | |
| 771 + mismatches[maxAbsErrorIndex].toExponential(16)
+ ' ' | |
| 772 + relError.toExponential(16) + ' ' | |
| 773 + Math.max(absoluteErrorThreshold, | |
| 774 relativeErrorThreshold * Math.abs(expected[m
axAbsErrorIndex])); | |
| 775 failureMessage += '\nand max relative error'; | |
| 776 failureMessage += ' at index ' + maxRelErrorIndex + ':'; | |
| 777 failureMessage += '\n[' + maxRelErrorIndex + ']: '; | |
| 778 failureMessage += this.target[maxRelErrorIndex].toExpone
ntial(16) + ' ' | |
| 779 + expected[maxRelErrorIndex].toExponential(16) +
' ' | |
| 780 + mismatches[maxRelErrorIndex].toExponential(16)
+ ' ' | |
| 781 + maxRelError.toExponential(16) + ' ' | |
| 782 + Math.max(absoluteErrorThreshold, | |
| 783 relativeErrorThreshold * Math.abs(expected[m
axRelErrorIndex])); | |
| 784 } else { | |
| 785 // Not verbose, so just print out the max absolute error | |
| 786 failureMessage += ' of ' + maxAbsError + ' at index ' +
maxAbsErrorIndex; | |
| 787 } | |
| 788 break; | |
| 789 } | |
| 790 } | |
| 791 | |
| 792 this._testFailed(failureMessage); | |
| 793 } | |
| 794 return this._success; | |
| 795 }; | |
| 796 | |
| 797 // Check if |target| array contains a set of values in a certain order. | |
| 798 // | |
| 799 // Example: | |
| 800 // Should('My random array', [1, 1, 3, 3, 2]).containValues([1, 3, 2]); | |
| 801 // Result: | |
| 802 // "PASS My random array contains all the expected values in the correct | |
| 803 // order: [1,3,2]." | |
| 804 ShouldModel.prototype.containValues = function (expected) { | |
| 805 this._checkNaN(expected, 'EXPECTED'); | |
| 806 | |
| 807 var indexExpected = 0, indexActual = 0; | |
| 808 while (indexExpected < expected.length && indexActual < this.target.leng
th) { | |
| 809 if (expected[indexExpected] === this.target[indexActual]) | |
| 810 indexActual++; | |
| 811 else | |
| 812 indexExpected++; | |
| 813 } | |
| 814 | |
| 815 if (indexExpected < expected.length-1 || indexActual < this.target.lengt
h-1) { | |
| 816 this._testFailed('contains an unexpected value ' + this.target[index
Actual] + | |
| 817 ' at index ' + indexActual); | |
| 818 } else { | |
| 819 this._testPassed('contains all the expected values in the correct or
der: [' + | |
| 820 expected + ']'); | |
| 821 } | |
| 822 return this._success; | |
| 823 }; | |
| 824 | |
| 825 // Check if |target| array does not have any glitches. Note that |threshold| | |
| 826 // is not optional and is to define the desired threshold value. | |
| 827 // | |
| 828 // Example: | |
| 829 // Should('Channel #0', chanL).notGlitch(0.0005); | |
| 830 // Result: | |
| 831 // "PASS Channel #0 has no glitch above the threshold of 0.0005." | |
| 832 ShouldModel.prototype.notGlitch = function (threshold) { | |
| 833 this._checkNaN(threshold, 'EXPECTED'); | |
| 834 | |
| 835 for (var i = 1; i < this.target.length; i++) { | |
| 836 var diff = Math.abs(this.target[i-1] - this.target[i]); | |
| 837 if (diff >= threshold) { | |
| 838 this._testFailed('has a glitch at index ' + i + ' of size ' + di
ff); | |
| 839 return this._success; | |
| 840 } | |
| 841 } | |
| 842 this._testPassed('has no glitch above the threshold of ' + threshold); | |
| 843 return this._success; | |
| 844 }; | |
| 845 | |
| 846 // Check if the target promise is resolved correctly. | |
| 847 // | |
| 848 // Example: | |
| 849 // Should('My promise', promise).beResolved().then(nextStuff); | |
| 850 // Result: | |
| 851 // "PASS My promise resolved correctly." | |
| 852 // "FAIL My promise rejected incorrectly (with _ERROR_)." | |
| 853 ShouldModel.prototype.beResolved = function () { | |
| 854 return this.target.then(function () { | |
| 855 this._testPassed('resolved correctly'); | |
| 856 }.bind(this), function (err) { | |
| 857 this._testFailed('rejected incorrectly (with ' + err + ')'); | |
| 858 }.bind(this)); | |
| 859 }; | |
| 860 | |
| 861 // Check if the target promise is rejected correctly. | |
| 862 // | |
| 863 // Example: | |
| 864 // Should('My promise', promise).beRejected().then(nextStuff); | |
| 865 // Result: | |
| 866 // "PASS My promise rejected correctly (with _ERROR_)." | |
| 867 // "FAIL My promise resolved incorrectly." | |
| 868 ShouldModel.prototype.beRejected = function () { | |
| 869 return this.target.then(function () { | |
| 870 this._testFailed('resolved incorrectly'); | |
| 871 }.bind(this), function (err) { | |
| 872 this._testPassed('rejected correctly (with ' + err + ')'); | |
| 873 }.bind(this)); | |
| 874 }; | |
| 875 | |
| 876 // A summary message | |
| 877 // | |
| 878 // Example: | |
| 879 // Should("Summary1", true).summarize("passed1", "failed1"); | |
| 880 // Should("Summary2", false).summarize("passed2", "failed2"); | |
| 881 // Result: | |
| 882 // "PASS Summary1: passed1." | |
| 883 // "FAIL Summary2: failed2." | |
| 884 ShouldModel.prototype.summarize = function (pass, fail) { | |
| 885 // It's really nice to have blank lines after the summary, but | |
| 886 // testharness thinks the whole testsuite fails if we do that. | |
| 887 if (this.target) | |
| 888 this._testPassed(pass, false); | |
| 889 else | |
| 890 this._testFailed(fail, false); | |
| 891 return this._success; | |
| 892 } | |
| 893 | |
| 894 // Should() method. | |
| 895 // | |
| 896 // |desc| is the description of the task or check and |target| is a value | |
| 897 // needs to be checked or a task to be performed. |opt| contains options for | |
| 898 // printing out log messages: options are |opt.numberOfErrorLog| and | |
| 899 // |opts.numberOfArrayLog|. | |
| 900 return function (desc, target, opts) { | |
| 901 var _opts = { | |
| 902 numberOfErrorLog: 8, | |
| 903 numberOfArrayLog: 16, | |
| 904 verbose: true | |
| 905 }; | |
| 906 | |
| 907 if (opts instanceof Object) { | |
| 908 if (opts.hasOwnProperty('numberOfErrorLog')) | |
| 909 _opts.numberOfErrorLog = opts.numberOfErrorLog; | |
| 910 if (opts.hasOwnProperty('numberOfArrayLog')) | |
| 911 _opts.numberOfArrayLog = opts.numberOfArrayLog; | |
| 912 if (opts.hasOwnProperty('brief')) | |
| 913 _opts.brief = opts.brief; | |
| 914 if (opts.hasOwnProperty('precision')) | |
| 915 _opts.precision = opts.precision; | |
| 916 } | |
| 917 | |
| 918 return new ShouldModel(desc, target, _opts); | |
| 919 }; | |
| 920 | |
| 921 })(); | |
| OLD | NEW |