| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 |
| 6 /** |
| 7 * @fileOverview WebAudio layout test utility library. Built around W3C's |
| 8 * testharness.js. Includes asynchronous test task manager, |
| 9 * assertion utilities. |
| 10 * @dependency testharness.js |
| 11 * @seealso webaudio/unit-tests/audit.html for actual examples. |
| 12 */ |
| 13 |
| 14 |
| 15 (function () { |
| 16 |
| 17 'use strict'; |
| 18 |
| 19 // Selected properties from testharness.js |
| 20 let testharnessProperties = [ |
| 21 'test', 'async_test', 'promise_test', 'promise_rejects', |
| 22 'generate_tests', 'setup', 'done', 'assert_true', 'assert_false' |
| 23 ]; |
| 24 |
| 25 // Check if testharness.js is properly loaded. Throw otherwise. |
| 26 for (let name in testharnessProperties) { |
| 27 if (!self.hasOwnProperty(testharnessProperties[name])) { |
| 28 throw new Error('Cannot proceed. testharness.js is not loaded.'); |
| 29 break; |
| 30 } |
| 31 } |
| 32 })(); |
| 33 |
| 34 |
| 35 /** |
| 36 * @class Audit |
| 37 * @description A WebAudio layout test task manager. |
| 38 * @example |
| 39 * let audit = Audit.createTaskRunner(); |
| 40 * audit.define('first-task', function (task, should) { |
| 41 * task.describe('the first task'); |
| 42 * should(someValue).beEqualTo(someValue); |
| 43 * task.done(); |
| 44 * }); |
| 45 * audit.run(); |
| 46 */ |
| 47 window.Audit = (function () { |
| 48 |
| 49 'use strict'; |
| 50 |
| 51 function _logPassed (message) { |
| 52 test(function (arg) { |
| 53 assert_true(true); |
| 54 }, message); |
| 55 } |
| 56 |
| 57 function _logFailed (message, detail) { |
| 58 test(function () { |
| 59 assert_true(false, detail); |
| 60 }, message); |
| 61 } |
| 62 |
| 63 function _throwException (message) { |
| 64 throw new Error(message); |
| 65 } |
| 66 |
| 67 // Generate a descriptive string from a target value in various types. |
| 68 function _generateDescription (target, options) { |
| 69 let targetString; |
| 70 |
| 71 switch (typeof target) { |
| 72 case 'object': |
| 73 // Handle Arrays. |
| 74 if (target instanceof Array || target instanceof Float32Array || |
| 75 target instanceof Float64Array) { |
| 76 let arrayElements = target.length < options.numberOfArrayElements |
| 77 ? String(target) |
| 78 : String(target.slice(0, options.numberOfArrayElements)) + '...'; |
| 79 targetString = '[' + arrayElements + ']'; |
| 80 } else { |
| 81 targetString = '' + String(targetString).split(/[\s\]]/)[1]; |
| 82 } |
| 83 break; |
| 84 default: |
| 85 targetString = String(target); |
| 86 break; |
| 87 } |
| 88 |
| 89 return targetString; |
| 90 } |
| 91 |
| 92 |
| 93 /** |
| 94 * @class Should |
| 95 * @description Assertion subtask for the Audit task. |
| 96 * @param {Task} parentTask Associated Task object. |
| 97 * @param {Any} actual Target value to be tested. |
| 98 * @param {String} actualDescription String description of the test target. |
| 99 */ |
| 100 class Should { |
| 101 |
| 102 constructor (parentTask, actual, actualDescription) { |
| 103 this._task = parentTask; |
| 104 |
| 105 this._actual = actual; |
| 106 this._actualDescription = (actualDescription || null); |
| 107 this._expected = null; |
| 108 this._expectedDescription = null; |
| 109 |
| 110 this._result = true; |
| 111 this._detail = ''; |
| 112 |
| 113 /** |
| 114 * @param {Number} numberOfErrors Number of errors to be printed. |
| 115 * @param {Number} numberOfArrayElements Number of array elements to be |
| 116 * printed in the test log. |
| 117 * @param {Boolean} verbose Verbose output from the assertion. |
| 118 */ |
| 119 this._options = { |
| 120 numberOfErrors: 4, |
| 121 numberOfArrayElements: 16, |
| 122 verbose: false |
| 123 }; |
| 124 } |
| 125 |
| 126 _processArguments (args) { |
| 127 if (args.length === 0) |
| 128 return; |
| 129 |
| 130 if (args.length > 0) |
| 131 this._expected = args[0]; |
| 132 |
| 133 if (typeof args[1] === 'string') { |
| 134 // case 1: (expected, description, options) |
| 135 this._expectedDescription = args[1]; |
| 136 Object.assign(this._options, args[2]); |
| 137 } else if (typeof args[1] === 'object') { |
| 138 // case 2: (expected, options) |
| 139 Object.assign(this._options, args[1]); |
| 140 } |
| 141 } |
| 142 |
| 143 _buildResultText () { |
| 144 if (!this._actualDescription) { |
| 145 this._actualDescription = |
| 146 _generateDescription(this._actual, this._options); |
| 147 } |
| 148 |
| 149 if (!this._expectedDescription) { |
| 150 this._expectedDescription = |
| 151 _generateDescription(this._expected, this._options); |
| 152 } |
| 153 |
| 154 // For the assertion with a single operand. |
| 155 this._detail = this._detail.replace('${actual}', this._actualDescription); |
| 156 |
| 157 // If there is a second operand (i.e. expected value), we have to build |
| 158 // the string for it as well. |
| 159 if (this._expected) { |
| 160 this._detail = this._detail.replace( |
| 161 '${expected}', this._expectedDescription); |
| 162 } |
| 163 |
| 164 // If there is any property in |_options|, replace the property name |
| 165 // with the value. |
| 166 for (let name in this._options) { |
| 167 this._detail = this._detail.replace( |
| 168 '${' + name + '}', |
| 169 _generateDescription(this._options[name])); |
| 170 } |
| 171 } |
| 172 |
| 173 _finalize () { |
| 174 if (this._result) { |
| 175 _logPassed(' ' + this._detail); |
| 176 } else { |
| 177 _logFailed('X ' + this._detail); |
| 178 } |
| 179 |
| 180 // This assertion is finished, so update the parent task accordingly. |
| 181 this._task.update(this); |
| 182 |
| 183 // TODO(hongchan): configurable 'detail' message. |
| 184 } |
| 185 |
| 186 _assert (condition, passDetail, failDetail) { |
| 187 this._result = Boolean(condition); |
| 188 this._detail = this._result ? passDetail : failDetail; |
| 189 this._buildResultText(); |
| 190 return this._finalize(); |
| 191 } |
| 192 |
| 193 get result () { |
| 194 return this._result; |
| 195 } |
| 196 |
| 197 get detail () { |
| 198 return this._detail; |
| 199 } |
| 200 |
| 201 /** |
| 202 * should() assertions. |
| 203 * |
| 204 * @example All the assertions can have 1, 2 or 3 arguments: |
| 205 * should().doAssert(expected); |
| 206 * should().doAssert(expected, options); |
| 207 * should().doAssert(expected, expectedDescription, options); |
| 208 * |
| 209 * @param {Any} expected Expected value of the assertion. |
| 210 * @param {String} expectedDescription Description of expected value. |
| 211 * @param {Object} options Options for assertion. |
| 212 * @param {Number} options.numberOfErrors Number of errors to be printed. |
| 213 * (if applicable) |
| 214 * @param {Number} options.numberOfArrayElements Number of array elements |
| 215 * to be printed. (if |
| 216 * applicable) |
| 217 * @notes Some assertions can have additional options for their specific |
| 218 * testing. |
| 219 */ |
| 220 |
| 221 /** |
| 222 * Check if |actual| exists. |
| 223 * |
| 224 * @example |
| 225 * should({}, 'An empty object').exist(); |
| 226 * @result |
| 227 * "PASS An empty object does exist." |
| 228 */ |
| 229 exist () { |
| 230 return this._assert( |
| 231 this._actual !== null && this._actual !== undefined, |
| 232 '${actual} does exist.', |
| 233 '${actual} does *NOT* exist.'); |
| 234 } |
| 235 |
| 236 /** |
| 237 * Check if |actual| operation wrapped in a function throws an exception |
| 238 * with a expected error type correctly. |expected| is optional. |
| 239 * |
| 240 * @example |
| 241 * should(() => { let a = b; }, 'A bad code').throw(); |
| 242 * should(() => { let c = d; }, 'Assigning d to c.') |
| 243 * .throw('ReferenceError'); |
| 244 * |
| 245 * @result |
| 246 * "PASS A bad code threw an exception of ReferenceError." |
| 247 * "PASS Assigning d to c threw ReferenceError." |
| 248 */ |
| 249 throw () { |
| 250 this._processArguments(arguments); |
| 251 |
| 252 let didThrowCorrectly = false; |
| 253 let passDetail, failDetail; |
| 254 |
| 255 try { |
| 256 // This should throw. |
| 257 this._actual(); |
| 258 // Catch did not happen, so the test is failed. |
| 259 failDetail = '${actual} did *NOT* throw an exception.'; |
| 260 } catch (error) { |
| 261 if (this._expected === undefined) { |
| 262 didThrowCorrectly = true; |
| 263 passDetail = '${actual} threw an exception of ' + error.name + '.'; |
| 264 } else if (error.name === this._expected) { |
| 265 didThrowCorrectly = true; |
| 266 passDetail = '${actual} threw ${expected} : "' + error.message + '".'; |
| 267 } else { |
| 268 didThrowCorrectly = false; |
| 269 failDetail = '${actual} threw "' + error.name |
| 270 + '" instead of ${expected}.'; |
| 271 } |
| 272 } |
| 273 |
| 274 return this._assert(didThrowCorrectly, passDetail, failDetail); |
| 275 } |
| 276 |
| 277 /** |
| 278 * Check if |actual| operation wrapped in a function does not throws an |
| 279 * exception correctly. |
| 280 * |
| 281 * @example |
| 282 * should(() => { let foo = 'bar'; }, 'let foo = "bar"').notThrow(); |
| 283 * |
| 284 * @result |
| 285 * "PASS let foo = "bar" did not throw an exception." |
| 286 */ |
| 287 notThrow () { |
| 288 let didThrowCorrectly = false; |
| 289 let passDetail, failDetail; |
| 290 |
| 291 try { |
| 292 this._actual(); |
| 293 passDetail = '${actual} did not throw an exception.'; |
| 294 } catch (error) { |
| 295 didThrowCorrectly = true; |
| 296 failDetail = '${actual} threw ' + error.name + ': ' |
| 297 + error.message + '.'; |
| 298 } |
| 299 |
| 300 return this._assert(!didThrowCorrectly, passDetail, failDetail); |
| 301 } |
| 302 |
| 303 /** |
| 304 * Check if |actual| promise is resolved correctly. |
| 305 * |
| 306 * @example |
| 307 * should('My promise', promise).beResolve().then(nextStuff); |
| 308 * |
| 309 * @result |
| 310 * "PASS My promise resolved correctly." |
| 311 * "FAIL X My promise rejected *INCORRECTLY* with _ERROR_." |
| 312 */ |
| 313 beResolved () { |
| 314 return this._actual.then(function () { |
| 315 this._assert(true, '${actual} resolved correctly.', null); |
| 316 }.bind(this), function (error) { |
| 317 this._assert(false, null, |
| 318 '${actual} rejected *INCORRECTLY* with ' + error + '.'); |
| 319 }.bind(this)); |
| 320 } |
| 321 |
| 322 /** |
| 323 * Check if |actual| promise is rejected correctly. |
| 324 * |
| 325 * @example |
| 326 * should('My promise', promise).beRejected().then(nextStuff); |
| 327 * |
| 328 * @result |
| 329 * "PASS My promise rejected correctly (with _ERROR_)." |
| 330 * "FAIL X My promise resolved *INCORRECTLY*." |
| 331 */ |
| 332 beRejected () { |
| 333 return this._actual.then(function () { |
| 334 this._assert(false, null, '${actual} resolved *INCORRECTLY*.'); |
| 335 }.bind(this), function (error) { |
| 336 this._assert(true, |
| 337 '${actual} rejected correctly with ' + error + '.', null); |
| 338 }.bind(this)); |
| 339 } |
| 340 |
| 341 /** |
| 342 * Check if |actual| is a boolean true. |
| 343 * |
| 344 * @example |
| 345 * should(3 < 5, '3 < 5').beTrue(); |
| 346 * |
| 347 * @result |
| 348 * "PASS 3 < 5 is true." |
| 349 */ |
| 350 beTrue () { |
| 351 return this._assert( |
| 352 this._actual === true, |
| 353 '${actual} is true.', |
| 354 '${actual} is *NOT* true.'); |
| 355 } |
| 356 |
| 357 /** |
| 358 * Check if |actual| is a boolean false. |
| 359 * |
| 360 * @example |
| 361 * should(3 > 5, '3 > 5').beFalse(); |
| 362 * |
| 363 * @result |
| 364 * "PASS 3 > 5 is false." |
| 365 */ |
| 366 beFalse () { |
| 367 return this._assert( |
| 368 this._actual === false, |
| 369 '${actual} is false.', |
| 370 '${actual} is *NOT* false.'); |
| 371 } |
| 372 |
| 373 /** |
| 374 * Check if |actual| is strictly equal to |expected|. (no type coercion) |
| 375 * |
| 376 * @example |
| 377 * should(1).beEqualTo(1); |
| 378 * |
| 379 * @result |
| 380 * "PASS 1 is equal to 1." |
| 381 */ |
| 382 beEqualTo () { |
| 383 this._processArguments(arguments); |
| 384 return this._assert( |
| 385 this._actual === this._expected, |
| 386 '${actual} is equal to ${expected}.', |
| 387 '${actual} is *NOT* equal to ${expected}.'); |
| 388 } |
| 389 |
| 390 /** |
| 391 * Check if |actual| is not equal to |expected|. |
| 392 * |
| 393 * @example |
| 394 * should(1).notBeEqualTo(2); |
| 395 * |
| 396 * @result |
| 397 * "PASS 1 is *NOT* equal to 2." |
| 398 */ |
| 399 notBeEqualTo () { |
| 400 this._processArguments(arguments); |
| 401 return this._assert( |
| 402 this._actual !== this._expected, |
| 403 '${actual} is not equal to ${expected}.', |
| 404 '${actual} should *NOT* be equal to ${expected}.'); |
| 405 } |
| 406 |
| 407 /** |
| 408 * Check if |actual| is greater than |expected|. |
| 409 * |
| 410 * @example |
| 411 * should(2).beGreaterThanOrEqualTo(2); |
| 412 * |
| 413 * @result |
| 414 * "PASS 2 is greater than or equal to 2." |
| 415 */ |
| 416 beGreaterThan () { |
| 417 this._processArguments(arguments); |
| 418 return this._assert( |
| 419 this._actual > this._expected, |
| 420 '${actual} is greater than ${expected}.', |
| 421 '${actual} is *NOT* greater than ${expected}.' |
| 422 ); |
| 423 } |
| 424 |
| 425 /** |
| 426 * Check if |actual| is greater than or equal to |expected|. |
| 427 * |
| 428 * @example |
| 429 * should(2).beGreaterThan(1); |
| 430 * |
| 431 * @result |
| 432 * "PASS 2 is greater than 1." |
| 433 */ |
| 434 beGreaterThanOrEqualTo () { |
| 435 this._processArguments(arguments); |
| 436 return this._assert( |
| 437 this._actual >= this._expected, |
| 438 '${actual} is greater than or equal to ${expected}.', |
| 439 '${actual} is *NOT* greater than or equal to ${expected}.' |
| 440 ); |
| 441 } |
| 442 |
| 443 /** |
| 444 * Check if |actual| is less than |expected|. |
| 445 * |
| 446 * @example |
| 447 * should(1).beLessThan(2); |
| 448 * |
| 449 * @result |
| 450 * "PASS 1 is less than 2." |
| 451 */ |
| 452 beLessThan () { |
| 453 this._processArguments(arguments); |
| 454 return this._assert( |
| 455 this._actual < this._expected, |
| 456 '${actual} is less than ${expected}.', |
| 457 '${actual} is *NOT* less than ${expected}.' |
| 458 ); |
| 459 } |
| 460 |
| 461 /** |
| 462 * Check if |actual| is less than or equal to |expected|. |
| 463 * |
| 464 * @example |
| 465 * should(1).beLessThanOrEqualTo(1); |
| 466 * |
| 467 * @result |
| 468 * "PASS 1 is less than or equal to 1." |
| 469 */ |
| 470 beLessThanOrEqualTo () { |
| 471 this._processArguments(arguments); |
| 472 return this._assert( |
| 473 this._actual <= this._expected, |
| 474 '${actual} is less than or equal to ${expected}.', |
| 475 '${actual} is *NOT* less than or equal to ${expected}.' |
| 476 ); |
| 477 } |
| 478 |
| 479 /** |
| 480 * Check if |actual| array is filled with a constant |expected| value. |
| 481 * |
| 482 * @example |
| 483 * should([1, 1, 1]).beConstantValueOf(1); |
| 484 * |
| 485 * @result |
| 486 * "PASS [1,1,1] contains only the constant 1." |
| 487 */ |
| 488 beConstantValueOf () { |
| 489 this._processArguments(arguments); |
| 490 |
| 491 let passed = true; |
| 492 let passDetail, failDetail; |
| 493 let errors = {}; |
| 494 |
| 495 for (let index in this._actual) { |
| 496 if (this._actual[index] !== this._expected) |
| 497 errors[index] = this._actual[index]; |
| 498 } |
| 499 |
| 500 let numberOfErrors = Object.keys(errors).length; |
| 501 passed = numberOfErrors === 0; |
| 502 |
| 503 if (passed) { |
| 504 passDetail = '${actual} contains only the constant ${expected}.'; |
| 505 } else { |
| 506 let counter = 0; |
| 507 failDetail = '${actual} contains ' + numberOfErrors |
| 508 + ' values that are *NOT* equal to ${expected}: '; |
| 509 failDetail += '\n\tIndex\tActual'; |
| 510 for (let errorIndex in errors) { |
| 511 failDetail += '\n\t[' + errorIndex + ']' |
| 512 + '\t' + errors[errorIndex]; |
| 513 if (++counter >= this._options.numberOfErrors) { |
| 514 failDetail += '\n\t...and ' + (numberOfErrors - counter) |
| 515 + ' more errors.'; |
| 516 break; |
| 517 } |
| 518 } |
| 519 } |
| 520 |
| 521 return this._assert(passed, passDetail, failDetail); |
| 522 } |
| 523 |
| 524 /** |
| 525 * Check if |actual| array is identical to |expected| array element-wise. |
| 526 * |
| 527 * @example |
| 528 * should([1, 2, 3]).beEqualToArray([1, 2, 3]); |
| 529 * |
| 530 * @result |
| 531 * "[1,2,3] is identical to the array [1,2,3]." |
| 532 */ |
| 533 beEqualToArray () { |
| 534 this._processArguments(arguments); |
| 535 |
| 536 let passed = true; |
| 537 let passDetail, failDetail; |
| 538 let errorIndices = []; |
| 539 |
| 540 if (this._actual.length !== this._expected.length) { |
| 541 passed = false; |
| 542 failDetail = 'The array length does *NOT* match.'; |
| 543 return this._assert(passed, passDetail, failDetail); |
| 544 } |
| 545 |
| 546 for (let index in this._actual) { |
| 547 if (this._actual[index] !== this._expected[index]) |
| 548 errorIndices.push(index); |
| 549 } |
| 550 |
| 551 passed = errorIndices.length === 0; |
| 552 |
| 553 if (passed) { |
| 554 passDetail = '${actual} is identical to the array ${expected}.'; |
| 555 } else { |
| 556 let counter = 0; |
| 557 failDetail = '${actual} contains ' + errorIndices.length |
| 558 + ' values that are *NOT* equal to ${expected}: '; |
| 559 failDetail += '\n\tIndex\tActual\t\t\tExpected'; |
| 560 for (let index of errorIndices) { |
| 561 failDetail += '\n\t[' + index + ']' |
| 562 + '\t' + this._actual[index].toExponential(16) |
| 563 + '\t' + this._expected[index].toExponential(16); |
| 564 if (++counter >= this._options.numberOfErrors) { |
| 565 failDetail += '\n\t...and ' + (numberOfErrors - counter) |
| 566 + ' more errors.'; |
| 567 break; |
| 568 } |
| 569 } |
| 570 } |
| 571 |
| 572 return this._assert(passed, passDetail, failDetail); |
| 573 } |
| 574 |
| 575 /** |
| 576 * Check if |actual| array contains only the values in |expected| in the |
| 577 * order of values in |expected|. |
| 578 * |
| 579 * @example |
| 580 * Should([1, 1, 3, 3, 2], 'My random array').containValues([1, 3, 2]); |
| 581 * |
| 582 * @result |
| 583 * "PASS [1,1,3,3,2] contains all the expected values in the correct |
| 584 * order: [1,3,2]. |
| 585 */ |
| 586 containValues () { |
| 587 this._processArguments(arguments); |
| 588 |
| 589 let passed = true; |
| 590 let indexActual = 0, indexExpected = 0; |
| 591 |
| 592 while (indexActual < this._actual.length |
| 593 && indexExpected < this._expected.length) { |
| 594 if (this._actual[indexActual] === this._expected[indexExpected]) { |
| 595 indexActual++; |
| 596 } else { |
| 597 indexExpected++; |
| 598 } |
| 599 } |
| 600 |
| 601 passed = !(indexActual < this._actual.length - 1 |
| 602 || indexExpected < this._expected.length - 1); |
| 603 |
| 604 return this._assert( |
| 605 passed, |
| 606 '${actual} contains all the expected values in the correct order: ' |
| 607 + '${expected}.', |
| 608 '${actual} contains an unexpected value of ' |
| 609 + this._actual[indexActual] + ' at index ' + indexActual + '.'); |
| 610 } |
| 611 |
| 612 /** |
| 613 * Check if |actual| array does not have any glitches. Note that |threshold| |
| 614 * is not optional and is to define the desired threshold value. |
| 615 * |
| 616 * @example |
| 617 * should([0.5, 0.5, 0.55, 0.5, 0.45, 0.5]).notGlitch(0.06); |
| 618 * |
| 619 * @result |
| 620 * "PASS [0.5,0.5,0.55,0.5,0.45,0.5] has no glitch above the threshold |
| 621 * of 0.06." |
| 622 * |
| 623 */ |
| 624 notGlitch () { |
| 625 this._processArguments(arguments); |
| 626 |
| 627 let passed = true; |
| 628 let passDetail, failDetail; |
| 629 |
| 630 for (let index in this._actual) { |
| 631 let diff = Math.abs(this._actual[index - 1] - this._actual[index]); |
| 632 if (diff >= this._expected) { |
| 633 passed = false; |
| 634 failDetail = '${actual} has a glitch at index ' + index + ' of size ' |
| 635 + diff + '.'; |
| 636 } |
| 637 } |
| 638 |
| 639 passDetail = |
| 640 '${actual} has no glitch above the threshold of ${expected}.'; |
| 641 |
| 642 return this._assert(passed, passDetail, failDetail); |
| 643 } |
| 644 |
| 645 /** |
| 646 * Check if |actual| is close to |expected| using the given relative error |
| 647 * |threshold|. |
| 648 * |
| 649 * @example |
| 650 * should(2.3).beCloseTo(2, 0.3); |
| 651 * |
| 652 * @result |
| 653 * "PASS 2.3 is 2 within an error of 0.3." |
| 654 * |
| 655 * @param {Number} options.threshold Threshold value for the comparison. |
| 656 */ |
| 657 beCloseTo () { |
| 658 this._processArguments(arguments); |
| 659 |
| 660 let absExpected = this._expected ? Math.abs(this._expected) : 1; |
| 661 let error = Math.abs(this._actual - this._expected) / absExpected; |
| 662 |
| 663 return this._assert( |
| 664 error < this._options.threshold, |
| 665 '${actual} is ${expected} within an error of ${threshold}', |
| 666 '${actual} is not ${expected} within a error of ${threshold}: ' + |
| 667 '${actual} with error of ${threshold}.'); |
| 668 } |
| 669 |
| 670 /** |
| 671 * Check if |target| array is close to |expected| array element-wise within |
| 672 * a certain error bound given by the |options|. |
| 673 * |
| 674 * The error criterion is: |
| 675 * abs(actual[k] - expected[k]) < max(absErr, relErr * abs(expected)) |
| 676 * |
| 677 * If nothing is given for |options|, then absErr = relErr = 0. If |
| 678 * absErr = 0, then the error criterion is a relative error. A non-zero |
| 679 * absErr value produces a mix intended to handle the case where the |
| 680 * expected value is 0, allowing the target value to differ by absErr from |
| 681 * the expected. |
| 682 * |
| 683 * @param {Number} options.absoluteThreshold Absolute threshold. |
| 684 * @param {Number} options.relativeThreshold Relative threshold. |
| 685 */ |
| 686 beCloseToArray () { |
| 687 this._processArguments(arguments); |
| 688 |
| 689 let passed = true; |
| 690 let passDetail, failDetail; |
| 691 |
| 692 // Parsing options. |
| 693 let absErrorThreshold = (this._options.absoluteThreshold || 0); |
| 694 let relErrorThreshold = (this._options.relativeThreshold || 0); |
| 695 |
| 696 // A collection of all of the values that satisfy the error criterion. |
| 697 // This holds the absolute difference between the target element and the |
| 698 // expected element. |
| 699 let errors = {}; |
| 700 |
| 701 // Keep track of the max absolute error found. |
| 702 let maxAbsError = -Infinity, maxAbsErrorIndex = -1; |
| 703 |
| 704 // Keep track of the max relative error found, ignoring cases where the |
| 705 // relative error is Infinity because the expected value is 0. |
| 706 let maxRelError = -Infinity, maxRelErrorIndex = -1; |
| 707 |
| 708 for (let index in this._expected) { |
| 709 let diff = Math.abs(this._actual[index] - this._expected[index]); |
| 710 let absExpected = Math.abs(this._expected[index]); |
| 711 let relError = diff / absExpected; |
| 712 |
| 713 if (diff > Math.max(absErrorThreshold, |
| 714 relErrorThreshold * absExpected)) { |
| 715 |
| 716 if (diff > maxAbsError) { |
| 717 maxAbsErrorIndex = index; |
| 718 maxAbsError = diff; |
| 719 } |
| 720 |
| 721 if (!isNaN(relError) && relError > maxRelError) { |
| 722 maxRelErrorIndex = index; |
| 723 maxRelError = relError; |
| 724 } |
| 725 |
| 726 errors[index] = diff; |
| 727 } |
| 728 } |
| 729 |
| 730 let numberOfErrors = Object.keys(errors).length; |
| 731 let maxAllowedErrorDetail = JSON.stringify({ |
| 732 absoluteThreshold: absErrorThreshold, |
| 733 relativeThreshold: relErrorThreshold |
| 734 }); |
| 735 |
| 736 if (numberOfErrors === 0) { |
| 737 // The assertion was successful. |
| 738 passDetail = '${actual} equals ${expected} with an element-wise ' |
| 739 + 'tolerance of ' + maxAllowedErrorDetail + '.'; |
| 740 } else { |
| 741 // Failed. Prepare the detailed failure log. |
| 742 passed = false; |
| 743 failDetail = '${actual} does not equal ${expected} with an ' |
| 744 + 'element-wise tolerance of ' + maxAllowedErrorDetail + '.\n'; |
| 745 |
| 746 // Print out actual, expected, absolute error, and relative error. |
| 747 let counter = 0; |
| 748 failDetail += '\tIndex\tActual\t\t\tExpected\t\tAbsError' |
| 749 + '\t\tRelError\t\tTest threshold'; |
| 750 for (let index in errors) { |
| 751 failDetail += '\n\t[' + index + ']\t' |
| 752 + this._actual[index].toExponential(16) + '\t' |
| 753 + this._expected[index].toExponential(16) + '\t' |
| 754 + errors[index].toExponential(16) + '\t' |
| 755 + (errors[index] / Math.abs(this._expected[index])) |
| 756 .toExponential(16) + '\t' |
| 757 + Math.max(absErrorThreshold, |
| 758 relErrorThreshold * Math.abs(this._expected[index])) |
| 759 .toExponential(16); |
| 760 if (++counter > this._options.numberOfErrors) |
| 761 break; |
| 762 } |
| 763 |
| 764 // Finalize the error log: print out the location of both the maxAbs |
| 765 // error and the maxRel error so we can adjust thresholds appropriately |
| 766 // in the test. |
| 767 failDetail += '\n' |
| 768 + '\tMax AbsError of ' + maxAbsError.toExponential(16) |
| 769 + ' at index of ' + maxAbsErrorIndex + '.\n' |
| 770 + '\tMax RelError of ' + maxRelError.toExponential(16) |
| 771 + ' at index of ' + maxRelErrorIndex + '.'; |
| 772 } |
| 773 |
| 774 return this._assert(passed, passDetail, failDetail); |
| 775 } |
| 776 |
| 777 } |
| 778 |
| 779 |
| 780 // Task Class state enum. |
| 781 const TaskState = { |
| 782 PENDING: 0, |
| 783 STARTED: 1, |
| 784 FINISHED: 2 |
| 785 }; |
| 786 |
| 787 |
| 788 /** |
| 789 * @class Task |
| 790 * @description WebAudio testing task. Managed by TaskRunner. |
| 791 */ |
| 792 class Task { |
| 793 |
| 794 constructor (taskRunner, taskLabel, taskFunction) { |
| 795 this._taskRunner = taskRunner; |
| 796 this._taskFunction = taskFunction; |
| 797 this._label = taskLabel; |
| 798 this._description = ''; |
| 799 this._state = TaskState.PENDING; |
| 800 this._result = true; |
| 801 |
| 802 this._totalAssertions = 0; |
| 803 this._failedAssertions = 0; |
| 804 } |
| 805 |
| 806 // Set the description of this task. This is printed out in the test |
| 807 // result. |
| 808 describe (message) { |
| 809 this._description = message; |
| 810 _logPassed('> [' + this._label + '] ' |
| 811 + this._description); |
| 812 } |
| 813 |
| 814 get state () { |
| 815 return this._state; |
| 816 } |
| 817 |
| 818 get result () { |
| 819 return this._result; |
| 820 } |
| 821 |
| 822 // Start the assertion chain. |
| 823 should (actual, actualDescription) { |
| 824 return new Should(this, actual, actualDescription); |
| 825 } |
| 826 |
| 827 // Run this task. |this| task will be passed into the user-supplied test |
| 828 // task function. |
| 829 run () { |
| 830 this._state = TaskState.STARTED; |
| 831 this._taskFunction( |
| 832 this, |
| 833 this.should.bind(this)); |
| 834 } |
| 835 |
| 836 // Update the task success based on the individual assertion/test inside. |
| 837 update (subTask) { |
| 838 // After one of tests fails within a task, the result is irreversible. |
| 839 if (subTask.result === false) { |
| 840 this._result = false; |
| 841 this._failedAssertions++; |
| 842 } |
| 843 |
| 844 this._totalAssertions++; |
| 845 } |
| 846 |
| 847 // Finish the current task and start the next one if available. |
| 848 done () { |
| 849 this._state = TaskState.FINISHED; |
| 850 |
| 851 let message = '< [' + this._label + '] '; |
| 852 |
| 853 if (this._result) { |
| 854 message += 'All assertion passed. (total ' + this._totalAssertions |
| 855 + ' assertions)'; |
| 856 _logPassed(message); |
| 857 } else { |
| 858 message += this._failedAssertions + ' out of ' + this._totalAssertions |
| 859 + ' assertions were failed.' |
| 860 _logFailed(message); |
| 861 } |
| 862 |
| 863 this._taskRunner._runNextTask(); |
| 864 } |
| 865 |
| 866 isPassed () { |
| 867 return this._state === TaskState.FINISHED && this._result; |
| 868 } |
| 869 |
| 870 toString () { |
| 871 return '"' + this._label + '": ' + this._description; |
| 872 } |
| 873 |
| 874 } |
| 875 |
| 876 |
| 877 /** |
| 878 * @class TaskRunner |
| 879 * @description WebAudio testing task runner. Manages tasks. |
| 880 */ |
| 881 class TaskRunner { |
| 882 |
| 883 constructor () { |
| 884 this._tasks = {}; |
| 885 this._taskSequence = []; |
| 886 this._currentTaskIndex = -1; |
| 887 |
| 888 // Configure testharness.js for the async operation. |
| 889 setup (new Function (), { |
| 890 explicit_done: true |
| 891 }); |
| 892 } |
| 893 |
| 894 _runNextTask () { |
| 895 if (this._currentTaskIndex < this._taskSequence.length) { |
| 896 this._tasks[this._taskSequence[this._currentTaskIndex++]].run(); |
| 897 } else { |
| 898 this._finish(); |
| 899 } |
| 900 } |
| 901 |
| 902 _finish () { |
| 903 let numberOfFailures = 0; |
| 904 for (let taskIndex in this._taskSequence) { |
| 905 let task = this._tasks[this._taskSequence[taskIndex]]; |
| 906 numberOfFailures += task.result ? 0 : 1; |
| 907 } |
| 908 |
| 909 let prefix = '# AUDIT TASK RUNNER FINISHED: '; |
| 910 if (numberOfFailures > 0) { |
| 911 _logFailed(prefix + numberOfFailures + ' out of ' |
| 912 + this._taskSequence.length + ' tasks were failed.'); |
| 913 } else { |
| 914 _logPassed(prefix + this._taskSequence.length |
| 915 + ' tasks ran successfully.'); |
| 916 } |
| 917 |
| 918 // From testharness.js, report back to the test infrastructure that |
| 919 // the task runner completed all the tasks. |
| 920 done(); |
| 921 } |
| 922 |
| 923 define (taskLabel, taskFunction) { |
| 924 if (this._tasks.hasOwnProperty(taskLabel)) { |
| 925 _throwException('Audit.define:: Duplicate task definition.'); |
| 926 return; |
| 927 } |
| 928 |
| 929 this._tasks[taskLabel] = new Task(this, taskLabel, taskFunction); |
| 930 this._taskSequence.push(taskLabel); |
| 931 } |
| 932 |
| 933 // Start running all the tasks scheduled. Multiple task names can be passed |
| 934 // to execute them sequentially. Zero argument will perform all defined |
| 935 // tasks in the order of definition. |
| 936 run () { |
| 937 // Display the beginning of the test suite. |
| 938 _logPassed('# AUDIT TASK RUNNER STARTED.'); |
| 939 |
| 940 // If the argument is specified, override the default task sequence with |
| 941 // the specified one. |
| 942 if (arguments.length > 0) { |
| 943 this._taskSequence = []; |
| 944 for (let i = 0; arguments.length; i++) { |
| 945 let taskLabel = arguments[i]; |
| 946 if (!this._tasks.hasOwnProperty(taskLabel)) { |
| 947 _throwException('Audit.run:: undefined task.'); |
| 948 } else if (this._taskSequence.includes(taskLabel)) { |
| 949 _throwException('Audit.run:: duplicate task request.'); |
| 950 } else { |
| 951 this._taskSequence.push[taskLabel]; |
| 952 } |
| 953 } |
| 954 } |
| 955 |
| 956 if (this._taskSequence.length === 0) { |
| 957 _throwException('Audit.run:: no task to run.'); |
| 958 return; |
| 959 } |
| 960 |
| 961 // Start the first task. |
| 962 this._currentTaskIndex = 0; |
| 963 this._runNextTask(); |
| 964 } |
| 965 |
| 966 } |
| 967 |
| 968 |
| 969 return { |
| 970 |
| 971 /** |
| 972 * Creates an instance of Audit task runner. |
| 973 */ |
| 974 createTaskRunner: function () { |
| 975 return new TaskRunner(); |
| 976 } |
| 977 |
| 978 }; |
| 979 |
| 980 })(); |
| OLD | NEW |