Skip to content

Commit 7f63137

Browse files
committed
fix #342 set child process debug port to available
1 parent c9c00d6 commit 7f63137

File tree

4 files changed

+184
-128
lines changed

4 files changed

+184
-128
lines changed

api.js

+163-126
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ var uniqueTempDir = require('unique-temp-dir');
1010
var findCacheDir = require('find-cache-dir');
1111
var debounce = require('lodash.debounce');
1212
var ms = require('ms');
13+
var getPort = require('get-port');
1314
var AvaError = require('./lib/ava-error');
1415
var fork = require('./lib/fork');
1516
var CachingPrecompiler = require('./lib/caching-precompiler');
@@ -42,7 +43,7 @@ function Api(options) {
4243
util.inherits(Api, EventEmitter);
4344
module.exports = Api;
4445

45-
Api.prototype._runFile = function (file, runStatus) {
46+
Api.prototype._runFile = function (file, runStatus, execArgv) {
4647
var hash = this.precompiler.precompileFile(file);
4748
var precompiled = {};
4849
precompiled[file] = hash;
@@ -51,7 +52,7 @@ Api.prototype._runFile = function (file, runStatus) {
5152
precompiled: precompiled
5253
});
5354

54-
var emitter = fork(file, options);
55+
var emitter = fork(file, options, execArgv);
5556

5657
runStatus.observeFork(emitter);
5758

@@ -127,6 +128,36 @@ Api.prototype._run = function (files, _options) {
127128
return overwatch;
128129
};
129130

131+
Api.prototype.computeForkExecArgs = function (files) {
132+
var execArgv = this.options.testOnlyExecArgv || process.execArgv;
133+
var debugArgIndex = -1;
134+
execArgv.some(function (arg, index) {
135+
if (arg === '--debug' || arg === '--debug-brk' || arg.indexOf('--debug-brk=') === 0 || arg.indexOf('--debug=') === 0) {
136+
debugArgIndex = index;
137+
return true;
138+
}
139+
return false;
140+
});
141+
142+
if (debugArgIndex === -1) {
143+
return Promise.resolve([]);
144+
}
145+
146+
return Promise.map(files, getPort)
147+
.then(function (ports) {
148+
return ports.map(function (port) {
149+
var forkExecArgv = execArgv.slice();
150+
var flagName = '--debug';
151+
var oldValue = forkExecArgv[debugArgIndex];
152+
if (oldValue.indexOf('brk') > 0) {
153+
flagName += '-brk';
154+
}
155+
forkExecArgv[debugArgIndex] = flagName + '=' + port;
156+
return forkExecArgv;
157+
});
158+
});
159+
};
160+
130161
Api.prototype._runNoPool = function (files, runStatus) {
131162
var self = this;
132163
var tests = new Array(self.fileCount);
@@ -138,94 +169,97 @@ Api.prototype._runNoPool = function (files, runStatus) {
138169
});
139170
});
140171

141-
return new Promise(function (resolve) {
142-
function run() {
143-
if (self.options.match.length > 0 && !runStatus.hasExclusive) {
144-
runStatus.handleExceptions({
145-
exception: new AvaError('Couldn\'t find any matching tests'),
146-
file: undefined
147-
});
172+
return self.computeForkExecArgs(files)
173+
.then(function (execArgvList) {
174+
return new Promise(function (resolve) {
175+
function run() {
176+
if (self.options.match.length > 0 && !runStatus.hasExclusive) {
177+
runStatus.handleExceptions({
178+
exception: new AvaError('Couldn\'t find any matching tests'),
179+
file: undefined
180+
});
181+
182+
resolve([]);
183+
return;
184+
}
148185

149-
resolve([]);
150-
return;
151-
}
152-
153-
var method = self.options.serial ? 'mapSeries' : 'map';
154-
var options = {
155-
runOnlyExclusive: runStatus.hasExclusive
156-
};
157-
158-
resolve(Promise[method](files, function (file, index) {
159-
return tests[index].run(options).catch(function (err) {
160-
// The test failed catastrophically. Flag it up as an
161-
// exception, then return an empty result. Other tests may
162-
// continue to run.
163-
runStatus.handleExceptions({
164-
exception: err,
165-
file: path.relative('.', file)
166-
});
186+
var method = self.options.serial ? 'mapSeries' : 'map';
187+
var options = {
188+
runOnlyExclusive: runStatus.hasExclusive
189+
};
167190

168-
return getBlankResults();
169-
});
170-
}));
171-
}
191+
resolve(Promise[method](files, function (file, index) {
192+
return tests[index].run(options).catch(function (err) {
193+
// The test failed catastrophically. Flag it up as an
194+
// exception, then return an empty result. Other tests may
195+
// continue to run.
196+
runStatus.handleExceptions({
197+
exception: err,
198+
file: path.relative('.', file)
199+
});
200+
201+
return getBlankResults();
202+
});
203+
}));
204+
}
172205

173-
// receive test count from all files and then run the tests
174-
var unreportedFiles = self.fileCount;
175-
var bailed = false;
206+
// receive test count from all files and then run the tests
207+
var unreportedFiles = self.fileCount;
208+
var bailed = false;
176209

177-
files.every(function (file, index) {
178-
var tried = false;
210+
files.every(function (file, index) {
211+
var tried = false;
179212

180-
function tryRun() {
181-
if (!tried && !bailed) {
182-
tried = true;
183-
unreportedFiles--;
213+
function tryRun() {
214+
if (!tried && !bailed) {
215+
tried = true;
216+
unreportedFiles--;
184217

185-
if (unreportedFiles === 0) {
186-
run();
218+
if (unreportedFiles === 0) {
219+
run();
220+
}
221+
}
187222
}
188-
}
189-
}
190223

191-
try {
192-
var test = tests[index] = self._runFile(file, runStatus);
224+
try {
225+
var test = tests[index] = self._runFile(file, runStatus, execArgvList[index]);
193226

194-
test.on('stats', tryRun);
195-
test.catch(tryRun);
227+
test.on('stats', tryRun);
228+
test.catch(tryRun);
196229

197-
return true;
198-
} catch (err) {
199-
bailed = true;
230+
return true;
231+
} catch (err) {
232+
bailed = true;
200233

201-
runStatus.handleExceptions({
202-
exception: err,
203-
file: path.relative('.', file)
204-
});
234+
runStatus.handleExceptions({
235+
exception: err,
236+
file: path.relative('.', file)
237+
});
205238

206-
resolve([]);
239+
resolve([]);
207240

208-
return false;
209-
}
210-
});
211-
}).then(function (results) {
212-
if (results.length === 0) {
213-
// No tests ran, make sure to tear down the child processes.
214-
tests.forEach(function (test) {
215-
test.send('teardown');
216-
});
217-
}
241+
return false;
242+
}
243+
});
244+
}).then(function (results) {
245+
if (results.length === 0) {
246+
// No tests ran, make sure to tear down the child processes.
247+
tests.forEach(function (test) {
248+
test.send('teardown');
249+
});
250+
}
218251

219-
return results;
220-
}).then(function (results) {
221-
// cancel debounced _onTimeout() from firing
222-
if (self.options.timeout) {
223-
runStatus._restartTimer.cancel();
224-
}
252+
return results;
253+
}).then(function (results) {
254+
// cancel debounced _onTimeout() from firing
255+
if (self.options.timeout) {
256+
runStatus._restartTimer.cancel();
257+
}
225258

226-
runStatus.processResults(results);
227-
return runStatus;
228-
});
259+
runStatus.processResults(results);
260+
return runStatus;
261+
});
262+
});
229263
};
230264

231265
function getBlankResults() {
@@ -253,57 +287,60 @@ Api.prototype._runLimitedPool = function (files, runStatus, concurrency) {
253287
});
254288
});
255289

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

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

lib/fork.js

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

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

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

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
"figures": "^1.4.0",
109109
"find-cache-dir": "^0.1.1",
110110
"fn-name": "^2.0.0",
111+
"get-port": "^2.1.0",
111112
"globby": "^4.0.0",
112113
"has-flag": "^2.0.0",
113114
"ignore-by-default": "^1.0.0",

test/api.js

+17
Original file line numberDiff line numberDiff line change
@@ -1010,3 +1010,20 @@ function generateTests(prefix, apiCreator) {
10101010
]);
10111011
});
10121012
}
1013+
1014+
function generatePassDebugTests(execArgv) {
1015+
test('pass ' + execArgv.join(' ') + ' to fork', function (t) {
1016+
t.plan(3);
1017+
1018+
var api = new Api({testOnlyExecArgv: execArgv});
1019+
return api.computeForkExecArgs(['foo.js'])
1020+
.then(function (result) {
1021+
t.true(result.length === 1);
1022+
t.true(result[0].length === 1);
1023+
t.true(/--debug=\d+/.test(result[0][0]));
1024+
});
1025+
});
1026+
}
1027+
1028+
generatePassDebugTests(['--debug=0']);
1029+
generatePassDebugTests(['--debug']);

0 commit comments

Comments
 (0)