Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(coverage): remove empty coverage folder on test failure too #6547

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions packages/coverage-istanbul/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,13 +307,23 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
const keepResults = !this.options.cleanOnRerun && this.ctx.config.watch

if (!keepResults) {
this.coverageFiles = new Map()
await fs.rm(this.coverageFilesDirectory, { recursive: true })
await this.cleanAfterRun()
}
}

// Remove empty reports directory, e.g. when only text-reporter is used
if (readdirSync(this.options.reportsDirectory).length === 0) {
await fs.rm(this.options.reportsDirectory, { recursive: true })
}
private async cleanAfterRun() {
this.coverageFiles = new Map()
await fs.rm(this.coverageFilesDirectory, { recursive: true })

// Remove empty reports directory, e.g. when only text-reporter is used
if (readdirSync(this.options.reportsDirectory).length === 0) {
await fs.rm(this.options.reportsDirectory, { recursive: true })
}
}

async onTestFailure() {
if (!this.options.reportOnFailure) {
await this.cleanAfterRun()
}
}

Expand Down
22 changes: 16 additions & 6 deletions packages/coverage-v8/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,13 +279,23 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
const keepResults = !this.options.cleanOnRerun && this.ctx.config.watch

if (!keepResults) {
this.coverageFiles = new Map()
await fs.rm(this.coverageFilesDirectory, { recursive: true })
await this.cleanAfterRun()
}
}

// Remove empty reports directory, e.g. when only text-reporter is used
if (readdirSync(this.options.reportsDirectory).length === 0) {
await fs.rm(this.options.reportsDirectory, { recursive: true })
}
private async cleanAfterRun() {
this.coverageFiles = new Map()
await fs.rm(this.coverageFilesDirectory, { recursive: true })

// Remove empty reports directory, e.g. when only text-reporter is used
if (readdirSync(this.options.reportsDirectory).length === 0) {
await fs.rm(this.options.reportsDirectory, { recursive: true })
}
}

async onTestFailure() {
if (!this.options.reportOnFailure) {
await this.cleanAfterRun()
}
}

Expand Down
8 changes: 6 additions & 2 deletions packages/vitest/src/node/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -974,8 +974,12 @@ export class Vitest {
}

private async reportCoverage(coverage: unknown, allTestsRun: boolean) {
if (!this.config.coverage.reportOnFailure && this.state.getCountOfFailedTests() > 0) {
return
if (this.state.getCountOfFailedTests() > 0) {
await this.coverageProvider?.onTestFailure?.()

if (!this.config.coverage.reportOnFailure) {
return
}
}

if (this.coverageProvider) {
Expand Down
3 changes: 3 additions & 0 deletions packages/vitest/src/node/types/coverage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export interface CoverageProvider {
/** Called with coverage results after a single test file has been run */
onAfterSuiteRun: (meta: AfterSuiteRunMeta) => void | Promise<void>

/** Callback called when test run fails */
onTestFailure?: () => void | Promise<void>

/** Callback to generate final coverage results */
generateCoverage: (
reportContext: ReportContext
Expand Down
34 changes: 24 additions & 10 deletions test/coverage-test/test/empty-coverage-directory.test.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
import { existsSync, readdirSync } from 'node:fs'
import { expect } from 'vitest'
import { coverageTest, normalizeURL, runVitest, test } from '../utils'
import { existsSync } from 'node:fs'
import { beforeEach, expect } from 'vitest'
import { captureStdout, coverageTest, normalizeURL, runVitest, test } from '../utils'
import { sum } from '../fixtures/src/math'

beforeEach(() => {
return captureStdout()
})

test('empty coverage directory is cleaned after tests', async () => {
await runVitest({
include: [normalizeURL(import.meta.url)],
testNamePattern: 'passing test',
coverage: { reporter: 'text', all: false },
})

if (existsSync('./coverage')) {
if (readdirSync('./coverage').length !== 0) {
throw new Error('Test case expected coverage directory to be empty')
}
expect(existsSync('./coverage')).toBe(false)
})

test('empty coverage directory is cleaned after failing test run', async () => {
const { exitCode } = await runVitest({
include: [normalizeURL(import.meta.url)],
testNamePattern: 'failing test',
coverage: { reporter: 'text', all: false },
}, { throwOnError: false })

throw new Error('Empty coverage directory was not cleaned')
}
expect(existsSync('./coverage')).toBe(false)
expect(exitCode).toBe(1)
})

coverageTest('cover some lines', () => {
coverageTest('passing test', () => {
expect(sum(2, 3)).toBe(5)
})

coverageTest('failing test', () => {
expect(sum(2, 3)).toBe(6)
})
62 changes: 62 additions & 0 deletions test/coverage-test/test/on-failure.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { expect } from 'vitest'
import { captureStdout, coverageTest, isV8Provider, normalizeURL, runVitest, test } from '../utils'
import { sum } from '../fixtures/src/math'

test('report is not generated when tests fail', async () => {
const stdout = captureStdout()

const { exitCode } = await runVitest({
include: [normalizeURL(import.meta.url)],
coverage: {
all: false,
include: ['**/fixtures/src/math.ts'],
reporter: 'text',
},
}, { throwOnError: false })

expect(stdout()).toBe('')
expect(exitCode).toBe(1)
})

test('report is generated when tests fail and { reportOnFailure: true }', async () => {
const stdout = captureStdout()

const { exitCode } = await runVitest({
include: [normalizeURL(import.meta.url)],
coverage: {
all: false,
include: ['**/fixtures/src/math.ts'],
reporter: 'text',
reportOnFailure: true,
},
}, { throwOnError: false })

if (isV8Provider()) {
expect(stdout()).toMatchInlineSnapshot(`
"----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 50 | 100 | 25 | 50 |
math.ts | 50 | 100 | 25 | 50 | 6-7,10-11,14-15
----------|---------|----------|---------|---------|-------------------
"
`)
}
else {
expect(stdout()).toMatchInlineSnapshot(`
"----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 25 | 100 | 25 | 25 |
math.ts | 25 | 100 | 25 | 25 | 6-14
----------|---------|----------|---------|---------|-------------------
"
`)
}

expect(exitCode).toBe(1)
})

coverageTest('failing test', () => {
expect(sum(1, 2)).toBe(4)
})
14 changes: 13 additions & 1 deletion test/coverage-test/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { normalize } from 'pathe'
import libCoverage from 'istanbul-lib-coverage'
import type { FileCoverageData } from 'istanbul-lib-coverage'
import type { TestFunction, UserConfig } from 'vitest'
import { describe as vitestDescribe, test as vitestTest } from 'vitest'
import { vi, describe as vitestDescribe, test as vitestTest } from 'vitest'
import stripAnsi from 'strip-ansi'
import * as testUtils from '../test-utils'

export function test(name: string, fn: TestFunction, skip = false) {
Expand Down Expand Up @@ -103,3 +104,14 @@ export function isBrowser() {
export function normalizeURL(importMetaURL: string) {
return normalize(fileURLToPath(importMetaURL))
}

export function captureStdout() {
const spy = vi.fn()
const original = process.stdout.write
process.stdout.write = spy

return function collect() {
process.stdout.write = original
return stripAnsi(spy.mock.calls.map(call => call[0]).join(''))
}
}
Loading