diff --git a/packages/api/schema/stryker-core.json b/packages/api/schema/stryker-core.json
index dc43cc3657..6c7528a82f 100644
--- a/packages/api/schema/stryker-core.json
+++ b/packages/api/schema/stryker-core.json
@@ -237,7 +237,7 @@
"default": []
},
"concurrency": {
- "description": "Set the concurrency of workers. Stryker will always run checkers and test runners in parallel by creating worker processes (note, not `worker_threads`). This defaults to n-1 where n is the number of cpu's available on your machine. This is a sane default for most use cases.",
+ "description": "Set the concurrency of workers. Stryker will always run checkers and test runners in parallel by creating worker processes (note, not `worker_threads`). This defaults to `n-1` where `n` is the number of logical CPU cores available on your machine, unless `n <= 4`, in that case it uses `n`. This is a sane default for most use cases.",
"type": "number"
},
"maxTestRunnerReuse": {
diff --git a/packages/core/README.md b/packages/core/README.md
index 804de0addd..bbbf2a0bc8 100644
--- a/packages/core/README.md
+++ b/packages/core/README.md
@@ -103,6 +103,7 @@ You can *ignore* files by adding an exclamation mark (`!`) at the start of an ex
* [allowConsoleColors](#allowConsoleColors)
* [buildCommand](#buildCommand)
* [cleanTempDir](#cleanTempDir)
+* [concurrency](#concurrency)
* [commandRunner](#commandRunner)
* [coverageAnalysis](#coverageAnalysis)
* [dashboard.*](#dashboard)
@@ -152,6 +153,15 @@ Config file: `cleanTempDir: false`
Choose whether or not to clean the temp dir (which is ".stryker-tmp" inside the current working directory by default) after a successful run.
The temp dir will never be removed when the run failed for some reason (for debugging purposes).
+
+### `concurrency` [`number`]
+
+Default: `cpuCoreCount <= 4? cpuCoreCount : cpuCoreCount - 1`
+Command line: `--concurrency 4`
+Config file: `concurrency: 4`
+
+Set the concurrency of workers. Stryker will always run checkers and test runners in parallel by creating worker processes (note, not `worker_threads`). This defaults to `n-1` where `n` is the number of logical CPU cores available on your machine, unless `n <= 4`, in that case it uses `n`. This is a sane default for most use cases.
+
### `commandRunner` [`object`]
@@ -240,21 +250,13 @@ Config file: `logLevel: 'info'`
Thus, to see logging output from the test runner set the `logLevel` to `all` or `trace`.
-### `maxConcurrentTestRunners` [`number`]
+### DEPRECATED `maxConcurrentTestRunners` [`number`]
-Default: `(number of CPU Cores)`
+Default: (see [concurrency](#concurrency))
Command line: `--maxConcurrentTestRunners 3`
Config file: `maxConcurrentTestRunners: 3`
-Specifies the maximum number of concurrent test runners to spawn.
-Mutation testing is time consuming. By default, Stryker tries to make the most of your CPU, by spawning as many test runners as you have CPU cores.
-This setting allows you to override this default behavior.
-
-Reasons you might want to lower this setting:
-
-* Your test runner starts a browser (another CPU-intensive process)
-* You're running on a shared server and/or
-* Your hard disk cannot handle the I/O of all test runners
+DEPRECATED. Please use [concurrency](#concurrency) instead.
### `mutate` [`string[]`]
diff --git a/packages/core/src/concurrent/concurrency-token-provider.ts b/packages/core/src/concurrent/concurrency-token-provider.ts
index c7f239ee37..6e054452ae 100644
--- a/packages/core/src/concurrent/concurrency-token-provider.ts
+++ b/packages/core/src/concurrent/concurrency-token-provider.ts
@@ -18,7 +18,8 @@ export class ConcurrencyTokenProvider implements Disposable {
public static readonly inject = tokens(commonTokens.options, commonTokens.logger);
constructor(options: Pick, private readonly log: Logger) {
- const concurrency = options.concurrency ?? os.cpus().length - 1;
+ const cpuCount = os.cpus().length;
+ const concurrency = options.concurrency ?? (cpuCount > 4 ? cpuCount - 1 : cpuCount);
if (options.checkers.length > 0) {
this.concurrencyCheckers = Math.max(Math.ceil(concurrency / 2), 1);
this.checkerToken$ = range(this.concurrencyCheckers);
diff --git a/packages/core/test/unit/concurrent/concurrency-token-provider.spec.ts b/packages/core/test/unit/concurrent/concurrency-token-provider.spec.ts
index 419eba4d00..f1b96a5fb4 100644
--- a/packages/core/test/unit/concurrent/concurrency-token-provider.spec.ts
+++ b/packages/core/test/unit/concurrent/concurrency-token-provider.spec.ts
@@ -28,11 +28,18 @@ describe(ConcurrencyTokenProvider.name, () => {
});
describe('testRunnerToken$', () => {
- it('should use cpuCount - 1 if concurrency is not set', async () => {
- sinon.stub(os, 'cpus').returns([0, 1, 2]);
+ it('should use cpuCount if concurrency is not set and CPU count <= 4', async () => {
+ sinon.stub(os, 'cpus').returns([0, 1, 2, 3]);
const sut = createSut();
const actualTokens = await actAllTestRunnerTokens(sut);
- expect(actualTokens).deep.eq([0, 1]);
+ expect(actualTokens).deep.eq([0, 1, 2, 3]);
+ });
+
+ it('should use cpuCount - 1 if concurrency is not set and CPU count > 4', async () => {
+ sinon.stub(os, 'cpus').returns([0, 1, 2, 3, 4]);
+ const sut = createSut();
+ const actualTokens = await actAllTestRunnerTokens(sut);
+ expect(actualTokens).deep.eq([0, 1, 2, 3]);
});
it('should allow half of the concurrency when there are checkers configured', async () => {