summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/common/framework/params_builder.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/common/framework/params_builder.ts')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/framework/params_builder.ts337
1 files changed, 337 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/common/framework/params_builder.ts b/dom/webgpu/tests/cts/checkout/src/common/framework/params_builder.ts
new file mode 100644
index 0000000000..d22444a9b6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/common/framework/params_builder.ts
@@ -0,0 +1,337 @@
+import { Merged, mergeParams } from '../internal/params_utils.js';
+import { stringifyPublicParams } from '../internal/query/stringify_params.js';
+import { assert, mapLazy } from '../util/util.js';
+
+// ================================================================
+// "Public" ParamsBuilder API / Documentation
+// ================================================================
+
+/**
+ * Provides doc comments for the methods of CaseParamsBuilder and SubcaseParamsBuilder.
+ * (Also enforces rough interface match between them.)
+ */
+export interface ParamsBuilder {
+ /**
+ * Expands each item in `this` into zero or more items.
+ * Each item has its parameters expanded with those returned by the `expander`.
+ *
+ * **Note:** When only a single key is being added, use the simpler `expand` for readability.
+ *
+ * ```text
+ * this = [ a , b , c ]
+ * this.map(expander) = [ f(a) f(b) f(c) ]
+ * = [[a1, a2, a3] , [ b1 ] , [] ]
+ * merge and flatten = [ merge(a, a1), merge(a, a2), merge(a, a3), merge(b, b1) ]
+ * ```
+ */
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
+ expandWithParams(expander: (_: any) => any): any;
+
+ /**
+ * Expands each item in `this` into zero or more items. Each item has its parameters expanded
+ * with one new key, `key`, and the values returned by `expander`.
+ */
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
+ expand(key: string, expander: (_: any) => any): any;
+
+ /**
+ * Expands each item in `this` to multiple items, one for each item in `newParams`.
+ *
+ * In other words, takes the cartesian product of [ the items in `this` ] and `newParams`.
+ *
+ * **Note:** When only a single key is being added, use the simpler `combine` for readability.
+ *
+ * ```text
+ * this = [ {a:1}, {b:2} ]
+ * newParams = [ {x:1}, {y:2} ]
+ * this.combineP(newParams) = [ {a:1,x:1}, {a:1,y:2}, {b:2,x:1}, {b:2,y:2} ]
+ * ```
+ */
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
+ combineWithParams(newParams: Iterable<any>): any;
+
+ /**
+ * Expands each item in `this` to multiple items with `{ [name]: value }` for each value.
+ *
+ * In other words, takes the cartesian product of [ the items in `this` ]
+ * and `[ {[name]: value} for each value in values ]`
+ */
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
+ combine(key: string, newParams: Iterable<any>): any;
+
+ /**
+ * Filters `this` to only items for which `pred` returns true.
+ */
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
+ filter(pred: (_: any) => boolean): any;
+
+ /**
+ * Filters `this` to only items for which `pred` returns false.
+ */
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
+ unless(pred: (_: any) => boolean): any;
+}
+
+/**
+ * Determines the resulting parameter object type which would be generated by an object of
+ * the given ParamsBuilder type.
+ */
+export type ParamTypeOf<
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
+ T extends ParamsBuilder
+> = T extends SubcaseParamsBuilder<infer CaseP, infer SubcaseP>
+ ? Merged<CaseP, SubcaseP>
+ : T extends CaseParamsBuilder<infer CaseP>
+ ? CaseP
+ : never;
+
+// ================================================================
+// Implementation
+// ================================================================
+
+/**
+ * Iterable over pairs of either:
+ * - `[case params, Iterable<subcase params>]` if there are subcases.
+ * - `[case params, undefined]` if not.
+ */
+export type CaseSubcaseIterable<CaseP, SubcaseP> = Iterable<
+ readonly [CaseP, Iterable<SubcaseP> | undefined]
+>;
+
+/**
+ * Base class for `CaseParamsBuilder` and `SubcaseParamsBuilder`.
+ */
+export abstract class ParamsBuilderBase<CaseP extends {}, SubcaseP extends {}> {
+ protected readonly cases: () => Generator<CaseP>;
+
+ constructor(cases: () => Generator<CaseP>) {
+ this.cases = cases;
+ }
+
+ /**
+ * Hidden from test files. Use `builderIterateCasesWithSubcases` to access this.
+ */
+ protected abstract iterateCasesWithSubcases(): CaseSubcaseIterable<CaseP, SubcaseP>;
+}
+
+/**
+ * Calls the (normally hidden) `iterateCasesWithSubcases()` method.
+ */
+export function builderIterateCasesWithSubcases(builder: ParamsBuilderBase<{}, {}>) {
+ interface IterableParamsBuilder {
+ iterateCasesWithSubcases(): CaseSubcaseIterable<{}, {}>;
+ }
+
+ return ((builder as unknown) as IterableParamsBuilder).iterateCasesWithSubcases();
+}
+
+/**
+ * Builder for combinatorial test **case** parameters.
+ *
+ * CaseParamsBuilder is immutable. Each method call returns a new, immutable object,
+ * modifying the list of cases according to the method called.
+ *
+ * This means, for example, that the `unit` passed into `TestBuilder.params()` can be reused.
+ */
+export class CaseParamsBuilder<CaseP extends {}>
+ extends ParamsBuilderBase<CaseP, {}>
+ implements Iterable<CaseP>, ParamsBuilder {
+ *iterateCasesWithSubcases(): CaseSubcaseIterable<CaseP, {}> {
+ for (const a of this.cases()) {
+ yield [a, undefined];
+ }
+ }
+
+ [Symbol.iterator](): Iterator<CaseP> {
+ return this.cases();
+ }
+
+ /** @inheritDoc */
+ expandWithParams<NewP extends {}>(
+ expander: (_: Merged<{}, CaseP>) => Iterable<NewP>
+ ): CaseParamsBuilder<Merged<CaseP, NewP>> {
+ const newGenerator = expanderGenerator(this.cases, expander);
+ return new CaseParamsBuilder(() => newGenerator({}));
+ }
+
+ /** @inheritDoc */
+ expand<NewPKey extends string, NewPValue>(
+ key: NewPKey,
+ expander: (_: Merged<{}, CaseP>) => Iterable<NewPValue>
+ ): CaseParamsBuilder<Merged<CaseP, { [name in NewPKey]: NewPValue }>> {
+ return this.expandWithParams(function* (p) {
+ for (const value of expander(p)) {
+ yield { [key]: value } as { readonly [name in NewPKey]: NewPValue };
+ }
+ });
+ }
+
+ /** @inheritDoc */
+ combineWithParams<NewP extends {}>(
+ newParams: Iterable<NewP>
+ ): CaseParamsBuilder<Merged<CaseP, NewP>> {
+ assertNotGenerator(newParams);
+ const seenValues = new Set<string>();
+ for (const params of newParams) {
+ const paramsStr = stringifyPublicParams(params);
+ assert(!seenValues.has(paramsStr), `Duplicate entry in combine[WithParams]: ${paramsStr}`);
+ seenValues.add(paramsStr);
+ }
+
+ return this.expandWithParams(() => newParams);
+ }
+
+ /** @inheritDoc */
+ combine<NewPKey extends string, NewPValue>(
+ key: NewPKey,
+ values: Iterable<NewPValue>
+ ): CaseParamsBuilder<Merged<CaseP, { [name in NewPKey]: NewPValue }>> {
+ assertNotGenerator(values);
+ const mapped = mapLazy(values, v => ({ [key]: v } as { [name in NewPKey]: NewPValue }));
+ return this.combineWithParams(mapped);
+ }
+
+ /** @inheritDoc */
+ filter(pred: (_: Merged<{}, CaseP>) => boolean): CaseParamsBuilder<CaseP> {
+ const newGenerator = filterGenerator(this.cases, pred);
+ return new CaseParamsBuilder(() => newGenerator({}));
+ }
+
+ /** @inheritDoc */
+ unless(pred: (_: Merged<{}, CaseP>) => boolean): CaseParamsBuilder<CaseP> {
+ return this.filter(x => !pred(x));
+ }
+
+ /**
+ * "Finalize" the list of cases and begin defining subcases.
+ * Returns a new SubcaseParamsBuilder. Methods called on SubcaseParamsBuilder
+ * generate new subcases instead of new cases.
+ */
+ beginSubcases(): SubcaseParamsBuilder<CaseP, {}> {
+ return new SubcaseParamsBuilder(
+ () => this.cases(),
+ function* () {
+ yield {};
+ }
+ );
+ }
+}
+
+/**
+ * The unit CaseParamsBuilder, representing a single case with no params: `[ {} ]`.
+ *
+ * `punit` is passed to every `.params()`/`.paramsSubcasesOnly()` call, so `kUnitCaseParamsBuilder`
+ * is only explicitly needed if constructing a ParamsBuilder outside of a test builder.
+ */
+export const kUnitCaseParamsBuilder = new CaseParamsBuilder(function* () {
+ yield {};
+});
+
+/**
+ * Builder for combinatorial test _subcase_ parameters.
+ *
+ * SubcaseParamsBuilder is immutable. Each method call returns a new, immutable object,
+ * modifying the list of subcases according to the method called.
+ */
+export class SubcaseParamsBuilder<CaseP extends {}, SubcaseP extends {}>
+ extends ParamsBuilderBase<CaseP, SubcaseP>
+ implements ParamsBuilder {
+ protected readonly subcases: (_: CaseP) => Generator<SubcaseP>;
+
+ constructor(cases: () => Generator<CaseP>, generator: (_: CaseP) => Generator<SubcaseP>) {
+ super(cases);
+ this.subcases = generator;
+ }
+
+ *iterateCasesWithSubcases(): CaseSubcaseIterable<CaseP, SubcaseP> {
+ for (const caseP of this.cases()) {
+ const subcases = Array.from(this.subcases(caseP));
+ if (subcases.length) {
+ yield [caseP, subcases];
+ }
+ }
+ }
+
+ /** @inheritDoc */
+ expandWithParams<NewP extends {}>(
+ expander: (_: Merged<CaseP, SubcaseP>) => Iterable<NewP>
+ ): SubcaseParamsBuilder<CaseP, Merged<SubcaseP, NewP>> {
+ return new SubcaseParamsBuilder(this.cases, expanderGenerator(this.subcases, expander));
+ }
+
+ /** @inheritDoc */
+ expand<NewPKey extends string, NewPValue>(
+ key: NewPKey,
+ expander: (_: Merged<CaseP, SubcaseP>) => Iterable<NewPValue>
+ ): SubcaseParamsBuilder<CaseP, Merged<SubcaseP, { [name in NewPKey]: NewPValue }>> {
+ return this.expandWithParams(function* (p) {
+ for (const value of expander(p)) {
+ // TypeScript doesn't know here that NewPKey is always a single literal string type.
+ yield { [key]: value } as { [name in NewPKey]: NewPValue };
+ }
+ });
+ }
+
+ /** @inheritDoc */
+ combineWithParams<NewP extends {}>(
+ newParams: Iterable<NewP>
+ ): SubcaseParamsBuilder<CaseP, Merged<SubcaseP, NewP>> {
+ assertNotGenerator(newParams);
+ return this.expandWithParams(() => newParams);
+ }
+
+ /** @inheritDoc */
+ combine<NewPKey extends string, NewPValue>(
+ key: NewPKey,
+ values: Iterable<NewPValue>
+ ): SubcaseParamsBuilder<CaseP, Merged<SubcaseP, { [name in NewPKey]: NewPValue }>> {
+ assertNotGenerator(values);
+ return this.expand(key, () => values);
+ }
+
+ /** @inheritDoc */
+ filter(pred: (_: Merged<CaseP, SubcaseP>) => boolean): SubcaseParamsBuilder<CaseP, SubcaseP> {
+ return new SubcaseParamsBuilder(this.cases, filterGenerator(this.subcases, pred));
+ }
+
+ /** @inheritDoc */
+ unless(pred: (_: Merged<CaseP, SubcaseP>) => boolean): SubcaseParamsBuilder<CaseP, SubcaseP> {
+ return this.filter(x => !pred(x));
+ }
+}
+
+function expanderGenerator<Base, A, B>(
+ baseGenerator: (_: Base) => Generator<A>,
+ expander: (_: Merged<Base, A>) => Iterable<B>
+): (_: Base) => Generator<Merged<A, B>> {
+ return function* (base: Base) {
+ for (const a of baseGenerator(base)) {
+ for (const b of expander(mergeParams(base, a))) {
+ yield mergeParams(a, b);
+ }
+ }
+ };
+}
+
+function filterGenerator<Base, A>(
+ baseGenerator: (_: Base) => Generator<A>,
+ pred: (_: Merged<Base, A>) => boolean
+): (_: Base) => Generator<A> {
+ return function* (base: Base) {
+ for (const a of baseGenerator(base)) {
+ if (pred(mergeParams(base, a))) {
+ yield a;
+ }
+ }
+ };
+}
+
+/** Assert an object is not a Generator (a thing returned from a generator function). */
+function assertNotGenerator(x: object) {
+ if ('constructor' in x) {
+ assert(
+ x.constructor !== (function* () {})().constructor,
+ 'Argument must not be a generator, as generators are not reusable'
+ );
+ }
+}