summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webaudio/resources/audit.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webaudio/resources/audit.js')
-rw-r--r--testing/web-platform/tests/webaudio/resources/audit.js1445
1 files changed, 1445 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webaudio/resources/audit.js b/testing/web-platform/tests/webaudio/resources/audit.js
new file mode 100644
index 0000000000..2bb078b111
--- /dev/null
+++ b/testing/web-platform/tests/webaudio/resources/audit.js
@@ -0,0 +1,1445 @@
+// Copyright 2016 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// See https://github.com/web-platform-tests/wpt/issues/12781 for information on
+// the purpose of audit.js, and why testharness.js does not suffice.
+
+/**
+ * @fileOverview WebAudio layout test utility library. Built around W3C's
+ * testharness.js. Includes asynchronous test task manager,
+ * assertion utilities.
+ * @dependency testharness.js
+ */
+
+
+(function() {
+
+ 'use strict';
+
+ // Selected methods from testharness.js.
+ let testharnessProperties = [
+ 'test', 'async_test', 'promise_test', 'promise_rejects_js', '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]))
+ throw new Error('Cannot proceed. testharness.js is not loaded.');
+ }
+})();
+
+
+window.Audit = (function() {
+
+ 'use strict';
+
+ // NOTE: Moving this method (or any other code above) will change the location
+ // of 'CONSOLE ERROR...' message in the expected text files.
+ function _logError(message) {
+ console.error('[audit.js] ' + message);
+ }
+
+ 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);
+ }
+
+ // TODO(hongchan): remove this hack after confirming all the tests are
+ // finished correctly. (crbug.com/708817)
+ const _testharnessDone = window.done;
+ window.done = () => {
+ _throwException('Do NOT call done() method from the test code.');
+ };
+
+ // Generate a descriptive string from a target value in various types.
+ function _generateDescription(target, options) {
+ let targetString;
+
+ switch (typeof target) {
+ case 'object':
+ // Handle Arrays.
+ if (target instanceof Array || target instanceof Float32Array ||
+ target instanceof Float64Array || target instanceof Uint8Array) {
+ let arrayElements = target.length < options.numberOfArrayElements ?
+ String(target) :
+ String(target.slice(0, options.numberOfArrayElements)) + '...';
+ targetString = '[' + arrayElements + ']';
+ } else if (target === null) {
+ targetString = String(target);
+ } else {
+ targetString = '' + String(target).split(/[\s\]]/)[1];
+ }
+ break;
+ case 'function':
+ if (Error.isPrototypeOf(target)) {
+ targetString = "EcmaScript error " + target.name;
+ } else {
+ targetString = String(target);
+ }
+ break;
+ default:
+ targetString = String(target);
+ break;
+ }
+
+ return targetString;
+ }
+
+ // Return a string suitable for printing one failed element in
+ // |beCloseToArray|.
+ function _formatFailureEntry(index, actual, expected, abserr, threshold) {
+ return '\t[' + index + ']\t' + actual.toExponential(16) + '\t' +
+ expected.toExponential(16) + '\t' + abserr.toExponential(16) + '\t' +
+ (abserr / Math.abs(expected)).toExponential(16) + '\t' +
+ threshold.toExponential(16);
+ }
+
+ // Compute the error threshold criterion for |beCloseToArray|
+ function _closeToThreshold(abserr, relerr, expected) {
+ return Math.max(abserr, relerr * Math.abs(expected));
+ }
+
+ /**
+ * @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._actualDescription = (actualDescription || null);
+ this._expected = null;
+ this._expectedDescription = null;
+
+ this._detail = '';
+ // If true and the test failed, print the actual value at the
+ // end of the message.
+ this._printActualForFailure = true;
+
+ this._result = null;
+
+ /**
+ * @param {Number} numberOfErrors Number of errors to be printed.
+ * @param {Number} numberOfArrayElements Number of array elements to be
+ * printed in the test log.
+ * @param {Boolean} verbose Verbose output from the assertion.
+ */
+ this._options = {
+ numberOfErrors: 4,
+ numberOfArrayElements: 16,
+ verbose: false
+ };
+ }
+
+ _processArguments(args) {
+ if (args.length === 0)
+ return;
+
+ if (args.length > 0)
+ this._expected = args[0];
+
+ if (typeof args[1] === 'string') {
+ // case 1: (expected, description, options)
+ this._expectedDescription = args[1];
+ Object.assign(this._options, args[2]);
+ } else if (typeof args[1] === 'object') {
+ // case 2: (expected, options)
+ Object.assign(this._options, args[1]);
+ }
+ }
+
+ _buildResultText() {
+ if (this._result === null)
+ _throwException('Illegal invocation: the assertion is not finished.');
+
+ let actualString = _generateDescription(this._actual, this._options);
+
+ // Use generated text when the description is not provided.
+ if (!this._actualDescription)
+ this._actualDescription = actualString;
+
+ if (!this._expectedDescription) {
+ this._expectedDescription =
+ _generateDescription(this._expected, this._options);
+ }
+
+ // For the assertion with a single operand.
+ this._detail =
+ this._detail.replace(/\$\{actual\}/g, this._actualDescription);
+
+ // If there is a second operand (i.e. expected value), we have to build
+ // the string for it as well.
+ this._detail =
+ this._detail.replace(/\$\{expected\}/g, this._expectedDescription);
+
+ // If there is any property in |_options|, replace the property name
+ // with the value.
+ for (let name in this._options) {
+ if (name === 'numberOfErrors' || name === 'numberOfArrayElements' ||
+ name === 'verbose') {
+ continue;
+ }
+
+ // The RegExp key string contains special character. Take care of it.
+ let re = '\$\{' + name + '\}';
+ re = re.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
+ this._detail = this._detail.replace(
+ new RegExp(re, 'g'), _generateDescription(this._options[name]));
+ }
+
+ // If the test failed, add the actual value at the end.
+ if (this._result === false && this._printActualForFailure === true) {
+ this._detail += ' Got ' + actualString + '.';
+ }
+ }
+
+ _finalize() {
+ if (this._result) {
+ _logPassed(' ' + this._detail);
+ } else {
+ _logFailed('X ' + this._detail);
+ }
+
+ // This assertion is finished, so update the parent task accordingly.
+ this._task.update(this);
+
+ // TODO(hongchan): configurable 'detail' message.
+ }
+
+ _assert(condition, passDetail, failDetail) {
+ this._result = Boolean(condition);
+ this._detail = this._result ? passDetail : failDetail;
+ this._buildResultText();
+ this._finalize();
+
+ return this._result;
+ }
+
+ get result() {
+ return this._result;
+ }
+
+ get detail() {
+ return this._detail;
+ }
+
+ /**
+ * should() assertions.
+ *
+ * @example All the assertions can have 1, 2 or 3 arguments:
+ * should().doAssert(expected);
+ * should().doAssert(expected, options);
+ * should().doAssert(expected, expectedDescription, options);
+ *
+ * @param {Any} expected Expected value of the assertion.
+ * @param {String} expectedDescription Description of expected value.
+ * @param {Object} options Options for assertion.
+ * @param {Number} options.numberOfErrors Number of errors to be printed.
+ * (if applicable)
+ * @param {Number} options.numberOfArrayElements Number of array elements
+ * to be printed. (if
+ * applicable)
+ * @notes Some assertions can have additional options for their specific
+ * testing.
+ */
+
+ /**
+ * Check if |actual| exists.
+ *
+ * @example
+ * should({}, 'An empty object').exist();
+ * @result
+ * "PASS An empty object does exist."
+ */
+ 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. If it is an
+ * instance of DOMException, then the description (second argument) can be
+ * provided to be more strict about the expected exception type. |expected|
+ * also can be other generic error types such as TypeError, RangeError or
+ * etc.
+ *
+ * @example
+ * should(() => { let a = b; }, 'A bad code').throw();
+ * should(() => { new SomeConstructor(); }, 'A bad construction')
+ * .throw(DOMException, 'NotSupportedError');
+ * should(() => { let c = d; }, 'Assigning d to c')
+ * .throw(ReferenceError);
+ * should(() => { let e = f; }, 'Assigning e to f')
+ * .throw(ReferenceError, { omitErrorMessage: true });
+ *
+ * @result
+ * "PASS A bad code threw an exception of ReferenceError: b is not
+ * defined."
+ * "PASS A bad construction threw DOMException:NotSupportedError."
+ * "PASS Assigning d to c threw ReferenceError: d is not defined."
+ * "PASS Assigning e to f threw ReferenceError: [error message
+ * omitted]."
+ */
+ throw() {
+ this._processArguments(arguments);
+ this._printActualForFailure = false;
+
+ let didThrowCorrectly = 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) {
+ let errorMessage = this._options.omitErrorMessage ?
+ ': [error message omitted]' :
+ ': "' + error.message + '"';
+ if (this._expected === null || this._expected === undefined) {
+ // The expected error type was not given.
+ didThrowCorrectly = true;
+ passDetail = '${actual} threw ' + error.name + errorMessage + '.';
+ } else if (this._expected === DOMException &&
+ this._expectedDescription !== undefined) {
+ // Handles DOMException with an expected exception name.
+ if (this._expectedDescription === error.name) {
+ didThrowCorrectly = true;
+ passDetail = '${actual} threw ${expected}' + errorMessage + '.';
+ } else {
+ didThrowCorrectly = false;
+ failDetail =
+ '${actual} threw "' + error.name + '" instead of ${expected}.';
+ }
+ } else if (this._expected == error.constructor) {
+ // Handler other error types.
+ didThrowCorrectly = true;
+ passDetail = '${actual} threw ' + error.name + errorMessage + '.';
+ } else {
+ didThrowCorrectly = false;
+ failDetail =
+ '${actual} threw "' + error.name + '" instead of ${expected}.';
+ }
+ }
+
+ return this._assert(didThrowCorrectly, passDetail, failDetail);
+ }
+
+ /**
+ * Check if |actual| operation wrapped in a function does not throws an
+ * exception correctly.
+ *
+ * @example
+ * should(() => { let foo = 'bar'; }, 'let foo = "bar"').notThrow();
+ *
+ * @result
+ * "PASS let foo = "bar" did not throw an exception."
+ */
+ notThrow() {
+ this._printActualForFailure = false;
+
+ let didThrowCorrectly = false;
+ let passDetail, failDetail;
+
+ try {
+ this._actual();
+ passDetail = '${actual} did not throw an exception.';
+ } catch (error) {
+ didThrowCorrectly = true;
+ failDetail = '${actual} incorrectly threw ' + error.name + ': "' +
+ error.message + '".';
+ }
+
+ return this._assert(!didThrowCorrectly, passDetail, failDetail);
+ }
+
+ /**
+ * Check if |actual| promise is resolved correctly. Note that the returned
+ * result from promise object will be passed to the following then()
+ * function.
+ *
+ * @example
+ * should('My promise', promise).beResolve().then((result) => {
+ * log(result);
+ * });
+ *
+ * @result
+ * "PASS My promise resolved correctly."
+ * "FAIL X My promise rejected *INCORRECTLY* with _ERROR_."
+ */
+ beResolved() {
+ return this._actual.then(
+ function(result) {
+ this._assert(true, '${actual} resolved correctly.', null);
+ return result;
+ }.bind(this),
+ function(error) {
+ this._assert(
+ false, null,
+ '${actual} rejected incorrectly with ' + error + '.');
+ }.bind(this));
+ }
+
+ /**
+ * Check if |actual| promise is rejected correctly.
+ *
+ * @example
+ * should('My promise', promise).beRejected().then(nextStuff);
+ *
+ * @result
+ * "PASS My promise rejected correctly (with _ERROR_)."
+ * "FAIL X My promise resolved *INCORRECTLY*."
+ */
+ 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| promise is rejected correctly.
+ *
+ * @example
+ * should(promise, 'My promise').beRejectedWith('_ERROR_').then();
+ *
+ * @result
+ * "PASS My promise rejected correctly with _ERROR_."
+ * "FAIL X My promise rejected correctly but got _ACTUAL_ERROR instead of
+ * _EXPECTED_ERROR_."
+ * "FAIL X My promise resolved incorrectly."
+ */
+ beRejectedWith() {
+ this._processArguments(arguments);
+
+ return this._actual.then(
+ function() {
+ this._assert(false, null, '${actual} resolved incorrectly.');
+ }.bind(this),
+ function(error) {
+ if (this._expected !== error.name) {
+ this._assert(
+ false, null,
+ '${actual} rejected correctly but got ' + error.name +
+ ' instead of ' + this._expected + '.');
+ } else {
+ this._assert(
+ true,
+ '${actual} rejected correctly with ' + this._expected + '.',
+ null);
+ }
+ }.bind(this));
+ }
+
+ /**
+ * Check if |actual| is a boolean true.
+ *
+ * @example
+ * should(3 < 5, '3 < 5').beTrue();
+ *
+ * @result
+ * "PASS 3 < 5 is true."
+ */
+ beTrue() {
+ return this._assert(
+ this._actual === true, '${actual} is true.',
+ '${actual} is not true.');
+ }
+
+ /**
+ * Check if |actual| is a boolean false.
+ *
+ * @example
+ * should(3 > 5, '3 > 5').beFalse();
+ *
+ * @result
+ * "PASS 3 > 5 is false."
+ */
+ beFalse() {
+ return this._assert(
+ this._actual === false, '${actual} is false.',
+ '${actual} is not false.');
+ }
+
+ /**
+ * Check if |actual| is strictly equal to |expected|. (no type coercion)
+ *
+ * @example
+ * should(1).beEqualTo(1);
+ *
+ * @result
+ * "PASS 1 is equal to 1."
+ */
+ beEqualTo() {
+ this._processArguments(arguments);
+ return this._assert(
+ this._actual === this._expected, '${actual} is equal to ${expected}.',
+ '${actual} is not equal to ${expected}.');
+ }
+
+ /**
+ * Check if |actual| is not equal to |expected|.
+ *
+ * @example
+ * should(1).notBeEqualTo(2);
+ *
+ * @result
+ * "PASS 1 is not equal to 2."
+ */
+ notBeEqualTo() {
+ this._processArguments(arguments);
+ return this._assert(
+ this._actual !== this._expected,
+ '${actual} is not equal to ${expected}.',
+ '${actual} should not be equal to ${expected}.');
+ }
+
+ /**
+ * check if |actual| is NaN
+ *
+ * @example
+ * should(NaN).beNaN();
+ *
+ * @result
+ * "PASS NaN is NaN"
+ *
+ */
+ beNaN() {
+ this._processArguments(arguments);
+ return this._assert(
+ isNaN(this._actual),
+ '${actual} is NaN.',
+ '${actual} is not NaN but should be.');
+ }
+
+ /**
+ * check if |actual| is NOT NaN
+ *
+ * @example
+ * should(42).notBeNaN();
+ *
+ * @result
+ * "PASS 42 is not NaN"
+ *
+ */
+ notBeNaN() {
+ this._processArguments(arguments);
+ return this._assert(
+ !isNaN(this._actual),
+ '${actual} is not NaN.',
+ '${actual} is NaN but should not be.');
+ }
+
+ /**
+ * Check if |actual| is greater than |expected|.
+ *
+ * @example
+ * should(2).beGreaterThanOrEqualTo(2);
+ *
+ * @result
+ * "PASS 2 is greater than or equal to 2."
+ */
+ beGreaterThan() {
+ this._processArguments(arguments);
+ return this._assert(
+ this._actual > this._expected,
+ '${actual} is greater than ${expected}.',
+ '${actual} is not greater than ${expected}.');
+ }
+
+ /**
+ * Check if |actual| is greater than or equal to |expected|.
+ *
+ * @example
+ * should(2).beGreaterThan(1);
+ *
+ * @result
+ * "PASS 2 is greater than 1."
+ */
+ beGreaterThanOrEqualTo() {
+ this._processArguments(arguments);
+ 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 less than |expected|.
+ *
+ * @example
+ * should(1).beLessThan(2);
+ *
+ * @result
+ * "PASS 1 is less than 2."
+ */
+ beLessThan() {
+ this._processArguments(arguments);
+ return this._assert(
+ this._actual < this._expected, '${actual} is less than ${expected}.',
+ '${actual} is not less than ${expected}.');
+ }
+
+ /**
+ * Check if |actual| is less than or equal to |expected|.
+ *
+ * @example
+ * should(1).beLessThanOrEqualTo(1);
+ *
+ * @result
+ * "PASS 1 is less than or equal to 1."
+ */
+ beLessThanOrEqualTo() {
+ this._processArguments(arguments);
+ 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| array is filled with a constant |expected| value.
+ *
+ * @example
+ * should([1, 1, 1]).beConstantValueOf(1);
+ *
+ * @result
+ * "PASS [1,1,1] contains only the constant 1."
+ */
+ beConstantValueOf() {
+ this._processArguments(arguments);
+ this._printActualForFailure = false;
+
+ let passed = true;
+ let passDetail, failDetail;
+ let errors = {};
+
+ let actual = this._actual;
+ let expected = this._expected;
+ for (let index = 0; index < actual.length; ++index) {
+ if (actual[index] !== expected)
+ errors[index] = 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}: Expected ${expected} for all values but found ' +
+ numberOfErrors + ' unexpected values: ';
+ failDetail += '\n\tIndex\tActual';
+ for (let errorIndex in errors) {
+ failDetail += '\n\t[' + errorIndex + ']' +
+ '\t' + errors[errorIndex];
+ if (++counter >= this._options.numberOfErrors) {
+ failDetail +=
+ '\n\t...and ' + (numberOfErrors - counter) + ' more errors.';
+ break;
+ }
+ }
+ }
+
+ return this._assert(passed, passDetail, failDetail);
+ }
+
+ /**
+ * Check if |actual| array is not filled with a constant |expected| value.
+ *
+ * @example
+ * should([1, 0, 1]).notBeConstantValueOf(1);
+ * should([0, 0, 0]).notBeConstantValueOf(0);
+ *
+ * @result
+ * "PASS [1,0,1] is not constantly 1 (contains 1 different value)."
+ * "FAIL X [0,0,0] should have contain at least one value different
+ * from 0."
+ */
+ notBeConstantValueOf() {
+ this._processArguments(arguments);
+ this._printActualForFailure = false;
+
+ let passed = true;
+ let passDetail;
+ let failDetail;
+ let differences = {};
+
+ let actual = this._actual;
+ let expected = this._expected;
+ for (let index = 0; index < actual.length; ++index) {
+ if (actual[index] !== expected)
+ differences[index] = actual[index];
+ }
+
+ let numberOfDifferences = Object.keys(differences).length;
+ passed = numberOfDifferences > 0;
+
+ if (passed) {
+ let valueString = numberOfDifferences > 1 ? 'values' : 'value';
+ passDetail = '${actual} is not constantly ${expected} (contains ' +
+ numberOfDifferences + ' different ' + valueString + ').';
+ } else {
+ failDetail = '${actual} should have contain at least one value ' +
+ 'different from ${expected}.';
+ }
+
+ return this._assert(passed, passDetail, failDetail);
+ }
+
+ /**
+ * Check if |actual| array is identical to |expected| array element-wise.
+ *
+ * @example
+ * should([1, 2, 3]).beEqualToArray([1, 2, 3]);
+ *
+ * @result
+ * "[1,2,3] is identical to the array [1,2,3]."
+ */
+ beEqualToArray() {
+ this._processArguments(arguments);
+ this._printActualForFailure = false;
+
+ 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);
+ }
+
+ let actual = this._actual;
+ let expected = this._expected;
+ for (let index = 0; index < actual.length; ++index) {
+ if (actual[index] !== 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} expected to be equal to the array ${expected} ' +
+ 'but differs in ' + errorIndices.length + ' places:' +
+ '\n\tIndex\tActual\t\t\tExpected';
+ for (let index of errorIndices) {
+ failDetail += '\n\t[' + index + ']' +
+ '\t' + this._actual[index].toExponential(16) + '\t' +
+ this._expected[index].toExponential(16);
+ if (++counter >= this._options.numberOfErrors) {
+ failDetail += '\n\t...and ' + (errorIndices.length - counter) +
+ ' more errors.';
+ break;
+ }
+ }
+ }
+
+ return this._assert(passed, passDetail, failDetail);
+ }
+
+ /**
+ * Check if |actual| array contains only the values in |expected| in the
+ * order of values in |expected|.
+ *
+ * @example
+ * Should([1, 1, 3, 3, 2], 'My random array').containValues([1, 3, 2]);
+ *
+ * @result
+ * "PASS [1,1,3,3,2] contains all the expected values in the correct
+ * order: [1,3,2].
+ */
+ containValues() {
+ this._processArguments(arguments);
+ this._printActualForFailure = false;
+
+ let passed = true;
+ let indexedActual = [];
+ let firstErrorIndex = null;
+
+ // Collect the unique value sequence from the actual.
+ for (let i = 0, prev = null; i < this._actual.length; i++) {
+ if (this._actual[i] !== prev) {
+ indexedActual.push({index: i, value: this._actual[i]});
+ prev = this._actual[i];
+ }
+ }
+
+ // Compare against the expected sequence.
+ let failMessage =
+ '${actual} expected to have the value sequence of ${expected} but ' +
+ 'got ';
+ if (this._expected.length === indexedActual.length) {
+ for (let j = 0; j < this._expected.length; j++) {
+ if (this._expected[j] !== indexedActual[j].value) {
+ firstErrorIndex = indexedActual[j].index;
+ passed = false;
+ failMessage += this._actual[firstErrorIndex] + ' at index ' +
+ firstErrorIndex + '.';
+ break;
+ }
+ }
+ } else {
+ passed = false;
+ let indexedValues = indexedActual.map(x => x.value);
+ failMessage += `${indexedActual.length} values, [${
+ indexedValues}], instead of ${this._expected.length}.`;
+ }
+
+ return this._assert(
+ passed,
+ '${actual} contains all the expected values in the correct order: ' +
+ '${expected}.',
+ failMessage);
+ }
+
+ /**
+ * Check if |actual| array does not have any glitches. Note that |threshold|
+ * is not optional and is to define the desired threshold value.
+ *
+ * @example
+ * should([0.5, 0.5, 0.55, 0.5, 0.45, 0.5]).notGlitch(0.06);
+ *
+ * @result
+ * "PASS [0.5,0.5,0.55,0.5,0.45,0.5] has no glitch above the threshold
+ * of 0.06."
+ *
+ */
+ notGlitch() {
+ this._processArguments(arguments);
+ this._printActualForFailure = false;
+
+ let passed = true;
+ let passDetail, failDetail;
+
+ let actual = this._actual;
+ let expected = this._expected;
+ for (let index = 0; index < actual.length; ++index) {
+ let diff = Math.abs(actual[index - 1] - actual[index]);
+ if (diff >= 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|.
+ *
+ * @example
+ * should(2.3).beCloseTo(2, { threshold: 0.3 });
+ *
+ * @result
+ * "PASS 2.3 is 2 within an error of 0.3."
+ * @param {Object} options Options for assertion.
+ * @param {Number} options.threshold Threshold value for the comparison.
+ */
+ beCloseTo() {
+ this._processArguments(arguments);
+
+ // The threshold is relative except when |expected| is zero, in which case
+ // it is absolute.
+ let absExpected = this._expected ? Math.abs(this._expected) : 1;
+ let error = Math.abs(this._actual - this._expected) / absExpected;
+
+ return this._assert(
+ error <= this._options.threshold,
+ '${actual} is ${expected} within an error of ${threshold}.',
+ '${actual} is not close to ${expected} within a relative error of ' +
+ '${threshold} (RelErr=' + error + ').');
+ }
+
+ /**
+ * Check if |target| array is close to |expected| array element-wise within
+ * a certain error bound given by the |options|.
+ *
+ * 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.
+ *
+ * @param {Number} options.absoluteThreshold Absolute threshold.
+ * @param {Number} options.relativeThreshold Relative threshold.
+ */
+ beCloseToArray() {
+ this._processArguments(arguments);
+ this._printActualForFailure = false;
+
+ 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;
+
+ let actual = this._actual;
+ let expected = this._expected;
+
+ for (let index = 0; index < expected.length; ++index) {
+ let diff = Math.abs(actual[index] - expected[index]);
+ let absExpected = Math.abs(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) {
+ // The assertion was successful.
+ passDetail = '${actual} equals ${expected} with an element-wise ' +
+ 'tolerance of ' + maxAllowedErrorDetail + '.';
+ } else {
+ // Failed. Prepare the detailed failure log.
+ passed = false;
+ failDetail = '${actual} does not equal ${expected} with an ' +
+ 'element-wise tolerance of ' + maxAllowedErrorDetail + '.\n';
+
+ // Print out actual, expected, absolute error, and relative error.
+ let counter = 0;
+ failDetail += '\tIndex\tActual\t\t\tExpected\t\tAbsError' +
+ '\t\tRelError\t\tTest threshold';
+ let printedIndices = [];
+ for (let index in errors) {
+ failDetail +=
+ '\n' +
+ _formatFailureEntry(
+ index, actual[index], expected[index], errors[index],
+ _closeToThreshold(
+ absErrorThreshold, relErrorThreshold, expected[index]));
+
+ printedIndices.push(index);
+ if (++counter > this._options.numberOfErrors) {
+ failDetail +=
+ '\n\t...and ' + (numberOfErrors - counter) + ' more errors.';
+ break;
+ }
+ }
+
+ // Finalize the error log: print out the location of both the maxAbs
+ // error and the maxRel error so we can adjust thresholds appropriately
+ // in the test.
+ failDetail += '\n' +
+ '\tMax AbsError of ' + maxAbsError.toExponential(16) +
+ ' at index of ' + maxAbsErrorIndex + '.\n';
+ if (printedIndices.find(element => {
+ return element == maxAbsErrorIndex;
+ }) === undefined) {
+ // Print an entry for this index if we haven't already.
+ failDetail +=
+ _formatFailureEntry(
+ maxAbsErrorIndex, actual[maxAbsErrorIndex],
+ expected[maxAbsErrorIndex], errors[maxAbsErrorIndex],
+ _closeToThreshold(
+ absErrorThreshold, relErrorThreshold,
+ expected[maxAbsErrorIndex])) +
+ '\n';
+ }
+ failDetail += '\tMax RelError of ' + maxRelError.toExponential(16) +
+ ' at index of ' + maxRelErrorIndex + '.\n';
+ if (printedIndices.find(element => {
+ return element == maxRelErrorIndex;
+ }) === undefined) {
+ // Print an entry for this index if we haven't already.
+ failDetail +=
+ _formatFailureEntry(
+ maxRelErrorIndex, actual[maxRelErrorIndex],
+ expected[maxRelErrorIndex], errors[maxRelErrorIndex],
+ _closeToThreshold(
+ absErrorThreshold, relErrorThreshold,
+ expected[maxRelErrorIndex])) +
+ '\n';
+ }
+ }
+
+ return this._assert(passed, passDetail, failDetail);
+ }
+
+ /**
+ * A temporary escape hat for printing an in-task message. The description
+ * for the |actual| is required to get the message printed properly.
+ *
+ * TODO(hongchan): remove this method when the transition from the old Audit
+ * to the new Audit is completed.
+ * @example
+ * should(true, 'The message is').message('truthful!', 'false!');
+ *
+ * @result
+ * "PASS The message is truthful!"
+ */
+ message(passDetail, failDetail) {
+ return this._assert(
+ this._actual, '${actual} ' + passDetail, '${actual} ' + failDetail);
+ }
+
+ /**
+ * Check if |expected| property is truly owned by |actual| object.
+ *
+ * @example
+ * should(BaseAudioContext.prototype,
+ * 'BaseAudioContext.prototype').haveOwnProperty('createGain');
+ *
+ * @result
+ * "PASS BaseAudioContext.prototype has an own property of
+ * 'createGain'."
+ */
+ haveOwnProperty() {
+ this._processArguments(arguments);
+
+ return this._assert(
+ this._actual.hasOwnProperty(this._expected),
+ '${actual} has an own property of "${expected}".',
+ '${actual} does not own the property of "${expected}".');
+ }
+
+
+ /**
+ * Check if |expected| property is not owned by |actual| object.
+ *
+ * @example
+ * should(BaseAudioContext.prototype,
+ * 'BaseAudioContext.prototype')
+ * .notHaveOwnProperty('startRendering');
+ *
+ * @result
+ * "PASS BaseAudioContext.prototype does not have an own property of
+ * 'startRendering'."
+ */
+ notHaveOwnProperty() {
+ this._processArguments(arguments);
+
+ return this._assert(
+ !this._actual.hasOwnProperty(this._expected),
+ '${actual} does not have an own property of "${expected}".',
+ '${actual} has an own the property of "${expected}".')
+ }
+
+
+ /**
+ * Check if an object is inherited from a class. This looks up the entire
+ * prototype chain of a given object and tries to find a match.
+ *
+ * @example
+ * should(sourceNode, 'A buffer source node')
+ * .inheritFrom('AudioScheduledSourceNode');
+ *
+ * @result
+ * "PASS A buffer source node inherits from 'AudioScheduledSourceNode'."
+ */
+ inheritFrom() {
+ this._processArguments(arguments);
+
+ let prototypes = [];
+ let currentPrototype = Object.getPrototypeOf(this._actual);
+ while (currentPrototype) {
+ prototypes.push(currentPrototype.constructor.name);
+ currentPrototype = Object.getPrototypeOf(currentPrototype);
+ }
+
+ return this._assert(
+ prototypes.includes(this._expected),
+ '${actual} inherits from "${expected}".',
+ '${actual} does not inherit from "${expected}".');
+ }
+ }
+
+
+ // Task Class state enum.
+ const TaskState = {PENDING: 0, STARTED: 1, FINISHED: 2};
+
+
+ /**
+ * @class Task
+ * @description WebAudio testing task. Managed by TaskRunner.
+ */
+ class Task {
+ /**
+ * Task constructor.
+ * @param {Object} taskRunner Reference of associated task runner.
+ * @param {String||Object} taskLabel Task label if a string is given. This
+ * parameter can be a dictionary with the
+ * following fields.
+ * @param {String} taskLabel.label Task label.
+ * @param {String} taskLabel.description Description of task.
+ * @param {Function} taskFunction Task function to be performed.
+ * @return {Object} Task object.
+ */
+ constructor(taskRunner, taskLabel, taskFunction) {
+ this._taskRunner = taskRunner;
+ this._taskFunction = taskFunction;
+
+ if (typeof taskLabel === 'string') {
+ this._label = taskLabel;
+ this._description = null;
+ } else if (typeof taskLabel === 'object') {
+ if (typeof taskLabel.label !== 'string') {
+ _throwException('Task.constructor:: task label must be string.');
+ }
+ this._label = taskLabel.label;
+ this._description = (typeof taskLabel.description === 'string') ?
+ taskLabel.description :
+ null;
+ } else {
+ _throwException(
+ 'Task.constructor:: task label must be a string or ' +
+ 'a dictionary.');
+ }
+
+ this._state = TaskState.PENDING;
+ this._result = true;
+
+ this._totalAssertions = 0;
+ this._failedAssertions = 0;
+ }
+
+ get label() {
+ return this._label;
+ }
+
+ get state() {
+ return this._state;
+ }
+
+ get result() {
+ return this._result;
+ }
+
+ // Start the assertion chain.
+ should(actual, actualDescription) {
+ // If no argument is given, we cannot proceed. Halt.
+ if (arguments.length === 0)
+ _throwException('Task.should:: requires at least 1 argument.');
+
+ return new Should(this, actual, actualDescription);
+ }
+
+ // Run this task. |this| task will be passed into the user-supplied test
+ // task function.
+ run(harnessTest) {
+ this._state = TaskState.STARTED;
+ this._harnessTest = harnessTest;
+ // Print out the task entry with label and description.
+ _logPassed(
+ '> [' + this._label + '] ' +
+ (this._description ? this._description : ''));
+
+ return new Promise((resolve, reject) => {
+ this._resolve = resolve;
+ this._reject = reject;
+ let result = this._taskFunction(this, this.should.bind(this));
+ if (result && typeof result.then === "function") {
+ result.then(() => this.done()).catch(reject);
+ }
+ });
+ }
+
+ // 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() {
+ assert_equals(this._state, TaskState.STARTED)
+ this._state = TaskState.FINISHED;
+
+ let message = '< [' + this._label + '] ';
+
+ if (this._result) {
+ message += 'All assertions passed. (total ' + this._totalAssertions +
+ ' assertions)';
+ _logPassed(message);
+ } else {
+ message += this._failedAssertions + ' out of ' + this._totalAssertions +
+ ' assertions were failed.'
+ _logFailed(message);
+ }
+
+ this._resolve();
+ }
+
+ // Runs |subTask| |time| milliseconds later. |setTimeout| is not allowed in
+ // WPT linter, so a thin wrapper around the harness's |step_timeout| is
+ // used here. Returns a Promise which is resolved after |subTask| runs.
+ timeout(subTask, time) {
+ return new Promise(resolve => {
+ this._harnessTest.step_timeout(() => {
+ let result = subTask();
+ if (result && typeof result.then === "function") {
+ // Chain rejection directly to the harness test Promise, to report
+ // the rejection against the subtest even when the caller of
+ // timeout does not handle the rejection.
+ result.then(resolve, this._reject());
+ } else {
+ resolve();
+ }
+ }, time);
+ });
+ }
+
+ isPassed() {
+ return this._state === TaskState.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 = [];
+
+ // Configure testharness.js for the async operation.
+ setup(new Function(), {explicit_done: true});
+ }
+
+ _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 ran successfully.');
+ }
+
+ return Promise.resolve();
+ }
+
+ // |taskLabel| can be either a string or a dictionary. See Task constructor
+ // for the detail. If |taskFunction| returns a thenable, then the task
+ // is considered complete when the thenable is fulfilled; otherwise the
+ // task must be completed with an explicit call to |task.done()|.
+ define(taskLabel, taskFunction) {
+ let task = new Task(this, taskLabel, taskFunction);
+ if (this._tasks.hasOwnProperty(task.label)) {
+ _throwException('Audit.define:: Duplicate task definition.');
+ return;
+ }
+ this._tasks[task.label] = task;
+ this._taskSequence.push(task.label);
+ }
+
+ // Start running all the tasks scheduled. Multiple task names can be passed
+ // to execute them sequentially. Zero argument will perform all defined
+ // tasks in the order of definition.
+ 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; i < 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;
+ }
+
+ for (let taskIndex in this._taskSequence) {
+ let task = this._tasks[this._taskSequence[taskIndex]];
+ // Some tests assume that tasks run in sequence, which is provided by
+ // promise_test().
+ promise_test((t) => task.run(t), `Executing "${task.label}"`);
+ }
+
+ // Schedule a summary report on completion.
+ promise_test(() => this._finish(), "Audit report");
+
+ // From testharness.js. The harness now need not wait for more subtests
+ // to be added.
+ _testharnessDone();
+ }
+ }
+
+ /**
+ * Load file from a given URL and pass ArrayBuffer to the following promise.
+ * @param {String} fileUrl file URL.
+ * @return {Promise}
+ *
+ * @example
+ * Audit.loadFileFromUrl('resources/my-sound.ogg').then((response) => {
+ * audioContext.decodeAudioData(response).then((audioBuffer) => {
+ * // Do something with AudioBuffer.
+ * });
+ * });
+ */
+ function loadFileFromUrl(fileUrl) {
+ return new Promise((resolve, reject) => {
+ let xhr = new XMLHttpRequest();
+ xhr.open('GET', fileUrl, true);
+ xhr.responseType = 'arraybuffer';
+
+ xhr.onload = () => {
+ // |status = 0| is a workaround for the run_web_test.py server. We are
+ // speculating the server quits the transaction prematurely without
+ // completing the request.
+ if (xhr.status === 200 || xhr.status === 0) {
+ resolve(xhr.response);
+ } else {
+ let errorMessage = 'loadFile: Request failed when loading ' +
+ fileUrl + '. ' + xhr.statusText + '. (status = ' + xhr.status +
+ ')';
+ if (reject) {
+ reject(errorMessage);
+ } else {
+ new Error(errorMessage);
+ }
+ }
+ };
+
+ xhr.onerror = (event) => {
+ let errorMessage =
+ 'loadFile: Network failure when loading ' + fileUrl + '.';
+ if (reject) {
+ reject(errorMessage);
+ } else {
+ new Error(errorMessage);
+ }
+ };
+
+ xhr.send();
+ });
+ }
+
+ /**
+ * @class Audit
+ * @description A WebAudio layout test task manager.
+ * @example
+ * let audit = Audit.createTaskRunner();
+ * audit.define('first-task', function (task, should) {
+ * should(someValue).beEqualTo(someValue);
+ * task.done();
+ * });
+ * audit.run();
+ */
+ return {
+
+ /**
+ * Creates an instance of Audit task runner.
+ * @param {Object} options Options for task runner.
+ * @param {Boolean} options.requireResultFile True if the test suite
+ * requires explicit text
+ * comparison with the expected
+ * result file.
+ */
+ createTaskRunner: function(options) {
+ if (options && options.requireResultFile == true) {
+ _logError(
+ 'this test requires the explicit comparison with the ' +
+ 'expected result when it runs with run_web_tests.py.');
+ }
+
+ return new TaskRunner();
+ },
+
+ /**
+ * Load file from a given URL and pass ArrayBuffer to the following promise.
+ * See |loadFileFromUrl| method for the detail.
+ */
+ loadFileFromUrl: loadFileFromUrl
+
+ };
+
+})();