diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /dom/webgpu/tests/cts/checkout/src/common/framework/params_builder.ts | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
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.ts | 389 |
1 files changed, 389 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..09a7d9c320 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/common/framework/params_builder.ts @@ -0,0 +1,389 @@ +import { Merged, mergeParams, mergeParamsChecked } from '../internal/params_utils.js'; +import { comparePublicParamsPaths, Ordering } from '../internal/query/compare.js'; +import { stringifyPublicParams } from '../internal/query/stringify_params.js'; +import { DeepReadonly } from '../util/types.js'; +import { assert, mapLazy, objectEquals } from '../util/util.js'; + +import { TestParams } from './fixture.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 [DeepReadonly<CaseP>, Iterable<DeepReadonly<SubcaseP>> | undefined] +>; + +/** + * Base class for `CaseParamsBuilder` and `SubcaseParamsBuilder`. + */ +export abstract class ParamsBuilderBase<CaseP extends {}, SubcaseP extends {}> { + protected readonly cases: (caseFilter: TestParams | null) => Generator<CaseP>; + + constructor(cases: (caseFilter: TestParams | null) => Generator<CaseP>) { + this.cases = cases; + } + + /** + * Hidden from test files. Use `builderIterateCasesWithSubcases` to access this. + */ + protected abstract iterateCasesWithSubcases( + caseFilter: TestParams | null + ): CaseSubcaseIterable<CaseP, SubcaseP>; +} + +/** + * Calls the (normally hidden) `iterateCasesWithSubcases()` method. + */ +export function builderIterateCasesWithSubcases( + builder: ParamsBuilderBase<{}, {}>, + caseFilter: TestParams | null +) { + interface IterableParamsBuilder { + iterateCasesWithSubcases(caseFilter: TestParams | null): CaseSubcaseIterable<{}, {}>; + } + + return (builder as unknown as IterableParamsBuilder).iterateCasesWithSubcases(caseFilter); +} + +/** + * 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<DeepReadonly<CaseP>>, ParamsBuilder +{ + *iterateCasesWithSubcases(caseFilter: TestParams | null): CaseSubcaseIterable<CaseP, {}> { + for (const caseP of this.cases(caseFilter)) { + if (caseFilter) { + // this.cases() only filters out cases which conflict with caseFilter. Now that we have + // the final caseP, filter out cases which are missing keys that caseFilter requires. + const ordering = comparePublicParamsPaths(caseP, caseFilter); + if (ordering === Ordering.StrictSuperset || ordering === Ordering.Unordered) { + continue; + } + } + + yield [caseP as DeepReadonly<typeof caseP>, undefined]; + } + } + + [Symbol.iterator](): Iterator<DeepReadonly<CaseP>> { + return this.cases(null) as Iterator<DeepReadonly<CaseP>>; + } + + /** @inheritDoc */ + expandWithParams<NewP extends {}>( + expander: (_: CaseP) => Iterable<NewP> + ): CaseParamsBuilder<Merged<CaseP, NewP>> { + const baseGenerator = this.cases; + return new CaseParamsBuilder(function* (caseFilter) { + for (const a of baseGenerator(caseFilter)) { + for (const b of expander(a)) { + if (caseFilter) { + // If the expander generated any key-value pair that conflicts with caseFilter, skip. + const kvPairs = Object.entries(b); + if (kvPairs.some(([k, v]) => k in caseFilter && !objectEquals(caseFilter[k], v))) { + continue; + } + } + + yield mergeParamsChecked(a, b); + } + } + }); + } + + /** @inheritDoc */ + expand<NewPKey extends string, NewPValue>( + key: NewPKey, + expander: (_: CaseP) => Iterable<NewPValue> + ): CaseParamsBuilder<Merged<CaseP, { [name in NewPKey]: NewPValue }>> { + const baseGenerator = this.cases; + return new CaseParamsBuilder(function* (caseFilter) { + for (const a of baseGenerator(caseFilter)) { + assert(!(key in a), `New key '${key}' already exists in ${JSON.stringify(a)}`); + + for (const v of expander(a)) { + // If the expander generated a value for this key that conflicts with caseFilter, skip. + if (caseFilter && key in caseFilter) { + if (!objectEquals(caseFilter[key], v)) { + continue; + } + } + yield { ...a, [key]: v } as Merged<CaseP, { [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: (_: CaseP) => boolean): CaseParamsBuilder<CaseP> { + const baseGenerator = this.cases; + return new CaseParamsBuilder(function* (caseFilter) { + for (const a of baseGenerator(caseFilter)) { + if (pred(a)) yield a; + } + }); + } + + /** @inheritDoc */ + unless(pred: (_: 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: (caseFilter: TestParams | null) => Generator<CaseP>, + generator: (_: CaseP) => Generator<SubcaseP> + ) { + super(cases); + this.subcases = generator; + } + + *iterateCasesWithSubcases(caseFilter: TestParams | null): CaseSubcaseIterable<CaseP, SubcaseP> { + for (const caseP of this.cases(caseFilter)) { + if (caseFilter) { + // this.cases() only filters out cases which conflict with caseFilter. Now that we have + // the final caseP, filter out cases which are missing keys that caseFilter requires. + const ordering = comparePublicParamsPaths(caseP, caseFilter); + if (ordering === Ordering.StrictSuperset || ordering === Ordering.Unordered) { + continue; + } + } + + const subcases = Array.from(this.subcases(caseP)); + if (subcases.length) { + yield [ + caseP as DeepReadonly<typeof caseP>, + subcases as DeepReadonly<(typeof subcases)[number]>[], + ]; + } + } + } + + /** @inheritDoc */ + expandWithParams<NewP extends {}>( + expander: (_: Merged<CaseP, SubcaseP>) => Iterable<NewP> + ): SubcaseParamsBuilder<CaseP, Merged<SubcaseP, NewP>> { + const baseGenerator = this.subcases; + return new SubcaseParamsBuilder(this.cases, function* (base) { + for (const a of baseGenerator(base)) { + for (const b of expander(mergeParams(base, a))) { + yield mergeParamsChecked(a, b); + } + } + }); + } + + /** @inheritDoc */ + expand<NewPKey extends string, NewPValue>( + key: NewPKey, + expander: (_: Merged<CaseP, SubcaseP>) => Iterable<NewPValue> + ): SubcaseParamsBuilder<CaseP, Merged<SubcaseP, { [name in NewPKey]: NewPValue }>> { + const baseGenerator = this.subcases; + return new SubcaseParamsBuilder(this.cases, function* (base) { + for (const a of baseGenerator(base)) { + const before = mergeParams(base, a); + assert(!(key in before), () => `Key '${key}' already exists in ${JSON.stringify(before)}`); + + for (const v of expander(before)) { + yield { ...a, [key]: v } as Merged<SubcaseP, { [k 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> { + const baseGenerator = this.subcases; + return new SubcaseParamsBuilder(this.cases, function* (base) { + for (const a of baseGenerator(base)) { + if (pred(mergeParams(base, a))) yield a; + } + }); + } + + /** @inheritDoc */ + unless(pred: (_: Merged<CaseP, SubcaseP>) => boolean): SubcaseParamsBuilder<CaseP, SubcaseP> { + return this.filter(x => !pred(x)); + } +} + +/** 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' + ); + } +} |