-
Notifications
You must be signed in to change notification settings - Fork 13
Add JaCoCo test coverage configuration and CI reporting #448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
f7d7cb1
e6b088e
9bda932
8d69558
f28132d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| name: Test Coverage | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [ master ] | ||
|
|
||
| permissions: | ||
| pull-requests: write | ||
| contents: read | ||
|
|
||
| jobs: | ||
| coverage: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout PR branch | ||
| uses: actions/checkout@v3 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Set up JDK 11 | ||
| uses: actions/setup-java@v3 | ||
| with: | ||
| java-version: '11' | ||
| distribution: 'temurin' | ||
|
|
||
| - name: Cache Gradle packages | ||
| uses: actions/cache@v3 | ||
| with: | ||
| path: | | ||
| ~/.gradle/caches | ||
| ~/.gradle/wrapper | ||
| key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | ||
| restore-keys: ${{ runner.os }}-gradle- | ||
|
|
||
| - name: Run tests and generate coverage report (PR branch) | ||
| run: ./gradlew test jacocoTestReport | ||
|
|
||
| - name: Save PR branch coverage report | ||
| run: cp build/reports/jacoco/test/jacocoTestReport.xml pr-coverage.xml | ||
|
|
||
| - name: Extract PR branch coverage percentage | ||
| id: pr_coverage | ||
| run: | | ||
| # Extract the report-level LINE counter (last occurrence in the XML) | ||
| line=$(grep -oP 'counter type="LINE" missed="\d+" covered="\d+"' pr-coverage.xml | tail -1) | ||
| missed=$(echo "$line" | grep -oP 'missed="\K\d+') | ||
| covered=$(echo "$line" | grep -oP 'covered="\K\d+') | ||
| total=$((missed + covered)) | ||
| if [ "$total" -gt 0 ]; then | ||
| pct=$(awk "BEGIN {printf \"%.2f\", ($covered/$total)*100}") | ||
| else | ||
| pct="0.00" | ||
| fi | ||
| echo "coverage=$pct" >> "$GITHUB_OUTPUT" | ||
| echo "PR branch line coverage: $pct% (missed=$missed, covered=$covered)" | ||
|
|
||
| - name: Checkout base branch | ||
| run: git checkout ${{ github.base_ref }} | ||
|
|
||
| - name: Run tests and generate coverage report (base branch) | ||
| run: | | ||
| # Base branch may not have JaCoCo configured yet; skip gracefully | ||
| ./gradlew clean test jacocoTestReport || true | ||
|
|
||
| - name: Extract base branch coverage percentage | ||
| id: base_coverage | ||
| run: | | ||
| if [ -f build/reports/jacoco/test/jacocoTestReport.xml ]; then | ||
| line=$(grep -oP 'counter type="LINE" missed="\d+" covered="\d+"' build/reports/jacoco/test/jacocoTestReport.xml | tail -1) | ||
| missed=$(echo "$line" | grep -oP 'missed="\K\d+') | ||
| covered=$(echo "$line" | grep -oP 'covered="\K\d+') | ||
| total=$((missed + covered)) | ||
| if [ "$total" -gt 0 ]; then | ||
| pct=$(awk "BEGIN {printf \"%.2f\", ($covered/$total)*100}") | ||
| else | ||
| pct="0.00" | ||
| fi | ||
| echo "coverage=$pct" >> "$GITHUB_OUTPUT" | ||
| echo "has_base=true" >> "$GITHUB_OUTPUT" | ||
| echo "Base branch line coverage: $pct% (missed=$missed, covered=$covered)" | ||
| else | ||
| echo "coverage=N/A" >> "$GITHUB_OUTPUT" | ||
| echo "has_base=false" >> "$GITHUB_OUTPUT" | ||
| echo "No JaCoCo report found on base branch" | ||
| fi | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
|
||
|
|
||
| - name: Checkout PR branch again | ||
| run: git checkout ${{ github.event.pull_request.head.sha }} | ||
|
|
||
| - name: Restore PR coverage report | ||
| run: | | ||
| mkdir -p build/reports/jacoco/test | ||
| cp pr-coverage.xml build/reports/jacoco/test/jacocoTestReport.xml | ||
|
|
||
| - name: Post coverage comparison comment | ||
| uses: actions/github-script@v6 | ||
| with: | ||
| script: | | ||
| const prCoverage = '${{ steps.pr_coverage.outputs.coverage }}'; | ||
| const baseCoverage = '${{ steps.base_coverage.outputs.coverage }}'; | ||
| const hasBase = '${{ steps.base_coverage.outputs.has_base }}' === 'true'; | ||
|
|
||
| let diff = ''; | ||
| let emoji = ''; | ||
| if (hasBase && baseCoverage !== 'N/A') { | ||
| const delta = (parseFloat(prCoverage) - parseFloat(baseCoverage)).toFixed(2); | ||
| if (delta > 0) { | ||
| emoji = '🟢'; | ||
| diff = `+${delta}%`; | ||
| } else if (delta < 0) { | ||
| emoji = '🔴'; | ||
| diff = `${delta}%`; | ||
| } else { | ||
| emoji = '⚪'; | ||
| diff = `±0.00%`; | ||
| } | ||
| } | ||
|
|
||
| let body = `## Test Coverage Report\n\n`; | ||
| body += `| Metric | Coverage |\n`; | ||
| body += `|--------|----------|\n`; | ||
| if (hasBase) { | ||
| body += `| **Base branch** (\`${{ github.base_ref }}\`) | ${baseCoverage}% |\n`; | ||
| } else { | ||
| body += `| **Base branch** (\`${{ github.base_ref }}\`) | _No JaCoCo configured_ |\n`; | ||
| } | ||
| body += `| **PR branch** (\`${{ github.head_ref }}\`) | ${prCoverage}% |\n`; | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Script injection via On line 127, Vulnerable interpolation pattern# Line 127 - github.head_ref injected into JS template literal
body += `| **PR branch** (\`${{ github.head_ref }}\`) | ${prCoverage}% |\n`;Should instead use: env:
HEAD_REF: ${{ github.head_ref }}
BASE_REF: ${{ github.base_ref }}
script: |
const headRef = process.env.HEAD_REF;
...
body += `| **PR branch** (\`${headRef}\`) | ${prCoverage}% |\n`;Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback. |
||
| if (hasBase && baseCoverage !== 'N/A') { | ||
| body += `| **Diff** | ${emoji} ${diff} |\n`; | ||
| } | ||
|
|
||
| // Find and update existing comment or create new one | ||
| const marker = '<!-- coverage-report -->'; | ||
| body = marker + '\n' + body; | ||
|
|
||
| const { data: comments } = await github.rest.issues.listComments({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number, | ||
| }); | ||
|
|
||
| const existing = comments.find(c => c.body.includes(marker)); | ||
| if (existing) { | ||
| await github.rest.issues.updateComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| comment_id: existing.id, | ||
| body, | ||
| }); | ||
| } else { | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number, | ||
| body, | ||
| }); | ||
| } | ||
|
|
||
| - name: Post per-file coverage details | ||
| uses: madrapps/jacoco-report@v1.6.1 | ||
| with: | ||
| paths: build/reports/jacoco/test/jacocoTestReport.xml | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
| min-coverage-overall: 34 | ||
| min-coverage-changed-files: 60 | ||
| title: "Per-File Coverage Details" | ||
| update-comment: true | ||
| pass-emoji: ':green_circle:' | ||
| fail-emoji: ':red_circle:' | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Shell injection via unquoted
github.base_refinrun:blockOn line 58,
${{ github.base_ref }}is interpolated directly into a shellrun:command without quotes:git checkout ${{ github.base_ref }}. While the workflow restricts to PRs targetingmaster(mitigating this somewhat), directly interpolating GitHub context values into shell commands is a security anti-pattern. If the workflow trigger was ever broadened to allow other base branches, a branch name containing shell metacharacters could execute arbitrary commands. The safe pattern is to use an environment variable:env: BASE_REF: ${{ github.base_ref }}and thengit checkout "$BASE_REF".Prompt for agents
Was this helpful? React with 👍 or 👎 to provide feedback.