CI / lint (pull_request) Successful in 44s
CI / test (3.12) (pull_request) Successful in 1m1s
CI / test (3.13) (pull_request) Successful in 1m4s
CI / test (3.14) (pull_request) Successful in 1m7s
CI / test-with-beamline-plugins (pxi_bec) (pull_request) Successful in 1m10s
CI / test-with-beamline-plugins (pxii_bec) (pull_request) Successful in 1m9s
CI / test-with-beamline-plugins (pxiii_bec) (pull_request) Successful in 1m24s
CI / test-with-coverage (pull_request) Successful in 1m36s
CI / coverage-analysis (pull_request) Successful in 3s
CI / lint (push) Successful in 34s
Docs build and publish / docker (push) Successful in 17s
Build and Publish / release (push) Successful in 16s
CI / test (3.12) (push) Successful in 1m5s
CI / test (3.13) (push) Successful in 1m3s
CI / test-with-beamline-plugins (pxi_bec) (push) Successful in 1m12s
CI / test (3.14) (push) Successful in 1m22s
CI / test-with-beamline-plugins (pxiii_bec) (push) Successful in 1m22s
CI / test-with-beamline-plugins (pxii_bec) (push) Successful in 1m22s
CI / test-with-coverage (push) Successful in 1m33s
CI / coverage-analysis (push) Skipped
254 lines
8.3 KiB
YAML
254 lines
8.3 KiB
YAML
name: CI
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
pull_request:
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
lint:
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: https://github.com/actions/checkout@v5
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Setup
|
|
uses: ./.gitea/actions/install
|
|
with:
|
|
python_version: "3.12"
|
|
index_username: ${{ secrets.GITEA_USER }}
|
|
index_password: ${{ secrets.GITEA_TOKEN }}
|
|
|
|
- name: Format
|
|
run: |
|
|
source .venv/bin/activate
|
|
ruff format --check
|
|
|
|
- name: Lint
|
|
run: |
|
|
source .venv/bin/activate
|
|
ruff check
|
|
|
|
- name: Checkout pyright diff plugin
|
|
uses: https://github.com/actions/checkout@v5
|
|
with:
|
|
repository: mx/diff_quality_basedpyright
|
|
path: diff_quality_basedpyright
|
|
|
|
- name: Typecheck Diff
|
|
run: |
|
|
source .venv/bin/activate
|
|
uv pip install -e diff_quality_basedpyright
|
|
diff-quality --violations=basedpyright --fail-under=100
|
|
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
matrix:
|
|
python-version: ["3.12", "3.13", "3.14"]
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: https://github.com/actions/checkout@v5
|
|
|
|
- name: Setup
|
|
uses: ./.gitea/actions/install
|
|
with:
|
|
python_version: ${{ matrix.python-version }}
|
|
index_username: ${{ secrets.GITEA_USER }}
|
|
index_password: ${{ secrets.GITEA_TOKEN }}
|
|
|
|
- name: Run Pytest
|
|
env:
|
|
QT_QPA_PLATFORM: offscreen
|
|
BEAMLINE: SIMULATED
|
|
run: |
|
|
source .venv/bin/activate
|
|
# -vv: one line per test, so a hung job shows the exact test it
|
|
# died in, not just a file of dots. -s: no output capture — prints
|
|
# and logs stream live instead of vanishing with a wedged worker
|
|
# (a pxiii_bec job once sat silent at 81% for hours).
|
|
pytest -s -vv ./tests/unit
|
|
|
|
test-with-beamline-plugins:
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
matrix:
|
|
plugin_repo: ["pxi_bec", "pxii_bec", "pxiii_bec"]
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: https://github.com/actions/checkout@v5
|
|
|
|
- name: Setup
|
|
uses: ./.gitea/actions/install
|
|
with:
|
|
python_version: "3.12"
|
|
index_username: ${{ secrets.GITEA_USER }}
|
|
index_password: ${{ secrets.GITEA_TOKEN }}
|
|
|
|
- name: Install beamline plugin
|
|
run: |
|
|
source .venv/bin/activate
|
|
git clone https://gitea.psi.ch/bec/mx_bec.git
|
|
git clone https://gitea.psi.ch/bec/${{ matrix.plugin_repo }}.git
|
|
uv pip install -e ./mx_bec
|
|
uv pip install -e ./${{ matrix.plugin_repo }}
|
|
|
|
- name: Run Pytest
|
|
env:
|
|
QT_QPA_PLATFORM: offscreen
|
|
BEAMLINE: SIMULATED
|
|
run: |
|
|
source .venv/bin/activate
|
|
# Same -s -vv rationale as the plain test job: live, per-test
|
|
# output so a plugin-variant hang is diagnosable from the log.
|
|
pytest -s -vv ./tests/unit
|
|
|
|
test-with-coverage:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
diff-coverage: ${{ steps.diff_cover.outputs.percent }}
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: https://github.com/actions/checkout@v5
|
|
with:
|
|
# diff-cover needs the base branch present to diff against
|
|
fetch-depth: 0
|
|
|
|
- name: Setup
|
|
uses: ./.gitea/actions/install
|
|
with:
|
|
python_version: "3.12"
|
|
index_username: ${{ secrets.GITEA_USER }}
|
|
index_password: ${{ secrets.GITEA_TOKEN }}
|
|
|
|
- name: Run Pytest with Coverage
|
|
env:
|
|
QT_QPA_PLATFORM: offscreen
|
|
BEAMLINE: SIMULATED
|
|
run: |
|
|
source .venv/bin/activate
|
|
pytest --cov=aare --cov-config=./pyproject.toml --cov-branch --cov-report=xml --no-cov-on-fail ./tests/unit
|
|
|
|
# No --fail-under here: the gate lives in coverage-analysis so that the PR
|
|
# comment is posted before the build is failed.
|
|
- name: Diff coverage
|
|
id: diff_cover
|
|
env:
|
|
BASE_BRANCH: ${{ github.base_ref || 'main' }}
|
|
run: |
|
|
source .venv/bin/activate
|
|
diff-cover coverage.xml \
|
|
--compare-branch="origin/${BASE_BRANCH}" \
|
|
--format "markdown:diff-cover.md,json:diff-cover.json"
|
|
percent=$(python -c "import json; print(json.load(open('diff-cover.json'))['total_percent_covered'])")
|
|
echo "percent=${percent}" >> "$GITHUB_OUTPUT"
|
|
echo "Diff coverage: ${percent}%"
|
|
|
|
# Two artefacts on purpose: the full report is too big to post. The F5 WAF
|
|
# in front of gitea.psi.ch rejects large POST bodies with an HTML 403, so
|
|
# the comment body is capped and the detail lives in the artifact.
|
|
- name: Build coverage summary
|
|
env:
|
|
BASE_BRANCH: ${{ github.base_ref || 'main' }}
|
|
DIFF_COVERAGE: ${{ steps.diff_cover.outputs.percent }}
|
|
COMMENT_DIFF_BUDGET: "6000"
|
|
run: |
|
|
source .venv/bin/activate
|
|
total=$(coverage report --format=total)
|
|
|
|
{
|
|
echo "# Coverage report"
|
|
echo
|
|
echo "| Metric | Value |"
|
|
echo "| --- | --- |"
|
|
echo "| Total line + branch coverage | ${total}% |"
|
|
echo "| Diff coverage vs \`${BASE_BRANCH}\` | ${DIFF_COVERAGE}% (minimum 80%) |"
|
|
echo
|
|
cat diff-cover.md
|
|
echo
|
|
echo "## Per-file breakdown"
|
|
echo
|
|
coverage report --format=markdown
|
|
} > coverage-summary.md
|
|
|
|
# Headline numbers only. The F5 WAF in front of gitea.psi.ch rejects
|
|
# the comment POST with an HTML 403 for anything sizeable -- an empty
|
|
# body posts, 6 kB does not, and the threshold between is unknown. The
|
|
# per-line and per-file detail lives in the coverage artifact instead.
|
|
# Kept free of markup and backticks as well, since that is untested
|
|
# independently of size.
|
|
{
|
|
echo "Coverage report (automated)"
|
|
echo
|
|
echo "Total line + branch coverage: ${total}%"
|
|
echo "Diff coverage vs ${BASE_BRANCH}: ${DIFF_COVERAGE}% (minimum 80%)"
|
|
echo
|
|
echo "Full report: coverage artifact on this run."
|
|
} > coverage-comment.md
|
|
|
|
# Only the rendered summary is uploaded. coverage.xml (~1 MB) is rejected
|
|
# with an HTML 403 by the proxy in front of gitea.psi.ch.
|
|
- name: Upload coverage
|
|
uses: https://github.com/actions/upload-artifact@v3
|
|
with:
|
|
name: coverage
|
|
path: |
|
|
coverage-summary.md
|
|
coverage-comment.md
|
|
if-no-files-found: error
|
|
|
|
coverage-analysis:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
issues: write
|
|
pull-requests: write
|
|
needs: ["test-with-coverage"]
|
|
if: github.event_name == 'pull_request'
|
|
steps:
|
|
- name: Download coverage
|
|
uses: https://github.com/actions/download-artifact@v3
|
|
with:
|
|
name: coverage
|
|
|
|
- name: Find Comment
|
|
uses: https://github.com/peter-evans/find-comment@v3
|
|
id: fc
|
|
with:
|
|
issue-number: ${{ github.event.pull_request.number }}
|
|
body-includes: "Coverage report (automated)"
|
|
|
|
- name: Create or update comment
|
|
uses: https://github.com/peter-evans/create-or-update-comment@v5
|
|
with:
|
|
comment-id: ${{ steps.fc.outputs.comment-id }}
|
|
issue-number: ${{ github.event.pull_request.number }}
|
|
body-path: coverage-comment.md
|
|
edit-mode: replace
|
|
|
|
# Last, so the report is always visible on the PR even when this fails.
|
|
- name: Enforce diff coverage
|
|
env:
|
|
DIFF_COVERAGE: ${{ needs.test-with-coverage.outputs.diff-coverage }}
|
|
MINIMUM: "80"
|
|
run: |
|
|
if [ -z "${DIFF_COVERAGE}" ]; then
|
|
echo "::error::test-with-coverage produced no diff coverage value"
|
|
exit 1
|
|
fi
|
|
echo "Diff coverage: ${DIFF_COVERAGE}% (minimum ${MINIMUM}%)"
|
|
if awk -v p="${DIFF_COVERAGE}" -v m="${MINIMUM}" 'BEGIN { exit !(p + 0 < m + 0) }'; then
|
|
echo "::error::Diff coverage ${DIFF_COVERAGE}% is below the required ${MINIMUM}%"
|
|
exit 1
|
|
fi
|