summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/util/check_contents.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/util/check_contents.ts')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/check_contents.ts245
1 files changed, 245 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/check_contents.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/check_contents.ts
new file mode 100644
index 0000000000..325cae41a2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/check_contents.ts
@@ -0,0 +1,245 @@
+// MAINTENANCE_TODO: The "checkThingTrue" naming is confusing; these must be used with `expectOK`
+// or the result is dropped on the floor. Rename these to things like `typedArrayIsOK`(??) to
+// make it clearer.
+// MAINTENANCE_TODO: Also, audit to make sure we aren't dropping any on the floor. Consider a
+// no-ignored-return lint check if we can find one that we can use.
+
+import {
+ assert,
+ ErrorWithExtra,
+ iterRange,
+ range,
+ TypedArrayBufferView,
+ TypedArrayBufferViewConstructor,
+} from '../../common/util/util.js';
+
+import { float16BitsToFloat32 } from './conversion.js';
+import { generatePrettyTable } from './pretty_diff_tables.js';
+
+/** Generate an expected value at `index`, to test for equality with the actual value. */
+export type CheckElementsGenerator = (index: number) => number;
+/** Check whether the actual `value` at `index` is as expected. */
+export type CheckElementsPredicate = (index: number, value: number) => boolean;
+/**
+ * Provides a pretty-printing implementation for a particular CheckElementsPredicate.
+ * This is an array; each element provides info to print an additional row in the error message.
+ */
+export type CheckElementsSupplementalTableRows = Array<{
+ /** Row header. */
+ leftHeader: string;
+ /**
+ * Get the value for a cell in the table with element index `index`.
+ * May be a string or a number; a number will be formatted according to the TypedArray type used.
+ */
+ getValueForCell: (index: number) => number | string;
+}>;
+
+/**
+ * Check whether two `TypedArray`s have equal contents.
+ * Returns `undefined` if the check passes, or an `Error` if not.
+ */
+export function checkElementsEqual(
+ actual: TypedArrayBufferView,
+ expected: TypedArrayBufferView
+): ErrorWithExtra | undefined {
+ assert(actual.constructor === expected.constructor, 'TypedArray type mismatch');
+ assert(actual.length === expected.length, 'size mismatch');
+ return checkElementsEqualGenerated(actual, i => expected[i]);
+}
+
+/**
+ * Check whether each value in a `TypedArray` is between the two corresponding "expected" values
+ * (either `a(i) <= actual[i] <= b(i)` or `a(i) >= actual[i] => b(i)`).
+ */
+export function checkElementsBetween(
+ actual: TypedArrayBufferView,
+ expected: readonly [CheckElementsGenerator, CheckElementsGenerator]
+): ErrorWithExtra | undefined {
+ const error = checkElementsPassPredicate(
+ actual,
+ (index, value) =>
+ value >= Math.min(expected[0](index), expected[1](index)) &&
+ value <= Math.max(expected[0](index), expected[1](index)),
+ {
+ predicatePrinter: [
+ { leftHeader: 'between', getValueForCell: index => expected[0](index) },
+ { leftHeader: 'and', getValueForCell: index => expected[1](index) },
+ ],
+ }
+ );
+ // If there was an error, extend it with additional extras.
+ return error ? new ErrorWithExtra(error, () => ({ expected })) : undefined;
+}
+
+/**
+ * Equivalent to {@link checkElementsBetween} but interpret values as float16 and convert to JS number before comparison.
+ */
+export function checkElementsFloat16Between(
+ actual: TypedArrayBufferView,
+ expected: readonly [TypedArrayBufferView, TypedArrayBufferView]
+): ErrorWithExtra | undefined {
+ assert(actual.BYTES_PER_ELEMENT === 2, 'bytes per element need to be 2 (16bit)');
+ const actualF32 = new Float32Array(actual.length);
+ actual.forEach((v: number, i: number) => {
+ actualF32[i] = float16BitsToFloat32(v);
+ });
+ const expectedF32 = [new Float32Array(expected[0].length), new Float32Array(expected[1].length)];
+ expected[0].forEach((v: number, i: number) => {
+ expectedF32[0][i] = float16BitsToFloat32(v);
+ });
+ expected[1].forEach((v: number, i: number) => {
+ expectedF32[1][i] = float16BitsToFloat32(v);
+ });
+
+ const error = checkElementsPassPredicate(
+ actualF32,
+ (index, value) =>
+ value >= Math.min(expectedF32[0][index], expectedF32[1][index]) &&
+ value <= Math.max(expectedF32[0][index], expectedF32[1][index]),
+ {
+ predicatePrinter: [
+ { leftHeader: 'between', getValueForCell: index => expectedF32[0][index] },
+ { leftHeader: 'and', getValueForCell: index => expectedF32[1][index] },
+ ],
+ }
+ );
+ // If there was an error, extend it with additional extras.
+ return error ? new ErrorWithExtra(error, () => ({ expectedF32 })) : undefined;
+}
+
+/**
+ * Check whether each value in a `TypedArray` is equal to one of the two corresponding "expected"
+ * values (either `actual[i] === a[i]` or `actual[i] === b[i]`)
+ */
+export function checkElementsEqualEither(
+ actual: TypedArrayBufferView,
+ expected: readonly [TypedArrayBufferView, TypedArrayBufferView]
+): ErrorWithExtra | undefined {
+ const error = checkElementsPassPredicate(
+ actual,
+ (index, value) => value === expected[0][index] || value === expected[1][index],
+ {
+ predicatePrinter: [
+ { leftHeader: 'either', getValueForCell: index => expected[0][index] },
+ { leftHeader: 'or', getValueForCell: index => expected[1][index] },
+ ],
+ }
+ );
+ // If there was an error, extend it with additional extras.
+ return error ? new ErrorWithExtra(error, () => ({ expected })) : undefined;
+}
+
+/**
+ * Check whether a `TypedArray`'s contents equal the values produced by a generator function.
+ * Returns `undefined` if the check passes, or an `Error` if not.
+ *
+ * ```text
+ * Array had unexpected contents at indices 2 through 19.
+ * Starting at index 1:
+ * actual == 0x: 00 fe ff 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00
+ * failed -> xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
+ * expected == 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * ```
+ *
+ * ```text
+ * Array had unexpected contents at indices 2 through 29.
+ * Starting at index 1:
+ * actual == 0.000 -2.000e+100 -1.000e+100 0.000 1.000e+100 2.000e+100 3.000e+100 4.000e+100 5.000e+100 6.000e+100 7.000e+100 ...
+ * failed -> xx xx xx xx xx xx xx xx xx ...
+ * expected == 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 ...
+ * ```
+ */
+export function checkElementsEqualGenerated(
+ actual: TypedArrayBufferView,
+ generator: CheckElementsGenerator
+): ErrorWithExtra | undefined {
+ const error = checkElementsPassPredicate(actual, (index, value) => value === generator(index), {
+ predicatePrinter: [{ leftHeader: 'expected ==', getValueForCell: index => generator(index) }],
+ });
+ // If there was an error, extend it with additional extras.
+ return error ? new ErrorWithExtra(error, () => ({ generator })) : undefined;
+}
+
+/**
+ * Check whether a `TypedArray`'s values pass the provided predicate function.
+ * Returns `undefined` if the check passes, or an `Error` if not.
+ */
+export function checkElementsPassPredicate(
+ actual: TypedArrayBufferView,
+ predicate: CheckElementsPredicate,
+ { predicatePrinter }: { predicatePrinter?: CheckElementsSupplementalTableRows }
+): ErrorWithExtra | undefined {
+ const size = actual.length;
+ const ctor = actual.constructor as TypedArrayBufferViewConstructor;
+ const printAsFloat = ctor === Float32Array || ctor === Float64Array;
+
+ let failedElementsFirstMaybe: number | undefined = undefined;
+ /** Sparse array with `true` for elements that failed. */
+ const failedElements: (true | undefined)[] = [];
+ for (let i = 0; i < size; ++i) {
+ if (!predicate(i, actual[i])) {
+ failedElementsFirstMaybe ??= i;
+ failedElements[i] = true;
+ }
+ }
+
+ if (failedElementsFirstMaybe === undefined) {
+ return undefined;
+ }
+ const failedElementsFirst = failedElementsFirstMaybe;
+ const failedElementsLast = failedElements.length - 1;
+
+ // Include one extra non-failed element at the beginning and end (if they exist), for context.
+ const printElementsStart = Math.max(0, failedElementsFirst - 1);
+ const printElementsEnd = Math.min(size, failedElementsLast + 2);
+ const printElementsCount = printElementsEnd - printElementsStart;
+
+ const numberToString = printAsFloat
+ ? (n: number) => n.toPrecision(4)
+ : (n: number) => intToPaddedHex(n, { byteLength: ctor.BYTES_PER_ELEMENT });
+ const numberPrefix = printAsFloat ? '' : '0x:';
+
+ const printActual = actual.subarray(printElementsStart, printElementsEnd);
+ const printExpected: Array<Iterable<string | number>> = [];
+ if (predicatePrinter) {
+ for (const { leftHeader, getValueForCell: cell } of predicatePrinter) {
+ printExpected.push(
+ (function* () {
+ yield* [leftHeader, ''];
+ yield* iterRange(printElementsCount, i => cell(printElementsStart + i));
+ })()
+ );
+ }
+ }
+
+ const printFailedValueMarkers = (function* () {
+ yield* ['failed ->', ''];
+ yield* range(printElementsCount, i => (failedElements[printElementsStart + i] ? 'xx' : ''));
+ })();
+
+ const opts = {
+ fillToWidth: 120,
+ numberToString,
+ };
+ const msg = `Array had unexpected contents at indices ${failedElementsFirst} through ${failedElementsLast}.
+ Starting at index ${printElementsStart}:
+${generatePrettyTable(opts, [
+ ['actual ==', numberPrefix, ...printActual],
+ printFailedValueMarkers,
+ ...printExpected,
+])}`;
+ return new ErrorWithExtra(msg, () => ({
+ actual: actual.slice(),
+ }));
+}
+
+// Helper helpers
+
+/** Convert an integral `number` into a hex string, padded to the specified `byteLength`. */
+function intToPaddedHex(number: number, { byteLength }: { byteLength: number }) {
+ assert(Number.isInteger(number), 'number must be integer');
+ let s = Math.abs(number).toString(16);
+ if (byteLength) s = s.padStart(byteLength * 2, '0');
+ if (number < 0) s = '-' + s;
+ return s;
+}