Skip to content

Commit c268c8d

Browse files
develarsindresorhus
authored andcommitted
set child process debug port to an available port - fixes #342 (#874)
1 parent 8816faf commit c268c8d

File tree

5 files changed

+231
-128
lines changed

5 files changed

+231
-128
lines changed

api.js

+176-126
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ var debounce = require('lodash.debounce');
1212
var ms = require('ms');
1313
var AvaFiles = require('ava-files');
1414
var autoBind = require('auto-bind');
15+
var getPort = require('get-port');
1516
var AvaError = require('./lib/ava-error');
1617
var fork = require('./lib/fork');
1718
var CachingPrecompiler = require('./lib/caching-precompiler');
@@ -45,7 +46,7 @@ function Api(options) {
4546
util.inherits(Api, EventEmitter);
4647
module.exports = Api;
4748

48-
Api.prototype._runFile = function (file, runStatus) {
49+
Api.prototype._runFile = function (file, runStatus, execArgv) {
4950
var hash = this.precompiler.precompileFile(file);
5051
var precompiled = {};
5152
precompiled[file] = hash;
@@ -54,7 +55,7 @@ Api.prototype._runFile = function (file, runStatus) {
5455
precompiled: precompiled
5556
});
5657

57-
var emitter = fork(file, options);
58+
var emitter = fork(file, options, execArgv);
5859

5960
runStatus.observeFork(emitter);
6061

@@ -132,6 +133,49 @@ Api.prototype._run = function (files, _options) {
132133
return overwatch;
133134
};
134135

136+
Api.prototype.computeForkExecArgs = function (files) {
137+
var execArgv = this.options.testOnlyExecArgv || process.execArgv;
138+
var debugArgIndex = -1;
139+
140+
// --debug-brk is used in addition to --inspect to break on first line and wait
141+
execArgv.some(function (arg, index) {
142+
if (arg === '--inspect' || arg.indexOf('--inspect=') === 0) {
143+
debugArgIndex = index;
144+
return true;
145+
}
146+
return false;
147+
});
148+
149+
var isInspect = debugArgIndex !== -1;
150+
if (!isInspect) {
151+
execArgv.some(function (arg, index) {
152+
if (arg === '--debug' || arg === '--debug-brk' || arg.indexOf('--debug-brk=') === 0 || arg.indexOf('--debug=') === 0) {
153+
debugArgIndex = index;
154+
return true;
155+
}
156+
return false;
157+
});
158+
}
159+
160+
if (debugArgIndex === -1) {
161+
return Promise.resolve([]);
162+
}
163+
164+
return Promise.map(files, getPort)
165+
.then(function (ports) {
166+
return ports.map(function (port) {
167+
var forkExecArgv = execArgv.slice();
168+
var flagName = isInspect ? '--inspect' : '--debug';
169+
var oldValue = forkExecArgv[debugArgIndex];
170+
if (oldValue.indexOf('brk') > 0) {
171+
flagName += '-brk';
172+
}
173+
forkExecArgv[debugArgIndex] = flagName + '=' + port;
174+
return forkExecArgv;
175+
});
176+
});
177+
};
178+
135179
Api.prototype._runNoPool = function (files, runStatus) {
136180
var self = this;
137181
var tests = new Array(self.fileCount);
@@ -143,94 +187,97 @@ Api.prototype._runNoPool = function (files, runStatus) {
143187
});
144188
});
145189

146-
return new Promise(function (resolve) {
147-
function run() {
148-
if (self.options.match.length > 0 && !runStatus.hasExclusive) {
149-
runStatus.handleExceptions({
150-
exception: new AvaError('Couldn\'t find any matching tests'),
151-
file: undefined
152-
});
153-
154-
resolve([]);
155-
return;
156-
}
190+
return self.computeForkExecArgs(files)
191+
.then(function (execArgvList) {
192+
return new Promise(function (resolve) {
193+
function run() {
194+
if (self.options.match.length > 0 && !runStatus.hasExclusive) {
195+
runStatus.handleExceptions({
196+
exception: new AvaError('Couldn\'t find any matching tests'),
197+
file: undefined
198+
});
199+
200+
resolve([]);
201+
return;
202+
}
157203

158-
var method = self.options.serial ? 'mapSeries' : 'map';
159-
var options = {
160-
runOnlyExclusive: runStatus.hasExclusive
161-
};
162-
163-
resolve(Promise[method](files, function (file, index) {
164-
return tests[index].run(options).catch(function (err) {
165-
// The test failed catastrophically. Flag it up as an
166-
// exception, then return an empty result. Other tests may
167-
// continue to run.
168-
runStatus.handleExceptions({
169-
exception: err,
170-
file: path.relative('.', file)
171-
});
204+
var method = self.options.serial ? 'mapSeries' : 'map';
205+
var options = {
206+
runOnlyExclusive: runStatus.hasExclusive
207+
};
172208

173-
return getBlankResults();
174-
});
175-
}));
176-
}
209+
resolve(Promise[method](files, function (file, index) {
210+
return tests[index].run(options).catch(function (err) {
211+
// The test failed catastrophically. Flag it up as an
212+
// exception, then return an empty result. Other tests may
213+
// continue to run.
214+
runStatus.handleExceptions({
215+
exception: err,
216+
file: path.relative('.', file)
217+
});
218+
219+
return getBlankResults();
220+
});
221+
}));
222+
}
177223

178-
// receive test count from all files and then run the tests
179-
var unreportedFiles = self.fileCount;
180-
var bailed = false;
224+
// receive test count from all files and then run the tests
225+
var unreportedFiles = self.fileCount;
226+
var bailed = false;
181227

182-
files.every(function (file, index) {
183-
var tried = false;
228+
files.every(function (file, index) {
229+
var tried = false;
184230

185-
function tryRun() {
186-
if (!tried && !bailed) {
187-
tried = true;
188-
unreportedFiles--;
231+
function tryRun() {
232+
if (!tried && !bailed) {
233+
tried = true;
234+
unreportedFiles--;
189235

190-
if (unreportedFiles === 0) {
191-
run();
236+
if (unreportedFiles === 0) {
237+
run();
238+
}
239+
}
192240
}
193-
}
194-
}
195241

196-
try {
197-
var test = tests[index] = self._runFile(file, runStatus);
242+
try {
243+
var test = tests[index] = self._runFile(file, runStatus, execArgvList[index]);
198244

199-
test.on('stats', tryRun);
200-
test.catch(tryRun);
245+
test.on('stats', tryRun);
246+
test.catch(tryRun);
201247

202-
return true;
203-
} catch (err) {
204-
bailed = true;
248+
return true;
249+
} catch (err) {
250+
bailed = true;
205251

206-
runStatus.handleExceptions({
207-
exception: err,
208-
file: path.relative('.', file)
209-
});
252+
runStatus.handleExceptions({
253+
exception: err,
254+
file: path.relative('.', file)
255+
});
210256

211-
resolve([]);
257+
resolve([]);
212258

213-
return false;
214-
}
215-
});
216-
}).then(function (results) {
217-
if (results.length === 0) {
218-
// No tests ran, make sure to tear down the child processes.
219-
tests.forEach(function (test) {
220-
test.send('teardown');
221-
});
222-
}
259+
return false;
260+
}
261+
});
262+
}).then(function (results) {
263+
if (results.length === 0) {
264+
// No tests ran, make sure to tear down the child processes.
265+
tests.forEach(function (test) {
266+
test.send('teardown');
267+
});
268+
}
223269

224-
return results;
225-
}).then(function (results) {
226-
// cancel debounced _onTimeout() from firing
227-
if (self.options.timeout) {
228-
runStatus._restartTimer.cancel();
229-
}
270+
return results;
271+
}).then(function (results) {
272+
// cancel debounced _onTimeout() from firing
273+
if (self.options.timeout) {
274+
runStatus._restartTimer.cancel();
275+
}
230276

231-
runStatus.processResults(results);
232-
return runStatus;
233-
});
277+
runStatus.processResults(results);
278+
return runStatus;
279+
});
280+
});
234281
};
235282

236283
function getBlankResults() {
@@ -258,57 +305,60 @@ Api.prototype._runLimitedPool = function (files, runStatus, concurrency) {
258305
});
259306
});
260307

261-
return Promise.map(files, function (file) {
262-
var handleException = function (err) {
263-
runStatus.handleExceptions({
264-
exception: err,
265-
file: path.relative('.', file)
266-
});
267-
};
268-
269-
try {
270-
var test = tests[file] = self._runFile(file, runStatus);
271-
272-
return new Promise(function (resolve, reject) {
273-
var runner = function () {
274-
var options = {
275-
// If we're looking for matches, run every single test process in exclusive-only mode
276-
runOnlyExclusive: self.options.match.length > 0
308+
return self.computeForkExecArgs(files)
309+
.then(function (execArgvList) {
310+
return Promise.map(files, function (file, index) {
311+
var handleException = function (err) {
312+
runStatus.handleExceptions({
313+
exception: err,
314+
file: path.relative('.', file)
315+
});
277316
};
278-
test.run(options)
279-
.then(resolve)
280-
.catch(reject);
281-
};
282-
283-
test.on('stats', runner);
284-
test.on('exit', function () {
285-
delete tests[file];
286-
});
287-
test.catch(runner);
288-
}).catch(handleException);
289-
} catch (err) {
290-
handleException(err);
291-
}
292-
}, {concurrency: concurrency})
293-
.then(function (results) {
294-
// Filter out undefined results (usually result of caught exceptions)
295-
results = results.filter(Boolean);
296-
297-
// cancel debounced _onTimeout() from firing
298-
if (self.options.timeout) {
299-
runStatus._restartTimer.cancel();
300-
}
301317

302-
if (self.options.match.length > 0 && !runStatus.hasExclusive) {
303-
// Ensure results are empty
304-
results = [];
305-
runStatus.handleExceptions({
306-
exception: new AvaError('Couldn\'t find any matching tests'),
307-
file: undefined
308-
});
309-
}
310-
311-
runStatus.processResults(results);
312-
return runStatus;
313-
});
318+
try {
319+
var test = tests[file] = self._runFile(file, runStatus, execArgvList[index]);
320+
321+
return new Promise(function (resolve, reject) {
322+
var runner = function () {
323+
var options = {
324+
// If we're looking for matches, run every single test process in exclusive-only mode
325+
runOnlyExclusive: self.options.match.length > 0
326+
};
327+
test.run(options)
328+
.then(resolve)
329+
.catch(reject);
330+
};
331+
332+
test.on('stats', runner);
333+
test.on('exit', function () {
334+
delete tests[file];
335+
});
336+
test.catch(runner);
337+
}).catch(handleException);
338+
} catch (err) {
339+
handleException(err);
340+
}
341+
}, {concurrency: concurrency})
342+
.then(function (results) {
343+
// Filter out undefined results (usually result of caught exceptions)
344+
results = results.filter(Boolean);
345+
346+
// cancel debounced _onTimeout() from firing
347+
if (self.options.timeout) {
348+
runStatus._restartTimer.cancel();
349+
}
350+
351+
if (self.options.match.length > 0 && !runStatus.hasExclusive) {
352+
// Ensure results are empty
353+
results = [];
354+
runStatus.handleExceptions({
355+
exception: new AvaError('Couldn\'t find any matching tests'),
356+
file: undefined
357+
});
358+
}
359+
360+
runStatus.processResults(results);
361+
return runStatus;
362+
});
363+
});
314364
};

lib/fork.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ if (env.NODE_PATH) {
3030
.join(path.delimiter);
3131
}
3232

33-
module.exports = function (file, opts) {
33+
module.exports = function (file, opts, execArgv) {
3434
opts = objectAssign({
3535
file: file,
3636
baseDir: process.cwd(),
@@ -43,7 +43,8 @@ module.exports = function (file, opts) {
4343
var ps = childProcess.fork(path.join(__dirname, 'test-worker.js'), [JSON.stringify(opts)], {
4444
cwd: path.dirname(file),
4545
silent: true,
46-
env: env
46+
env: env,
47+
execArgv: execArgv || process.execArgv
4748
});
4849

4950
var relFile = path.relative('.', file);

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
"figures": "^1.4.0",
120120
"find-cache-dir": "^0.1.1",
121121
"fn-name": "^2.0.0",
122+
"get-port": "^2.1.0",
122123
"has-flag": "^2.0.0",
123124
"ignore-by-default": "^1.0.0",
124125
"is-ci": "^1.0.7",

0 commit comments

Comments
 (0)