1+ const startTime = Date . now ( ) ;
12const fs = require ( "fs" ) ;
23const path = require ( "path" ) ;
34const os = require ( "os" ) ;
@@ -8,6 +9,8 @@ const optionsUtil = require("../cli/util/options");
89const diff = require ( "./util/diff" ) ;
910const asc = require ( "../cli/asc.js" ) ;
1011const rtrace = require ( "../lib/rtrace" ) ;
12+ const cluster = require ( "cluster" ) ;
13+ const coreCount = require ( "physical-cpu-count" ) ;
1114
1215const config = {
1316 "create" : {
@@ -34,13 +37,18 @@ const config = {
3437 "Enables verbose rtrace output."
3538 ]
3639 } ,
40+ "parallel" : {
41+ "description" : [
42+ "Runs tests in parallel."
43+ ]
44+ } ,
3745 "help" : {
3846 "description" : "Prints this message and exits." ,
3947 "type" : "b" ,
4048 "alias" : "h"
4149 }
4250} ;
43- const opts = optionsUtil . parse ( process . argv . slice ( 2 ) , config ) ;
51+ const opts = optionsUtil . parse ( process . argv . slice ( 2 ) , config ) ;
4452const args = opts . options ;
4553const argv = opts . arguments ;
4654
@@ -65,23 +73,23 @@ var skippedMessages = new Map();
6573
6674const basedir = path . join ( __dirname , "compiler" ) ;
6775
68- // Get a list of all tests
69- var tests = glob . sync ( "**/!(_*).ts" , { cwd : basedir } ) ;
70-
71- // Run specific tests only if arguments are provided
72- if ( argv . length ) {
73- tests = tests . filter ( filename => argv . indexOf ( filename . replace ( / \. t s $ / , "" ) ) >= 0 ) ;
74- if ( ! tests . length ) {
75- console . error ( "No matching tests: " + argv . join ( " " ) ) ;
76- process . exit ( 1 ) ;
76+ // Gets a list of all relevant tests
77+ function getTests ( ) {
78+ var tests = glob . sync ( "**/!(_*).ts" , { cwd : basedir } ) . map ( name => name . replace ( / \. t s $ / , "" ) ) ;
79+ if ( argv . length ) { // run matching tests only
80+ tests = tests . filter ( filename => argv . indexOf ( filename . replace ( / \. t s $ / , "" ) ) >= 0 ) ;
81+ if ( ! tests . length ) {
82+ console . error ( "No matching tests: " + argv . join ( " " ) ) ;
83+ process . exit ( 1 ) ;
84+ }
7785 }
86+ return tests ;
7887}
7988
80- // TODO: asc's callback is synchronous here. This might change.
81- tests . forEach ( filename => {
82- console . log ( colorsUtil . white ( "Testing compiler/" + filename ) + "\n" ) ;
89+ // Runs a single test
90+ function runTest ( basename ) {
91+ console . log ( colorsUtil . white ( "Testing compiler/" + basename ) + "\n" ) ;
8392
84- const basename = filename . replace ( / \. t s $ / , "" ) ;
8593 const configPath = path . join ( basedir , basename + ".json" ) ;
8694 const config = fs . existsSync ( configPath )
8795 ? require ( configPath )
@@ -118,6 +126,7 @@ tests.forEach(filename => {
118126 console . log ( "- " + colorsUtil . yellow ( "feature SKIPPED" ) + " (" + missing_features . join ( ", " ) + ")\n" ) ;
119127 skippedTests . add ( basename ) ;
120128 skippedMessages . set ( basename , "feature not enabled" ) ;
129+ if ( cluster . isWorker ) process . send ( { cmd : "skipped" , message : skippedMessages . get ( basename ) } ) ;
121130 return ;
122131 }
123132 }
@@ -129,11 +138,9 @@ tests.forEach(filename => {
129138
130139 var failed = false ;
131140
132- // TODO: also save stdout/stderr and diff it (-> expected failures)
133-
134141 // Build unoptimized
135142 var cmd = [
136- filename ,
143+ basename + ".ts" ,
137144 "--baseDir" , basedir ,
138145 "--validate" ,
139146 "--measure" ,
@@ -142,10 +149,7 @@ tests.forEach(filename => {
142149 ] ;
143150 if ( asc_flags )
144151 Array . prototype . push . apply ( cmd , asc_flags ) ;
145- if ( args . createBinary )
146- cmd . push ( "--binaryFile" , basename + ".untouched.wasm" ) ;
147- else
148- cmd . push ( "--binaryFile" , "temp.wasm" ) ;
152+ cmd . push ( "--binaryFile" , basename + ".untouched.wasm" ) ;
149153 asc . main ( cmd , {
150154 stdout : stdout ,
151155 stderr : stderr
@@ -214,7 +218,7 @@ tests.forEach(filename => {
214218
215219 // Build optimized
216220 var cmd = [
217- filename ,
221+ basename + ".ts" ,
218222 "--baseDir" , basedir ,
219223 "--validate" ,
220224 "--measure" ,
@@ -237,7 +241,7 @@ tests.forEach(filename => {
237241 failedTests . add ( basename ) ;
238242 return 1 ;
239243 }
240- let untouchedBuffer = fs . readFileSync ( path . join ( basedir , "temp .wasm") ) ;
244+ let untouchedBuffer = fs . readFileSync ( path . join ( basedir , basename + ".untouched .wasm") ) ;
241245 let optimizedBuffer = stdout . toBuffer ( ) ;
242246 const gluePath = path . join ( basedir , basename + ".js" ) ;
243247 var glue = { } ;
@@ -258,29 +262,11 @@ tests.forEach(filename => {
258262 if ( failed ) return 1 ;
259263 } ) ;
260264 if ( v8_no_flags ) v8 . setFlagsFromString ( v8_no_flags ) ;
261- } ) ;
262-
263- if ( skippedTests . size ) {
264- console . log ( colorsUtil . yellow ( "WARNING: " ) + colorsUtil . white ( skippedTests . size + " compiler tests have been skipped:\n" ) ) ;
265- skippedTests . forEach ( name => {
266- var message = skippedMessages . has ( name ) ? colorsUtil . gray ( "[" + skippedMessages . get ( name ) + "]" ) : "" ;
267- console . log ( " " + name + " " + message ) ;
268- } ) ;
269- console . log ( ) ;
270- }
271- if ( failedTests . size ) {
272- process . exitCode = 1 ;
273- console . log ( colorsUtil . red ( "ERROR: " ) + colorsUtil . white ( failedTests . size + " compiler tests had failures:\n" ) ) ;
274- failedTests . forEach ( name => {
275- var message = failedMessages . has ( name ) ? colorsUtil . gray ( "[" + failedMessages . get ( name ) + "]" ) : "" ;
276- console . log ( " " + name + " " + message ) ;
277- } ) ;
278- console . log ( ) ;
279- }
280- if ( ! process . exitCode ) {
281- console . log ( "[ " + colorsUtil . white ( "OK" ) + " ]" ) ;
265+ if ( ! args . createBinary ) fs . unlink ( path . join ( basedir , basename + ".untouched.wasm" ) , err => { } ) ;
266+ if ( cluster . isWorker ) process . send ( { cmd : "done" , failed : failed , message : failedMessages . get ( basename ) } ) ;
282267}
283268
269+ // Tests if instantiation of a module succeeds
284270function testInstantiate ( basename , binaryBuffer , name , glue ) {
285271 var failed = false ;
286272 try {
@@ -370,3 +356,94 @@ function testInstantiate(basename, binaryBuffer, name, glue) {
370356 }
371357 return false ;
372358}
359+
360+ // Evaluates the overall test result
361+ function evaluateResult ( ) {
362+ if ( skippedTests . size ) {
363+ console . log ( colorsUtil . yellow ( "WARNING: " ) + colorsUtil . white ( skippedTests . size + " compiler tests have been skipped:\n" ) ) ;
364+ skippedTests . forEach ( name => {
365+ var message = skippedMessages . has ( name ) ? colorsUtil . gray ( "[" + skippedMessages . get ( name ) + "]" ) : "" ;
366+ console . log ( " " + name + " " + message ) ;
367+ } ) ;
368+ console . log ( ) ;
369+ }
370+ if ( failedTests . size ) {
371+ process . exitCode = 1 ;
372+ console . log ( colorsUtil . red ( "ERROR: " ) + colorsUtil . white ( failedTests . size + " compiler tests had failures:\n" ) ) ;
373+ failedTests . forEach ( name => {
374+ var message = failedMessages . has ( name ) ? colorsUtil . gray ( "[" + failedMessages . get ( name ) + "]" ) : "" ;
375+ console . log ( " " + name + " " + message ) ;
376+ } ) ;
377+ console . log ( ) ;
378+ }
379+ console . log ( "Time: " + ( Date . now ( ) - startTime ) + " ms\n" ) ;
380+ if ( ! process . exitCode ) {
381+ console . log ( "[ " + colorsUtil . white ( "OK" ) + " ]" ) ;
382+ }
383+ }
384+
385+ // Run tests in parallel if requested
386+ if ( args . parallel && coreCount > 1 ) {
387+ if ( cluster . isWorker ) {
388+ colorsUtil . supported = true ;
389+ process . on ( "message" , msg => {
390+ if ( msg . cmd != "run" ) throw Error ( "invalid command: " + msg . cmd ) ;
391+ try {
392+ runTest ( msg . test ) ;
393+ } catch ( e ) {
394+ process . send ( { cmd : "done" , failed : true , message : e . message } ) ;
395+ }
396+ } ) ;
397+ process . send ( { cmd : "ready" } ) ;
398+ } else {
399+ const tests = getTests ( ) ;
400+ // const sizes = new Map();
401+ // tests.forEach(name => sizes.set(name, fs.statSync(path.join(basedir, name + ".ts")).size));
402+ // tests.sort((a, b) => sizes.get(b) - sizes.get(a));
403+ const workers = [ ] ;
404+ const current = [ ] ;
405+ const outputs = [ ] ;
406+ let numWorkers = Math . min ( coreCount - 1 , tests . length ) ;
407+ console . log ( "Spawning " + numWorkers + " workers ..." ) ;
408+ cluster . settings . silent = true ;
409+ let index = 0 ;
410+ for ( let i = 0 ; i < numWorkers ; ++ i ) {
411+ let worker = cluster . fork ( ) ;
412+ workers [ i ] = worker ;
413+ current [ i ] = null ;
414+ outputs [ i ] = [ ] ;
415+ worker . process . stdout . on ( "data" , buf => outputs [ i ] . push ( buf ) ) ;
416+ worker . process . stderr . on ( "data" , buf => outputs [ i ] . push ( buf ) ) ;
417+ worker . on ( "message" , msg => {
418+ if ( msg . cmd == "done" ) {
419+ process . stdout . write ( Buffer . concat ( outputs [ i ] ) . toString ( ) ) ;
420+ if ( msg . failed ) failedTests . add ( current [ i ] ) ;
421+ if ( msg . message ) failedMessages . set ( current [ i ] , msg . message ) ;
422+ } else if ( msg . cmd == "skipped" ) {
423+ process . stdout . write ( Buffer . concat ( outputs [ i ] ) . toString ( ) ) ;
424+ skippedTests . add ( current [ i ] ) ;
425+ if ( msg . message ) skippedMessages . set ( current [ i ] , msg . message ) ;
426+ } else if ( msg . cmd != "ready" ) {
427+ throw Error ( "invalid command: " + msg . cmd ) ;
428+ }
429+ if ( index >= tests . length ) {
430+ workers [ i ] = null ;
431+ worker . kill ( ) ;
432+ return ;
433+ }
434+ current [ i ] = tests [ index ++ ] ;
435+ outputs [ i ] = [ ] ;
436+ worker . send ( { cmd : "run" , test : current [ i ] } ) ;
437+ } ) ;
438+ worker . on ( "disconnect" , ( ) => {
439+ if ( workers [ i ] ) throw Error ( "worker#" + i + " died unexpectedly" ) ;
440+ if ( ! -- numWorkers ) evaluateResult ( ) ;
441+ } ) ;
442+ }
443+ }
444+
445+ // Otherwise run tests sequentially
446+ } else {
447+ getTests ( ) . forEach ( runTest ) ;
448+ evaluateResult ( ) ;
449+ }
0 commit comments