Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(96)

Unified Diff: third_party/WebKit/LayoutTests/webaudio/resources/audit.js

Issue 2435593009: New WebAudio layout testing utility: audit.js (Closed)
Patch Set: more progress Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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();
+ }
+
+ };
+
+})();

Powered by Google App Engine
This is Rietveld 408576698