@@ -42,6 +42,18 @@ class ResourceLoader {
42
42
this . path = path ;
43
43
}
44
44
45
+ toRealFilePath ( from , url ) {
46
+ // We need to patch this to load the WebIDL parser
47
+ url = url . replace (
48
+ '/resources/WebIDLParser.js' ,
49
+ '/resources/webidl2/lib/webidl2.js'
50
+ ) ;
51
+ const base = path . dirname ( from ) ;
52
+ return url . startsWith ( '/' ) ?
53
+ fixtures . path ( 'wpt' , url ) :
54
+ fixtures . path ( 'wpt' , base , url ) ;
55
+ }
56
+
45
57
/**
46
58
* Load a resource in test/fixtures/wpt specified with a URL
47
59
* @param {string } from the path of the file loading this resource,
@@ -51,15 +63,7 @@ class ResourceLoader {
51
63
* pseudo-Response object.
52
64
*/
53
65
read ( from , url , asFetch = true ) {
54
- // We need to patch this to load the WebIDL parser
55
- url = url . replace (
56
- '/resources/WebIDLParser.js' ,
57
- '/resources/webidl2/lib/webidl2.js'
58
- ) ;
59
- const base = path . dirname ( from ) ;
60
- const file = url . startsWith ( '/' ) ?
61
- fixtures . path ( 'wpt' , url ) :
62
- fixtures . path ( 'wpt' , base , url ) ;
66
+ const file = this . toRealFilePath ( from , url ) ;
63
67
if ( asFetch ) {
64
68
return fsPromises . readFile ( file )
65
69
. then ( ( data ) => {
@@ -135,7 +139,8 @@ class StatusRuleSet {
135
139
}
136
140
}
137
141
138
- class WPTTest {
142
+ // A specification of WPT test
143
+ class WPTTestSpec {
139
144
/**
140
145
* @param {string } mod name of the WPT module, e.g.
141
146
* 'html/webappapis/microtask-queuing'
@@ -227,8 +232,8 @@ class StatusLoader {
227
232
this . path = path ;
228
233
this . loaded = false ;
229
234
this . rules = new StatusRuleSet ( ) ;
230
- /** @type {WPTTest [] } */
231
- this . tests = [ ] ;
235
+ /** @type {WPTTestSpec [] } */
236
+ this . specs = [ ] ;
232
237
}
233
238
234
239
/**
@@ -265,15 +270,19 @@ class StatusLoader {
265
270
for ( const file of list ) {
266
271
const relativePath = path . relative ( subDir , file ) ;
267
272
const match = this . rules . match ( relativePath ) ;
268
- this . tests . push ( new WPTTest ( this . path , relativePath , match ) ) ;
273
+ this . specs . push ( new WPTTestSpec ( this . path , relativePath , match ) ) ;
269
274
}
270
275
this . loaded = true ;
271
276
}
272
277
}
273
278
274
- const PASSED = 1 ;
275
- const FAILED = 2 ;
276
- const SKIPPED = 3 ;
279
+ const kPass = 'pass' ;
280
+ const kFail = 'fail' ;
281
+ const kSkip = 'skip' ;
282
+ const kTimeout = 'timeout' ;
283
+ const kIncomplete = 'incomplete' ;
284
+ const kUncaught = 'uncaught' ;
285
+ const NODE_UNCAUGHT = 100 ;
277
286
278
287
class WPTRunner {
279
288
constructor ( path ) {
@@ -286,12 +295,13 @@ class WPTRunner {
286
295
287
296
this . status = new StatusLoader ( path ) ;
288
297
this . status . load ( ) ;
289
- this . tests = new Map (
290
- this . status . tests . map ( ( item ) => [ item . filename , item ] )
298
+ this . specMap = new Map (
299
+ this . status . specs . map ( ( item ) => [ item . filename , item ] )
291
300
) ;
292
301
293
- this . results = new Map ( ) ;
302
+ this . results = { } ;
294
303
this . inProgress = new Set ( ) ;
304
+ this . unexpectedFailures = [ ] ;
295
305
}
296
306
297
307
/**
@@ -328,39 +338,97 @@ class WPTRunner {
328
338
// only `subset.any.js` will be run by the runner.
329
339
if ( process . argv [ 2 ] ) {
330
340
const filename = process . argv [ 2 ] ;
331
- if ( ! this . tests . has ( filename ) ) {
341
+ if ( ! this . specMap . has ( filename ) ) {
332
342
throw new Error ( `${ filename } not found!` ) ;
333
343
}
334
- queue . push ( this . tests . get ( filename ) ) ;
344
+ queue . push ( this . specMap . get ( filename ) ) ;
335
345
} else {
336
346
queue = this . buildQueue ( ) ;
337
347
}
338
348
339
- this . inProgress = new Set ( queue . map ( ( item ) => item . filename ) ) ;
340
-
341
- for ( const test of queue ) {
342
- const filename = test . filename ;
343
- const content = test . getContent ( ) ;
344
- const meta = test . title = this . getMeta ( content ) ;
345
-
346
- const absolutePath = test . getAbsolutePath ( ) ;
347
- const context = this . generateContext ( test ) ;
348
- const relativePath = test . getRelativePath ( ) ;
349
- const code = this . mergeScripts ( relativePath , meta , content ) ;
350
- try {
351
- vm . runInContext ( code , context , {
352
- filename : absolutePath
353
- } ) ;
354
- } catch ( err ) {
355
- this . fail ( filename , {
356
- name : '' ,
357
- message : err . message ,
358
- stack : inspect ( err )
359
- } , 'UNCAUGHT' ) ;
360
- this . inProgress . delete ( filename ) ;
349
+ this . inProgress = new Set ( queue . map ( ( spec ) => spec . filename ) ) ;
350
+
351
+ for ( const spec of queue ) {
352
+ const testFileName = spec . filename ;
353
+ const content = spec . getContent ( ) ;
354
+ const meta = spec . title = this . getMeta ( content ) ;
355
+
356
+ const absolutePath = spec . getAbsolutePath ( ) ;
357
+ const context = this . generateContext ( spec ) ;
358
+ const relativePath = spec . getRelativePath ( ) ;
359
+ const scriptsToRun = [ ] ;
360
+ // Scripts specified with the `// META: script=` header
361
+ if ( meta . script ) {
362
+ for ( const script of meta . script ) {
363
+ scriptsToRun . push ( {
364
+ filename : this . resource . toRealFilePath ( relativePath , script ) ,
365
+ code : this . resource . read ( relativePath , script , false )
366
+ } ) ;
367
+ }
368
+ }
369
+ // The actual test
370
+ scriptsToRun . push ( {
371
+ code : content ,
372
+ filename : absolutePath
373
+ } ) ;
374
+
375
+ for ( const { code, filename } of scriptsToRun ) {
376
+ try {
377
+ vm . runInContext ( code , context , { filename } ) ;
378
+ } catch ( err ) {
379
+ this . fail (
380
+ testFileName ,
381
+ {
382
+ status : NODE_UNCAUGHT ,
383
+ name : 'evaluation in WPTRunner.runJsTests()' ,
384
+ message : err . message ,
385
+ stack : inspect ( err )
386
+ } ,
387
+ kUncaught
388
+ ) ;
389
+ this . inProgress . delete ( filename ) ;
390
+ break ;
391
+ }
361
392
}
362
393
}
363
- this . tryFinish ( ) ;
394
+
395
+ process . on ( 'exit' , ( ) => {
396
+ const total = this . specMap . size ;
397
+ if ( this . inProgress . size > 0 ) {
398
+ for ( const filename of this . inProgress ) {
399
+ this . fail ( filename , { name : 'Unknown' } , kIncomplete ) ;
400
+ }
401
+ }
402
+ inspect . defaultOptions . depth = Infinity ;
403
+ console . log ( this . results ) ;
404
+
405
+ const failures = [ ] ;
406
+ let expectedFailures = 0 ;
407
+ let skipped = 0 ;
408
+ for ( const key of Object . keys ( this . results ) ) {
409
+ const item = this . results [ key ] ;
410
+ if ( item . fail && item . fail . unexpected ) {
411
+ failures . push ( key ) ;
412
+ }
413
+ if ( item . fail && item . fail . expected ) {
414
+ expectedFailures ++ ;
415
+ }
416
+ if ( item . skip ) {
417
+ skipped ++ ;
418
+ }
419
+ }
420
+ const ran = total - skipped ;
421
+ const passed = ran - expectedFailures - failures . length ;
422
+ console . log ( `Ran ${ ran } /${ total } tests, ${ skipped } skipped,` ,
423
+ `${ passed } passed, ${ expectedFailures } expected failures,` ,
424
+ `${ failures . length } unexpected failures` ) ;
425
+ if ( failures . length > 0 ) {
426
+ const file = path . join ( 'test' , 'wpt' , 'status' , `${ this . path } .json` ) ;
427
+ throw new Error (
428
+ `Found ${ failures . length } unexpected failures. ` +
429
+ `Consider updating ${ file } for these files:\n${ failures . join ( '\n' ) } ` ) ;
430
+ }
431
+ } ) ;
364
432
}
365
433
366
434
mock ( testfile ) {
@@ -410,115 +478,124 @@ class WPTRunner {
410
478
sandbox . self = sandbox ;
411
479
// TODO(joyeecheung): we are not a window - work with the upstream to
412
480
// add a new scope for us.
413
-
414
481
return context ;
415
482
}
416
483
417
- resultCallback ( filename , test ) {
418
- switch ( test . status ) {
484
+ getTestTitle ( filename ) {
485
+ const spec = this . specMap . get ( filename ) ;
486
+ const title = spec . meta && spec . meta . title ;
487
+ return title ? `${ filename } : ${ title } ` : filename ;
488
+ }
489
+
490
+ // Map WPT test status to strings
491
+ getTestStatus ( status ) {
492
+ switch ( status ) {
419
493
case 1 :
420
- this . fail ( filename , test , 'FAILURE' ) ;
421
- break ;
494
+ return kFail ;
422
495
case 2 :
423
- this . fail ( filename , test , 'TIMEOUT' ) ;
424
- break ;
496
+ return kTimeout ;
425
497
case 3 :
426
- this . fail ( filename , test , 'INCOMPLETE' ) ;
427
- break ;
498
+ return kIncomplete ;
499
+ case NODE_UNCAUGHT :
500
+ return kUncaught ;
428
501
default :
429
- this . succeed ( filename , test ) ;
502
+ return kPass ;
430
503
}
431
504
}
432
505
506
+ /**
507
+ * Report the status of each specific test case (there could be multiple
508
+ * in one test file).
509
+ *
510
+ * @param {string } filename
511
+ * @param {Test } test The Test object returned by WPT harness
512
+ */
513
+ resultCallback ( filename , test ) {
514
+ const status = this . getTestStatus ( test . status ) ;
515
+ const title = this . getTestTitle ( filename ) ;
516
+ console . log ( `---- ${ title } ----` ) ;
517
+ if ( status !== kPass ) {
518
+ this . fail ( filename , test , status ) ;
519
+ } else {
520
+ this . succeed ( filename , test , status ) ;
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Report the status of each WPT test (one per file)
526
+ *
527
+ * @param {string } filename
528
+ * @param {Test[] } test The Test objects returned by WPT harness
529
+ */
433
530
completionCallback ( filename , tests , harnessStatus ) {
531
+ // Treat it like a test case failure
434
532
if ( harnessStatus . status === 2 ) {
435
- assert . fail ( `test harness timed out in ${ filename } ` ) ;
533
+ const title = this . getTestTitle ( filename ) ;
534
+ console . log ( `---- ${ title } ----` ) ;
535
+ this . resultCallback ( filename , { status : 2 , name : 'Unknown' } ) ;
436
536
}
437
537
this . inProgress . delete ( filename ) ;
438
- this . tryFinish ( ) ;
439
538
}
440
539
441
- tryFinish ( ) {
442
- if ( this . inProgress . size > 0 ) {
443
- return ;
540
+ addTestResult ( filename , item ) {
541
+ let result = this . results [ filename ] ;
542
+ if ( ! result ) {
543
+ result = this . results [ filename ] = { } ;
444
544
}
445
-
446
- this . reportResults ( ) ;
447
- }
448
-
449
- reportResults ( ) {
450
- const unexpectedFailures = [ ] ;
451
- for ( const [ filename , items ] of this . results ) {
452
- const test = this . tests . get ( filename ) ;
453
- let title = test . meta && test . meta . title ;
454
- title = title ? `${ filename } : ${ title } ` : filename ;
455
- console . log ( `---- ${ title } ----` ) ;
456
- for ( const item of items ) {
457
- switch ( item . type ) {
458
- case FAILED : {
459
- if ( test . failReasons . length ) {
460
- console . log ( `[EXPECTED_FAILURE] ${ item . test . name } ` ) ;
461
- console . log ( test . failReasons . join ( '; ' ) ) ;
462
- } else {
463
- console . log ( `[UNEXPECTED_FAILURE] ${ item . test . name } ` ) ;
464
- unexpectedFailures . push ( [ title , filename , item ] ) ;
465
- }
466
- break ;
467
- }
468
- case PASSED : {
469
- console . log ( `[PASSED] ${ item . test . name } ` ) ;
470
- break ;
471
- }
472
- case SKIPPED : {
473
- console . log ( `[SKIPPED] ${ item . reason } ` ) ;
474
- break ;
475
- }
476
- }
545
+ if ( item . status === kSkip ) {
546
+ // { filename: { skip: 'reason' } }
547
+ result [ kSkip ] = item . reason ;
548
+ } else {
549
+ // { filename: { fail: { expected: [ ... ],
550
+ // unexpected: [ ... ] } }}
551
+ if ( ! result [ item . status ] ) {
552
+ result [ item . status ] = { } ;
477
553
}
478
- }
479
-
480
- if ( unexpectedFailures . length > 0 ) {
481
- for ( const [ title , filename , item ] of unexpectedFailures ) {
482
- console . log ( `---- ${ title } ----` ) ;
483
- console . log ( `[${ item . reason } ] ${ item . test . name } ` ) ;
484
- console . log ( item . test . message ) ;
485
- console . log ( item . test . stack ) ;
486
- const command = `${ process . execPath } ${ process . execArgv } ` +
487
- ` ${ require . main . filename } ${ filename } ` ;
488
- console . log ( `Command: ${ command } \n` ) ;
554
+ const key = item . expected ? 'expected' : 'unexpected' ;
555
+ if ( ! result [ item . status ] [ key ] ) {
556
+ result [ item . status ] [ key ] = [ ] ;
557
+ }
558
+ if ( result [ item . status ] [ key ] . indexOf ( item . reason ) === - 1 ) {
559
+ result [ item . status ] [ key ] . push ( item . reason ) ;
489
560
}
490
- assert . fail ( `${ unexpectedFailures . length } unexpected failures found` ) ;
491
- }
492
- }
493
-
494
- addResult ( filename , item ) {
495
- const result = this . results . get ( filename ) ;
496
- if ( result ) {
497
- result . push ( item ) ;
498
- } else {
499
- this . results . set ( filename , [ item ] ) ;
500
561
}
501
562
}
502
563
503
- succeed ( filename , test ) {
504
- this . addResult ( filename , {
505
- type : PASSED ,
506
- test
507
- } ) ;
564
+ succeed ( filename , test , status ) {
565
+ console . log ( `[${ status . toUpperCase ( ) } ] ${ test . name } ` ) ;
508
566
}
509
567
510
- fail ( filename , test , reason ) {
511
- this . addResult ( filename , {
512
- type : FAILED ,
513
- test,
514
- reason
568
+ fail ( filename , test , status ) {
569
+ const spec = this . specMap . get ( filename ) ;
570
+ const expected = ! ! ( spec . failReasons . length ) ;
571
+ if ( expected ) {
572
+ console . log ( `[EXPECTED_FAILURE][${ status . toUpperCase ( ) } ] ${ test . name } ` ) ;
573
+ console . log ( spec . failReasons . join ( '; ' ) ) ;
574
+ } else {
575
+ console . log ( `[UNEXPECTED_FAILURE][${ status . toUpperCase ( ) } ] ${ test . name } ` ) ;
576
+ }
577
+ if ( status === kFail || status === kUncaught ) {
578
+ console . log ( test . message ) ;
579
+ console . log ( test . stack ) ;
580
+ }
581
+ const command = `${ process . execPath } ${ process . execArgv } ` +
582
+ ` ${ require . main . filename } ${ filename } ` ;
583
+ console . log ( `Command: ${ command } \n` ) ;
584
+ this . addTestResult ( filename , {
585
+ expected,
586
+ status : kFail ,
587
+ reason : test . message || status
515
588
} ) ;
516
589
}
517
590
518
591
skip ( filename , reasons ) {
519
- this . addResult ( filename , {
520
- type : SKIPPED ,
521
- reason : reasons . join ( '; ' )
592
+ const title = this . getTestTitle ( filename ) ;
593
+ console . log ( `---- ${ title } ----` ) ;
594
+ const joinedReasons = reasons . join ( '; ' ) ;
595
+ console . log ( `[SKIPPED] ${ joinedReasons } ` ) ;
596
+ this . addTestResult ( filename , {
597
+ status : kSkip ,
598
+ reason : joinedReasons
522
599
} ) ;
523
600
}
524
601
@@ -546,36 +623,22 @@ class WPTRunner {
546
623
}
547
624
}
548
625
549
- mergeScripts ( base , meta , content ) {
550
- if ( ! meta . script ) {
551
- return content ;
552
- }
553
-
554
- // only one script
555
- let result = '' ;
556
- for ( const script of meta . script ) {
557
- result += this . resource . read ( base , script , false ) ;
558
- }
559
-
560
- return result + content ;
561
- }
562
-
563
626
buildQueue ( ) {
564
627
const queue = [ ] ;
565
- for ( const test of this . tests . values ( ) ) {
566
- const filename = test . filename ;
567
- if ( test . skipReasons . length > 0 ) {
568
- this . skip ( filename , test . skipReasons ) ;
628
+ for ( const spec of this . specMap . values ( ) ) {
629
+ const filename = spec . filename ;
630
+ if ( spec . skipReasons . length > 0 ) {
631
+ this . skip ( filename , spec . skipReasons ) ;
569
632
continue ;
570
633
}
571
634
572
- const lackingIntl = intlRequirements . isLacking ( test . requires ) ;
635
+ const lackingIntl = intlRequirements . isLacking ( spec . requires ) ;
573
636
if ( lackingIntl ) {
574
637
this . skip ( filename , [ `requires ${ lackingIntl } ` ] ) ;
575
638
continue ;
576
639
}
577
640
578
- queue . push ( test ) ;
641
+ queue . push ( spec ) ;
579
642
}
580
643
return queue ;
581
644
}
0 commit comments