Skip to content

Commit

Permalink
Merge branch 'main' into fix-non-tty-bench-table
Browse files Browse the repository at this point in the history
  • Loading branch information
hi-ogawa committed Apr 6, 2024
2 parents c5232ba + 6797b04 commit 97734d9
Show file tree
Hide file tree
Showing 29 changed files with 1,573 additions and 186 deletions.
68 changes: 68 additions & 0 deletions docs/config/index.md
Expand Up @@ -1103,6 +1103,20 @@ List of files included in coverage as glob patterns

List of files excluded from coverage as glob patterns.

This option overrides all default options. Extend the default options when adding new patterns to ignore:

```ts
import { coverageConfigDefaults, defineConfig } from 'vitest/config'

export default defineConfig({
test: {
coverage: {
exclude: ['**/custom-pattern/**', ...coverageConfigDefaults.exclude]
},
},
})
```

#### coverage.all

- **Type:** `boolean`
Expand Down Expand Up @@ -1320,6 +1334,38 @@ Sets thresholds for files matching the glob pattern.
}
```

#### coverage.ignoreEmptyLines

- **Type:** `boolean`
- **Default:** `false`
- **Available for providers:** `'v8'`
- **CLI:** `--coverage.ignoreEmptyLines=<boolean>`

Ignore empty lines, comments and other non-runtime code, e.g. Typescript types.

This option works only if the used compiler removes comments and other non-runtime code from the transpiled code.
By default Vite uses ESBuild which removes comments and Typescript types from `.ts`, `.tsx` and `.jsx` files.

If you want to apply ESBuild to other files as well, define them in [`esbuild` options](https://vitejs.dev/config/shared-options.html#esbuild):

```ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
esbuild: {
// Transpile all files with ESBuild to remove comments from code coverage.
// Required for `test.coverage.ignoreEmptyLines` to work:
include: ['**/*.js', '**/*.jsx', '**/*.mjs', '**/*.ts', '**/*.tsx'],
},
test: {
coverage: {
provider: 'v8',
ignoreEmptyLines: true,
},
},
})
```

#### coverage.ignoreClassMethods

- **Type:** `string[]`
Expand Down Expand Up @@ -2031,6 +2077,28 @@ export default defineConfig({
```
:::

#### diff.truncateThreshold

- **Type**: `number`
- **Default**: `0`

The maximum length of diff result to be displayed. Diffs above this threshold will be truncated.
Truncation won't take effect with default value 0.

#### diff.truncateAnnotation

- **Type**: `string`
- **Default**: `'... Diff result is truncated'`

Annotation that is output at the end of diff result if it's truncated.

#### diff.truncateAnnotationColor

- **Type**: `DiffOptionsColor = (arg: string) => string`
- **Default**: `noColor = (string: string): string => string`

Color of truncate annotation, default is output with no color.

### fakeTimers

- **Type:** `FakeTimerInstallOpts`
Expand Down
5 changes: 5 additions & 0 deletions docs/guide/coverage.md
Expand Up @@ -43,6 +43,11 @@ npm i -D @vitest/coverage-istanbul

## Coverage Setup

:::tip
It's recommended to always define [`coverage.include`](https://vitest.dev/config/#coverage-include) in your configuration file.
This helps Vitest to reduce the amount of files picked by [`coverage.all`](https://vitest.dev/config/#coverage-all).
:::

To test with coverage enabled, you can pass the `--coverage` flag in CLI.
By default, reporter `['text', 'html', 'clover', 'json']` will be used.

Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -84,7 +84,8 @@
"@types/chai@4.3.6": "patches/@types__chai@4.3.6.patch",
"@sinonjs/fake-timers@11.1.0": "patches/@sinonjs__fake-timers@11.1.0.patch",
"cac@6.7.14": "patches/cac@6.7.14.patch",
"@types/sinonjs__fake-timers@8.1.5": "patches/@types__sinonjs__fake-timers@8.1.5.patch"
"@types/sinonjs__fake-timers@8.1.5": "patches/@types__sinonjs__fake-timers@8.1.5.patch",
"v8-to-istanbul@9.2.0": "patches/v8-to-istanbul@9.2.0.patch"
}
},
"simple-git-hooks": {
Expand Down
4 changes: 2 additions & 2 deletions packages/coverage-v8/package.json
Expand Up @@ -56,8 +56,7 @@
"picocolors": "^1.0.0",
"std-env": "^3.5.0",
"strip-literal": "^2.0.0",
"test-exclude": "^6.0.0",
"v8-to-istanbul": "^9.2.0"
"test-exclude": "^6.0.0"
},
"devDependencies": {
"@types/debug": "^4.1.12",
Expand All @@ -66,6 +65,7 @@
"@types/istanbul-lib-source-maps": "^4.0.4",
"@types/istanbul-reports": "^3.0.4",
"pathe": "^1.1.1",
"v8-to-istanbul": "^9.2.0",
"vite-node": "workspace:*",
"vitest": "workspace:*"
}
Expand Down
12 changes: 5 additions & 7 deletions packages/coverage-v8/src/provider.ts
Expand Up @@ -266,14 +266,12 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
}

const coverages = await Promise.all(chunk.map(async (filename) => {
const transformResult = await this.ctx.vitenode.transformRequest(filename.pathname).catch(() => {})
const { originalSource, source } = await this.getSources(filename.href, transformResults)

// Ignore empty files, e.g. files that contain only typescript types and no runtime code
if (transformResult && stripLiteral(transformResult.code).trim() === '')
if (source && stripLiteral(source).trim() === '')
return null

const { originalSource } = await this.getSources(filename.href, transformResults)

const coverage = {
url: filename.href,
scriptId: '0',
Expand Down Expand Up @@ -309,9 +307,9 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
}> {
const filePath = normalize(fileURLToPath(url))

const transformResult = transformResults.get(filePath)
const transformResult = transformResults.get(filePath) || await this.ctx.vitenode.transformRequest(filePath).catch(() => {})

const map = transformResult?.map
const map = transformResult?.map as (EncodedSourceMap | undefined)
const code = transformResult?.code
const sourcesContent = map?.sourcesContent?.[0] || await fs.readFile(filePath, 'utf-8').catch(() => {
// If file does not exist construct a dummy source for it.
Expand Down Expand Up @@ -367,7 +365,7 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
// If no source map was found from vite-node we can assume this file was not run in the wrapper
const wrapperLength = sources.sourceMap ? WRAPPER_LENGTH : 0

const converter = v8ToIstanbul(url, wrapperLength, sources)
const converter = v8ToIstanbul(url, wrapperLength, sources, undefined, this.options.ignoreEmptyLines)
await converter.load()

converter.applyCoverage(functions)
Expand Down
36 changes: 21 additions & 15 deletions packages/utils/src/diff/diffLines.ts
Expand Up @@ -81,21 +81,24 @@ function printAnnotation({
return `${aColor(a)}\n${bColor(b)}\n\n`
}

export function printDiffLines(diffs: Array<Diff>, options: DiffOptionsNormalized): string {
export function printDiffLines(diffs: Array<Diff>, truncated: boolean, options: DiffOptionsNormalized): string {
return printAnnotation(options, countChanges(diffs))
+ (options.expand
? joinAlignedDiffsExpand(diffs, options)
: joinAlignedDiffsNoExpand(diffs, options))
+ (options.expand ? joinAlignedDiffsExpand(diffs, options) : joinAlignedDiffsNoExpand(diffs, options))
+ (truncated ? options.truncateAnnotationColor(`\n${options.truncateAnnotation}`) : '')
}

// Compare two arrays of strings line-by-line. Format as comparison lines.
export function diffLinesUnified(aLines: Array<string>, bLines: Array<string>, options?: DiffOptions): string {
const normalizedOptions = normalizeDiffOptions(options)
const [diffs, truncated] = diffLinesRaw(
isEmptyString(aLines) ? [] : aLines,
isEmptyString(bLines) ? [] : bLines,
normalizedOptions,
)
return printDiffLines(
diffLinesRaw(
isEmptyString(aLines) ? [] : aLines,
isEmptyString(bLines) ? [] : bLines,
),
normalizeDiffOptions(options),
diffs,
truncated,
normalizedOptions,
)
}

Expand All @@ -120,7 +123,7 @@ export function diffLinesUnified2(aLinesDisplay: Array<string>, bLinesDisplay: A
return diffLinesUnified(aLinesDisplay, bLinesDisplay, options)
}

const diffs = diffLinesRaw(aLinesCompare, bLinesCompare)
const [diffs, truncated] = diffLinesRaw(aLinesCompare, bLinesCompare, options)

// Replace comparison lines with displayable lines.
let aIndex = 0
Expand All @@ -144,13 +147,16 @@ export function diffLinesUnified2(aLinesDisplay: Array<string>, bLinesDisplay: A
}
})

return printDiffLines(diffs, normalizeDiffOptions(options))
return printDiffLines(diffs, truncated, normalizeDiffOptions(options))
}

// Compare two arrays of strings line-by-line.
export function diffLinesRaw(aLines: Array<string>, bLines: Array<string>): Array<Diff> {
const aLength = aLines.length
const bLength = bLines.length
export function diffLinesRaw(aLines: Array<string>, bLines: Array<string>, options?: DiffOptions): [Array<Diff>, boolean] {
const truncate = options?.truncateThreshold ?? false
const truncateThreshold = Math.max(Math.floor(options?.truncateThreshold ?? 0), 0)
const aLength = truncate ? Math.min(aLines.length, truncateThreshold) : aLines.length
const bLength = truncate ? Math.min(bLines.length, truncateThreshold) : bLines.length
const truncated = aLength !== aLines.length || bLength !== bLines.length

const isCommon = (aIndex: number, bIndex: number) => aLines[aIndex] === bLines[bIndex]

Expand Down Expand Up @@ -185,5 +191,5 @@ export function diffLinesRaw(aLines: Array<string>, bLines: Array<string>): Arra
for (; bIndex !== bLength; bIndex += 1)
diffs.push(new Diff(DIFF_INSERT, bLines[bIndex]))

return diffs
return [diffs, truncated]
}
33 changes: 28 additions & 5 deletions packages/utils/src/diff/diffStrings.ts
Expand Up @@ -7,8 +7,31 @@

import * as diff from 'diff-sequences'
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, Diff } from './cleanupSemantic'
import type { DiffOptions } from './types'

// platforms compatible
function getNewLineSymbol(string: string) {
return string.includes('\r\n') ? '\r\n' : '\n'
}

function diffStrings(a: string, b: string, options?: DiffOptions): [Array<Diff>, boolean] {
const truncate = options?.truncateThreshold ?? false
const truncateThreshold = Math.max(Math.floor(options?.truncateThreshold ?? 0), 0)
let aLength = a.length
let bLength = b.length
if (truncate) {
const aMultipleLines = a.includes('\n')
const bMultipleLines = b.includes('\n')
const aNewLineSymbol = getNewLineSymbol(a)
const bNewLineSymbol = getNewLineSymbol(b)
// multiple-lines string expects a newline to be appended at the end
const _a = aMultipleLines ? `${a.split(aNewLineSymbol, truncateThreshold).join(aNewLineSymbol)}\n` : a
const _b = bMultipleLines ? `${b.split(bNewLineSymbol, truncateThreshold).join(bNewLineSymbol)}\n` : b
aLength = _a.length
bLength = _b.length
}
const truncated = aLength !== a.length || bLength !== b.length

function diffStrings(a: string, b: string): Array<Diff> {
const isCommon = (aIndex: number, bIndex: number) => a[aIndex] === b[bIndex]

let aIndex = 0
Expand All @@ -34,16 +57,16 @@ function diffStrings(a: string, b: string): Array<Diff> {
// @ts-expect-error wrong bundling
const diffSequences = diff.default.default || diff.default

diffSequences(a.length, b.length, isCommon, foundSubsequence)
diffSequences(aLength, bLength, isCommon, foundSubsequence)

// After the last common subsequence, push remaining change items.
if (aIndex !== a.length)
if (aIndex !== aLength)
diffs.push(new Diff(DIFF_DELETE, a.slice(aIndex)))

if (bIndex !== b.length)
if (bIndex !== bLength)
diffs.push(new Diff(DIFF_INSERT, b.slice(bIndex)))

return diffs
return [diffs, truncated]
}

export default diffStrings
4 changes: 4 additions & 0 deletions packages/utils/src/diff/normalizeDiffOptions.ts
Expand Up @@ -12,6 +12,7 @@ import type { DiffOptions, DiffOptionsNormalized } from './types'
export const noColor = (string: string): string => string

const DIFF_CONTEXT_DEFAULT = 5
const DIFF_TRUNCATE_THRESHOLD_DEFAULT = 0 // not truncate

function getDefaultOptions(): DiffOptionsNormalized {
const c = getColors()
Expand All @@ -35,6 +36,9 @@ function getDefaultOptions(): DiffOptionsNormalized {
includeChangeCounts: false,
omitAnnotationLines: false,
patchColor: c.yellow,
truncateThreshold: DIFF_TRUNCATE_THRESHOLD_DEFAULT,
truncateAnnotation: '... Diff result is truncated',
truncateAnnotationColor: noColor,
}
}

Expand Down
11 changes: 6 additions & 5 deletions packages/utils/src/diff/printDiffs.ts
Expand Up @@ -32,16 +32,17 @@ export function diffStringsUnified(a: string, b: string, options?: DiffOptions):
const isMultiline = a.includes('\n') || b.includes('\n')

// getAlignedDiffs assumes that a newline was appended to the strings.
const diffs = diffStringsRaw(
const [diffs, truncated] = diffStringsRaw(
isMultiline ? `${a}\n` : a,
isMultiline ? `${b}\n` : b,
true, // cleanupSemantic
options,
)

if (hasCommonDiff(diffs, isMultiline)) {
const optionsNormalized = normalizeDiffOptions(options)
const lines = getAlignedDiffs(diffs, optionsNormalized.changeColor)
return printDiffLines(lines, optionsNormalized)
return printDiffLines(lines, truncated, optionsNormalized)
}
}

Expand All @@ -51,11 +52,11 @@ export function diffStringsUnified(a: string, b: string, options?: DiffOptions):

// Compare two strings character-by-character.
// Optionally clean up small common substrings, also known as chaff.
export function diffStringsRaw(a: string, b: string, cleanup: boolean): Array<Diff> {
const diffs = diffStrings(a, b)
export function diffStringsRaw(a: string, b: string, cleanup: boolean, options?: DiffOptions): [Array<Diff>, boolean] {
const [diffs, truncated] = diffStrings(a, b, options)

if (cleanup)
cleanupSemantic(diffs) // impure function

return diffs
return [diffs, truncated]
}
6 changes: 6 additions & 0 deletions packages/utils/src/diff/types.ts
Expand Up @@ -27,6 +27,9 @@ export interface DiffOptions {
omitAnnotationLines?: boolean
patchColor?: DiffOptionsColor
compareKeys?: CompareKeys
truncateThreshold?: number
truncateAnnotation?: string
truncateAnnotationColor?: DiffOptionsColor
}

export interface DiffOptionsNormalized {
Expand All @@ -48,4 +51,7 @@ export interface DiffOptionsNormalized {
includeChangeCounts: boolean
omitAnnotationLines: boolean
patchColor: DiffOptionsColor
truncateThreshold: number
truncateAnnotation: string
truncateAnnotationColor: DiffOptionsColor
}
1 change: 1 addition & 0 deletions packages/vitest/src/defaults.ts
Expand Up @@ -43,6 +43,7 @@ export const coverageConfigDefaults: ResolvedCoverageOptions = {
reporter: [['text', {}], ['html', {}], ['clover', {}], ['json', {}]],
extension: ['.js', '.cjs', '.mjs', '.ts', '.mts', '.cts', '.tsx', '.jsx', '.vue', '.svelte', '.marko'],
allowExternal: false,
ignoreEmptyLines: false,
processingConcurrency: Math.min(20, os.availableParallelism?.() ?? os.cpus().length),
}

Expand Down
6 changes: 5 additions & 1 deletion packages/vitest/src/node/reporters/base.ts
Expand Up @@ -328,8 +328,12 @@ export abstract class BaseReporter implements Reporter {
const groupName = getFullName(group, c.dim(' > '))
logger.log(` ${bench.name}${c.dim(` - ${groupName}`)}`)
const siblings = group.tasks
.filter(i => i.result?.benchmark && i !== bench)
.filter(i => i.meta.benchmark && i.result?.benchmark && i !== bench)
.sort((a, b) => a.result!.benchmark!.rank - b.result!.benchmark!.rank)
if (siblings.length === 0) {
logger.log('')
continue
}
for (const sibling of siblings) {
const number = `${(sibling.result!.benchmark!.mean / bench.result!.benchmark!.mean).toFixed(2)}x`
logger.log(` ${c.green(number)} ${c.gray('faster than')} ${sibling.name}`)
Expand Down
7 changes: 6 additions & 1 deletion packages/vitest/src/types/coverage.ts
Expand Up @@ -233,7 +233,12 @@ export interface CoverageIstanbulOptions extends BaseCoverageOptions {
ignoreClassMethods?: string[]
}

export interface CoverageV8Options extends BaseCoverageOptions {}
export interface CoverageV8Options extends BaseCoverageOptions {
/**
* Ignore empty lines, comments and other non-runtime code, e.g. Typescript types
*/
ignoreEmptyLines?: boolean
}

export interface CustomProviderOptions extends Pick<BaseCoverageOptions, FieldsWithDefaultValues> {
/** Name of the module or path to a file to load the custom provider from */
Expand Down

0 comments on commit 97734d9

Please sign in to comment.