11import { spawn } from 'node:child_process'
2- import { once } from 'node:events'
32import { readFileSync , writeFileSync , existsSync } from 'node:fs'
43import { join } from 'node:path'
54import { createInterface } from 'node:readline'
65import { debuglog } from 'node:util'
6+ import {
7+ sanitizeUnpairedSurrogates ,
8+ createDeferredPromise
9+ } from './runner/utils.mjs'
710
811const WPT_DIR = join ( import . meta. dirname , 'wpt' )
912const EXPECTATION_PATH = join ( import . meta. dirname , 'expectation.json' )
1013
1114const log = debuglog ( 'UNDICI_WPT' )
1215
1316async 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
4963function 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
111130function 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+
206275async 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 ( `
0 commit comments