Skip to content

Commit e6ba8b4

Browse files
committed
feat: add code coverage reporting
Signed-off-by: jbrinkman <[email protected]>
1 parent 1b706ed commit e6ba8b4

File tree

3 files changed

+297
-0
lines changed

3 files changed

+297
-0
lines changed

.github/workflows/test-coverage.yml

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
name: Test Coverage
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
- release-*
8+
- v*
9+
10+
jobs:
11+
test-coverage:
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v4
17+
with:
18+
submodules: recursive
19+
20+
- name: Setup .NET
21+
uses: actions/setup-dotnet@v4
22+
with:
23+
dotnet-version: |
24+
6.0.x
25+
8.0.x
26+
27+
- name: Install Task
28+
run: |
29+
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
30+
31+
- name: Build and run coverage
32+
run: task coverage
33+
34+
- name: Display coverage summary and set outputs
35+
id: coverage
36+
run: |
37+
task coverage:summary
38+
# Set outputs for GitHub Actions using same JSON parsing approach
39+
if [ -f "reports/Summary.json" ]; then
40+
SUMMARY=$(cat reports/Summary.json)
41+
LINE_COVERAGE=$(echo "$SUMMARY" | jq -r '.summary.linecoverage')
42+
BRANCH_COVERAGE=$(echo "$SUMMARY" | jq -r '.summary.branchcoverage')
43+
echo "line-coverage=${LINE_COVERAGE}" >> $GITHUB_OUTPUT
44+
echo "branch-coverage=${BRANCH_COVERAGE}" >> $GITHUB_OUTPUT
45+
else
46+
echo "line-coverage=0" >> $GITHUB_OUTPUT
47+
echo "branch-coverage=0" >> $GITHUB_OUTPUT
48+
fi
49+
50+
- name: Upload coverage report artifact
51+
uses: actions/upload-artifact@v4
52+
with:
53+
name: coverage-report
54+
path: reports/
55+
retention-days: 30
56+
57+
- name: Upload test results artifact
58+
uses: actions/upload-artifact@v4
59+
with:
60+
name: test-results
61+
path: testresults/
62+
retention-days: 30
63+
64+
- name: Comment coverage on PR
65+
if: github.event_name == 'pull_request'
66+
uses: actions/github-script@v7
67+
with:
68+
script: |
69+
const fs = require('fs');
70+
71+
let coverageComment = '## 📊 Test Coverage Report\n\n';
72+
73+
try {
74+
const summaryPath = 'reports/Summary.json';
75+
if (fs.existsSync(summaryPath)) {
76+
const lineCoverage = '${{ steps.coverage.outputs.line-coverage }}' || '0';
77+
const branchCoverage = '${{ steps.coverage.outputs.branch-coverage }}' || '0';
78+
79+
coverageComment += `| Metric | Coverage |\n`;
80+
coverageComment += `|--------|----------|\n`;
81+
coverageComment += `| **Line Coverage** | ${lineCoverage}% |\n`;
82+
coverageComment += `| **Branch Coverage** | ${branchCoverage}% |\n\n`;
83+
84+
// Add coverage badge indicators
85+
const lineCoverageNum = parseFloat(lineCoverage);
86+
const branchCoverageNum = parseFloat(branchCoverage);
87+
88+
if (lineCoverageNum >= 80) {
89+
coverageComment += `🟢 **Good line coverage** (${lineCoverage}%)\n`;
90+
} else if (lineCoverageNum >= 60) {
91+
coverageComment += `🟡 **Moderate line coverage** (${lineCoverage}%)\n`;
92+
} else {
93+
coverageComment += `🔴 **Low line coverage** (${lineCoverage}%)\n`;
94+
}
95+
96+
if (branchCoverageNum >= 70) {
97+
coverageComment += `🟢 **Good branch coverage** (${branchCoverage}%)\n`;
98+
} else if (branchCoverageNum >= 50) {
99+
coverageComment += `🟡 **Moderate branch coverage** (${branchCoverage}%)\n`;
100+
} else {
101+
coverageComment += `🔴 **Low branch coverage** (${branchCoverage}%)\n`;
102+
}
103+
} else {
104+
coverageComment += '❌ Coverage report not found\n';
105+
}
106+
} catch (error) {
107+
coverageComment += `❌ Error reading coverage report: ${error.message}\n`;
108+
}
109+
110+
coverageComment += '\n📁 **Coverage report artifact** has been uploaded and will be available for download.';
111+
112+
// Find existing coverage comment
113+
const comments = await github.rest.issues.listComments({
114+
owner: context.repo.owner,
115+
repo: context.repo.repo,
116+
issue_number: context.issue.number,
117+
});
118+
119+
const existingComment = comments.data.find(comment =>
120+
comment.user.login === 'github-actions[bot]' &&
121+
comment.body.includes('📊 Test Coverage Report')
122+
);
123+
124+
if (existingComment) {
125+
// Update existing comment
126+
await github.rest.issues.updateComment({
127+
owner: context.repo.owner,
128+
repo: context.repo.repo,
129+
comment_id: existingComment.id,
130+
body: coverageComment
131+
});
132+
} else {
133+
// Create new comment
134+
await github.rest.issues.createComment({
135+
owner: context.repo.owner,
136+
repo: context.repo.repo,
137+
issue_number: context.issue.number,
138+
body: coverageComment
139+
});
140+
}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ x64/
3131
[Tt]est[Rr]esult*/
3232
[Bb]uild[Ll]og.*
3333

34+
# Code Coverage Reports
35+
[Rr]eports/
36+
3437
*_i.c
3538
*_p.c
3639
*_i.h

Taskfile.yml

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
version: '3'
2+
3+
vars:
4+
TEST_RESULTS_DIR: testresults
5+
REPORTS_DIR: reports
6+
COVERAGE_FILE: "{{.TEST_RESULTS_DIR}}/coverage.cobertura.xml"
7+
UNIT_TEST_PROJECT: tests/Valkey.Glide.UnitTests/Valkey.Glide.UnitTests.csproj
8+
INTEGRATION_TEST_PROJECT: tests/Valkey.Glide.IntegrationTests/Valkey.Glide.IntegrationTests.csproj
9+
10+
tasks:
11+
clean:
12+
desc: Clean test results and reports directories
13+
cmds:
14+
- rm -rf {{.TEST_RESULTS_DIR}}
15+
- rm -rf {{.REPORTS_DIR}}
16+
- mkdir -p {{.TEST_RESULTS_DIR}}
17+
- mkdir -p {{.REPORTS_DIR}}
18+
19+
install-tools:
20+
desc: Install required tools for coverage reporting
21+
cmds:
22+
- dotnet tool install --global dotnet-reportgenerator-globaltool --version 5.4.12
23+
status:
24+
- which reportgenerator
25+
26+
test:unit:
27+
desc: Run unit tests only
28+
deps: [clean]
29+
cmds:
30+
- dotnet test {{.UNIT_TEST_PROJECT}} --configuration Release --no-build --verbosity normal
31+
32+
test:integration:
33+
desc: Run integration tests only
34+
deps: [clean]
35+
cmds:
36+
- dotnet test {{.INTEGRATION_TEST_PROJECT}} --configuration Release --no-build --verbosity normal
37+
38+
test:coverage:unit:
39+
desc: Run unit tests with coverage collection
40+
deps: [clean]
41+
cmds:
42+
- dotnet test {{.UNIT_TEST_PROJECT}}
43+
--configuration Release
44+
--collect:"XPlat Code Coverage"
45+
--results-directory {{.TEST_RESULTS_DIR}}
46+
--logger trx
47+
--logger "console;verbosity=detailed"
48+
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura
49+
50+
test:coverage:integration:
51+
desc: Run integration tests with coverage collection
52+
deps: [clean]
53+
cmds:
54+
- dotnet test {{.INTEGRATION_TEST_PROJECT}}
55+
--configuration Release
56+
--collect:"XPlat Code Coverage"
57+
--results-directory {{.TEST_RESULTS_DIR}}
58+
--logger trx
59+
--logger "console;verbosity=detailed"
60+
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura
61+
62+
test:coverage:all:
63+
desc: Run all tests with coverage collection
64+
deps: [clean]
65+
cmds:
66+
- dotnet test
67+
--configuration Release
68+
--collect:"XPlat Code Coverage"
69+
--results-directory {{.TEST_RESULTS_DIR}}
70+
--logger trx
71+
--logger "console;verbosity=detailed"
72+
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura
73+
74+
coverage:merge:
75+
desc: Merge coverage files if multiple exist
76+
cmds:
77+
- |
78+
COVERAGE_FILES=$(find {{.TEST_RESULTS_DIR}} -name "coverage.cobertura.xml" -type f)
79+
if [ $(echo "$COVERAGE_FILES" | wc -l) -gt 1 ]; then
80+
echo "Multiple coverage files found, merging..."
81+
reportgenerator \
82+
-reports:"{{.TEST_RESULTS_DIR}}/**/coverage.cobertura.xml" \
83+
-targetdir:{{.TEST_RESULTS_DIR}} \
84+
-reporttypes:Cobertura \
85+
-assemblyfilters:"+Valkey.Glide*"
86+
mv {{.TEST_RESULTS_DIR}}/Cobertura.xml {{.COVERAGE_FILE}}
87+
else
88+
echo "Single coverage file found, copying..."
89+
cp $(echo "$COVERAGE_FILES" | head -1) {{.COVERAGE_FILE}}
90+
fi
91+
92+
coverage:report:
93+
desc: Generate HTML coverage report from collected data
94+
deps: [install-tools, coverage:merge]
95+
cmds:
96+
- reportgenerator
97+
-reports:{{.COVERAGE_FILE}}
98+
-targetdir:{{.REPORTS_DIR}}
99+
-reporttypes:"Html;JsonSummary"
100+
-assemblyfilters:"+Valkey.Glide*"
101+
-classfilters:"-*.Tests*"
102+
- echo "Coverage report generated in {{.REPORTS_DIR}}/index.html"
103+
104+
coverage:summary:
105+
desc: Display coverage summary from the report
106+
cmds:
107+
- |
108+
if [ -f "{{.REPORTS_DIR}}/Summary.json" ]; then
109+
echo "=== Coverage Summary ==="
110+
LINE_COVERAGE=$(cat {{.REPORTS_DIR}}/Summary.json | jq -r '.summary.linecoverage')
111+
BRANCH_COVERAGE=$(cat {{.REPORTS_DIR}}/Summary.json | jq -r '.summary.branchcoverage')
112+
echo "Line Coverage: ${LINE_COVERAGE}%"
113+
echo "Branch Coverage: ${BRANCH_COVERAGE}%"
114+
else
115+
echo "No coverage summary found. Run 'task coverage:report' first."
116+
fi
117+
118+
coverage:
119+
desc: Run all tests with coverage and generate HTML report
120+
cmds:
121+
- task: test:coverage:all
122+
- task: coverage:report
123+
- task: coverage:summary
124+
125+
coverage:unit:
126+
desc: Run unit tests with coverage and generate HTML report
127+
cmds:
128+
- task: test:coverage:unit
129+
- task: coverage:report
130+
- task: coverage:summary
131+
132+
coverage:integration:
133+
desc: Run integration tests with coverage and generate HTML report
134+
cmds:
135+
- task: test:coverage:integration
136+
- task: coverage:report
137+
- task: coverage:summary
138+
139+
build:
140+
desc: Build the solution
141+
cmds:
142+
- dotnet build --configuration Release
143+
144+
test:
145+
desc: Build and run all tests
146+
deps: [build]
147+
cmds:
148+
- task: test:coverage:all
149+
150+
default:
151+
desc: Default task - build and run coverage
152+
cmds:
153+
- task: build
154+
- task: coverage

0 commit comments

Comments
 (0)