From b1a27d40493301f7b135b6ba99d52c9edc28fffb Mon Sep 17 00:00:00 2001
From: Sung Ye In <66503450+syi0808@users.noreply.github.com>
Date: Mon, 1 Jul 2024 20:52:45 +0900
Subject: [PATCH] feat(config): allow percentage value for workers option
(#5982)
---
docs/config/index.md | 40 ++++++-------
docs/guide/cli-table.md | 20 +++----
packages/vitest/rollup.config.js | 1 +
packages/vitest/src/node/cli/cli-config.ts | 8 +--
packages/vitest/src/node/config.ts | 42 +++++++++++++-
packages/vitest/src/types/config.ts | 15 +++--
packages/vitest/src/types/pool-options.ts | 25 +++++++--
packages/vitest/src/utils/workers.ts | 8 +++
.../fixtures/workers-option/example.test.ts | 5 ++
.../fixtures/workers-option/vitest.config.js | 1 +
test/config/test/workers-option.test.ts | 56 +++++++++++++++++++
11 files changed, 174 insertions(+), 47 deletions(-)
create mode 100644 packages/vitest/src/utils/workers.ts
create mode 100644 test/config/fixtures/workers-option/example.test.ts
create mode 100644 test/config/fixtures/workers-option/vitest.config.js
create mode 100644 test/config/test/workers-option.test.ts
diff --git a/docs/config/index.md b/docs/config/index.md
index fb5e08a45fdb..7015323dcf7f 100644
--- a/docs/config/index.md
+++ b/docs/config/index.md
@@ -658,17 +658,17 @@ export default defineConfig({
##### poolOptions.threads.maxThreads
-- **Type:** `number`
+- **Type:** `number | string`
- **Default:** _available CPUs_
-Maximum number of threads. You can also use `VITEST_MAX_THREADS` environment variable.
+Maximum number or percentage of threads. You can also use `VITEST_MAX_THREADS` environment variable.
##### poolOptions.threads.minThreads
-- **Type:** `number`
+- **Type:** `number | string`
- **Default:** _available CPUs_
-Minimum number of threads. You can also use `VITEST_MIN_THREADS` environment variable.
+Minimum number or percentage of threads. You can also use `VITEST_MIN_THREADS` environment variable.
##### poolOptions.threads.singleThread
@@ -730,17 +730,17 @@ export default defineConfig({
##### poolOptions.forks.maxForks
-- **Type:** `number`
+- **Type:** `number | string`
- **Default:** _available CPUs_
-Maximum number of forks.
+Maximum number or percentage of forks.
##### poolOptions.forks.minForks
-- **Type:** `number`
+- **Type:** `number | string`
- **Default:** _available CPUs_
-Minimum number of forks.
+Minimum number or percentage of forks.
##### poolOptions.forks.isolate
@@ -793,17 +793,17 @@ export default defineConfig({
##### poolOptions.vmThreads.maxThreads
-- **Type:** `number`
+- **Type:** `number | string`
- **Default:** _available CPUs_
-Maximum number of threads. You can also use `VITEST_MAX_THREADS` environment variable.
+Maximum number or percentage of threads. You can also use `VITEST_MAX_THREADS` environment variable.
##### poolOptions.vmThreads.minThreads
-- **Type:** `number`
+- **Type:** `number | string`
- **Default:** _available CPUs_
-Minimum number of threads. You can also use `VITEST_MIN_THREADS` environment variable.
+Minimum number or percentage of threads. You can also use `VITEST_MIN_THREADS` environment variable.
##### poolOptions.vmThreads.memoryLimit
@@ -874,17 +874,17 @@ export default defineConfig({
##### poolOptions.vmForks.maxForks
-- **Type:** `number`
+- **Type:** `number | string`
- **Default:** _available CPUs_
-Maximum number of threads. You can also use `VITEST_MAX_FORKS` environment variable.
+Maximum number or percentage of threads. You can also use `VITEST_MAX_FORKS` environment variable.
##### poolOptions.vmForks.minForks
-- **Type:** `number`
+- **Type:** `number | string`
- **Default:** _available CPUs_
-Minimum number of threads. You can also use `VITEST_MIN_FORKS` environment variable.
+Minimum number or percentage of threads. You can also use `VITEST_MIN_FORKS` environment variable.
##### poolOptions.vmForks.memoryLimit
@@ -918,15 +918,15 @@ This option doesn't affect tests running in the same file. If you want to run th
### maxWorkers {#maxworkers}
-- **Type:** `number`
+- **Type:** `number | string`
-Maximum number of workers to run tests in. `poolOptions.{threads,vmThreads}.maxThreads`/`poolOptions.forks.maxForks` has higher priority.
+Maximum number or percentage of workers to run tests in. `poolOptions.{threads,vmThreads}.maxThreads`/`poolOptions.forks.maxForks` has higher priority.
### minWorkers {#minworkers}
-- **Type:** `number`
+- **Type:** `number | string`
-Minimum number of workers to run tests in. `poolOptions.{threads,vmThreads}.minThreads`/`poolOptions.forks.minForks` has higher priority.
+Minimum number or percentage of workers to run tests in. `poolOptions.{threads,vmThreads}.minThreads`/`poolOptions.forks.minForks` has higher priority.
### testTimeout
diff --git a/docs/guide/cli-table.md b/docs/guide/cli-table.md
index e36b70452893..c343c3bb6250 100644
--- a/docs/guide/cli-table.md
+++ b/docs/guide/cli-table.md
@@ -61,27 +61,27 @@
| `--pool ` | Specify pool, if not running in the browser (default: `threads`) |
| `--poolOptions.threads.isolate` | Isolate tests in threads pool (default: `true`) |
| `--poolOptions.threads.singleThread` | Run tests inside a single thread (default: `false`) |
-| `--poolOptions.threads.maxThreads ` | Maximum number of threads to run tests in |
-| `--poolOptions.threads.minThreads ` | Minimum number of threads to run tests in |
+| `--poolOptions.threads.maxThreads ` | Maximum number or percentage of threads to run tests in |
+| `--poolOptions.threads.minThreads ` | Minimum number or percentage of threads to run tests in |
| `--poolOptions.threads.useAtomics` | Use Atomics to synchronize threads. This can improve performance in some cases, but might cause segfault in older Node versions (default: `false`) |
| `--poolOptions.vmThreads.isolate` | Isolate tests in threads pool (default: `true`) |
| `--poolOptions.vmThreads.singleThread` | Run tests inside a single thread (default: `false`) |
-| `--poolOptions.vmThreads.maxThreads ` | Maximum number of threads to run tests in |
-| `--poolOptions.vmThreads.minThreads ` | Minimum number of threads to run tests in |
+| `--poolOptions.vmThreads.maxThreads ` | Maximum number or percentage of threads to run tests in |
+| `--poolOptions.vmThreads.minThreads ` | Minimum number or percentage of threads to run tests in |
| `--poolOptions.vmThreads.useAtomics` | Use Atomics to synchronize threads. This can improve performance in some cases, but might cause segfault in older Node versions (default: `false`) |
| `--poolOptions.vmThreads.memoryLimit ` | Memory limit for VM threads pool. If you see memory leaks, try to tinker this value. |
| `--poolOptions.forks.isolate` | Isolate tests in forks pool (default: `true`) |
| `--poolOptions.forks.singleFork` | Run tests inside a single child_process (default: `false`) |
-| `--poolOptions.forks.maxForks ` | Maximum number of processes to run tests in |
-| `--poolOptions.forks.minForks ` | Minimum number of processes to run tests in |
+| `--poolOptions.forks.maxForks ` | Maximum number or percentage of processes to run tests in |
+| `--poolOptions.forks.minForks ` | Minimum number or percentage of processes to run tests in |
| `--poolOptions.vmForks.isolate` | Isolate tests in forks pool (default: `true`) |
| `--poolOptions.vmForks.singleFork` | Run tests inside a single child_process (default: `false`) |
-| `--poolOptions.vmForks.maxForks ` | Maximum number of processes to run tests in |
-| `--poolOptions.vmForks.minForks ` | Minimum number of processes to run tests in |
+| `--poolOptions.vmForks.maxForks ` | Maximum number or percentage of processes to run tests in |
+| `--poolOptions.vmForks.minForks ` | Minimum number or percentage of processes to run tests in |
| `--poolOptions.vmForks.memoryLimit ` | Memory limit for VM forks pool. If you see memory leaks, try to tinker this value. |
| `--fileParallelism` | Should all test files run in parallel. Use `--no-file-parallelism` to disable (default: `true`) |
-| `--maxWorkers ` | Maximum number of workers to run tests in |
-| `--minWorkers ` | Minimum number of workers to run tests in |
+| `--maxWorkers ` | Maximum number or percentage of workers to run tests in |
+| `--minWorkers ` | Minimum number or percentage of workers to run tests in |
| `--environment ` | Specify runner environment, if not running in the browser (default: `node`) |
| `--passWithNoTests` | Pass when no tests are found |
| `--logHeapUsage` | Show the size of heap for each test when running in node |
diff --git a/packages/vitest/rollup.config.js b/packages/vitest/rollup.config.js
index 9b0693000521..133ec7fdab5e 100644
--- a/packages/vitest/rollup.config.js
+++ b/packages/vitest/rollup.config.js
@@ -67,6 +67,7 @@ const external = [
'worker_threads',
'node:worker_threads',
'node:fs',
+ 'node:os',
'node:stream',
'node:vm',
'inspector',
diff --git a/packages/vitest/src/node/cli/cli-config.ts b/packages/vitest/src/node/cli/cli-config.ts
index 50293f2e9277..f8fa04c5dbd9 100644
--- a/packages/vitest/src/node/cli/cli-config.ts
+++ b/packages/vitest/src/node/cli/cli-config.ts
@@ -62,11 +62,11 @@ const poolThreadsCommands: CLIOptions = {
description: 'Run tests inside a single thread (default: `false`)',
},
maxThreads: {
- description: 'Maximum number of threads to run tests in',
+ description: 'Maximum number or percentage of threads to run tests in',
argument: '',
},
minThreads: {
- description: 'Minimum number of threads to run tests in',
+ description: 'Minimum number or percentage of threads to run tests in',
argument: '',
},
useAtomics: {
@@ -84,11 +84,11 @@ const poolForksCommands: CLIOptions = {
description: 'Run tests inside a single child_process (default: `false`)',
},
maxForks: {
- description: 'Maximum number of processes to run tests in',
+ description: 'Maximum number or percentage of processes to run tests in',
argument: '',
},
minForks: {
- description: 'Minimum number of processes to run tests in',
+ description: 'Minimum number or percentage of processes to run tests in',
argument: '',
},
execArgv: null,
diff --git a/packages/vitest/src/node/config.ts b/packages/vitest/src/node/config.ts
index f07163183bb7..85359cc02fc5 100644
--- a/packages/vitest/src/node/config.ts
+++ b/packages/vitest/src/node/config.ts
@@ -16,7 +16,8 @@ import {
} from '../constants'
import { benchmarkConfigDefaults, configDefaults } from '../defaults'
import { isCI, stdProvider, toArray } from '../utils'
-import type { BuiltinPool } from '../types/pool-options'
+import type { BuiltinPool, ForksOptions, PoolOptions, ThreadsOptions } from '../types/pool-options'
+import { getWorkersCountByPercentage } from '../utils/workers'
import { VitestCache } from './cache'
import { BaseSequencer } from './sequencers/BaseSequencer'
import { RandomSequencer } from './sequencers/RandomSequencer'
@@ -97,6 +98,15 @@ export function resolveApiServerConfig(
return api
}
+function resolveInlineWorkerOption(value: string | number): number {
+ if (typeof value === 'string' && value.trim().endsWith('%')) {
+ return getWorkersCountByPercentage(value)
+ }
+ else {
+ return Number(value)
+ }
+}
+
export function resolveConfig(
mode: VitestRunMode,
options: UserConfig,
@@ -176,11 +186,11 @@ export function resolveConfig(
}
if (resolved.maxWorkers) {
- resolved.maxWorkers = Number(resolved.maxWorkers)
+ resolved.maxWorkers = resolveInlineWorkerOption(resolved.maxWorkers)
}
if (resolved.minWorkers) {
- resolved.minWorkers = Number(resolved.minWorkers)
+ resolved.minWorkers = resolveInlineWorkerOption(resolved.minWorkers)
}
resolved.browser ??= {} as any
@@ -436,6 +446,32 @@ export function resolveConfig(
}
}
+ const poolThreadsOptions = [
+ ['threads', 'minThreads'],
+ ['threads', 'maxThreads'],
+ ['vmThreads', 'minThreads'],
+ ['vmThreads', 'maxThreads'],
+ ] as const satisfies [keyof PoolOptions, keyof ThreadsOptions][]
+
+ for (const [poolOptionKey, workerOptionKey] of poolThreadsOptions) {
+ if (resolved.poolOptions?.[poolOptionKey]?.[workerOptionKey]) {
+ resolved.poolOptions[poolOptionKey]![workerOptionKey] = resolveInlineWorkerOption(resolved.poolOptions[poolOptionKey]![workerOptionKey]!)
+ }
+ }
+
+ const poolForksOptions = [
+ ['forks', 'minForks'],
+ ['forks', 'maxForks'],
+ ['vmForks', 'minForks'],
+ ['vmForks', 'maxForks'],
+ ] as const satisfies [keyof PoolOptions, keyof ForksOptions][]
+
+ for (const [poolOptionKey, workerOptionKey] of poolForksOptions) {
+ if (resolved.poolOptions?.[poolOptionKey]?.[workerOptionKey]) {
+ resolved.poolOptions[poolOptionKey]![workerOptionKey] = resolveInlineWorkerOption(resolved.poolOptions[poolOptionKey]![workerOptionKey]!)
+ }
+ }
+
if (resolved.workspace) {
// if passed down from the CLI and it's relative, resolve relative to CWD
resolved.workspace
diff --git a/packages/vitest/src/types/config.ts b/packages/vitest/src/types/config.ts
index b77748a87fb2..6b230a80c18e 100644
--- a/packages/vitest/src/types/config.ts
+++ b/packages/vitest/src/types/config.ts
@@ -17,7 +17,7 @@ import type { SnapshotStateOptions } from './snapshot'
import type { Arrayable, ParsedStack } from './general'
import type { BenchmarkUserOptions } from './benchmark'
import type { BrowserConfigOptions, ResolvedBrowserOptions } from './browser'
-import type { Pool, PoolOptions } from './pool-options'
+import type { Pool, PoolOptions, ResolvedPoolOptions } from './pool-options'
export type { BrowserScript, BrowserConfigOptions } from './browser'
export type { SequenceHooks, SequenceSetupFiles } from '@vitest/runner'
@@ -343,13 +343,13 @@ export interface InlineConfig {
poolOptions?: PoolOptions
/**
- * Maximum number of workers to run tests in. `poolOptions.{threads,vmThreads}.maxThreads`/`poolOptions.forks.maxForks` has higher priority.
+ * Maximum number or percentage of workers to run tests in. `poolOptions.{threads,vmThreads}.maxThreads`/`poolOptions.forks.maxForks` has higher priority.
*/
- maxWorkers?: number
+ maxWorkers?: number | string
/**
- * Minimum number of workers to run tests in. `poolOptions.{threads,vmThreads}.minThreads`/`poolOptions.forks.minForks` has higher priority.
+ * Minimum number or percentage of workers to run tests in. `poolOptions.{threads,vmThreads}.minThreads`/`poolOptions.forks.minForks` has higher priority.
*/
- minWorkers?: number
+ minWorkers?: number | string
/**
* Should all test files run in parallel. Doesn't affect tests running in the same file.
@@ -969,7 +969,7 @@ export interface ResolvedConfig
browser: ResolvedBrowserOptions
pool: Pool
- poolOptions?: PoolOptions
+ poolOptions?: ResolvedPoolOptions
reporters: (InlineReporter | ReporterWithOptions)[]
@@ -1009,6 +1009,9 @@ export interface ResolvedConfig
enabled: boolean
}
runner?: string
+
+ maxWorkers: number
+ minWorkers: number
}
export type ProjectConfig = Omit<
diff --git a/packages/vitest/src/types/pool-options.ts b/packages/vitest/src/types/pool-options.ts
index 25e26c1d8cea..a3fbf8a20d0a 100644
--- a/packages/vitest/src/types/pool-options.ts
+++ b/packages/vitest/src/types/pool-options.ts
@@ -42,12 +42,19 @@ export interface PoolOptions extends Record {
vmForks?: ForksOptions & VmOptions
}
+export interface ResolvedPoolOptions extends PoolOptions {
+ threads?: ResolvedThreadsOptions & WorkerContextOptions
+ forks?: ResolvedForksOptions & WorkerContextOptions
+ vmThreads?: ResolvedThreadsOptions & VmOptions
+ vmForks?: ResolvedForksOptions & VmOptions
+}
+
export interface ThreadsOptions {
/** Minimum amount of threads to use */
- minThreads?: number
+ minThreads?: number | string
/** Maximum amount of threads to use */
- maxThreads?: number
+ maxThreads?: number | string
/**
* Run tests inside a single thread.
@@ -66,12 +73,17 @@ export interface ThreadsOptions {
useAtomics?: boolean
}
+export interface ResolvedThreadsOptions extends ThreadsOptions {
+ minThreads?: number
+ maxThreads?: number
+}
+
export interface ForksOptions {
/** Minimum amount of child processes to use */
- minForks?: number
+ minForks?: number | string
/** Maximum amount of child processes to use */
- maxForks?: number
+ maxForks?: number | string
/**
* Run tests inside a single fork.
@@ -81,6 +93,11 @@ export interface ForksOptions {
singleFork?: boolean
}
+export interface ResolvedForksOptions extends ForksOptions {
+ minForks?: number
+ maxForks?: number
+}
+
export interface WorkerContextOptions {
/**
* Isolate test environment by recycling `worker_threads` or `child_process` after each test
diff --git a/packages/vitest/src/utils/workers.ts b/packages/vitest/src/utils/workers.ts
new file mode 100644
index 000000000000..f702219d7578
--- /dev/null
+++ b/packages/vitest/src/utils/workers.ts
@@ -0,0 +1,8 @@
+import os from 'node:os'
+
+export function getWorkersCountByPercentage(percent: string) {
+ const maxWorkersCount = os.availableParallelism?.() ?? os.cpus().length
+ const workersCountByPercentage = Math.round((Number.parseInt(percent) / 100) * maxWorkersCount)
+
+ return Math.max(1, Math.min(maxWorkersCount, workersCountByPercentage))
+}
diff --git a/test/config/fixtures/workers-option/example.test.ts b/test/config/fixtures/workers-option/example.test.ts
new file mode 100644
index 000000000000..5a275a3fb95f
--- /dev/null
+++ b/test/config/fixtures/workers-option/example.test.ts
@@ -0,0 +1,5 @@
+import { expect, test } from 'vitest'
+
+test('it works', () => {
+ expect(true).toBe(true)
+})
diff --git a/test/config/fixtures/workers-option/vitest.config.js b/test/config/fixtures/workers-option/vitest.config.js
new file mode 100644
index 000000000000..56004c9f9e06
--- /dev/null
+++ b/test/config/fixtures/workers-option/vitest.config.js
@@ -0,0 +1 @@
+export default {}
\ No newline at end of file
diff --git a/test/config/test/workers-option.test.ts b/test/config/test/workers-option.test.ts
new file mode 100644
index 000000000000..a31fa95f46bf
--- /dev/null
+++ b/test/config/test/workers-option.test.ts
@@ -0,0 +1,56 @@
+import { type UserConfig, describe, expect, test, vi } from 'vitest'
+
+import { getWorkersCountByPercentage } from 'vitest/src/utils/workers.js'
+import * as testUtils from '../../test-utils'
+
+vi.mock(import('node:os'), async importOriginal => ({
+ default: {
+ ...(await importOriginal()).default,
+ availableParallelism: () => 10,
+ },
+}))
+
+describe('workers util', () => {
+ test('percent=50% should return 5', () => {
+ expect(getWorkersCountByPercentage('50%')).toBe(5)
+ })
+
+ test('percent=-10% should return 1', () => {
+ expect(getWorkersCountByPercentage('-10%')).toBe(1)
+ })
+
+ test('percent=110% should return 10', () => {
+ expect(getWorkersCountByPercentage('110%')).toBe(10)
+ })
+})
+
+function runVitest(config: UserConfig) {
+ return testUtils.runVitest({ ...config, root: './fixtures/workers-option' })
+}
+
+test('workers percent argument should not throw error', async () => {
+ const { stderr } = await runVitest({ maxWorkers: '100%', minWorkers: '10%' })
+
+ expect(stderr).toBe('')
+})
+
+test.each([
+ { poolOption: 'threads' },
+ { poolOption: 'vmThreads' },
+ { poolOption: 'forks' },
+ { poolOption: 'vmForks' },
+] as const)('workers percent argument in $poolOption should not throw error', async ({ poolOption }) => {
+ let workerOptions = {}
+
+ if (poolOption.toLowerCase().includes('threads')) {
+ workerOptions = { maxThreads: '100%', minThreads: '10%' }
+ }
+
+ if (poolOption.toLowerCase().includes('forks')) {
+ workerOptions = { maxForks: '100%', minForks: '10%' }
+ }
+
+ const { stderr } = await runVitest({ poolOptions: { [poolOption]: workerOptions } })
+
+ expect(stderr).toBe('')
+})