Skip to content

Commit 71b9f2b

Browse files
committed
add WPT report
1 parent 0f960bd commit 71b9f2b

4 files changed

Lines changed: 156 additions & 60 deletions

File tree

test/web-platform-tests/runner/config.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
"ssl": {
44
"type": "pregenerated",
55
"pregenerated": {
6-
"ca_cert_path": "./certs/cacert.pem",
7-
"host_cert_path": "./certs/web-platform.test.pem",
8-
"host_key_path": "./certs/web-platform.test.key"
6+
"ca_cert_path": "../../../test/web-platform-tests/runner/certs/cacert.pem",
7+
"host_cert_path": "../../../test/web-platform-tests/runner/certs/web-platform.test.pem",
8+
"host_key_path": "../../../test/web-platform-tests/runner/certs/web-platform.test.key"
99
}
1010
}
1111
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L3705
2+
export function sanitizeUnpairedSurrogates (str) {
3+
return str.replace(
4+
/([\ud800-\udbff]+)(?![\udc00-\udfff])|(^|[^\ud800-\udbff])([\udc00-\udfff]+)/g,
5+
function (_, low, prefix, high) {
6+
let output = prefix || '' // Prefix may be undefined
7+
const string = low || high // Only one of these alternates can match
8+
for (let i = 0; i < string.length; i++) {
9+
output += codeUnitStr(string[i])
10+
}
11+
return output
12+
})
13+
}
14+
15+
function codeUnitStr (char) {
16+
return 'U+' + char.charCodeAt(0).toString(16)
17+
}
18+
19+
/**
20+
* @type {import('../../../lib/util/promise')['createDeferredPromise']}
21+
*/
22+
export const createDeferredPromise =
23+
Promise.withResolvers?.bind(Promise) ?? (await import('../../../lib/util/promise')).createDeferredPromise

test/web-platform-tests/wpt-runner.mjs

Lines changed: 129 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,30 @@
11
import { spawn } from 'node:child_process'
2-
import { once } from 'node:events'
32
import { readFileSync, writeFileSync, existsSync } from 'node:fs'
43
import { join } from 'node:path'
54
import { createInterface } from 'node:readline'
65
import { debuglog } from 'node:util'
6+
import {
7+
sanitizeUnpairedSurrogates,
8+
createDeferredPromise
9+
} from './runner/utils.mjs'
710

811
const WPT_DIR = join(import.meta.dirname, 'wpt')
912
const EXPECTATION_PATH = join(import.meta.dirname, 'expectation.json')
1013

1114
const log = debuglog('UNDICI_WPT')
1215

1316
async function runWithTestUtil (testFunction) {
17+
const { promise, resolve, reject } = createDeferredPromise()
18+
1419
console.log('Starting WPT server...')
1520
const proc = spawn('python3', ['wpt', 'serve', '--config', '../runner/config.json'], {
1621
cwd: WPT_DIR,
1722
stdio: 'inherit'
1823
})
1924

25+
proc.once('exit', () => resolve())
26+
proc.once('error', (err) => reject(err))
27+
2028
const serverUrl = 'http://web-platform.test:8000/'
2129

2230
// Wait for server to be ready
@@ -36,76 +44,87 @@ async function runWithTestUtil (testFunction) {
3644

3745
console.log(`✅ WPT server started at ${serverUrl}`)
3846

47+
let results
48+
3949
try {
40-
return await testFunction()
50+
results = await testFunction()
4151
} finally {
4252
console.log('Killing WPT server')
4353

44-
proc.kill()
45-
await once(proc, 'exit')
54+
if (!proc.killed) {
55+
proc.kill('SIGINT')
56+
}
4657
}
58+
59+
await promise
60+
return results
4761
}
4862

4963
function runSingleTest (url, options, expectation, timeout = 10000) {
5064
const startTime = Date.now()
65+
const { promise, resolve, reject } = createDeferredPromise()
66+
67+
const proc = spawn('node', [
68+
'--expose-gc',
69+
'--no-warnings',
70+
join(import.meta.dirname, 'runner/test-runner.mjs'),
71+
url.toString()
72+
], {
73+
stdio: ['ignore', 'pipe', 'pipe'],
74+
env: {
75+
...process.env,
76+
NO_COLOR: '1'
77+
}
78+
})
5179

52-
return new Promise((resolve) => {
53-
const proc = spawn('node', [
54-
'--expose-gc',
55-
'--no-warnings',
56-
join(import.meta.dirname, 'runner/test-runner.mjs'),
57-
url.toString()
58-
], {
59-
stdio: ['ignore', 'pipe', 'pipe'],
60-
env: {
61-
...process.env,
62-
NO_COLOR: '1'
63-
}
64-
})
65-
66-
const cases = []
67-
let harnessStatus = null
68-
let output = ''
80+
const cases = []
81+
let harnessStatus = null
82+
let output = ''
6983

70-
const timer = setTimeout(() => {
84+
const timer = setTimeout(() => {
85+
if (!proc.killed) {
7186
proc.kill('SIGINT')
72-
}, timeout)
73-
74-
proc.stdout.setEncoding('utf-8')
75-
proc.stdout.on('data', (chunk) => {
76-
output += chunk
77-
78-
let delimiterIndex
79-
while ((delimiterIndex = output.indexOf('#$#$#')) !== -1) {
80-
const endIndex = output.indexOf('\n', delimiterIndex)
81-
if (endIndex !== -1) {
82-
const message = output.slice(delimiterIndex + 5, endIndex)
83-
try {
84-
const { tests, harnessStatus: _harnessStatus } = JSON.parse(message)
85-
harnessStatus = _harnessStatus
86-
cases.push(...tests)
87-
} catch (e) {
88-
console.error('Failed to parse:', message)
89-
}
90-
output = output.slice(endIndex + 1)
91-
} else {
92-
break // Wait for more data
87+
}
88+
}, timeout)
89+
90+
proc.stdout.setEncoding('utf-8')
91+
proc.stdout.on('data', (chunk) => {
92+
output += chunk
93+
94+
let delimiterIndex
95+
while ((delimiterIndex = output.indexOf('#$#$#')) !== -1) {
96+
const endIndex = output.indexOf('\n', delimiterIndex)
97+
if (endIndex !== -1) {
98+
const message = output.slice(delimiterIndex + 5, endIndex)
99+
try {
100+
const { tests, harnessStatus: _harnessStatus } = JSON.parse(message)
101+
harnessStatus = _harnessStatus
102+
cases.push(...tests)
103+
} catch (e) {
104+
console.error('Failed to parse:', message)
93105
}
106+
output = output.slice(endIndex + 1)
107+
} else {
108+
break // Wait for more data
94109
}
95-
})
110+
}
111+
})
96112

97-
proc.on('exit', () => {
98-
clearTimeout(timer)
99-
const duration = Date.now() - startTime
113+
proc.once('exit', () => {
114+
clearTimeout(timer)
115+
const duration = Date.now() - startTime
100116

101-
resolve({
102-
status: harnessStatus?.status ?? 1,
103-
harnessStatus,
104-
duration,
105-
cases
106-
})
117+
resolve({
118+
status: harnessStatus?.status ?? 1,
119+
harnessStatus,
120+
duration,
121+
cases
107122
})
108123
})
124+
125+
proc.once('error', (err) => reject(err))
126+
127+
return promise
109128
}
110129

111130
function getExpectation () {
@@ -203,6 +222,56 @@ function discoverTestsToRun (filter, expectation) {
203222
return tests
204223
}
205224

225+
function generateWPTReport (results, startTime, endTime) {
226+
const reportResults = []
227+
228+
for (const { test, result } of results) {
229+
const status = result.status !== 0
230+
? 'CRASH'
231+
: result.harnessStatus?.status === 0
232+
? 'OK'
233+
: 'ERROR'
234+
235+
const message = result.harnessStatus?.message ?? null
236+
const reportResult = {
237+
test: test.path,
238+
subtests: result.cases.map((c) => {
239+
let expected
240+
if (c.status !== 0) {
241+
if (typeof test.expectation === 'boolean') {
242+
expected = test.expectation ? 'PASS' : 'FAIL'
243+
} else if (Array.isArray(test.expectation)) {
244+
expected = test.expectation.includes(c.name) ? 'FAIL' : 'PASS'
245+
} else {
246+
expected = 'PASS'
247+
}
248+
}
249+
250+
return {
251+
name: sanitizeUnpairedSurrogates(c.name),
252+
status: c.status === 0 ? 'PASS' : 'FAIL',
253+
message: c.message ? sanitizeUnpairedSurrogates(c.message) : null,
254+
expected,
255+
known_intermittent: []
256+
}
257+
}),
258+
status,
259+
message: message ? sanitizeUnpairedSurrogates(message) : null,
260+
duration: result.duration,
261+
expected: status === 'OK' ? undefined : 'OK',
262+
known_intermittent: []
263+
}
264+
265+
reportResults.push(reportResult)
266+
}
267+
268+
return {
269+
time_start: startTime,
270+
time_end: endTime,
271+
results: reportResults
272+
}
273+
}
274+
206275
async function setup () {
207276
console.log('Setting up WPT environment...')
208277

@@ -330,6 +399,7 @@ async function run (filters = []) {
330399

331400
const timeout = test.options.timeout === 'long' ? 60_000 : 10_000
332401
const result = await runSingleTest(test.url, test.options, test.expectation, timeout)
402+
333403
testResults.push({ test, result })
334404

335405
console.log(`${test.path}: ${result.cases.length} tests ran in ${result.duration}ms:`)
@@ -372,6 +442,11 @@ async function run (filters = []) {
372442
console.log('='.repeat(50))
373443

374444
updateExpectations(results)
445+
446+
if (process.env.WPT_REPORT) {
447+
const report = generateWPTReport(results, startTime, endTime)
448+
writeFileSync(process.env.WPT_REPORT, JSON.stringify(report))
449+
}
375450
}
376451

377452
// CLI
@@ -384,10 +459,7 @@ switch (command) {
384459
break
385460
case 'run':
386461
// TODO: find what's causing the unsettled top-level await
387-
run(filters).then(
388-
() => process.exit(0),
389-
() => process.exit(1)
390-
)
462+
await run(filters)
391463
break
392464
default:
393465
console.log(`

wpt-report.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)