Skip to content

Feature: Add CI workflow for linting, testing, and security scanning #5

Feature: Add CI workflow for linting, testing, and security scanning

Feature: Add CI workflow for linting, testing, and security scanning #5

Workflow file for this run

name: CI
on:
push:
branches: [ main ]
pull_request: {}
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
build-test:
name: Lint & Test (Python)
runs-on: ubuntu-latest
timeout-minutes: 25
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install system deps
run: |
sudo apt-get update
sudo apt-get install -y libgl1 libglib2.0-0
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install ruff pytest pytest-cov
- name: Ruff Lint
run: ruff check .
- name: Ruff Format Check
run: ruff format --check .
- name: Run Core Tests (exclude GUI directory)
env:
PYTHONWARNINGS: default
run: |
pytest -q --ignore=tests/gui --cov=src --cov-report=xml:coverage-core.xml --cov-report=term
cp .coverage .coverage.core || true
- name: Upload core coverage XML
uses: actions/upload-artifact@v4
if: success() || failure()
with:
name: coverage-core-xml
path: coverage-core.xml
- name: Upload core coverage data
uses: actions/upload-artifact@v4
if: success() || failure()
with:
name: coverage-core-data
path: .coverage.core
- name: Upload pytest cache & reports
uses: actions/upload-artifact@v4
if: success() || failure()
with:
name: pytest-artifacts
path: |
.pytest_cache
./**/pytest-*.log
gui-tests:
name: GUI Tests (Qt)
runs-on: ubuntu-latest
needs: build-test
continue-on-error: true # temporary while stabilizing GUI tests
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install system GUI deps
run: |
sudo apt-get update
sudo apt-get install -y xvfb libegl1 libgl1 libxkbcommon-x11-0 libxcb-xinerama0 libglib2.0-0
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest
- name: Run GUI tests under Xvfb (coverage)
env:
QT_QPA_PLATFORM: offscreen
run: |
xvfb-run -a pytest -q tests/gui --cov=src --cov-report=xml:coverage-gui.xml --cov-append
cp .coverage .coverage.gui || true
- name: Upload GUI coverage XML
if: success() || failure()
uses: actions/upload-artifact@v4
with:
name: coverage-gui-xml
path: coverage-gui.xml
- name: Upload GUI coverage data
if: success() || failure()
uses: actions/upload-artifact@v4
with:
name: coverage-gui-data
path: .coverage.gui
- name: Upload GUI pytest cache
if: success() || failure()
uses: actions/upload-artifact@v4
with:
name: gui-pytest-cache
path: .pytest_cache
security:
name: Basic Security Scan
runs-on: ubuntu-latest
needs: [build-test]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install deps (no extras)
run: |
python -m pip install --upgrade pip
pip install pip-audit -r requirements.txt || true
- name: pip-audit (non-failing)
continue-on-error: true
run: |
pip install pip-audit
pip-audit -r requirements.txt -f json > pip-audit.json || true
- name: Upload pip-audit report
uses: actions/upload-artifact@v4
with:
name: pip-audit-report
path: pip-audit.json
summary:
name: PR Summary
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
needs: [build-test, gui-tests, security]
permissions:
contents: read
pull-requests: write
steps:
- name: Download coverage artifacts
uses: actions/download-artifact@v4
with:
path: coverage-artifacts
- name: Combine coverage (if both present)
run: |
pip install coverage
ls -R coverage-artifacts || true
# Prepare coverage data files
if [ -f coverage-artifacts/coverage-core-data/.coverage.core ]; then cp coverage-artifacts/coverage-core-data/.coverage.core .; fi
if [ -f coverage-artifacts/coverage-gui-data/.coverage.gui ]; then cp coverage-artifacts/coverage-gui-data/.coverage.gui .; fi
# Combine if at least one present
if ls .coverage.* 1>/dev/null 2>&1; then coverage combine .coverage.core .coverage.gui || true; fi
if [ -f .coverage ]; then coverage xml -o coverage-combined.xml || true; fi
if [ -f coverage-combined.xml ]; then echo "Combined coverage generated"; fi
echo '### Coverage Summary' >> $GITHUB_STEP_SUMMARY
if [ -f coverage-combined.xml ]; then
TOTAL=$(grep -o 'line-rate="[0-9.]*"' coverage-combined.xml | head -1 | cut -d '"' -f2)
echo "* Combined line-rate: ${TOTAL}" >> $GITHUB_STEP_SUMMARY
else
echo "* Combined coverage not available" >> $GITHUB_STEP_SUMMARY
fi
- name: Generate Summary
run: |
echo '### CI Results' >> $GITHUB_STEP_SUMMARY
echo '* Build/Test: ${{ needs.build-test.result }}' >> $GITHUB_STEP_SUMMARY
echo '* Security: ${{ needs.security.result }}' >> $GITHUB_STEP_SUMMARY