Feature: Add CI workflow for linting, testing, and security scanning #5
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |