diff --git a/docs/intro/developing.md b/docs/intro/developing.md index 0cfd311680a8..0016c2c048b9 100644 --- a/docs/intro/developing.md +++ b/docs/intro/developing.md @@ -56,7 +56,7 @@ The following url parameters change how the harness runs: - `runnow=1` runs all matching tests on page load. - `debug=1` enables verbose debug logging from tests. -- `worker=dedicated` runs the tests on a dedicated worker instead of the main thread. +- `worker=dedicated` (or `worker` or `worker=1`) runs the tests on a dedicated worker instead of the main thread. - `worker=shared` runs the tests on a shared worker instead of the main thread. - `worker=service` runs the tests on a service worker instead of the main thread. - `power_preference=low-power` runs most tests passing `powerPreference: low-power` to `requestAdapter` diff --git a/src/common/internal/query/query.ts b/src/common/internal/query/query.ts index ad7cf246e9d9..676ac46d3888 100644 --- a/src/common/internal/query/query.ts +++ b/src/common/internal/query/query.ts @@ -1,5 +1,5 @@ import { TestParams } from '../../framework/fixture.js'; -import { optionString } from '../../runtime/helper/options.js'; +import { optionWorkerMode } from '../../runtime/helper/options.js'; import { assert, unreachable } from '../../util/util.js'; import { Expectation } from '../logging/result.js'; @@ -193,7 +193,7 @@ Expectation should be of the form path/to/cts.https.html?debug=0&q=suite:test_pa ); const params = expectationURL.searchParams; - if (optionString('worker', params) !== optionString('worker', wptURL.searchParams)) { + if (optionWorkerMode('worker', params) !== optionWorkerMode('worker', wptURL.searchParams)) { continue; } diff --git a/src/common/runtime/helper/options.ts b/src/common/runtime/helper/options.ts index e19856b6fbe1..4a82c7d2928e 100644 --- a/src/common/runtime/helper/options.ts +++ b/src/common/runtime/helper/options.ts @@ -1,3 +1,5 @@ +import { unreachable } from '../../util/util.js'; + let windowURL: URL | undefined = undefined; function getWindowURL() { if (windowURL === undefined) { @@ -6,6 +8,7 @@ function getWindowURL() { return windowURL; } +/** Parse a runner option that is always boolean-typed. False if missing or '0'. */ export function optionEnabled( opt: string, searchParams: URLSearchParams = getWindowURL().searchParams @@ -14,33 +17,54 @@ export function optionEnabled( return val !== null && val !== '0'; } +/** Parse a runner option that is string-typed. If the option is missing, returns `null`. */ export function optionString( opt: string, searchParams: URLSearchParams = getWindowURL().searchParams -): string { - return searchParams.get(opt) || ''; +): string | null { + return searchParams.get(opt); +} + +/** Runtime modes for running tests in different types of workers. */ +export type WorkerMode = 'dedicated' | 'service' | 'shared'; +/** Parse a runner option for different worker modes (as in `?worker=shared`). Null if no worker. */ +export function optionWorkerMode( + opt: string, + searchParams: URLSearchParams = getWindowURL().searchParams +): WorkerMode | null { + 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'); } /** * The possible options for the tests. */ export interface CTSOptions { - worker?: 'dedicated' | 'shared' | 'service' | ''; + worker: WorkerMode | null; debug: boolean; compatibility: boolean; forceFallbackAdapter: boolean; unrollConstEvalLoops: boolean; - powerPreference: GPUPowerPreference | ''; + powerPreference: GPUPowerPreference | null; logToWebSocket: boolean; } export const kDefaultCTSOptions: CTSOptions = { - worker: '', + worker: null, debug: true, compatibility: false, forceFallbackAdapter: false, unrollConstEvalLoops: false, - powerPreference: '', + powerPreference: null, logToWebSocket: false, }; @@ -49,8 +73,8 @@ export const kDefaultCTSOptions: CTSOptions = { */ export interface OptionInfo { description: string; - parser?: (key: string, searchParams?: URLSearchParams) => boolean | string; - selectValueDescriptions?: { value: string; description: string }[]; + parser?: (key: string, searchParams?: URLSearchParams) => boolean | string | null; + selectValueDescriptions?: { value: string | null; description: string }[]; } /** @@ -65,9 +89,9 @@ export type OptionsInfos = Record; export const kCTSOptionsInfo: OptionsInfos = { worker: { description: 'run in a worker', - parser: optionString, + parser: optionWorkerMode, selectValueDescriptions: [ - { value: '', description: 'no worker' }, + { value: null, description: 'no worker' }, { value: 'dedicated', description: 'dedicated worker' }, { value: 'shared', description: 'shared worker' }, { value: 'service', description: 'service worker' }, @@ -81,7 +105,7 @@ export const kCTSOptionsInfo: OptionsInfos = { 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' }, ], @@ -110,7 +134,7 @@ function getOptionsInfoFromSearchString( searchString: string ): Type { const searchParams = new URLSearchParams(searchString); - const optionValues: Record = {}; + const optionValues: Record = {}; for (const [optionName, info] of Object.entries(optionsInfos)) { const parser = info.parser || optionEnabled; optionValues[optionName] = parser(camelCaseToSnakeCase(optionName), searchParams); diff --git a/src/common/runtime/helper/test_worker.ts b/src/common/runtime/helper/test_worker.ts index 42848106b68e..4f64212b0e5c 100644 --- a/src/common/runtime/helper/test_worker.ts +++ b/src/common/runtime/helper/test_worker.ts @@ -5,7 +5,7 @@ import { TestQueryWithExpectation } from '../../internal/query/query.js'; import { timeout } from '../../util/timeout.js'; import { assert } from '../../util/util.js'; -import { CTSOptions, kDefaultCTSOptions } from './options.js'; +import { CTSOptions, WorkerMode, kDefaultCTSOptions } from './options.js'; import { WorkerTestRunRequest } from './utils_worker.js'; /** Query all currently-registered service workers, and unregister them. */ @@ -28,7 +28,7 @@ class TestBaseWorker { protected readonly ctsOptions: CTSOptions; protected readonly resolvers = new Map void>(); - constructor(worker: CTSOptions['worker'], ctsOptions?: CTSOptions) { + constructor(worker: WorkerMode, ctsOptions?: CTSOptions) { this.ctsOptions = { ...(ctsOptions || kDefaultCTSOptions), ...{ worker } }; } diff --git a/src/common/runtime/standalone.ts b/src/common/runtime/standalone.ts index ad7b25bbaf32..dc75e6fd01a4 100644 --- a/src/common/runtime/standalone.ts +++ b/src/common/runtime/standalone.ts @@ -57,10 +57,16 @@ const logger = new Logger(); setBaseResourcePath('../out/resources'); -const dedicatedWorker = - options.worker === 'dedicated' ? new TestDedicatedWorker(options) : undefined; -const sharedWorker = options.worker === 'shared' ? new TestSharedWorker(options) : undefined; -const serviceWorker = options.worker === 'service' ? new TestServiceWorker(options) : undefined; +const testWorker = + options.worker === null + ? null + : options.worker === 'dedicated' + ? new TestDedicatedWorker(options) + : options.worker === 'shared' + ? new TestSharedWorker(options) + : options.worker === 'service' + ? new TestServiceWorker(options) + : unreachable(); const autoCloseOnPass = document.getElementById('autoCloseOnPass') as HTMLInputElement; const resultsVis = document.getElementById('resultsVis')!; @@ -173,12 +179,8 @@ function makeCaseHTML(t: TestTreeLeaf): VisualizedSubtree { const [rec, res] = logger.record(name); caseResult = res; - if (dedicatedWorker) { - await dedicatedWorker.run(rec, name); - } else if (sharedWorker) { - await sharedWorker.run(rec, name); - } else if (serviceWorker) { - await serviceWorker.run(rec, name); + if (testWorker) { + await testWorker.run(rec, name); } else { await t.run(rec); } @@ -515,13 +517,11 @@ function makeTreeNodeHeaderHTML( // Collapse s:f:t:* or s:f:t:c by default. let lastQueryLevelToExpand: TestQueryLevel = 2; -type ParamValue = string | undefined | null | boolean | string[]; - /** * Takes an array of string, ParamValue and returns an array of pairs * of [key, value] where value is a string. Converts boolean to '0' or '1'. */ -function keyValueToPairs([k, v]: [string, ParamValue]): [string, string][] { +function keyValueToPairs([k, v]: [string, boolean | string | null]): [string, string][] { const key = camelCaseToSnakeCase(k); if (typeof v === 'boolean') { return [[key, v ? '1' : '0']]; @@ -542,9 +542,9 @@ function keyValueToPairs([k, v]: [string, ParamValue]): [string, string][] { * @param params Some object with key value pairs. * @returns a search string. */ -function prepareParams(params: Record): string { +function prepareParams(params: Record): string { const pairsArrays = Object.entries(params) - .filter(([, v]) => !!v) + .filter(([, v]) => !(v === false || v === null || v === '0')) .map(keyValueToPairs); const pairs = pairsArrays.flat(); return new URLSearchParams(pairs).toString(); @@ -552,7 +552,7 @@ function prepareParams(params: Record): string { // This is just a cast in one place. export function optionsToRecord(options: CTSOptions) { - return options as unknown as Record; + return options as unknown as Record; } /** @@ -612,15 +612,15 @@ void (async () => { }; const createSelect = (optionName: string, info: OptionInfo) => { - const select = $('').on('change', function (this: HTMLSelectElement) { + optionValues[optionName] = JSON.parse(this.value); updateURLsWithCurrentOptions(); }); const currentValue = optionValues[optionName]; for (const { value, description } of info.selectValueDescriptions!) { $('