Skip to content

Commit f2c4c06

Browse files
onimshaclaude
andcommitted
Add GitHub workflows for CI and PR coverage analysis
Workflow 1: CI (.github/workflows/ci.yml) - Runs on pushes to main and all pull requests - Tests on Python 3.11 and 3.12 - Executes all pre-commit hooks (ruff, mypy, formatting) - Runs full test suite with coverage reporting - Uploads coverage to Codecov Workflow 2: PR Coverage Check (.github/workflows/pr-coverage.yml) - Runs on pull requests to analyze coverage impact - Generates detailed coverage reports with file-by-file breakdown - Comments on PR with coverage summary and changed files analysis - Enforces minimum 60% coverage threshold (configurable) - Uploads coverage artifacts for detailed review - Shows coverage thresholds: 90% target, 80% good, 60% minimum Features: - Uses modern uv package manager for fast dependency installation - Matrix testing across Python versions - Automated PR comments with coverage insights - Artifact uploads for detailed coverage analysis - Configurable coverage thresholds with clear status indicators Updated .gitignore to exclude coverage artifacts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent beddeb3 commit f2c4c06

File tree

3 files changed

+208
-0
lines changed

3 files changed

+208
-0
lines changed

.github/workflows/ci.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ["3.11", "3.12"]
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Install uv
20+
uses: astral-sh/setup-uv@v3
21+
with:
22+
version: "latest"
23+
24+
- name: Set up Python ${{ matrix.python-version }}
25+
run: uv python install ${{ matrix.python-version }}
26+
27+
- name: Install dependencies
28+
run: |
29+
uv sync --dev
30+
31+
- name: Run pre-commit hooks
32+
run: |
33+
uv run pre-commit run --all-files
34+
35+
- name: Run tests
36+
run: |
37+
uv run pytest tests/ -v
38+
39+
- name: Generate coverage report
40+
run: |
41+
uv run pytest --cov=src --cov-report=xml --cov-report=term-missing tests/
42+
43+
- name: Upload coverage to Codecov
44+
uses: codecov/codecov-action@v4
45+
with:
46+
file: ./coverage.xml
47+
flags: unittests
48+
name: codecov-umbrella
49+
fail_ci_if_error: false

.github/workflows/pr-coverage.yml

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
name: PR Coverage Check
2+
3+
on:
4+
pull_request:
5+
branches: [ main ]
6+
7+
jobs:
8+
coverage-check:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: read
12+
pull-requests: write
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Install uv
18+
uses: astral-sh/setup-uv@v3
19+
with:
20+
version: "latest"
21+
22+
- name: Set up Python
23+
run: uv python install 3.11
24+
25+
- name: Install dependencies
26+
run: uv sync --dev
27+
28+
- name: Run tests with coverage
29+
run: |
30+
uv run pytest --cov=src --cov-report=xml --cov-report=html --cov-report=term-missing tests/ > coverage_output.txt 2>&1
31+
32+
- name: Extract coverage summary
33+
run: |
34+
# Extract the coverage table from pytest output
35+
grep -A 50 "Name.*Stmts.*Miss.*Cover.*Missing" coverage_output.txt > coverage_summary.txt || true
36+
37+
# Extract total coverage percentage
38+
TOTAL_COVERAGE=$(grep "TOTAL" coverage_output.txt | awk '{print $4}' | sed 's/%//' || echo "0")
39+
echo "TOTAL_COVERAGE=$TOTAL_COVERAGE" >> $GITHUB_ENV
40+
41+
echo "## Coverage Summary" > coverage_comment.md
42+
echo "\`\`\`" >> coverage_comment.md
43+
cat coverage_summary.txt >> coverage_comment.md
44+
echo "\`\`\`" >> coverage_comment.md
45+
46+
- name: Get changed files
47+
id: changed-files
48+
run: |
49+
# Get list of changed Python files in src/
50+
git fetch origin main
51+
CHANGED_FILES=$(git diff --name-only origin/main...HEAD | grep -E '^src/.*\.py$' | tr '\n' ' ')
52+
echo "changed_files=$CHANGED_FILES" >> $GITHUB_OUTPUT
53+
echo "Changed Python files: $CHANGED_FILES"
54+
55+
- name: Comment PR with coverage
56+
uses: actions/github-script@v7
57+
with:
58+
script: |
59+
const fs = require('fs');
60+
const changedFiles = '${{ steps.changed-files.outputs.changed_files }}';
61+
const totalCoverage = '${{ env.TOTAL_COVERAGE }}';
62+
63+
let coverageSummary = '';
64+
try {
65+
coverageSummary = fs.readFileSync('coverage_comment.md', 'utf8');
66+
} catch (error) {
67+
coverageSummary = 'Coverage summary not available';
68+
}
69+
70+
let status = '✅';
71+
let message = 'Good coverage!';
72+
73+
if (parseFloat(totalCoverage) < 60) {
74+
status = '❌';
75+
message = `Coverage ${totalCoverage}% is below minimum threshold (60%)`;
76+
} else if (parseFloat(totalCoverage) < 80) {
77+
status = '⚠️';
78+
message = `Coverage ${totalCoverage}% could be improved (target: 90%)`;
79+
} else if (parseFloat(totalCoverage) < 90) {
80+
status = '👍';
81+
message = `Coverage ${totalCoverage}% is good (target: 90%)`;
82+
}
83+
84+
const comment = `## ${status} Coverage Report
85+
86+
**Overall Coverage: ${totalCoverage}%**
87+
88+
${message}
89+
90+
${coverageSummary}
91+
92+
### Changed Files in this PR:
93+
${changedFiles ? changedFiles.split(' ').map(f => `- \`${f}\``).join('\n') : '_No Python source files changed_'}
94+
95+
---
96+
97+
📋 **Coverage Thresholds:**
98+
- 🎯 **Target**: 90% overall coverage
99+
- 👍 **Good**: 80%+ overall coverage
100+
- ⚠️ **Warning**: 60-80% overall coverage
101+
- ❌ **Failure**: <60% overall coverage
102+
103+
📊 [View detailed coverage report in job artifacts](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
104+
105+
_Generated by GitHub Actions_`;
106+
107+
// Find and update existing comment or create new one
108+
const { data: comments } = await github.rest.issues.listComments({
109+
owner: context.repo.owner,
110+
repo: context.repo.repo,
111+
issue_number: context.issue.number,
112+
});
113+
114+
const existingComment = comments.find(comment =>
115+
comment.body.includes('Coverage Report') && comment.user.type === 'Bot'
116+
);
117+
118+
if (existingComment) {
119+
await github.rest.issues.updateComment({
120+
owner: context.repo.owner,
121+
repo: context.repo.repo,
122+
comment_id: existingComment.id,
123+
body: comment
124+
});
125+
} else {
126+
await github.rest.issues.createComment({
127+
owner: context.repo.owner,
128+
repo: context.repo.repo,
129+
issue_number: context.issue.number,
130+
body: comment
131+
});
132+
}
133+
134+
- name: Upload coverage artifacts
135+
uses: actions/upload-artifact@v4
136+
if: always()
137+
with:
138+
name: coverage-report
139+
path: |
140+
coverage.xml
141+
htmlcov/
142+
coverage_output.txt
143+
coverage_summary.txt
144+
145+
- name: Check minimum coverage threshold
146+
run: |
147+
COVERAGE=${{ env.TOTAL_COVERAGE }}
148+
MIN_THRESHOLD=60
149+
150+
if (( $(echo "$COVERAGE < $MIN_THRESHOLD" | bc -l) )); then
151+
echo "❌ Coverage $COVERAGE% is below minimum threshold of $MIN_THRESHOLD%"
152+
echo "Please add tests to improve coverage before merging."
153+
exit 1
154+
else
155+
echo "✅ Coverage $COVERAGE% meets minimum threshold of $MIN_THRESHOLD%"
156+
fi

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ wheels/
88

99
# Coverage
1010
.coverage
11+
htmlcov/
12+
coverage.xml
13+
coverage_*.txt
1114

1215
# Virtual environments
1316
.venv

0 commit comments

Comments
 (0)