Chromium Code Reviews| Index: third_party/WebKit/LayoutTests/webaudio/resources/audit.js |
| diff --git a/third_party/WebKit/LayoutTests/webaudio/resources/audit.js b/third_party/WebKit/LayoutTests/webaudio/resources/audit.js |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..f637cd46174444a0693b6bfa5323e28e1324cdc9 |
| --- /dev/null |
| +++ b/third_party/WebKit/LayoutTests/webaudio/resources/audit.js |
| @@ -0,0 +1,887 @@ |
| +// Copyright 2016 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +/** |
| + * @fileOverview WebAudio layout test utility library. Built around W3C's |
| + * testharness.js. Includes a test task manager, assertion |
| + * utilities. |
| + * @dependency testharness.js |
| + */ |
| + |
| +(function () { |
| + |
| + 'use strict'; |
| + |
| + // Selected properties from testharness.js |
| + let testharnessProperties = [ |
| + 'test', 'async_test', 'promise_test', 'promise_rejects', |
| + 'generate_tests', 'setup', 'done', 'assert_true', 'assert_false' |
| + ]; |
| + |
| + // Check if testharness.js is properly loaded. Throw otherwise. |
| + for (let name in testharnessProperties) { |
| + if (!self.hasOwnProperty(testharnessProperties[name])) { |
|
Raymond Toy
2016/11/08 21:14:18
From our BaseAudioContext experience, do we want h
hongchan
2016/11/08 22:07:29
|self| is the global scope, so there is no inherit
|
| + throw new Error('Cannot proceed. testharness.js is not loaded.'); |
| + break; |
| + } |
| + } |
| + |
| + // Attach style to body > pre element. |
| + // TODO(hongchan): is this something we want? |
|
Raymond Toy
2016/11/08 21:14:18
Do we really need this? I think we should let tes
hongchan
2016/11/08 22:07:29
For my use case, this significantly improves the r
hongchan
2016/11/29 20:25:01
I decided to remove this.
|
| + let style = document.createElement('style'); |
| + style.textContent = 'pre { font-family: "Roboto Mono", monospace; ' |
| + + 'font-size: 12px; font-weight: 400; text-rendering: optimizeLegibility; ' |
| + + 'line-height: 16px; }'; |
| + document.head.appendChild(style); |
| +})(); |
| + |
| + |
| +/** |
| + * @class Audit |
| + * @description A WebAudio layout test task manager. |
| + * @example |
| + * let audit = Audit.createTaskRunner(); |
| + * audit.define('task-1', function (describe, should, report) { |
| + * describe('the first task'); |
| + * should(someValue).beEqualTo(someValue); |
| + * report('passed!', 'failed.'); |
| + * }); |
| + * audit.run(); |
| + * |
| + * @see webaudio/unit-tests/audit.html for more examples. |
| + */ |
| +var Audit = (function () { |
| + |
| + 'use strict'; |
| + |
| + function _logPassed (message) { |
| + test(function (arg) { |
| + assert_true(true); |
| + }, message); |
| + } |
| + |
| + function _logFailed (message, detail) { |
| + test(function () { |
| + assert_true(false, detail); |
| + }, message); |
| + } |
| + |
| + function _throwException (message) { |
| + throw new Error(message); |
| + } |
| + |
| + // Check the expected value if it is a NaN or has NaN(s) in its content (Array |
| + // or Float32Array). Returns a result with indices of NaN. |
|
Raymond Toy
2016/11/08 21:14:18
Update comment because it returns for an indices.
hongchan
2016/11/08 22:07:29
This method is confusing. I thought it's clever to
hongchan
2016/11/29 20:25:00
This method is removed. It's better to use isNaN()
|
| + function _isOrHasNaN (target) { |
|
Raymond Toy
2016/11/08 21:14:18
isOrHasNan? I misread this at first as missing so
hongchan
2016/11/08 22:07:29
Acknowledged.
|
| + let isOrHasNaN = false; |
| + let detail = null; |
| + |
| + if (Number.isNaN(target)) { |
| + isOrHasNaN = true; |
| + detail = { |
| + type: 'number' |
| + }; |
| + } else if (target instanceof Float32Array || target instanceof Array) { |
|
Raymond Toy
2016/11/08 21:14:18
Should we support Float64Array too?
hongchan
2016/11/29 20:25:00
The method is removed.
|
| + // Collect indices of NaN element in the array. |
| + detail = { |
| + type: 'array', |
| + indicesNaN: [] |
| + }; |
| + for (let index in target) { |
| + if (Number.isNaN(target[index])) |
| + detail.indicesNaN.push(index); |
| + } |
| + |
| + isOrHasNaN = detail.indicesNaN.length > 0; |
| + } else { |
| + // The target is neither number nor array. |
| + detail = { |
| + type: typeof target |
| + }; |
| + } |
| + |
| + return { |
| + isOrHasNaN: isOrHasNaN, |
| + detail: detail |
| + }; |
| + } |
| + |
| + // Generate a descriptive string from a test target value in various types. |
| + // TODO(hongchan): this might not be needed. |
|
Raymond Toy
2016/11/08 21:14:18
You're using this, so it's needed right?
hongchan
2016/11/08 22:07:29
This needs more work. There are some stuffs that c
hongchan
2016/11/29 20:25:00
I scaled down to a single case (object/array), but
|
| + function _generateStringFromTestTarget(target) { |
| + let targetString = new String(target); |
| + |
| + switch (typeof target) { |
| + case 'boolean': |
| + // targetString = '"' + targetString + '"(boolean)'; |
| + break; |
| + case 'number': |
| + // targetString = '"' + targetString + '"(number)'; |
| + break; |
| + case 'string': |
| + // targetString = '"' + targetString + '"(string)'; |
| + break; |
| + case 'object': |
| + // Take care of arrays. |
| + if (target instanceof Array || target instanceof Float32Array) { |
| + targetString = '[' + targetString + ']'; |
| + } |
| + // } else { |
|
Raymond Toy
2016/11/08 21:14:18
This should be removed.
hongchan
2016/11/08 22:07:28
I am still deciding on this. We need some method t
hongchan
2016/11/29 20:25:01
Removed.
|
| + // targetString = '' + targetString.split(/[\s\]]/)[1] + '(object)'; |
| + // } |
| + break; |
| + case 'function': |
| + // let elements = targetString.split(' '); |
|
Raymond Toy
2016/11/08 21:14:18
This should be removed.
hongchan
2016/11/08 22:07:29
Noted. I will sit on this few more days.
hongchan
2016/11/29 20:25:01
Removed.
|
| + // if (elements[1] !== '()') { |
| + // // 1. named function? print function name. |
| + // targetString = '"' + elements[1] + '"'; |
| + // } else { |
| + // // 2. anon function? print function content. |
| + // targetString = '"' + elements.slice(3, elements.length - 1).join(' ') |
| + // + '"'; |
| + // } |
| + break; |
| + case 'undefined': |
| + // targetString = '"' + targetString + '"(undefined)'; |
| + break; |
| + } |
| + |
| + return targetString; |
| + } |
| + |
| + |
| + /** |
| + * @class SubtaskReporter |
| + * @description If detailed report/summary on a task is necessary, use this |
| + * with a assertion subtask. |
|
Raymond Toy
2016/11/08 21:14:18
"a assertion" -> "an assertion"
Not really clear
hongchan
2016/11/08 22:07:29
This one is also a bare bone design of the summary
hongchan
2016/11/29 20:25:01
This class is removed.
|
| + */ |
| + class SubtaskReporter { |
| + |
| + constructor (should) { |
| + this._assertion = should; |
| + } |
| + |
| + configure (options) { } |
| + |
| + report (descActual, descExpected) { } |
| + |
| + } |
| + |
| + |
| + /** |
| + * @class Should |
| + * @description Assertion subtask for the Audit task. |
| + * @param {Task} parentTask Associated Task object. |
| + * @param {Any} actual Target value to be tested. |
| + * @param {String} actualDescription String description of the test target. |
| + */ |
| + class Should { |
| + |
| + constructor (parentTask, actual, actualDescription) { |
| + this._task = parentTask; |
| + |
| + this._actual = actual; |
| + this._expected = null; |
| + this._options = {}; |
| + |
| + this._result = true; |
| + this._actualDescription = (actualDescription || null); |
| + this._detail = ''; |
| + |
| + this._config = { |
| + numberOfErrors: 4, |
| + numberOfArrayElements: 8, |
| + precision: 8, |
| + verbose: false |
| + }; |
| + } |
| + |
| + _buildDetailString () { |
| + let actualDescription = (this._actualDescription || this._actual); |
| + |
| + // For the assertion with a single operand. |
| + this._detail = this._detail.replace( |
| + '${actual}', _generateStringFromTestTarget(actualDescription)); |
| + |
| + // If there is a second operand (i.e. expected value), we have to build |
| + // the string for it as well. |
| + if (this._expected) { |
| + this._detail = this._detail.replace( |
| + '${expected}', _generateStringFromTestTarget(this._expected)); |
| + } |
| + |
| + // If there is any property in |_options|, replace the property name |
| + // with the value. |
| + for (let name in this._options) { |
| + this._detail = this._detail.replace( |
| + '${' + name + '}', |
| + _generateStringFromTestTarget(this._options[name])); |
| + } |
| + } |
| + |
| + _assert (expression, passDetail, failDetail) { |
| + if (!expression) { |
| + this._result = false; |
| + this._detail = failDetail; |
| + } else { |
| + this._detail = passDetail; |
| + } |
| + |
| + this._buildDetailString(); |
| + return this._finalize(); |
| + } |
| + |
| + _finalize () { |
| + if (this._result) { |
| + _logPassed(' ' + this.detail); |
| + } else { |
| + _logFailed(' X ' + this.detail); |
| + } |
| + |
| + this._task.update(this); |
| + return new SubtaskReporter(this); |
| + } |
| + |
| + get result () { |
| + return this._result; |
| + } |
| + |
| + get detail () { |
| + return this._detail; |
| + } |
| + |
| + |
| + // should() assertions. |
| + |
| + |
| + /** |
| + * Check if |actual| exists. |
| + */ |
| + exist () { |
| + return this._assert( |
| + this._actual !== null && this._actual !== undefined, |
| + '${actual} does exist.', |
| + '${actual} does *NOT* exist.'); |
| + } |
| + |
| + /** |
| + * Check if |actual| operation wrapped in a function throws an exception |
| + * with a expected error type correctly. |expected| is optional. |
| + */ |
| + throw (expected) { |
| + this._expected = expected; |
| + let didThrow = false; |
| + let passDetail, failDetail; |
| + |
| + try { |
| + // This should throw. |
| + this._actual(); |
| + // Catch did not happen, so the test is failed. |
| + failDetail = '${actual} did *NOT* throw an exception.'; |
| + } catch (error) { |
| + if (expected === undefined) { |
| + didThrow = true; |
| + passDetail = '${actual} threw an exception of type ' |
| + + error.name + '.'; |
| + } else if (error.name === expected) { |
| + didThrow = true; |
| + passDetail = '${actual} threw ${expected} : "' + error.message + '".'; |
| + } else { |
| + didThrow = false; |
|
Raymond Toy
2016/11/08 21:14:18
Why false? This did throw, right? If it didn't,
hongchan
2016/11/08 22:07:29
I should rename the variable to |passed|. The thi
hongchan
2016/11/29 20:25:00
Done with a different variable name.
|
| + failDetail = '${actual} threw "' + error.name |
| + + '" instead of ${expected}.'; |
| + } |
| + } |
| + |
| + return this._assert(didThrow, passDetail, failDetail); |
| + } |
| + |
| + /** |
| + * Check if |actual| operation wrapped in a function does not throws an |
| + * exception correctly. |
| + */ |
| + notThrow () { |
| + let didThrow = false; |
| + let passDetail, failDetail; |
| + |
| + try { |
| + this._actual(); |
| + passDetail = '${actual} did not throw an exception as expected.'; |
| + } catch (error) { |
| + didThrow = true; |
| + failDetail = '${actual} threw ' + error.name + ': ' |
| + + error.message + '.'; |
| + } |
| + |
| + return this._assert(!didThrow, passDetail, failDetail); |
| + } |
| + |
| + /** |
| + * Check if |actual| promise is resolved correctly. |
| + */ |
| + beResolved () { |
| + return this._actual.then(function () { |
| + this._assert(true, '${actual} resolved correctly.', null); |
| + }.bind(this), function (error) { |
| + this._assert(false, null, |
| + '${actual} rejected incorrectly with ' + error + '.'); |
| + }.bind(this)); |
| + } |
| + |
| + /** |
| + * Check if |actual| promise is rejected correctly. |
| + */ |
| + beRejected () { |
| + return this._actual.then(function () { |
| + this._assert(false, null, '${actual} resolved incorrectly.'); |
| + }.bind(this), function (error) { |
| + this._assert(true, |
| + '${actual} rejected correctly with "' + error + '".', null); |
| + |
| + }.bind(this)); |
| + } |
| + |
| + /** |
| + * Check if |actual| is a boolean true. |
| + */ |
| + beTrue () { |
| + return this._assert( |
| + this._actual === true, |
| + '${actual} is true.', |
| + '${actual} is *NOT* true.'); |
| + } |
| + |
| + /** |
| + * Check if |actual| is a boolean false. |
| + */ |
| + beFalse () { |
| + return this._assert( |
| + this._actual === false, |
| + '${actual} is false.', |
| + '${actual} is *NOT* false.'); |
| + } |
| + |
| + /** |
| + * Check if |actual| is not a NaN or does not have a NaN in it. |
| + */ |
| + notBeNaN () { |
| + let result = _isOrHasNaN(this._actual); |
| + let detail = result.detail; |
| + let passDetail, failDetail; |
| + |
| + switch (detail.type) { |
| + case 'number': |
| + if (result.isOrHasNaN) |
| + failDetail = '${actual} is NaN.'; |
| + else |
| + passDetail = '${actual} is not NaN.'; |
| + break; |
| + case 'array': |
| + if (result.isOrHasNaN) { |
| + failDetail = '${actual} contains ' + detail.indicesNaN.length |
| + + ' NaNs at the index of (' + detail.indicesNaN.toString() + ')'; |
| + } else { |
| + passDetail = '${actual} does not contain NaN.'; |
| + } |
| + break; |
| + default: |
| + passDetail = '${actual} is not NaN. (non-Number or non-Array)'; |
| + break; |
| + } |
| + |
| + return this._assert( |
| + !result.isOrHasNaN, |
| + passDetail, |
| + failDetail |
| + ); |
| + } |
| + |
| + /** |
| + * Check if |actual| is strictly equal to |expected|. (no type coercion) |
| + */ |
| + beEqualTo (expected) { |
| + this._expected = expected; |
| + return this._assert( |
| + this._actual === expected, |
| + '${actual} is equal to ${expected}.', |
| + '${actual} is *NOT* equal to ${expected}.'); |
| + } |
| + |
| + /** |
| + * Check if |actual| is not equal to |expected|. |
| + */ |
| + notBeEqualTo (expected) { |
| + this._expected = expected; |
| + return this._assert( |
| + this._actual !== expected, |
| + '${actual} is not equal to ${expected}.', |
| + '${actual} should *NOT* be equal to ${expected}.'); |
| + } |
| + |
| + /** |
| + * Check if |actual| is greater than or equal to |expected|. |
| + */ |
| + beGreaterThanOrEqualTo (expected) { |
| + this._expected = expected; |
| + return this._assert( |
| + this._actual >= this._expected, |
| + '${actual} is greater than or equal to ${expected}.', |
| + '${actual} is *NOT* greater than or equal to ${expected}.' |
| + ); |
| + } |
| + |
| + /** |
| + * Check if |actual| is greater than |expected|. |
| + */ |
| + beGreaterThan (expected) { |
| + this._expected = expected; |
| + return this._assert( |
| + this._actual > this._expected, |
| + '${actual} is greater than ${expected}.', |
| + '${actual} is *NOT* greater than ${expected}.' |
| + ); |
| + } |
| + |
| + /** |
| + * Check if |actual| is less than or equal to |expected|. |
| + */ |
| + beLessThanOrEqualTo (expected) { |
| + this._expected = expected; |
| + return this._assert( |
| + this._actual <= this._expected, |
| + '${actual} is less than or equal to ${expected}.', |
| + '${actual} is *NOT* less than or equal to ${expected}.' |
| + ); |
| + } |
| + |
| + /** |
| + * Check if |actual| is less than |expected|. |
| + */ |
| + beLessThan (expected) { |
| + this._expected = expected; |
| + return this._assert( |
| + this._actual < this._expected, |
| + '${actual} is less than ${expected}.', |
| + '${actual} is *NOT* less than ${expected}.' |
| + ); |
| + } |
| + |
| + /** |
| + * Check if |actual| array is filled with a constant |expected| value. |
| + */ |
| + beConstantValueOf (expected) { |
| + this._expected = expected; |
| + let passed = true; |
| + let passDetail, failDetail; |
| + let errors = {}; |
| + |
| + for (let index in this._actual) { |
| + if (this._actual[index] !== this._expected) |
| + errors[index] = this._actual[index]; |
| + } |
| + |
| + let numberOfErrors = Object.keys(errors).length; |
| + passed = numberOfErrors === 0; |
| + |
| + if (passed) { |
| + passDetail = '${actual} contains only the constant ${expected}.'; |
| + } else { |
| + let counter = 0; |
| + failDetail = '${actual} contains ' + numberOfErrors |
| + + ' values that are *NOT* equal to ${expected}: '; |
| + for (let errorIndex in errors) { |
| + failDetail += '<' + errorIndex + ':' + errors[errorIndex] + '> '; |
| + if (++counter >= 5) { |
|
Raymond Toy
2016/11/08 21:14:18
Probably want 5 to be settable by the test.
hongchan
2016/11/08 22:07:29
The assertion configuration is another matter. It
|
| + failDetail += ' and ' + (numberOfErrors - counter) |
| + + ' more errors.'; |
| + break; |
| + } |
| + } |
| + } |
| + |
| + return this._assert(passed, passDetail, failDetail); |
| + } |
| + |
| + /** |
| + * Check if |actual| array is identical to |expected| array element-wise. |
| + */ |
| + beEqualToArray (expected) { |
| + this._expected = expected; |
| + let passed = true; |
| + let passDetail, failDetail; |
| + let errorIndices = []; |
| + |
| + if (this._actual.length !== this._expected.length) { |
| + passed = false; |
| + failDetail = 'The array length does *NOT* match.'; |
| + return this._assert(passed, passDetail, failDetail); |
| + } |
| + |
| + for (let index in this._actual) { |
| + if (this._actual[index] !== this._expected[index]) |
| + errorIndices.push(index); |
| + } |
| + |
| + passed = errorIndices.length === 0; |
| + |
| + if (passed) { |
| + passDetail = '${actual} is identical to the array ${expected}.'; |
| + } else { |
| + let counter = 0; |
| + failDetail = '${actual} contains ' + errorIndices.length |
| + + ' values that are *NOT* equal to ${expected}: '; |
| + for (let index of errorIndices) { |
| + failDetail += '<' + index + ':' + this._actual[index] + ' != ' |
| + + this._expected[index] + '> '; |
| + if (++counter >= 5) { |
| + failDetail += ' and ' + (numberOfErrors - counter) |
| + + ' more errors.'; |
| + break; |
| + } |
| + } |
| + } |
| + |
| + return this._assert(passed, passDetail, failDetail); |
| + } |
| + |
| + /** |
| + * Check if |target| array is close to |expected| array element-wise within |
| + * a certain error bound given by the |options|. |
| + * |
| + * @param {Array} expected Expected array. |
| + * @param {Object} Options |
| + * @param {Number} Options.absoluteThreshold Absolute threshold. |
| + * @param {Number} Options.relativeThreshold Relative threshold. |
| + * |
| + * The error criterion is: |
| + * abs(actual[k] - expected[k]) < max(abserr, relerr * abs(expected)) |
| + * |
| + * If nothing is given for |options|, then abserr = relerr = 0. If |
| + * abserr = 0, then the error criterion is a relative error. A non-zero |
| + * abserr value produces a mix intended to handle the case where the |
| + * expected value is 0, allowing the target value to differ by abserr from |
| + * the expected. |
| + */ |
| + beCloseToArray (expected, options) { |
| + this._expected = expected; |
| + this._options = options; |
| + let passed = true; |
| + let passDetail, failDetail; |
| + |
| + // Parsing options. |
| + let absErrorThreshold = (this._options.absoluteThreshold || 0); |
| + let relErrorThreshold = (this._options.relativeThreshold || 0); |
| + |
| + // A collection of all of the values that satisfy the error criterion. |
| + // This holds the absolute difference between the target element and the |
| + // expected element. |
| + let errors = {}; |
| + |
| + // Keep track of the max absolute error found. |
| + let maxAbsError = -Infinity, maxAbsErrorIndex = -1; |
| + |
| + // Keep track of the max relative error found, ignoring cases where the |
| + // relative error is Infinity because the expected value is 0. |
| + let maxRelError = -Infinity, maxRelErrorIndex = -1; |
| + |
| + for (let index in this._expected) { |
| + let diff = Math.abs(this._actual[index] - this._expected[index]); |
| + let absExpected = Math.abs(this._expected[index]); |
| + let relError = diff / absExpected; |
| + |
| + if (diff > Math.max(absErrorThreshold, |
| + relErrorThreshold * absExpected)) { |
| + |
| + if (diff > maxAbsError) { |
| + maxAbsErrorIndex = index; |
| + maxAbsError = diff; |
| + } |
| + |
| + if (!isNaN(relError) && relError > maxRelError) { |
| + maxRelErrorIndex = index; |
| + maxRelError = relError; |
| + } |
| + |
| + errors[index] = diff; |
| + } |
| + } |
| + |
| + let numberOfErrors = Object.keys(errors).length; |
| + let maxAllowedErrorDetail = JSON.stringify({ |
| + absoluteThreshold: absErrorThreshold, |
| + relativeThreshold: relErrorThreshold |
| + }); |
| + |
| + if (numberOfErrors === 0) { |
| + passDetail = '${actual} equals ${expected} with an element-wise ' |
| + + 'tolerance of ' + maxAllowedErrorDetail + '.'; |
| + } else { |
| + passed = false; |
| + failDetail = '${actual} does not equal ${expected} with an ' |
| + + 'element-wise tolerance of ' + maxAllowedErrorDetail + '.'; |
|
Raymond Toy
2016/11/08 21:14:18
This seems a regression from the old Audit that pr
hongchan
2016/11/08 22:07:29
beCloseToArray() was an overloaded beast. What typ
Raymond Toy
2016/11/14 17:44:34
Recall we used to print out a short summary and a
|
| + } |
| + |
| + return this._assert(passed, passDetail, failDetail); |
| + } |
| + |
| + /** |
| + * Check if |actual| array contains a set of |expected| values in a certain |
|
Raymond Toy
2016/11/08 21:14:18
Need to expand on this to explain what "certain or
hongchan
2016/11/08 22:07:29
Perhaps a 'user-defined' order?
Raymond Toy
2016/11/14 17:44:34
Yes, that's much clearer. Or just say the array c
hongchan
2016/11/29 20:25:01
Done.
|
| + * order. |
| + */ |
| + containValues (expected) { |
| + this._expected = expected; |
| + let passed = true; |
| + let indexActual = 0, indexExpected = 0; |
| + |
| + while (indexActual < this._actual.length |
| + && indexExpected < this._expected.length) { |
| + if (this._actual[indexActual] === this._expected[indexExpected]) { |
| + indexActual++; |
| + } else { |
| + indexExpected++; |
| + } |
| + } |
| + |
| + passed = !(indexActual < this._actual.length - 1 |
| + || indexExpected < this._expected.length - 1); |
| + |
| + return this._assert( |
| + passed, |
| + '${actual} contains all the expected values in the correct order: ' |
| + + '${expected}.', |
| + '${actual} contains an unexpected value of ' |
| + + this._actual[indexActual] + ' at index ' + indexActual + '.'); |
| + } |
| + |
| + /** |
| + * Check if |actual| array does not have any glitches. Note that |threshold| |
|
Raymond Toy
2016/11/08 21:14:18
Needs more detailed explanation of what "glitch" m
hongchan
2016/11/08 22:07:29
Nope. The comments are same. I didn't use the exam
Raymond Toy
2016/11/14 17:44:34
Prefer 1 because it's easier to find; I won't be l
hongchan
2016/11/29 20:25:01
Added examples. Your request on the last comment w
|
| + * is not optional and is to define the desired threshold value. |
| + */ |
| + notGlitch (threshold) { |
| + this._expected = threshold; |
| + let passed = true; |
| + let passDetail, failDetail; |
| + |
| + for (let index in this._actual) { |
| + let diff = Math.abs(this._actual[index - 1] - this._actual[index]); |
| + if (diff >= this._expected) { |
| + passed = false; |
| + failDetail = '${actual} has a glitch at index ' + index + ' of size ' |
| + + diff + '.'; |
| + } |
| + } |
| + |
| + passDetail = |
| + '${actual} has no glitch above the threshold of ${expected}.'; |
| + |
| + return this._assert(passed, passDetail, failDetail); |
| + } |
| + |
| + /** |
| + * Check if |actual| is close to |expected| using the given relative error |
| + * |threshold|. |
| + */ |
| + beCloseTo (expected, threshold) { |
| + this._expected = expected; |
| + this._options.threshold = threshold; |
| + |
| + let absExpected = this._expected ? Math.abs(this._expected) : 1; |
| + let error = Math.abs(this._actual - this._expected) / absExpected; |
| + |
| + return this._assert( |
| + error < threshold, |
| + '${actual} is ${expected} within an error of ${threshold}', |
| + '${actual} is not ${expected} within a error of ${threshold}: ' + |
| + '${actual} with error of ${threshold}.'); |
| + } |
| + } |
| + |
| + |
| + /** |
| + * @class Task |
| + * @description WebAudio testing task. Managed by TaskRunner. |
| + */ |
| + class Task { |
| + |
| + constructor (taskRunner, taskLabel, taskFunction) { |
| + this._taskRunner = taskRunner; |
| + this._taskFunction = taskFunction; |
| + this._label = taskLabel; |
| + this._description = ''; |
| + this._state = 'PENDING'; |
| + this._result = true; |
| + |
| + this._totalAssertions = 0; |
| + this._failedAssertions = 0; |
| + } |
| + |
| + // Set the description of this task. This is printed out in the test |
| + // result. |
| + describe (message) { |
| + this._description = message; |
| + _logPassed('>> [' + this._label + '] ' |
| + + this._description); |
| + } |
| + |
| + get state () { |
| + return this._state; |
| + } |
| + |
| + get result () { |
| + return this._result; |
| + } |
| + |
| + // Start the assertion chain. |
| + should (actual, actualDescription) { |
| + return new Should(this, actual, actualDescription); |
| + } |
| + |
| + // Run this task. |this| task will be passed into the user-supplied test |
| + // task function. |
| + run () { |
| + this._state = 'STARTED'; |
| + this._taskFunction( |
| + this, |
| + this.should.bind(this)); |
| + } |
| + |
| + // Update the task success based on the individual assertion/test inside. |
| + update (subTask) { |
| + // After one of tests fails within a task, the result is irreversible. |
| + if (subTask.result === false) { |
| + this._result = false; |
| + this._failedAssertions++; |
| + } |
| + |
| + this._totalAssertions++; |
| + } |
| + |
| + // Finish the current task and start the next one if available. |
| + done () { |
| + this._state = 'FINISHED'; |
| + |
| + let message = '<< [' + this._label + '] '; |
| + |
| + if (this._result) { |
| + message += 'All assertion passed. (total ' + this._totalAssertions |
| + + ' assertions)'; |
| + _logPassed(message); |
| + } else { |
| + message += this._failedAssertions + ' out of ' + this._totalAssertions |
| + + ' assertions were failed.' |
| + _logFailed(message); |
| + } |
| + |
| + this._taskRunner._runNextTask(); |
| + } |
| + |
| + isPassed () { |
| + return this._state === 'FINISHED' && this._result; |
| + } |
| + |
| + toString () { |
| + return '"' + this._label + '": ' + this._description; |
| + } |
| + |
| + } |
| + |
| + |
| + /** |
| + * @class TaskRunner |
| + * @description WebAudio testing task runner. Manages tasks. |
| + */ |
| + class TaskRunner { |
| + |
| + constructor () { |
| + this._tasks = {}; |
| + this._taskSequence = []; |
| + this._currentTaskIndex = -1; |
| + |
| + // Configure testharness.js for the async operation. |
| + setup (new Function (), { |
| + explicit_done: true |
| + }); |
| + } |
| + |
| + define (taskLabel, taskFunction) { |
| + if (this._tasks.hasOwnProperty(taskLabel)) { |
| + _throwException('Audit.define:: Duplicate task definition.'); |
| + return; |
| + } |
| + |
| + this._tasks[taskLabel] = new Task(this, taskLabel, taskFunction); |
| + this._taskSequence.push(taskLabel); |
| + } |
| + |
| + // Start running all the tasks scheduled. |
|
Raymond Toy
2016/11/08 21:14:18
Probably want to describe the optional args to run
hongchan
2016/11/08 22:07:29
It's being properly handled. See the if statement
hongchan
2016/11/29 20:25:01
Oh, I misunderstood your comment. Added comments a
|
| + run () { |
| + // Display the beginning of the test suite. |
| + _logPassed('## AUDIT TASK RUNNER STARTED.'); |
| + |
| + // If the argument is specified, override the default task sequence with |
| + // the specified one. |
| + if (arguments.length > 0) { |
| + this._taskSequence = []; |
| + for (let i = 0; arguments.length; i++) { |
| + let taskLabel = arguments[i]; |
| + if (!this._tasks.hasOwnProperty(taskLabel)) { |
| + _throwException('Audit.run:: undefined task.'); |
| + } else if (this._taskSequence.includes(taskLabel)) { |
| + _throwException('Audit.run:: duplicate task request.'); |
| + } else { |
| + this._taskSequence.push[taskLabel]; |
| + } |
| + } |
| + } |
| + |
| + if (this._taskSequence.length === 0) { |
| + _throwException('Audit.run:: no task to run.'); |
| + return; |
| + } |
| + |
| + // Start the first task. |
| + this._currentTaskIndex = 0; |
| + this._runNextTask(); |
| + } |
| + |
| + _runNextTask () { |
| + if (this._currentTaskIndex < this._taskSequence.length) { |
| + this._tasks[this._taskSequence[this._currentTaskIndex++]].run(); |
| + } else { |
| + this._finish(); |
| + } |
| + } |
| + |
| + _finish () { |
| + let numberOfFailures = 0; |
| + for (let taskIndex in this._taskSequence) { |
| + let task = this._tasks[this._taskSequence[taskIndex]]; |
| + numberOfFailures += task.result ? 0 : 1; |
| + } |
| + |
| + let prefix = '## AUDIT TASK RUNNER FINISHED: '; |
| + if (numberOfFailures > 0) { |
| + _logFailed(prefix + numberOfFailures + ' out of ' |
| + + this._taskSequence.length + ' tasks were failed.'); |
| + } else { |
| + _logPassed(prefix + this._taskSequence.length |
| + + ' tasks were successfully executed.'); |
| + } |
| + |
| + // From testharness.js, report back to the test infrastructure that |
| + // the task runner completed all the tasks. |
| + done(); |
| + } |
| + } |
| + |
| + |
| + return { |
| + |
| + createTaskRunner: function () { |
| + return new TaskRunner(); |
| + } |
| + |
| + }; |
| + |
| +})(); |