Skip to content

Commit 459409f

Browse files
sindresorhuskevva
authored andcommitted
Limit concurrency to the number of CPU cores (#1467)
Fixes #966
1 parent b81edfb commit 459409f

File tree

6 files changed

+15
-118
lines changed

6 files changed

+15
-118
lines changed

api.js

Lines changed: 9 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
const EventEmitter = require('events');
33
const path = require('path');
44
const fs = require('fs');
5+
const os = require('os');
56
const commonPathPrefix = require('common-path-prefix');
67
const uniqueTempDir = require('unique-temp-dir');
78
const findCacheDir = require('find-cache-dir');
@@ -160,16 +161,17 @@ class Api extends EventEmitter {
160161
this._setupTimeout(runStatus);
161162
}
162163

163-
let overwatch;
164+
let concurrency = os.cpus().length;
165+
164166
if (this.options.concurrency > 0) {
165-
const concurrency = this.options.serial ? 1 : this.options.concurrency;
166-
overwatch = this._runWithPool(files, runStatus, concurrency);
167-
} else {
168-
// _runWithoutPool exists to preserve legacy behavior, specifically around `.only`
169-
overwatch = this._runWithoutPool(files, runStatus);
167+
concurrency = this.options.concurrency;
168+
}
169+
170+
if (this.options.serial) {
171+
concurrency = 1;
170172
}
171173

172-
return overwatch;
174+
return this._runWithPool(files, runStatus, concurrency);
173175
});
174176
}
175177
_computeForkExecArgs(files) {
@@ -223,79 +225,6 @@ class Api extends EventEmitter {
223225
file: err.file ? path.relative(process.cwd(), err.file) : undefined
224226
});
225227
}
226-
_runWithoutPool(files, runStatus) {
227-
const tests = [];
228-
let execArgvList;
229-
230-
// TODO: This should be cleared at the end of the run
231-
runStatus.on('timeout', () => {
232-
tests.forEach(fork => {
233-
fork.exit();
234-
});
235-
});
236-
237-
return this._computeForkExecArgs(files)
238-
.then(argvList => {
239-
execArgvList = argvList;
240-
})
241-
.return(files)
242-
.each((file, index) => {
243-
return new Promise(resolve => {
244-
const forkArgs = execArgvList[index];
245-
const test = this._runFile(file, runStatus, forkArgs);
246-
tests.push(test);
247-
test.on('stats', resolve);
248-
test.catch(resolve);
249-
}).catch(err => {
250-
err.results = [];
251-
err.file = file;
252-
return Promise.reject(err);
253-
});
254-
})
255-
.then(() => {
256-
if (this.options.match.length > 0 && !runStatus.hasExclusive) {
257-
const err = new AvaError('Couldn\'t find any matching tests');
258-
err.file = undefined;
259-
err.results = [];
260-
return Promise.reject(err);
261-
}
262-
263-
const method = this.options.serial ? 'mapSeries' : 'map';
264-
const options = {
265-
runOnlyExclusive: runStatus.hasExclusive
266-
};
267-
268-
return Promise[method](files, (file, index) => {
269-
return tests[index].run(options).catch(err => {
270-
err.file = file;
271-
this._handleError(runStatus, err);
272-
return getBlankResults();
273-
});
274-
});
275-
})
276-
.catch(err => {
277-
this._handleError(runStatus, err);
278-
return err.results;
279-
})
280-
.tap(results => {
281-
// If no tests ran, make sure to tear down the child processes
282-
if (results.length === 0) {
283-
tests.forEach(test => {
284-
test.send('teardown');
285-
});
286-
}
287-
})
288-
.then(results => {
289-
// Cancel debounced _onTimeout() from firing
290-
if (this.options.timeout) {
291-
this._cancelTimeout(runStatus);
292-
}
293-
294-
runStatus.processResults(results);
295-
296-
return runStatus;
297-
});
298-
}
299228
_runWithPool(files, runStatus, concurrency) {
300229
const tests = [];
301230
let execArgvList;

docs/common-pitfalls.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ AVA uses [is-ci](https://github.com/watson/is-ci) to decide if it's in a CI envi
1616

1717
You may be using a service that only allows a limited number of concurrent connections. For example, many database-as-a-service businesses offer a free plan with a limit on how many clients can be using it at the same time. AVA can hit those limits as it runs multiple processes, but well-written services should emit an error or throttle in those cases. If the one you're using doesn't, the tests will hang.
1818

19+
By default, AVA will use as many processes as there are CPU cores in your machine.
20+
1921
Use the `concurrency` flag to limit the number of processes ran. For example, if your service plan allows 5 clients, you should run AVA with `concurrency=5` or less.
2022

2123
## Asynchronous operations

docs/recipes/precompiling-with-webpack.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ npm scripts:
224224
"precompile-src": "cross-env NODE_ENV=test babel src --out-dir _src",
225225
"precompile-tests": "cross-env NODE_ENV=test webpack --config webpack.config.test.js",
226226
"pretest": "npm run precompile-src && npm run precompile-tests",
227-
"test": "cross-env NODE_ENV=test nyc --cache ava _build --concurrency 3"
227+
"test": "cross-env NODE_ENV=test nyc --cache ava _build"
228228
}
229229
}
230230
```

lib/cli.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ exports.run = () => {
4343
--match, -m Only run tests with matching title (Can be repeated)
4444
--watch, -w Re-run tests when tests and source files change
4545
--timeout, -T Set global timeout
46-
--concurrency, -c Maximum number of test files running at the same time (EXPERIMENTAL)
46+
--concurrency, -c Max number of test files running at the same time (Default: CPU cores)
4747
--update-snapshots, -u Update snapshots
4848
4949
Examples

readme.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ $ ava --help
169169
--match, -m Only run tests with matching title (Can be repeated)
170170
--watch, -w Re-run tests when tests and source files change
171171
--timeout, -T Set global timeout
172-
--concurrency, -c Maximum number of test files running at the same time (EXPERIMENTAL)
172+
--concurrency, -c Max number of test files running at the same time (Default: CPU cores)
173173
--update-snapshots, -u Update snapshots
174174

175175
Examples
@@ -400,7 +400,7 @@ test.only('will be run', t => {
400400
});
401401
```
402402

403-
`.only` applies across all test files, so if you use it in one file, no tests from the other file will run.
403+
*Note:* The `.only` modifier applies to the test file it's defined in, so if you run multiple test files, tests in other files will still run. If you want to only run the `test.only` test, provide just that test file to AVA.
404404

405405
### Running tests with matching titles
406406

test/api.js

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -23,40 +23,6 @@ function apiCreator(options) {
2323
return instance;
2424
}
2525

26-
generateTests('Without Pool:', options => apiCreator(options || {}));
27-
28-
// The following two tests are only run against "Without Pool" behavior as they test the exclusive test features. These features are currently not expected to work correctly in the limited process pool. When the limited process pool behavior is finalized this test file will be updated. See: https://github.com/avajs/ava/pull/791#issuecomment-216293302
29-
test('Without Pool: test file with exclusive tests causes non-exclusive tests in other files to be ignored', t => {
30-
const files = [
31-
path.join(__dirname, 'fixture/exclusive.js'),
32-
path.join(__dirname, 'fixture/exclusive-nonexclusive.js'),
33-
path.join(__dirname, 'fixture/one-pass-one-fail.js')
34-
];
35-
36-
const api = apiCreator({});
37-
38-
return api.run(files)
39-
.then(result => {
40-
t.ok(result.hasExclusive);
41-
t.is(result.testCount, 5);
42-
t.is(result.passCount, 2);
43-
t.is(result.failCount, 0);
44-
});
45-
});
46-
47-
test('Without Pool: test files can be forced to run in exclusive mode', t => {
48-
const api = apiCreator();
49-
return api.run(
50-
[path.join(__dirname, 'fixture/es2015.js')],
51-
{runOnlyExclusive: true}
52-
).then(result => {
53-
t.ok(result.hasExclusive);
54-
t.is(result.testCount, 1);
55-
t.is(result.passCount, 0);
56-
t.is(result.failCount, 0);
57-
});
58-
});
59-
6026
generateTests('With Pool:', options => {
6127
options = options || {};
6228
options.concurrency = 2;

0 commit comments

Comments
 (0)