Permalink
Browse files

Limit concurrency to the number of CPU cores (#1467)

Fixes #966
  • Loading branch information...
sindresorhus committed Jul 30, 2017
1 parent 4eea226 commit 465fcecc9ae0d3274d4d41d3baaca241d6a40130
Showing with 15 additions and 118 deletions.
  1. +9 βˆ’80 api.js
  2. +2 βˆ’0 docs/common-pitfalls.md
  3. +1 βˆ’1 docs/recipes/precompiling-with-webpack.md
  4. +1 βˆ’1 lib/cli.js
  5. +2 βˆ’2 readme.md
  6. +0 βˆ’34 test/api.js
View
89 api.js
@@ -2,6 +2,7 @@
const EventEmitter = require('events');
const path = require('path');
const fs = require('fs');
const os = require('os');
const commonPathPrefix = require('common-path-prefix');
const uniqueTempDir = require('unique-temp-dir');
const findCacheDir = require('find-cache-dir');
@@ -160,16 +161,17 @@ class Api extends EventEmitter {
this._setupTimeout(runStatus);
}
let overwatch;
let concurrency = os.cpus().length;
if (this.options.concurrency > 0) {
const concurrency = this.options.serial ? 1 : this.options.concurrency;
overwatch = this._runWithPool(files, runStatus, concurrency);
} else {
// _runWithoutPool exists to preserve legacy behavior, specifically around `.only`
overwatch = this._runWithoutPool(files, runStatus);
concurrency = this.options.concurrency;
}
if (this.options.serial) {
concurrency = 1;
}
return overwatch;
return this._runWithPool(files, runStatus, concurrency);
});
}
_computeForkExecArgs(files) {
@@ -223,79 +225,6 @@ class Api extends EventEmitter {
file: err.file ? path.relative(process.cwd(), err.file) : undefined
});
}
_runWithoutPool(files, runStatus) {
const tests = [];
let execArgvList;
// TODO: This should be cleared at the end of the run
runStatus.on('timeout', () => {
tests.forEach(fork => {
fork.exit();
});
});
return this._computeForkExecArgs(files)
.then(argvList => {
execArgvList = argvList;
})
.return(files)
.each((file, index) => {
return new Promise(resolve => {
const forkArgs = execArgvList[index];
const test = this._runFile(file, runStatus, forkArgs);
tests.push(test);
test.on('stats', resolve);
test.catch(resolve);
}).catch(err => {
err.results = [];
err.file = file;
return Promise.reject(err);
});
})
.then(() => {
if (this.options.match.length > 0 && !runStatus.hasExclusive) {
const err = new AvaError('Couldn\'t find any matching tests');
err.file = undefined;
err.results = [];
return Promise.reject(err);
}
const method = this.options.serial ? 'mapSeries' : 'map';
const options = {
runOnlyExclusive: runStatus.hasExclusive
};
return Promise[method](files, (file, index) => {
return tests[index].run(options).catch(err => {
err.file = file;
this._handleError(runStatus, err);
return getBlankResults();
});
});
})
.catch(err => {
this._handleError(runStatus, err);
return err.results;
})
.tap(results => {
// If no tests ran, make sure to tear down the child processes
if (results.length === 0) {
tests.forEach(test => {
test.send('teardown');
});
}
})
.then(results => {
// Cancel debounced _onTimeout() from firing
if (this.options.timeout) {
this._cancelTimeout(runStatus);
}
runStatus.processResults(results);
return runStatus;
});
}
_runWithPool(files, runStatus, concurrency) {
const tests = [];
let execArgvList;
View
@@ -16,6 +16,8 @@ AVA uses [is-ci](https://github.com/watson/is-ci) to decide if it's in a CI envi
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.
By default, AVA will use as many processes as there are CPU cores in your machine.
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.
## Asynchronous operations
@@ -224,7 +224,7 @@ npm scripts:
"precompile-src": "cross-env NODE_ENV=test babel src --out-dir _src",
"precompile-tests": "cross-env NODE_ENV=test webpack --config webpack.config.test.js",
"pretest": "npm run precompile-src && npm run precompile-tests",
"test": "cross-env NODE_ENV=test nyc --cache ava _build --concurrency 3"
"test": "cross-env NODE_ENV=test nyc --cache ava _build"
}
}
```
View
@@ -43,7 +43,7 @@ exports.run = () => {
--match, -m Only run tests with matching title (Can be repeated)
--watch, -w Re-run tests when tests and source files change
--timeout, -T Set global timeout
--concurrency, -c Maximum number of test files running at the same time (EXPERIMENTAL)
--concurrency, -c Max number of test files running at the same time (Default: CPU cores)
--update-snapshots, -u Update snapshots
Examples
View
@@ -169,7 +169,7 @@ $ ava --help
--match, -m Only run tests with matching title (Can be repeated)
--watch, -w Re-run tests when tests and source files change
--timeout, -T Set global timeout
--concurrency, -c Maximum number of test files running at the same time (EXPERIMENTAL)
--concurrency, -c Max number of test files running at the same time (Default: CPU cores)
--update-snapshots, -u Update snapshots
Examples
@@ -400,7 +400,7 @@ test.only('will be run', t => {
});
```
`.only` applies across all test files, so if you use it in one file, no tests from the other file will run.
*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.
### Running tests with matching titles
View
@@ -23,40 +23,6 @@ function apiCreator(options) {
return instance;
}
generateTests('Without Pool:', options => apiCreator(options || {}));
// 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
test('Without Pool: test file with exclusive tests causes non-exclusive tests in other files to be ignored', t => {
const files = [
path.join(__dirname, 'fixture/exclusive.js'),
path.join(__dirname, 'fixture/exclusive-nonexclusive.js'),
path.join(__dirname, 'fixture/one-pass-one-fail.js')
];
const api = apiCreator({});
return api.run(files)
.then(result => {
t.ok(result.hasExclusive);
t.is(result.testCount, 5);
t.is(result.passCount, 2);
t.is(result.failCount, 0);
});
});
test('Without Pool: test files can be forced to run in exclusive mode', t => {
const api = apiCreator();
return api.run(
[path.join(__dirname, 'fixture/es2015.js')],
{runOnlyExclusive: true}
).then(result => {
t.ok(result.hasExclusive);
t.is(result.testCount, 1);
t.is(result.passCount, 0);
t.is(result.failCount, 0);
});
});
generateTests('With Pool:', options => {
options = options || {};
options.concurrency = 2;

0 comments on commit 465fcec

Please sign in to comment.