testem 3.19.1 → 3.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -134
- package/lib/api.js +1 -1
- package/lib/app.js +186 -116
- package/lib/cli-local-delegation.js +93 -0
- package/lib/config.js +260 -119
- package/lib/file_watcher.js +111 -37
- package/lib/file_watcher_impl.js +295 -0
- package/lib/launcher.js +7 -12
- package/lib/reporters/dev/runner_tabs.js +52 -47
- package/lib/reporters/dev/split_log_panel.js +1 -1
- package/lib/reporters/dev/toast_notify.js +5 -0
- package/lib/runners/browser_test_runner.js +3 -1
- package/lib/runners/hook_runner.js +0 -1
- package/lib/server/index.js +39 -10
- package/lib/tap_consumer.js +73 -11
- package/lib/utils/esbuild-browser-polyfills.js +1 -1
- package/lib/utils/esbuild-buffer-inject.js +1 -1
- package/lib/utils/expand_glob_pattern.js +19 -0
- package/lib/utils/file_watch_glob_policy.js +119 -0
- package/lib/utils/is_emfile_error.js +16 -0
- package/lib/utils/known-browsers.js +155 -118
- package/lib/utils/path_pattern_match.js +39 -0
- package/lib/utils/process.js +42 -0
- package/lib/utils/promises.js +20 -1
- package/lib/utils/reporter.js +2 -2
- package/lib/utils/tmp-cleanup.js +26 -0
- package/package.json +40 -31
- package/testem.js +174 -78
package/lib/app.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
1
|
const EventEmitter = require('events').EventEmitter;
|
|
4
2
|
const { fromCallback, using, disposer, mapLimit } = require('./utils/promises');
|
|
5
3
|
const Path = require('path');
|
|
@@ -40,7 +38,7 @@ module.exports = class App extends EventEmitter {
|
|
|
40
38
|
|
|
41
39
|
let alreadyExit = false;
|
|
42
40
|
|
|
43
|
-
this.cleanExit = err => {
|
|
41
|
+
this.cleanExit = (err) => {
|
|
44
42
|
if (!alreadyExit) {
|
|
45
43
|
alreadyExit = true;
|
|
46
44
|
|
|
@@ -52,7 +50,9 @@ module.exports = class App extends EventEmitter {
|
|
|
52
50
|
|
|
53
51
|
if (this.testSuiteTimedOut === true) {
|
|
54
52
|
let timeoutSeconds = this.testSuiteTimeout.timeout;
|
|
55
|
-
err = new Error(
|
|
53
|
+
err = new Error(
|
|
54
|
+
`Test suite execution has timed out (config.timeout = ${timeoutSeconds} seconds). Terminated all test runners.`,
|
|
55
|
+
);
|
|
56
56
|
exitCode = 1;
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -64,53 +64,63 @@ module.exports = class App extends EventEmitter {
|
|
|
64
64
|
start(cb) {
|
|
65
65
|
log.info('Starting ' + this.config.appMode);
|
|
66
66
|
|
|
67
|
-
return using(SignalListeners.with(), signalListeners => {
|
|
68
|
-
signalListeners.on('signal', err => this.exit(err));
|
|
67
|
+
return using(SignalListeners.with(), (signalListeners) => {
|
|
68
|
+
signalListeners.on('signal', (err) => this.exit(err));
|
|
69
69
|
|
|
70
|
-
return using(
|
|
71
|
-
this.
|
|
70
|
+
return using(
|
|
71
|
+
Reporter.with(this, this.stdoutStream, this.reportFileName),
|
|
72
|
+
(reporter) => {
|
|
73
|
+
this.reporter = reporter;
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
return using(this.fileWatch(), () => {
|
|
76
|
+
return using(this.getServer(), () => {
|
|
77
|
+
return using(this.getRunners(), () => {
|
|
78
|
+
return using(this.runHook('on_start'), () => {
|
|
79
|
+
let w = this.waitForTests();
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
if (cb) {
|
|
82
|
+
cb();
|
|
83
|
+
}
|
|
82
84
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
return w;
|
|
86
|
+
})
|
|
87
|
+
.then(() => {
|
|
88
|
+
log.info('Stopping ' + this.config.appMode);
|
|
86
89
|
|
|
87
|
-
|
|
90
|
+
this.emit('tests-finish');
|
|
88
91
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
return using(this.runHook('on_exit'), () => {});
|
|
93
|
+
})
|
|
94
|
+
.catch((error) => {
|
|
95
|
+
log.error(error);
|
|
96
|
+
log.info('Stopping ' + this.config.appMode);
|
|
93
97
|
|
|
94
|
-
|
|
98
|
+
this.emit('tests-error');
|
|
95
99
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
using(this.runHook('on_exit'), () => {}).then(() => {
|
|
102
|
+
reject(error);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
99
105
|
});
|
|
100
|
-
});
|
|
101
106
|
});
|
|
102
107
|
});
|
|
103
108
|
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
}).then(
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
}).then(
|
|
112
|
+
() => this.cleanExit(),
|
|
113
|
+
(err) => this.cleanExit(err),
|
|
114
|
+
);
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
waitForTests() {
|
|
110
118
|
log.info('Waiting for tests.');
|
|
111
119
|
|
|
112
120
|
if (this.exited) {
|
|
113
|
-
return Promise.reject(
|
|
121
|
+
return Promise.reject(
|
|
122
|
+
this.exitErr || new Error('Testem exited before running any tests.'),
|
|
123
|
+
);
|
|
114
124
|
}
|
|
115
125
|
|
|
116
126
|
let run = this.triggerRun('Start');
|
|
@@ -133,11 +143,13 @@ module.exports = class App extends EventEmitter {
|
|
|
133
143
|
}
|
|
134
144
|
this.restarting = true;
|
|
135
145
|
|
|
136
|
-
return this.stopCurrentRun()
|
|
137
|
-
this.
|
|
146
|
+
return this.stopCurrentRun()
|
|
147
|
+
.catch(this.exit.bind(this))
|
|
148
|
+
.then(() => {
|
|
149
|
+
this.restarting = false;
|
|
138
150
|
|
|
139
|
-
|
|
140
|
-
|
|
151
|
+
return this.runTests();
|
|
152
|
+
});
|
|
141
153
|
}
|
|
142
154
|
|
|
143
155
|
stopCurrentRun() {
|
|
@@ -145,7 +157,7 @@ module.exports = class App extends EventEmitter {
|
|
|
145
157
|
return Promise.resolve();
|
|
146
158
|
}
|
|
147
159
|
|
|
148
|
-
return Promise.all([
|
|
160
|
+
return Promise.all([this.stopRunners(), this.currentRun]);
|
|
149
161
|
}
|
|
150
162
|
|
|
151
163
|
runTests() {
|
|
@@ -158,13 +170,15 @@ module.exports = class App extends EventEmitter {
|
|
|
158
170
|
this.reporter.onStart('testem', { launcherId: 0 });
|
|
159
171
|
|
|
160
172
|
return using(this.runHook('before_tests'), () => {
|
|
161
|
-
return using(RunTimeout.with(this.config.get('timeout')), timeout => {
|
|
173
|
+
return using(RunTimeout.with(this.config.get('timeout')), (timeout) => {
|
|
162
174
|
this.testSuiteTimeout = timeout;
|
|
163
175
|
|
|
164
176
|
timeout.on('timeout', () => {
|
|
165
177
|
let timeoutSeconds = timeout.timeout;
|
|
166
178
|
|
|
167
|
-
log.info(
|
|
179
|
+
log.info(
|
|
180
|
+
`Test suite execution has timed out (config.timeout = ${timeoutSeconds} seconds). Terminating all test runners`,
|
|
181
|
+
);
|
|
168
182
|
this.testSuiteTimedOut = true;
|
|
169
183
|
this.killRunners();
|
|
170
184
|
});
|
|
@@ -178,23 +192,25 @@ module.exports = class App extends EventEmitter {
|
|
|
178
192
|
}).then(() => {
|
|
179
193
|
return using(this.runHook('after_tests'), () => {});
|
|
180
194
|
});
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
let result = {
|
|
187
|
-
failed: 1,
|
|
188
|
-
passed: 0,
|
|
189
|
-
name: 'testem',
|
|
190
|
-
launcherId: 0,
|
|
191
|
-
error: {
|
|
192
|
-
message: err.toString()
|
|
195
|
+
})
|
|
196
|
+
.catch((err) => {
|
|
197
|
+
if (err.hideFromReporter) {
|
|
198
|
+
return;
|
|
193
199
|
}
|
|
194
|
-
};
|
|
195
200
|
|
|
196
|
-
|
|
197
|
-
|
|
201
|
+
let result = {
|
|
202
|
+
failed: 1,
|
|
203
|
+
passed: 0,
|
|
204
|
+
name: 'testem',
|
|
205
|
+
launcherId: 0,
|
|
206
|
+
error: {
|
|
207
|
+
message: err.toString(),
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
this.reporter.report('testem', result);
|
|
212
|
+
})
|
|
213
|
+
.finally(() => this.reporter.onEnd('testem', { launcherId: 0 }));
|
|
198
214
|
}
|
|
199
215
|
|
|
200
216
|
exit(err, cb) {
|
|
@@ -233,12 +249,25 @@ module.exports = class App extends EventEmitter {
|
|
|
233
249
|
}
|
|
234
250
|
|
|
235
251
|
getServer() {
|
|
236
|
-
return disposer(Promise.resolve(this.startServer()), () =>
|
|
252
|
+
return disposer(Promise.resolve(this.startServer()), () =>
|
|
253
|
+
this.stopServer(),
|
|
254
|
+
);
|
|
237
255
|
}
|
|
238
256
|
|
|
239
|
-
|
|
257
|
+
/**
|
|
258
|
+
* When the server requests a file, add it to the file watcher if it is not already being watched.
|
|
259
|
+
* If the file watcher is disabled, do nothing.
|
|
260
|
+
*
|
|
261
|
+
* @param {string} filepath
|
|
262
|
+
* @returns {Promise<void>}
|
|
263
|
+
*/
|
|
264
|
+
async onFileRequested(filepath) {
|
|
240
265
|
if (this.fileWatcher && !this.config.get('serve_files')) {
|
|
241
|
-
|
|
266
|
+
try {
|
|
267
|
+
await this.fileWatcher.add(filepath);
|
|
268
|
+
} catch (err) {
|
|
269
|
+
log.error(err);
|
|
270
|
+
}
|
|
242
271
|
}
|
|
243
272
|
}
|
|
244
273
|
|
|
@@ -251,18 +280,30 @@ module.exports = class App extends EventEmitter {
|
|
|
251
280
|
}
|
|
252
281
|
|
|
253
282
|
onBrowserLogin(browserName, id, socket) {
|
|
254
|
-
let browser = _.find(this.runners, runner => {
|
|
255
|
-
return
|
|
283
|
+
let browser = _.find(this.runners, (runner) => {
|
|
284
|
+
return (
|
|
285
|
+
runner.launcherId === id && (!runner.socket || !runner.socket.connected)
|
|
286
|
+
);
|
|
256
287
|
});
|
|
257
288
|
|
|
258
289
|
if (!browser) {
|
|
259
|
-
let launcher = new LauncherFactory(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
290
|
+
let launcher = new LauncherFactory(
|
|
291
|
+
browserName,
|
|
292
|
+
{
|
|
293
|
+
id: id,
|
|
294
|
+
protocol: 'browser',
|
|
295
|
+
},
|
|
296
|
+
this.config,
|
|
297
|
+
).create();
|
|
263
298
|
const singleRun = this.config.get('single_run');
|
|
264
299
|
|
|
265
|
-
browser = new BrowserTestRunner(
|
|
300
|
+
browser = new BrowserTestRunner(
|
|
301
|
+
launcher,
|
|
302
|
+
this.reporter,
|
|
303
|
+
this.runnerIndex++,
|
|
304
|
+
singleRun,
|
|
305
|
+
this.config,
|
|
306
|
+
);
|
|
266
307
|
this.addRunner(browser);
|
|
267
308
|
}
|
|
268
309
|
|
|
@@ -270,10 +311,12 @@ module.exports = class App extends EventEmitter {
|
|
|
270
311
|
}
|
|
271
312
|
|
|
272
313
|
onBrowserRelogin(browserName, id, socket) {
|
|
273
|
-
let browser = _.find(this.runners, runner => {
|
|
314
|
+
let browser = _.find(this.runners, (runner) => {
|
|
274
315
|
// a browser relogin can happen if a client socket was disconnected, which may not be reflected in runner.socket's connected state
|
|
275
316
|
// or if the socket was nulled by 'onDisconnect'
|
|
276
|
-
return
|
|
317
|
+
return (
|
|
318
|
+
runner.launcherId === id && (runner.socket || runner.socket === null)
|
|
319
|
+
);
|
|
277
320
|
});
|
|
278
321
|
|
|
279
322
|
if (!browser) {
|
|
@@ -293,8 +336,15 @@ module.exports = class App extends EventEmitter {
|
|
|
293
336
|
this.emit('runnerAdded', runner);
|
|
294
337
|
}
|
|
295
338
|
|
|
339
|
+
// Watcher teardown is this disposer's cleanup (using() awaits it on success and on setup failure).
|
|
296
340
|
fileWatch() {
|
|
297
|
-
return disposer(Promise.resolve(this.configureFileWatch()), () => {
|
|
341
|
+
return disposer(Promise.resolve(this.configureFileWatch()), () => {
|
|
342
|
+
if (this.fileWatcher) {
|
|
343
|
+
const w = this.fileWatcher;
|
|
344
|
+
this.fileWatcher = undefined;
|
|
345
|
+
return w.close();
|
|
346
|
+
}
|
|
347
|
+
});
|
|
298
348
|
}
|
|
299
349
|
|
|
300
350
|
configureFileWatch() {
|
|
@@ -302,46 +352,58 @@ module.exports = class App extends EventEmitter {
|
|
|
302
352
|
return Promise.resolve();
|
|
303
353
|
}
|
|
304
354
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
this.
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
355
|
+
return FileWatcher.create(this.config).then((fw) => {
|
|
356
|
+
this.fileWatcher = fw;
|
|
357
|
+
this.fileWatcher.on('fileChanged', (filepath) => {
|
|
358
|
+
log.info(
|
|
359
|
+
filepath +
|
|
360
|
+
' changed (' +
|
|
361
|
+
(this.disableFileWatch ? 'disabled' : 'enabled') +
|
|
362
|
+
').',
|
|
363
|
+
);
|
|
364
|
+
if (this.disableFileWatch || this.paused) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
let configFile = this.config.get('file');
|
|
368
|
+
if (
|
|
369
|
+
(configFile && filepath === Path.resolve(configFile)) ||
|
|
370
|
+
(this.config.isCwdMode() && filepath === process.cwd())
|
|
371
|
+
) {
|
|
372
|
+
// config changed
|
|
373
|
+
this.configure(() => {
|
|
374
|
+
this.triggerRun('Config changed');
|
|
375
|
+
});
|
|
376
|
+
} else {
|
|
377
|
+
using(this.runHook('on_change', { file: filepath }), () => {
|
|
378
|
+
this.triggerRun('File changed: ' + filepath);
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
this.fileWatcher.on('EMFILE', () => {
|
|
383
|
+
let view = this.view;
|
|
384
|
+
let text = [
|
|
385
|
+
'The file watcher received a EMFILE system error, which means that ',
|
|
386
|
+
'it has hit the maximum number of files that can be open at a time. ',
|
|
387
|
+
'Luckily, you can increase this limit as a workaround. See the directions below \n \n',
|
|
388
|
+
'Linux: http://stackoverflow.com/a/34645/5304\n',
|
|
389
|
+
'Mac OS: http://serverfault.com/a/15575/47234',
|
|
390
|
+
].join('');
|
|
391
|
+
view.setErrorPopupMessage(
|
|
392
|
+
new StyledString(text + '\n ').foreground('megenta'),
|
|
393
|
+
);
|
|
394
|
+
});
|
|
334
395
|
});
|
|
335
|
-
|
|
336
|
-
return Promise.resolve();
|
|
337
396
|
}
|
|
338
397
|
|
|
339
398
|
getRunners() {
|
|
340
|
-
return disposer(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
399
|
+
return disposer(
|
|
400
|
+
fromCallback((callback) => {
|
|
401
|
+
this.createRunners(callback);
|
|
402
|
+
}),
|
|
403
|
+
() => {
|
|
404
|
+
return this.killRunners();
|
|
405
|
+
},
|
|
406
|
+
);
|
|
345
407
|
}
|
|
346
408
|
|
|
347
409
|
createRunners(callback) {
|
|
@@ -374,14 +436,20 @@ module.exports = class App extends EventEmitter {
|
|
|
374
436
|
case 'tap':
|
|
375
437
|
return TapProcessTestRunner;
|
|
376
438
|
default:
|
|
377
|
-
throw new Error('
|
|
439
|
+
throw new Error('Unknown protocol: ' + protocol);
|
|
378
440
|
}
|
|
379
441
|
}
|
|
380
442
|
|
|
381
443
|
createTestRunner(launcher, reporter) {
|
|
382
444
|
let singleRun = this.config.get('single_run');
|
|
383
445
|
|
|
384
|
-
return new (this.getRunnerFactory(launcher))(
|
|
446
|
+
return new (this.getRunnerFactory(launcher))(
|
|
447
|
+
launcher,
|
|
448
|
+
reporter,
|
|
449
|
+
this.runnerIndex++,
|
|
450
|
+
singleRun,
|
|
451
|
+
this.config,
|
|
452
|
+
);
|
|
385
453
|
}
|
|
386
454
|
|
|
387
455
|
withTestTimeout() {
|
|
@@ -392,9 +460,9 @@ module.exports = class App extends EventEmitter {
|
|
|
392
460
|
|
|
393
461
|
singleRun(timeout) {
|
|
394
462
|
let limit = this.config.get('parallel');
|
|
395
|
-
let concurrency =
|
|
463
|
+
let concurrency = limit && limit >= 1 ? parseInt(limit) : Infinity;
|
|
396
464
|
|
|
397
|
-
return mapLimit(this.runners, concurrency, runner => {
|
|
465
|
+
return mapLimit(this.runners, concurrency, (runner) => {
|
|
398
466
|
if (this.exited) {
|
|
399
467
|
let e = new Error('Run canceled.');
|
|
400
468
|
e.hideFromReporter = true;
|
|
@@ -435,20 +503,22 @@ module.exports = class App extends EventEmitter {
|
|
|
435
503
|
}
|
|
436
504
|
|
|
437
505
|
stopRunners() {
|
|
438
|
-
return Promise.all(
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
506
|
+
return Promise.all(
|
|
507
|
+
this.runners.map((runner) => {
|
|
508
|
+
if (typeof runner.stop === 'function') {
|
|
509
|
+
return runner.stop();
|
|
510
|
+
}
|
|
442
511
|
|
|
443
|
-
|
|
444
|
-
|
|
512
|
+
return runner.exit();
|
|
513
|
+
}),
|
|
514
|
+
);
|
|
445
515
|
}
|
|
446
516
|
|
|
447
517
|
killRunners() {
|
|
448
|
-
return Promise.all(this.runners.map(runner => runner.exit()));
|
|
518
|
+
return Promise.all(this.runners.map((runner) => runner.exit()));
|
|
449
519
|
}
|
|
450
520
|
|
|
451
521
|
launchers() {
|
|
452
|
-
return this.runners.map(runner => runner.launcher);
|
|
522
|
+
return this.runners.map((runner) => runner.launcher);
|
|
453
523
|
}
|
|
454
524
|
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { spawnSync } = require('child_process');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Walks upward from `startDir` looking for `node_modules/testem/testem.js`.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} startDir
|
|
9
|
+
* @returns {string | null}
|
|
10
|
+
*/
|
|
11
|
+
function findLocalTestemScript(startDir) {
|
|
12
|
+
let dir = path.resolve(startDir);
|
|
13
|
+
const { root } = path.parse(dir);
|
|
14
|
+
while (true) {
|
|
15
|
+
const candidate = path.join(dir, 'node_modules', 'testem', 'testem.js');
|
|
16
|
+
if (fs.existsSync(candidate)) {
|
|
17
|
+
return candidate;
|
|
18
|
+
}
|
|
19
|
+
if (dir === root) {
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
dir = path.dirname(dir);
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* If a local install exists and is not the same file as the running CLI (e.g. global),
|
|
29
|
+
* returns the path to the local `testem.js`. Otherwise returns null.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} currentFile - `__filename` of the running CLI
|
|
32
|
+
* @param {string} cwd
|
|
33
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
34
|
+
* @returns {string | null}
|
|
35
|
+
*/
|
|
36
|
+
function resolveLocalTestemCliOrNull(currentFile, cwd, env = process.env) {
|
|
37
|
+
if (env.TESTEM_USE_GLOBAL === '1') {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const local = findLocalTestemScript(cwd);
|
|
41
|
+
if (!local) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
let cur;
|
|
45
|
+
let loc;
|
|
46
|
+
try {
|
|
47
|
+
cur = fs.realpathSync(currentFile);
|
|
48
|
+
loc = fs.realpathSync(local);
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
if (cur === loc) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return local;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* When the globally installed `testem` is run inside a project that has a local
|
|
60
|
+
* `testem` dependency, re-exec that local copy so CLI and project stay in sync.
|
|
61
|
+
*
|
|
62
|
+
* Exits the process when delegation runs; otherwise returns normally.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} currentFile
|
|
65
|
+
* @param {string[]} argv
|
|
66
|
+
* @param {string} cwd
|
|
67
|
+
* @param {NodeJS.ProcessEnv} env
|
|
68
|
+
*/
|
|
69
|
+
function tryDelegateToLocalTestem(currentFile, argv, cwd, env) {
|
|
70
|
+
const local = resolveLocalTestemCliOrNull(currentFile, cwd, env);
|
|
71
|
+
if (!local) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const result = spawnSync(process.execPath, [local, ...argv.slice(2)], {
|
|
75
|
+
stdio: 'inherit',
|
|
76
|
+
env,
|
|
77
|
+
windowsHide: true,
|
|
78
|
+
});
|
|
79
|
+
if (result.error) {
|
|
80
|
+
console.error(result.error);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
if (result.signal) {
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
process.exit(result.status === null ? 0 : result.status);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = {
|
|
90
|
+
findLocalTestemScript,
|
|
91
|
+
resolveLocalTestemCliOrNull,
|
|
92
|
+
tryDelegateToLocalTestem,
|
|
93
|
+
};
|