From 4e179426399dfcf105eaa558fe061d4ab1951b21 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Sat, 10 Feb 2024 11:53:41 +0100 Subject: [PATCH] feat(vitest): add new CLI options (#5163) --- docs/config/index.md | 9 ++- packages/vitest/src/node/cli/cac.ts | 8 ++- packages/vitest/src/node/cli/cli-config.ts | 83 +++++++++++++++++----- test/core/test/cli-test.test.ts | 65 ++++++++++++++++- 4 files changed, 143 insertions(+), 22 deletions(-) diff --git a/docs/config/index.md b/docs/config/index.md index 598e6b0ae218..47910be20820 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -965,7 +965,7 @@ Minimum number of workers to run tests in. `poolOptions.{threads,vmThreads}.minT - **Type:** `number` - **Default:** `5000` -- **CLI:** `--test-timeout=5000` +- **CLI:** `--test-timeout=5000`, `--testTimeout=5000` Default timeout of a test in milliseconds @@ -973,6 +973,7 @@ Default timeout of a test in milliseconds - **Type:** `number` - **Default:** `10000` +- **CLI:** `--hook-timeout=10000`, `--hookTimeout=10000` Default timeout of a hook in milliseconds @@ -980,6 +981,7 @@ Default timeout of a hook in milliseconds - **Type:** `number` - **Default:** `10000` +- **CLI:** `--teardown-timeout=5000`, `--teardownTimeout=5000` Default timeout to wait for close when Vitest shuts down, in milliseconds @@ -1419,6 +1421,7 @@ See [istanbul documentation](https://github.com/istanbuljs/nyc#ignoring-methods) ``` - **Available for providers:** `'v8' | 'istanbul'` +- **CLI:** `--coverage.watermarks.statements=50,80`, `--coverage.watermarks.branches=50,80` Watermarks for statements, lines, branches and functions. See [istanbul documentation](https://github.com/istanbuljs/nyc#high-and-low-watermarks) for more information. @@ -1791,6 +1794,7 @@ By default, Vitest exports a proxy, bypassing CSS Modules processing. If you rel - **Type**: `number` - **Default**: `5` +- **CLI**: `--max-concurrency=10`, `--maxConcurrency=10` A number of tests that are allowed to run at the same time marked with `test.concurrent`. @@ -1799,6 +1803,7 @@ Test above this limit will be queued to run when available slot appears. ### cache - **Type**: `false | { dir? }` +- **CLI**: `--no-cache`, `--cache=false` Options to configure Vitest cache policy. At the moment Vitest stores cache for test results to run the longer and failed tests first. @@ -1806,6 +1811,7 @@ Options to configure Vitest cache policy. At the moment Vitest stores cache for - **Type**: `string` - **Default**: `node_modules/.vitest` +- **CLI**: `--cache.dir=./cache` Path to cache directory. @@ -1954,6 +1960,7 @@ Path to custom tsconfig, relative to the project root. - **Type**: `number` - **Default**: `300` +- **CLI**: `--slow-test-threshold=`, `--slowTestThreshold=` The number of milliseconds after which a test is considered slow and reported as such in the results. diff --git a/packages/vitest/src/node/cli/cac.ts b/packages/vitest/src/node/cli/cac.ts index ff83e5a754c9..903b748445ad 100644 --- a/packages/vitest/src/node/cli/cac.ts +++ b/packages/vitest/src/node/cli/cac.ts @@ -17,8 +17,12 @@ function addCommand(cli: CAC, name: string, option: CLIOption) { command += ` ${option.argument}` function transform(value: unknown) { - if (!option.array && Array.isArray(value)) - throw new Error(`Expected a single value for option "${command}"`) + if (!option.array && Array.isArray(value)) { + const received = value.map(s => typeof s === 'string' ? `"${s}"` : s).join(', ') + throw new Error( + `Expected a single value for option "${command}", received [${received}]`, + ) + } if (option.transform) return option.transform(value) if (option.array) diff --git a/packages/vitest/src/node/cli/cli-config.ts b/packages/vitest/src/node/cli/cli-config.ts index b30fabd2f7d7..855af3e5bb3d 100644 --- a/packages/vitest/src/node/cli/cli-config.ts +++ b/packages/vitest/src/node/cli/cli-config.ts @@ -81,6 +81,12 @@ const poolForksCommands: CLIOptions = { execArgv: null, } +function watermarkTransform(value: unknown) { + if (typeof value === 'string') + return value.split(',').map(Number) + return value +} + function transformNestedBoolean(value: unknown) { if (typeof value === 'boolean') return { enabled: value } @@ -249,9 +255,32 @@ export const cliOptionsConfig: VitestCLIOptions = { argument: '', normalize: true, }, - // TODO: suport watermarks via a special command? - // CAC requires --watermarks.statements=50 --watermarks.statements=80 for "statements:[50,80]" which looks rediculous - watermarks: null, + watermarks: { + description: null, + argument: '', // no displayed + subcommands: { + statements: { + description: 'High and low watermarks for statements in the format of ,', + argument: '', + transform: watermarkTransform, + }, + lines: { + description: 'High and low watermarks for lines in the format of ,', + argument: '', + transform: watermarkTransform, + }, + branches: { + description: 'High and low watermarks for branches in the format of ,', + argument: '', + transform: watermarkTransform, + }, + functions: { + description: 'High and low watermarks for functions in the format of ,', + argument: '', + transform: watermarkTransform, + }, + }, + }, }, }, mode: { @@ -434,6 +463,10 @@ export const cliOptionsConfig: VitestCLIOptions = { description: 'Default timeout of a test in milliseconds (default: 5000)', argument: '', }, + hookTimeout: { + description: 'Default hook timeout in milliseconds (default: 10000)', + argument: '', + }, bail: { description: 'Stop test execution when given number of tests have failed (default: 0)', argument: '', @@ -492,14 +525,35 @@ export const cliOptionsConfig: VitestCLIOptions = { description: 'The name of the project to run if you are using Vitest workspace feature. This can be repeated for multiple projects: --project=1 --project=2', argument: '', }, - // slowTestThreshold: { - // description: 'Threshold in milliseconds for a test to be considered slow (default: 300)', - // argument: '', - // }, - // teardownTimeout: { - // description: 'Default timeout of a teardown function in milliseconds (default: 10000)', - // argument: '', - // }, + slowTestThreshold: { + description: 'Threshold in milliseconds for a test to be considered slow (default: 300)', + argument: '', + }, + teardownTimeout: { + description: 'Default timeout of a teardown function in milliseconds (default: 10000)', + argument: '', + }, + cache: { + description: 'Enable cache', + argument: '', // allow only boolean + subcommands: { + dir: { + description: 'Path to the cache directory', + argument: '', + normalize: true, + }, + }, + // cache can only be "false" or an object + transform(cache) { + if (cache) + return {} + return cache + }, + }, + maxConcurrency: { + description: 'Maximum number of concurrent tests in a suite (default: 5)', + argument: '', + }, // CLI only options run: { @@ -537,18 +591,13 @@ export const cliOptionsConfig: VitestCLIOptions = { unstubGlobals: null, uiBase: null, benchmark: null, - name: null, include: null, testTransformMode: null, - hookTimeout: null, fakeTimers: null, - cache: null, chaiConfig: null, clearMocks: null, css: null, - maxConcurrency: null, poolMatchGlobs: null, deps: null, - slowTestThreshold: null, - teardownTimeout: null, + name: null, } diff --git a/test/core/test/cli-test.test.ts b/test/core/test/cli-test.test.ts index d7ab84962037..774b846b4eca 100644 --- a/test/core/test/cli-test.test.ts +++ b/test/core/test/cli-test.test.ts @@ -62,6 +62,11 @@ test('nested coverage options have correct types', async () => { --coverage.thresholds.lines 100 --coverage.thresholds.functions 30 --coverage.thresholds.branches 25 + + --coverage.watermarks.statements 50,80 + --coverage.watermarks.lines=30,40 + --coverage.watermarks.branches=70,80 + --coverage.watermarks.functions 20,60 `).coverage).toEqual({ enabled: true, reporter: ['text'], @@ -85,6 +90,12 @@ test('nested coverage options have correct types', async () => { autoUpdate: true, 100: true, }, + watermarks: { + statements: [50, 80], + lines: [30, 40], + branches: [70, 80], + functions: [20, 60], + }, }) }) @@ -110,7 +121,7 @@ test('all coverage enable options are working correctly', () => { test('fails when an array is passed down for a single value', async () => { expect(() => parseArguments('--coverage.provider v8 --coverage.provider istanbul')) - .toThrowErrorMatchingInlineSnapshot(`[Error: Expected a single value for option "--coverage.provider "]`) + .toThrowErrorMatchingInlineSnapshot(`[Error: Expected a single value for option "--coverage.provider ", received ["v8", "istanbul"]]`) }) test('even if coverage is boolean, don\'t fail', () => { @@ -137,7 +148,14 @@ test('array options', () => { } `) - expect(parseArguments('--reporter json --reporter=default --coverage.reporter=json --coverage.reporter html --coverage.extension=ts --coverage.extension=tsx')).toMatchInlineSnapshot(` + expect(parseArguments(` + --reporter json + --reporter=default + --coverage.reporter=json + --coverage.reporter html + --coverage.extension=ts + --coverage.extension=tsx + `)).toMatchInlineSnapshot(` { "coverage": { "extension": [ @@ -156,3 +174,46 @@ test('array options', () => { } `) }) + +test('hookTimeout is parsed correctly', () => { + expect(parseArguments('--hookTimeout 1000')).toEqual({ hookTimeout: 1000 }) + expect(parseArguments('--hook-timeout 1000')).toEqual({ hookTimeout: 1000 }) + expect(parseArguments('--hook-timeout=1000')).toEqual({ hookTimeout: 1000 }) + expect(parseArguments('--hookTimeout=1000')).toEqual({ hookTimeout: 1000 }) +}) + +test('teardownTimeout is parsed correctly', () => { + expect(parseArguments('--teardownTimeout 1000')).toEqual({ teardownTimeout: 1000 }) + expect(parseArguments('--teardown-timeout 1000')).toEqual({ teardownTimeout: 1000 }) + expect(parseArguments('--teardownTimeout=1000')).toEqual({ teardownTimeout: 1000 }) + expect(parseArguments('--teardown-timeout=1000')).toEqual({ teardownTimeout: 1000 }) +}) + +test('slowTestThreshold is parsed correctly', () => { + expect(parseArguments('--slowTestThreshold 1000')).toEqual({ slowTestThreshold: 1000 }) + expect(parseArguments('--slow-test-threshold 1000')).toEqual({ slowTestThreshold: 1000 }) + expect(parseArguments('--slowTestThreshold=1000')).toEqual({ slowTestThreshold: 1000 }) + expect(parseArguments('--slow-test-threshold=1000')).toEqual({ slowTestThreshold: 1000 }) +}) + +test('maxConcurrency is parsed correctly', () => { + expect(parseArguments('--maxConcurrency 1000')).toEqual({ maxConcurrency: 1000 }) + expect(parseArguments('--max-concurrency 1000')).toEqual({ maxConcurrency: 1000 }) + expect(parseArguments('--maxConcurrency=1000')).toEqual({ maxConcurrency: 1000 }) + expect(parseArguments('--max-concurrency=1000')).toEqual({ maxConcurrency: 1000 }) +}) + +test('cache is parsed correctly', () => { + expect(parseArguments('--cache')).toEqual({ cache: {} }) + expect(parseArguments('--no-cache')).toEqual({ cache: false }) + + expect(parseArguments('--cache.dir=./test/cache.json')).toEqual({ + cache: { dir: 'test/cache.json' }, + }) + expect(parseArguments('--cache.dir ./test/cache.json')).toEqual({ + cache: { dir: 'test/cache.json' }, + }) + expect(parseArguments('--cache.dir .\\test\\cache.json')).toEqual({ + cache: { dir: 'test/cache.json' }, + }) +})