Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions .github/workflows/analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
name: Code Analysis
on:
workflow_run:
workflows: ["Build and Test"]
types: [completed]

permissions:
checks: write
pull-requests: write
actions: read

jobs:
sonar:
name: SonarQube Analysis
runs-on: ubuntu-latest
if: >-
github.event.workflow_run.conclusion == 'success' &&
(github.event.workflow_run.event == 'push' || github.event.workflow_run.event == 'pull_request')
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
repository: ${{ github.event.workflow_run.head_repository.full_name }}
ref: ${{ github.event.workflow_run.head_sha }}
fetch-depth: 0

- name: Set up JDK 17
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654
with:
java-version: '17'
distribution: 'zulu'
cache: 'gradle'

- name: Cache SonarQube packages
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar

- name: Setup Gradle
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2

- name: Download coverage report
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: coverage-report
path: build/reports/kover
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Download test results
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: junit-test-results
path: build/test-results
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Resolve PR details
id: pr
if: github.event.workflow_run.event == 'pull_request'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# workflow_run.pull_requests can be empty for fork PRs, fall back to API search
PR_NUMBER="${{ github.event.workflow_run.pull_requests[0].number }}"
if [ -z "$PR_NUMBER" ]; then
PR_NUMBER=$(gh pr list --search "${{ github.event.workflow_run.head_sha }}" --state open --json number --jq '.[0].number // empty')
fi
if [ -z "$PR_NUMBER" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Could not resolve PR number, skipping PR-specific analysis"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
echo "branch=${{ github.event.workflow_run.head_branch }}" >> "$GITHUB_OUTPUT"
echo "base=${{ github.event.workflow_run.pull_requests[0].base.ref || 'main' }}" >> "$GITHUB_OUTPUT"
Comment thread
driessamyn marked this conversation as resolved.
fi

- name: Run SonarQube Analysis (PR)
if: github.event.workflow_run.event == 'pull_request' && steps.pr.outputs.skip != 'true'
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: >-
./gradlew sonar
-Dsonar.pullrequest.key=${{ steps.pr.outputs.number }}
-Dsonar.pullrequest.branch=${{ steps.pr.outputs.branch }}
-Dsonar.pullrequest.base=${{ steps.pr.outputs.base }}

- name: Run SonarQube Analysis (push)
if: github.event.workflow_run.event == 'push'
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./gradlew sonar

coverage-comment:
name: Coverage PR Comment
runs-on: ubuntu-latest
if: >-
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'pull_request'
steps:
- name: Download coverage report
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: coverage-report
path: build/reports/kover
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Resolve PR number
id: pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER="${{ github.event.workflow_run.pull_requests[0].number }}"
if [ -z "$PR_NUMBER" ]; then
PR_NUMBER=$(gh pr list --search "${{ github.event.workflow_run.head_sha }}" --state open --json number --jq '.[0].number // empty')
fi
if [ -n "$PR_NUMBER" ]; then
echo "number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
else
echo "Could not resolve PR number"
exit 1
fi

- name: Parse coverage and post comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
REPORT="build/reports/kover/report.xml"
if [ ! -f "$REPORT" ]; then
echo "Coverage report not found at $REPORT"
exit 1
fi

# Extract coverage counters from the Kover/JaCoCo XML report
# Parse the top-level <counter> elements for INSTRUCTION, BRANCH, and LINE
extract_coverage() {
local type=$1
local missed covered total pct
missed=$(grep -oP "<counter type=\"$type\" missed=\"\K[0-9]+" "$REPORT" | tail -1)
covered=$(grep -oP "<counter type=\"$type\" covered=\"\K[0-9]+" "$REPORT" | tail -1)
if [ -n "$missed" ] && [ -n "$covered" ]; then
total=$((missed + covered))
if [ "$total" -gt 0 ]; then
pct=$(( (covered * 10000) / total ))
echo "$(( pct / 100 )).$(printf '%02d' $(( pct % 100 )))%"
else
echo "N/A"
fi
else
echo "N/A"
fi
}

LINE_COV=$(extract_coverage "LINE")
BRANCH_COV=$(extract_coverage "BRANCH")
INSTRUCTION_COV=$(extract_coverage "INSTRUCTION")

BODY=$(cat <<EOF
## Code Coverage

| Metric | Coverage |
|--------|----------|
| Line | $LINE_COV |
| Branch | $BRANCH_COV |
| Instruction | $INSTRUCTION_COV |

*Updated for commit ${{ github.event.workflow_run.head_sha }}*
EOF
)

# Find existing coverage comment to update, or create a new one
PR_NUMBER=${{ steps.pr.outputs.number }}
EXISTING_COMMENT=$(gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
--jq '.[] | select(.body | startswith("## Code Coverage")) | .id' | head -1)

if [ -n "$EXISTING_COMMENT" ]; then
gh api "repos/${{ github.repository }}/issues/comments/${EXISTING_COMMENT}" \
-X PATCH -f body="$BODY"
else
gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
-X POST -f body="$BODY"
fi
20 changes: 1 addition & 19 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,11 @@ jobs:
distribution: 'zulu'
cache: 'gradle'

- name: Cache SonarQube packages
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar

- name: Setup Gradle
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2

- name: Build & Test with Gradle
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./gradlew check sonar
run: ./gradlew check

- name: Upload Test Report
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
Expand All @@ -69,15 +60,6 @@ jobs:
files: |
**/build/test-results/*/TEST-*.xml

- name: Add coverage report to PR
if: github.event_name == 'pull_request' || github.event_name == 'push'
id: kover
uses: mi-kas/kover-report@5f58465b6f395c8fa3adc2665e27250bad87de50
with:
path: |
build/reports/kover/report.xml
title: Code Coverage
update-comment: true
integration-test:
name: Integration tests
runs-on: ubuntu-latest
Expand Down
Loading