Update web-platform-tests to revision 44702f2bc8ea98bc32b5b244f2fe63c6ce66d49d

This commit is contained in:
Josh Matthews 2017-11-15 12:15:13 -05:00
parent 85fa6409bb
commit c227604a2c
997 changed files with 45660 additions and 14650 deletions

View file

@ -0,0 +1,554 @@
(function(global) {
// Information about the starting/ending times and starting/ending values for
// each time interval.
let timeValueInfo;
// The difference between starting values between each time interval.
let startingValueDelta;
// For any automation function that has an end or target value, the end value
// is based the starting value of the time interval. The starting value will
// be increased or decreased by |startEndValueChange|. We choose half of
// |startingValueDelta| so that the ending value will be distinct from the
// starting value for next time interval. This allows us to detect where the
// ramp begins and ends.
let startEndValueChange;
// Default threshold to use for detecting discontinuities that should appear
// at each time interval.
let discontinuityThreshold;
// Time interval between value changes. It is best if 1 / numberOfTests is
// not close to timeInterval.
let timeIntervalInternal = .03;
let context;
// Make sure we render long enough to capture all of our test data.
function renderLength(numberOfTests) {
return timeToSampleFrame((numberOfTests + 1) * timeInterval, sampleRate);
}
// Create a constant reference signal with the given |value|. Basically the
// same as |createConstantBuffer|, but with the parameters to match the other
// create functions. The |endValue| is ignored.
function createConstantArray(
startTime, endTime, value, endValue, sampleRate) {
let startFrame = timeToSampleFrame(startTime, sampleRate);
let endFrame = timeToSampleFrame(endTime, sampleRate);
let length = endFrame - startFrame;
let buffer = createConstantBuffer(context, length, value);
return buffer.getChannelData(0);
}
function getStartEndFrames(startTime, endTime, sampleRate) {
// Start frame is the ceiling of the start time because the ramp starts at
// or after the sample frame. End frame is the ceiling because it's the
// exclusive ending frame of the automation.
let startFrame = Math.ceil(startTime * sampleRate);
let endFrame = Math.ceil(endTime * sampleRate);
return {startFrame: startFrame, endFrame: endFrame};
}
// Create a linear ramp starting at |startValue| and ending at |endValue|. The
// ramp starts at time |startTime| and ends at |endTime|. (The start and end
// times are only used to compute how many samples to return.)
function createLinearRampArray(
startTime, endTime, startValue, endValue, sampleRate) {
let frameInfo = getStartEndFrames(startTime, endTime, sampleRate);
let startFrame = frameInfo.startFrame;
let endFrame = frameInfo.endFrame;
let length = endFrame - startFrame;
let array = new Array(length);
let step = Math.fround(
(endValue - startValue) / (endTime - startTime) / sampleRate);
let start = Math.fround(
startValue +
(endValue - startValue) * (startFrame / sampleRate - startTime) /
(endTime - startTime));
let slope = (endValue - startValue) / (endTime - startTime);
// v(t) = v0 + (v1 - v0)*(t-t0)/(t1-t0)
for (k = 0; k < length; ++k) {
// array[k] = Math.fround(start + k * step);
let t = (startFrame + k) / sampleRate;
array[k] = startValue + slope * (t - startTime);
}
return array;
}
// Create an exponential ramp starting at |startValue| and ending at
// |endValue|. The ramp starts at time |startTime| and ends at |endTime|.
// (The start and end times are only used to compute how many samples to
// return.)
function createExponentialRampArray(
startTime, endTime, startValue, endValue, sampleRate) {
let deltaTime = endTime - startTime;
let frameInfo = getStartEndFrames(startTime, endTime, sampleRate);
let startFrame = frameInfo.startFrame;
let endFrame = frameInfo.endFrame;
let length = endFrame - startFrame;
let array = new Array(length);
let ratio = endValue / startValue;
// v(t) = v0*(v1/v0)^((t-t0)/(t1-t0))
for (let k = 0; k < length; ++k) {
let t = Math.fround((startFrame + k) / sampleRate);
array[k] = Math.fround(
startValue * Math.pow(ratio, (t - startTime) / deltaTime));
}
return array;
}
function discreteTimeConstantForSampleRate(timeConstant, sampleRate) {
return 1 - Math.exp(-1 / (sampleRate * timeConstant));
}
// Create a signal that starts at |startValue| and exponentially approaches
// the target value of |targetValue|, using a time constant of |timeConstant|.
// The ramp starts at time |startTime| and ends at |endTime|. (The start and
// end times are only used to compute how many samples to return.)
function createExponentialApproachArray(
startTime, endTime, startValue, targetValue, sampleRate, timeConstant) {
let startFrameFloat = startTime * sampleRate;
let frameInfo = getStartEndFrames(startTime, endTime, sampleRate);
let startFrame = frameInfo.startFrame;
let endFrame = frameInfo.endFrame;
let length = Math.floor(endFrame - startFrame);
let array = new Array(length);
let c = discreteTimeConstantForSampleRate(timeConstant, sampleRate);
let delta = startValue - targetValue;
// v(t) = v1 + (v0 - v1) * exp(-(t-t0)/tau)
for (let k = 0; k < length; ++k) {
let t = (startFrame + k) / sampleRate;
let value =
targetValue + delta * Math.exp(-(t - startTime) / timeConstant);
array[k] = value;
}
return array;
}
// Create a sine wave of the specified duration.
function createReferenceSineArray(
startTime, endTime, startValue, endValue, sampleRate) {
// Ignore |startValue| and |endValue| for the sine wave.
let curve = createSineWaveArray(
endTime - startTime, freqHz, sineAmplitude, sampleRate);
// Sample the curve appropriately.
let frameInfo = getStartEndFrames(startTime, endTime, sampleRate);
let startFrame = frameInfo.startFrame;
let endFrame = frameInfo.endFrame;
let length = Math.floor(endFrame - startFrame);
let array = new Array(length);
// v(t) = linearly interpolate between V[k] and V[k + 1] where k =
// floor((N-1)/duration*(t - t0))
let f = (length - 1) / (endTime - startTime);
for (let k = 0; k < length; ++k) {
let t = (startFrame + k) / sampleRate;
let indexFloat = f * (t - startTime);
let index = Math.floor(indexFloat);
if (index + 1 < length) {
let v0 = curve[index];
let v1 = curve[index + 1];
array[k] = v0 + (v1 - v0) * (indexFloat - index);
} else {
array[k] = curve[length - 1];
}
}
return array;
}
// Create a sine wave of the given frequency and amplitude. The sine wave is
// offset by half the amplitude so that result is always positive.
function createSineWaveArray(durationSeconds, freqHz, amplitude, sampleRate) {
let length = timeToSampleFrame(durationSeconds, sampleRate);
let signal = new Float32Array(length);
let omega = 2 * Math.PI * freqHz / sampleRate;
let halfAmplitude = amplitude / 2;
for (let k = 0; k < length; ++k) {
signal[k] = halfAmplitude + halfAmplitude * Math.sin(omega * k);
}
return signal;
}
// Return the difference between the starting value and the ending value for
// time interval |timeIntervalIndex|. We alternate between an end value that
// is above or below the starting value.
function endValueDelta(timeIntervalIndex) {
if (timeIntervalIndex & 1) {
return -startEndValueChange;
} else {
return startEndValueChange;
}
}
// Relative error metric
function relativeErrorMetric(actual, expected) {
return (actual - expected) / Math.abs(expected);
}
// Difference metric
function differenceErrorMetric(actual, expected) {
return actual - expected;
}
// Return the difference between the starting value at |timeIntervalIndex| and
// the starting value at the next time interval. Since we started at a large
// initial value, we decrease the value at each time interval.
function valueUpdate(timeIntervalIndex) {
return -startingValueDelta;
}
// Compare a section of the rendered data against our expected signal.
function comparePartialSignals(
should, rendered, expectedFunction, startTime, endTime, valueInfo,
sampleRate, errorMetric) {
let startSample = timeToSampleFrame(startTime, sampleRate);
let expected = expectedFunction(
startTime, endTime, valueInfo.startValue, valueInfo.endValue,
sampleRate, timeConstant);
let n = expected.length;
let maxError = -1;
let maxErrorIndex = -1;
for (let k = 0; k < n; ++k) {
// Make sure we don't pass these tests because a NaN has been generated in
// either the
// rendered data or the reference data.
if (!isValidNumber(rendered[startSample + k])) {
maxError = Infinity;
maxErrorIndex = startSample + k;
should(
isValidNumber(rendered[startSample + k]),
'NaN or infinity for rendered data at ' + maxErrorIndex)
.beTrue();
break;
}
if (!isValidNumber(expected[k])) {
maxError = Infinity;
maxErrorIndex = startSample + k;
should(
isValidNumber(expected[k]),
'NaN or infinity for rendered data at ' + maxErrorIndex)
.beTrue();
break;
}
let error = Math.abs(errorMetric(rendered[startSample + k], expected[k]));
if (error > maxError) {
maxError = error;
maxErrorIndex = k;
}
}
return {maxError: maxError, index: maxErrorIndex, expected: expected};
}
// Find the discontinuities in the data and compare the locations of the
// discontinuities with the times that define the time intervals. There is a
// discontinuity if the difference between successive samples exceeds the
// threshold.
function verifyDiscontinuities(should, values, times, threshold) {
let n = values.length;
let success = true;
let badLocations = 0;
let breaks = [];
// Find discontinuities.
for (let k = 1; k < n; ++k) {
if (Math.abs(values[k] - values[k - 1]) > threshold) {
breaks.push(k);
}
}
let testCount;
// If there are numberOfTests intervals, there are only numberOfTests - 1
// internal interval boundaries. Hence the maximum number of discontinuties
// we expect to find is numberOfTests - 1. If we find more than that, we
// have no reference to compare against. We also assume that the actual
// discontinuities are close to the expected ones.
//
// This is just a sanity check when something goes really wrong. For
// example, if the threshold is too low, every sample frame looks like a
// discontinuity.
if (breaks.length >= numberOfTests) {
testCount = numberOfTests - 1;
should(breaks.length, 'Number of discontinuities')
.beLessThan(numberOfTests);
success = false;
} else {
testCount = breaks.length;
}
// Compare the location of each discontinuity with the end time of each
// interval. (There is no discontinuity at the start of the signal.)
for (let k = 0; k < testCount; ++k) {
let expectedSampleFrame = timeToSampleFrame(times[k + 1], sampleRate);
if (breaks[k] != expectedSampleFrame) {
success = false;
++badLocations;
should(breaks[k], 'Discontinuity at index')
.beEqualTo(expectedSampleFrame);
}
}
if (badLocations) {
should(badLocations, 'Number of discontinuites at incorrect locations')
.beEqualTo(0);
success = false;
} else {
should(
breaks.length + 1,
'Number of tests started and ended at the correct time')
.beEqualTo(numberOfTests);
}
return success;
}
// Compare the rendered data with the expected data.
//
// testName - string describing the test
//
// maxError - maximum allowed difference between the rendered data and the
// expected data
//
// rendererdData - array containing the rendered (actual) data
//
// expectedFunction - function to compute the expected data
//
// timeValueInfo - array containing information about the start and end times
// and the start and end values of each interval.
//
// breakThreshold - threshold to use for determining discontinuities.
function compareSignals(
should, testName, maxError, renderedData, expectedFunction, timeValueInfo,
breakThreshold, errorMetric) {
let success = true;
let failedTestCount = 0;
let times = timeValueInfo.times;
let values = timeValueInfo.values;
let n = values.length;
let expectedSignal = [];
success =
verifyDiscontinuities(should, renderedData, times, breakThreshold);
for (let k = 0; k < n; ++k) {
let result = comparePartialSignals(
should, renderedData, expectedFunction, times[k], times[k + 1],
values[k], sampleRate, errorMetric);
expectedSignal =
expectedSignal.concat(Array.prototype.slice.call(result.expected));
should(
result.maxError,
'Max error for test ' + k + ' at offset ' +
(result.index + timeToSampleFrame(times[k], sampleRate)))
.beLessThanOrEqualTo(maxError);
}
should(
failedTestCount,
'Number of failed tests with an acceptable relative tolerance of ' +
maxError)
.beEqualTo(0);
}
// Create a function to test the rendered data with the reference data.
//
// testName - string describing the test
//
// error - max allowed error between rendered data and the reference data.
//
// referenceFunction - function that generates the reference data to be
// compared with the rendered data.
//
// jumpThreshold - optional parameter that specifies the threshold to use for
// detecting discontinuities. If not specified, defaults to
// discontinuityThreshold.
//
function checkResultFunction(
task, should, testName, error, referenceFunction, jumpThreshold,
errorMetric) {
return function(event) {
let buffer = event.renderedBuffer;
renderedData = buffer.getChannelData(0);
let threshold;
if (!jumpThreshold) {
threshold = discontinuityThreshold;
} else {
threshold = jumpThreshold;
}
compareSignals(
should, testName, error, renderedData, referenceFunction,
timeValueInfo, threshold, errorMetric);
task.done();
}
}
// Run all the automation tests.
//
// numberOfTests - number of tests (time intervals) to run.
//
// initialValue - The initial value of the first time interval.
//
// setValueFunction - function that sets the specified value at the start of a
// time interval.
//
// automationFunction - function that sets the end value for the time
// interval. It specifies how the value approaches the end value.
//
// An object is returned containing an array of start times for each time
// interval, and an array giving the start and end values for the interval.
function doAutomation(
numberOfTests, initialValue, setValueFunction, automationFunction) {
let timeInfo = [0];
let valueInfo = [];
let value = initialValue;
for (let k = 0; k < numberOfTests; ++k) {
let startTime = k * timeInterval;
let endTime = (k + 1) * timeInterval;
let endValue = value + endValueDelta(k);
// Set the value at the start of the time interval.
setValueFunction(value, startTime);
// Specify the end or target value, and how we should approach it.
automationFunction(endValue, startTime, endTime);
// Keep track of the start times, and the start and end values for each
// time interval.
timeInfo.push(endTime);
valueInfo.push({startValue: value, endValue: endValue});
value += valueUpdate(k);
}
return {times: timeInfo, values: valueInfo};
}
// Create the audio graph for the test and then run the test.
//
// numberOfTests - number of time intervals (tests) to run.
//
// initialValue - the initial value of the gain at time 0.
//
// setValueFunction - function to set the value at the beginning of each time
// interval.
//
// automationFunction - the AudioParamTimeline automation function
//
// testName - string indicating the test that is being run.
//
// maxError - maximum allowed error between the rendered data and the
// reference data
//
// referenceFunction - function that generates the reference data to be
// compared against the rendered data.
//
// jumpThreshold - optional parameter that specifies the threshold to use for
// detecting discontinuities. If not specified, defaults to
// discontinuityThreshold.
//
function createAudioGraphAndTest(
task, should, numberOfTests, initialValue, setValueFunction,
automationFunction, testName, maxError, referenceFunction, jumpThreshold,
errorMetric) {
// Create offline audio context.
context =
new OfflineAudioContext(2, renderLength(numberOfTests), sampleRate);
let constantBuffer =
createConstantBuffer(context, renderLength(numberOfTests), 1);
// We use an AudioGainNode here simply as a convenient way to test the
// AudioParam automation, since it's easy to pass a constant value through
// the node, automate the .gain attribute and observe the resulting values.
gainNode = context.createGain();
let bufferSource = context.createBufferSource();
bufferSource.buffer = constantBuffer;
bufferSource.connect(gainNode);
gainNode.connect(context.destination);
// Set up default values for the parameters that control how the automation
// test values progress for each time interval.
startingValueDelta = initialValue / numberOfTests;
startEndValueChange = startingValueDelta / 2;
discontinuityThreshold = startEndValueChange / 2;
// Run the automation tests.
timeValueInfo = doAutomation(
numberOfTests, initialValue, setValueFunction, automationFunction);
bufferSource.start(0);
context.oncomplete = checkResultFunction(
task, should, testName, maxError, referenceFunction, jumpThreshold,
errorMetric || relativeErrorMetric);
context.startRendering();
}
// Export local references to global scope. All the new objects in this file
// must be exported through this if it is to be used in the actual test HTML
// page.
let exports = {
'sampleRate': 44100,
'gainNode': null,
'timeInterval': timeIntervalInternal,
// Some suitable time constant so that we can see a significant change over
// a timeInterval. This is only needed by setTargetAtTime() which needs a
// time constant.
'timeConstant': timeIntervalInternal / 3,
'renderLength': renderLength,
'createConstantArray': createConstantArray,
'getStartEndFrames': getStartEndFrames,
'createLinearRampArray': createLinearRampArray,
'createExponentialRampArray': createExponentialRampArray,
'discreteTimeConstantForSampleRate': discreteTimeConstantForSampleRate,
'createExponentialApproachArray': createExponentialApproachArray,
'createReferenceSineArray': createReferenceSineArray,
'createSineWaveArray': createSineWaveArray,
'endValueDelta': endValueDelta,
'relativeErrorMetric': relativeErrorMetric,
'differenceErrorMetric': differenceErrorMetric,
'valueUpdate': valueUpdate,
'comparePartialSignals': comparePartialSignals,
'verifyDiscontinuities': verifyDiscontinuities,
'compareSignals': compareSignals,
'checkResultFunction': checkResultFunction,
'doAutomation': doAutomation,
'createAudioGraphAndTest': createAudioGraphAndTest
};
for (let reference in exports) {
global[reference] = exports[reference];
}
})(window);

View file

@ -0,0 +1,195 @@
// 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 This file includes legacy utility functions for the layout
* test.
*/
// How many frames in a WebAudio render quantum.
let RENDER_QUANTUM_FRAMES = 128;
// Compare two arrays (commonly extracted from buffer.getChannelData()) with
// constraints:
// options.thresholdSNR: Minimum allowed SNR between the actual and expected
// signal. The default value is 10000.
// options.thresholdDiffULP: Maximum allowed difference between the actual
// and expected signal in ULP(Unit in the last place). The default is 0.
// options.thresholdDiffCount: Maximum allowed number of sample differences
// which exceeds the threshold. The default is 0.
// options.bitDepth: The expected result is assumed to come from an audio
// file with this number of bits of precision. The default is 16.
function compareBuffersWithConstraints(should, actual, expected, options) {
if (!options)
options = {};
// Only print out the message if the lengths are different; the
// expectation is that they are the same, so don't clutter up the
// output.
if (actual.length !== expected.length) {
should(
actual.length === expected.length,
'Length of actual and expected buffers should match')
.beTrue();
}
let maxError = -1;
let diffCount = 0;
let errorPosition = -1;
let thresholdSNR = (options.thresholdSNR || 10000);
let thresholdDiffULP = (options.thresholdDiffULP || 0);
let thresholdDiffCount = (options.thresholdDiffCount || 0);
// By default, the bit depth is 16.
let bitDepth = (options.bitDepth || 16);
let scaleFactor = Math.pow(2, bitDepth - 1);
let noisePower = 0, signalPower = 0;
for (let i = 0; i < actual.length; i++) {
let diff = actual[i] - expected[i];
noisePower += diff * diff;
signalPower += expected[i] * expected[i];
if (Math.abs(diff) > maxError) {
maxError = Math.abs(diff);
errorPosition = i;
}
// The reference file is a 16-bit WAV file, so we will almost never get
// an exact match between it and the actual floating-point result.
if (Math.abs(diff) > scaleFactor)
diffCount++;
}
let snr = 10 * Math.log10(signalPower / noisePower);
let maxErrorULP = maxError * scaleFactor;
should(snr, 'SNR').beGreaterThanOrEqualTo(thresholdSNR);
should(
maxErrorULP,
options.prefix + ': Maximum difference (in ulp units (' + bitDepth +
'-bits))')
.beLessThanOrEqualTo(thresholdDiffULP);
should(diffCount, options.prefix + ': Number of differences between results')
.beLessThanOrEqualTo(thresholdDiffCount);
}
// Create an impulse in a buffer of length sampleFrameLength
function createImpulseBuffer(context, sampleFrameLength) {
let audioBuffer =
context.createBuffer(1, sampleFrameLength, context.sampleRate);
let n = audioBuffer.length;
let dataL = audioBuffer.getChannelData(0);
for (let k = 0; k < n; ++k) {
dataL[k] = 0;
}
dataL[0] = 1;
return audioBuffer;
}
// Create a buffer of the given length with a linear ramp having values 0 <= x <
// 1.
function createLinearRampBuffer(context, sampleFrameLength) {
let audioBuffer =
context.createBuffer(1, sampleFrameLength, context.sampleRate);
let n = audioBuffer.length;
let dataL = audioBuffer.getChannelData(0);
for (let i = 0; i < n; ++i)
dataL[i] = i / n;
return audioBuffer;
}
// Create an AudioBuffer of length |sampleFrameLength| having a constant value
// |constantValue|. If |constantValue| is a number, the buffer has one channel
// filled with that value. If |constantValue| is an array, the buffer is created
// wit a number of channels equal to the length of the array, and channel k is
// filled with the k'th element of the |constantValue| array.
function createConstantBuffer(context, sampleFrameLength, constantValue) {
let channels;
let values;
if (typeof constantValue === 'number') {
channels = 1;
values = [constantValue];
} else {
channels = constantValue.length;
values = constantValue;
}
let audioBuffer =
context.createBuffer(channels, sampleFrameLength, context.sampleRate);
let n = audioBuffer.length;
for (let c = 0; c < channels; ++c) {
let data = audioBuffer.getChannelData(c);
for (let i = 0; i < n; ++i)
data[i] = values[c];
}
return audioBuffer;
}
// Create a stereo impulse in a buffer of length sampleFrameLength
function createStereoImpulseBuffer(context, sampleFrameLength) {
let audioBuffer =
context.createBuffer(2, sampleFrameLength, context.sampleRate);
let n = audioBuffer.length;
let dataL = audioBuffer.getChannelData(0);
let dataR = audioBuffer.getChannelData(1);
for (let k = 0; k < n; ++k) {
dataL[k] = 0;
dataR[k] = 0;
}
dataL[0] = 1;
dataR[0] = 1;
return audioBuffer;
}
// Convert time (in seconds) to sample frames.
function timeToSampleFrame(time, sampleRate) {
return Math.floor(0.5 + time * sampleRate);
}
// Compute the number of sample frames consumed by noteGrainOn with
// the specified |grainOffset|, |duration|, and |sampleRate|.
function grainLengthInSampleFrames(grainOffset, duration, sampleRate) {
let startFrame = timeToSampleFrame(grainOffset, sampleRate);
let endFrame = timeToSampleFrame(grainOffset + duration, sampleRate);
return endFrame - startFrame;
}
// True if the number is not an infinity or NaN
function isValidNumber(x) {
return !isNaN(x) && (x != Infinity) && (x != -Infinity);
}
// Compute the (linear) signal-to-noise ratio between |actual| and
// |expected|. The result is NOT in dB! If the |actual| and
// |expected| have different lengths, the shorter length is used.
function computeSNR(actual, expected) {
let signalPower = 0;
let noisePower = 0;
let length = Math.min(actual.length, expected.length);
for (let k = 0; k < length; ++k) {
let diff = actual[k] - expected[k];
signalPower += expected[k] * expected[k];
noisePower += diff * diff;
}
return signalPower / noisePower;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,45 @@
// Test that exceptions are throw for invalid values for start and
// stop.
function testStartStop(should, node, options) {
// Test non-finite values for start. These should all throw a TypeError
const nonFiniteValues = [NaN, Infinity, -Infinity];
nonFiniteValues.forEach(time => {
should(() => {
node.start(time);
}, `start(${time})`)
.throw('TypeError');
});
should(() => {
node.stop();
}, 'Calling stop() before start()').throw('InvalidStateError');
should(() => {
node.start(-1);
}, 'start(-1)').throw('RangeError');
if (options) {
options.forEach(test => {
should(() => {node.start(...test.args)},
'start(' + test.args + ')').throw(test.errorType);
});
}
node.start();
should(() => {
node.start();
}, 'Calling start() twice').throw('InvalidStateError');
should(() => {
node.stop(-1);
}, 'stop(-1)').throw('RangeError');
// Test non-finite stop times
nonFiniteValues.forEach(time => {
should(() => {
node.stop(time);
}, `stop(${time})`)
.throw('TypeError');
});
}