Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer, web workflow engine and protocol, tooling, docs and task records. Large generated evidence, user-testing archives, build snapshots and imported test assets remain untracked. Co-Authored-By: Claude Code <noreply@anthropic.com>
41 lines
5.7 KiB
TypeScript
41 lines
5.7 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { createHash } from 'node:crypto';
|
|
import { COMPUTE_LIMITS } from '../../protocol/gpu-compute';
|
|
for(const mode of ['page-async', 'worker-async', 'worker-blocking', 'page-relay-blocking', 'page-relay-webgl-blocking', 'page-relay-presented-webgl-blocking']) test(`Captured native compute requests complete with ${mode}`, async ({ page }, info) => {
|
|
const blocking = mode.endsWith('blocking'), nested = mode !== 'page-async', relay = mode.startsWith('page-relay'), webgl = mode.includes('webgl'), presented = mode.includes('presented');
|
|
test.setTimeout(180000); const capture = process.env.WBV2_COMPUTE_CAPTURE_FILE!; expect(capture).toBeTruthy();
|
|
const bytes = fs.readFileSync(capture), requests = JSON.parse(bytes.toString()); expect(requests.length).toBeGreaterThan(0); expect(requests.every((request: any) => Array.isArray(request.releaseOwners) || Array.isArray(request.data))).toBe(true);
|
|
const assets = path.join(process.env.WBV2_CASE_APP!, 'assets'), names = fs.readdirSync(assets).filter(name => name.startsWith('gpu-compute.worker-') && name.endsWith('.js')); expect(names).toHaveLength(1);
|
|
await page.route('**/__native_gpu_replay', route => route.fulfill({ status: 200, headers: { 'Content-Type': 'text/html', 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp' }, body: '<!doctype html><title>Native request replay diagnostic</title>' }));
|
|
await page.goto('/__native_gpu_replay'); expect(await page.evaluate(() => crossOriginIsolated)).toBe(true);
|
|
const results = await page.evaluate(async input => {
|
|
const run = async ({ requests, url, errorBytes, blocking, relay, webgl, canvas }: any) => {
|
|
const worker: any = relay ? { postMessage: (request: any) => (self as any).postMessage({ gpu: request }), terminate: () => {} } : new Worker(url, { type: 'module' });
|
|
if(!relay)await new Promise<void>((resolve, reject) => { const timer = setTimeout(() => reject(new Error('Replay GPU worker startup timeout')), 30000); worker.onerror = (event: ErrorEvent) => { clearTimeout(timer); reject(new Error(event.message)); }; worker.onmessage = (event: MessageEvent) => { if(event.data?.type === 'gpu-compute-ready'){clearTimeout(timer); resolve();} }; });
|
|
try {
|
|
const output: any[] = [];
|
|
const gl = webgl ? (canvas ?? new OffscreenCanvas(64, 64)).getContext('webgl2') : null; if(webgl && !gl)throw new Error('Replay WebGL context unavailable');
|
|
for(const request of requests){
|
|
if(request.releaseOwners){worker.postMessage({ releaseOwners: request.releaseOwners }); continue;}
|
|
if(gl){gl.clearColor(0, 0, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT);}
|
|
const control = new Int32Array(new SharedArrayBuffer(8)), error = new Uint8Array(new SharedArrayBuffer(errorBytes)), data = new Uint8Array(new SharedArrayBuffer(request.data.length)); data.set(request.data);
|
|
const started = performance.now(); worker.postMessage({ job: request.job, sizes: request.sizes, control: control.buffer, error: error.buffer, data: data.buffer });
|
|
while(Atomics.load(control, 0) === 0 && performance.now() - started < 30000){if(blocking)Atomics.wait(control, 0, 0, 50); else await new Promise(resolve => setTimeout(resolve, 10));}
|
|
const state = Atomics.load(control, 0), length = Math.min(error.length, Math.max(0, Atomics.load(control, 1))); output.push({ state, elapsedMs: performance.now() - started, diagnostic: new TextDecoder().decode(error.slice(0, length)), data: Array.from(data) }); if(state !== 1)break;
|
|
}
|
|
return output;
|
|
} finally { worker.terminate(); }
|
|
};
|
|
const parameters = { ...input, url: new URL(input.url, location.href).href }; if(!input.nested)return run(parameters);
|
|
let canvas: OffscreenCanvas | undefined; if(input.presented){const element = document.createElement('canvas'); element.width = element.height = 64; document.body.append(element); canvas = element.transferControlToOffscreen();}
|
|
const gpu = input.relay ? new Worker(parameters.url, { type: 'module' }) : null;
|
|
if(gpu)await new Promise<void>((resolve, reject) => { const timer = setTimeout(() => reject(new Error('Sibling GPU startup timeout')), 30000); gpu.onerror = event => { clearTimeout(timer); reject(new Error(event.message)); }; gpu.onmessage = event => { if(event.data?.type === 'gpu-compute-ready'){clearTimeout(timer); resolve();} }; });
|
|
const script = URL.createObjectURL(new Blob([`self.onmessage = async event => { try { self.postMessage({ result: await (${run.toString()})(event.data) }); } catch(error) { self.postMessage({ error: String(error) }); } };`], { type: 'text/javascript' })), parent = new Worker(script);
|
|
try{return await new Promise<any[]>((resolve, reject) => { parent.onerror = event => reject(new Error(event.message)); parent.onmessage = event => { if(event.data.gpu){gpu!.postMessage(event.data.gpu); return;} event.data.error ? reject(new Error(event.data.error)) : resolve(event.data.result); }; parent.postMessage({ ...parameters, canvas }, canvas ? [canvas] : []); });}finally{parent.terminate(); gpu?.terminate(); URL.revokeObjectURL(script);}
|
|
}, { requests, url: `/assets/${names[0]}`, errorBytes: COMPUTE_LIMITS.errorBytes, blocking, nested, relay, webgl, presented });
|
|
fs.writeFileSync(info.outputPath('native-compute-replay.json'), JSON.stringify({ capture, captureSha256: createHash('sha256').update(bytes).digest('hex'), worker: names[0], blocking, results, scope: 'DIAGNOSTIC_ONLY_NOT_PRODUCT_ACCEPTANCE' }));
|
|
const jobs = requests.filter((request: any) => request.job); expect(results).toHaveLength(jobs.length); expect(results.map((result: any) => result.state)).toEqual(jobs.map(() => 1));
|
|
});
|