Skip to content
Open
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
86 changes: 86 additions & 0 deletions .github/scripts/python_coverage_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""Render a Cobertura-style coverage.xml (as produced by `coverage xml` /
pytest-cov's --cov-report=xml) as Markdown suitable for a GitHub Actions job
summary.

Usage:
python3 python_coverage_summary.py <path-to-coverage.xml>

Prints to stdout; the caller is expected to redirect into $GITHUB_STEP_SUMMARY.
Deliberately has no third-party dependencies so it can run with the stock
`python3` already available on GitHub-hosted runners -- no extra permissions
or installs are needed, which keeps it safe to run on pull requests from
forks.
"""

import sys
import xml.etree.ElementTree as ET


def _pct(rate_attr):
try:
return float(rate_attr) * 100
except (TypeError, ValueError):
return None


def _fmt_pct(value):
return "N/A" if value is None else f"{value:.1f}%"


def main(argv):
if len(argv) != 2:
print("Usage: python_coverage_summary.py <coverage.xml>", file=sys.stderr)
return 2

path = argv[1]

try:
root = ET.parse(path).getroot()
except (OSError, ET.ParseError) as exc:
print("## Python test coverage")
print()
print(f"No coverage report found at `{path}` ({exc}).")
return 0

line_rate = _pct(root.get("line-rate"))
branch_rate = _pct(root.get("branch-rate"))
lines_covered = root.get("lines-covered", "?")
lines_valid = root.get("lines-valid", "?")

print("## Python test coverage")
print()
print(
f"**Overall line coverage: {_fmt_pct(line_rate)}** "
f"({lines_covered}/{lines_valid} lines) &nbsp;|&nbsp; "
f"branch coverage: {_fmt_pct(branch_rate)}"
)
print()
print("<details><summary>Per-file coverage</summary>")
print()
print("| File | Line coverage | Lines covered |")
print("| --- | --- | --- |")

classes = sorted(root.iter("class"), key=lambda c: c.get("filename", ""))
for cls in classes:
filename = cls.get("filename", "?")
file_line_rate = _pct(cls.get("line-rate"))

total = covered = 0
lines_elem = cls.find("lines")
if lines_elem is not None:
for line in lines_elem.findall("line"):
total += 1
if int(line.get("hits", "0")) > 0:
covered += 1

print(f"| `{filename}` | {_fmt_pct(file_line_rate)} | {covered}/{total} |")

print()
print("</details>")

return 0


if __name__ == "__main__":
raise SystemExit(main(sys.argv))
66 changes: 66 additions & 0 deletions .github/workflows/build-wheel.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Build Python Wheel

on:
workflow_call:
inputs:
ref:
description: Git ref to check out
required: false
type: string
default: ""

jobs:
build-wheel:
name: Build Python wheel
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 0
ref: ${{ inputs.ref || github.ref }}

- name: Set up Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6

- name: Make Gradle wrapper executable
run: chmod +x ./gradlew

- name: Build and test
run: ./gradlew clean build buildPythonWheel

- name: Publish Python coverage summary
if: always()
run: |
python3 .github/scripts/python_coverage_summary.py \
regi-headless/build/reports/coverage/coverage.xml \
>> "$GITHUB_STEP_SUMMARY"

- name: Upload Python coverage report
if: always()
uses: actions/upload-artifact@v4
with:
name: python-coverage-html
path: regi-headless/build/reports/coverage/html
if-no-files-found: warn
retention-days: 14

- name: Collect wheel
run: |
mkdir -p dist
find . -path "*/build/install/*/dist/*.whl" -exec cp {} dist/ \;

- name: Upload Python wheel artifact
uses: actions/upload-artifact@v4
with:
name: python-wheel
path: dist/*.whl
if-no-files-found: error
retention-days: 14
31 changes: 7 additions & 24 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,18 @@ on:
permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
build-wheel:
name: Build and test
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Set up Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6

- name: Make Gradle wrapper executable
run: chmod +x ./gradlew

- name: Build and test
run: ./gradlew clean build
uses: ./.github/workflows/build-wheel.yml

dependency-submission:
name: Submit Gradle dependencies
needs: build
needs: build-wheel
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Release

on:
release:
types:
- published

permissions:
contents: read
actions: read

concurrency:
group: release-${{ github.event.release.id }}
cancel-in-progress: false

jobs:
build-wheel:
name: Build wheel from release tag
uses: ./.github/workflows/build-wheel.yml
with:
ref: ${{ github.event.release.tag_name }}

publish-wheel:
name: Publish Python wheel to GitHub Release
needs: build-wheel
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Download Python wheel artifact
uses: actions/download-artifact@v4
with:
name: python-wheel
path: dist

- name: Generate checksums
run: |
cd dist
sha256sum *.whl > SHA256SUMS.txt

- name: Publish wheel to GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release upload "${{ github.event.release.tag_name }}" dist/*.whl dist/SHA256SUMS.txt --clobber
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
*.iml
.DS_STORE
*/build/
.coverage*
16 changes: 13 additions & 3 deletions regi-headless/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ tasks.register('installPythonBuildTools', VenvTask) {
description = 'Installs Python packages needed to build and test the wheel.'

venvExec = 'pip'
args = ['install', '--upgrade', 'pip', 'build', 'pytest']
args = ['install', '--upgrade', 'pip', 'build', 'pytest', 'pytest-cov']

outputs.file(layout.buildDirectory.file("python-build-tools/install.marker"))

Expand Down Expand Up @@ -152,15 +152,25 @@ tasks.register('installPythonWheelForSmokeTest', VenvTask) {
}
tasks.register('testPythonWheel', VenvTask) {
group = 'verification'
description = 'Runs pytest against the installed Python wheel.'
description = 'Runs pytest (with coverage) against the installed Python wheel.'

dependsOn installPythonWheelForSmokeTest

def coverageDir = layout.buildDirectory.dir('reports/coverage')

venvExec = 'python'
args = ['-m', 'pytest', 'src/test/python']
args = [
'-m', 'pytest', 'src/test/python',
'--cov=regi_python',
'--cov-branch',
'--cov-report=term-missing',
"--cov-report=xml:${coverageDir.get().file('coverage.xml').asFile}",
"--cov-report=html:${coverageDir.get().dir('html').asFile}",
]

inputs.files(fileTree(dir: 'src/test/python', include: '**/*.py'))
outputs.upToDateWhen { false }
outputs.dir(coverageDir)
}

check {
Expand Down
Loading