Skip to content

Commit

Permalink
set child process debug port to an available port - fixes #342 (#874)
Browse files Browse the repository at this point in the history
  • Loading branch information
develar authored and sindresorhus committed Aug 12, 2016
1 parent 8816faf commit c268c8d
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 128 deletions.
302 changes: 176 additions & 126 deletions api.js
Expand Up @@ -12,6 +12,7 @@ var debounce = require('lodash.debounce');
var ms = require('ms');
var AvaFiles = require('ava-files');
var autoBind = require('auto-bind');
var getPort = require('get-port');
var AvaError = require('./lib/ava-error');
var fork = require('./lib/fork');
var CachingPrecompiler = require('./lib/caching-precompiler');
Expand Down Expand Up @@ -45,7 +46,7 @@ function Api(options) {
util.inherits(Api, EventEmitter);
module.exports = Api;

Api.prototype._runFile = function (file, runStatus) {
Api.prototype._runFile = function (file, runStatus, execArgv) {
var hash = this.precompiler.precompileFile(file);
var precompiled = {};
precompiled[file] = hash;
Expand All @@ -54,7 +55,7 @@ Api.prototype._runFile = function (file, runStatus) {
precompiled: precompiled
});

var emitter = fork(file, options);
var emitter = fork(file, options, execArgv);

runStatus.observeFork(emitter);

Expand Down Expand Up @@ -132,6 +133,49 @@ Api.prototype._run = function (files, _options) {
return overwatch;
};

Api.prototype.computeForkExecArgs = function (files) {
var execArgv = this.options.testOnlyExecArgv || process.execArgv;
var debugArgIndex = -1;

// --debug-brk is used in addition to --inspect to break on first line and wait
execArgv.some(function (arg, index) {
if (arg === '--inspect' || arg.indexOf('--inspect=') === 0) {
debugArgIndex = index;
return true;
}
return false;
});

var isInspect = debugArgIndex !== -1;
if (!isInspect) {
execArgv.some(function (arg, index) {
if (arg === '--debug' || arg === '--debug-brk' || arg.indexOf('--debug-brk=') === 0 || arg.indexOf('--debug=') === 0) {
debugArgIndex = index;
return true;
}
return false;
});
}

if (debugArgIndex === -1) {
return Promise.resolve([]);
}

return Promise.map(files, getPort)
.then(function (ports) {
return ports.map(function (port) {
var forkExecArgv = execArgv.slice();
var flagName = isInspect ? '--inspect' : '--debug';
var oldValue = forkExecArgv[debugArgIndex];
if (oldValue.indexOf('brk') > 0) {
flagName += '-brk';
}
forkExecArgv[debugArgIndex] = flagName + '=' + port;
return forkExecArgv;
});
});
};

Api.prototype._runNoPool = function (files, runStatus) {
var self = this;
var tests = new Array(self.fileCount);
Expand All @@ -143,94 +187,97 @@ Api.prototype._runNoPool = function (files, runStatus) {
});
});

return new Promise(function (resolve) {
function run() {
if (self.options.match.length > 0 && !runStatus.hasExclusive) {
runStatus.handleExceptions({
exception: new AvaError('Couldn\'t find any matching tests'),
file: undefined
});

resolve([]);
return;
}
return self.computeForkExecArgs(files)
.then(function (execArgvList) {
return new Promise(function (resolve) {
function run() {
if (self.options.match.length > 0 && !runStatus.hasExclusive) {
runStatus.handleExceptions({
exception: new AvaError('Couldn\'t find any matching tests'),
file: undefined
});

resolve([]);
return;
}

var method = self.options.serial ? 'mapSeries' : 'map';
var options = {
runOnlyExclusive: runStatus.hasExclusive
};

resolve(Promise[method](files, function (file, index) {
return tests[index].run(options).catch(function (err) {
// The test failed catastrophically. Flag it up as an
// exception, then return an empty result. Other tests may
// continue to run.
runStatus.handleExceptions({
exception: err,
file: path.relative('.', file)
});
var method = self.options.serial ? 'mapSeries' : 'map';
var options = {
runOnlyExclusive: runStatus.hasExclusive
};

return getBlankResults();
});
}));
}
resolve(Promise[method](files, function (file, index) {
return tests[index].run(options).catch(function (err) {
// The test failed catastrophically. Flag it up as an
// exception, then return an empty result. Other tests may
// continue to run.
runStatus.handleExceptions({
exception: err,
file: path.relative('.', file)
});

return getBlankResults();
});
}));
}

// receive test count from all files and then run the tests
var unreportedFiles = self.fileCount;
var bailed = false;
// receive test count from all files and then run the tests
var unreportedFiles = self.fileCount;
var bailed = false;

files.every(function (file, index) {
var tried = false;
files.every(function (file, index) {
var tried = false;

function tryRun() {
if (!tried && !bailed) {
tried = true;
unreportedFiles--;
function tryRun() {
if (!tried && !bailed) {
tried = true;
unreportedFiles--;

if (unreportedFiles === 0) {
run();
if (unreportedFiles === 0) {
run();
}
}
}
}
}

try {
var test = tests[index] = self._runFile(file, runStatus);
try {
var test = tests[index] = self._runFile(file, runStatus, execArgvList[index]);

test.on('stats', tryRun);
test.catch(tryRun);
test.on('stats', tryRun);
test.catch(tryRun);

return true;
} catch (err) {
bailed = true;
return true;
} catch (err) {
bailed = true;

runStatus.handleExceptions({
exception: err,
file: path.relative('.', file)
});
runStatus.handleExceptions({
exception: err,
file: path.relative('.', file)
});

resolve([]);
resolve([]);

return false;
}
});
}).then(function (results) {
if (results.length === 0) {
// No tests ran, make sure to tear down the child processes.
tests.forEach(function (test) {
test.send('teardown');
});
}
return false;
}
});
}).then(function (results) {
if (results.length === 0) {
// No tests ran, make sure to tear down the child processes.
tests.forEach(function (test) {
test.send('teardown');
});
}

return results;
}).then(function (results) {
// cancel debounced _onTimeout() from firing
if (self.options.timeout) {
runStatus._restartTimer.cancel();
}
return results;
}).then(function (results) {
// cancel debounced _onTimeout() from firing
if (self.options.timeout) {
runStatus._restartTimer.cancel();
}

runStatus.processResults(results);
return runStatus;
});
runStatus.processResults(results);
return runStatus;
});
});
};

function getBlankResults() {
Expand Down Expand Up @@ -258,57 +305,60 @@ Api.prototype._runLimitedPool = function (files, runStatus, concurrency) {
});
});

return Promise.map(files, function (file) {
var handleException = function (err) {
runStatus.handleExceptions({
exception: err,
file: path.relative('.', file)
});
};

try {
var test = tests[file] = self._runFile(file, runStatus);

return new Promise(function (resolve, reject) {
var runner = function () {
var options = {
// If we're looking for matches, run every single test process in exclusive-only mode
runOnlyExclusive: self.options.match.length > 0
return self.computeForkExecArgs(files)
.then(function (execArgvList) {
return Promise.map(files, function (file, index) {
var handleException = function (err) {
runStatus.handleExceptions({
exception: err,
file: path.relative('.', file)
});
};
test.run(options)
.then(resolve)
.catch(reject);
};

test.on('stats', runner);
test.on('exit', function () {
delete tests[file];
});
test.catch(runner);
}).catch(handleException);
} catch (err) {
handleException(err);
}
}, {concurrency: concurrency})
.then(function (results) {
// Filter out undefined results (usually result of caught exceptions)
results = results.filter(Boolean);

// cancel debounced _onTimeout() from firing
if (self.options.timeout) {
runStatus._restartTimer.cancel();
}

if (self.options.match.length > 0 && !runStatus.hasExclusive) {
// Ensure results are empty
results = [];
runStatus.handleExceptions({
exception: new AvaError('Couldn\'t find any matching tests'),
file: undefined
});
}

runStatus.processResults(results);
return runStatus;
});
try {
var test = tests[file] = self._runFile(file, runStatus, execArgvList[index]);

return new Promise(function (resolve, reject) {
var runner = function () {
var options = {
// If we're looking for matches, run every single test process in exclusive-only mode
runOnlyExclusive: self.options.match.length > 0
};
test.run(options)
.then(resolve)
.catch(reject);
};

test.on('stats', runner);
test.on('exit', function () {
delete tests[file];
});
test.catch(runner);
}).catch(handleException);
} catch (err) {
handleException(err);
}
}, {concurrency: concurrency})
.then(function (results) {
// Filter out undefined results (usually result of caught exceptions)
results = results.filter(Boolean);

// cancel debounced _onTimeout() from firing
if (self.options.timeout) {
runStatus._restartTimer.cancel();
}

if (self.options.match.length > 0 && !runStatus.hasExclusive) {
// Ensure results are empty
results = [];
runStatus.handleExceptions({
exception: new AvaError('Couldn\'t find any matching tests'),
file: undefined
});
}

runStatus.processResults(results);
return runStatus;
});
});
};
5 changes: 3 additions & 2 deletions lib/fork.js
Expand Up @@ -30,7 +30,7 @@ if (env.NODE_PATH) {
.join(path.delimiter);
}

module.exports = function (file, opts) {
module.exports = function (file, opts, execArgv) {
opts = objectAssign({
file: file,
baseDir: process.cwd(),
Expand All @@ -43,7 +43,8 @@ module.exports = function (file, opts) {
var ps = childProcess.fork(path.join(__dirname, 'test-worker.js'), [JSON.stringify(opts)], {
cwd: path.dirname(file),
silent: true,
env: env
env: env,
execArgv: execArgv || process.execArgv
});

var relFile = path.relative('.', file);
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -119,6 +119,7 @@
"figures": "^1.4.0",
"find-cache-dir": "^0.1.1",
"fn-name": "^2.0.0",
"get-port": "^2.1.0",
"has-flag": "^2.0.0",
"ignore-by-default": "^1.0.0",
"is-ci": "^1.0.7",
Expand Down

0 comments on commit c268c8d

Please sign in to comment.