1/**
2 * Headless GPU test runner: serves dist/, opens test.html in headless Chrome
3 * (hardware WebGPU if there is any, the SwiftShader software adapter
4 * otherwise), and reports the suite's results.
5 *
6 * Run after `vite build`: node scripts/test-gpu.mjs [?m=2&norder=4&k=4]
7 */
8import { createServer } from 'node:http';
9import { readFile } from 'node:fs/promises';
10import { extname, join } from 'node:path';
11import puppeteer from 'puppeteer-core';
13const DIST = new URL('../dist/', import.meta.url).pathname;
14const CHROME = process.env.CHROME_PATH ?? '/usr/bin/google-chrome';
15const QUERY = process.argv[2] ?? '';
16const MIME = {
17 '.html': 'text/html',
18 '.js': 'text/javascript',
19 '.css': 'text/css',
20 '.json': 'application/json',
21};
23const server = createServer(async (req, res) => {
24 try {
25 const path = req.url === '/' ? '/index.html' : req.url.split('?')[0];
26 const data = await readFile(join(DIST, path));
27 res.writeHead(200, { 'content-type': MIME[extname(path)] ?? 'application/octet-stream' });
28 res.end(data);
29 } catch {
30 res.writeHead(404);
31 res.end('not found');
32 }
33});
34await new Promise((r) => server.listen(0, '127.0.0.1', r));
35const port = server.address().port;
37const flagSets = [
38 ['--headless=new', '--no-sandbox', '--enable-unsafe-webgpu', '--enable-features=Vulkan'],
39 ['--headless=new', '--no-sandbox', '--enable-unsafe-webgpu', '--use-webgpu-adapter=swiftshader', '--enable-unsafe-swiftshader'],
40];
41if (process.env.SWIFTSHADER_ONLY) flagSets.shift();
43let final = null;
44for (const flags of flagSets) {
45 const browser = await puppeteer.launch({ executablePath: CHROME, args: flags, protocolTimeout: 1_800_000 });
46 try {
47 const page = await browser.newPage();
48 page.on('console', (msg) => {
49 const t = msg.text();
50 if (!/^(ok|FAIL|surface|device|operator|FATAL|\s)/.test(t)) console.log(` [page] ${t}`);
51 else console.log(t);
52 });
53 page.on('pageerror', (err) => console.log(` [pageerror] ${err.message}`));
54 await page.goto(`http://127.0.0.1:${port}/test.html${QUERY}`, { waitUntil: 'load' });
55 const results = await page.waitForFunction(() => window.__RESULTS__, { timeout: 1_800_000, polling: 500 });
56 final = await results.jsonValue();
57 } catch (e) {
58 console.error(`run with flags [${flags.join(' ')}] failed: ${e.message}`);
59 } finally {
60 await browser.close();
61 }
62 if (final && !final.fatal) break;
63 if (final?.fatal) console.log(`fatal: ${final.fatal}`);
64 console.log('retrying with next flag set…');
65}
66server.close();
67if (!final) {
68 console.error('no results');
69 process.exit(1);
70}
71console.log(`\n${final.passed} passed, ${final.failed} failed`);
72process.exit(final.failed ? 1 : 0);