mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-19 22:09:37 +00:00
fix: remove CI script tests, restore E2E coverage on PRs
- Remove coverage-slack-notify.test.ts (not needed for CI scripts) - Un-export internal functions in coverage-slack-notify.ts - Add pull_request trigger back to ci-tests-e2e-coverage.yaml so E2E coverage appears in PR report comments (runs in parallel, does not block other checks) - Restore E2E coverage section in pr-report.yaml and unified-report.ts
This commit is contained in:
3
.github/workflows/ci-tests-e2e-coverage.yaml
vendored
3
.github/workflows/ci-tests-e2e-coverage.yaml
vendored
@@ -4,6 +4,9 @@ on:
|
||||
push:
|
||||
branches: [main, core/*]
|
||||
paths-ignore: ['**/*.md']
|
||||
pull_request:
|
||||
branches-ignore: [wip/*, draft/*, temp/*]
|
||||
paths-ignore: ['**/*.md']
|
||||
|
||||
concurrency:
|
||||
group: e2e-coverage-${{ github.ref }}
|
||||
|
||||
22
.github/workflows/pr-report.yaml
vendored
22
.github/workflows/pr-report.yaml
vendored
@@ -2,7 +2,7 @@ name: 'PR: Unified Report'
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['CI: Size Data', 'CI: Performance Report']
|
||||
workflows: ['CI: Size Data', 'CI: Performance Report', 'CI: E2E Coverage']
|
||||
types:
|
||||
- completed
|
||||
|
||||
@@ -104,6 +104,25 @@ jobs:
|
||||
path: temp/size-prev
|
||||
if_no_artifact_found: warn
|
||||
|
||||
- name: Find coverage workflow run
|
||||
if: steps.pr-meta.outputs.skip != 'true'
|
||||
id: find-coverage
|
||||
uses: ./.github/actions/find-workflow-run
|
||||
with:
|
||||
workflow-id: ci-tests-e2e-coverage.yaml
|
||||
head-sha: ${{ steps.pr-meta.outputs.head-sha }}
|
||||
not-found-status: skip
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Download coverage data
|
||||
if: steps.pr-meta.outputs.skip != 'true' && steps.find-coverage.outputs.status == 'ready'
|
||||
uses: dawidd6/action-download-artifact@0bd50d53a6d7fb5cb921e607957e9cc12b4ce392 # v12
|
||||
with:
|
||||
name: e2e-coverage
|
||||
run_id: ${{ steps.find-coverage.outputs.run-id }}
|
||||
path: temp/coverage
|
||||
if_no_artifact_found: warn
|
||||
|
||||
- name: Download perf metrics (current)
|
||||
if: steps.pr-meta.outputs.skip != 'true' && steps.find-perf.outputs.status == 'ready'
|
||||
uses: dawidd6/action-download-artifact@0bd50d53a6d7fb5cb921e607957e9cc12b4ce392 # v12
|
||||
@@ -142,6 +161,7 @@ jobs:
|
||||
pnpm exec tsx scripts/unified-report.ts
|
||||
--size-status=${{ steps.find-size.outputs.status }}
|
||||
--perf-status=${{ steps.find-perf.outputs.status }}
|
||||
--coverage-status=${{ steps.find-coverage.outputs.status }}
|
||||
> pr-report.md
|
||||
|
||||
- name: Remove legacy separate comments
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { CoverageData } from './coverage-slack-notify'
|
||||
import {
|
||||
buildMilestoneBlock,
|
||||
crossedMilestone,
|
||||
formatCoverageRow,
|
||||
formatDelta,
|
||||
formatPct,
|
||||
parseArgs,
|
||||
parseLcovContent,
|
||||
progressBar
|
||||
} from './coverage-slack-notify'
|
||||
|
||||
describe('parseLcovContent', () => {
|
||||
it('parses valid LCOV content', () => {
|
||||
const content = ['LF:100', 'LH:75', 'end_of_record'].join('\n')
|
||||
const result = parseLcovContent(content)
|
||||
expect(result).toEqual({
|
||||
percentage: 75,
|
||||
totalLines: 100,
|
||||
coveredLines: 75
|
||||
})
|
||||
})
|
||||
|
||||
it('sums lines across multiple records', () => {
|
||||
const content = [
|
||||
'LF:100',
|
||||
'LH:50',
|
||||
'end_of_record',
|
||||
'LF:200',
|
||||
'LH:150',
|
||||
'end_of_record'
|
||||
].join('\n')
|
||||
const result = parseLcovContent(content)
|
||||
expect(result).toEqual({
|
||||
percentage: (200 / 300) * 100,
|
||||
totalLines: 300,
|
||||
coveredLines: 200
|
||||
})
|
||||
})
|
||||
|
||||
it('returns null for empty content', () => {
|
||||
expect(parseLcovContent('')).toBeNull()
|
||||
})
|
||||
|
||||
it('returns null when total lines is zero', () => {
|
||||
const content = 'LH:10\nend_of_record'
|
||||
expect(parseLcovContent(content)).toBeNull()
|
||||
})
|
||||
|
||||
it('treats malformed numeric values as zero', () => {
|
||||
const content = ['LF:abc', 'LH:10', 'LF:50', 'end_of_record'].join('\n')
|
||||
const result = parseLcovContent(content)
|
||||
expect(result).toEqual({
|
||||
percentage: 20,
|
||||
totalLines: 50,
|
||||
coveredLines: 10
|
||||
})
|
||||
})
|
||||
|
||||
it('ignores unrelated lines', () => {
|
||||
const content = [
|
||||
'TN:',
|
||||
'SF:/some/file.ts',
|
||||
'LF:200',
|
||||
'LH:100',
|
||||
'end_of_record'
|
||||
].join('\n')
|
||||
const result = parseLcovContent(content)
|
||||
expect(result).toEqual({
|
||||
percentage: 50,
|
||||
totalLines: 200,
|
||||
coveredLines: 100
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('progressBar', () => {
|
||||
it('renders empty bar at 0%', () => {
|
||||
expect(progressBar(0)).toBe('░'.repeat(20))
|
||||
})
|
||||
|
||||
it('renders half-filled bar at 50%', () => {
|
||||
const bar = progressBar(50)
|
||||
expect(bar).toBe('█'.repeat(10) + '░'.repeat(10))
|
||||
})
|
||||
|
||||
it('renders full bar at 100%', () => {
|
||||
expect(progressBar(100)).toBe('█'.repeat(20))
|
||||
})
|
||||
|
||||
it('clamps negative values to 0%', () => {
|
||||
expect(progressBar(-10)).toBe('░'.repeat(20))
|
||||
})
|
||||
|
||||
it('clamps values above 100% to 100%', () => {
|
||||
expect(progressBar(150)).toBe('█'.repeat(20))
|
||||
})
|
||||
|
||||
it('has consistent length of 20 characters', () => {
|
||||
for (const pct of [0, 25, 50, 75, 100]) {
|
||||
expect(progressBar(pct)).toHaveLength(20)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatPct', () => {
|
||||
it('formats integer percentage', () => {
|
||||
expect(formatPct(50)).toBe('50.0%')
|
||||
})
|
||||
|
||||
it('formats decimal percentage', () => {
|
||||
expect(formatPct(12.34)).toBe('12.3%')
|
||||
})
|
||||
|
||||
it('formats zero', () => {
|
||||
expect(formatPct(0)).toBe('0.0%')
|
||||
})
|
||||
|
||||
it('formats 100', () => {
|
||||
expect(formatPct(100)).toBe('100.0%')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatDelta', () => {
|
||||
it('formats positive delta with + sign', () => {
|
||||
expect(formatDelta(1.2)).toBe('+1.2%')
|
||||
})
|
||||
|
||||
it('formats negative delta with - sign', () => {
|
||||
expect(formatDelta(-0.5)).toBe('-0.5%')
|
||||
})
|
||||
|
||||
it('formats zero delta with + sign', () => {
|
||||
expect(formatDelta(0)).toBe('+0.0%')
|
||||
})
|
||||
|
||||
it('formats large positive delta', () => {
|
||||
expect(formatDelta(15.67)).toBe('+15.7%')
|
||||
})
|
||||
})
|
||||
|
||||
describe('crossedMilestone', () => {
|
||||
it('detects crossing a 5% boundary', () => {
|
||||
expect(crossedMilestone(23, 27)).toBe(25)
|
||||
})
|
||||
|
||||
it('returns null when no boundary is crossed', () => {
|
||||
expect(crossedMilestone(21, 24)).toBeNull()
|
||||
})
|
||||
|
||||
it('detects exact boundary crossing', () => {
|
||||
expect(crossedMilestone(24.9, 25)).toBe(25)
|
||||
})
|
||||
|
||||
it('returns highest milestone when crossing multiple boundaries', () => {
|
||||
expect(crossedMilestone(24, 31)).toBe(30)
|
||||
})
|
||||
|
||||
it('returns null when values are equal', () => {
|
||||
expect(crossedMilestone(25, 25)).toBeNull()
|
||||
})
|
||||
|
||||
it('returns null when coverage decreases', () => {
|
||||
expect(crossedMilestone(30, 28)).toBeNull()
|
||||
})
|
||||
|
||||
it('detects crossing from 0', () => {
|
||||
expect(crossedMilestone(0, 7)).toBe(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildMilestoneBlock', () => {
|
||||
it('builds celebration block below target', () => {
|
||||
const block = buildMilestoneBlock('Unit test', 50)
|
||||
expect(block.type).toBe('section')
|
||||
expect(block.text.type).toBe('mrkdwn')
|
||||
expect(block.text.text).toContain('MILESTONE: Unit test coverage hit 50%')
|
||||
expect(block.text.text).toContain('30 percentage points to go')
|
||||
})
|
||||
|
||||
it('builds goal-reached block at target', () => {
|
||||
const block = buildMilestoneBlock('Unit test', 80)
|
||||
expect(block.text.text).toContain('GOAL REACHED')
|
||||
expect(block.text.text).toContain('80%')
|
||||
expect(block.text.text).toContain('✅')
|
||||
})
|
||||
|
||||
it('builds goal-reached block above target', () => {
|
||||
const block = buildMilestoneBlock('E2E test', 85)
|
||||
expect(block.text.text).toContain('GOAL REACHED')
|
||||
expect(block.text.text).toContain('85%')
|
||||
})
|
||||
|
||||
it('uses singular "point" when 1 remaining', () => {
|
||||
const block = buildMilestoneBlock('Unit test', 79)
|
||||
expect(block.text.text).toContain('1 percentage point to go')
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseArgs', () => {
|
||||
it('parses all args', () => {
|
||||
const argv = [
|
||||
'--pr-url=https://github.com/org/repo/pull/42',
|
||||
'--pr-number=42',
|
||||
'--author=testuser'
|
||||
]
|
||||
expect(parseArgs(argv)).toEqual({
|
||||
prUrl: 'https://github.com/org/repo/pull/42',
|
||||
prNumber: '42',
|
||||
author: 'testuser'
|
||||
})
|
||||
})
|
||||
|
||||
it('returns empty strings for missing args', () => {
|
||||
expect(parseArgs([])).toEqual({
|
||||
prUrl: '',
|
||||
prNumber: '',
|
||||
author: ''
|
||||
})
|
||||
})
|
||||
|
||||
it('handles args with empty values', () => {
|
||||
const argv = ['--pr-url=', '--pr-number=', '--author=']
|
||||
expect(parseArgs(argv)).toEqual({
|
||||
prUrl: '',
|
||||
prNumber: '',
|
||||
author: ''
|
||||
})
|
||||
})
|
||||
|
||||
it('ignores unknown args', () => {
|
||||
const argv = ['--unknown=value', '--pr-number=99']
|
||||
expect(parseArgs(argv)).toEqual({
|
||||
prUrl: '',
|
||||
prNumber: '99',
|
||||
author: ''
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatCoverageRow', () => {
|
||||
it('formats a coverage comparison row', () => {
|
||||
const current: CoverageData = {
|
||||
percentage: 55,
|
||||
totalLines: 1000,
|
||||
coveredLines: 550
|
||||
}
|
||||
const baseline: CoverageData = {
|
||||
percentage: 50,
|
||||
totalLines: 1000,
|
||||
coveredLines: 500
|
||||
}
|
||||
const row = formatCoverageRow('Unit', current, baseline)
|
||||
expect(row).toBe('*Unit:* 50.0% → 55.0% (+5.0%)')
|
||||
})
|
||||
|
||||
it('formats a row with negative delta', () => {
|
||||
const current: CoverageData = {
|
||||
percentage: 48,
|
||||
totalLines: 1000,
|
||||
coveredLines: 480
|
||||
}
|
||||
const baseline: CoverageData = {
|
||||
percentage: 50,
|
||||
totalLines: 1000,
|
||||
coveredLines: 500
|
||||
}
|
||||
const row = formatCoverageRow('E2E', current, baseline)
|
||||
expect(row).toBe('*E2E:* 50.0% → 48.0% (-2.0%)')
|
||||
})
|
||||
})
|
||||
@@ -4,7 +4,7 @@ const TARGET = 80
|
||||
const MILESTONE_STEP = 5
|
||||
const BAR_WIDTH = 20
|
||||
|
||||
export interface CoverageData {
|
||||
interface CoverageData {
|
||||
percentage: number
|
||||
totalLines: number
|
||||
coveredLines: number
|
||||
@@ -18,7 +18,7 @@ interface SlackBlock {
|
||||
}
|
||||
}
|
||||
|
||||
export function parseLcovContent(content: string): CoverageData | null {
|
||||
function parseLcovContent(content: string): CoverageData | null {
|
||||
let totalLines = 0
|
||||
let coveredLines = 0
|
||||
|
||||
@@ -44,23 +44,23 @@ function parseLcov(filePath: string): CoverageData | null {
|
||||
return parseLcovContent(readFileSync(filePath, 'utf-8'))
|
||||
}
|
||||
|
||||
export function progressBar(percentage: number): string {
|
||||
function progressBar(percentage: number): string {
|
||||
const clamped = Math.max(0, Math.min(100, percentage))
|
||||
const filled = Math.round((clamped / 100) * BAR_WIDTH)
|
||||
const empty = BAR_WIDTH - filled
|
||||
return '█'.repeat(filled) + '░'.repeat(empty)
|
||||
}
|
||||
|
||||
export function formatPct(value: number): string {
|
||||
function formatPct(value: number): string {
|
||||
return value.toFixed(1) + '%'
|
||||
}
|
||||
|
||||
export function formatDelta(delta: number): string {
|
||||
function formatDelta(delta: number): string {
|
||||
const sign = delta >= 0 ? '+' : ''
|
||||
return sign + delta.toFixed(1) + '%'
|
||||
}
|
||||
|
||||
export function crossedMilestone(prev: number, curr: number): number | null {
|
||||
function crossedMilestone(prev: number, curr: number): number | null {
|
||||
const prevBucket = Math.floor(prev / MILESTONE_STEP)
|
||||
const currBucket = Math.floor(curr / MILESTONE_STEP)
|
||||
|
||||
@@ -70,10 +70,7 @@ export function crossedMilestone(prev: number, curr: number): number | null {
|
||||
return null
|
||||
}
|
||||
|
||||
export function buildMilestoneBlock(
|
||||
label: string,
|
||||
milestone: number
|
||||
): SlackBlock {
|
||||
function buildMilestoneBlock(label: string, milestone: number): SlackBlock {
|
||||
if (milestone >= TARGET) {
|
||||
return {
|
||||
type: 'section',
|
||||
@@ -102,7 +99,7 @@ export function buildMilestoneBlock(
|
||||
}
|
||||
}
|
||||
|
||||
export function parseArgs(argv: string[]): {
|
||||
function parseArgs(argv: string[]): {
|
||||
prUrl: string
|
||||
prNumber: string
|
||||
author: string
|
||||
@@ -121,7 +118,7 @@ export function parseArgs(argv: string[]): {
|
||||
return { prUrl, prNumber, author }
|
||||
}
|
||||
|
||||
export function formatCoverageRow(
|
||||
function formatCoverageRow(
|
||||
label: string,
|
||||
current: CoverageData,
|
||||
baseline: CoverageData
|
||||
@@ -213,6 +210,4 @@ function main() {
|
||||
process.stdout.write(JSON.stringify(payload))
|
||||
}
|
||||
|
||||
if (process.argv[1] === import.meta.filename) {
|
||||
main()
|
||||
}
|
||||
main()
|
||||
|
||||
@@ -11,6 +11,8 @@ function getArg(name: string): string | undefined {
|
||||
|
||||
const sizeStatus = getArg('size-status') ?? 'pending'
|
||||
const perfStatus = getArg('perf-status') ?? 'pending'
|
||||
const coverageStatus = getArg('coverage-status') ?? 'skip'
|
||||
|
||||
const lines: string[] = []
|
||||
|
||||
if (sizeStatus === 'ready') {
|
||||
@@ -66,4 +68,33 @@ if (perfStatus === 'ready' && existsSync('test-results/perf-metrics.json')) {
|
||||
lines.push('> ⏳ Performance tests in progress…')
|
||||
}
|
||||
|
||||
if (coverageStatus === 'ready' && existsSync('temp/coverage/coverage.lcov')) {
|
||||
try {
|
||||
const coverageReport = execFileSync(
|
||||
'pnpm',
|
||||
[
|
||||
'exec',
|
||||
'tsx',
|
||||
'scripts/coverage-report.ts',
|
||||
'temp/coverage/coverage.lcov'
|
||||
],
|
||||
{ encoding: 'utf-8' }
|
||||
).trimEnd()
|
||||
lines.push('')
|
||||
lines.push(coverageReport)
|
||||
} catch {
|
||||
lines.push('')
|
||||
lines.push('## 🔬 E2E Coverage')
|
||||
lines.push('')
|
||||
lines.push(
|
||||
'> ⚠️ Failed to render coverage report. Check the CI workflow logs.'
|
||||
)
|
||||
}
|
||||
} else if (coverageStatus === 'failed') {
|
||||
lines.push('')
|
||||
lines.push('## 🔬 E2E Coverage')
|
||||
lines.push('')
|
||||
lines.push('> ⚠️ Coverage collection failed. Check the CI workflow logs.')
|
||||
}
|
||||
|
||||
process.stdout.write(lines.join('\n') + '\n')
|
||||
|
||||
Reference in New Issue
Block a user