Skip to content

Commit

Permalink
fix avajs#342 set child process debug port to available
Browse files Browse the repository at this point in the history
  • Loading branch information
develar committed Jun 20, 2016
1 parent ca800eb commit 3d3520c
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 128 deletions.
289 changes: 163 additions & 126 deletions api.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var uniqueTempDir = require('unique-temp-dir');
var findCacheDir = require('find-cache-dir');
var debounce = require('lodash.debounce');
var ms = require('ms');
var getPort = require('get-port');
var AvaFiles = require('ava-files');
var AvaError = require('./lib/ava-error');
var fork = require('./lib/fork');
Expand Down Expand Up @@ -46,7 +47,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 @@ -55,7 +56,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 @@ -133,6 +134,36 @@ Api.prototype._run = function (files, _options) {
return overwatch;
};

Api.prototype.computeForkExecArgs = function (files) {
var execArgv = this.options.testOnlyExecArgv || process.execArgv;
var debugArgIndex = -1;
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 = '--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 @@ -144,94 +175,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
});
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;
}

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 @@ -259,57 +293,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
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,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 @@ -44,7 +44,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
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"figures": "^1.4.0",
"find-cache-dir": "^0.1.1",
"fn-name": "^2.0.0",
"get-port": "^2.1.0",
"globby": "^5.0.0",
"has-flag": "^2.0.0",
"ignore-by-default": "^1.0.0",
Expand Down
17 changes: 17 additions & 0 deletions test/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1022,3 +1022,20 @@ function generateTests(prefix, apiCreator) {
]);
});
}

function generatePassDebugTests(execArgv) {
test('pass ' + execArgv.join(' ') + ' to fork', function (t) {
t.plan(3);

var api = new Api({testOnlyExecArgv: execArgv});
return api.computeForkExecArgs(['foo.js'])
.then(function (result) {
t.true(result.length === 1);
t.true(result[0].length === 1);
t.true(/--debug=\d+/.test(result[0][0]));
});
});
}

generatePassDebugTests(['--debug=0']);
generatePassDebugTests(['--debug']);

0 comments on commit 3d3520c

Please sign in to comment.