From be499706f4ec5d3f93d14764950efa0cb73cfc60 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Wed, 21 Oct 2026 14:58:35 +0900 Subject: [PATCH] test: enforce the Bun coverage threshold --- bunfig.toml | 5 +- package.json | 2 +- packages/__tests__/coverage-threshold.test.ts | 93 +++++++++++++++++++ 3 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 packages/__tests__/coverage-threshold.test.ts diff --git a/bunfig.toml b/bunfig.toml index e423c0262..cdc48a859 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -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 ` 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/**"] diff --git a/package.json b/package.json index 390c16948..a757be641 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/__tests__/coverage-threshold.test.ts b/packages/__tests__/coverage-threshold.test.ts new file mode 100644 index 000000000..842d314bd --- /dev/null +++ b/packages/__tests__/coverage-threshold.test.ts @@ -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[][] { + 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 }) +})