summaryrefslogtreecommitdiffstats
path: root/test/wpt/runner/util.mjs
blob: ec284df68b264cdf9456bc02c664cde2a50dc644 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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)] ?? {} }
}