@@ -6,6 +6,7 @@ const path = require('path');
6
6
const pc = require ( 'picocolors' ) ;
7
7
const { globSync } = require ( 'tinyglobby' ) ;
8
8
const utils = require ( './utils.js' ) ;
9
+ const { spawn } = require ( 'child_process' ) ;
9
10
const host = 'node' + utils . getNodeMajorVersion ( ) ;
10
11
let target = process . argv [ 2 ] || 'host' ;
11
12
if ( target === 'host' ) target = host ;
@@ -17,6 +18,8 @@ if (target === 'host') target = host;
17
18
18
19
const flavor = process . env . FLAVOR || process . argv [ 3 ] || 'all' ;
19
20
21
+ const isCI = process . env . CI === 'true' ;
22
+
20
23
console . log ( '' ) ;
21
24
console . log ( '*************************************' ) ;
22
25
console . log ( target + ' ' + flavor ) ;
@@ -86,18 +89,156 @@ if (flavor.match(/^test/)) {
86
89
87
90
const files = globSync ( list , { ignore } ) ;
88
91
89
- files . sort ( ) . some ( function ( file ) {
90
- file = path . resolve ( file ) ;
91
- try {
92
- utils . spawn . sync ( 'node' , [ path . basename ( file ) , target ] , {
92
+ function msToHumanDuration ( ms ) {
93
+ if ( ms < 1000 ) return `${ ms } ms` ;
94
+ const seconds = Math . floor ( ms / 1000 ) ;
95
+ const minutes = Math . floor ( seconds / 60 ) ;
96
+ const hours = Math . floor ( minutes / 60 ) ;
97
+ const human = [ ] ;
98
+ if ( hours > 0 ) human . push ( `${ hours } h` ) ;
99
+ if ( minutes > 0 ) human . push ( `${ minutes % 60 } m` ) ;
100
+ if ( seconds > 0 ) human . push ( `${ seconds % 60 } s` ) ;
101
+ return human . join ( ' ' ) ;
102
+ }
103
+
104
+ /** @type {Array<import('child_process').ChildProcessWithoutNullStreams> } */
105
+ const activeProcesses = [ ] ;
106
+
107
+ function runTest ( file ) {
108
+ return new Promise ( ( resolve , reject ) => {
109
+ const process = spawn ( 'node' , [ path . basename ( file ) , target ] , {
93
110
cwd : path . dirname ( file ) ,
94
- stdio : 'inherit' ,
111
+ stdio : 'pipe' ,
112
+ } ) ;
113
+
114
+ activeProcesses . push ( process ) ;
115
+
116
+ const removeProcess = ( ) => {
117
+ const index = activeProcesses . indexOf ( process ) ;
118
+ if ( index !== - 1 ) {
119
+ activeProcesses . splice ( index , 1 ) ;
120
+ }
121
+ } ;
122
+
123
+ const output = [ ] ;
124
+
125
+ const rejectWithError = ( error ) => {
126
+ error . logOutput = `${ error . message } \n${ output . join ( '' ) } ` ;
127
+ reject ( error ) ;
128
+ } ;
129
+
130
+ process . on ( 'close' , ( code ) => {
131
+ removeProcess ( ) ;
132
+ if ( code !== 0 ) {
133
+ rejectWithError ( new Error ( `Process exited with code ${ code } ` ) ) ;
134
+ } else {
135
+ resolve ( ) ;
136
+ }
95
137
} ) ;
96
- } catch ( error ) {
97
- console . log ( ) ;
98
- console . log ( `> ${ pc . red ( 'Error!' ) } ${ error . message } ` ) ;
99
- console . log ( `> ${ pc . red ( 'Error!' ) } ${ file } FAILED (in ${ target } )` ) ;
138
+
139
+ process . stdout . on ( 'data' , ( data ) => {
140
+ output . push ( data . toString ( ) ) ;
141
+ } ) ;
142
+
143
+ process . stderr . on ( 'data' , ( data ) => {
144
+ output . push ( data . toString ( ) ) ;
145
+ } ) ;
146
+
147
+ process . on ( 'error' , ( error ) => {
148
+ removeProcess ( ) ;
149
+ rejectWithError ( error ) ;
150
+ } ) ;
151
+ } ) ;
152
+ }
153
+
154
+ const clearLastLine = ( ) => {
155
+ if ( isCI ) return ;
156
+ process . stdout . moveCursor ( 0 , - 1 ) ; // up one line
157
+ process . stdout . clearLine ( 1 ) ; // from cursor to end
158
+ } ;
159
+
160
+ async function run ( ) {
161
+ let done = 0 ;
162
+ let ok = 0 ;
163
+ let failed = [ ] ;
164
+ const start = Date . now ( ) ;
165
+
166
+ function addLog ( log , isError = false ) {
167
+ clearLastLine ( ) ;
168
+ if ( isError ) {
169
+ console . error ( log ) ;
170
+ } else {
171
+ console . log ( log ) ;
172
+ }
173
+ }
174
+
175
+ const promises = files . sort ( ) . map ( ( file ) => async ( ) => {
176
+ file = path . resolve ( file ) ;
177
+ const startTest = Date . now ( ) ;
178
+ try {
179
+ if ( ! isCI ) {
180
+ console . log ( pc . gray ( `⏳ ${ file } - ${ done } /${ files . length } ` ) ) ;
181
+ }
182
+ await runTest ( file ) ;
183
+ ok ++ ;
184
+ addLog (
185
+ pc . green (
186
+ `✔ ${ file } ok - ${ msToHumanDuration ( Date . now ( ) - startTest ) } ` ,
187
+ ) ,
188
+ ) ;
189
+ } catch ( error ) {
190
+ failed . push ( {
191
+ file,
192
+ error : error . message ,
193
+ output : error . logOutput ,
194
+ } ) ;
195
+ addLog (
196
+ pc . red (
197
+ `✖ ${ file } FAILED (in ${ target } ) - ${ msToHumanDuration ( Date . now ( ) - startTest ) } \n${ error . message } ` ,
198
+ ) ,
199
+ true ,
200
+ ) ;
201
+ }
202
+
203
+ done ++ ;
204
+ } ) ;
205
+
206
+ for ( let i = 0 ; i < promises . length ; i ++ ) {
207
+ await promises [ i ] ( ) ;
208
+ }
209
+
210
+ const end = Date . now ( ) ;
211
+
212
+ console . log ( '' ) ;
213
+ console . log ( '*************************************' ) ;
214
+ console . log ( 'Summary' ) ;
215
+ console . log ( '*************************************' ) ;
216
+ console . log ( '' ) ;
217
+
218
+ console . log ( `Total: ${ done } ` ) ;
219
+ console . log ( `Ok: ${ ok } ` ) ;
220
+ console . log ( `Failed: ${ failed . length } ` ) ;
221
+ // print failed tests
222
+ for ( const { file, error, output } of failed ) {
223
+ console . log ( '' ) ;
224
+ console . log ( `--- ${ file } ---` ) ;
225
+ console . log ( pc . red ( error ) ) ;
226
+ console . log ( pc . red ( output ) ) ;
227
+ }
228
+ console . log ( `Time: ${ msToHumanDuration ( end - start ) } ` ) ;
229
+
230
+ if ( failed . length > 0 ) {
100
231
process . exit ( 2 ) ;
101
232
}
102
- console . log ( file , 'ok' ) ;
103
- } ) ;
233
+ }
234
+
235
+ function cleanup ( ) {
236
+ for ( const process of activeProcesses ) {
237
+ process . kill ( ) ;
238
+ }
239
+ }
240
+
241
+ process . on ( 'SIGINT' , cleanup ) ;
242
+ process . on ( 'SIGTERM' , cleanup ) ;
243
+
244
+ run ( ) ;
0 commit comments