feat(sounds): preserve alert mappings per sound pack #2819
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, dev ] | |
| pull_request: | |
| branches: [ main, dev ] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| defaults: | |
| run: | |
| shell: bash | |
| env: | |
| FORCE_COLOR: "1" | |
| jobs: | |
| validate: | |
| name: Validate (Ubuntu, Python ${{ matrix.python-version }}) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - python-version: "3.12" | |
| primary: true | |
| - python-version: "3.13" | |
| primary: false | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Restore pip cache | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.cache/pip | |
| key: pip-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('requirements-dev.txt', 'pyproject.toml') }} | |
| restore-keys: | | |
| pip-${{ runner.os }}-py${{ matrix.python-version }}- | |
| pip-${{ runner.os }}- | |
| - name: Install system dependencies (wxPython runtime + xvfb for headless) | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| libgtk-3-dev \ | |
| libnotify-dev \ | |
| libsdl2-dev \ | |
| libwebkit2gtk-4.1-dev \ | |
| freeglut3-dev \ | |
| xvfb | |
| - name: Ruff (format check + lint) | |
| if: matrix.primary | |
| # Collapsed from two astral-sh/ruff-action invocations into one | |
| # inline step. Each action invocation carried ~5s of composite- | |
| # action boot overhead; running both commands in a single step | |
| # against a pip-installed ruff (tiny wheel, cached by the | |
| # explicit pip cache) saves ~10s per CI run while keeping | |
| # the same fail-before-install-deps ordering. | |
| run: | | |
| pip install --quiet 'ruff>=0.9.0' | |
| ruff format --check . | |
| ruff check . | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| # Unit tests use in-repo stubs for wx and sound_lib when | |
| # those GUI/audio runtime packages are unavailable. | |
| # Keep CI validation independent from extras.wxpython.org wheel availability; | |
| # packaging workflows still install real wxPython for build artifacts. | |
| python - <<'PY' | |
| from pathlib import Path | |
| runtime_stubs = ("wxpython", "sound_lib @") | |
| filters = { | |
| "requirements.txt": Path("/tmp/requirements-ci.txt"), | |
| "requirements-dev.txt": Path("/tmp/requirements-dev-ci.txt"), | |
| } | |
| for source, target in filters.items(): | |
| kept = [] | |
| for line in Path(source).read_text(encoding="utf-8").splitlines(): | |
| stripped = line.strip().lower() | |
| if stripped == "-r requirements.txt" or stripped.startswith(runtime_stubs): | |
| continue | |
| kept.append(line) | |
| target.write_text("\n".join(kept) + "\n", encoding="utf-8") | |
| PY | |
| pip install -r /tmp/requirements-ci.txt | |
| pip install -r /tmp/requirements-dev-ci.txt | |
| pip install --no-deps -e . | |
| - name: Check CHANGELOG entry | |
| if: matrix.primary | |
| run: | | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| BASE="origin/${{ github.base_ref }}" | |
| git fetch --no-tags --depth=1 origin "${{ github.base_ref }}:${BASE}" || true | |
| echo "PR detected - checking changelog entries against $BASE" | |
| elif [ "${{ github.event_name }}" = "push" ] && [ -n "${{ github.event.before }}" ] && [ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]; then | |
| BASE="${{ github.event.before }}" | |
| git fetch --no-tags origin "$BASE" || true | |
| echo "Push detected - checking changelog entries against $BASE" | |
| else | |
| BASE="HEAD~1" | |
| echo "Manual run detected - checking changelog entries against $BASE" | |
| fi | |
| if ! python scripts/changelog_tools.py check --base "$BASE" --head HEAD; then | |
| { | |
| echo "### CHANGELOG gate" | |
| echo | |
| echo "User-facing changes need a curated \`CHANGELOG.md\` entry under \`## [Unreleased]\`." | |
| echo "This keeps nightly and stable release notes based on product wording instead of PR titles." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| exit 1 | |
| fi | |
| - name: Run tests (headless via xvfb) | |
| timeout-minutes: 15 | |
| env: | |
| PYTHONPATH: src | |
| ACCESSIWEATHER_TEST_MODE: "1" | |
| HYPOTHESIS_PROFILE: ci | |
| run: | | |
| if [ "${{ matrix.primary }}" = "true" ]; then | |
| mkdir -p reports | |
| xvfb-run -a pytest tests/ \ | |
| --ignore=tests/test_alert_dialog_copy_integration.py \ | |
| --ignore=tests/test_alert_dialog_dispatch.py \ | |
| -n 8 --dist=loadgroup -v --tb=short -m "not integration" \ | |
| --cov=src/accessiweather \ | |
| --cov-report= | |
| xvfb-run -a pytest \ | |
| tests/test_alert_dialog_copy_integration.py \ | |
| tests/test_alert_dialog_dispatch.py \ | |
| -n 0 -v --tb=short -m "not integration" \ | |
| --cov=src/accessiweather \ | |
| --cov-append \ | |
| --cov-report=xml:reports/coverage.xml \ | |
| --cov-report=term-missing | |
| else | |
| xvfb-run -a pytest tests/ \ | |
| --ignore=tests/test_alert_dialog_copy_integration.py \ | |
| --ignore=tests/test_alert_dialog_dispatch.py \ | |
| -n 8 --dist=loadgroup -v --tb=short -m "not integration" | |
| xvfb-run -a pytest \ | |
| tests/test_alert_dialog_copy_integration.py \ | |
| tests/test_alert_dialog_dispatch.py \ | |
| -n 0 -v --tb=short -m "not integration" | |
| fi | |
| - name: Install diff-cover | |
| if: matrix.primary && github.event_name == 'pull_request' | |
| run: pip install diff-cover>=9.0.0 | |
| - name: Coverage gate | |
| if: matrix.primary && github.event_name == 'pull_request' | |
| env: | |
| HEAD_REF: ${{ github.head_ref }} | |
| run: | | |
| if [[ "$HEAD_REF" == refactor/* ]]; then | |
| echo "Refactor PR branch detected; skipping diff coverage gate for mechanical code movement." | |
| exit 0 | |
| fi | |
| BASE="origin/${{ github.base_ref }}" | |
| CHANGED_SRC=$(git diff --name-only "$BASE"..HEAD | grep '^src/' || true) | |
| NON_EXCLUDED_CHANGED=$(echo "$CHANGED_SRC" | grep -Ev '^src/accessiweather/ui/|^src/accessiweather/notifications/toast_notifier.py$|^src/weather_gov_api_client/' || true) | |
| if [ -z "$NON_EXCLUDED_CHANGED" ]; then | |
| echo "No non-excluded src/ changes to gate. Skipping diff coverage gate." | |
| exit 0 | |
| fi | |
| diff-cover reports/coverage.xml \ | |
| --compare-branch="$BASE" \ | |
| --fail-under=80 \ | |
| --diff-range-notation='..' \ | |
| --exclude '*/accessiweather/ui/*' | |
| echo "New code meets coverage threshold." |