summaryrefslogtreecommitdiffstats
path: root/test/wpt/runner/util.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'test/wpt/runner/util.mjs')
-rw-r--r--test/wpt/runner/util.mjs172
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)] ?? {} }
+}