1
1
import { EventEmitter , once } from 'node:events'
2
- import { readdirSync , readFileSync , statSync } from 'node:fs'
3
2
import { isAbsolute , join , resolve } from 'node:path'
3
+ import { existsSync , readdirSync , readFileSync , statSync , writeFileSync } from 'node:fs'
4
4
import { fileURLToPath } from 'node:url'
5
5
import { Worker } from 'node:worker_threads'
6
6
import { colors , handlePipes , normalizeName , parseMeta , resolveStatusPath } from './util.mjs'
@@ -9,6 +9,24 @@ const basePath = fileURLToPath(join(import.meta.url, '../../..'))
9
9
const testPath = join ( basePath , 'tests' )
10
10
const statusPath = join ( basePath , 'status' )
11
11
12
+ // https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L3705
13
+ function sanitizeUnpairedSurrogates ( str ) {
14
+ return str . replace (
15
+ / ( [ \ud800 - \udbff ] + ) (? ! [ \udc00 - \udfff ] ) | ( ^ | [ ^ \ud800 - \udbff ] ) ( [ \udc00 - \udfff ] + ) / g,
16
+ function ( _ , low , prefix , high ) {
17
+ let output = prefix || '' // Prefix may be undefined
18
+ const string = low || high // Only one of these alternates can match
19
+ for ( let i = 0 ; i < string . length ; i ++ ) {
20
+ output += codeUnitStr ( string [ i ] )
21
+ }
22
+ return output
23
+ } )
24
+ }
25
+
26
+ function codeUnitStr ( char ) {
27
+ return 'U+' + char . charCodeAt ( 0 ) . toString ( 16 )
28
+ }
29
+
12
30
export class WPTRunner extends EventEmitter {
13
31
/** @type {string } */
14
32
#folderName
@@ -33,6 +51,12 @@ export class WPTRunner extends EventEmitter {
33
51
34
52
#uncaughtExceptions = [ ]
35
53
54
+ /** @type {boolean } */
55
+ #appendReport
56
+
57
+ /** @type {string } */
58
+ #reportPath
59
+
36
60
#stats = {
37
61
completed : 0 ,
38
62
failed : 0 ,
@@ -41,7 +65,7 @@ export class WPTRunner extends EventEmitter {
41
65
skipped : 0
42
66
}
43
67
44
- constructor ( folder , url ) {
68
+ constructor ( folder , url , { appendReport = false , reportPath } = { } ) {
45
69
super ( )
46
70
47
71
this . #folderName = folder
@@ -52,6 +76,19 @@ export class WPTRunner extends EventEmitter {
52
76
( file ) => file . endsWith ( '.any.js' )
53
77
)
54
78
)
79
+
80
+ if ( appendReport ) {
81
+ if ( ! reportPath ) {
82
+ throw new TypeError ( 'reportPath must be provided when appendReport is true' )
83
+ }
84
+ if ( ! existsSync ( reportPath ) ) {
85
+ throw new TypeError ( 'reportPath is invalid' )
86
+ }
87
+ }
88
+
89
+ this . #appendReport = appendReport
90
+ this . #reportPath = reportPath
91
+
55
92
this . #status = JSON . parse ( readFileSync ( join ( statusPath , `${ folder } .status.json` ) ) )
56
93
this . #url = url
57
94
@@ -148,13 +185,29 @@ export class WPTRunner extends EventEmitter {
148
185
}
149
186
} )
150
187
188
+ let result , report
189
+ if ( this . #appendReport) {
190
+ report = JSON . parse ( readFileSync ( this . #reportPath) )
191
+
192
+ const fileUrl = new URL ( `/${ this . #folderName} ${ test . slice ( this . #folderPath. length ) } ` , 'http://wpt' )
193
+ fileUrl . pathname = fileUrl . pathname . replace ( / \. j s $ / , '.html' )
194
+ fileUrl . search = variant
195
+
196
+ result = {
197
+ test : fileUrl . href . slice ( fileUrl . origin . length ) ,
198
+ subtests : [ ] ,
199
+ status : 'OK'
200
+ }
201
+ report . results . push ( result )
202
+ }
203
+
151
204
activeWorkers . add ( worker )
152
205
// These values come directly from the web-platform-tests
153
206
const timeout = meta . timeout === 'long' ? 60_000 : 10_000
154
207
155
208
worker . on ( 'message' , ( message ) => {
156
209
if ( message . type === 'result' ) {
157
- this . handleIndividualTestCompletion ( message , status , test )
210
+ this . handleIndividualTestCompletion ( message , status , test , meta , result )
158
211
} else if ( message . type === 'completion' ) {
159
212
this . handleTestCompletion ( worker )
160
213
} else if ( message . type === 'error' ) {
@@ -174,6 +227,10 @@ export class WPTRunner extends EventEmitter {
174
227
console . log ( `Test took ${ ( performance . now ( ) - start ) . toFixed ( 2 ) } ms` )
175
228
console . log ( '=' . repeat ( 96 ) )
176
229
230
+ if ( result ?. subtests . length > 0 ) {
231
+ writeFileSync ( this . #reportPath, JSON . stringify ( report ) )
232
+ }
233
+
177
234
finishedFiles ++
178
235
activeWorkers . delete ( worker )
179
236
} catch ( e ) {
@@ -192,15 +249,25 @@ export class WPTRunner extends EventEmitter {
192
249
/**
193
250
* Called after a test has succeeded or failed.
194
251
*/
195
- handleIndividualTestCompletion ( message , status , path ) {
252
+ handleIndividualTestCompletion ( message , status , path , meta , wptResult ) {
196
253
const { file, topLevel } = status
197
254
198
255
if ( message . type === 'result' ) {
199
256
this . #stats. completed += 1
200
257
258
+ if ( / ^ U n t i t l e d ( \d + ) ? $ / . test ( message . result . name ) ) {
259
+ message . result . name = `${ meta . title } ${ message . result . name . slice ( 8 ) } `
260
+ }
261
+
201
262
if ( message . result . status === 1 ) {
202
263
this . #stats. failed += 1
203
264
265
+ wptResult ?. subtests . push ( {
266
+ status : 'FAIL' ,
267
+ name : sanitizeUnpairedSurrogates ( message . result . name ) ,
268
+ message : sanitizeUnpairedSurrogates ( message . result . message )
269
+ } )
270
+
204
271
const name = normalizeName ( message . result . name )
205
272
206
273
if ( file . flaky ?. includes ( name ) ) {
@@ -219,6 +286,10 @@ export class WPTRunner extends EventEmitter {
219
286
console . error ( message . result )
220
287
}
221
288
} else {
289
+ wptResult ?. subtests . push ( {
290
+ status : 'PASS' ,
291
+ name : sanitizeUnpairedSurrogates ( message . result . name )
292
+ } )
222
293
this . #stats. success += 1
223
294
}
224
295
}
0 commit comments