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 () => {