540 lines
13 KiB
JavaScript
540 lines
13 KiB
JavaScript
/**
|
|
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
|
|
**/import { Float16Array } from '../../external/petamoriken/float16/float16.js';import { SkipTestCase } from '../framework/fixture.js';import { globalTestConfig } from '../framework/test_config.js';
|
|
|
|
import { keysOf } from './data_tables.js';
|
|
import { timeout } from './timeout.js';
|
|
|
|
/**
|
|
* Error with arbitrary `extra` data attached, for debugging.
|
|
* The extra data is omitted if not running the test in debug mode (`?debug=1`).
|
|
*/
|
|
export class ErrorWithExtra extends Error {
|
|
|
|
|
|
/**
|
|
* `extra` function is only called if in debug mode.
|
|
* If an `ErrorWithExtra` is passed, its message is used and its extras are passed through.
|
|
*/
|
|
|
|
|
|
constructor(baseOrMessage, newExtra) {
|
|
const message = typeof baseOrMessage === 'string' ? baseOrMessage : baseOrMessage.message;
|
|
super(message);
|
|
|
|
const oldExtras = baseOrMessage instanceof ErrorWithExtra ? baseOrMessage.extra : {};
|
|
this.extra = globalTestConfig.enableDebugLogs ?
|
|
{ ...oldExtras, ...newExtra() } :
|
|
{ omitted: 'pass ?debug=1' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Asserts `condition` is true. Otherwise, throws an `Error` with the provided message.
|
|
*/
|
|
export function assert(condition, msg) {
|
|
if (!condition) {
|
|
throw new Error(msg && (typeof msg === 'string' ? msg : msg()));
|
|
}
|
|
}
|
|
|
|
/** If the argument is an Error, throw it. Otherwise, pass it back. */
|
|
export function assertOK(value) {
|
|
if (value instanceof Error) {
|
|
throw value;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
/** Options for assertReject, shouldReject, and friends. */
|
|
|
|
|
|
/**
|
|
* Resolves if the provided promise rejects; rejects if it does not.
|
|
*/
|
|
export async function assertReject(
|
|
expectedName,
|
|
p,
|
|
{ allowMissingStack = false, message } = {})
|
|
{
|
|
await p.then(
|
|
() => {
|
|
unreachable(message);
|
|
},
|
|
(ex) => {
|
|
assert(ex instanceof Error, 'rejected with a non-Error object');
|
|
assert(ex.name === expectedName, `rejected with name ${ex.name} instead of ${expectedName}`);
|
|
// Asserted as expected
|
|
if (!allowMissingStack) {
|
|
const m = message ? ` (${message})` : '';
|
|
assert(typeof ex.stack === 'string', 'threw as expected, but missing stack' + m);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Assert this code is unreachable. Unconditionally throws an `Error`.
|
|
*/
|
|
export function unreachable(msg) {
|
|
throw new Error(msg);
|
|
}
|
|
|
|
/**
|
|
* Throw a `SkipTestCase` exception, which skips the test case.
|
|
*/
|
|
export function skipTestCase(msg) {
|
|
throw new SkipTestCase(msg);
|
|
}
|
|
|
|
/**
|
|
* The `performance` interface.
|
|
* It is available in all browsers, but it is not in scope by default in Node.
|
|
*/
|
|
|
|
const perf = typeof performance !== 'undefined' ? performance : require('perf_hooks').performance;
|
|
|
|
/**
|
|
* Calls the appropriate `performance.now()` depending on whether running in a browser or Node.
|
|
*/
|
|
export function now() {
|
|
return perf.now();
|
|
}
|
|
|
|
/**
|
|
* Returns a promise which resolves after the specified time.
|
|
*/
|
|
export function resolveOnTimeout(ms) {
|
|
return new Promise((resolve) => {
|
|
timeout(() => {
|
|
resolve();
|
|
}, ms);
|
|
});
|
|
}
|
|
|
|
export class PromiseTimeoutError extends Error {}
|
|
|
|
/**
|
|
* Returns a promise which rejects after the specified time.
|
|
*/
|
|
export function rejectOnTimeout(ms, msg) {
|
|
return new Promise((_resolve, reject) => {
|
|
timeout(() => {
|
|
reject(new PromiseTimeoutError(msg));
|
|
}, ms);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Takes a promise `p`, and returns a new one which rejects if `p` takes too long,
|
|
* and otherwise passes the result through.
|
|
*/
|
|
export function raceWithRejectOnTimeout(p, ms, msg) {
|
|
if (globalTestConfig.noRaceWithRejectOnTimeout) {
|
|
return p;
|
|
}
|
|
// Setup a promise that will reject after `ms` milliseconds. We cancel this timeout when
|
|
// `p` is finalized, so the JavaScript VM doesn't hang around waiting for the timer to
|
|
// complete, once the test runner has finished executing the tests.
|
|
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
const handle = timeout(() => {
|
|
reject(new PromiseTimeoutError(msg));
|
|
}, ms);
|
|
p = p.finally(() => clearTimeout(handle));
|
|
});
|
|
return Promise.race([p, timeoutPromise]);
|
|
}
|
|
|
|
/**
|
|
* Takes a promise `p` and returns a new one which rejects if `p` resolves or rejects,
|
|
* and otherwise resolves after the specified time.
|
|
*/
|
|
export function assertNotSettledWithinTime(
|
|
p,
|
|
ms,
|
|
msg)
|
|
{
|
|
// Rejects regardless of whether p resolves or rejects.
|
|
const rejectWhenSettled = p.then(() => Promise.reject(new Error(msg)));
|
|
// Resolves after `ms` milliseconds.
|
|
const timeoutPromise = new Promise((resolve) => {
|
|
const handle = timeout(() => {
|
|
resolve(undefined);
|
|
}, ms);
|
|
void p.finally(() => clearTimeout(handle));
|
|
});
|
|
return Promise.race([rejectWhenSettled, timeoutPromise]);
|
|
}
|
|
|
|
/**
|
|
* Returns a `Promise.reject()`, but also registers a dummy `.catch()` handler so it doesn't count
|
|
* as an uncaught promise rejection in the runtime.
|
|
*/
|
|
export function rejectWithoutUncaught(err) {
|
|
const p = Promise.reject(err);
|
|
// Suppress uncaught promise rejection.
|
|
p.catch(() => {});
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Returns true if v is a plain JavaScript object.
|
|
*/
|
|
export function isPlainObject(v) {
|
|
return !!v && Object.getPrototypeOf(v).constructor === Object.prototype.constructor;
|
|
}
|
|
|
|
/**
|
|
* Makes a copy of a JS `object`, with the keys reordered into sorted order.
|
|
*/
|
|
export function sortObjectByKey(v) {
|
|
const sortedObject = {};
|
|
for (const k of Object.keys(v).sort()) {
|
|
sortedObject[k] = v[k];
|
|
}
|
|
return sortedObject;
|
|
}
|
|
|
|
/**
|
|
* Determines whether two JS values are equal, recursing into objects and arrays.
|
|
* NaN is treated specially, such that `objectEquals(NaN, NaN)`. +/-0.0 are treated as equal
|
|
* by default, but can be opted to be distinguished.
|
|
* @param x the first JS values that get compared
|
|
* @param y the second JS values that get compared
|
|
* @param distinguishSignedZero if set to true, treat 0.0 and -0.0 as unequal. Default to false.
|
|
*/
|
|
export function objectEquals(
|
|
x,
|
|
y,
|
|
distinguishSignedZero = false)
|
|
{
|
|
if (typeof x !== 'object' || typeof y !== 'object') {
|
|
if (typeof x === 'number' && typeof y === 'number' && Number.isNaN(x) && Number.isNaN(y)) {
|
|
return true;
|
|
}
|
|
// Object.is(0.0, -0.0) is false while (0.0 === -0.0) is true. Other than +/-0.0 and NaN cases,
|
|
// Object.is works in the same way as ===.
|
|
return distinguishSignedZero ? Object.is(x, y) : x === y;
|
|
}
|
|
if (x === null || y === null) return x === y;
|
|
if (x.constructor !== y.constructor) return false;
|
|
if (x instanceof Function) return x === y;
|
|
if (x instanceof RegExp) return x === y;
|
|
if (x === y || x.valueOf() === y.valueOf()) return true;
|
|
if (Array.isArray(x) && Array.isArray(y) && x.length !== y.length) return false;
|
|
if (x instanceof Date) return false;
|
|
if (!(x instanceof Object)) return false;
|
|
if (!(y instanceof Object)) return false;
|
|
|
|
const x1 = x;
|
|
const y1 = y;
|
|
const p = Object.keys(x);
|
|
return Object.keys(y).every((i) => p.indexOf(i) !== -1) && p.every((i) => objectEquals(x1[i], y1[i]));
|
|
}
|
|
|
|
/**
|
|
* Generates a range of values `fn(0)..fn(n-1)`.
|
|
*/
|
|
export function range(n, fn) {
|
|
return [...new Array(n)].map((_, i) => fn(i));
|
|
}
|
|
|
|
/**
|
|
* Generates a range of values `fn(0)..fn(n-1)`.
|
|
*/
|
|
export function* iterRange(n, fn) {
|
|
for (let i = 0; i < n; ++i) {
|
|
yield fn(i);
|
|
}
|
|
}
|
|
|
|
/** Creates a (reusable) iterable object that maps `f` over `xs`, lazily. */
|
|
export function mapLazy(xs, f) {
|
|
return {
|
|
*[Symbol.iterator]() {
|
|
for (const x of xs) {
|
|
yield f(x);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/** Count the number of elements `x` for which `predicate(x)` is true. */
|
|
export function count(xs, predicate) {
|
|
let count = 0;
|
|
for (const x of xs) {
|
|
if (predicate(x)) count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
const ReorderOrders = {
|
|
forward: true,
|
|
backward: true,
|
|
shiftByHalf: true
|
|
};
|
|
|
|
export const kReorderOrderKeys = keysOf(ReorderOrders);
|
|
|
|
/**
|
|
* Creates a new array from the given array with the first half
|
|
* swapped with the last half.
|
|
*/
|
|
export function shiftByHalf(arr) {
|
|
const len = arr.length;
|
|
const half = len / 2 | 0;
|
|
const firstHalf = arr.splice(0, half);
|
|
return [...arr, ...firstHalf];
|
|
}
|
|
|
|
/**
|
|
* Creates a reordered array from the input array based on the Order
|
|
*/
|
|
export function reorder(order, arr) {
|
|
switch (order) {
|
|
case 'forward':
|
|
return arr.slice();
|
|
case 'backward':
|
|
return arr.slice().reverse();
|
|
case 'shiftByHalf':{
|
|
// should this be pseudo random?
|
|
return shiftByHalf(arr);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A typed version of Object.entries
|
|
*/
|
|
|
|
export function typedEntries(obj) {
|
|
// The cast is done once, inside the helper function,
|
|
// keeping the call site clean and type-safe.
|
|
return Object.entries(obj);
|
|
}
|
|
|
|
const TypedArrayBufferViewInstances = [
|
|
new Uint8Array(),
|
|
new Uint8ClampedArray(),
|
|
new Uint16Array(),
|
|
new Uint32Array(),
|
|
new Int8Array(),
|
|
new Int16Array(),
|
|
new Int32Array(),
|
|
new Float16Array(),
|
|
new Float32Array(),
|
|
new Float64Array(),
|
|
new BigInt64Array(),
|
|
new BigUint64Array()];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const kTypedArrayBufferViews =
|
|
|
|
{
|
|
...(() => {
|
|
|
|
const result = {};
|
|
for (const v of TypedArrayBufferViewInstances) {
|
|
result[v.constructor.name] = v.constructor;
|
|
}
|
|
return result;
|
|
})()
|
|
};
|
|
export const kTypedArrayBufferViewKeys = keysOf(kTypedArrayBufferViews);
|
|
export const kTypedArrayBufferViewConstructors = Object.values(kTypedArrayBufferViews);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Creates a case parameter for a typedarray.
|
|
*
|
|
* You can't put typedarrays in case parameters directly so instead of
|
|
*
|
|
* ```
|
|
* u.combine('data', [
|
|
* new Uint8Array([1, 2, 3]),
|
|
* new Float32Array([4, 5, 6]),
|
|
* ])
|
|
* ```
|
|
*
|
|
* You can use
|
|
*
|
|
* ```
|
|
* u.combine('data', [
|
|
* typedArrayParam('Uint8Array' [1, 2, 3]),
|
|
* typedArrayParam('Float32Array' [4, 5, 6]),
|
|
* ])
|
|
* ```
|
|
*
|
|
* and then convert the params to typedarrays eg.
|
|
*
|
|
* ```
|
|
* .fn(t => {
|
|
* const data = t.params.data.map(v => typedArrayFromParam(v));
|
|
* })
|
|
* ```
|
|
*/
|
|
export function typedArrayParam(
|
|
type,
|
|
data)
|
|
{
|
|
return { type, data };
|
|
}
|
|
|
|
export function createTypedArray(
|
|
type,
|
|
data)
|
|
{
|
|
return new kTypedArrayBufferViews[type](data);
|
|
}
|
|
|
|
/**
|
|
* Converts a TypedArrayParam to a typedarray. See typedArrayParam
|
|
*/
|
|
export function typedArrayFromParam(
|
|
param)
|
|
{
|
|
const { type, data } = param;
|
|
return createTypedArray(type, data);
|
|
}
|
|
|
|
function subarrayAsU8(
|
|
buf,
|
|
{ start = 0, length })
|
|
{
|
|
if (buf instanceof ArrayBuffer) {
|
|
return new Uint8Array(buf, start, length);
|
|
} else if (buf instanceof Uint8Array || buf instanceof Uint8ClampedArray) {
|
|
// Don't wrap in new views if we don't need to.
|
|
if (start === 0 && (length === undefined || length === buf.byteLength)) {
|
|
return buf;
|
|
}
|
|
}
|
|
const byteOffset = buf.byteOffset + start * buf.BYTES_PER_ELEMENT;
|
|
const byteLength =
|
|
length !== undefined ?
|
|
length * buf.BYTES_PER_ELEMENT :
|
|
buf.byteLength - (byteOffset - buf.byteOffset);
|
|
return new Uint8Array(buf.buffer, byteOffset, byteLength);
|
|
}
|
|
|
|
/**
|
|
* Copy a range of bytes from one ArrayBuffer or TypedArray to another.
|
|
*
|
|
* `start`/`length` are in elements (or in bytes, if ArrayBuffer).
|
|
*/
|
|
export function memcpy(
|
|
src,
|
|
dst)
|
|
{
|
|
subarrayAsU8(dst.dst, dst).set(subarrayAsU8(src.src, src));
|
|
}
|
|
|
|
/**
|
|
* Used to create a value that is specified by multiplying some runtime value
|
|
* by a constant and then adding a constant to it.
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Filters out SpecValues that are the same.
|
|
*/
|
|
export function filterUniqueValueTestVariants(valueTestVariants) {
|
|
return new Map(
|
|
valueTestVariants.map((v) => [`m:${v.mult},a:${v.add}`, v])
|
|
).values();
|
|
}
|
|
|
|
/**
|
|
* Used to create a value that is specified by multiplied some runtime value
|
|
* by a constant and then adding a constant to it. This happens often in test
|
|
* with limits that can only be known at runtime and yet we need a way to
|
|
* add parameters to a test and those parameters must be constants.
|
|
*/
|
|
export function makeValueTestVariant(base, variant) {
|
|
return base * variant.mult + variant.add;
|
|
}
|
|
|
|
/**
|
|
* Use instead of features.has because feature's has takes any string
|
|
* and we want to prevent typos.
|
|
*/
|
|
export function hasFeature(features, feature) {
|
|
|
|
return features.has(feature);
|
|
}
|
|
|
|
/** Convenience helper for combinations of 1-2 usage bits from a list of usage bits. */
|
|
export function combinationsOfOneOrTwoUsages(usages) {
|
|
const combinations = [];
|
|
for (const usage0 of usages) {
|
|
for (const usage1 of usages) {
|
|
if (usage0 <= usage1) {
|
|
combinations.push(usage0 | usage1);
|
|
}
|
|
}
|
|
}
|
|
return combinations;
|
|
}
|
|
|
|
/**
|
|
* Checks if the browser supports immediate data (experimental).
|
|
*
|
|
* Checks for:
|
|
* - `setImmediates` method on `GPURenderPassEncoder`, `GPUComputePassEncoder`, or `GPURenderBundleEncoder` prototypes.
|
|
* - `maxImmediateSize` property on `GPUSupportedLimits` prototype.
|
|
* - `immediate_address_space` feature in `gpu.wgslLanguageFeatures`.
|
|
*
|
|
* This helper is used to skip tests when the environment does not support immediate data functionality.
|
|
*/
|
|
export function supportsImmediateData(gpu) {
|
|
return (
|
|
'setImmediates' in GPURenderPassEncoder.prototype ||
|
|
'setImmediates' in GPUComputePassEncoder.prototype ||
|
|
'setImmediates' in GPURenderBundleEncoder.prototype ||
|
|
'maxImmediateSize' in GPUSupportedLimits.prototype ||
|
|
gpu.wgslLanguageFeatures.has('immediate_address_space'));
|
|
|
|
} |