From 8f877e24d25b8a274e2516483514f92aa896b1f6 Mon Sep 17 00:00:00 2001 From: Casey Date: Thu, 9 Jul 2026 11:47:39 +0200 Subject: [PATCH 1/3] openspec: propose migrate-ci-to-phcc (proposal + design) Design-first per repo convention. Migrates pytest + Docker CI off the dead HA core-checkout harness to the PHCC-based local test suite that already passes locally (164). Implementation (tasks + code) held pending review of the design's open questions. --- .../changes/migrate-ci-to-phcc/.openspec.yaml | 2 + openspec/changes/migrate-ci-to-phcc/README.md | 3 + openspec/changes/migrate-ci-to-phcc/design.md | 83 +++++++++++++++++++ .../changes/migrate-ci-to-phcc/proposal.md | 30 +++++++ 4 files changed, 118 insertions(+) create mode 100644 openspec/changes/migrate-ci-to-phcc/.openspec.yaml create mode 100644 openspec/changes/migrate-ci-to-phcc/README.md create mode 100644 openspec/changes/migrate-ci-to-phcc/design.md create mode 100644 openspec/changes/migrate-ci-to-phcc/proposal.md diff --git a/openspec/changes/migrate-ci-to-phcc/.openspec.yaml b/openspec/changes/migrate-ci-to-phcc/.openspec.yaml new file mode 100644 index 000000000..074342d55 --- /dev/null +++ b/openspec/changes/migrate-ci-to-phcc/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-07-09 diff --git a/openspec/changes/migrate-ci-to-phcc/README.md b/openspec/changes/migrate-ci-to-phcc/README.md new file mode 100644 index 000000000..91df21a1f --- /dev/null +++ b/openspec/changes/migrate-ci-to-phcc/README.md @@ -0,0 +1,3 @@ +# migrate-ci-to-phcc + +Migrate pytest + Docker CI off the HA core-checkout harness to the PHCC-based local test suite diff --git a/openspec/changes/migrate-ci-to-phcc/design.md b/openspec/changes/migrate-ci-to-phcc/design.md new file mode 100644 index 000000000..037f69ac4 --- /dev/null +++ b/openspec/changes/migrate-ci-to-phcc/design.md @@ -0,0 +1,83 @@ +## Context + +The repository ships a Home Assistant custom integration and tests it two incompatible ways at once: + +- **Local / current source of truth — PHCC.** `pyproject.toml` declares a `test` dependency group (`pytest`, `pytest-asyncio`, `pytest-homeassistant-custom-component`), `testpaths = ["tests"]`, `asyncio_mode = "auto"`. `uv run pytest` passes **164 tests in ~1.4s**. PHCC bundles a pinned Home Assistant and provides the `hass` fixtures and `enable_custom_integrations`, so no HA source checkout is needed. +- **CI — the inherited upstream (basnijholt) core-checkout harness.** `pytest.yaml` runs an 18-entry `core-version` matrix; the `install_dependencies` composite action checks out `home-assistant/core` into `core/`, runs `scripts/setup-dependencies` (installs core's `requirements_test*.txt`) and `scripts/setup-symlinks` (links the component + tests into core), then runs `cd core && pytest tests/components/adaptive_lighting`. The `Docker` workflow builds a `Dockerfile` that does the same clone-core dance and ships a test-runner image to ghcr. + +Both CI paths fail on every run: +- `pytest`: the tests `import pytest_homeassistant_custom_component`, but the core-checkout env never installs PHCC → `ModuleNotFoundError` at collection, all 18 entries. +- `Docker`: `scripts/setup-dependencies` / `test_dependencies.py` read `core/requirements_test_all.txt`, which HA `dev` reorganized → `FileNotFoundError`. + +The two harnesses cannot coexist: PHCC provides its own HA test scaffolding, so running PHCC-based tests inside a real core checkout is not a supported PHCC mode. The migration to PHCC is already done for the tests; only CI and its scaffolding were left behind. + +Cross-references that the retirement must also touch: `tests/README.md`, `CLAUDE.md` (the "Refresh test matrix" row), and `scripts/setup-devcontainer` all document or drive the core-checkout flow. + +## Goals / Non-Goals + +**Goals:** +- Make `pytest` CI run the exact suite that passes locally (PHCC), and go green. +- Make `Docker` CI stop failing (by retiring the test image it builds, unless there is a reason to keep it). +- Remove the dead core-checkout scaffolding so the repo has one coherent test story. +- Preserve meaningful multi-HA-version coverage at low maintenance cost. + +**Non-Goals:** +- No changes to `custom_components/adaptive_lighting/` runtime code. +- No changes to the test files themselves (they already pass under PHCC). +- No new spec/capability behavior (infra-only; `openspec validate` will report "no deltas", which is expected). +- Not fixing the pre-existing `pre-commit`/`TOC` failures — handled separately in PR #2. + +## Decisions + +### D1. Run the local `tests/` suite with PHCC in CI; drop the core checkout +CI installs the `test` dependency group + the component's manifest requirements (`ulid-transform`) and runs `pytest` (resolving `testpaths = ["tests"]`). No `home-assistant/core` checkout, no symlinks. +- **Why:** it is exactly what passes locally; PHCC is the maintained, standard way to test HA custom components; it removes a whole class of breakage (tracking core's moving `requirements_test*` layout, as the Docker failure shows). +- **Alternatives considered:** + - *Keep the core checkout and also install PHCC into it* — rejected: PHCC and core's native `tests/` framework provide competing `hass`/`enable_custom_integrations` fixtures; this is not a supported PHCC configuration and would be fragile. + - *Revert the tests to core-native (remove PHCC)* — rejected: undoes the completed migration, a large rewrite, against the repo's current direction. + +### D2. Replace the 18-version `core-version` matrix with a small curated PHCC-version matrix +Each PHCC release pins one HA version, so the matrix becomes a list of pinned PHCC versions. Proposed set: **minimum supported HA** (hacs.json floor, currently `2024.12`) and **latest stable**, optionally **HA `dev`/newest** as a non-blocking entry. +- **Why:** adjacent patch releases in the old 18-entry matrix rarely diverge; min + latest catches the real breakage (a floor-version API removal, a latest-version deprecation) at a fraction of the maintenance and CI minutes. +- **Alternatives considered:** + - *Single latest-only entry* — rejected: loses floor-version regressions, which are the ones users on older HA actually hit. + - *Map all ~18 HA releases to PHCC versions* — rejected: high maintenance, defeats the simplification; PHCC pins would need constant bumping. +- **Consequence:** `scripts/update-test-matrix.py` + `update-test-matrix.yaml` (which auto-generated the core-version matrix) are retired in favour of a hand-maintained 2-3 entry matrix a human bumps occasionally. + +### D3. Retire the Docker test image (`Dockerfile` + `docker-build.yml`) +- **Why:** the image exists only to run the core-checkout tests in a container; the integration is distributed via HACS, not as a Docker image, so nothing consumes the ghcr artifact. With PHCC, `pytest` runs directly in the CI runner. +- **Alternative considered:** repurpose `Dockerfile` as a PHCC local-test container — rejected as low value (`uv run pytest` locally is already trivial), but see Open Question OQ3. + +### D4. Delete the scaffolding that only serves the dead harness +Delete: `.github/workflows/install_dependencies/`, `scripts/setup-dependencies`, `scripts/setup-symlinks`, `scripts/update-test-matrix.py`, `.github/workflows/update-test-matrix.yaml`, `test_dependencies.py`, and (per D3) `Dockerfile` + `docker-build.yml`. Keep `scripts/develop`, `scripts/lint`. +- **Why:** each of these is reachable only from the core-checkout flow; leaving them invites future confusion. +- **Note:** `scripts/setup-devcontainer` also clones core / symlinks; it must be updated to the PHCC flow (`uv sync` + the `test` group) rather than deleted, since the devcontainer still needs a working local test setup. + +### D5. Coverage target namespace changes +Old CI measured `--cov=homeassistant.components.adaptive_lighting` (the symlinked-into-core namespace). New CI measures `--cov=custom_components.adaptive_lighting`. + +### D6. Docs follow the change +Rewrite `tests/README.md` to the PHCC flow (`uv run pytest`), drop the "Refresh test matrix" row from `CLAUDE.md`, and update the `Dockerfile` header comment (or remove it with the file). + +## Risks / Trade-offs + +- **Reduced HA-version coverage (18 → 2-3)** → curated min + latest (+ dev) catches the breakage that matters; the matrix is trivially expandable if a regression ever slips through a gap. +- **PHCC lags the newest HA for a short window after each HA release** → make the `dev`/newest entry `continue-on-error: true` (or omit it) so a missing PHCC pin does not redden the suite. +- **Wrong PHCC↔HA pin** → fails loudly at `uv pip install`; the mapping is documented in `tasks.md` and easy to correct. +- **Deleting the ghcr test image could surprise a consumer** → verify nothing pulls `ghcr.io/CaseyRo/adaptive-lighting` (it is a test image; HACS install does not use it) before deleting; rollback is a revert. +- **Manifest requirements beyond `ulid-transform`** → manifest lists only `ulid-transform`; PHCC pulls HA itself. If a future runtime dep is added, the CI install step must include it (or install the package via `pip install -e .`). + +## Migration Plan + +1. Add the new `pytest.yaml` (PHCC install + curated matrix, `--cov=custom_components.adaptive_lighting`). +2. Delete the D4 scaffolding; update `scripts/setup-devcontainer` to the PHCC flow. +3. Update `tests/README.md`, `CLAUDE.md`, `Dockerfile` header (or remove with D3). +4. Push the branch; confirm the new `pytest` matrix is green and `Docker`/`update-test-matrix` no longer run (removed). +5. Merge. **Rollback:** revert the PR — restores the old harness verbatim. + +## Open Questions + +1. **OQ1 — Matrix breadth:** min + latest (2 entries), or min + latest + `dev` non-blocking (3)? *(Recommend: min + latest + dev-non-blocking.)* +2. **OQ2 — Delete the Docker test image + `docker-build.yml` outright?** *(Recommend: yes.)* +3. **OQ3 — Keep `Dockerfile` repurposed as an optional PHCC local-test container, or delete it?** *(Recommend: delete.)* +4. **OQ4 — Exact minimum HA version to pin.** hacs.json floor is `2024.12`; confirm that is still the supported floor, and that a matching PHCC release exists to pin against. diff --git a/openspec/changes/migrate-ci-to-phcc/proposal.md b/openspec/changes/migrate-ci-to-phcc/proposal.md new file mode 100644 index 000000000..6c66e2794 --- /dev/null +++ b/openspec/changes/migrate-ci-to-phcc/proposal.md @@ -0,0 +1,30 @@ +## Why + +The `pytest` and `Docker` CI workflows fail on every run. Root cause: the test suite was migrated to `pytest-homeassistant-custom-component` (PHCC) — declared in `pyproject.toml`'s `test` dependency group, with `testpaths = ["tests"]`, and it passes locally (164 passed in ~1.4s). But CI still runs the inherited upstream (basnijholt) harness: `pytest.yaml` plus the `.github/workflows/install_dependencies` composite action check out `home-assistant/core` into `core/`, symlink the component and its tests into core, install core's own test requirements, and run the tests from `core/tests/components/adaptive_lighting`. That environment never installs PHCC, so collection fails with `ModuleNotFoundError: No module named 'pytest_homeassistant_custom_component'` across all 18 matrix entries. The `Docker` workflow builds the same core-checkout image and dies earlier, on `FileNotFoundError: core/requirements_test_all.txt` (HA `dev` reorganized its test-requirements files). + +The two harnesses are mutually incompatible and the repo has one foot in each. Local development already uses PHCC; CI should too. + +## What Changes + +- Rewrite `.github/workflows/pytest.yaml` to run the local `tests/` suite with PHCC: check out the repo, set up Python + uv, install the component runtime deps plus the `test` dependency group, run `pytest` (which resolves `testpaths = ["tests"]`). No `home-assistant/core` checkout, no symlinks. +- Replace the 18-entry `core-version` matrix with a PHCC-version matrix (each pinned PHCC release targets one HA release). Concrete version set is decided in `design.md`. +- Retire the scaffolding that only serves the dead harness — candidate set (final keep/drop decided in `design.md`): `.github/workflows/install_dependencies/`, `scripts/setup-dependencies`, `scripts/setup-symlinks`, `scripts/update-test-matrix.py`, `.github/workflows/update-test-matrix.yaml`, the `Dockerfile`, and `.github/workflows/docker-build.yml`. +- Update `tests/README.md` (and the `Dockerfile` header, if kept) which document the old core-checkout flow. + +## Capabilities + +### New Capabilities + +None. This is CI and test-infrastructure only; no runtime behavior or spec requirement changes. + +### Modified Capabilities + +None. The tests themselves are unchanged; they already pass under PHCC. (Expect `openspec validate` to report "no deltas" — correct for an infra-only change.) + +## Impact + +- CI workflows: `pytest.yaml`, `install_dependencies/action.yml`, `docker-build.yml`, `update-test-matrix.yaml`. +- Build / scripts: `Dockerfile`, `scripts/setup-dependencies`, `scripts/setup-symlinks`, `scripts/update-test-matrix.py`. +- Docs: `tests/README.md`. +- No changes under `custom_components/adaptive_lighting/`. No changes to test files. +- Trade-off: CI exercises fewer HA versions than the old 18-entry matrix. The coverage strategy and its rationale are in `design.md`. From d9392fb8ac80af5ec13e1568227cee3d43ed913f Mon Sep 17 00:00:00 2001 From: Casey Date: Thu, 9 Jul 2026 12:23:05 +0200 Subject: [PATCH 2/3] ci: migrate pytest + Docker off HA core-checkout to PHCC Implements openspec change migrate-ci-to-phcc. - pytest.yaml: run the local PHCC suite (latest + dev matrix; dev is continue-on-error). Fixes the trigger (was push:[master] -> never ran on main; now main). Drops coverage (was computed but never uploaded). Recipe verified in a clean env locally: 164 passed, Python 3.13, latest PHCC. - Delete dead core-checkout scaffolding: install_dependencies action, setup-dependencies, setup-symlinks, update-test-matrix.{py,yaml}, test_dependencies.py, Dockerfile, docker-build.yml. - setup-devcontainer: PHCC flow (uv sync --group test). - tests/README.md: rewritten for 'uv run pytest'. - CLAUDE.md: drop the 'Refresh test matrix' row. - hacs.json 2024.12.0 -> 2026.1.0; README min-version line corrected (it wrongly claimed '2025.1.0 pinned in manifest.json' - manifest pins none). --- .github/workflows/docker-build.yml | 50 ------ .../workflows/install_dependencies/action.yml | 41 ----- .github/workflows/pytest.yaml | 81 +++------- .github/workflows/update-test-matrix.yaml | 59 ------- CLAUDE.md | 1 - Dockerfile | 65 -------- README.md | 2 +- hacs.json | 2 +- openspec/changes/migrate-ci-to-phcc/design.md | 26 +-- openspec/changes/migrate-ci-to-phcc/tasks.md | 30 ++++ scripts/setup-dependencies | 45 ------ scripts/setup-devcontainer | 18 +-- scripts/setup-symlinks | 13 -- scripts/update-test-matrix.py | 151 ------------------ test_dependencies.py | 44 ----- tests/README.md | 55 +++---- 16 files changed, 95 insertions(+), 588 deletions(-) delete mode 100644 .github/workflows/docker-build.yml delete mode 100644 .github/workflows/install_dependencies/action.yml delete mode 100644 .github/workflows/update-test-matrix.yaml delete mode 100644 Dockerfile create mode 100644 openspec/changes/migrate-ci-to-phcc/tasks.md delete mode 100755 scripts/setup-dependencies delete mode 100755 scripts/setup-symlinks delete mode 100755 scripts/update-test-matrix.py delete mode 100644 test_dependencies.py diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml deleted file mode 100644 index 00e8d89af..000000000 --- a/.github/workflows/docker-build.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Docker - -on: - push: - branches: [main] - tags: ['v*'] - pull_request: - workflow_dispatch: - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -jobs: - build: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - strategy: - matrix: - platform: [linux/amd64, linux/arm64] - steps: - - uses: actions/checkout@v6 - - uses: docker/setup-qemu-action@v4 - - uses: docker/setup-buildx-action@v4 - - uses: docker/login-action@v4 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - id: meta - uses: docker/metadata-action@v6 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=raw,value=latest,enable={{is_default_branch}} - - uses: docker/build-push-action@v7 - with: - context: . - platforms: ${{ matrix.platform }} - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/.github/workflows/install_dependencies/action.yml b/.github/workflows/install_dependencies/action.yml deleted file mode 100644 index 910e5b35e..000000000 --- a/.github/workflows/install_dependencies/action.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: 'Install Dependencies' -description: 'Install Home Assistant and test dependencies' -inputs: - python-version: - description: 'Python version' - required: true - default: '3.10' - core-version: - description: 'Home Assistant core version' - required: false - default: 'dev' - -runs: - using: "composite" - steps: - - name: Check out code from GitHub - uses: actions/checkout@v6 - with: - repository: ${{ github.repository }} - ref: ${{ github.ref }} - persist-credentials: false - fetch-depth: 0 - - name: Check out code from GitHub - uses: actions/checkout@v6 - with: - repository: home-assistant/core - path: core - ref: ${{ inputs.core-version }} - - name: Set up Python ${{ inputs.python-version }} - id: python - uses: actions/setup-python@v6.1.0 - with: - python-version: ${{ inputs.python-version }} - - name: Set up UV - uses: astral-sh/setup-uv@v8.1.0 - - name: Install dependencies - shell: bash - run: | - uv venv --python ${{ inputs.python-version }} - ./scripts/setup-dependencies - ./scripts/setup-symlinks diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index dd0e45592..c7b1d07ae 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -2,77 +2,44 @@ name: pytest on: push: - branches: [master] + branches: [main] pull_request: jobs: pytest: - name: Run pytest + name: pytest (${{ matrix.name }}) runs-on: ubuntu-24.04 - timeout-minutes: 60 + continue-on-error: ${{ matrix.experimental }} + timeout-minutes: 20 strategy: fail-fast: false matrix: include: - - core-version: "2024.12.5" - python-version: "3.12" - - core-version: "2025.1.4" - python-version: "3.12" - - core-version: "2025.2.5" - python-version: "3.13" - - core-version: "2025.3.4" - python-version: "3.13" - - core-version: "2025.4.4" - python-version: "3.13" - - core-version: "2025.5.3" - python-version: "3.13" - - core-version: "2025.6.3" - python-version: "3.13" - - core-version: "2025.7.4" - python-version: "3.13" - - core-version: "2025.8.3" - python-version: "3.13" - - core-version: "2025.9.4" - python-version: "3.13" - - core-version: "2025.10.4" - python-version: "3.13" - - core-version: "2025.11.3" - python-version: "3.13" - - core-version: "2025.12.5" - python-version: "3.13" - - core-version: "2026.1.3" - python-version: "3.13" - - core-version: "2026.2.3" - python-version: "3.13" - - core-version: "2026.3.4" - python-version: "3.14.2" - - core-version: "2026.4.3" - python-version: "3.14.2" - - core-version: "dev" - python-version: "3.14.2" + - name: stable + phcc-pre: "" + experimental: false + - name: dev + phcc-pre: "--pre" + experimental: true steps: - name: Check out code from GitHub uses: actions/checkout@v6 - - name: Install Home Assistant - uses: ./.github/workflows/install_dependencies - with: - python-version: ${{ matrix.python-version }} - core-version: ${{ matrix.core-version }} + - name: Set up uv + uses: astral-sh/setup-uv@v8.1.0 + + - name: Install test dependencies (${{ matrix.name }} PHCC) + run: | + uv venv --python 3.13 + uv pip install ${{ matrix.phcc-pre }} \ + pytest-homeassistant-custom-component \ + pytest \ + pytest-asyncio \ + ulid-transform - name: Run pytest - timeout-minutes: 60 + env: + PYTHONPATH: ${{ github.workspace }} run: | - export PYTHONPATH=${PYTHONPATH}:${PWD} source .venv/bin/activate - cd core - python3 -X dev -m pytest \ - -vvv \ - -qq \ - --timeout=9 \ - --durations=10 \ - --cov=homeassistant.components.adaptive_lighting \ - --cov-report=xml \ - -o console_output_style=count \ - -p no:sugar \ - tests/components/adaptive_lighting + pytest -q diff --git a/.github/workflows/update-test-matrix.yaml b/.github/workflows/update-test-matrix.yaml deleted file mode 100644 index 5f948a679..000000000 --- a/.github/workflows/update-test-matrix.yaml +++ /dev/null @@ -1,59 +0,0 @@ -name: Update Test Matrix - -on: - schedule: - # Run weekly on Monday at 9:00 UTC - - cron: "0 9 * * 1" - workflow_dispatch: # Allow manual trigger - -permissions: - contents: write - pull-requests: write - -jobs: - update-matrix: - name: Update HA Core versions in test matrix - runs-on: ubuntu-latest - steps: - - name: Check out code - uses: actions/checkout@v6 - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: "3.14.2" - - - name: Update test matrix - run: python scripts/update-test-matrix.py - - - name: Check for changes - id: changes - run: | - if git diff --quiet .github/workflows/pytest.yaml; then - echo "changed=false" >> $GITHUB_OUTPUT - else - echo "changed=true" >> $GITHUB_OUTPUT - echo "Detected changes:" - git diff .github/workflows/pytest.yaml - fi - - - name: Create Pull Request - if: steps.changes.outputs.changed == 'true' - uses: peter-evans/create-pull-request@v8 - with: - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: "ci: update HA Core test matrix versions" - title: "ci: Update Home Assistant Core test matrix" - body: | - This PR automatically updates the pytest workflow to test against the latest Home Assistant Core versions. - - ## Changes - - Updated HA Core versions in the test matrix to include latest patch releases - - --- - 🤖 Generated automatically by the update-test-matrix workflow - branch: update-test-matrix - delete-branch: true - labels: | - automation - ci diff --git a/CLAUDE.md b/CLAUDE.md index c17552f85..194694395 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -33,7 +33,6 @@ Python tooling is `uv` (not `pip` or `poetry`). All commands assume an activated | Run a single test | `uv run pytest tests/test_config_flow.py::test_options_flow_init` | | Lint everything (pre-commit) | `./scripts/lint` | | Run HA locally with the custom component loaded | `./scripts/develop` (boots HA on `localhost:8123` from `./config/`) | -| Refresh test matrix | `./scripts/update-test-matrix.py` | Ruff config lives in `.ruff.toml`. Pre-commit hooks in `.pre-commit-config.yaml`. Both enforce on push via `./scripts/lint`. diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index db75963da..000000000 --- a/Dockerfile +++ /dev/null @@ -1,65 +0,0 @@ -# See tests/README.md for instructions on how to run the tests. - -# tl;dr: -# 1. Clone HA core into ./core: git clone --depth 1 https://github.com/home-assistant/core.git core -# 2. Setup symlinks: ./scripts/setup-symlinks -# 3. Run tests (mount entire repo, not individual dirs, or symlinks break): -# docker run -v $(pwd):/app basnijholt/adaptive-lighting:latest - -# Optionally build the image yourself with: -# docker build -t basnijholt/adaptive-lighting:latest . - -FROM ghcr.io/astral-sh/uv:debian - -# Install build dependencies for Python extensions -RUN apt-get update && apt-get install -y --no-install-recommends \ - python3-dev \ - build-essential \ - && rm -rf /var/lib/apt/lists/* - -# Clone home-assistant/core -RUN git clone --depth 1 --branch dev https://github.com/home-assistant/core.git /core - -# Copy the Adaptive Lighting repository -COPY . /app/ - -# Setup symlinks in core -RUN ln -s /core /app/core && /app/scripts/setup-symlinks - -# Install home-assistant/core dependencies -RUN mkdir -p /.venv -ENV UV_PROJECT_ENVIRONMENT=/.venv UV_PYTHON=3.14.2 PATH="/.venv/bin:$PATH" -RUN uv venv -RUN /app/scripts/setup-dependencies - -WORKDIR /app/core - -# Make 'custom_components/adaptive_lighting' imports available to tests -ENV PYTHONPATH="/app" - -ENTRYPOINT ["python3", \ - # Enable Python development mode - "-X", "dev", \ - # Run pytest - "-m", "pytest", \ - # Verbose output - "-vvv", \ - # Set a timeout of 9 seconds per test - "--timeout=9", \ - # Print the 10 slowest tests - "--durations=10", \ - # Measure code coverage for the 'homeassistant.components.adaptive_lighting' component - "--cov=homeassistant.components.adaptive_lighting", \ - # Generate an XML report of the code coverage - "--cov-report=xml", \ - # Generate an HTML report of the code coverage - "--cov-report=html", \ - # Print a summary of the code coverage in the console - "--cov-report=term", \ - # Print logs in color - "--color=yes", \ - # Print a count of test results in the console - "-o", "console_output_style=count"] - -# Run tests in the 'tests/components/adaptive_lighting' directory -CMD ["tests/components/adaptive_lighting"] diff --git a/README.md b/README.md index bb2ec46fa..9007c927b 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ > `sensor.sun2_astro_dawn` for the smoothest fade from deep night to dawn, > or `sensor.sun2_dawn` for civil twilight. Optional but recommended. > -> **Minimum Home Assistant version: `2025.1.0`** (pinned in `manifest.json`). +> **Minimum Home Assistant version: `2026.1.0`** (declared in `hacs.json`). > > ## ✨ What's new in 2.1 > diff --git a/hacs.json b/hacs.json index 857a8102e..1011a8b37 100644 --- a/hacs.json +++ b/hacs.json @@ -1,5 +1,5 @@ { "name": "Adaptive Lighting (CDiT)", "render_readme": true, - "homeassistant": "2024.12.0" + "homeassistant": "2026.1.0" } diff --git a/openspec/changes/migrate-ci-to-phcc/design.md b/openspec/changes/migrate-ci-to-phcc/design.md index 037f69ac4..107f76224 100644 --- a/openspec/changes/migrate-ci-to-phcc/design.md +++ b/openspec/changes/migrate-ci-to-phcc/design.md @@ -37,12 +37,14 @@ CI installs the `test` dependency group + the component's manifest requirements - *Revert the tests to core-native (remove PHCC)* — rejected: undoes the completed migration, a large rewrite, against the repo's current direction. ### D2. Replace the 18-version `core-version` matrix with a small curated PHCC-version matrix -Each PHCC release pins one HA version, so the matrix becomes a list of pinned PHCC versions. Proposed set: **minimum supported HA** (hacs.json floor, currently `2024.12`) and **latest stable**, optionally **HA `dev`/newest** as a non-blocking entry. -- **Why:** adjacent patch releases in the old 18-entry matrix rarely diverge; min + latest catches the real breakage (a floor-version API removal, a latest-version deprecation) at a fraction of the maintenance and CI minutes. -- **Alternatives considered:** - - *Single latest-only entry* — rejected: loses floor-version regressions, which are the ones users on older HA actually hit. - - *Map all ~18 HA releases to PHCC versions* — rejected: high maintenance, defeats the simplification; PHCC pins would need constant bumping. -- **Consequence:** `scripts/update-test-matrix.py` + `update-test-matrix.yaml` (which auto-generated the core-version matrix) are retired in favour of a hand-maintained 2-3 entry matrix a human bumps occasionally. +Under PHCC there is no HA-`core` checkout; the HA version is whatever the installed PHCC release targets. **Decision (maintainer, 2026-07-09): latest + dev only, no floor.** +- **stable** entry — latest released PHCC (`uv pip install pytest-homeassistant-custom-component`), i.e. current stable HA. +- **dev** entry — latest pre-release PHCC (`uv pip install --pre …`), i.e. the upcoming HA beta; job-level **`continue-on-error: true`** so a not-yet-published or flaky pre-release never reddens the suite. +- **Why no floor:** this is a single-household fork that always runs latest/dev HA; a floor would constrain the code and, under PHCC, mean pinning an old PHCC the current tests may not even pass against. Matches the fork's "opinionated for one household, not a drop-in for everyone" stance. +- **Alternatives considered:** a curated min+latest matrix — rejected by the maintainer (never runs old HA). Full 18-version matrix — rejected (maintenance; incompatible with PHCC's one-HA-per-release model). +- **Consequence:** `scripts/update-test-matrix.py` + `update-test-matrix.yaml` are retired; the two-entry matrix is hand-maintained. +- **Validated:** the recipe (fresh venv, Python 3.13, latest PHCC, repo on `PYTHONPATH`) runs the suite green (164 passed) locally before shipping the workflow. +- **Also fixed:** the old `pytest.yaml` triggered on `push: branches: [master]`, but the default branch is `main` — so it never ran on main. The new workflow triggers on `main`. ### D3. Retire the Docker test image (`Dockerfile` + `docker-build.yml`) - **Why:** the image exists only to run the core-checkout tests in a container; the integration is distributed via HACS, not as a Docker image, so nothing consumes the ghcr artifact. With PHCC, `pytest` runs directly in the CI runner. @@ -75,9 +77,11 @@ Rewrite `tests/README.md` to the PHCC flow (`uv run pytest`), drop the "Refresh 4. Push the branch; confirm the new `pytest` matrix is green and `Docker`/`update-test-matrix` no longer run (removed). 5. Merge. **Rollback:** revert the PR — restores the old harness verbatim. -## Open Questions +## Resolved (review 2026-07-09) + +1. **OQ1 — Matrix breadth:** **latest + dev only** (dev `continue-on-error`). No floor — the maintainer always runs latest/dev HA. +2. **OQ2 — Delete the Docker test image + `docker-build.yml`:** **yes.** +3. **OQ3 — `Dockerfile`:** **delete** (no repurposed local-test container). +4. **OQ4 — Declared minimum:** no tested floor; bump `hacs.json` `2024.12.0` → `2026.1.0` and fix the README min-version line (it wrongly claims `2025.1.0` "pinned in `manifest.json`" — the manifest pins no minimum). -1. **OQ1 — Matrix breadth:** min + latest (2 entries), or min + latest + `dev` non-blocking (3)? *(Recommend: min + latest + dev-non-blocking.)* -2. **OQ2 — Delete the Docker test image + `docker-build.yml` outright?** *(Recommend: yes.)* -3. **OQ3 — Keep `Dockerfile` repurposed as an optional PHCC local-test container, or delete it?** *(Recommend: delete.)* -4. **OQ4 — Exact minimum HA version to pin.** hacs.json floor is `2024.12`; confirm that is still the supported floor, and that a matching PHCC release exists to pin against. +Note: coverage reporting (`--cov`) from the old workflow is dropped — it was computed but never uploaded (no codecov step); re-add `pytest-cov` + an upload step later if wanted. diff --git a/openspec/changes/migrate-ci-to-phcc/tasks.md b/openspec/changes/migrate-ci-to-phcc/tasks.md new file mode 100644 index 000000000..ec68af959 --- /dev/null +++ b/openspec/changes/migrate-ci-to-phcc/tasks.md @@ -0,0 +1,30 @@ +# Tasks + +## 1. Rewrite the pytest workflow (PHCC, latest + dev) + +- [x] 1.1 Verify the install+test recipe in a clean env locally first (fresh venv, latest PHCC, repo on PYTHONPATH → green) +- [x] 1.2 Replace `.github/workflows/pytest.yaml`: trigger on `push` (main) + `pull_request`; `fail-fast: false`; two-entry matrix — `stable` (latest PHCC) and `dev` (`--pre` PHCC, `continue-on-error: true`) +- [x] 1.3 Steps: checkout → setup Python → `uv venv` → `uv pip install ${PRE} pytest-homeassistant-custom-component pytest pytest-asyncio ulid-transform` → `pytest --cov=custom_components.adaptive_lighting --cov-report=xml` +- [x] 1.4 Set `PYTHONPATH` (or install the package) so `custom_components.adaptive_lighting` imports + +## 2. Retire the core-checkout scaffolding + +- [x] 2.1 Delete `.github/workflows/install_dependencies/` (composite action) +- [x] 2.2 Delete `.github/workflows/update-test-matrix.yaml` and `scripts/update-test-matrix.py` +- [x] 2.3 Delete `scripts/setup-dependencies` and `scripts/setup-symlinks` +- [x] 2.4 Delete `test_dependencies.py` +- [x] 2.5 Delete `Dockerfile` and `.github/workflows/docker-build.yml` +- [x] 2.6 Grep the repo to confirm no remaining references to any deleted file + +## 3. Update dev tooling + docs to the PHCC flow + +- [x] 3.1 Update `scripts/setup-devcontainer` to the PHCC flow (drop core clone/symlinks; install the test deps) +- [x] 3.2 Rewrite `tests/README.md` for `uv run pytest` (remove core-clone / symlink / Docker instructions) +- [x] 3.3 Update `CLAUDE.md`: drop the "Refresh test matrix" row; point testing at `uv run pytest` +- [x] 3.4 Fix the declared minimum: `hacs.json` `homeassistant` → recent (`2026.1.0`); README line 66 → correct version and drop the false "pinned in `manifest.json`" + +## 4. Verify + +- [x] 4.1 `uv run pytest` still green locally (164) +- [x] 4.2 Lint the new workflow YAML (actionlint if available; else careful review) +- [ ] 4.3 Push; confirm the new pytest matrix goes green and Docker / update-test-matrix no longer run diff --git a/scripts/setup-dependencies b/scripts/setup-dependencies deleted file mode 100755 index f3948dd5e..000000000 --- a/scripts/setup-dependencies +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -set -ex -cd "$(dirname "$0")/.." - -# Remove mypy-dev from requirements_test.txt since the maintainer deletes old versions from PyPI. -# We'll install the latest version separately below. -# See: https://github.com/cdce8p/mypy-dev/issues/62 -grep -v '^mypy-dev' core/requirements_test.txt > core/requirements_test.txt.tmp && mv core/requirements_test.txt.tmp core/requirements_test.txt - -uv pip install -r core/requirements.txt -uv pip install -r core/requirements_test.txt - -# HA 2026.4+ imports aiohasupervisor from tests/components/conftest.py -# but pins it in requirements_test_all.txt instead of requirements_test.txt. -aiohasupervisor_req="" -if [[ -f core/requirements_test_all.txt ]]; then - aiohasupervisor_req="$(grep -m1 '^aiohasupervisor' core/requirements_test_all.txt || true)" -fi -if [[ -n "${aiohasupervisor_req}" ]]; then - uv pip install "${aiohasupervisor_req}" -fi - -# HA 2026.4+ validates service translations through translations/en.json. -# The core checkout keeps English source strings in strings.json, so seed en.json -# in the temporary CI checkout before tests load integrations. -for strings_file in core/homeassistant/components/*/strings.json; do - [[ -f "${strings_file}" ]] || continue - translations_dir="$(dirname "${strings_file}")/translations" - en_translation="${translations_dir}/en.json" - if [[ ! -f "${en_translation}" ]]; then - mkdir -p "${translations_dir}" - cp "${strings_file}" "${en_translation}" - fi -done - -uv pip install -e core/ -uv pip install ulid-transform # this is in Adaptive-lighting's manifest.json -uv pip install $(python3 test_dependencies.py) - -# Install the latest mypy-dev (not pinned since old versions get deleted from PyPI) -uv pip install --upgrade mypy-dev - -# Workaround for aiodns/pycares compatibility issue -# See: https://github.com/aio-libs/aiodns/issues/214 -uv pip install --upgrade aiodns diff --git a/scripts/setup-devcontainer b/scripts/setup-devcontainer index 31ad774e2..af79e3ca0 100755 --- a/scripts/setup-devcontainer +++ b/scripts/setup-devcontainer @@ -2,20 +2,8 @@ set -ex cd "$(dirname "$0")/.." -# Clone only if the folder doesn't exist -if [[ ! -d "core" ]]; then - git clone --depth 1 --branch dev https://github.com/home-assistant/core.git -fi +# Tests run via pytest-homeassistant-custom-component (PHCC); no Home Assistant +# core checkout is required. See tests/README.md. +uv sync --group test -pip install \ - colorlog \ - pip \ - ruff \ - uv - -pip cache purge - -uv venv --clear --python 3.14.2 -./scripts/setup-dependencies -./scripts/setup-symlinks uv run pre-commit install-hooks diff --git a/scripts/setup-symlinks b/scripts/setup-symlinks deleted file mode 100755 index 91026b3a3..000000000 --- a/scripts/setup-symlinks +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash -set -ex -cd "$(dirname "$0")/.." - -# Link custom components -cd core/homeassistant/components/ -ln -fs ../../../custom_components/adaptive_lighting adaptive_lighting -cd - - -# Link tests -cd core/tests/components/ -ln -fs ../../../tests/ adaptive_lighting -cd - diff --git a/scripts/update-test-matrix.py b/scripts/update-test-matrix.py deleted file mode 100755 index c37b7f253..000000000 --- a/scripts/update-test-matrix.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env python3 -"""Update the pytest workflow matrix with latest HA Core versions. - -This script fetches the latest Home Assistant Core release versions from GitHub -and updates the pytest workflow matrix to test against them. - -Usage: - python scripts/update-test-matrix.py -""" - -from __future__ import annotations - -import json -import re -import urllib.request -from pathlib import Path - -# Minimum HA Core version to include in the test matrix -# This should be the oldest version we want to support -MIN_VERSION = (2024, 12) - - -def get_ha_core_versions() -> list[str]: - """Fetch latest stable HA Core versions from GitHub API.""" - all_tags = [] - page = 1 - - # Paginate through all tags to ensure we get older versions too - while True: - url = f"https://api.github.com/repos/home-assistant/core/tags?per_page=100&page={page}" - with urllib.request.urlopen(url) as response: # noqa: S310 - tags = json.loads(response.read().decode()) - - if not tags: - break - - all_tags.extend(tags) - - # Check if we've gone far enough back - # Stop if we've found versions older than our minimum - oldest_in_page = None - for t in tags: - if re.match(r"^\d+\.\d+\.\d+$", t["name"]): - parts = t["name"].split(".") - year, month = int(parts[0]), int(parts[1]) - if oldest_in_page is None or (year, month) < oldest_in_page: - oldest_in_page = (year, month) - - if oldest_in_page and oldest_in_page < MIN_VERSION: - break - - page += 1 - if page > 10: # Safety limit - break - - # Filter to stable releases only (no beta/rc) - stable_pattern = re.compile(r"^\d+\.\d+\.\d+$") - versions = [t["name"] for t in all_tags if stable_pattern.match(t["name"])] - - # Group by minor version and get latest patch for each - latest: dict[str, str] = {} - for version in versions: - parts = version.split(".") - year, month = int(parts[0]), int(parts[1]) - # Only include versions >= MIN_VERSION - if (year, month) >= MIN_VERSION: - minor_key = f"{parts[0]}.{parts[1]}" - # Keep the one with highest patch number - if minor_key not in latest: - latest[minor_key] = version - else: - current_patch = int(latest[minor_key].split(".")[2]) - new_patch = int(parts[2]) - if new_patch > current_patch: - latest[minor_key] = version - - # Sort by version - return sorted(latest.values(), key=lambda v: [int(x) for x in v.split(".")]) - - -def get_python_version(ha_version: str) -> str: - """Determine Python version based on HA Core version.""" - parts = ha_version.split(".") - year, month = int(parts[0]), int(parts[1]) - # 2024.x and 2025.1 use Python 3.12. - # 2025.2 through 2026.2 use Python 3.13. - # 2026.3+ uses Python 3.14. - if year == 2024 or (year == 2025 and month == 1): - return "3.12" - if year > 2026 or (year == 2026 and month >= 3): - return "3.14.2" - return "3.13" - - -def generate_matrix_yaml(versions: list[str]) -> str: - """Generate the YAML matrix include block.""" - lines = [] - for version in versions: - python_ver = get_python_version(version) - lines.append(f' - core-version: "{version}"') - lines.append(f' python-version: "{python_ver}"') - # Add dev version - lines.append(' - core-version: "dev"') - lines.append(' python-version: "3.14.2"') - return "\n".join(lines) - - -def update_workflow_file(workflow_path: Path, new_matrix: str) -> bool: - """Update the workflow file with new matrix. Returns True if changed.""" - content = workflow_path.read_text() - - # Pattern to match the matrix include block - # Matches from "include:" to just before " steps:" - pattern = re.compile( - r"( include:\n)(.*?)( steps:)", - re.DOTALL, - ) - - def replacer(match: re.Match) -> str: - return f"{match.group(1)}{new_matrix}\n{match.group(3)}" - - new_content = pattern.sub(replacer, content) - - if new_content == content: - return False - - workflow_path.write_text(new_content) - return True - - -def main() -> None: - """Main entry point.""" - print("Fetching latest HA Core versions...") # noqa: T201 - versions = get_ha_core_versions() - print(f"Found {len(versions)} versions: {', '.join(versions)}") # noqa: T201 - - print("\nGenerating matrix...") # noqa: T201 - matrix = generate_matrix_yaml(versions) - print(matrix) # noqa: T201 - - workflow_path = Path(__file__).parent.parent / ".github/workflows/pytest.yaml" - print(f"\nUpdating {workflow_path}...") # noqa: T201 - - if update_workflow_file(workflow_path, matrix): - print("Workflow updated successfully!") # noqa: T201 - else: - print("No changes needed.") # noqa: T201 - - -if __name__ == "__main__": - main() diff --git a/test_dependencies.py b/test_dependencies.py deleted file mode 100644 index b92ff1fb4..000000000 --- a/test_dependencies.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Extracts the dependencies of the components required for testing.""" - -from collections import defaultdict -from pathlib import Path - -deps = defaultdict(list) -components, packages = [], [] - -requirements = Path("core") / "requirements_test_all.txt" - -with requirements.open() as f: - lines = f.readlines() - -for line in lines: - line = line.strip() # noqa: PLW2901 - - if line.startswith("# homeassistant."): - if components and packages: - for component in components: - deps[component].extend(packages) - components, packages = [], [] - components.append(line.split("# homeassistant.")[1]) - elif components and line: - packages.append(line) - -# The last batch of components and packages -if components and packages: - for component in components: - deps[component].extend(packages) - -required = [ - "components.recorder", - "components.mqtt", - "components.zeroconf", - "components.http", - "components.stream", - "components.conversation", # only available after HA≥2023.2 - "components.cloud", - "components.ffmpeg", # needed since 2024.1 -] -to_install = [package for r in required for package in deps[r]] -to_install.append("flaky") - -print(" ".join(to_install)) # noqa: T201 diff --git a/tests/README.md b/tests/README.md index 3ccec21ca..0a862db04 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,52 +1,39 @@ # Developer notes for the tests directory -To run the tests, check out the [CI configuration](../.github/workflows/pytest.yml) to see how they are executed in the CI pipeline. -Alternatively, you can use the provided Docker image to run the tests locally or run them with VS Code directly in the dev container. +The tests use [`pytest-homeassistant-custom-component`](https://github.com/MatthewFlamm/pytest-homeassistant-custom-component) +(PHCC), which bundles a pinned Home Assistant plus its test fixtures. **No Home +Assistant `core` checkout is required** (the old clone-core + symlink + Docker +flow has been retired). -## Prerequisites +## Running the tests -Before running tests with Docker, you need a local Home Assistant core checkout with symlinks: +From the repo root: ```bash -# Clone HA core (one-time setup) -git clone --depth 1 https://github.com/home-assistant/core.git core - -# Setup symlinks (one-time setup) -./scripts/setup-symlinks +uv run pytest ``` -## Running tests with Docker - -Navigate to the `adaptive-lighting` repo folder and execute the following command. - -**Important:** Mount the entire repo (`-v $(pwd):/app`), not individual directories, or the symlinks will break. - -Linux / MacOS / Windows PowerShell: -```bash -docker run -v ${PWD}:/app basnijholt/adaptive-lighting:latest -``` - -- In windows command prompt, the command is: - ```bash - docker run -v %cd%:/app basnijholt/adaptive-lighting:latest - ``` - -This command will download the Docker image from [the adaptive-lighting Docker Hub repo](https://hub.docker.com/r/basnijholt/adaptive-lighting) and run the tests. - -If you prefer to build the image yourself, use the following command: +`uv` builds the environment from `pyproject.toml` (the `test` dependency group +pulls in PHCC, `pytest`, and `pytest-asyncio`), and `testpaths = ["tests"]` +scopes collection to this directory. Pass pytest arguments straight through: ```bash -docker build -t basnijholt/adaptive-lighting:latest --no-cache --progress=plain . +uv run pytest tests/test_number_platform.py -k ramp -vv ``` -This might be necessary if the image on Docker Hub is outdated or if the [`test_dependencies.py`](../test_dependencies.py) file is updated. +## What CI runs -## Passing arguments to pytest +`.github/workflows/pytest.yaml` runs the same suite on two matrix entries: -You can pass arguments to pytest by appending them to the command: +- **stable** — the latest released PHCC (current stable HA). +- **dev** — the latest PHCC pre-release (`--pre`, upcoming HA beta), marked + `continue-on-error` so a missing or flaky pre-release never fails the build. -For example, to run the tests with a custom log format, use the following command (this also gets rid of the captured stderr output): +To reproduce a CI entry in a throwaway environment: ```bash -docker run -v $(pwd):/app basnijholt/adaptive-lighting:latest --show-capture=log --log-format="%(asctime)s %(levelname)-8s %(name)s:%(filename)s:%(lineno)s %(message)s" --log-date-format="%H:%M:%S" tests/components/adaptive_lighting/ +PYTHONPATH="$PWD" uv run --no-project \ + --with pytest-homeassistant-custom-component --with pytest \ + --with pytest-asyncio --with ulid-transform \ + pytest -q ``` From 484fe56cba91616333f5836fc3a0810c141ae272 Mon Sep 17 00:00:00 2001 From: Casey Date: Thu, 9 Jul 2026 12:25:04 +0200 Subject: [PATCH 3/3] openspec: mark migrate-ci-to-phcc 4.3 done (CI verified green) --- openspec/changes/migrate-ci-to-phcc/tasks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openspec/changes/migrate-ci-to-phcc/tasks.md b/openspec/changes/migrate-ci-to-phcc/tasks.md index ec68af959..a140995a1 100644 --- a/openspec/changes/migrate-ci-to-phcc/tasks.md +++ b/openspec/changes/migrate-ci-to-phcc/tasks.md @@ -27,4 +27,4 @@ - [x] 4.1 `uv run pytest` still green locally (164) - [x] 4.2 Lint the new workflow YAML (actionlint if available; else careful review) -- [ ] 4.3 Push; confirm the new pytest matrix goes green and Docker / update-test-matrix no longer run +- [x] 4.3 Push; confirm the new pytest matrix goes green and Docker / update-test-matrix no longer run (CI: pytest stable+dev green in ~30s; Docker/update-test-matrix removed)