diff options
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/common/util')
9 files changed, 840 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/common/util/collect_garbage.ts b/dom/webgpu/tests/cts/checkout/src/common/util/collect_garbage.ts new file mode 100644 index 0000000000..670028d41c --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/common/util/collect_garbage.ts @@ -0,0 +1,58 @@ +import { resolveOnTimeout } from './util.js'; + +/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ +declare const Components: any; + +/** + * Attempts to trigger JavaScript garbage collection, either using explicit methods if exposed + * (may be available in testing environments with special browser runtime flags set), or using + * some weird tricks to incur GC pressure. Adopted from the WebGL CTS. + */ +export async function attemptGarbageCollection(): Promise<void> { + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + const w: any = globalThis; + if (w.GCController) { + w.GCController.collect(); + return; + } + + if (w.opera && w.opera.collect) { + w.opera.collect(); + return; + } + + try { + w.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils) + .garbageCollect(); + return; + } catch (e) { + // ignore any failure + } + + if (w.gc) { + w.gc(); + return; + } + + if (w.CollectGarbage) { + w.CollectGarbage(); + return; + } + + let i: number; + function gcRec(n: number): void { + if (n < 1) return; + /* eslint-disable @typescript-eslint/restrict-plus-operands */ + let temp: object | string = { i: 'ab' + i + i / 100000 }; + /* eslint-disable @typescript-eslint/restrict-plus-operands */ + temp = temp + 'foo'; + temp; // dummy use of unused variable + gcRec(n - 1); + } + for (i = 0; i < 1000; i++) { + gcRec(10); + } + + return resolveOnTimeout(35); // Let the event loop run a few frames in case it helps. +} diff --git a/dom/webgpu/tests/cts/checkout/src/common/util/colors.ts b/dom/webgpu/tests/cts/checkout/src/common/util/colors.ts new file mode 100644 index 0000000000..709d159320 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/common/util/colors.ts @@ -0,0 +1,127 @@ +/** + * The interface used for formatting strings to contain color metadata. + * + * Use the interface properties to construct a style, then use the + * `(s: string): string` function to format the provided string with the given + * style. + */ +export interface Colors { + // Are colors enabled? + enabled: boolean; + + // Returns the string formatted to contain the specified color or style. + (s: string): string; + + // modifiers + reset: Colors; + bold: Colors; + dim: Colors; + italic: Colors; + underline: Colors; + inverse: Colors; + hidden: Colors; + strikethrough: Colors; + + // colors + black: Colors; + red: Colors; + green: Colors; + yellow: Colors; + blue: Colors; + magenta: Colors; + cyan: Colors; + white: Colors; + gray: Colors; + grey: Colors; + + // bright colors + blackBright: Colors; + redBright: Colors; + greenBright: Colors; + yellowBright: Colors; + blueBright: Colors; + magentaBright: Colors; + cyanBright: Colors; + whiteBright: Colors; + + // background colors + bgBlack: Colors; + bgRed: Colors; + bgGreen: Colors; + bgYellow: Colors; + bgBlue: Colors; + bgMagenta: Colors; + bgCyan: Colors; + bgWhite: Colors; + + // bright background colors + bgBlackBright: Colors; + bgRedBright: Colors; + bgGreenBright: Colors; + bgYellowBright: Colors; + bgBlueBright: Colors; + bgMagentaBright: Colors; + bgCyanBright: Colors; + bgWhiteBright: Colors; +} + +/** + * The interface used for formatting strings with color metadata. + * + * Currently Colors will use the 'ansi-colors' module if it can be loaded. + * If it cannot be loaded, then the Colors implementation is a straight pass-through. + * + * Colors may also be a no-op if the current environment does not support colors. + */ +export let Colors: Colors; + +try { + /* eslint-disable-next-line node/no-unpublished-require */ + Colors = require('ansi-colors') as Colors; +} catch { + const passthrough = ((s: string) => s) as Colors; + passthrough.enabled = false; + passthrough.reset = passthrough; + passthrough.bold = passthrough; + passthrough.dim = passthrough; + passthrough.italic = passthrough; + passthrough.underline = passthrough; + passthrough.inverse = passthrough; + passthrough.hidden = passthrough; + passthrough.strikethrough = passthrough; + passthrough.black = passthrough; + passthrough.red = passthrough; + passthrough.green = passthrough; + passthrough.yellow = passthrough; + passthrough.blue = passthrough; + passthrough.magenta = passthrough; + passthrough.cyan = passthrough; + passthrough.white = passthrough; + passthrough.gray = passthrough; + passthrough.grey = passthrough; + passthrough.blackBright = passthrough; + passthrough.redBright = passthrough; + passthrough.greenBright = passthrough; + passthrough.yellowBright = passthrough; + passthrough.blueBright = passthrough; + passthrough.magentaBright = passthrough; + passthrough.cyanBright = passthrough; + passthrough.whiteBright = passthrough; + passthrough.bgBlack = passthrough; + passthrough.bgRed = passthrough; + passthrough.bgGreen = passthrough; + passthrough.bgYellow = passthrough; + passthrough.bgBlue = passthrough; + passthrough.bgMagenta = passthrough; + passthrough.bgCyan = passthrough; + passthrough.bgWhite = passthrough; + passthrough.bgBlackBright = passthrough; + passthrough.bgRedBright = passthrough; + passthrough.bgGreenBright = passthrough; + passthrough.bgYellowBright = passthrough; + passthrough.bgBlueBright = passthrough; + passthrough.bgMagentaBright = passthrough; + passthrough.bgCyanBright = passthrough; + passthrough.bgWhiteBright = passthrough; + Colors = passthrough; +} diff --git a/dom/webgpu/tests/cts/checkout/src/common/util/data_tables.ts b/dom/webgpu/tests/cts/checkout/src/common/util/data_tables.ts new file mode 100644 index 0000000000..7f1be2f701 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/common/util/data_tables.ts @@ -0,0 +1,39 @@ +import { ResolveType, ZipKeysWithValues } from './types.js'; + +export type valueof<K> = K[keyof K]; + +export function keysOf<T extends string>(obj: { [k in T]: unknown }): readonly T[] { + return (Object.keys(obj) as unknown[]) as T[]; +} + +export function numericKeysOf<T>(obj: object): readonly T[] { + return (Object.keys(obj).map(n => Number(n)) as unknown[]) as T[]; +} + +/** + * Creates an info lookup object from a more nicely-formatted table. See below for examples. + * + * Note: Using `as const` on the arguments to this function is necessary to infer the correct type. + */ +export function makeTable< + Members extends readonly string[], + Defaults extends readonly unknown[], + Table extends { readonly [k: string]: readonly unknown[] } +>( + members: Members, + defaults: Defaults, + table: Table +): { + readonly [k in keyof Table]: ResolveType<ZipKeysWithValues<Members, Table[k], Defaults>>; +} { + const result: { [k: string]: { [m: string]: unknown } } = {}; + for (const [k, v] of Object.entries<readonly unknown[]>(table)) { + const item: { [m: string]: unknown } = {}; + for (let i = 0; i < members.length; ++i) { + item[members[i]] = v[i] ?? defaults[i]; + } + result[k] = item; + } + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + return result as any; +} diff --git a/dom/webgpu/tests/cts/checkout/src/common/util/navigator_gpu.ts b/dom/webgpu/tests/cts/checkout/src/common/util/navigator_gpu.ts new file mode 100644 index 0000000000..47cb1a4701 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/common/util/navigator_gpu.ts @@ -0,0 +1,74 @@ +/// <reference types="@webgpu/types" /> + +import { assert } from './util.js'; + +/** + * Finds and returns the `navigator.gpu` object (or equivalent, for non-browser implementations). + * Throws an exception if not found. + */ +function defaultGPUProvider(): GPU { + assert( + typeof navigator !== 'undefined' && navigator.gpu !== undefined, + 'No WebGPU implementation found' + ); + return navigator.gpu; +} + +/** + * GPUProvider is a function that creates and returns a new GPU instance. + * May throw an exception if a GPU cannot be created. + */ +export type GPUProvider = () => GPU; + +let gpuProvider: GPUProvider = defaultGPUProvider; + +/** + * Sets the function to create and return a new GPU instance. + */ +export function setGPUProvider(provider: GPUProvider) { + assert(impl === undefined, 'setGPUProvider() should not be after getGPU()'); + gpuProvider = provider; +} + +let impl: GPU | undefined = undefined; + +let defaultRequestAdapterOptions: GPURequestAdapterOptions | undefined; + +export function setDefaultRequestAdapterOptions(options: GPURequestAdapterOptions) { + if (impl) { + throw new Error('must call setDefaultRequestAdapterOptions before getGPU'); + } + defaultRequestAdapterOptions = { ...options }; +} + +/** + * Finds and returns the `navigator.gpu` object (or equivalent, for non-browser implementations). + * Throws an exception if not found. + */ +export function getGPU(): GPU { + if (impl) { + return impl; + } + + impl = gpuProvider(); + + if (defaultRequestAdapterOptions) { + // eslint-disable-next-line @typescript-eslint/unbound-method + const oldFn = impl.requestAdapter; + impl.requestAdapter = function ( + options?: GPURequestAdapterOptions + ): Promise<GPUAdapter | null> { + const promise = oldFn.call(this, { ...defaultRequestAdapterOptions, ...(options || {}) }); + void promise.then(async adapter => { + if (adapter) { + const info = await adapter.requestAdapterInfo(); + // eslint-disable-next-line no-console + console.log(info); + } + }); + return promise; + }; + } + + return impl; +} diff --git a/dom/webgpu/tests/cts/checkout/src/common/util/preprocessor.ts b/dom/webgpu/tests/cts/checkout/src/common/util/preprocessor.ts new file mode 100644 index 0000000000..7dc2822498 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/common/util/preprocessor.ts @@ -0,0 +1,149 @@ +import { assert } from './util.js'; + +// The state of the preprocessor is a stack of States. +type StateStack = { allowsFollowingElse: boolean; state: State }[]; +const enum State { + Seeking, // Still looking for a passing condition + Passing, // Currently inside a passing condition (the root is always in this state) + Skipping, // Have already seen a passing condition; now skipping the rest +} + +// The transitions in the state space are the following preprocessor directives: +// - Sibling elif +// - Sibling else +// - Sibling endif +// - Child if +abstract class Directive { + private readonly depth: number; + + constructor(depth: number) { + this.depth = depth; + } + + protected checkDepth(stack: StateStack): void { + assert( + stack.length === this.depth, + `Number of "$"s must match nesting depth, currently ${stack.length} (e.g. $if $$if $$endif $endif)` + ); + } + + abstract applyTo(stack: StateStack): void; +} + +class If extends Directive { + private readonly predicate: boolean; + + constructor(depth: number, predicate: boolean) { + super(depth); + this.predicate = predicate; + } + + applyTo(stack: StateStack) { + this.checkDepth(stack); + const parentState = stack[stack.length - 1].state; + stack.push({ + allowsFollowingElse: true, + state: + parentState !== State.Passing + ? State.Skipping + : this.predicate + ? State.Passing + : State.Seeking, + }); + } +} + +class ElseIf extends If { + applyTo(stack: StateStack) { + assert(stack.length >= 1); + const { allowsFollowingElse, state: siblingState } = stack.pop()!; + this.checkDepth(stack); + assert(allowsFollowingElse, 'pp.elif after pp.else'); + if (siblingState !== State.Seeking) { + stack.push({ allowsFollowingElse: true, state: State.Skipping }); + } else { + super.applyTo(stack); + } + } +} + +class Else extends Directive { + applyTo(stack: StateStack) { + assert(stack.length >= 1); + const { allowsFollowingElse, state: siblingState } = stack.pop()!; + this.checkDepth(stack); + assert(allowsFollowingElse, 'pp.else after pp.else'); + stack.push({ + allowsFollowingElse: false, + state: siblingState === State.Seeking ? State.Passing : State.Skipping, + }); + } +} + +class EndIf extends Directive { + applyTo(stack: StateStack) { + stack.pop(); + this.checkDepth(stack); + } +} + +/** + * A simple template-based, non-line-based preprocessor implementing if/elif/else/endif. + * + * @example + * ``` + * const shader = pp` + * ${pp._if(expr)} + * const x: ${type} = ${value}; + * ${pp._elif(expr)} + * ${pp.__if(expr)} + * ... + * ${pp.__else} + * ... + * ${pp.__endif} + * ${pp._endif}`; + * ``` + * + * @param strings - The array of constant string chunks of the template string. + * @param ...values - The array of interpolated `${}` values within the template string. + */ +export function pp( + strings: TemplateStringsArray, + ...values: ReadonlyArray<Directive | string | number> +): string { + let result = ''; + const stateStack: StateStack = [{ allowsFollowingElse: false, state: State.Passing }]; + + for (let i = 0; i < values.length; ++i) { + const passing = stateStack[stateStack.length - 1].state === State.Passing; + if (passing) { + result += strings[i]; + } + + const value = values[i]; + if (value instanceof Directive) { + value.applyTo(stateStack); + } else { + if (passing) { + result += value; + } + } + } + assert(stateStack.length === 1, 'Unterminated preprocessor condition at end of file'); + result += strings[values.length]; + + return result; +} +pp._if = (predicate: boolean) => new If(1, predicate); +pp._elif = (predicate: boolean) => new ElseIf(1, predicate); +pp._else = new Else(1); +pp._endif = new EndIf(1); +pp.__if = (predicate: boolean) => new If(2, predicate); +pp.__elif = (predicate: boolean) => new ElseIf(2, predicate); +pp.__else = new Else(2); +pp.__endif = new EndIf(2); +pp.___if = (predicate: boolean) => new If(3, predicate); +pp.___elif = (predicate: boolean) => new ElseIf(3, predicate); +pp.___else = new Else(3); +pp.___endif = new EndIf(3); +// Add more if needed. diff --git a/dom/webgpu/tests/cts/checkout/src/common/util/timeout.ts b/dom/webgpu/tests/cts/checkout/src/common/util/timeout.ts new file mode 100644 index 0000000000..13c3b7fb90 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/common/util/timeout.ts @@ -0,0 +1,7 @@ +/** Defined by WPT. Like `setTimeout`, but applies a timeout multiplier for slow test systems. */ +declare const step_timeout: undefined | typeof setTimeout; + +/** + * Equivalent of `setTimeout`, but redirects to WPT's `step_timeout` when it is defined. + */ +export const timeout = typeof step_timeout !== 'undefined' ? step_timeout : setTimeout; diff --git a/dom/webgpu/tests/cts/checkout/src/common/util/types.ts b/dom/webgpu/tests/cts/checkout/src/common/util/types.ts new file mode 100644 index 0000000000..dfd5e4b5ea --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/common/util/types.ts @@ -0,0 +1,59 @@ +/** Forces a type to resolve its type definitions, to make it readable/debuggable. */ +export type ResolveType<T> = T extends object + ? T extends infer O + ? { [K in keyof O]: ResolveType<O[K]> } + : never + : T; + +/** Returns the type `true` iff X and Y are exactly equal */ +export type TypeEqual<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 + ? true + : false; + +/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ +export function assertTypeTrue<T extends true>() {} + +/** + * Computes the intersection of a set of types, given the union of those types. + * + * From: https://stackoverflow.com/a/56375136 + */ +export type UnionToIntersection<U> = + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; + +/** "Type asserts" that `X` is a subtype of `Y`. */ +type EnsureSubtype<X, Y> = X extends Y ? X : never; + +type TupleHeadOr<T, Default> = T extends readonly [infer H, ...(readonly unknown[])] ? H : Default; +type TupleTailOr<T, Default> = T extends readonly [unknown, ...infer Tail] ? Tail : Default; +type TypeOr<T, Default> = T extends undefined ? Default : T; + +/** + * Zips a key tuple type and a value tuple type together into an object. + * + * @template Keys Keys of the resulting object. + * @template Values Values of the resulting object. If a key corresponds to a `Values` member that + * is undefined or past the end, it defaults to the corresponding `Defaults` member. + * @template Defaults Default values. If a key corresponds to a `Defaults` member that is past the + * end, the default falls back to `undefined`. + */ +export type ZipKeysWithValues< + Keys extends readonly string[], + Values extends readonly unknown[], + Defaults extends readonly unknown[] +> = + // + Keys extends readonly [infer KHead, ...infer KTail] + ? { + readonly [k in EnsureSubtype<KHead, string>]: TypeOr< + TupleHeadOr<Values, undefined>, + TupleHeadOr<Defaults, undefined> + >; + } & + ZipKeysWithValues< + EnsureSubtype<KTail, readonly string[]>, + TupleTailOr<Values, []>, + TupleTailOr<Defaults, []> + > + : {}; // K exhausted diff --git a/dom/webgpu/tests/cts/checkout/src/common/util/util.ts b/dom/webgpu/tests/cts/checkout/src/common/util/util.ts new file mode 100644 index 0000000000..f775c3c634 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/common/util/util.ts @@ -0,0 +1,303 @@ +import { Float16Array } from '../../external/petamoriken/float16/float16.js'; +import { globalTestConfig } from '../framework/test_config.js'; +import { Logger } from '../internal/logging/logger.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 { + readonly extra: { [k: string]: unknown }; + + /** + * `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(message: string, extra: () => {}); + constructor(base: ErrorWithExtra, newExtra: () => {}); + constructor(baseOrMessage: string | ErrorWithExtra, newExtra: () => {}) { + const message = typeof baseOrMessage === 'string' ? baseOrMessage : baseOrMessage.message; + super(message); + + const oldExtras = baseOrMessage instanceof ErrorWithExtra ? baseOrMessage.extra : {}; + this.extra = Logger.globalDebugMode + ? { ...oldExtras, ...newExtra() } + : { omitted: 'pass ?debug=1' }; + } +} + +/** + * Asserts `condition` is true. Otherwise, throws an `Error` with the provided message. + */ +export function assert(condition: boolean, msg?: string | (() => string)): asserts condition { + 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<T>(value: Error | T): T { + if (value instanceof Error) { + throw value; + } + return value; +} + +/** + * Resolves if the provided promise rejects; rejects if it does not. + */ +export async function assertReject(p: Promise<unknown>, msg?: string): Promise<void> { + try { + await p; + unreachable(msg); + } catch (ex) { + // Assertion OK + } +} + +/** + * Assert this code is unreachable. Unconditionally throws an `Error`. + */ +export function unreachable(msg?: string): never { + throw new Error(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(): number { + return perf.now(); +} + +/** + * Returns a promise which resolves after the specified time. + */ +export function resolveOnTimeout(ms: number): Promise<void> { + 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: number, msg: string): Promise<never> { + 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<T>(p: Promise<T>, ms: number, msg: string): Promise<T> { + 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]) as Promise<T>; +} + +/** + * 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: Promise<unknown>, + ms: number, + msg: string +): Promise<undefined> { + // 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<undefined>(resolve => { + const handle = timeout(() => { + resolve(undefined); + }, ms); + 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<T>(err: unknown): Promise<T> { + const p = Promise.reject(err); + // Suppress uncaught promise rejection. + p.catch(() => {}); + return p; +} + +/** + * Makes a copy of a JS `object`, with the keys reordered into sorted order. + */ +export function sortObjectByKey(v: { [k: string]: unknown }): { [k: string]: unknown } { + const sortedObject: { [k: string]: unknown } = {}; + 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)`. + */ +export function objectEquals(x: unknown, y: unknown): boolean { + if (typeof x !== 'object' || typeof y !== 'object') { + if (typeof x === 'number' && typeof y === 'number' && Number.isNaN(x) && Number.isNaN(y)) { + return true; + } + return 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 as { [k: string]: unknown }; + const y1 = y as { [k: string]: unknown }; + 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<T>(n: number, fn: (i: number) => T): T[] { + return [...new Array(n)].map((_, i) => fn(i)); +} + +/** + * Generates a range of values `fn(0)..fn(n-1)`. + */ +export function* iterRange<T>(n: number, fn: (i: number) => T): Iterable<T> { + for (let i = 0; i < n; ++i) { + yield fn(i); + } +} + +/** Creates a (reusable) iterable object that maps `f` over `xs`, lazily. */ +export function mapLazy<T, R>(xs: Iterable<T>, f: (x: T) => R): Iterable<R> { + return { + *[Symbol.iterator]() { + for (const x of xs) { + yield f(x); + } + }, + }; +} + +const TypedArrayBufferViewInstances = [ + new Uint8Array(), + new Uint8ClampedArray(), + new Uint16Array(), + new Uint32Array(), + new Int8Array(), + new Int16Array(), + new Int32Array(), + new Float16Array(), + new Float32Array(), + new Float64Array(), +] as const; + +export type TypedArrayBufferView = typeof TypedArrayBufferViewInstances[number]; + +export type TypedArrayBufferViewConstructor< + A extends TypedArrayBufferView = TypedArrayBufferView +> = { + // Interface copied from Uint8Array, and made generic. + readonly prototype: A; + readonly BYTES_PER_ELEMENT: number; + + new (): A; + new (elements: Iterable<number>): A; + new (array: ArrayLike<number> | ArrayBufferLike): A; + new (buffer: ArrayBufferLike, byteOffset?: number, length?: number): A; + new (length: number): A; + + from(arrayLike: ArrayLike<number>): A; + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + from(arrayLike: Iterable<number>, mapfn?: (v: number, k: number) => number, thisArg?: any): A; + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + from<T>(arrayLike: ArrayLike<T>, mapfn: (v: T, k: number) => number, thisArg?: any): A; + of(...items: number[]): A; +}; + +export const kTypedArrayBufferViews: { + readonly [k: string]: TypedArrayBufferViewConstructor; +} = { + ...(() => { + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + const result: { [k: string]: any } = {}; + for (const v of TypedArrayBufferViewInstances) { + result[v.constructor.name] = v.constructor; + } + return result; + })(), +}; +export const kTypedArrayBufferViewKeys = keysOf(kTypedArrayBufferViews); +export const kTypedArrayBufferViewConstructors = Object.values(kTypedArrayBufferViews); + +function subarrayAsU8( + buf: ArrayBuffer | TypedArrayBufferView, + { start = 0, length }: { start?: number; length?: number } +): Uint8Array | Uint8ClampedArray { + 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: { src: ArrayBuffer | TypedArrayBufferView; start?: number; length?: number }, + dst: { dst: ArrayBuffer | TypedArrayBufferView; start?: number } +): void { + subarrayAsU8(dst.dst, dst).set(subarrayAsU8(src.src, src)); +} diff --git a/dom/webgpu/tests/cts/checkout/src/common/util/wpt_reftest_wait.ts b/dom/webgpu/tests/cts/checkout/src/common/util/wpt_reftest_wait.ts new file mode 100644 index 0000000000..7d10520bcb --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/common/util/wpt_reftest_wait.ts @@ -0,0 +1,24 @@ +import { timeout } from './timeout.js'; + +// Copied from https://github.com/web-platform-tests/wpt/blob/master/common/reftest-wait.js + +/** + * Remove the `reftest-wait` class on the document element. + * The reftest runner will wait with taking a screenshot while + * this class is present. + * + * See https://web-platform-tests.org/writing-tests/reftests.html#controlling-when-comparison-occurs + */ +export function takeScreenshot() { + document.documentElement.classList.remove('reftest-wait'); +} + +/** + * Call `takeScreenshot()` after a delay of at least `ms` milliseconds. + * @param {number} ms - milliseconds + */ +export function takeScreenshotDelayed(ms: number) { + timeout(() => { + takeScreenshot(); + }, ms); +} |