t38 found two holes in the drift gate itself. F2 — the job could never run where it mattered. Home Assistant 2026.10.1 declares ``Requires-Python >=3.14.2`` (PyPI metadata), so the old guard let a 3.13 runner through and the ``homeassistant>=2026.10`` install then failed with a resolver error. The job now provisions its own interpreter (``actions/setup-python@v5`` with ``python-version: "3.14"``) and the guard runs against the REAL floor, read from ``ha_api_drift.HOMEASSISTANT_PYTHON_FLOOR`` — one source of truth, pinned by a test against the installed distribution's own ``Requires-Python`` metadata. Verified: on 3.13.16 the guard refuses with "Home Assistant needs Python >= 3.14.2, but this job runs 3.13.16 / Fix: raise actions/setup-python…"; on 3.14.8 it reports "interpreter ok"; and ``uvx --python 3.14 --with 'homeassistant>=2026.10'`` resolves 2026.10.1. F1 — the standalone entry point was a false green. It printed counters and exited 0 even when it had walked nothing, which is exactly how the manual red-proof tool gets misused. The non-vacuity floors now live in ``ha_api_drift.FLOORS`` and are enforced in ONE place, ``Report.floor_violations()``, used by the pytest wrapper and by ``__main__`` (so the constants cannot drift apart; a test asserts the wrapper holds no floor-like constants of its own). On top of that the sweep can no longer lie in either direction: * an empty or mis-targeted directory reports ``FLOOR:`` lines and ``drift sweep FAILED`` and exits 1 (a non-existent path is a ``bad-path`` finding, not a traceback and not a silent zero-module sweep); * with no homeassistant installed it returns a single ``no-homeassistant`` finding with zero counters — never a wall of phantom "broken-import"s, never a green exit. Verified in the CI-equivalent environment (Python 3.14.8 + homeassistant 2026.10.1): HEAD exits 0 with "no Home-Assistant API drift (13 modules, 60 imports, 35 call arguments, 7 awaited HA calls)"; the empty directory exits 1 with 4 FLOOR lines and no findings; a missing path exits 1 with the ``bad-path`` finding; and the full pre-fix tree (``git archive 5ede4fe``) exits 1 with exactly the three t36 drifts (media_player.py:965 BrowseMediaSource, diagnostics.py:93 homeassistant.util.diagnostic, config_flow.py:425 missing-await) and 0 floor violations. The fast ``test`` job and ``release.yml`` are untouched; the workflow is unchanged in shape except the new provisioning + guard steps, and parses as valid YAML.
115 lines
5.1 KiB
YAML
115 lines
5.1 KiB
YAML
# Quality gate: lint + unit tests + the HA API-drift sweep.
|
|
#
|
|
# The test suite is stdlib-only (parsers, intents, running-apps) and runs the
|
|
# vendored ha_adb_common helpers without installing Home Assistant — see
|
|
# tests/conftest.py. androidtvremote2 is installed so the real-library
|
|
# volume/TYPED-dict tests execute (tests/test_protocol_transport.py). Real
|
|
# firmware captures live in tests/fixtures
|
|
# (how to capture more: docs/RESEARCH_SHIELD.md §8).
|
|
#
|
|
# The `ha-api-drift` job is the ONE place Home Assistant is installed: every
|
|
# other job validates our code against hand-written stubs, which stay green
|
|
# even when a call site no longer matches the real API. That is how three
|
|
# drifts reached master (t36 D1/D2/D3) — see tests/ha_api_drift.py.
|
|
|
|
name: Test
|
|
|
|
on:
|
|
push:
|
|
branches: [master]
|
|
pull_request:
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Install test tooling
|
|
run: |
|
|
set -euo pipefail
|
|
# Try the plain install first: it succeeds on images whose Python is
|
|
# not externally managed (and on pip < 23, which has no PEP 668
|
|
# enforcement at all). When the runner image DOES mark its Python as
|
|
# externally managed — Debian/Ubuntu based Gitea act images do — pip
|
|
# refuses with externally-managed-environment and we retry with the
|
|
# sanctioned override. That override is safe here specifically
|
|
# because the job runs in a disposable container, and because the
|
|
# flag only exists on pip >= 23.0.1, the same version that raises
|
|
# the refusal in the first place.
|
|
#
|
|
# Tools are pinned to the versions the suite was validated against so
|
|
# a newer ruff rule set cannot redden the gate on unrelated commits;
|
|
# androidtvremote2 tracks the manifest floor so CI exercises the same
|
|
# dependency users install.
|
|
PKGS="pytest~=9.1 pytest-asyncio~=1.4 ruff~=0.17.0 'androidtvremote2>=0.2.0'"
|
|
if ! eval python3 -m pip install --quiet $PKGS; then
|
|
echo "system Python is externally managed; retrying with --break-system-packages"
|
|
eval python3 -m pip install --quiet --break-system-packages $PKGS
|
|
fi
|
|
|
|
- name: Lint
|
|
run: ruff check custom_components tests
|
|
|
|
- name: Unit tests
|
|
run: python3 -m pytest tests -q
|
|
|
|
# Resolve every Home-Assistant API use in our production modules against a
|
|
# real install: imports, keyword arguments and await/sync mismatches. The
|
|
# sweep is repo-owned (tests/ha_api_drift.py) and skips cleanly when
|
|
# Home Assistant is absent, so it is inert in the job above and live here.
|
|
ha-api-drift:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Provision the interpreter Home Assistant needs
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
# Home Assistant 2026.10.1 declares Requires-Python >= 3.14.2 (PyPI
|
|
# metadata; tests/ha_api_drift.py holds our copy of that number and the
|
|
# drift suite pins it against the installed distribution), so the
|
|
# runner's own python3 cannot install the version this gate must run
|
|
# against. This resolves the newest 3.14.x patch; the guard below
|
|
# refuses anything still short of the floor rather than letting the
|
|
# install fail halfway through.
|
|
python-version: "3.14"
|
|
|
|
- name: Check the interpreter satisfies Home Assistant's floor
|
|
run: |
|
|
set -euo pipefail
|
|
# The floor comes from tests/ha_api_drift.py — one source of truth for
|
|
# this guard and the metadata pin in tests/test_ha_api_drift.py.
|
|
python3 - <<'PY'
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path("tests").resolve()))
|
|
from ha_api_drift import HOMEASSISTANT_PYTHON_FLOOR as FLOOR # noqa: E402
|
|
|
|
needed = ".".join(str(part) for part in FLOOR)
|
|
if sys.version_info < FLOOR:
|
|
print(
|
|
f"Home Assistant needs Python >= {needed}, but this job runs "
|
|
f"{sys.version.split()[0]}."
|
|
)
|
|
print(f"Fix: raise actions/setup-python in this job to a >= {needed} build.")
|
|
raise SystemExit(1)
|
|
print(f"interpreter ok: Python {sys.version.split()[0]} satisfies >= {needed}")
|
|
PY
|
|
|
|
- name: Install Home Assistant + test tooling
|
|
run: |
|
|
set -euo pipefail
|
|
# Same PEP 668 dance as the test job (see the note there). The
|
|
# version floor tracks what we validate against; a resolver failure
|
|
# here is a real signal (Python too old), not something to swallow.
|
|
PKGS="pytest~=9.1 pytest-asyncio~=1.4 'homeassistant>=2026.10'"
|
|
if ! eval python3 -m pip install --quiet $PKGS; then
|
|
echo "system Python is externally managed; retrying with --break-system-packages"
|
|
eval python3 -m pip install --quiet --break-system-packages $PKGS
|
|
fi
|
|
|
|
- name: HA API-drift sweep
|
|
run: python3 -m pytest tests/test_ha_api_drift.py -q
|