Skip to content
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
5 changes: 3 additions & 2 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
[test]
root = "packages"
preload = ["./bun.setup.ts", "./packages/bun-plugin/src/index.ts", "bun-test-env-dom"]
coverage = true
# `bun run test` passes `--coverage`; enabling it here would hold focused `bun test <file>` runs to the full-suite threshold.
coverageReporter = ["text", "lcov"]
coverageThreshold = { line = 1.0, function = 1.0, statement = 1.0 }
# Bun ignores unknown keys without a warning; packages/__tests__/coverage-threshold.test.ts checks this gate.
coverageThreshold = { lines = 1.0, functions = 1.0, statements = 1.0 }
coverageSkipTestFiles = true
# Generated artifacts are tested separately by the packed-consumer release gate.
coveragePathIgnorePatterns = ["**/pkg/**", "**/dist/**"]
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"lint": "cargo fmt --all -- --check && cargo clippy --all-targets --all-features -- -D warnings && node --require ./eslint-typescript6.cjs ./node_modules/eslint/bin/eslint.js",
"lint:fix": "node --require ./eslint-typescript6.cjs ./node_modules/eslint/bin/eslint.js --fix && cargo fmt",
"test": "cargo tarpaulin --out xml --out stdout --all-targets --engine llvm $(node -p \"process.env.CI ? '--fail-under 100' : ''\") && bun test",
"test": "cargo tarpaulin --out xml --out stdout --all-targets --engine llvm $(node -p \"process.env.CI ? '--fail-under 100' : ''\") && bun test --coverage",
"test:e2e": "bunx playwright test",
"test:e2e:next": "LANDING_BUILD_MODE=next bunx playwright test",
"test:e2e:rsc-css": "bunx playwright test -c playwright.rsc-css.config.ts",
Expand Down
93 changes: 93 additions & 0 deletions packages/__tests__/coverage-threshold.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import assert from 'node:assert/strict'
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'

import { expect, it } from 'bun:test'

const root = resolve(import.meta.dir, '../..')
const bunfig = await Bun.file(resolve(root, 'bunfig.toml')).text()
const manifest: unknown = await Bun.file(resolve(root, 'package.json')).json()
assert(
typeof manifest === 'object' && manifest !== null && 'scripts' in manifest,
)
const { scripts } = manifest
assert(typeof scripts === 'object' && scripts !== null)

function script(name: string): string {
const command: unknown = Reflect.get(scripts, name)
assert(typeof command === 'string', `package.json has no script ${name}`)
return command
}

function bunTestCommands(
name: string,
visited = new Set<string>(),
): string[][] {
if (visited.has(name)) return []
visited.add(name)
return script(name)
.split(' && ')
.map((step) => step.trim().split(/\s+/))
.filter((words) => words[0] === 'bun')
.flatMap((words) => {
if (words[1] === 'run') return bunTestCommands(String(words[2]), visited)
return words.includes('test') ? [words] : []
})
}

// `root` and `preload` name repository files; every other option applies unchanged.
const fixtureConfig = bunfig.replace(/^(?:root|preload) = .*$/gm, '')
const fixtureTest = `import { expect, test } from 'bun:test'
import { pick } from './source'

test('pick', () => {
expect(pick(true)).toBe(1)
})
`
const fullyCovered =
'export function pick(value: boolean) {\n return value ? 1 : 2\n}\n'

function coverageRun(source: string) {
const directory = mkdtempSync(join(tmpdir(), 'coverage-threshold-'))
try {
writeFileSync(join(directory, 'bunfig.toml'), fixtureConfig)
writeFileSync(join(directory, 'source.ts'), source)
writeFileSync(join(directory, 'source.test.ts'), fixtureTest)
const run = Bun.spawnSync([process.execPath, 'test', '--coverage'], {
cwd: directory,
})
return {
exitCode: run.exitCode,
output: run.stdout.toString() + run.stderr.toString(),
}
} finally {
rmSync(directory, { recursive: true, force: true })
}
}

it('requires every file to be fully covered', () => {
expect(Bun.TOML.parse(bunfig)).toMatchObject({
test: { coverageThreshold: { lines: 1, functions: 1, statements: 1 } },
})
})

it('collects coverage in every Bun test run of the test script', () => {
const commands = bunTestCommands('test')
expect(commands).not.toEqual([])
expect(commands.filter((words) => !words.includes('--coverage'))).toEqual([])
})

it.each([
[
'an uncovered line',
'export function pick(value: boolean) {\n if (value) {\n return 1\n }\n return 2\n}\n',
],
['an uncovered function', `${fullyCovered}export const unused = () => 3\n`],
])('fails the run when a file has %s', (_, source) => {
expect(coverageRun(source)).toMatchObject({ exitCode: 1 })
})

it('passes the run when every file is fully covered', () => {
expect(coverageRun(fullyCovered)).toMatchObject({ exitCode: 0 })
})
Loading