@@ -6,9 +6,119 @@ const fs = require('fs');
6
6
const fsPromises = fs . promises ;
7
7
const path = require ( 'path' ) ;
8
8
const events = require ( 'events' ) ;
9
+ const os = require ( 'os' ) ;
9
10
const { inspect } = require ( 'util' ) ;
10
11
const { Worker } = require ( 'worker_threads' ) ;
11
12
13
+ function getBrowserProperties ( ) {
14
+ const { node : version } = process . versions ; // e.g. 18.13.0, 20.0.0-nightly202302078e6e215481
15
+ const release = / ^ \d + \. \d + \. \d + $ / . test ( version ) ;
16
+ const browser = {
17
+ browser_channel : release ? 'stable' : 'experimental' ,
18
+ browser_version : version ,
19
+ } ;
20
+
21
+ return browser ;
22
+ }
23
+
24
+ /**
25
+ * Return one of three expected values
26
+ * https://github.com/web-platform-tests/wpt/blob/1c6ff12/tools/wptrunner/wptrunner/tests/test_update.py#L953-L958
27
+ */
28
+ function getOs ( ) {
29
+ switch ( os . type ( ) ) {
30
+ case 'Linux' :
31
+ return 'linux' ;
32
+ case 'Darwin' :
33
+ return 'mac' ;
34
+ case 'Windows_NT' :
35
+ return 'win' ;
36
+ default :
37
+ throw new Error ( 'Unsupported os.type()' ) ;
38
+ }
39
+ }
40
+
41
+ // https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L3705
42
+ function sanitizeUnpairedSurrogates ( str ) {
43
+ return str . replace (
44
+ / ( [ \ud800 - \udbff ] + ) (? ! [ \udc00 - \udfff ] ) | ( ^ | [ ^ \ud800 - \udbff ] ) ( [ \udc00 - \udfff ] + ) / g,
45
+ function ( _ , low , prefix , high ) {
46
+ let output = prefix || '' ; // Prefix may be undefined
47
+ const string = low || high ; // Only one of these alternates can match
48
+ for ( let i = 0 ; i < string . length ; i ++ ) {
49
+ output += codeUnitStr ( string [ i ] ) ;
50
+ }
51
+ return output ;
52
+ } ) ;
53
+ }
54
+
55
+ function codeUnitStr ( char ) {
56
+ return 'U+' + char . charCodeAt ( 0 ) . toString ( 16 ) ;
57
+ }
58
+
59
+ class WPTReport {
60
+ constructor ( ) {
61
+ this . results = [ ] ;
62
+ this . time_start = Date . now ( ) ;
63
+ }
64
+
65
+ addResult ( name , status ) {
66
+ const result = {
67
+ test : name ,
68
+ status,
69
+ subtests : [ ] ,
70
+ addSubtest ( name , status , message ) {
71
+ const subtest = {
72
+ status,
73
+ // https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L3722
74
+ name : sanitizeUnpairedSurrogates ( name ) ,
75
+ } ;
76
+ if ( message ) {
77
+ // https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L4506
78
+ subtest . message = sanitizeUnpairedSurrogates ( message ) ;
79
+ }
80
+ this . subtests . push ( subtest ) ;
81
+ return subtest ;
82
+ } ,
83
+ } ;
84
+ this . results . push ( result ) ;
85
+ return result ;
86
+ }
87
+
88
+ write ( ) {
89
+ this . time_end = Date . now ( ) ;
90
+ this . results = this . results . filter ( ( result ) => {
91
+ return result . status === 'SKIP' || result . subtests . length !== 0 ;
92
+ } ) . map ( ( result ) => {
93
+ const url = new URL ( result . test , 'http://wpt' ) ;
94
+ url . pathname = url . pathname . replace ( / \. j s $ / , '.html' ) ;
95
+ result . test = url . href . slice ( url . origin . length ) ;
96
+ return result ;
97
+ } ) ;
98
+
99
+ if ( fs . existsSync ( 'out/wpt/wptreport.json' ) ) {
100
+ const prev = JSON . parse ( fs . readFileSync ( 'out/wpt/wptreport.json' ) ) ;
101
+ this . results = [ ...prev . results , ...this . results ] ;
102
+ this . time_start = prev . time_start ;
103
+ this . time_end = Math . max ( this . time_end , prev . time_end ) ;
104
+ this . run_info = prev . run_info ;
105
+ } else {
106
+ /**
107
+ * Return required and some optional properties
108
+ * https://github.com/web-platform-tests/wpt.fyi/blob/60da175/api/README.md?plain=1#L331-L335
109
+ */
110
+ this . run_info = {
111
+ product : 'node.js' ,
112
+ ...getBrowserProperties ( ) ,
113
+ revision : process . env . WPT_REVISION || 'unknown' ,
114
+ os : getOs ( ) ,
115
+ } ;
116
+ }
117
+
118
+ fs . writeFileSync ( 'out/wpt/wptreport.json' , JSON . stringify ( this ) ) ;
119
+ }
120
+ }
121
+
12
122
// https://github.com/web-platform-tests/wpt/blob/HEAD/resources/testharness.js
13
123
// TODO: get rid of this half-baked harness in favor of the one
14
124
// pulled from WPT
@@ -313,6 +423,10 @@ class WPTRunner {
313
423
this . unexpectedFailures = [ ] ;
314
424
315
425
this . scriptsModifier = null ;
426
+
427
+ if ( process . env . WPT_REPORT != null ) {
428
+ this . report = new WPTReport ( ) ;
429
+ }
316
430
}
317
431
318
432
/**
@@ -339,18 +453,27 @@ class WPTRunner {
339
453
this . scriptsModifier = modifier ;
340
454
}
341
455
342
- get fullInitScript ( ) {
456
+ fullInitScript ( hasSubsetScript , locationSearchString ) {
457
+ let { initScript } = this ;
458
+ if ( hasSubsetScript || locationSearchString ) {
459
+ initScript = `${ initScript } \n\n//===\nglobalThis.location ||= {};` ;
460
+ }
461
+
462
+ if ( locationSearchString ) {
463
+ initScript = `${ initScript } \n\n//===\nglobalThis.location.search = "${ locationSearchString } ";` ;
464
+ }
465
+
343
466
if ( this . globalThisInitScripts . length === null ) {
344
- return this . initScript ;
467
+ return initScript ;
345
468
}
346
469
347
470
const globalThisInitScript = this . globalThisInitScripts . join ( '\n\n//===\n' ) ;
348
471
349
- if ( this . initScript === null ) {
472
+ if ( initScript === null ) {
350
473
return globalThisInitScript ;
351
474
}
352
475
353
- return `${ globalThisInitScript } \n\n//===\n${ this . initScript } ` ;
476
+ return `${ globalThisInitScript } \n\n//===\n${ initScript } ` ;
354
477
}
355
478
356
479
/**
@@ -455,15 +578,20 @@ class WPTRunner {
455
578
for ( const spec of queue ) {
456
579
const testFileName = spec . filename ;
457
580
const content = spec . getContent ( ) ;
458
- const meta = spec . title = this . getMeta ( content ) ;
581
+ const meta = spec . meta = this . getMeta ( content ) ;
459
582
460
583
const absolutePath = spec . getAbsolutePath ( ) ;
461
584
const relativePath = spec . getRelativePath ( ) ;
462
585
const harnessPath = fixtures . path ( 'wpt' , 'resources' , 'testharness.js' ) ;
463
586
const scriptsToRun = [ ] ;
587
+ let hasSubsetScript = false ;
588
+
464
589
// Scripts specified with the `// META: script=` header
465
590
if ( meta . script ) {
466
591
for ( const script of meta . script ) {
592
+ if ( script === '/common/subset-tests.js' || script === '/common/subset-tests-by-key.js' ) {
593
+ hasSubsetScript = true ;
594
+ }
467
595
const obj = {
468
596
filename : this . resource . toRealFilePath ( relativePath , script ) ,
469
597
code : this . resource . read ( relativePath , script , false ) ,
@@ -480,54 +608,65 @@ class WPTRunner {
480
608
this . scriptsModifier ?. ( obj ) ;
481
609
scriptsToRun . push ( obj ) ;
482
610
483
- const workerPath = path . join ( __dirname , 'wpt/worker.js' ) ;
484
- const worker = new Worker ( workerPath , {
485
- execArgv : this . flags ,
486
- workerData : {
487
- testRelativePath : relativePath ,
488
- wptRunner : __filename ,
489
- wptPath : this . path ,
490
- initScript : this . fullInitScript ,
491
- harness : {
492
- code : fs . readFileSync ( harnessPath , 'utf8' ) ,
493
- filename : harnessPath ,
611
+ /**
612
+ * Example test with no META variant
613
+ * https://github.com/nodejs/node/blob/03854f6/test/fixtures/wpt/WebCryptoAPI/sign_verify/hmac.https.any.js#L1-L4
614
+ *
615
+ * Example test with multiple META variants
616
+ * https://github.com/nodejs/node/blob/03854f6/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_RSASSA-PKCS1-v1_5.https.any.js#L1-L9
617
+ */
618
+ for ( const variant of meta . variant || [ '' ] ) {
619
+ const workerPath = path . join ( __dirname , 'wpt/worker.js' ) ;
620
+ const worker = new Worker ( workerPath , {
621
+ execArgv : this . flags ,
622
+ workerData : {
623
+ testRelativePath : relativePath ,
624
+ wptRunner : __filename ,
625
+ wptPath : this . path ,
626
+ initScript : this . fullInitScript ( hasSubsetScript , variant ) ,
627
+ harness : {
628
+ code : fs . readFileSync ( harnessPath , 'utf8' ) ,
629
+ filename : harnessPath ,
630
+ } ,
631
+ scriptsToRun,
494
632
} ,
495
- scriptsToRun ,
496
- } ,
497
- } ) ;
498
- this . workers . set ( testFileName , worker ) ;
499
-
500
- worker . on ( 'message' , ( message ) => {
501
- switch ( message . type ) {
502
- case 'result' :
503
- return this . resultCallback ( testFileName , message . result ) ;
504
- case 'completion' :
505
- return this . completionCallback ( testFileName , message . status ) ;
506
- default :
507
- throw new Error ( `Unexpected message from worker: ${ message . type } ` ) ;
508
- }
509
- } ) ;
633
+ } ) ;
634
+ this . workers . set ( testFileName , worker ) ;
635
+
636
+ let reportResult ;
637
+ worker . on ( 'message' , ( message ) => {
638
+ switch ( message . type ) {
639
+ case 'result' :
640
+ reportResult ||= this . report ?. addResult ( `/ ${ relativePath } ${ variant } ` , 'OK' ) ;
641
+ return this . resultCallback ( testFileName , message . result , reportResult ) ;
642
+ case 'completion' :
643
+ return this . completionCallback ( testFileName , message . status ) ;
644
+ default :
645
+ throw new Error ( `Unexpected message from worker: ${ message . type } ` ) ;
646
+ }
647
+ } ) ;
510
648
511
- worker . on ( 'error' , ( err ) => {
512
- if ( ! this . inProgress . has ( testFileName ) ) {
513
- // The test is already finished. Ignore errors that occur after it.
514
- // This can happen normally, for example in timers tests.
515
- return ;
516
- }
517
- this . fail (
518
- testFileName ,
519
- {
520
- status : NODE_UNCAUGHT ,
521
- name : 'evaluation in WPTRunner.runJsTests()' ,
522
- message : err . message ,
523
- stack : inspect ( err ) ,
524
- } ,
525
- kUncaught ,
526
- ) ;
527
- this . inProgress . delete ( testFileName ) ;
528
- } ) ;
649
+ worker . on ( 'error' , ( err ) => {
650
+ if ( ! this . inProgress . has ( testFileName ) ) {
651
+ // The test is already finished. Ignore errors that occur after it.
652
+ // This can happen normally, for example in timers tests.
653
+ return ;
654
+ }
655
+ this . fail (
656
+ testFileName ,
657
+ {
658
+ status : NODE_UNCAUGHT ,
659
+ name : 'evaluation in WPTRunner.runJsTests()' ,
660
+ message : err . message ,
661
+ stack : inspect ( err ) ,
662
+ } ,
663
+ kUncaught ,
664
+ ) ;
665
+ this . inProgress . delete ( testFileName ) ;
666
+ } ) ;
529
667
530
- await events . once ( worker , 'exit' ) . catch ( ( ) => { } ) ;
668
+ await events . once ( worker , 'exit' ) . catch ( ( ) => { } ) ;
669
+ }
531
670
}
532
671
533
672
process . on ( 'exit' , ( ) => {
@@ -587,6 +726,8 @@ class WPTRunner {
587
726
}
588
727
}
589
728
729
+ this . report ?. write ( ) ;
730
+
590
731
const ran = queue . length ;
591
732
const total = ran + skipped ;
592
733
const passed = ran - expectedFailures - failures . length ;
@@ -611,8 +752,7 @@ class WPTRunner {
611
752
612
753
getTestTitle ( filename ) {
613
754
const spec = this . specMap . get ( filename ) ;
614
- const title = spec . meta && spec . meta . title ;
615
- return title ? `${ filename } : ${ title } ` : filename ;
755
+ return spec . meta ?. title || filename ;
616
756
}
617
757
618
758
// Map WPT test status to strings
@@ -638,14 +778,14 @@ class WPTRunner {
638
778
* @param {string } filename
639
779
* @param {Test } test The Test object returned by WPT harness
640
780
*/
641
- resultCallback ( filename , test ) {
781
+ resultCallback ( filename , test , reportResult ) {
642
782
const status = this . getTestStatus ( test . status ) ;
643
783
const title = this . getTestTitle ( filename ) ;
644
784
console . log ( `---- ${ title } ----` ) ;
645
785
if ( status !== kPass ) {
646
- this . fail ( filename , test , status ) ;
786
+ this . fail ( filename , test , status , reportResult ) ;
647
787
} else {
648
- this . succeed ( filename , test , status ) ;
788
+ this . succeed ( filename , test , status , reportResult ) ;
649
789
}
650
790
}
651
791
@@ -693,11 +833,12 @@ class WPTRunner {
693
833
}
694
834
}
695
835
696
- succeed ( filename , test , status ) {
836
+ succeed ( filename , test , status , reportResult ) {
697
837
console . log ( `[${ status . toUpperCase ( ) } ] ${ test . name } ` ) ;
838
+ reportResult ?. addSubtest ( test . name , 'PASS' ) ;
698
839
}
699
840
700
- fail ( filename , test , status ) {
841
+ fail ( filename , test , status , reportResult ) {
701
842
const spec = this . specMap . get ( filename ) ;
702
843
const expected = spec . failedTests . includes ( test . name ) ;
703
844
if ( expected ) {
@@ -713,6 +854,9 @@ class WPTRunner {
713
854
const command = `${ process . execPath } ${ process . execArgv } ` +
714
855
` ${ require . main . filename } ${ filename } ` ;
715
856
console . log ( `Command: ${ command } \n` ) ;
857
+
858
+ reportResult ?. addSubtest ( test . name , 'FAIL' , test . message ) ;
859
+
716
860
this . addTestResult ( filename , {
717
861
name : test . name ,
718
862
expected,
@@ -742,7 +886,7 @@ class WPTRunner {
742
886
const parts = match . match ( / \/ \/ M E T A : ( [ ^ = ] + ?) = ( .+ ) / ) ;
743
887
const key = parts [ 1 ] ;
744
888
const value = parts [ 2 ] ;
745
- if ( key === 'script' ) {
889
+ if ( key === 'script' || key === 'variant' ) {
746
890
if ( result [ key ] ) {
747
891
result [ key ] . push ( value ) ;
748
892
} else {
0 commit comments