Skip to content

Commit 1c3c86c

Browse files
dcousineaunovemberborn
authored andcommitted
Limited Process Pools (#791)
New experimental `--concurrency` option. Note that `.only` behavior won't work reliably across test files when concurrency is limited.
1 parent 8ba3811 commit 1c3c86c

File tree

8 files changed

+898
-710
lines changed

8 files changed

+898
-710
lines changed

api.js

Lines changed: 94 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,19 @@ Api.prototype._run = function (files, _options) {
116116
self.precompiler = new CachingPrecompiler(cacheDir, self.options.babelConfig);
117117
self.fileCount = files.length;
118118

119+
var overwatch;
120+
if (this.options.concurrency > 0) {
121+
overwatch = this._runLimitedPool(files, runStatus, self.options.serial ? 1 : this.options.concurrency);
122+
} else {
123+
// _runNoPool exists to preserve legacy behavior, specifically around `.only`
124+
overwatch = this._runNoPool(files, runStatus);
125+
}
126+
127+
return overwatch;
128+
};
129+
130+
Api.prototype._runNoPool = function (files, runStatus) {
131+
var self = this;
119132
var tests = new Array(self.fileCount);
120133

121134
// TODO: thid should be cleared at the end of the run
@@ -154,15 +167,7 @@ Api.prototype._run = function (files, _options) {
154167
file: path.relative('.', file)
155168
});
156169

157-
return {
158-
stats: {
159-
passCount: 0,
160-
skipCount: 0,
161-
todoCount: 0,
162-
failCount: 0
163-
},
164-
tests: []
165-
};
170+
return getBlankResults();
166171
});
167172
}));
168173
}
@@ -224,3 +229,83 @@ Api.prototype._run = function (files, _options) {
224229
return runStatus;
225230
});
226231
};
232+
233+
function getBlankResults() {
234+
return {
235+
stats: {
236+
testCount: 0,
237+
passCount: 0,
238+
skipCount: 0,
239+
todoCount: 0,
240+
failCount: 0
241+
},
242+
tests: []
243+
};
244+
}
245+
246+
Api.prototype._runLimitedPool = function (files, runStatus, concurrency) {
247+
var self = this;
248+
var tests = {};
249+
250+
runStatus.on('timeout', function () {
251+
Object.keys(tests).forEach(function (file) {
252+
var fork = tests[file];
253+
fork.exit();
254+
});
255+
});
256+
257+
return Promise.map(files, function (file) {
258+
var handleException = function (err) {
259+
runStatus.handleExceptions({
260+
exception: err,
261+
file: path.relative('.', file)
262+
});
263+
};
264+
265+
try {
266+
var test = tests[file] = self._runFile(file, runStatus);
267+
268+
return new Promise(function (resolve, reject) {
269+
var runner = function () {
270+
self.emit('ready');
271+
var options = {
272+
// If we're looking for matches, run every single test process in exclusive-only mode
273+
runOnlyExclusive: self.options.match.length > 0
274+
};
275+
test.run(options)
276+
.then(resolve)
277+
.catch(reject);
278+
};
279+
280+
test.on('stats', runner);
281+
test.on('exit', function () {
282+
delete tests[file];
283+
});
284+
test.catch(runner);
285+
}).catch(handleException);
286+
} catch (err) {
287+
handleException(err);
288+
}
289+
}, {concurrency: concurrency})
290+
.then(function (results) {
291+
// Filter out undefined results (usually result of caught exceptions)
292+
results = results.filter(Boolean);
293+
294+
// cancel debounced _onTimeout() from firing
295+
if (self.options.timeout) {
296+
runStatus._restartTimer.cancel();
297+
}
298+
299+
if (self.options.match.length > 0 && !runStatus.hasExclusive) {
300+
// Ensure results are empty
301+
results = [];
302+
runStatus.handleExceptions({
303+
exception: new AvaError('Couldn\'t find any matching tests'),
304+
file: undefined
305+
});
306+
}
307+
308+
runStatus.processResults(results);
309+
return runStatus;
310+
});
311+
};

cli.js

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -60,17 +60,18 @@ var cli = meow([
6060
' ava [<file|directory|glob> ...]',
6161
'',
6262
'Options',
63-
' --init Add AVA to your project',
64-
' --fail-fast Stop after first test failure',
65-
' --serial, -s Run tests serially',
66-
' --require, -r Module to preload (Can be repeated)',
67-
' --tap, -t Generate TAP output',
68-
' --verbose, -v Enable verbose output',
69-
' --no-cache Disable the transpiler cache',
70-
' --match, -m Only run tests with matching title (Can be repeated)',
71-
' --watch, -w Re-run tests when tests and source files change',
72-
' --source, -S Pattern to match source files so tests can be re-run (Can be repeated)',
73-
' --timeout, -T Set global timeout',
63+
' --init Add AVA to your project',
64+
' --fail-fast Stop after first test failure',
65+
' --serial, -s Run tests serially',
66+
' --require, -r Module to preload (Can be repeated)',
67+
' --tap, -t Generate TAP output',
68+
' --verbose, -v Enable verbose output',
69+
' --no-cache Disable the transpiler cache',
70+
' --match, -m Only run tests with matching title (Can be repeated)',
71+
' --watch, -w Re-run tests when tests and source files change',
72+
' --source, -S Pattern to match source files so tests can be re-run (Can be repeated)',
73+
' --timeout, -T Set global timeout',
74+
' --concurrency, -c Maximum number of test files running at the same time (EXPERIMENTAL)',
7475
'',
7576
'Examples',
7677
' ava',
@@ -88,7 +89,8 @@ var cli = meow([
8889
'require',
8990
'timeout',
9091
'source',
91-
'match'
92+
'match',
93+
'concurrency'
9294
],
9395
boolean: [
9496
'fail-fast',
@@ -106,7 +108,8 @@ var cli = meow([
106108
m: 'match',
107109
w: 'watch',
108110
S: 'source',
109-
T: 'timeout'
111+
T: 'timeout',
112+
c: 'concurrency'
110113
}
111114
});
112115

@@ -133,7 +136,8 @@ var api = new Api({
133136
explicitTitles: cli.flags.watch,
134137
match: arrify(cli.flags.match),
135138
babelConfig: conf.babel,
136-
timeout: cli.flags.timeout
139+
timeout: cli.flags.timeout,
140+
concurrency: cli.flags.concurrency ? parseInt(cli.flags.concurrency, 10) : 0
137141
});
138142

139143
var reporter;

readme.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -136,17 +136,18 @@ $ ava --help
136136
ava [<file|directory|glob> ...]
137137

138138
Options
139-
--init Add AVA to your project
140-
--fail-fast Stop after first test failure
141-
--serial, -s Run tests serially
142-
--require, -r Module to preload (Can be repeated)
143-
--tap, -t Generate TAP output
144-
--verbose, -v Enable verbose output
145-
--no-cache Disable the transpiler cache
146-
--match, -m Only run tests with matching title (Can be repeated)
147-
--watch, -w Re-run tests when tests and source files change
148-
--source, -S Pattern to match source files so tests can be re-run (Can be repeated)
149-
--timeout, -T Set global timeout
139+
--init Add AVA to your project
140+
--fail-fast Stop after first test failure
141+
--serial, -s Run tests serially
142+
--require, -r Module to preload (Can be repeated)
143+
--tap, -t Generate TAP output
144+
--verbose, -v Enable verbose output
145+
--no-cache Disable the transpiler cache
146+
--match, -m Only run tests with matching title (Can be repeated)
147+
--watch, -w Re-run tests when tests and source files change
148+
--source, -S Pattern to match source files so tests can be re-run (Can be repeated)
149+
--timeout, -T Set global timeout
150+
--concurrency, -c Maximum number of test files running at the same time (EXPERIMENTAL)
150151

151152
Examples
152153
ava

0 commit comments

Comments
 (0)