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
@@ -31,6 +49,12 @@ export class WPTRunner extends EventEmitter {
31
49
/** Tests that have expectedly failed mapped by file name */
32
50
#statusOutput = { }
33
51
52
+ /** @type {boolean } */
53
+ #appendReport
54
+
55
+ /** @type {string } */
56
+ #reportPath
57
+
34
58
#stats = {
35
59
completed : 0 ,
36
60
failed : 0 ,
@@ -39,7 +63,7 @@ export class WPTRunner extends EventEmitter {
39
63
skipped : 0
40
64
}
41
65
42
- constructor ( folder , url ) {
66
+ constructor ( folder , url , { appendReport = false , reportPath } = { } ) {
43
67
super ( )
44
68
45
69
this . #folderName = folder
@@ -50,6 +74,19 @@ export class WPTRunner extends EventEmitter {
50
74
( file ) => file . endsWith ( '.any.js' )
51
75
)
52
76
)
77
+
78
+ if ( appendReport ) {
79
+ if ( ! reportPath ) {
80
+ throw new TypeError ( 'reportPath must be provided when appendReport is true' )
81
+ }
82
+ if ( ! existsSync ( reportPath ) ) {
83
+ throw new TypeError ( 'reportPath is invalid' )
84
+ }
85
+ }
86
+
87
+ this . #appendReport = appendReport
88
+ this . #reportPath = reportPath
89
+
53
90
this . #status = JSON . parse ( readFileSync ( join ( statusPath , `${ folder } .status.json` ) ) )
54
91
this . #url = url
55
92
@@ -139,13 +176,29 @@ export class WPTRunner extends EventEmitter {
139
176
}
140
177
} )
141
178
179
+ let result , report
180
+ if ( this . #appendReport) {
181
+ report = JSON . parse ( readFileSync ( this . #reportPath) )
182
+
183
+ const fileUrl = new URL ( `/${ this . #folderName} ${ test . slice ( this . #folderPath. length ) } ` , 'http://wpt' )
184
+ fileUrl . pathname = fileUrl . pathname . replace ( / \. j s $ / , '.html' )
185
+ fileUrl . search = variant
186
+
187
+ result = {
188
+ test : fileUrl . href . slice ( fileUrl . origin . length ) ,
189
+ subtests : [ ] ,
190
+ status : 'OK'
191
+ }
192
+ report . results . push ( result )
193
+ }
194
+
142
195
activeWorkers . add ( worker )
143
196
// These values come directly from the web-platform-tests
144
197
const timeout = meta . timeout === 'long' ? 60_000 : 10_000
145
198
146
199
worker . on ( 'message' , ( message ) => {
147
200
if ( message . type === 'result' ) {
148
- this . handleIndividualTestCompletion ( message , status , test )
201
+ this . handleIndividualTestCompletion ( message , status , test , meta , result )
149
202
} else if ( message . type === 'completion' ) {
150
203
this . handleTestCompletion ( worker )
151
204
}
@@ -161,6 +214,10 @@ export class WPTRunner extends EventEmitter {
161
214
console . log ( `Test took ${ ( performance . now ( ) - start ) . toFixed ( 2 ) } ms` )
162
215
console . log ( '=' . repeat ( 96 ) )
163
216
217
+ if ( result ?. subtests . length > 0 ) {
218
+ writeFileSync ( this . #reportPath, JSON . stringify ( report ) )
219
+ }
220
+
164
221
finishedFiles ++
165
222
activeWorkers . delete ( worker )
166
223
} catch ( e ) {
@@ -179,15 +236,25 @@ export class WPTRunner extends EventEmitter {
179
236
/**
180
237
* Called after a test has succeeded or failed.
181
238
*/
182
- handleIndividualTestCompletion ( message , status , path ) {
239
+ handleIndividualTestCompletion ( message , status , path , meta , wptResult ) {
183
240
const { file, topLevel } = status
184
241
185
242
if ( message . type === 'result' ) {
186
243
this . #stats. completed += 1
187
244
245
+ if ( / ^ U n t i t l e d ( \d + ) ? $ / . test ( message . result . name ) ) {
246
+ message . result . name = `${ meta . title } ${ message . result . name . slice ( 8 ) } `
247
+ }
248
+
188
249
if ( message . result . status === 1 ) {
189
250
this . #stats. failed += 1
190
251
252
+ wptResult ?. subtests . push ( {
253
+ status : 'FAIL' ,
254
+ name : sanitizeUnpairedSurrogates ( message . result . name ) ,
255
+ message : sanitizeUnpairedSurrogates ( message . result . message )
256
+ } )
257
+
191
258
const name = normalizeName ( message . result . name )
192
259
193
260
if ( file . flaky ?. includes ( name ) ) {
@@ -206,6 +273,10 @@ export class WPTRunner extends EventEmitter {
206
273
console . error ( message . result )
207
274
}
208
275
} else {
276
+ wptResult ?. subtests . push ( {
277
+ status : 'PASS' ,
278
+ name : sanitizeUnpairedSurrogates ( message . result . name )
279
+ } )
209
280
this . #stats. success += 1
210
281
}
211
282
}
0 commit comments