mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-15 12:53:31 +08:00
fix(ci): root explicit Bun test filters
Avoid Bun filter-mode repository scans that exhaust macOS file descriptors and corrupt subprocess test evidence. Apply rooted filters across server, contract, coverage, persistence, policy, desktop native, and adapter test entrypoints. Confidence: high Scope-risk: narrow Tested: bun run check:policy; bun run check:server; bun run check:chat-contract
This commit is contained in:
parent
46dca4e961
commit
08dd64596a
@ -10,12 +10,12 @@
|
||||
"dingtalk": "bun run dingtalk/index.ts",
|
||||
"whatsapp": "bun run whatsapp/index.ts",
|
||||
"test": "bun test",
|
||||
"test:common": "bun test common/",
|
||||
"test:telegram": "bun test telegram/",
|
||||
"test:feishu": "bun test feishu/",
|
||||
"test:wechat": "bun test wechat/",
|
||||
"test:dingtalk": "bun test dingtalk/",
|
||||
"test:whatsapp": "bun test whatsapp/"
|
||||
"test:common": "bun test ./common/",
|
||||
"test:telegram": "bun test ./telegram/",
|
||||
"test:feishu": "bun test ./feishu/",
|
||||
"test:wechat": "bun test ./wechat/",
|
||||
"test:dingtalk": "bun test ./dingtalk/",
|
||||
"test:whatsapp": "bun test ./whatsapp/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@larksuiteoapi/node-sdk": "^1.60.0",
|
||||
|
||||
@ -91,7 +91,7 @@
|
||||
"electron:build": "bun run build:sidecars && bun run build && bun run build:electron",
|
||||
"electron:package": "bun run clean:electron-output && bun run electron:build && node ./node_modules/electron-builder/out/cli/cli.js --publish never",
|
||||
"electron:package:dir": "bun run clean:electron-output && bun run electron:build && node ./node_modules/electron-builder/out/cli/cli.js --dir --publish never",
|
||||
"check:electron": "node ./node_modules/typescript/bin/tsc -p electron/tsconfig.json && bun test electron && bun run build:electron",
|
||||
"check:electron": "node ./node_modules/typescript/bin/tsc -p electron/tsconfig.json && bun test ./electron && bun run build:electron",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
"start": "bun run ./bin/claude-haha",
|
||||
"check:pr": "bun run scripts/pr/check-pr.ts",
|
||||
"check:impact": "bun run scripts/pr/impact-report.ts",
|
||||
"check:policy": "bun test scripts/pr/change-policy.test.ts scripts/pr/changed-files.test.ts scripts/pr/pr-triage-workflow.test.ts scripts/pr/pr-quality-workflow.test.ts scripts/pr/release-workflow.test.ts scripts/pr/quality-contract.test.ts scripts/pr/test-environment.test.ts scripts/release-update-metadata.test.ts scripts/git-hooks/install.test.ts scripts/quality-gate/quarantine.test.ts scripts/quality-gate/coverage.test.ts scripts/quality-gate/provider-smoke/execute.test.ts scripts/quality-gate/desktop-smoke/execute.test.ts scripts/quality-gate/providerTargets.test.ts scripts/quality-gate/runner.test.ts",
|
||||
"check:policy": "bun test ./scripts/pr/bun-test-filter.test.ts ./scripts/pr/change-policy.test.ts ./scripts/pr/changed-files.test.ts ./scripts/pr/pr-triage-workflow.test.ts ./scripts/pr/pr-quality-workflow.test.ts ./scripts/pr/release-workflow.test.ts ./scripts/pr/quality-contract.test.ts ./scripts/pr/test-environment.test.ts ./scripts/release-update-metadata.test.ts ./scripts/git-hooks/install.test.ts ./scripts/quality-gate/quarantine.test.ts ./scripts/quality-gate/coverage.test.ts ./scripts/quality-gate/provider-smoke/execute.test.ts ./scripts/quality-gate/desktop-smoke/execute.test.ts ./scripts/quality-gate/providerTargets.test.ts ./scripts/quality-gate/runner.test.ts",
|
||||
"check:server": "bun run scripts/pr/run-server-tests.ts",
|
||||
"check:provider-contract": "bun run scripts/pr/run-provider-contract-tests.ts",
|
||||
"check:chat-contract": "bun run scripts/pr/run-chat-contract-tests.ts",
|
||||
|
||||
26
scripts/pr/bun-test-filter.test.ts
Normal file
26
scripts/pr/bun-test-filter.test.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { rootBunTestFilter } from './bun-test-filter'
|
||||
|
||||
describe('rootBunTestFilter', () => {
|
||||
test('roots repository-relative files and directories', () => {
|
||||
expect(rootBunTestFilter('src/server/example.test.ts')).toBe(
|
||||
'./src/server/example.test.ts',
|
||||
)
|
||||
expect(rootBunTestFilter('electron')).toBe('./electron')
|
||||
})
|
||||
|
||||
test('preserves already rooted and absolute filters', () => {
|
||||
expect(rootBunTestFilter('./src/example.test.ts')).toBe(
|
||||
'./src/example.test.ts',
|
||||
)
|
||||
expect(rootBunTestFilter('../shared/example.test.ts')).toBe(
|
||||
'../shared/example.test.ts',
|
||||
)
|
||||
expect(rootBunTestFilter('/tmp/example.test.ts')).toBe(
|
||||
'/tmp/example.test.ts',
|
||||
)
|
||||
expect(rootBunTestFilter('C:\\repo\\example.test.ts')).toBe(
|
||||
'C:\\repo\\example.test.ts',
|
||||
)
|
||||
})
|
||||
})
|
||||
20
scripts/pr/bun-test-filter.ts
Normal file
20
scripts/pr/bun-test-filter.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { isAbsolute } from 'node:path'
|
||||
|
||||
const WINDOWS_ABSOLUTE_PATH = /^(?:[a-zA-Z]:[\\/]|\\\\)/
|
||||
|
||||
/**
|
||||
* Root explicit Bun test filters so Bun does not scan the whole repository.
|
||||
* Unrooted filters can retain enough directory descriptors on macOS to break
|
||||
* subprocess output capture: https://github.com/oven-sh/bun/issues/32067
|
||||
*/
|
||||
export function rootBunTestFilter(filter: string): string {
|
||||
if (
|
||||
filter.startsWith('./') ||
|
||||
filter.startsWith('../') ||
|
||||
isAbsolute(filter) ||
|
||||
WINDOWS_ABSOLUTE_PATH.test(filter)
|
||||
) {
|
||||
return filter
|
||||
}
|
||||
return `./${filter}`
|
||||
}
|
||||
@ -90,6 +90,7 @@ describe('feature quality contract', () => {
|
||||
expect(packageJson.scripts?.['check:persistence-upgrade']).toBe('bun run scripts/quality-gate/persistence-upgrade.ts')
|
||||
expect(packageJson.scripts?.['check:provider-contract']).toBe('bun run scripts/pr/run-provider-contract-tests.ts')
|
||||
expect(packageJson.scripts?.['check:chat-contract']).toBe('bun run scripts/pr/run-chat-contract-tests.ts')
|
||||
expect(packageJson.scripts?.['check:policy']).toContain('bun test ./scripts/')
|
||||
expect(packageJson.scripts?.['check:native']).toContain('electron:package:dir')
|
||||
expect(packageJson.scripts?.['check:native']).toContain('test:package-smoke:current')
|
||||
expect(packageJson.scripts?.['test:package-smoke:current']).toBe('bun run scripts/quality-gate/package-smoke/current.ts')
|
||||
@ -137,6 +138,7 @@ describe('feature quality contract', () => {
|
||||
expect(serverRunner).toContain('TEST_FILE_PATTERN')
|
||||
expect(serverRunner).toContain("'--no-env-file'")
|
||||
expect(serverRunner).toContain('createSandboxedTestEnvironment')
|
||||
expect(serverRunner).toContain('rootBunTestFilter(file)')
|
||||
expect(serverRunner).toContain('evidenceComplete')
|
||||
expect(serverRunner).toContain('reportedFiles === 1')
|
||||
expect(serverRunner).toContain('passedTests + failedTests > 0')
|
||||
@ -145,6 +147,7 @@ describe('feature quality contract', () => {
|
||||
expect(coverageRunner).toContain('TEST_FILE_PATTERN')
|
||||
expect(coverageRunner).toContain("'--no-env-file'")
|
||||
expect(coverageRunner).toContain('createSandboxedTestEnvironment')
|
||||
expect(coverageRunner).toContain('serverFiles.map(rootBunTestFilter)')
|
||||
expect(coverageRunner).toContain("correctness is enforced by check:server's per-file sandboxed test processes")
|
||||
expect(coverageRunner).toContain('rootCoverageAvailable')
|
||||
expect(coverageRunner).toContain('rootTestDiscoveryComplete')
|
||||
@ -152,6 +155,7 @@ describe('feature quality contract', () => {
|
||||
for (const contractRunner of [providerRunner, chatRunner]) {
|
||||
expect(contractRunner).toContain("'--no-env-file'")
|
||||
expect(contractRunner).toContain('createSandboxedTestEnvironment')
|
||||
expect(contractRunner).toContain('rootBunTestFilter')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import { mkdtempSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { resolve } from 'node:path'
|
||||
import { rootBunTestFilter } from './bun-test-filter'
|
||||
import { createSandboxedTestEnvironment } from './test-environment'
|
||||
|
||||
interface ContractSuite {
|
||||
@ -20,7 +21,7 @@ const suites: ContractSuite[] = [
|
||||
'bun',
|
||||
'--no-env-file',
|
||||
'test',
|
||||
'src/server/__tests__/websocket-handler.test.ts',
|
||||
rootBunTestFilter('src/server/__tests__/websocket-handler.test.ts'),
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -30,7 +31,7 @@ const suites: ContractSuite[] = [
|
||||
'bun',
|
||||
'--no-env-file',
|
||||
'test',
|
||||
'src/server/__tests__/conversations.test.ts',
|
||||
rootBunTestFilter('src/server/__tests__/conversations.test.ts'),
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import { mkdtempSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { rootBunTestFilter } from './bun-test-filter'
|
||||
import { createSandboxedTestEnvironment } from './test-environment'
|
||||
|
||||
const root = join(import.meta.dir, '..', '..')
|
||||
@ -34,7 +35,12 @@ for (const testFile of testFiles) {
|
||||
const sandboxHome = mkdtempSync(join(tmpdir(), 'cc-haha-provider-contract-'))
|
||||
let exitCode = 1
|
||||
try {
|
||||
const proc = Bun.spawn(['bun', '--no-env-file', 'test', testFile], {
|
||||
const proc = Bun.spawn([
|
||||
'bun',
|
||||
'--no-env-file',
|
||||
'test',
|
||||
rootBunTestFilter(testFile),
|
||||
], {
|
||||
cwd: root,
|
||||
env: createSandboxedTestEnvironment(sandboxHome),
|
||||
stdout: 'inherit',
|
||||
|
||||
@ -4,6 +4,7 @@ import { mkdtempSync, readdirSync, rmSync, statSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join, relative, sep } from 'node:path'
|
||||
import { loadQuarantineManifest, quarantinedPathSet } from '../quality-gate/quarantine'
|
||||
import { rootBunTestFilter } from './bun-test-filter'
|
||||
import { createSandboxedTestEnvironment } from './test-environment'
|
||||
|
||||
const root = process.cwd()
|
||||
@ -69,7 +70,14 @@ async function runTestFile(file: string): Promise<TestFileResult> {
|
||||
const sandboxHome = mkdtempSync(join(tmpdir(), 'cc-haha-server-test-'))
|
||||
try {
|
||||
const proc = Bun.spawn(
|
||||
['bun', '--no-env-file', 'test', '--max-concurrency=1', '--timeout=20000', file],
|
||||
[
|
||||
'bun',
|
||||
'--no-env-file',
|
||||
'test',
|
||||
'--max-concurrency=1',
|
||||
'--timeout=20000',
|
||||
rootBunTestFilter(file),
|
||||
],
|
||||
{
|
||||
cwd: root,
|
||||
env: createSandboxedTestEnvironment(sandboxHome),
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join, relative, sep, win32 } from 'node:path'
|
||||
import { rootBunTestFilter } from '../pr/bun-test-filter'
|
||||
import { createSandboxedTestEnvironment } from '../pr/test-environment'
|
||||
import { loadQuarantineManifest, quarantinedPathSet } from './quarantine'
|
||||
|
||||
@ -805,7 +806,7 @@ export async function runCoverageGate(options: {
|
||||
const coverageByFile = new Map<string, FileLineCoverage>()
|
||||
|
||||
mkdirSync(join(outputDir, 'root-server'), { recursive: true })
|
||||
const rootCommand = ['bun', '--no-env-file', 'test', '--timeout=20000', '--coverage', '--coverage-reporter=lcov', '--coverage-reporter=text', '--coverage-dir', join(outputDir, 'root-server'), ...serverFiles]
|
||||
const rootCommand = ['bun', '--no-env-file', 'test', '--timeout=20000', '--coverage', '--coverage-reporter=lcov', '--coverage-reporter=text', '--coverage-dir', join(outputDir, 'root-server'), ...serverFiles.map(rootBunTestFilter)]
|
||||
const rootLogPath = join(outputDir, 'root-server', 'coverage.log')
|
||||
const rootResult = await runCommand(rootCommand, rootDir, rootLogPath)
|
||||
const rootLcovPath = join(outputDir, 'root-server', 'lcov.info')
|
||||
|
||||
@ -150,7 +150,7 @@ export function lanesForMode(mode: QualityGateMode, baselineTargets: BaselineTar
|
||||
title: 'Baseline case catalog validation',
|
||||
description: 'Validate real Coding Agent baseline case definitions and fixture metadata.',
|
||||
kind: 'command',
|
||||
command: ['bun', 'test', 'scripts/quality-gate/baseline/cases.test.ts'],
|
||||
command: ['bun', 'test', './scripts/quality-gate/baseline/cases.test.ts'],
|
||||
requiredForModes: ['baseline', 'release'],
|
||||
category: 'unit',
|
||||
},
|
||||
|
||||
@ -10,7 +10,7 @@ const rootDir = process.cwd()
|
||||
const checks: Check[] = [
|
||||
{
|
||||
title: 'Server persistent JSON migrations',
|
||||
command: ['bun', 'test', 'src/server/__tests__/persistence-upgrade.test.ts'],
|
||||
command: ['bun', 'test', './src/server/__tests__/persistence-upgrade.test.ts'],
|
||||
},
|
||||
{
|
||||
title: 'Desktop localStorage migrations',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user