import { IterableTestGroup } from '../internal/test_group.js'; import { assert } from '../util/util.js'; import { parseQuery } from './query/parseQuery.js'; import { TestQuery } from './query/query.js'; import { TestSuiteListing } from './test_suite_listing.js'; import { loadTreeForQuery, TestTree, TestTreeLeaf } from './tree.js'; // A listing file, e.g. either of: // - `src/webgpu/listing.ts` (which is dynamically computed, has a Promise) // - `out/webgpu/listing.js` (which is pre-baked, has a TestSuiteListing) interface ListingFile { listing: Promise | TestSuiteListing; } // A .spec.ts file, as imported. export interface SpecFile { readonly description: string; readonly g: IterableTestGroup; } export interface ImportInfo { url: string; } interface TestFileLoaderEventMap { import: MessageEvent; imported: MessageEvent; finish: MessageEvent; } // Override the types for addEventListener/removeEventListener so the callbacks can be used as // strongly-typed. /* eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging */ export interface TestFileLoader extends EventTarget { addEventListener( type: K, listener: (this: TestFileLoader, ev: TestFileLoaderEventMap[K]) => void, options?: boolean | AddEventListenerOptions ): void; addEventListener( type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions ): void; removeEventListener( type: K, listener: (this: TestFileLoader, ev: TestFileLoaderEventMap[K]) => void, options?: boolean | EventListenerOptions ): void; removeEventListener( type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions ): void; } // Base class for DefaultTestFileLoader and FakeTestFileLoader. /* eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging */ export abstract class TestFileLoader extends EventTarget { abstract listing(suite: string): Promise; protected abstract import(path: string): Promise; async importSpecFile(suite: string, path: string[]): Promise { const url = `${suite}/${path.join('/')}.spec.js`; this.dispatchEvent(new MessageEvent('import', { data: { url } })); const ret = await this.import(url); this.dispatchEvent(new MessageEvent('imported', { data: { url } })); return ret; } async loadTree( query: TestQuery, { subqueriesToExpand = [], fullyExpandSubtrees = [], maxChunkTime = Infinity, }: { subqueriesToExpand?: string[]; fullyExpandSubtrees?: string[]; maxChunkTime?: number } = {} ): Promise { const tree = await loadTreeForQuery(this, query, { subqueriesToExpand: subqueriesToExpand.map(s => { const q = parseQuery(s); assert(q.level >= 2, () => `subqueriesToExpand entries should not be multi-file:\n ${q}`); return q; }), fullyExpandSubtrees: fullyExpandSubtrees.map(s => parseQuery(s)), maxChunkTime, }); this.dispatchEvent(new MessageEvent('finish')); return tree; } async loadCases(query: TestQuery): Promise> { const tree = await this.loadTree(query); return tree.iterateLeaves(); } } export class DefaultTestFileLoader extends TestFileLoader { async listing(suite: string): Promise { return ((await import(`../../${suite}/listing.js`)) as ListingFile).listing; } import(path: string): Promise { return import(`../../${path}`); } }