diff options
Diffstat (limited to 'testing/web-platform/mozilla/tests/webgpu/common')
24 files changed, 624 insertions, 104 deletions
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/framework/fixture.js b/testing/web-platform/mozilla/tests/webgpu/common/framework/fixture.js index d64245f5f8..149226a6e2 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/framework/fixture.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/framework/fixture.js @@ -17,6 +17,7 @@ export { TestCaseRecorder } from '../internal/logging/test_case_recorder.js'; + export class SubcaseBatchState { constructor( recorder, @@ -124,8 +125,12 @@ export class Fixture { if (WEBGL_lose_context) WEBGL_lose_context.loseContext(); } else if ('destroy' in o) { o.destroy(); - } else { + } else if ('close' in o) { o.close(); + } else { + // HTMLVideoElement + o.src = ''; + o.srcObject = null; } } } @@ -161,6 +166,14 @@ export class Fixture { this.rec.debug(new Error(msg)); } + /** + * Log an info message. + * **Use sparingly. Use `debug()` instead if logs are only needed with debug logging enabled.** + */ + info(msg) { + this.rec.info(new Error(msg)); + } + /** Throws an exception marking the subcase as skipped. */ skip(msg) { throw new SkipTestCase(msg); diff --git a/testing/web-platform/mozilla/tests/webgpu/common/framework/test_config.js b/testing/web-platform/mozilla/tests/webgpu/common/framework/test_config.js index 81984dbec5..5d8a59217f 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/framework/test_config.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/framework/test_config.js @@ -23,10 +23,28 @@ + + + + + + + + + + + + + + + export const globalTestConfig = { + enableDebugLogs: false, maxSubcasesInFlight: 500, testHeartbeatCallback: () => {}, noRaceWithRejectOnTimeout: false, unrollConstEvalLoops: false, - compatibility: false + compatibility: false, + forceFallbackAdapter: false, + logToWebSocket: false };
\ No newline at end of file diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/file_loader.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/file_loader.js index f9f4f17fb4..3956108027 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/internal/file_loader.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/file_loader.js @@ -73,6 +73,7 @@ export class TestFileLoader extends EventTarget { query, { subqueriesToExpand = [], + fullyExpandSubtrees = [], maxChunkTime = Infinity } = {}) { @@ -82,6 +83,7 @@ export class TestFileLoader extends EventTarget { 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')); diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/log_message.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/log_message.js index 234b7c2cc9..53d2cd4b9c 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/log_message.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/log_message.js @@ -1,19 +1,36 @@ /** * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/import { extractImportantStackTrace } from '../stack.js'; + + export class LogMessageWithStack extends Error { stackHiddenMessage = undefined; - constructor(name, ex) { - super(ex.message); + /** + * Wrap an Error (which was created to capture the stack at that point) into a + * LogMessageWithStack (which has extra stuff for good log messages). + * + * The original `ex.name` is ignored. Inclued it in the `name` parameter if it + * needs to be preserved. + */ + static wrapError(name, ex) { + return new LogMessageWithStack({ + name, + message: ex.message, + stackHiddenMessage: undefined, + stack: ex.stack, + extra: 'extra' in ex ? ex.extra : undefined + }); + } - this.name = name; - this.stack = ex.stack; - if ('extra' in ex) { - this.extra = ex.extra; - } + constructor(o) { + super(o.message); + this.name = o.name; + this.stackHiddenMessage = o.stackHiddenMessage; + this.stack = o.stack; + this.extra = o.extra; } /** Set a flag so the stack is not printed in toJSON(). */ @@ -21,6 +38,11 @@ export class LogMessageWithStack extends Error { this.stackHiddenMessage ??= stackHiddenMessage; } + /** + * Print the message for display. + * + * Note: This is toJSON instead of toString to make it easy to save logs using JSON.stringify. + */ toJSON() { let m = this.name; if (this.message) m += ': ' + this.message; @@ -33,6 +55,21 @@ export class LogMessageWithStack extends Error { } return m; } + + /** + * Flatten the message for sending over a message channel. + * + * Note `extra` may get mangled by postMessage. + */ + toRawData() { + return { + name: this.name, + message: this.message, + stackHiddenMessage: this.stackHiddenMessage, + stack: this.stack, + extra: this.extra + }; + } } /** diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/logger.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/logger.js index 224af20ddc..4ec505cb69 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/logger.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/logger.js @@ -1,13 +1,12 @@ /** * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts -**/import { version } from '../version.js'; +**/import { globalTestConfig } from '../../framework/test_config.js';import { version } from '../version.js'; + import { TestCaseRecorder } from './test_case_recorder.js'; export class Logger { - static globalDebugMode = false; - results = new Map(); @@ -19,7 +18,7 @@ export class Logger { const result = { status: 'running', timems: -1 }; this.results.set(name, result); return [ - new TestCaseRecorder(result, this.overriddenDebugMode ?? Logger.globalDebugMode), + new TestCaseRecorder(result, this.overriddenDebugMode ?? globalTestConfig.enableDebugLogs), result]; } diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/result.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/result.js index a7eb281daf..cd4d0e86f1 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/result.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/result.js @@ -1,4 +1,34 @@ /** * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/ // MAINTENANCE_TODO: Add warn expectations -export {};
\ No newline at end of file + + + + + + + + + + + + + +/** + * Raw data for a test log message. + * + * This form is sendable over a message channel, except `extra` may get mangled. + */ + + + + + + + + +/** + * Test case results in a form sendable over a message channel. + * + * Note `extra` may get mangled by postMessage. + */export {};
\ No newline at end of file diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/test_case_recorder.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/test_case_recorder.js index 7b9a5302ce..5b2a4e8b8e 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/test_case_recorder.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/test_case_recorder.js @@ -45,8 +45,6 @@ export class TestCaseRecorder { logs = []; logLinesAtCurrentSeverity = 0; debugging = false; - /** Used to dedup log messages which have identical stacks. */ - messagesForPreviouslySeenStacks = new Map(); constructor(result, debugging) { this.result = result; @@ -143,13 +141,15 @@ export class TestCaseRecorder { this.skipped(ex); return; } - this.logImpl(LogSeverity.ThrewException, 'EXCEPTION', ex); + // logImpl will discard the original error's ex.name. Preserve it here. + const name = ex instanceof Error ? `EXCEPTION: ${ex.name}` : 'EXCEPTION'; + this.logImpl(LogSeverity.ThrewException, name, ex); } logImpl(level, name, baseException) { assert(baseException instanceof Error, 'test threw a non-Error object'); globalTestConfig.testHeartbeatCallback(); - const logMessage = new LogMessageWithStack(name, baseException); + const logMessage = LogMessageWithStack.wrapError(name, baseException); // Final case status should be the "worst" of all log entries. if (this.inSubCase) { diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/query/compare.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/compare.js index 8af64919a2..f2bb1b62f3 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/internal/query/compare.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/compare.js @@ -58,7 +58,10 @@ function compareOneLevel(ordering, aIsBig, bIsBig) { return Ordering.Unordered; } -function comparePaths(a, b) { +/** + * Compare two file paths, or file-local test paths, returning an Ordering between the two. + */ +export function comparePaths(a, b) { const shorter = Math.min(a.length, b.length); for (let i = 0; i < shorter; ++i) { diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/query/parseQuery.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/parseQuery.js index b66b16ce91..4c8bf32341 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/internal/query/parseQuery.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/parseQuery.js @@ -17,12 +17,49 @@ import { import { kBigSeparator, kWildcard, kPathSeparator, kParamSeparator } from './separators.js'; import { validQueryPart } from './validQueryPart.js'; -export function parseQuery(s) { +/** + * converts foo/bar/src/webgpu/this/that/file.spec.ts to webgpu:this,that,file,* + */ +function convertPathToQuery(path) { + // removes .spec.ts and splits by directory separators. + const parts = path.substring(0, path.length - 8).split(/\/|\\/g); + // Gets parts only after the last `src`. Example: returns ['webgpu', 'foo', 'bar', 'test'] + // for ['Users', 'me', 'src', 'cts', 'src', 'webgpu', 'foo', 'bar', 'test'] + const partsAfterSrc = parts.slice(parts.lastIndexOf('src') + 1); + const suite = partsAfterSrc.shift(); + return `${suite}:${partsAfterSrc.join(',')},*`; +} + +/** + * If a query looks like a path (ends in .spec.ts and has directory separators) + * then convert try to convert it to a query. + */ +function convertPathLikeToQuery(queryOrPath) { + return queryOrPath.endsWith('.spec.ts') && ( + queryOrPath.includes('/') || queryOrPath.includes('\\')) ? + convertPathToQuery(queryOrPath) : + queryOrPath; +} + +/** + * Convert long suite names (the part before the first colon) to the + * shortest last word + * foo.bar.moo:test,subtest,foo -> moo:test,subtest,foo + */ +function shortenSuiteName(query) { + const parts = query.split(':'); + // converts foo.bar.moo to moo + const suite = parts.shift()?.replace(/.*\.(\w+)$/, '$1'); + return [suite, ...parts].join(':'); +} + +export function parseQuery(queryLike) { try { - return parseQueryImpl(s); + const query = shortenSuiteName(convertPathLikeToQuery(queryLike)); + return parseQueryImpl(query); } catch (ex) { if (ex instanceof Error) { - ex.message += '\n on: ' + s; + ex.message += `\n on: ${queryLike}`; } throw ex; } diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/query/query.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/query.js index e1db875061..8fd443652a 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/internal/query/query.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/query.js @@ -1,6 +1,6 @@ /** * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts -**/import { optionEnabled } from '../../runtime/helper/options.js';import { assert, unreachable } from '../../util/util.js'; +**/import { optionWorkerMode } from '../../runtime/helper/options.js';import { assert, unreachable } from '../../util/util.js'; import { compareQueries, Ordering } from './compare.js'; @@ -188,12 +188,12 @@ wptURL) assert( expectationURL.pathname === wptURL.pathname, `Invalid expectation path ${expectationURL.pathname} -Expectation should be of the form path/to/cts.https.html?worker=0&q=suite:test_path:test_name:foo=1;bar=2;... +Expectation should be of the form path/to/cts.https.html?debug=0&q=suite:test_path:test_name:foo=1;bar=2;... ` ); const params = expectationURL.searchParams; - if (optionEnabled('worker', params) !== optionEnabled('worker', wptURL.searchParams)) { + if (optionWorkerMode('worker', params) !== optionWorkerMode('worker', wptURL.searchParams)) { continue; } diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/test_group.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/test_group.js index 3db409ffe7..2772194667 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/internal/test_group.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/test_group.js @@ -34,7 +34,7 @@ import { validQueryPart } from '../internal/query/validQueryPart.js'; import { assert, unreachable } from '../util/util.js'; -import { logToWebsocket } from './websocket_logger.js'; +import { logToWebSocket } from './websocket_logger.js'; @@ -294,9 +294,11 @@ class TestBuilder { (this.description ? this.description + '\n\n' : '') + 'TODO: .unimplemented()'; this.isUnimplemented = true; - this.testFn = () => { + // Use the beforeFn to skip the test, so we don't have to iterate the subcases. + this.beforeFn = () => { throw new SkipTestCase('test unimplemented'); }; + this.testFn = () => {}; } /** Perform various validation/"lint" chenks. */ @@ -350,7 +352,7 @@ class TestBuilder { const testcaseStringUnique = stringifyPublicParamsUniquely(params); assert( !seen.has(testcaseStringUnique), - `Duplicate public test case+subcase params for test ${testPathString}: ${testcaseString}` + `Duplicate public test case+subcase params for test ${testPathString}: ${testcaseString} (${caseQuery})` ); seen.add(testcaseStringUnique); } @@ -737,7 +739,7 @@ class RunCaseSpecific { timems: rec.result.timems, nonskippedSubcaseCount: rec.nonskippedSubcaseCount }; - logToWebsocket(JSON.stringify(msg)); + logToWebSocket(JSON.stringify(msg)); } } }
\ No newline at end of file diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/test_suite_listing.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/test_suite_listing.js index 81a15cf712..7ea1c066f5 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/internal/test_suite_listing.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/test_suite_listing.js @@ -2,5 +2,5 @@ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/ // A listing of all specs within a single suite. This is the (awaited) type of // `groups` in '{cts,unittests}/listing.ts' and `listing` in the auto-generated -// 'out/{cts,unittests}/listing.js' files (see tools/gen_listings). +// 'out/{cts,unittests}/listing.js' files (see tools/gen_listings_and_webworkers). export {};
\ No newline at end of file diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/tree.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/tree.js index 8f1e6bad66..3b33542752 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/internal/tree.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/tree.js @@ -286,6 +286,7 @@ loader, queryToLoad, { subqueriesToExpand, + fullyExpandSubtrees = [], maxChunkTime = Infinity }) { @@ -303,6 +304,10 @@ queryToLoad, // If toExpand == subquery, no expansion is needed (but it's still "seen"). if (ordering === Ordering.Equal) seenSubqueriesToExpand[i] = true; return ordering !== Ordering.StrictSubset; + }) && + fullyExpandSubtrees.every((toExpand) => { + const ordering = compareQueries(toExpand, subquery); + return ordering === Ordering.Unordered; }); // L0 = suite-level, e.g. suite:* diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/version.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/version.js index 7a632d5635..d300878e0e 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/internal/version.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/version.js @@ -1,3 +1,3 @@ // AUTO-GENERATED - DO NOT EDIT. See tools/gen_version. -export const version = '41f89e77b67e6b66cb017be4e00235a0a9429ca7'; +export const version = '5c8510ec0d47180d1cd4dd92790b5a69335e162c'; diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/websocket_logger.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/websocket_logger.js index 7a8f92b1a8..1790e5632d 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/internal/websocket_logger.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/websocket_logger.js @@ -1,19 +1,24 @@ /** * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts -**/ /** +**/import { globalTestConfig } from '../framework/test_config.js'; /** * - 'uninitialized' means we haven't tried to connect yet * - Promise means it's pending * - 'failed' means it failed (this is the most common case, where the logger isn't running) * - WebSocket means it succeeded - */let connection = 'uninitialized'; + */ +let connection = +'uninitialized'; /** - * Log a string to a websocket at `localhost:59497`. See `tools/websocket-logger`. + * If the logToWebSocket option is enabled (?log_to_web_socket=1 in browser, + * --log-to-web-socket on command line, or enable it by default in options.ts), + * log a string to a websocket at `localhost:59497`. See `tools/websocket-logger`. * - * This does nothing if a connection couldn't be established on the first call. + * This does nothing if a logToWebSocket is not enabled, or if a connection + * couldn't be established on the first call. */ -export function logToWebsocket(msg) { - if (connection === 'failed') { +export function logToWebSocket(msg) { + if (!globalTestConfig.logToWebSocket || connection === 'failed') { return; } diff --git a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/options.js b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/options.js index 139c3bc29f..01b7d95889 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/options.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/options.js @@ -1,11 +1,14 @@ /** * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts -**/let windowURL = undefined;function getWindowURL() {if (windowURL === undefined) { +**/import { unreachable } from '../../util/util.js';let windowURL = undefined; +function getWindowURL() { + if (windowURL === undefined) { windowURL = new URL(window.location.toString()); } return windowURL; } +/** Parse a runner option that is always boolean-typed. False if missing or '0'. */ export function optionEnabled( opt, searchParams = getWindowURL().searchParams) @@ -14,11 +17,32 @@ searchParams = getWindowURL().searchParams) return val !== null && val !== '0'; } +/** Parse a runner option that is string-typed. If the option is missing, returns `null`. */ export function optionString( opt, searchParams = getWindowURL().searchParams) { - return searchParams.get(opt) || ''; + return searchParams.get(opt); +} + +/** Runtime modes for running tests in different types of workers. */ + +/** Parse a runner option for different worker modes (as in `?worker=shared`). Null if no worker. */ +export function optionWorkerMode( +opt, +searchParams = getWindowURL().searchParams) +{ + const value = searchParams.get(opt); + if (value === null || value === '0') { + return null; + } else if (value === 'service') { + return 'service'; + } else if (value === 'shared') { + return 'shared'; + } else if (value === '' || value === '1' || value === 'dedicated') { + return 'dedicated'; + } + unreachable('invalid worker= option value'); } /** @@ -32,12 +56,16 @@ searchParams = getWindowURL().searchParams) + + export const kDefaultCTSOptions = { - worker: false, + worker: null, debug: true, compatibility: false, + forceFallbackAdapter: false, unrollConstEvalLoops: false, - powerPreference: '' + powerPreference: null, + logToWebSocket: false }; /** @@ -59,19 +87,30 @@ export const kDefaultCTSOptions = { * Options to the CTS. */ export const kCTSOptionsInfo = { - worker: { description: 'run in a worker' }, + worker: { + description: 'run in a worker', + parser: optionWorkerMode, + selectValueDescriptions: [ + { value: null, description: 'no worker' }, + { value: 'dedicated', description: 'dedicated worker' }, + { value: 'shared', description: 'shared worker' }, + { value: 'service', description: 'service worker' }] + + }, debug: { description: 'show more info' }, compatibility: { description: 'run in compatibility mode' }, + forceFallbackAdapter: { description: 'pass forceFallbackAdapter: true to requestAdapter' }, unrollConstEvalLoops: { description: 'unroll const eval loops in WGSL' }, powerPreference: { description: 'set default powerPreference for some tests', parser: optionString, selectValueDescriptions: [ - { value: '', description: 'default' }, + { value: null, description: 'default' }, { value: 'low-power', description: 'low-power' }, { value: 'high-performance', description: 'high-performance' }] - } + }, + logToWebSocket: { description: 'send some logs to ws://localhost:59497/' } }; /** diff --git a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker-worker.js b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker-worker.js index a0f13c54af..c5df860d0b 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker-worker.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker-worker.js @@ -1,15 +1,11 @@ /** * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts -**/import { setBaseResourcePath } from '../../framework/resources.js';import { globalTestConfig } from '../../framework/test_config.js';import { DefaultTestFileLoader } from '../../internal/file_loader.js'; -import { Logger } from '../../internal/logging/logger.js'; -import { parseQuery } from '../../internal/query/parseQuery.js'; - -import { setDefaultRequestAdapterOptions } from '../../util/navigator_gpu.js'; +**/import { setBaseResourcePath } from '../../framework/resources.js';import { DefaultTestFileLoader } from '../../internal/file_loader.js';import { parseQuery } from '../../internal/query/parseQuery.js'; import { assert } from '../../util/util.js'; +import { setupWorkerEnvironment } from './utils_worker.js'; - -// Should be DedicatedWorkerGlobalScope, but importing lib "webworker" conflicts with lib "dom". +// Should be WorkerGlobalScope, but importing lib "webworker" conflicts with lib "dom". @@ -17,25 +13,10 @@ const loader = new DefaultTestFileLoader(); setBaseResourcePath('../../../resources'); -self.onmessage = async (ev) => { - const query = ev.data.query; - const expectations = ev.data.expectations; - const ctsOptions = ev.data.ctsOptions; - - const { debug, unrollConstEvalLoops, powerPreference, compatibility } = ctsOptions; - globalTestConfig.unrollConstEvalLoops = unrollConstEvalLoops; - globalTestConfig.compatibility = compatibility; - - Logger.globalDebugMode = debug; - const log = new Logger(); +async function reportTestResults(ev) { + const { query, expectations, ctsOptions } = ev.data; - if (powerPreference || compatibility) { - setDefaultRequestAdapterOptions({ - ...(powerPreference && { powerPreference }), - // MAINTENANCE_TODO: Change this to whatever the option ends up being - ...(compatibility && { compatibilityMode: true }) - }); - } + const log = setupWorkerEnvironment(ctsOptions); const testcases = Array.from(await loader.loadCases(parseQuery(query))); assert(testcases.length === 1, 'worker query resulted in != 1 cases'); @@ -44,5 +25,23 @@ self.onmessage = async (ev) => { const [rec, result] = log.record(testcase.query.toString()); await testcase.run(rec, expectations); - self.postMessage({ query, result }); + this.postMessage({ + query, + result: { + ...result, + logs: result.logs?.map((l) => l.toRawData()) + } + }); +} + +self.onmessage = (ev) => { + void reportTestResults.call(ev.source || self, ev); +}; + +self.onconnect = (event) => { + const port = event.ports[0]; + + port.onmessage = (ev) => { + void reportTestResults.call(port, ev); + }; };
\ No newline at end of file diff --git a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker.js b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker.js index 1d65394180..4039aa1332 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker.js @@ -2,48 +2,190 @@ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/import { LogMessageWithStack } from '../../internal/logging/log_message.js'; +import { timeout } from '../../util/timeout.js'; +import { assert } from '../../util/util.js'; import { kDefaultCTSOptions } from './options.js'; -export class TestWorker { +/** Query all currently-registered service workers, and unregister them. */ +function unregisterAllServiceWorkers() { + void navigator.serviceWorker.getRegistrations().then((registrations) => { + for (const registration of registrations) { + void registration.unregister(); + } + }); +} + +// NOTE: This code runs on startup for any runtime with worker support. Here, we use that chance to +// delete any leaked service workers, and register to clean up after ourselves at shutdown. +unregisterAllServiceWorkers(); +window.addEventListener('beforeunload', () => { + unregisterAllServiceWorkers(); +}); + +class TestBaseWorker { resolvers = new Map(); - constructor(ctsOptions) { - this.ctsOptions = { ...(ctsOptions || kDefaultCTSOptions), ...{ worker: true } }; - const selfPath = import.meta.url; - const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/')); - const workerPath = selfPathDir + '/test_worker-worker.js'; - this.worker = new Worker(workerPath, { type: 'module' }); - this.worker.onmessage = (ev) => { - const query = ev.data.query; - const result = ev.data.result; - if (result.logs) { - for (const l of result.logs) { - Object.setPrototypeOf(l, LogMessageWithStack.prototype); - } - } - this.resolvers.get(query)(result); + constructor(worker, ctsOptions) { + this.ctsOptions = { ...(ctsOptions || kDefaultCTSOptions), ...{ worker } }; + } + + onmessage(ev) { + const query = ev.data.query; + const transferredResult = ev.data.result; - // MAINTENANCE_TODO(kainino0x): update the Logger with this result (or don't have a logger and - // update the entire results JSON somehow at some point). + const result = { + status: transferredResult.status, + timems: transferredResult.timems, + logs: transferredResult.logs?.map((l) => new LogMessageWithStack(l)) }; + + this.resolvers.get(query)(result); + this.resolvers.delete(query); + + // MAINTENANCE_TODO(kainino0x): update the Logger with this result (or don't have a logger and + // update the entire results JSON somehow at some point). } - async run( - rec, + makeRequestAndRecordResult( + target, query, - expectations = []) + expectations) { - this.worker.postMessage({ + const request = { query, expectations, ctsOptions: this.ctsOptions - }); - const workerResult = await new Promise((resolve) => { + }; + target.postMessage(request); + + return new Promise((resolve) => { + assert(!this.resolvers.has(query), "can't request same query twice simultaneously"); this.resolvers.set(query, resolve); }); - rec.injectResult(workerResult); + } + + async run( + rec, + query, + expectations = []) + { + try { + rec.injectResult(await this.runImpl(query, expectations)); + } catch (ex) { + rec.start(); + rec.threw(ex); + rec.finish(); + } + } + + + + + +} + +export class TestDedicatedWorker extends TestBaseWorker { + + + constructor(ctsOptions) { + super('dedicated', ctsOptions); + try { + if (typeof Worker === 'undefined') { + throw new Error('Dedicated Workers not available'); + } + + const selfPath = import.meta.url; + const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/')); + const workerPath = selfPathDir + '/test_worker-worker.js'; + this.worker = new Worker(workerPath, { type: 'module' }); + this.worker.onmessage = (ev) => this.onmessage(ev); + } catch (ex) { + assert(ex instanceof Error); + // Save the exception to re-throw in runImpl(). + this.worker = ex; + } + } + + runImpl(query, expectations = []) { + if (this.worker instanceof Worker) { + return this.makeRequestAndRecordResult(this.worker, query, expectations); + } else { + throw this.worker; + } + } +} + +/** @deprecated Use TestDedicatedWorker instead. */ +export class TestWorker extends TestDedicatedWorker {} + +export class TestSharedWorker extends TestBaseWorker { + /** MessagePort to the SharedWorker, or an Error if it couldn't be initialized. */ + + + constructor(ctsOptions) { + super('shared', ctsOptions); + try { + if (typeof SharedWorker === 'undefined') { + throw new Error('Shared Workers not available'); + } + + const selfPath = import.meta.url; + const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/')); + const workerPath = selfPathDir + '/test_worker-worker.js'; + const worker = new SharedWorker(workerPath, { type: 'module' }); + this.port = worker.port; + this.port.start(); + this.port.onmessage = (ev) => this.onmessage(ev); + } catch (ex) { + assert(ex instanceof Error); + // Save the exception to re-throw in runImpl(). + this.port = ex; + } + } + + runImpl(query, expectations = []) { + if (this.port instanceof MessagePort) { + return this.makeRequestAndRecordResult(this.port, query, expectations); + } else { + throw this.port; + } + } +} + +export class TestServiceWorker extends TestBaseWorker { + constructor(ctsOptions) { + super('service', ctsOptions); + } + + async runImpl(query, expectations = []) { + if (!('serviceWorker' in navigator)) { + throw new Error('Service Workers not available'); + } + const [suite, name] = query.split(':', 2); + const fileName = name.split(',').join('/'); + + const selfPath = import.meta.url; + const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/')); + // Construct the path to the worker file, then use URL to resolve the `../` components. + const serviceWorkerURL = new URL( + `${selfPathDir}/../../../${suite}/webworker/${fileName}.worker.js` + ).toString(); + + // If a registration already exists for this path, it will be ignored. + const registration = await navigator.serviceWorker.register(serviceWorkerURL, { + type: 'module' + }); + // Make sure the registration we just requested is active. (We don't worry about it being + // outdated from a previous page load, because we wipe all service workers on shutdown/startup.) + while (!registration.active || registration.active.scriptURL !== serviceWorkerURL) { + await new Promise((resolve) => timeout(resolve, 0)); + } + const serviceWorker = registration.active; + + navigator.serviceWorker.onmessage = (ev) => this.onmessage(ev); + return this.makeRequestAndRecordResult(serviceWorker, query, expectations); } }
\ No newline at end of file diff --git a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/utils_worker.js b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/utils_worker.js new file mode 100644 index 0000000000..5a34070e14 --- /dev/null +++ b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/utils_worker.js @@ -0,0 +1,35 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/import { globalTestConfig } from '../../framework/test_config.js';import { Logger } from '../../internal/logging/logger.js'; +import { setDefaultRequestAdapterOptions } from '../../util/navigator_gpu.js'; + + + + + + + + + +/** + * Set config environment for workers with ctsOptions and return a Logger. + */ +export function setupWorkerEnvironment(ctsOptions) { + const { powerPreference, compatibility } = ctsOptions; + globalTestConfig.enableDebugLogs = ctsOptions.debug; + globalTestConfig.unrollConstEvalLoops = ctsOptions.unrollConstEvalLoops; + globalTestConfig.compatibility = compatibility; + globalTestConfig.logToWebSocket = ctsOptions.logToWebSocket; + + const log = new Logger(); + + if (powerPreference || compatibility) { + setDefaultRequestAdapterOptions({ + ...(powerPreference && { powerPreference }), + // MAINTENANCE_TODO: Change this to whatever the option ends up being + ...(compatibility && { compatibilityMode: true }) + }); + } + + return log; +}
\ No newline at end of file diff --git a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/wrap_for_worker.js b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/wrap_for_worker.js new file mode 100644 index 0000000000..69b5f375f1 --- /dev/null +++ b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/wrap_for_worker.js @@ -0,0 +1,54 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/import { LogMessageWithStack } from '../../internal/logging/log_message.js';import { comparePaths, comparePublicParamsPaths, Ordering } from '../../internal/query/compare.js'; +import { parseQuery } from '../../internal/query/parseQuery.js'; +import { TestQuerySingleCase } from '../../internal/query/query.js'; + +import { assert } from '../../util/util.js'; + +import { setupWorkerEnvironment } from './utils_worker.js'; + +/** + * Sets up the currently running Web Worker to wrap the TestGroup object `g`. + * `g` is the `g` exported from a `.spec.ts` file: a TestGroupBuilder<F> interface, + * which underneath is actually a TestGroup<F> object. + * + * This is used in the generated `.worker.js` files that are generated to use as service workers. + */ +export function wrapTestGroupForWorker(g) { + self.onmessage = async (ev) => { + const { query, expectations, ctsOptions } = ev.data; + try { + const log = setupWorkerEnvironment(ctsOptions); + + const testQuery = parseQuery(query); + assert(testQuery instanceof TestQuerySingleCase); + let testcase = null; + for (const t of g.iterate()) { + if (comparePaths(t.testPath, testQuery.testPathParts) !== Ordering.Equal) { + continue; + } + for (const c of t.iterate(testQuery.params)) { + if (comparePublicParamsPaths(c.id.params, testQuery.params) === Ordering.Equal) { + testcase = c; + } + } + } + assert(!!testcase, 'testcase not found'); + const [rec, result] = log.record(query); + await testcase.run(rec, testQuery, expectations); + + ev.source?.postMessage({ query, result }); + } catch (thrown) { + const ex = thrown instanceof Error ? thrown : new Error(`${thrown}`); + ev.source?.postMessage({ + query, + result: { + status: 'fail', + timems: 0, + logs: [LogMessageWithStack.wrapError('INTERNAL', ex)] + } + }); + } + }; +}
\ No newline at end of file diff --git a/testing/web-platform/mozilla/tests/webgpu/common/runtime/wpt.js b/testing/web-platform/mozilla/tests/webgpu/common/runtime/wpt.js index 97c6a3886e..d1a025d1f8 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/runtime/wpt.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/runtime/wpt.js @@ -8,8 +8,8 @@ import { parseQuery } from '../internal/query/parseQuery.js'; import { parseExpectationsForTestQuery, relativeQueryString } from '../internal/query/query.js'; import { assert } from '../util/util.js'; -import { optionEnabled } from './helper/options.js'; -import { TestWorker } from './helper/test_worker.js'; +import { optionEnabled, optionWorkerMode } from './helper/options.js'; +import { TestDedicatedWorker, TestServiceWorker, TestSharedWorker } from './helper/test_worker.js'; // testharness.js API (https://web-platform-tests.org/writing-tests/testharness-api.html) @@ -31,8 +31,10 @@ setup({ }); void (async () => { - const workerEnabled = optionEnabled('worker'); - const worker = workerEnabled ? new TestWorker() : undefined; + const workerString = optionWorkerMode('worker'); + const dedicatedWorker = workerString === 'dedicated' ? new TestDedicatedWorker() : undefined; + const sharedWorker = workerString === 'shared' ? new TestSharedWorker() : undefined; + const serviceWorker = workerString === 'service' ? new TestServiceWorker() : undefined; globalTestConfig.unrollConstEvalLoops = optionEnabled('unroll_const_eval_loops'); @@ -63,8 +65,12 @@ void (async () => { const wpt_fn = async () => { const [rec, res] = log.record(name); - if (worker) { - await worker.run(rec, name, expectations); + if (dedicatedWorker) { + await dedicatedWorker.run(rec, name, expectations); + } else if (sharedWorker) { + await sharedWorker.run(rec, name, expectations); + } else if (serviceWorker) { + await serviceWorker.run(rec, name, expectations); } else { await testcase.run(rec, expectations); } diff --git a/testing/web-platform/mozilla/tests/webgpu/common/util/crc32.js b/testing/web-platform/mozilla/tests/webgpu/common/util/crc32.js new file mode 100644 index 0000000000..392d3b947c --- /dev/null +++ b/testing/web-platform/mozilla/tests/webgpu/common/util/crc32.js @@ -0,0 +1,57 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ /// CRC32 immutable lookup table data. +const kCRC32LUT = [0, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, +0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, +0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, +0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, +0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, +0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, +0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, +0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, +0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, +0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, +0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, +0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, +0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, +0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, +0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, +0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, +0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, +0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, +0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, +0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, +0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, +0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, +0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, +0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, +0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, +0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, +0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, +0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, +0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, +0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, +0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d]; + + +/** + * @param str the input string + * @returns the CRC32 of the input string + * @see https://en.wikipedia.org/wiki/Cyclic_redundancy_check#CRC-32_algorithm + */ +export function crc32(str) { + const utf8 = new TextEncoder().encode(str); + const u32 = new Uint32Array(1); + + u32[0] = 0xffffffff; + for (const c of utf8) { + u32[0] = u32[0] >>> 8 ^ kCRC32LUT[u32[0] & 0xff ^ c]; + } + u32[0] = u32[0] ^ 0xffffffff; + return u32[0]; +} + +/** @returns the input number has a 8-character hex string */ +export function toHexString(number) { + return ('00000000' + number.toString(16)).slice(-8); +}
\ No newline at end of file diff --git a/testing/web-platform/mozilla/tests/webgpu/common/util/parse_imports.js b/testing/web-platform/mozilla/tests/webgpu/common/util/parse_imports.js new file mode 100644 index 0000000000..bc970572b1 --- /dev/null +++ b/testing/web-platform/mozilla/tests/webgpu/common/util/parse_imports.js @@ -0,0 +1,36 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ /** + * Parses all the paths of the typescript `import` statements from content + * @param path the current path of the file + * @param content the file content + * @returns the list of import paths + */export function parseImports(path, content) {const out = []; + const importRE = /^import\s[^'"]*(['"])([./\w]*)(\1);/gm; + let importMatch; + while (importMatch = importRE.exec(content)) { + const importPath = importMatch[2].replace(`'`, '').replace(`"`, ''); + out.push(joinPath(path, importPath)); + } + return out; +} + +function joinPath(a, b) { + const aParts = a.split('/'); + const bParts = b.split('/'); + aParts.pop(); // remove file + let bStart = 0; + while (aParts.length > 0) { + switch (bParts[bStart]) { + case '.': + bStart++; + continue; + case '..': + aParts.pop(); + bStart++; + continue; + } + break; + } + return [...aParts, ...bParts.slice(bStart)].join('/'); +}
\ No newline at end of file diff --git a/testing/web-platform/mozilla/tests/webgpu/common/util/util.js b/testing/web-platform/mozilla/tests/webgpu/common/util/util.js index 34934af6c0..d0a3c0a8f7 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/util/util.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/util/util.js @@ -1,7 +1,6 @@ /** * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/import { Float16Array } from '../../external/petamoriken/float16/float16.js';import { SkipTestCase } from '../framework/fixture.js';import { globalTestConfig } from '../framework/test_config.js'; -import { Logger } from '../internal/logging/logger.js'; import { keysOf } from './data_tables.js'; import { timeout } from './timeout.js'; @@ -24,7 +23,7 @@ export class ErrorWithExtra extends Error { super(message); const oldExtras = baseOrMessage instanceof ErrorWithExtra ? baseOrMessage.extra : {}; - this.extra = Logger.globalDebugMode ? + this.extra = globalTestConfig.enableDebugLogs ? { ...oldExtras, ...newExtra() } : { omitted: 'pass ?debug=1' }; } @@ -303,7 +302,9 @@ new Int16Array(), new Int32Array(), new Float16Array(), new Float32Array(), -new Float64Array()]; +new Float64Array(), +new BigInt64Array(), +new BigUint64Array()]; |