diff options
Diffstat (limited to 'test/wpt/runner/util.mjs')
-rw-r--r-- | test/wpt/runner/util.mjs | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/test/wpt/runner/util.mjs b/test/wpt/runner/util.mjs new file mode 100644 index 0000000..ec284df --- /dev/null +++ b/test/wpt/runner/util.mjs @@ -0,0 +1,172 @@ +import assert from 'node:assert' +import { exit } from 'node:process' +import { inspect } from 'node:util' +import tty from 'node:tty' +import { sep } from 'node:path' + +/** + * Parse the `Meta:` tags sometimes included in tests. + * These can include resources to inject, how long it should + * take to timeout, and which globals to expose. + * @example + * // META: timeout=long + * // META: global=window,worker + * // META: script=/common/utils.js + * // META: script=/common/get-host-info.sub.js + * // META: script=../request/request-error.js + * @see https://nodejs.org/api/readline.html#readline_example_read_file_stream_line_by_line + * @param {string} fileContents + */ +export function parseMeta (fileContents) { + const lines = fileContents.split(/\r?\n/g) + + const meta = { + /** @type {string|null} */ + timeout: null, + /** @type {string[]} */ + global: [], + /** @type {string[]} */ + scripts: [], + /** @type {string[]} */ + variant: [] + } + + for (const line of lines) { + if (!line.startsWith('// META: ')) { + break + } + + const groups = /^\/\/ META: (?<type>.*?)=(?<match>.*)$/.exec(line)?.groups + + if (!groups) { + console.log(`Failed to parse META tag: ${line}`) + exit(1) + } + + switch (groups.type) { + case 'variant': + meta[groups.type].push(groups.match) + break + case 'title': + case 'timeout': { + meta[groups.type] = groups.match + break + } + case 'global': { + // window,worker -> ['window', 'worker'] + meta.global.push(...groups.match.split(',')) + break + } + case 'script': { + // A relative or absolute file path to the resources + // needed for the current test. + meta.scripts.push(groups.match) + break + } + default: { + console.log(`Unknown META tag: ${groups.type}`) + exit(1) + } + } + } + + return meta +} + +/** + * @param {string} sub + */ +function parseSubBlock (sub) { + const subName = sub.includes('[') ? sub.slice(0, sub.indexOf('[')) : sub + const options = sub.matchAll(/\[(.*?)\]/gm) + + return { + sub: subName, + options: [...options].map(match => match[1]) + } +} + +/** + * @see https://web-platform-tests.org/writing-tests/server-pipes.html?highlight=sub#built-in-pipes + * @param {string} code + * @param {string} url + */ +export function handlePipes (code, url) { + const server = new URL(url) + + // "Substitutions are marked in a file using a block delimited by + // {{ and }}. Inside the block the following variables are available:" + return code.replace(/{{(.*?)}}/gm, (_, match) => { + const { sub } = parseSubBlock(match) + + switch (sub) { + // "The host name of the server excluding any subdomain part." + // eslint-disable-next-line no-fallthrough + case 'host': + // "The domain name of a particular subdomain e.g. + // {{domains[www]}} for the www subdomain." + // eslint-disable-next-line no-fallthrough + case 'domains': + // "The domain name of a particular subdomain for a particular host. + // The first key may be empty (designating the “default” host) or + // the value alt; i.e., {{hosts[alt][]}} (designating the alternate + // host)." + // eslint-disable-next-line no-fallthrough + case 'hosts': { + return 'localhost' + } + // "The port number of servers, by protocol e.g. {{ports[http][0]}} + // for the first (and, depending on setup, possibly only) http server" + case 'ports': { + return server.port + } + default: { + throw new TypeError(`Unknown substitute "${sub}".`) + } + } + }) +} + +/** + * Some test names may contain characters that JSON cannot handle. + * @param {string} name + */ +export function normalizeName (name) { + return name.replace(/(\v)/g, (_, match) => { + switch (inspect(match)) { + case '\'\\x0B\'': return '\\x0B' + default: return match + } + }) +} + +export function colors (str, color) { + assert(Object.hasOwn(inspect.colors, color), `Missing color ${color}`) + + if (!tty.WriteStream.prototype.hasColors()) { + return str + } + + const [start, end] = inspect.colors[color] + + return `\u001b[${start}m${str}\u001b[${end}m` +} + +/** @param {string} path */ +export function resolveStatusPath (path, status) { + const paths = path + .slice(process.cwd().length + sep.length) + .split(sep) + .slice(3) // [test, wpt, tests, fetch, b, c.js] -> [fetch, b, c.js] + + // skip the first folder name + for (let i = 1; i < paths.length - 1; i++) { + status = status[paths[i]] + + if (!status) { + break + } + } + + return { topLevel: status ?? {}, file: status?.[paths.at(-1)] ?? {} } +} |