@@ -108,7 +108,7 @@ class WPTReport {
108108 * @returns {ReportResult }
109109 */
110110 getResult ( spec ) {
111- const name = `/${ spec . getRelativePath ( ) } ${ spec . variant } ` ;
111+ const name = `/${ spec . getTestPath ( ) } ` ;
112112 if ( this . results . has ( name ) ) {
113113 return this . results . get ( name ) ;
114114 }
@@ -122,13 +122,7 @@ class WPTReport {
122122 */
123123 write ( ) {
124124 this . time_end = Date . now ( ) ;
125- const results = Array . from ( this . results . values ( ) )
126- . map ( ( result ) => {
127- const url = new URL ( result . test , 'http://wpt' ) ;
128- url . pathname = url . pathname . replace ( / \. j s $ / , '.html' ) ;
129- result . test = url . href . slice ( url . origin . length ) ;
130- return result ;
131- } ) ;
125+ const results = Array . from ( this . results . values ( ) ) ;
132126
133127 /**
134128 * Return required and some optional properties
@@ -323,18 +317,21 @@ class WPTTestSpec {
323317 * 'test.any.js'
324318 * @param {StatusRule[] } rules
325319 * @param {string } variant test file variant
320+ * @param {'window'|'dedicatedworker' } [globalScope] generated test global
326321 */
327- constructor ( mod , filename , rules , variant = '' ) {
322+ constructor ( mod , filename , rules , variant = '' , globalScope ) {
328323 this . module = mod ;
329324 this . filename = filename ;
330325 this . variant = variant ;
326+ this . globalScope = globalScope ;
327+ this . rules = [ ...new Set ( rules ) ] ;
331328
332329 this . requires = new Set ( ) ;
333330 this . failedTests = [ ] ;
334331 this . flakyTests = [ ] ;
335332 this . skipReasons = [ ] ;
336333 this . skippedTests = [ ] ;
337- for ( const item of rules ) {
334+ for ( const item of this . rules ) {
338335 if ( item . requires . length ) {
339336 for ( const req of item . requires ) {
340337 this . requires . add ( req ) ;
@@ -364,12 +361,63 @@ class WPTTestSpec {
364361 * @param {string } mod
365362 * @param {string } filename
366363 * @param {StatusRule[] } rules
367- * @returns {ReturnType<WPTTestSpec['getMeta']>[] }
364+ * @param {(spec: WPTTestSpec) => StatusRule[] } [getAdditionalRules]
365+ * @returns {WPTTestSpec[] }
368366 */
369- static from ( mod , filename , rules ) {
367+ static from ( mod , filename , rules , getAdditionalRules ) {
370368 const spec = new WPTTestSpec ( mod , filename , rules ) ;
371369 const meta = spec . getMeta ( ) ;
372- return meta . variant ?. map ( ( variant ) => new WPTTestSpec ( mod , filename , rules , variant ) ) || [ spec ] ;
370+ const variants = meta . variant || [ '' ] ;
371+ const createSpec = ( variant , globalScope ) => {
372+ let result = new WPTTestSpec ( mod , filename , rules , variant , globalScope ) ;
373+ const additionalRules = getAdditionalRules ?. ( result ) || [ ] ;
374+ if ( additionalRules . length > 0 ) {
375+ result = new WPTTestSpec (
376+ mod ,
377+ filename ,
378+ [ ...new Set ( [ ...rules , ...additionalRules ] ) ] ,
379+ variant ,
380+ globalScope ,
381+ ) ;
382+ }
383+ return result ;
384+ } ;
385+
386+ if ( ! spec . isAnyTest ( ) ) {
387+ return variants . map ( ( variant ) => createSpec ( variant ) ) ;
388+ }
389+
390+ const requestedGlobals = meta . global ?
391+ meta . global . split ( ',' ) . map ( ( item ) => item . trim ( ) ) :
392+ [ 'window' , 'dedicatedworker' ] ;
393+ const supportedGlobals = new Set ( ) ;
394+ for ( const globalScope of requestedGlobals ) {
395+ if ( globalScope === 'window' || globalScope === 'dedicatedworker' ) {
396+ supportedGlobals . add ( globalScope ) ;
397+ } else if ( globalScope === 'worker' ) {
398+ supportedGlobals . add ( 'dedicatedworker' ) ;
399+ }
400+ }
401+
402+ return [ 'window' , 'dedicatedworker' ]
403+ . filter ( ( globalScope ) => supportedGlobals . has ( globalScope ) )
404+ . flatMap ( ( globalScope ) => variants . map (
405+ ( variant ) => createSpec ( variant , globalScope ) ) ) ;
406+ }
407+
408+ /**
409+ * @returns {boolean }
410+ */
411+ isAnyTest ( ) {
412+ return / \. a n y \. j s $ / . test ( this . filename ) ;
413+ }
414+
415+ /**
416+ * @returns {boolean }
417+ */
418+ isWebWorkerTest ( ) {
419+ return / \. w o r k e r \. j s $ / . test ( this . filename ) ||
420+ this . globalScope === 'dedicatedworker' ;
373421 }
374422
375423 /**
@@ -392,6 +440,22 @@ class WPTTestSpec {
392440 return path . join ( this . module , this . filename ) ;
393441 }
394442
443+ getStatusKey ( ) {
444+ if ( this . globalScope === 'dedicatedworker' ) {
445+ return this . filename . replace ( / \. a n y \. j s $ / , '.any.worker.html' ) ;
446+ }
447+ if ( this . globalScope === 'window' ) {
448+ return this . filename . replace ( / \. a n y \. j s $ / , '.any.html' ) ;
449+ }
450+ return this . filename ;
451+ }
452+
453+ getTestPath ( ) {
454+ let testPath = path . join ( this . module , this . getStatusKey ( ) ) ;
455+ testPath = testPath . replace ( / \. j s $ / , '.html' ) ;
456+ return `${ testPath . split ( path . sep ) . join ( '/' ) } ${ this . variant } ` ;
457+ }
458+
395459 getAbsolutePath ( ) {
396460 return fixtures . path ( 'wpt' , this . getRelativePath ( ) ) ;
397461 }
@@ -541,7 +605,15 @@ class StatusLoader {
541605 for ( const file of list ) {
542606 const relativePath = path . relative ( subDir , file ) ;
543607 const match = this . rules . match ( relativePath ) ;
544- this . specs . push ( ...WPTTestSpec . from ( this . path , relativePath , match ) ) ;
608+ this . specs . push ( ...WPTTestSpec . from (
609+ this . path ,
610+ relativePath ,
611+ match ,
612+ ( spec ) => [
613+ ...this . rules . match ( spec . getStatusKey ( ) ) ,
614+ ...this . rules . match ( `${ spec . getStatusKey ( ) } ${ spec . variant } ` ) ,
615+ ] ,
616+ ) ) ;
545617 }
546618 }
547619}
@@ -577,6 +649,31 @@ const limit = (concurrency) => {
577649 return execute ;
578650} ;
579651
652+ function getUnexpectedPasses ( queue , results ) {
653+ const specsByRule = new Map ( ) ;
654+ for ( const spec of queue ) {
655+ for ( const rule of spec . rules ) {
656+ if ( Array . isArray ( rule . fail ?. expected ) ) {
657+ const specs = specsByRule . get ( rule ) || [ ] ;
658+ specs . push ( spec ) ;
659+ specsByRule . set ( rule , specs ) ;
660+ }
661+ }
662+ }
663+
664+ const unexpectedPasses = [ ] ;
665+ for ( const [ rule , specs ] of specsByRule ) {
666+ for ( const expectedToFail of rule . fail . expected ) {
667+ const failed = specs . some ( ( spec ) =>
668+ results [ spec . getStatusKey ( ) ] ?. fail ?. expected ?. includes ( expectedToFail ) ) ;
669+ if ( ! failed ) {
670+ unexpectedPasses . push ( `${ rule . key } :${ expectedToFail } ` ) ;
671+ }
672+ }
673+ }
674+ return unexpectedPasses ;
675+ }
676+
580677class WPTRunner {
581678 constructor ( path , { concurrency = os . availableParallelism ( ) - 1 || 1 } = { } ) {
582679 // RISC-V has very limited virtual address space in the currently common
@@ -608,6 +705,7 @@ class WPTRunner {
608705 this . inProgress = new Set ( ) ;
609706 this . workers = new Map ( ) ;
610707 this . unexpectedFailures = [ ] ;
708+ this . skippedSpecCount = 0 ;
611709
612710 this . subtestCounts = { passed : 0 , failed : 0 , expectedFailures : 0 , skipped : 0 , unexpectedPasses : 0 } ;
613711
@@ -645,7 +743,7 @@ class WPTRunner {
645743 * @returns {string }
646744 */
647745 fullInitScript ( spec ) {
648- const url = new URL ( `/${ spec . getRelativePath ( ) . replace ( / \. j s $ / , '.html' ) } ${ spec . variant } ` , 'http://wpt' ) ;
746+ const url = new URL ( `/${ spec . getTestPath ( ) } ` , 'http://wpt' ) ;
649747 const title = spec . getMeta ( ) . title ;
650748 let { initScript } = this ;
651749
@@ -704,20 +802,12 @@ class WPTRunner {
704802 const absolutePath = spec . getAbsolutePath ( ) ;
705803 const relativePath = spec . getRelativePath ( ) ;
706804 const harnessPath = fixtures . path ( 'wpt' , 'resources' , 'testharness.js' ) ;
707- // *.worker.js tests are dedicated worker tests by definition.
708- // Multi-global (*.any.js) tests whose global scopes include a
709- // dedicated worker but not a window also run inside an actual Web
710- // Worker, like the .any.worker.html variant generated by the WPT
711- // server does. Refs:
805+ // *.worker.js tests are dedicated worker tests by definition. Each
806+ // dedicated worker variant generated from a multi-global (*.any.js)
807+ // test also runs inside an actual Web Worker. Refs:
712808 // https://web-platform-tests.org/writing-tests/testharness.html#multi-global-tests
713- const isAnyTest = / \. a n y \. j s $ / . test ( spec . filename ) ;
714- const globalScopes = isAnyTest ?
715- ( meta . global ?. split ( ',' ) . map ( ( s ) => s . trim ( ) ) ??
716- [ 'window' , 'dedicatedworker' ] ) : [ ] ;
717- const isWebWorkerTest = / \. w o r k e r \. j s $ / . test ( spec . filename ) ||
718- ( isAnyTest && ! globalScopes . includes ( 'window' ) &&
719- ( globalScopes . includes ( 'worker' ) ||
720- globalScopes . includes ( 'dedicatedworker' ) ) ) ;
809+ const isAnyTest = spec . isAnyTest ( ) ;
810+ const isWebWorkerTest = spec . isWebWorkerTest ( ) ;
721811
722812 // Scripts specified with the `// META: script=` header. For tests
723813 // that run inside a Web Worker they are imported by the worker
@@ -757,6 +847,8 @@ class WPTRunner {
757847 webWorker : isWebWorkerTest ? {
758848 path : absolutePath ,
759849 isAnyTest,
850+ initScript : this . initScript ,
851+ variant : spec . variant ,
760852 scripts : meta . script ?. map (
761853 ( script ) => this . resource . toRealFilePath ( relativePath , script ) ,
762854 ) ?? [ ] ,
@@ -832,45 +924,16 @@ class WPTRunner {
832924
833925 const failures = [ ] ;
834926 let expectedFailures = 0 ;
835- let skipped = 0 ;
836927 for ( const [ key , item ] of Object . entries ( this . results ) ) {
837928 if ( item . fail ?. unexpected ) {
838929 failures . push ( key ) ;
839930 }
840931 if ( item . fail ?. expected ) {
841932 expectedFailures ++ ;
842933 }
843- if ( item . skip ) {
844- skipped ++ ;
845- }
846934 }
847935
848- const unexpectedPasses = [ ] ;
849- for ( const specs of queue ) {
850- const key = specs . filename ;
851-
852- // File has no expected failures
853- if ( ! specs . failedTests . length ) {
854- continue ;
855- }
856-
857- // File was (maybe even conditionally) skipped
858- if ( this . results [ key ] ?. skip ) {
859- continue ;
860- }
861-
862- // Full check: every expected to fail test is present
863- const _unexpectedPasses = specs . failedTests . filter ( ( expectedToFail ) => {
864- if ( specs . flakyTests . includes ( expectedToFail ) ) {
865- return false ;
866- }
867- return this . results [ key ] ?. fail ?. expected ?. includes ( expectedToFail ) !== true ;
868- } ) ;
869- if ( _unexpectedPasses . length ) {
870- unexpectedPasses . push ( ..._unexpectedPasses . map ( ( name ) => `${ key } :${ name } ` ) ) ;
871- continue ;
872- }
873- }
936+ const unexpectedPasses = getUnexpectedPasses ( queue , this . results ) ;
874937
875938 // Write the report on clean exit. The report is also written
876939 // incrementally after each spec completes (see completionCallback)
@@ -879,6 +942,7 @@ class WPTRunner {
879942
880943 const p = ( n , word , suffix = 's' ) => `${ n } ${ word } ${ n === 1 ? '' : suffix } ` ;
881944 const ran = queue . length ;
945+ const skipped = this . skippedSpecCount ;
882946 const total = ran + skipped ;
883947 const passed = ran - expectedFailures - failures . length ;
884948 const { subtestCounts } = this ;
@@ -976,8 +1040,9 @@ class WPTRunner {
9761040 }
9771041
9781042 addTestResult ( spec , item ) {
979- let result = this . results [ spec . filename ] ;
980- result ||= this . results [ spec . filename ] = { } ;
1043+ const key = spec . getStatusKey ( ) ;
1044+ let result = this . results [ key ] ;
1045+ result ||= this . results [ key ] = { } ;
9811046 if ( item . status === kSkip ) {
9821047 if ( item . name ) {
9831048 // Subtest-level skip: { filename: { skipTests: [ ... ] } }
@@ -1056,7 +1121,8 @@ class WPTRunner {
10561121
10571122 skip ( spec , reasons ) {
10581123 const joinedReasons = reasons . join ( '; ' ) ;
1059- console . log ( `[SKIPPED] ${ spec . filename } ${ spec . variant } : ${ joinedReasons } ` ) ;
1124+ console . log ( `[SKIPPED] ${ spec . getTestPath ( ) } : ${ joinedReasons } ` ) ;
1125+ this . skippedSpecCount ++ ;
10601126 this . addTestResult ( spec , {
10611127 status : kSkip ,
10621128 reason : joinedReasons ,
@@ -1065,6 +1131,7 @@ class WPTRunner {
10651131
10661132 buildQueue ( ) {
10671133 const queue = [ ] ;
1134+ this . skippedSpecCount = 0 ;
10681135 let argFilename ;
10691136 let argVariant ;
10701137 if ( process . argv [ 2 ] ) {
@@ -1105,7 +1172,9 @@ class WPTRunner {
11051172}
11061173
11071174module . exports = {
1175+ getUnexpectedPasses,
11081176 harness : harnessMock ,
11091177 ResourceLoader,
1178+ WPTTestSpec,
11101179 WPTRunner,
11111180} ;
0 commit comments