Skip to content

Commit e0c5d68

Browse files
PHKennyclaude
andcommitted
Improve CI workflow: separate test results and coverage reporting
- Run tests with --coverage in lint-dart job - Upload coverage artifact for reuse - Create separate coverage-comment job that downloads artifact - Remove duplicate test execution (runs only once now) - Use lcov for reliable coverage parsing - Post coverage as separate PR comment Benefits: - Tests run once instead of twice (faster) - Clean separation: test results in check run, coverage in comment - Follows artifact pattern from other projects Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 234ff6b commit e0c5d68

1 file changed

Lines changed: 58 additions & 57 deletions

File tree

.github/workflows/checks.yaml

Lines changed: 58 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
run: flutter analyze
3030

3131
- name: Run tests
32-
run: flutter test --machine > test-results.json
32+
run: flutter test --machine --coverage > test-results.json
3333

3434
- name: Publish test results
3535
uses: dorny/test-reporter@v1
@@ -39,92 +39,93 @@ jobs:
3939
path: test-results.json
4040
reporter: flutter-json
4141

42-
- name: Parse test results
42+
- name: Upload coverage
43+
uses: actions/upload-artifact@v4
4344
if: always()
44-
id: test_summary
45+
with:
46+
name: flutter-coverage
47+
path: coverage/lcov.info
48+
retention-days: 1
49+
50+
coverage-comment:
51+
runs-on: ubuntu-latest
52+
needs: lint-dart
53+
if: always() && github.event_name == 'pull_request'
54+
55+
steps:
56+
- name: Download coverage
57+
uses: actions/download-artifact@v4
58+
with:
59+
name: flutter-coverage
60+
path: coverage
61+
continue-on-error: true
62+
63+
- name: Parse coverage
64+
id: coverage
4565
run: |
46-
if [ -f test-results.json ]; then
47-
# Count total tests and failures from the JSON output
48-
TOTAL=$(grep -c '"type":"testStart"' test-results.json 2>/dev/null || echo 0)
49-
FAILED=$(grep -c '"result":"error"' test-results.json 2>/dev/null || echo 0)
50-
51-
# Ensure variables are numeric (strip whitespace and default to 0)
52-
TOTAL=${TOTAL:-0}
53-
FAILED=${FAILED:-0}
54-
PASSED=$((TOTAL - FAILED))
55-
56-
# Calculate percentage
57-
if [ "$TOTAL" -gt 0 ] 2>/dev/null; then
58-
PASS_RATE=$(awk "BEGIN {printf \"%.1f\", ($PASSED/$TOTAL)*100}")
59-
else
60-
PASS_RATE="0.0"
61-
fi
62-
63-
echo "total=$TOTAL" >> $GITHUB_OUTPUT
64-
echo "passed=$PASSED" >> $GITHUB_OUTPUT
65-
echo "failed=$FAILED" >> $GITHUB_OUTPUT
66-
echo "pass_rate=$PASS_RATE" >> $GITHUB_OUTPUT
66+
if [ -f coverage/lcov.info ]; then
67+
# Use lcov to get summary (more reliable than grep)
68+
COVERAGE_SUMMARY=$(lcov --summary coverage/lcov.info 2>&1)
69+
70+
# Extract line coverage percentage
71+
COVERAGE_PCT=$(echo "$COVERAGE_SUMMARY" | grep -oP 'lines\.*: \K[\d.]+(?=%)')
72+
COVERAGE_PCT=${COVERAGE_PCT:-0.0}
73+
74+
# Extract hit/total counts
75+
LINES_HIT=$(grep -o 'DA:' coverage/lcov.info | wc -l)
76+
LINES_TOTAL=$(grep -o 'LF:' coverage/lcov.info | wc -l)
77+
78+
echo "coverage_pct=$COVERAGE_PCT" >> $GITHUB_OUTPUT
79+
echo "lines_hit=$LINES_HIT" >> $GITHUB_OUTPUT
80+
echo "lines_total=$LINES_TOTAL" >> $GITHUB_OUTPUT
6781
else
68-
echo "total=0" >> $GITHUB_OUTPUT
69-
echo "passed=0" >> $GITHUB_OUTPUT
70-
echo "failed=0" >> $GITHUB_OUTPUT
71-
echo "pass_rate=0.0" >> $GITHUB_OUTPUT
82+
echo "coverage_pct=0.0" >> $GITHUB_OUTPUT
83+
echo "lines_hit=0" >> $GITHUB_OUTPUT
84+
echo "lines_total=0" >> $GITHUB_OUTPUT
7285
fi
7386
74-
- name: Comment test results
75-
if: always()
87+
- name: Post coverage comment
7688
uses: actions/github-script@v7
7789
with:
7890
script: |
79-
const total = '${{ steps.test_summary.outputs.total }}';
80-
const passed = '${{ steps.test_summary.outputs.passed }}';
81-
const failed = '${{ steps.test_summary.outputs.failed }}';
82-
const passRate = '${{ steps.test_summary.outputs.pass_rate }}';
91+
const coveragePct = '${{ steps.coverage.outputs.coverage_pct }}';
92+
const linesHit = '${{ steps.coverage.outputs.lines_hit }}';
93+
const linesTotal = '${{ steps.coverage.outputs.lines_total }}';
94+
const linesMissing = parseInt(linesTotal) - parseInt(linesHit);
8395
84-
const emoji = failed === '0' ? '✅' : '❌';
85-
const status = failed === '0' ? 'All tests passed!' : `${failed} test(s) failed`;
86-
87-
const body = `## ${emoji} Test Results
88-
89-
**Status:** ${status}
96+
const body = `## 📊 Coverage Report
9097
9198
| Metric | Value |
9299
|--------|-------|
93-
| Total Tests | ${total} |
94-
| Passed | ✅ ${passed} |
95-
| Failed | ❌ ${failed} |
96-
| Pass Rate | ${passRate}% |
97-
98-
${failed !== '0' ? '⚠️ Please check the **Unit Tests** check run for detailed failure information.' : ''}
100+
| **Coverage** | **${coveragePct}%** |
101+
| Lines covered | ${linesHit} / ${linesTotal} |
102+
| Lines missing | ${linesMissing} |
99103
100-
<sub>🤖 Automated test results from [checks workflow](${context.payload.repository.html_url}/actions/runs/${context.runId})</sub>`;
104+
<sub>🤖 Coverage report from [checks workflow](${context.payload.repository.html_url}/actions/runs/${context.runId})</sub>`;
101105
102106
// Find existing comment
103-
const comments = await github.rest.issues.listComments({
107+
const { data: comments } = await github.rest.issues.listComments({
104108
owner: context.repo.owner,
105109
repo: context.repo.repo,
106110
issue_number: context.issue.number,
107111
});
108112
109-
const botComment = comments.data.find(comment =>
110-
comment.user.type === 'Bot' &&
111-
comment.body.includes('## ✅ Test Results') || comment.body.includes('## ❌ Test Results')
113+
const existing = comments.find(c =>
114+
c.user.type === 'Bot' && c.body.includes('## 📊 Coverage Report')
112115
);
113116
114-
if (botComment) {
115-
// Update existing comment
117+
if (existing) {
116118
await github.rest.issues.updateComment({
117119
owner: context.repo.owner,
118120
repo: context.repo.repo,
119-
comment_id: botComment.id,
120-
body: body
121+
comment_id: existing.id,
122+
body,
121123
});
122124
} else {
123-
// Create new comment
124125
await github.rest.issues.createComment({
125126
owner: context.repo.owner,
126127
repo: context.repo.repo,
127128
issue_number: context.issue.number,
128-
body: body
129+
body,
129130
});
130131
}

0 commit comments

Comments
 (0)