import { Fixture } from '../common/framework/fixture.js';
import { getGPU } from '../common/util/navigator_gpu.js';
import { assert, raceWithRejectOnTimeout } from '../common/util/util.js';
/**
* A test class to help test error scopes and uncapturederror events.
*/
export class ErrorTest extends Fixture {
_device: GPUDevice | undefined = undefined;
get device(): GPUDevice {
assert(this._device !== undefined);
return this._device;
}
override async init(): Promise<void> {
await super.init();
const gpu = getGPU(this.rec);
const adapter = await gpu.requestAdapter();
assert(adapter !== null);
// We need to max out the adapter limits related to texture dimensions to more reliably cause an
// OOM error when asked for it, so set that on the device now.
const device = await this.requestDeviceTracked(adapter, {
requiredLimits: {
maxTextureDimension2D: adapter.limits.maxTextureDimension2D,
maxTextureArrayLayers: adapter.limits.maxTextureArrayLayers,
},
});
assert(device !== null);
this._device = device;
}
/**
* Generates an error of the given filter type. For now, the errors are generated by calling a
* known code-path to cause the error. This can be updated in the future should there be a more
* direct way to inject errors.
*/
generateError(filter: GPUErrorFilter): void {
switch (filter) {
case 'out-of-memory':
this.trackForCleanup(
this.device.createTexture({
// One of the largest formats. With the base limits, the texture will be 256 GiB.
format: 'rgba32float',
usage: GPUTextureUsage.COPY_DST,
size: [
this.device.limits.maxTextureDimension2D,
this.device.limits.maxTextureDimension2D,
this.device.limits.maxTextureArrayLayers,
],
})
);
break;
case 'validation':
// Generating a validation error by passing in an invalid usage when creating a buffer.
this.trackForCleanup(
this.device.createBuffer({
size: 1024,
usage: 0xffff, // Invalid GPUBufferUsage
})
);
break;
}
// MAINTENANCE_TODO: This is a workaround for Chromium not flushing. Remove when not needed.
this.device.queue.submit([]);
}
/**
* Checks whether the error is of the type expected given the filter.
*/
isInstanceOfError(filter: GPUErrorFilter, error: GPUError | null): boolean {
switch (filter) {
case 'out-of-memory':
return error instanceof GPUOutOfMemoryError;
case 'validation':
return error instanceof GPUValidationError;
case 'internal':
return error instanceof GPUInternalError;
}
}
/**
* Expect an uncapturederror event to occur. Note: this MUST be awaited, because
* otherwise it could erroneously pass by capturing an error from later in the test.
*/
async expectUncapturedError(fn: Function): Promise<GPUUncapturedErrorEvent> {
return this.immediateAsyncExpectation(() => {
// MAINTENANCE_TODO: Make arbitrary timeout value a test runner variable
const TIMEOUT_IN_MS = 1000;
const promise: Promise<GPUUncapturedErrorEvent> = new Promise(resolve => {
const eventListener = ((event: GPUUncapturedErrorEvent) => {
this.debug(`Got uncaptured error event with ${event.error}`);
resolve(event);
}) as EventListener;
this.device.addEventListener('uncapturederror', eventListener, { once: true });
});
fn();
return raceWithRejectOnTimeout(
promise,
TIMEOUT_IN_MS,
'Timeout occurred waiting for uncaptured error'
);
});
}
}
[ Dauer der Verarbeitung: 0.3 Sekunden
(vorverarbeitet)
]