Skip to content

Commit

Permalink
Add service worker support
Browse files Browse the repository at this point in the history
  • Loading branch information
beaufortfrancois committed Feb 20, 2024
1 parent ec1aee0 commit 7b74efd
Show file tree
Hide file tree
Showing 12 changed files with 267 additions and 114 deletions.
1 change: 1 addition & 0 deletions docs/intro/developing.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ The following url parameters change how the harness runs:
- `debug=1` enables verbose debug logging from tests.
- `worker=dedicated` 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`
- `power_preference=high-performance` runs most tests passing `powerPreference: high-performance` to `requestAdapter`

Expand Down
3 changes: 2 additions & 1 deletion src/common/runtime/helper/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function optionString(
* The possible options for the tests.
*/
export interface CTSOptions {
worker?: 'dedicated' | 'shared' | '';
worker?: 'dedicated' | 'shared' | 'service' | '';
debug: boolean;
compatibility: boolean;
forceFallbackAdapter: boolean;
Expand Down Expand Up @@ -68,6 +68,7 @@ export const kCTSOptionsInfo: OptionsInfos<CTSOptions> = {
{ value: '', description: 'no worker' },
{ value: 'dedicated', description: 'dedicated worker' },
{ value: 'shared', description: 'shared worker' },
{ value: 'service', description: 'service worker' },
],
},
debug: { description: 'show more info' },
Expand Down
2 changes: 1 addition & 1 deletion src/common/runtime/helper/test_worker-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async function reportTestResults(this: MessagePort | Worker, ev: MessageEvent) {
}

self.onmessage = (ev: MessageEvent) => {
void reportTestResults.call(self, ev);
void reportTestResults.call(ev.source || self, ev);
};

self.onconnect = (event: MessageEvent) => {
Expand Down
49 changes: 49 additions & 0 deletions src/common/runtime/helper/test_worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,52 @@ export class TestSharedWorker {
rec.injectResult(workerResult);
}
}

export class TestServiceWorker {
private readonly ctsOptions: CTSOptions;
private readonly resolvers = new Map<string, (result: LiveTestCaseResult) => void>();

constructor(ctsOptions?: CTSOptions) {
this.ctsOptions = { ...(ctsOptions || kDefaultCTSOptions), ...{ worker: 'service' } };
}

async run(
rec: TestCaseRecorder,
query: string,
expectations: TestQueryWithExpectation[] = []
): Promise<void> {
const [suite, name] = query.split(":", 2);
const fileName = name.split(',').join('/');
const serviceWorkerPath = `/out/${suite}/webworker/${fileName}.worker.js`;

const registration = await navigator.serviceWorker.register(serviceWorkerPath, {
type: 'module',
scope: '/',
});
await navigator.serviceWorker.ready;

navigator.serviceWorker.onmessage = ev => {
const query: string = ev.data.query;
const result: TransferredTestCaseResult = ev.data.result;
if (result.logs) {
for (const l of result.logs) {
Object.setPrototypeOf(l, LogMessageWithStack.prototype);
}
}
this.resolvers.get(query)!(result as LiveTestCaseResult);

// 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).
};

registration.active.postMessage({
query,
expectations,
ctsOptions: this.ctsOptions,
});
const serviceWorkerResult = await new Promise<LiveTestCaseResult>(resolve => {
this.resolvers.set(query, resolve);
});
rec.injectResult(serviceWorkerResult);
}
}
5 changes: 4 additions & 1 deletion src/common/runtime/standalone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
OptionsInfos,
camelCaseToSnakeCase,
} from './helper/options.js';
import { TestDedicatedWorker, TestSharedWorker } from './helper/test_worker.js';
import { TestDedicatedWorker, TestSharedWorker, TestServiceWorker } from './helper/test_worker.js';

const rootQuerySpec = 'webgpu:*';
let promptBeforeReload = false;
Expand Down Expand Up @@ -66,6 +66,7 @@ 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 autoCloseOnPass = document.getElementById('autoCloseOnPass') as HTMLInputElement;
const resultsVis = document.getElementById('resultsVis')!;
Expand Down Expand Up @@ -182,6 +183,8 @@ function makeCaseHTML(t: TestTreeLeaf): VisualizedSubtree {
await dedicatedWorker.run(rec, name);
} else if (sharedWorker) {
await sharedWorker.run(rec, name);
} else if (serviceWorker) {
await serviceWorker.run(rec, name);
} else {
await t.run(rec);
}
Expand Down
3 changes: 3 additions & 0 deletions src/common/runtime/wpt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ void (async () => {
const workerString = optionString('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');

Expand Down Expand Up @@ -68,6 +69,8 @@ void (async () => {
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);
}
Expand Down
8 changes: 6 additions & 2 deletions src/common/tools/dev_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,17 @@ app.get('/out/**/*.js', async (req, res, next) => {
const tsUrl = jsUrl.replace(/\.js$/, '.ts');
if (compileCache.has(tsUrl)) {
res.setHeader('Content-Type', 'application/javascript');
res.setHeader('Service-Worker-Allowed', '/');
res.send(compileCache.get(tsUrl));
return;
}

let absPath = path.join(srcDir, tsUrl);
// FIXME: I'm not sure if this is the way I should handle it...
const dir = jsUrl.endsWith('worker.js') ? path.resolve(srcDir, '../out') : srcDir;
let absPath = path.join(dir, tsUrl);
if (!fs.existsSync(absPath)) {
// The .ts file doesn't exist. Try .js file in case this is a .js/.d.ts pair.
absPath = path.join(srcDir, jsUrl);
absPath = path.join(dir, jsUrl);
}

try {
Expand All @@ -166,6 +169,7 @@ app.get('/out/**/*.js', async (req, res, next) => {
compileCache.set(tsUrl, result.code);

res.setHeader('Content-Type', 'application/javascript');
res.setHeader('Service-Worker-Allowed', '/');
res.send(result.code);
} else {
throw new Error(`Failed compile ${tsUrl}.`);
Expand Down
51 changes: 50 additions & 1 deletion src/common/tools/gen_listings_and_webworkers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ if (argv.length < 4) {
usage(0);
}

const myself = 'src/common/tools/gen_listings.ts';
const myself = 'src/common/tools/gen_listings_and_webworkers.ts';

const outDir = argv[2];

Expand Down Expand Up @@ -84,6 +84,55 @@ import { g as oldG } from '${relPathToSuiteRoot}/${entry.file.join('/')}.spec.js
// FIXME: Expose a proxied test interface. I think this can completely replace test_worker-worker.js
// (using this instead of that), but if not then hopefully it can at least share code with it.
console.log(oldG.iterate());
import { globalTestConfig } from '/out/common/framework/test_config.js';
import { Logger } from '/out/common/internal/logging/logger.js';
import { setDefaultRequestAdapterOptions } from '/out/common/util/navigator_gpu.js';
async function reportTestResults(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();
if (powerPreference || compatibility) {
setDefaultRequestAdapterOptions({
...(powerPreference && { powerPreference }),
// MAINTENANCE_TODO: Change this to whatever the option ends up being
...(compatibility && { compatibilityMode: true }),
});
}
// const testcases = Array.from(await loader.loadCases(parseQuery(query)));
// assert(testcases.length === 1, 'worker query resulted in != 1 cases');
// const testcase = testcases[0];
const testcase = { query }; // FIXME! I failed to figure out how to get a testcase from oldG and query ;(
const [rec, result] = log.record(testcase.query.toString());
// await testcase.run(rec, expectations);
result.status = 'pass'; // FIXME
result.timems = 42; // FIXME
this.postMessage({ query, result });
}
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);
};
};
`
);
}
Expand Down
Loading

0 comments on commit 7b74efd

Please sign in to comment.