diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /dom/webgpu/tests/cts/checkout/src/common/tools/dev_server.ts | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/common/tools/dev_server.ts')
-rw-r--r-- | dom/webgpu/tests/cts/checkout/src/common/tools/dev_server.ts | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/common/tools/dev_server.ts b/dom/webgpu/tests/cts/checkout/src/common/tools/dev_server.ts new file mode 100644 index 0000000000..57cb6a7ea4 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/common/tools/dev_server.ts @@ -0,0 +1,214 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +import * as babel from '@babel/core'; +import * as chokidar from 'chokidar'; +import * as express from 'express'; +import * as morgan from 'morgan'; +import * as portfinder from 'portfinder'; +import * as serveIndex from 'serve-index'; + +import { makeListing } from './crawl.js'; + +// Make sure that makeListing doesn't cache imported spec files. See crawl(). +process.env.STANDALONE_DEV_SERVER = '1'; + +function usage(rc: number): void { + console.error(`\ +Usage: + tools/dev_server + tools/dev_server 0.0.0.0 + npm start + npm start 0.0.0.0 + +By default, serves on localhost only. If the argument 0.0.0.0 is passed, serves on all interfaces. +`); + process.exit(rc); +} + +const srcDir = path.resolve(__dirname, '../../'); + +// Import the project's babel.config.js. We'll use the same config for the runtime compiler. +const babelConfig = { + ...require(path.resolve(srcDir, '../babel.config.js'))({ + cache: () => { + /* not used */ + }, + }), + sourceMaps: 'inline', +}; + +// Caches for the generated listing file and compiled TS sources to speed up reloads. +// Keyed by suite name +const listingCache = new Map<string, string>(); +// Keyed by the path to the .ts file, without src/ +const compileCache = new Map<string, string>(); + +console.log('Watching changes in', srcDir); +const watcher = chokidar.watch(srcDir, { + persistent: true, +}); + +/** + * Handler to dirty the compile cache for changed .ts files. + */ +function dirtyCompileCache(absPath: string, stats?: fs.Stats) { + const relPath = path.relative(srcDir, absPath); + if ((stats === undefined || stats.isFile()) && relPath.endsWith('.ts')) { + const tsUrl = relPath; + if (compileCache.has(tsUrl)) { + console.debug('Dirtying compile cache', tsUrl); + } + compileCache.delete(tsUrl); + } +} + +/** + * Handler to dirty the listing cache for: + * - Directory changes + * - .spec.ts changes + * - README.txt changes + * Also dirties the compile cache for changed files. + */ +function dirtyListingAndCompileCache(absPath: string, stats?: fs.Stats) { + const relPath = path.relative(srcDir, absPath); + + const segments = relPath.split(path.sep); + // The listing changes if the directories change, or if a .spec.ts file is added/removed. + const listingChange = + // A directory or a file with no extension that we can't stat. + // (stat doesn't work for deletions) + ((path.extname(relPath) === '' && (stats === undefined || !stats.isFile())) || + // A spec file + relPath.endsWith('.spec.ts') || + // A README.txt + path.basename(relPath, 'txt') === 'README') && + segments.length > 0; + if (listingChange) { + const suite = segments[0]; + if (listingCache.has(suite)) { + console.debug('Dirtying listing cache', suite); + } + listingCache.delete(suite); + } + + dirtyCompileCache(absPath, stats); +} + +watcher.on('add', dirtyListingAndCompileCache); +watcher.on('unlink', dirtyListingAndCompileCache); +watcher.on('addDir', dirtyListingAndCompileCache); +watcher.on('unlinkDir', dirtyListingAndCompileCache); +watcher.on('change', dirtyCompileCache); + +const app = express(); + +// Send Chrome Origin Trial tokens +app.use((_req, res, next) => { + res.header('Origin-Trial', [ + // Token for http://localhost:8080 + 'AvyDIV+RJoYs8fn3W6kIrBhWw0te0klraoz04mw/nPb8VTus3w5HCdy+vXqsSzomIH745CT6B5j1naHgWqt/tw8AAABJeyJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJmZWF0dXJlIjoiV2ViR1BVIiwiZXhwaXJ5IjoxNjYzNzE4Mzk5fQ==', + ]); + next(); +}); + +// Set up logging +app.use(morgan('dev')); + +// Serve the standalone runner directory +app.use('/standalone', express.static(path.resolve(srcDir, '../standalone'))); +// Add out-wpt/ build dir for convenience +app.use('/out-wpt', express.static(path.resolve(srcDir, '../out-wpt'))); +app.use('/docs/tsdoc', express.static(path.resolve(srcDir, '../docs/tsdoc'))); + +// Serve a suite's listing.js file by crawling the filesystem for all tests. +app.get('/out/:suite([a-zA-Z0-9_-]+)/listing.js', async (req, res, next) => { + const suite = req.params['suite']; + + if (listingCache.has(suite)) { + res.setHeader('Content-Type', 'application/javascript'); + res.send(listingCache.get(suite)); + return; + } + + try { + const listing = await makeListing(path.resolve(srcDir, suite, 'listing.ts')); + const result = `export const listing = ${JSON.stringify(listing, undefined, 2)}`; + + listingCache.set(suite, result); + res.setHeader('Content-Type', 'application/javascript'); + res.send(result); + } catch (err) { + next(err); + } +}); + +// Serve all other .js files by fetching the source .ts file and compiling it. +app.get('/out/**/*.js', async (req, res, next) => { + const jsUrl = path.relative('/out', req.url); + const tsUrl = jsUrl.replace(/\.js$/, '.ts'); + if (compileCache.has(tsUrl)) { + res.setHeader('Content-Type', 'application/javascript'); + res.send(compileCache.get(tsUrl)); + return; + } + + let absPath = path.join(srcDir, 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); + } + + try { + const result = await babel.transformFileAsync(absPath, babelConfig); + if (result && result.code) { + compileCache.set(tsUrl, result.code); + + res.setHeader('Content-Type', 'application/javascript'); + res.send(result.code); + } else { + throw new Error(`Failed compile ${tsUrl}.`); + } + } catch (err) { + next(err); + } +}); + +// Serve everything else (not .js) as static, and directories as directory listings. +app.use('/out', serveIndex(path.resolve(srcDir, '../src'))); +app.use('/out', express.static(path.resolve(srcDir, '../src'))); + +void (async () => { + let host = '127.0.0.1'; + if (process.argv.length >= 3) { + if (process.argv.length !== 3) usage(1); + if (process.argv[2] === '0.0.0.0') { + host = '0.0.0.0'; + } else { + usage(1); + } + } + + console.log(`Finding an available port on ${host}...`); + const kPortFinderStart = 8080; + const port = await portfinder.getPortPromise({ host, port: kPortFinderStart }); + + watcher.on('ready', () => { + // Listen on the available port. + app.listen(port, host, () => { + console.log('Standalone test runner running at:'); + if (host === '0.0.0.0') { + for (const iface of Object.values(os.networkInterfaces())) { + for (const details of iface || []) { + if (details.family === 'IPv4') { + console.log(` http://${details.address}:${port}/standalone/`); + } + } + } + } else { + console.log(` http://${host}:${port}/standalone/`); + } + }); + }); +})(); |