Skip to content

Commit 10df425

Browse files
committed
fix avajs#342 set child process debug port to available
1 parent adc823e commit 10df425

File tree

4 files changed

+185
-128
lines changed

4 files changed

+185
-128
lines changed

api.js

Lines changed: 163 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ var findCacheDir = require('find-cache-dir');
1111
var debounce = require('lodash.debounce');
1212
var ms = require('ms');
1313
var AvaFiles = require('ava-files');
14+
var getPort = require('get-port');
1415
var AvaError = require('./lib/ava-error');
1516
var fork = require('./lib/fork');
1617
var CachingPrecompiler = require('./lib/caching-precompiler');
@@ -46,7 +47,7 @@ function Api(options) {
4647
util.inherits(Api, EventEmitter);
4748
module.exports = Api;
4849

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

58-
var emitter = fork(file, options);
59+
var emitter = fork(file, options, execArgv);
5960

6061
runStatus.observeFork(emitter);
6162

@@ -133,6 +134,36 @@ Api.prototype._run = function (files, _options) {
133134
return overwatch;
134135
};
135136

137+
Api.prototype.computeForkExecArgs = function (files) {
138+
var execArgv = this.options.testOnlyExecArgv || process.execArgv;
139+
var debugArgIndex = -1;
140+
execArgv.some(function (arg, index) {
141+
if (arg === '--debug' || arg === '--debug-brk' || arg.indexOf('--debug-brk=') === 0 || arg.indexOf('--debug=') === 0) {
142+
debugArgIndex = index;
143+
return true;
144+
}
145+
return false;
146+
});
147+
148+
if (debugArgIndex === -1) {
149+
return Promise.resolve([]);
150+
}
151+
152+
return Promise.map(files, getPort)
153+
.then(function (ports) {
154+
return ports.map(function (port) {
155+
var forkExecArgv = execArgv.slice();
156+
var flagName = '--debug';
157+
var oldValue = forkExecArgv[debugArgIndex];
158+
if (oldValue.indexOf('brk') > 0) {
159+
flagName += '-brk';
160+
}
161+
forkExecArgv[debugArgIndex] = flagName + '=' + port;
162+
return forkExecArgv;
163+
});
164+
});
165+
};
166+
136167
Api.prototype._runNoPool = function (files, runStatus) {
137168
var self = this;
138169
var tests = new Array(self.fileCount);
@@ -144,94 +175,97 @@ Api.prototype._runNoPool = function (files, runStatus) {
144175
});
145176
});
146177

147-
return new Promise(function (resolve) {
148-
function run() {
149-
if (self.options.match.length > 0 && !runStatus.hasExclusive) {
150-
runStatus.handleExceptions({
151-
exception: new AvaError('Couldn\'t find any matching tests'),
152-
file: undefined
153-
});
178+
return self.computeForkExecArgs(files)
179+
.then(function (execArgvList) {
180+
return new Promise(function (resolve) {
181+
function run() {
182+
if (self.options.match.length > 0 && !runStatus.hasExclusive) {
183+
runStatus.handleExceptions({
184+
exception: new AvaError('Couldn\'t find any matching tests'),
185+
file: undefined
186+
});
187+
188+
resolve([]);
189+
return;
190+
}
154191

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

174-
return getBlankResults();
175-
});
176-
}));
177-
}
197+
resolve(Promise[method](files, function (file, index) {
198+
return tests[index].run(options).catch(function (err) {
199+
// The test failed catastrophically. Flag it up as an
200+
// exception, then return an empty result. Other tests may
201+
// continue to run.
202+
runStatus.handleExceptions({
203+
exception: err,
204+
file: path.relative('.', file)
205+
});
206+
207+
return getBlankResults();
208+
});
209+
}));
210+
}
178211

179-
// receive test count from all files and then run the tests
180-
var unreportedFiles = self.fileCount;
181-
var bailed = false;
212+
// receive test count from all files and then run the tests
213+
var unreportedFiles = self.fileCount;
214+
var bailed = false;
182215

183-
files.every(function (file, index) {
184-
var tried = false;
216+
files.every(function (file, index) {
217+
var tried = false;
185218

186-
function tryRun() {
187-
if (!tried && !bailed) {
188-
tried = true;
189-
unreportedFiles--;
219+
function tryRun() {
220+
if (!tried && !bailed) {
221+
tried = true;
222+
unreportedFiles--;
190223

191-
if (unreportedFiles === 0) {
192-
run();
224+
if (unreportedFiles === 0) {
225+
run();
226+
}
227+
}
193228
}
194-
}
195-
}
196229

197-
try {
198-
var test = tests[index] = self._runFile(file, runStatus);
230+
try {
231+
var test = tests[index] = self._runFile(file, runStatus, execArgvList[index]);
199232

200-
test.on('stats', tryRun);
201-
test.catch(tryRun);
233+
test.on('stats', tryRun);
234+
test.catch(tryRun);
202235

203-
return true;
204-
} catch (err) {
205-
bailed = true;
236+
return true;
237+
} catch (err) {
238+
bailed = true;
206239

207-
runStatus.handleExceptions({
208-
exception: err,
209-
file: path.relative('.', file)
210-
});
240+
runStatus.handleExceptions({
241+
exception: err,
242+
file: path.relative('.', file)
243+
});
211244

212-
resolve([]);
245+
resolve([]);
213246

214-
return false;
215-
}
216-
});
217-
}).then(function (results) {
218-
if (results.length === 0) {
219-
// No tests ran, make sure to tear down the child processes.
220-
tests.forEach(function (test) {
221-
test.send('teardown');
222-
});
223-
}
247+
return false;
248+
}
249+
});
250+
}).then(function (results) {
251+
if (results.length === 0) {
252+
// No tests ran, make sure to tear down the child processes.
253+
tests.forEach(function (test) {
254+
test.send('teardown');
255+
});
256+
}
224257

225-
return results;
226-
}).then(function (results) {
227-
// cancel debounced _onTimeout() from firing
228-
if (self.options.timeout) {
229-
runStatus._restartTimer.cancel();
230-
}
258+
return results;
259+
}).then(function (results) {
260+
// cancel debounced _onTimeout() from firing
261+
if (self.options.timeout) {
262+
runStatus._restartTimer.cancel();
263+
}
231264

232-
runStatus.processResults(results);
233-
return runStatus;
234-
});
265+
runStatus.processResults(results);
266+
return runStatus;
267+
});
268+
});
235269
};
236270

237271
function getBlankResults() {
@@ -259,57 +293,60 @@ Api.prototype._runLimitedPool = function (files, runStatus, concurrency) {
259293
});
260294
});
261295

262-
return Promise.map(files, function (file) {
263-
var handleException = function (err) {
264-
runStatus.handleExceptions({
265-
exception: err,
266-
file: path.relative('.', file)
267-
});
268-
};
269-
270-
try {
271-
var test = tests[file] = self._runFile(file, runStatus);
272-
273-
return new Promise(function (resolve, reject) {
274-
var runner = function () {
275-
var options = {
276-
// If we're looking for matches, run every single test process in exclusive-only mode
277-
runOnlyExclusive: self.options.match.length > 0
296+
return self.computeForkExecArgs(files)
297+
.then(function (execArgvList) {
298+
return Promise.map(files, function (file, index) {
299+
var handleException = function (err) {
300+
runStatus.handleExceptions({
301+
exception: err,
302+
file: path.relative('.', file)
303+
});
278304
};
279-
test.run(options)
280-
.then(resolve)
281-
.catch(reject);
282-
};
283-
284-
test.on('stats', runner);
285-
test.on('exit', function () {
286-
delete tests[file];
287-
});
288-
test.catch(runner);
289-
}).catch(handleException);
290-
} catch (err) {
291-
handleException(err);
292-
}
293-
}, {concurrency: concurrency})
294-
.then(function (results) {
295-
// Filter out undefined results (usually result of caught exceptions)
296-
results = results.filter(Boolean);
297-
298-
// cancel debounced _onTimeout() from firing
299-
if (self.options.timeout) {
300-
runStatus._restartTimer.cancel();
301-
}
302-
303-
if (self.options.match.length > 0 && !runStatus.hasExclusive) {
304-
// Ensure results are empty
305-
results = [];
306-
runStatus.handleExceptions({
307-
exception: new AvaError('Couldn\'t find any matching tests'),
308-
file: undefined
309-
});
310-
}
311305

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

lib/fork.js

Lines changed: 3 additions & 2 deletions
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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@
118118
"figures": "^1.4.0",
119119
"find-cache-dir": "^0.1.1",
120120
"fn-name": "^2.0.0",
121+
"get-port": "^2.1.0",
122+
"globby": "^5.0.0",
121123
"has-flag": "^2.0.0",
122124
"ignore-by-default": "^1.0.0",
123125
"is-ci": "^1.0.7",

test/api.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,3 +1022,20 @@ function generateTests(prefix, apiCreator) {
10221022
]);
10231023
});
10241024
}
1025+
1026+
function generatePassDebugTests(execArgv) {
1027+
test('pass ' + execArgv.join(' ') + ' to fork', function (t) {
1028+
t.plan(3);
1029+
1030+
var api = new Api({testOnlyExecArgv: execArgv});
1031+
return api.computeForkExecArgs(['foo.js'])
1032+
.then(function (result) {
1033+
t.true(result.length === 1);
1034+
t.true(result[0].length === 1);
1035+
t.true(/--debug=\d+/.test(result[0][0]));
1036+
});
1037+
});
1038+
}
1039+
1040+
generatePassDebugTests(['--debug=0']);
1041+
generatePassDebugTests(['--debug']);

0 commit comments

Comments
 (0)